diff --git a/.eslintrc.json b/.eslintrc.json index 06589c1db7..41070379ad 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,7 +3,7 @@ "node": true, "browser": true, "amd": true, - "es6": true + "es2022": true }, "globals": { "p5": true @@ -11,9 +11,9 @@ "root": true, "extends": ["eslint:recommended"], "parserOptions": { - "ecmaVersion": 2017, "sourceType": "module" }, + "ignorePatterns": ["node_modules/**/*", "test/js/*", "lib/**/*", "contributor_docs/**/*"], "rules": { "no-cond-assign": [2, "except-parens"], "eqeqeq": ["error", "smart"], diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index ebf61a1d3f..bd3ca55ba0 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - dev-2.0 pull_request: branches: - '*' diff --git a/.github/workflows/release-workflow.yml b/.github/workflows/release-workflow.yml index 2a3ca4e40d..d407e9ddf6 100644 --- a/.github/workflows/release-workflow.yml +++ b/.github/workflows/release-workflow.yml @@ -41,11 +41,12 @@ jobs: run: npm ci env: CI: true - - name: Run build + - name: Run test run: npm test env: CI: true - - run: rm ./lib/p5-test.js ./lib/p5.pre-min.js + - name: Run build + run: npm run build # 2. Prepare release files - run: mkdir release && mkdir p5 && cp -r ./lib/* p5/ diff --git a/.gitignore b/.gitignore index 88d97958df..42f1e447dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,25 @@ -*.DS_Store -.project -node_modules/* -experiments/* -lib_old/* -lib/p5.* -lib/modules -docs/reference/* -!*.gitkeep -examples/3d/ -.idea -dist/ -p5.zip -bower-repo/ -p5-website/ -.vscode/settings.json -.nyc_output/* -coverage/ -lib/p5-test.js -release/ -parameterData.json -yarn.lock \ No newline at end of file +*.DS_Store +.project +node_modules/* +experiments/* +lib_old/* +lib/p5.* +lib/modules +docs/reference/* +!*.gitkeep +examples/3d/ +.idea +dist/ +p5.zip +bower-repo/ +p5-website/ +.vscode/settings.json +.nyc_output/* +coverage/ +lib/p5-test.js +release/ +yarn.lock +docs/data.json +analyzer/ +preview/ +__screenshots__/ \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index b7c30d3f0b..0000000000 --- a/Gruntfile.js +++ /dev/null @@ -1,345 +0,0 @@ -// these requires allow us to use es6 features such as -// `import`/`export` and `async`/`await` in the Grunt tasks -// we load from other files (`tasks/`) -require('regenerator-runtime/runtime'); -require('@babel/register'); - -module.exports = grunt => { - const connectConfig = open => { - return { - options: { - directory: { - path: './', - options: { - icons: true - } - }, - port: 9001, - open, - middleware: function(connect, options, middlewares) { - middlewares.unshift( - require('connect-modrewrite')([ - '^/assets/js/p5(\\.min)?\\.js(.*) /lib/p5$1.js$2 [L]', - '^/assets/js/p5\\.(sound)(\\.min)?\\.js(.*) /lib/addons/p5.$1$2.js$3 [L]' - ]), - function(req, res, next) { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', '*'); - return next(); - } - ); - return middlewares; - } - } - }; - }; - - const gruntConfig = { - // read in the package, used for knowing the current version, et al. - pkg: grunt.file.readJSON('package.json'), - - // Configure style consistency checking for this file, the source, and the tests. - eslint: { - options: { - format: 'unix' - }, - build: { - src: [ - 'Gruntfile.js', - 'docs/preprocessor.js', - 'utils/**/*.js', - 'tasks/**/*.js' - ] - }, - source: { - src: ['src/**/*.js'] - }, - test: { - src: ['test/**/*.js', '!test/js/*.js'] - }, - fix: { - // src: is calculated below... - options: { - rules: { - 'no-undef': 0, - 'no-unused-vars': 0 - }, - fix: true - } - } - }, - - 'eslint-samples': { - options: { - parserOptions: { - ecmaVersion: 8 - }, - format: 'unix' - }, - source: { - src: ['src/**/*.js'] - }, - fix: { - options: { - fix: true - } - } - }, - - // Set up the watch task, used for live-reloading during development. - // This watches both the codebase and the yuidoc theme. Changing the - // code touches files within the theme, so it will also recompile the - // documentation. - watch: { - quick: { - files: [ - 'src/**/*.js', - 'src/**/*.frag', - 'src/**/*.vert', - 'src/**/*.glsl' - ], - tasks: ['browserify:dev'], - options: { - livereload: true - } - }, - // Watch the codebase for changes - main: { - files: ['src/**/*.js'], - tasks: ['newer:eslint:source', 'test'], - options: { - livereload: true - } - }, - // watch the theme for changes - reference_build: { - files: ['docs/yuidoc-p5-theme/**/*'], - tasks: ['yuidoc'], - options: { - livereload: true, - interrupt: true - } - }, - // Watch the codebase for doc updates - // launch with 'grunt yui connect:yui watch:yui' - yui: { - files: [ - 'src/**/*.js', - 'lib/addons/*.js', - 'src/**/*.frag', - 'src/**/*.vert', - 'src/**/*.glsl' - ], - tasks: [ - 'browserify', - 'browserify:min', - 'yuidoc:prod', - 'clean:reference', - 'minjson', - 'uglify' - ], - options: { - livereload: true - } - } - }, - - // Set up node-side (non-browser) mocha tests. - mochaTest: { - test: { - src: ['test/node/**/*.js'], - options: { - reporter: 'spec', - require: '@babel/register', - ui: 'tdd' - } - } - }, - - // Set up the mocha task, used for running the automated tests. - mochaChrome: { - yui: { - options: { - urls: ['http://localhost:9001/test/test-reference.html'] - } - }, - test: { - options: { - urls: [ - 'http://localhost:9001/test/test.html', - 'http://localhost:9001/test/test-minified.html' - ] - } - } - }, - - nyc: { - report: { - options: { - reporter: ['text-summary', 'html', 'json'] - } - } - }, - babel: { - options: { - presets: ['@babel/preset-env'] - }, - dist: { - files: { - 'lib/p5.pre-min.js': 'lib/p5.js' - } - } - }, - - // This minifies the javascript into a single file and adds a banner to the - // front of the file. - uglify: { - options: { - compress: { - global_defs: { - IS_MINIFIED: true - } - }, - banner: - '/*! p5.js v<%= pkg.version %> <%= grunt.template.today("mmmm dd, yyyy") %> */ ' - }, - dist: { - files: { - 'lib/p5.min.js': ['lib/p5.pre-min.js'], - 'lib/modules/p5Custom.min.js': ['lib/modules/p5Custom.pre-min.js'] - } - } - }, - - // this builds the documentation for the codebase. - yuidoc: { - prod: { - name: '<%= pkg.name %>', - description: '<%= pkg.description %>', - version: '<%= pkg.version %>', - url: '<%= pkg.homepage %>', - options: { - paths: ['src/', 'lib/addons/'], - themedir: 'docs/yuidoc-p5-theme/', - helpers: ['docs/yuidoc-p5-theme/helpers/helpers_prod.js'], - preprocessor: './docs/preprocessor.js', - outdir: 'docs/reference/' - } - } - }, - - clean: { - // Clean up unused files generated by yuidoc - reference: { - src: [ - 'docs/reference/classes/', - 'docs/reference/elements/', - 'docs/reference/files/', - 'docs/reference/modules/', - 'docs/reference/api.js' - ] - } - }, - - // This is a static server which is used when testing connectivity for the - // p5 library. This avoids needing an internet connection to run the tests. - // It serves all the files in the test directory at http://localhost:9001/ - connect: { - server: connectConfig(), - yui: connectConfig('http://127.0.0.1:9001/docs/reference/') - }, - - // This minifies the data.json file created from the inline reference - minjson: { - compile: { - files: { - './docs/reference/data.min.json': './docs/reference/data.json' - } - } - } - }; - - // eslint fixes everything it checks: - gruntConfig.eslint.fix.src = Object.keys(gruntConfig.eslint).reduce( - (acc, key) => { - if (gruntConfig.eslint[key].src) { - acc.push(...gruntConfig.eslint[key].src); - } - return acc; - }, - [] - ); - - /* not yet - gruntConfig['eslint-samples'].fix.src = Object.keys( - gruntConfig['eslint-samples'] - ) - .map(s => gruntConfig['eslint-samples'][s].src) - .reduce((a, b) => a.concat(b), []) - .filter(a => a); - */ - - grunt.initConfig(gruntConfig); - - // Load build tasks. - // This contains the complete build task ("browserify") - // and the task to generate user select modules of p5 - // ("combineModules") which can be invoked directly by - // `grunt combineModules:module_1:module_2` where core - // is included by default in all combinations always. - // NOTE: "module_x" is the name of it's folder in /src. - grunt.loadTasks('tasks/build'); - - // Load tasks for testing - grunt.loadTasks('tasks/test'); - - // Load the external libraries used. - grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-eslint'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-yuidoc'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-minjson'); - grunt.loadNpmTasks('grunt-mocha-test'); - grunt.loadNpmTasks('grunt-newer'); - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-simple-nyc'); - - // Create the multitasks. - grunt.registerTask('build', [ - 'yui', - 'browserify', - 'browserify:min', - 'uglify', - 'browserify:test' - ]); - grunt.registerTask('lint', ['lint:source', 'lint:samples']); - grunt.registerTask('lint:source', [ - 'eslint:build', - 'eslint:source', - 'eslint:test' - ]); - grunt.registerTask('lint:samples', [ - 'yui', // required for eslint-samples - 'eslint-samples:source' - ]); - grunt.registerTask('lint-fix', ['eslint:fix']); - grunt.registerTask('test', [ - 'build', - 'connect:server', - 'mochaChrome', - 'mochaTest', - 'nyc:report' - ]); - grunt.registerTask('test:nobuild', [ - 'eslint:test', - 'connect:server', - 'mochaChrome', - 'mochaTest', - 'nyc:report' - ]); - grunt.registerTask('yui', ['yuidoc:prod', 'clean:reference', 'minjson']); - grunt.registerTask('yui:test', ['yui', 'connect:yui', 'mochaChrome:yui']); - grunt.registerTask('yui:dev', ['yui', 'build', 'connect:yui', 'watch:yui']); - grunt.registerTask('default', ['lint', 'test']); -}; diff --git a/docs/converted.json b/docs/converted.json new file mode 100644 index 0000000000..c5b5010368 --- /dev/null +++ b/docs/converted.json @@ -0,0 +1,27999 @@ +{ + "project": {}, + "files": {}, + "modules": { + "Environment": { + "name": "Environment", + "submodules": { + "Environment": 1 + }, + "classes": {} + }, + "Color": { + "name": "Color", + "submodules": { + "Color Conversion": 1, + "Creating & Reading": 1, + "Setting": 1 + }, + "classes": {} + }, + "Constants": { + "name": "Constants", + "submodules": { + "Constants": 1 + }, + "classes": {} + }, + "Structure": { + "name": "Structure", + "submodules": { + "Structure": 1 + }, + "classes": {} + }, + "DOM": { + "name": "DOM", + "submodules": { + "DOM": 1 + }, + "classes": {} + }, + "Rendering": { + "name": "Rendering", + "submodules": { + "Rendering": 1 + }, + "classes": {} + }, + "Foundation": { + "name": "Foundation", + "submodules": { + "Foundation": 1 + }, + "classes": {} + }, + "Shape": { + "name": "Shape", + "submodules": { + "2D Primitives": 1, + "Attributes": 1, + "Curves": 1, + "Vertex": 1, + "3D Primitives": 1, + "3D Models": 1 + }, + "classes": {} + }, + "Transform": { + "name": "Transform", + "submodules": { + "Transform": 1 + }, + "classes": {} + }, + "Data": { + "name": "Data", + "submodules": { + "LocalStorage": 1, + "Dictionary": 1, + "Array Functions": 1, + "Conversion": 1, + "String Functions": 1 + }, + "classes": {} + }, + "Events": { + "name": "Events", + "submodules": { + "Acceleration": 1, + "Keyboard": 1, + "Mouse": 1, + "Touch": 1 + }, + "classes": {} + }, + "Image": { + "name": "Image", + "submodules": { + "Image": 1, + "Loading & Displaying": 1, + "Pixels": 1 + }, + "classes": {} + }, + "IO": { + "name": "IO", + "submodules": { + "Input": 1, + "Output": 1, + "Table": 1, + "Time & Date": 1 + }, + "classes": {} + }, + "Math": { + "name": "Math", + "submodules": { + "Calculation": 1, + "Vector": 1, + "Noise": 1, + "Random": 1, + "Trigonometry": 1 + }, + "classes": {} + }, + "Typography": { + "name": "Typography", + "submodules": { + "Attributes": 1, + "Loading & Displaying": 1 + }, + "classes": {} + }, + "3D": { + "name": "3D", + "submodules": { + "Interaction": 1, + "Lights": 1, + "Material": 1, + "Camera": 1 + }, + "classes": {} + }, + "Color Conversion": { + "name": "Color Conversion", + "module": "Color", + "is_submodule": 1 + }, + "Creating & Reading": { + "name": "Creating & Reading", + "module": "Color", + "is_submodule": 1 + }, + "Setting": { + "name": "Setting", + "module": "Color", + "is_submodule": 1 + }, + "2D Primitives": { + "name": "2D Primitives", + "module": "Shape", + "is_submodule": 1 + }, + "Attributes": { + "name": "Attributes", + "module": "Shape", + "is_submodule": 1 + }, + "Curves": { + "name": "Curves", + "module": "Shape", + "is_submodule": 1 + }, + "Vertex": { + "name": "Vertex", + "module": "Shape", + "is_submodule": 1 + }, + "3D Primitives": { + "name": "3D Primitives", + "module": "Shape", + "is_submodule": 1 + }, + "3D Models": { + "name": "3D Models", + "module": "Shape", + "is_submodule": 1 + }, + "LocalStorage": { + "name": "LocalStorage", + "module": "Data", + "is_submodule": 1 + }, + "Dictionary": { + "name": "Dictionary", + "module": "Data", + "is_submodule": 1 + }, + "Array Functions": { + "name": "Array Functions", + "module": "Data", + "is_submodule": 1 + }, + "Conversion": { + "name": "Conversion", + "module": "Data", + "is_submodule": 1 + }, + "String Functions": { + "name": "String Functions", + "module": "Data", + "is_submodule": 1 + }, + "Acceleration": { + "name": "Acceleration", + "module": "Events", + "is_submodule": 1 + }, + "Keyboard": { + "name": "Keyboard", + "module": "Events", + "is_submodule": 1 + }, + "Mouse": { + "name": "Mouse", + "module": "Events", + "is_submodule": 1 + }, + "Touch": { + "name": "Touch", + "module": "Events", + "is_submodule": 1 + }, + "Loading & Displaying": { + "name": "Loading & Displaying", + "module": "Image", + "is_submodule": 1 + }, + "Pixels": { + "name": "Pixels", + "module": "Image", + "is_submodule": 1 + }, + "Input": { + "name": "Input", + "module": "IO", + "is_submodule": 1 + }, + "Output": { + "name": "Output", + "module": "IO", + "is_submodule": 1 + }, + "Table": { + "name": "Table", + "module": "IO", + "is_submodule": 1 + }, + "Time & Date": { + "name": "Time & Date", + "module": "IO", + "is_submodule": 1 + }, + "Calculation": { + "name": "Calculation", + "module": "Math", + "is_submodule": 1 + }, + "Vector": { + "name": "Vector", + "module": "Math", + "is_submodule": 1 + }, + "Noise": { + "name": "Noise", + "module": "Math", + "is_submodule": 1 + }, + "Random": { + "name": "Random", + "module": "Math", + "is_submodule": 1 + }, + "Trigonometry": { + "name": "Trigonometry", + "module": "Math", + "is_submodule": 1 + }, + "Interaction": { + "name": "Interaction", + "module": "3D", + "is_submodule": 1 + }, + "Lights": { + "name": "Lights", + "module": "3D", + "is_submodule": 1 + }, + "Material": { + "name": "Material", + "module": "3D", + "is_submodule": 1 + }, + "Camera": { + "name": "Camera", + "module": "3D", + "is_submodule": 1 + } + }, + "classes": { + "p5": { + "name": "p5", + "file": "src/core/main.js", + "line": 32, + "description": "

This is the p5 instance constructor.

\n

A p5 instance holds all the properties and methods related to\na p5 sketch. It expects an incoming sketch closure and it can also\ntake an optional node parameter for attaching the generated p5 canvas\nto a node. The sketch closure takes the newly created p5 instance as\nits sole argument and may optionally set preload(),\nsetup(), and/or\ndraw() properties on it for running a sketch.

\n

A p5 sketch can run in \"global\" or \"instance\" mode:\n\"global\" - all properties and methods are attached to the window\n\"instance\" - all properties and methods are bound to this p5 object

\n", + "example": [], + "params": [ + { + "name": "sketch", + "description": "a closure that can set optional preload(),\nsetup(), and/or draw() properties on the\ngiven p5 instance" + }, + { + "name": "node", + "description": "element to attach canvas to", + "optional": 1, + "type": "HTMLElement" + } + ], + "return": { + "description": "a p5 instance", + "type": "p5" + }, + "is_constructor": 1, + "module": "Structure", + "submodule": "Structure" + }, + "p5.Color": { + "name": "p5.Color", + "file": "src/color/p5.Color.js", + "line": 343, + "description": "

A class to describe a color. Each p5.Color object stores the color mode\nand level maxes that were active during its construction. These values are\nused to interpret the arguments passed to the object's constructor. They\nalso determine output formatting such as when\nsaturation() is called.

\n

Color is stored internally as an array of ideal RGBA values in floating\npoint form, normalized from 0 to 1. These values are used to calculate the\nclosest screen colors, which are RGBA levels from 0 to 255. Screen colors\nare sent to the renderer.

\n

When different color representations are calculated, the results are cached\nfor performance. These values are normalized, floating-point numbers.

\n

color() is the recommended way to create an instance\nof this class.

\n", + "example": [], + "params": [ + { + "name": "pInst", + "description": "pointer to p5 instance.", + "optional": 1, + "type": "p5" + }, + { + "name": "vals", + "description": "an array containing the color values\nfor red, green, blue and alpha channel\nor CSS color.", + "type": "Number[]|String" + } + ], + "is_constructor": 1, + "module": "Color", + "submodule": "Creating & Reading" + }, + "FetchResources": { + "name": "FetchResources", + "file": "src/core/internationalization.js", + "line": 30, + "description": "This is our i18next \"backend\" plugin. It tries to fetch languages\nfrom a CDN.", + "example": [], + "params": [], + "is_constructor": 1 + }, + "p5.Element": { + "name": "p5.Element", + "file": "src/core/p5.Element.js", + "line": 53, + "description": "

A class to describe an\nHTML element.\nSketches can use many elements. Common elements include the drawing canvas,\nbuttons, sliders, webcam feeds, and so on.

\n

All elements share the methods of the p5.Element class. They're created\nwith functions such as createCanvas() and\ncreateButton().

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100);\n\n background(200);\n\n // Create a button element and\n // place it beneath the canvas.\n let btn = createButton('change');\n btn.position(0, 100);\n\n // Call randomColor() when\n // the button is pressed.\n btn.mousePressed(randomColor);\n\n describe('A gray square with a button that says \"change\" beneath it. The square changes color when the user presses the button.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
" + ], + "params": [ + { + "name": "elt", + "description": "wrapped DOM element.", + "type": "HTMLElement" + }, + { + "name": "pInst", + "description": "pointer to p5 instance.", + "optional": 1, + "type": "p5" + } + ], + "is_constructor": 1, + "module": "DOM", + "submodule": "DOM" + }, + "p5.Graphics": { + "name": "p5.Graphics", + "file": "src/core/p5.Graphics.js", + "line": 25, + "extends": "p5.Element", + "description": "Thin wrapper around a renderer, to be used for creating a\ngraphics buffer object. Use this class if you need\nto draw into an off-screen graphics buffer. The two parameters define the\nwidth and height in pixels. The fields and methods for this class are\nextensive, but mirror the normal drawing API for p5.", + "example": [], + "params": [ + { + "name": "w", + "description": "width", + "type": "Number" + }, + { + "name": "h", + "description": "height", + "type": "Number" + }, + { + "name": "renderer", + "description": "the renderer to use, either P2D or WEBGL", + "type": "Constant" + }, + { + "name": "pInst", + "description": "pointer to p5 instance", + "optional": 1, + "type": "p5" + }, + { + "name": "canvas", + "description": "existing html canvas element", + "optional": 1, + "type": "HTMLCanvasElement" + } + ], + "is_constructor": 1, + "module": "Rendering", + "submodule": "Rendering" + }, + "p5.Renderer": { + "name": "p5.Renderer", + "file": "src/core/p5.Renderer.js", + "line": 21, + "extends": "p5.Element", + "description": "Main graphics and rendering context, as well as the base API\nimplementation for p5.js \"core\". To be used as the superclass for\nRenderer2D and Renderer3D classes, respectively.", + "example": [], + "params": [ + { + "name": "elt", + "description": "DOM node that is wrapped", + "type": "HTMLElement" + }, + { + "name": "pInst", + "description": "pointer to p5 instance", + "optional": 1, + "type": "p5" + }, + { + "name": "isMainCanvas", + "description": "whether we're using it as main canvas", + "optional": 1, + "type": "Boolean" + } + ], + "is_constructor": 1, + "module": "Rendering", + "submodule": "Rendering" + }, + "p5.TypedDict": { + "name": "p5.TypedDict", + "file": "src/data/p5.TypedDict.js", + "line": 89, + "description": "Base class for all p5.Dictionary types. Specifically\ntyped Dictionary classes inherit from this class.", + "example": [], + "params": [], + "is_constructor": 1, + "module": "Data", + "submodule": "Dictionary" + }, + "p5.StringDict": { + "name": "p5.StringDict", + "file": "src/data/p5.TypedDict.js", + "line": 384, + "extends": "p5.TypedDict", + "description": "A simple Dictionary class for Strings.", + "example": [], + "params": [], + "is_constructor": 1, + "module": "Data", + "submodule": "Dictionary" + }, + "p5.NumberDict": { + "name": "p5.NumberDict", + "file": "src/data/p5.TypedDict.js", + "line": 402, + "extends": "p5.TypedDict", + "description": "A simple Dictionary class for Numbers.", + "example": [], + "params": [], + "is_constructor": 1, + "module": "Data", + "submodule": "Dictionary" + }, + "p5.MediaElement": { + "name": "p5.MediaElement", + "file": "src/dom/dom.js", + "line": 3676, + "extends": "p5.Element", + "description": "

A class to handle audio and video.

\n

p5.MediaElement extends p5.Element with\nmethods to handle audio and video. p5.MediaElement objects are created by\ncalling createVideo,\ncreateAudio, and\ncreateCapture.

\n", + "example": [ + "
\n\nlet capture;\n\nfunction setup() {\n createCanvas(100, 100);\n\n // Create a p5.MediaElement using createCapture().\n capture = createCapture(VIDEO);\n capture.hide();\n}\n\nfunction draw() {\n // Display the video stream and invert the colors.\n image(capture, 0, 0, width, width * capture.height / capture.width);\n filter(INVERT);\n}\n\n
" + ], + "params": [ + { + "name": "elt", + "description": "DOM node that is wrapped", + "type": "String" + } + ], + "is_constructor": 1, + "module": "DOM", + "submodule": "DOM" + }, + "p5.File": { + "name": "p5.File", + "file": "src/dom/dom.js", + "line": 4958, + "description": "

A class to describe a file.

\n

p5.File objects are used by\nmyElement.drop() and\ncreated by\ncreateFileInput.

\n", + "example": [ + "
\n\n// Use the file input to load a\n// file and display its info.\n\nfunction setup() {\n background(200);\n\n // Create a file input and place it beneath\n // the canvas. Call displayInfo() when\n // the file loads.\n let input = createFileInput(displayInfo);\n input.position(0, 100);\n\n describe('A gray square with a file input beneath it. If the user loads a file, its info is written in black.');\n}\n\n// Display the p5.File's info\n// once it loads.\nfunction displayInfo(file) {\n background(200);\n\n // Display the p5.File's name.\n text(file.name, 10, 10, 80, 40);\n // Display the p5.File's type and subtype.\n text(`${file.type}/${file.subtype}`, 10, 70);\n // Display the p5.File's size in bytes.\n text(file.size, 10, 90);\n}\n\n
\n\n
\n\n// Use the file input to select an image to\n// load and display.\nlet img;\n\nfunction setup() {\n // Create a file input and place it beneath\n // the canvas. Call handleImage() when\n // the file image loads.\n let input = createFileInput(handleImage);\n input.position(0, 100);\n\n describe('A gray square with a file input beneath it. If the user selects an image file to load, it is displayed on the square.');\n}\n\nfunction draw() {\n background(200);\n\n // Draw the image if it's ready.\n if (img) {\n image(img, 0, 0, width, height);\n }\n}\n\n// Use the p5.File's data once\n// it loads.\nfunction handleImage(file) {\n // Check the p5.File's type.\n if (file.type === 'image') {\n // Create an image using using\n // the p5.File's data.\n img = createImg(file.data, '');\n\n // Hide the image element so it\n // doesn't appear twice.\n img.hide();\n } else {\n img = null;\n }\n}\n\n
" + ], + "params": [ + { + "name": "file", + "description": "wrapped file.", + "type": "File" + } + ], + "is_constructor": 1, + "module": "DOM", + "submodule": "DOM" + }, + "p5.Image": { + "name": "p5.Image", + "file": "src/image/p5.Image.js", + "line": 87, + "description": "

A class to describe an image. Images are rectangular grids of pixels that\ncan be displayed and modified.

\n

Existing images can be loaded by calling\nloadImage(). Blank images can be created by\ncalling createImage(). p5.Image objects\nhave methods for common tasks such as applying filters and modifying\npixel values.

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n\n describe('An image of a brick wall.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n img.filter(GRAY);\n image(img, 0, 0);\n\n describe('A grayscale image of a brick wall.');\n}\n\n
\n\n
\n\nbackground(200);\nlet img = createImage(66, 66);\nimg.loadPixels();\nfor (let x = 0; x < img.width; x += 1) {\n for (let y = 0; y < img.height; y += 1) {\n img.set(x, y, 0);\n }\n}\nimg.updatePixels();\nimage(img, 17, 17);\n\ndescribe('A black square drawn in the middle of a gray square.');\n\n
" + ], + "params": [ + { + "name": "width", + "type": "Number" + }, + { + "name": "height", + "type": "Number" + } + ], + "is_constructor": 1, + "module": "Image", + "submodule": "Image" + }, + "p5.Table": { + "name": "p5.Table", + "file": "src/io/p5.Table.js", + "line": 41, + "description": "Table objects store data with multiple rows and columns, much\nlike in a traditional spreadsheet. Tables can be generated from\nscratch, dynamically, or using data from an existing file.", + "example": [], + "params": [ + { + "name": "rows", + "description": "An array of p5.TableRow objects", + "optional": 1, + "type": "p5.TableRow[]" + } + ], + "is_constructor": 1, + "module": "IO", + "submodule": "Table" + }, + "p5.TableRow": { + "name": "p5.TableRow", + "file": "src/io/p5.TableRow.js", + "line": 23, + "description": "

A TableRow object represents a single row of data values,\nstored in columns, from a table.

\n

A Table Row contains both an ordered array, and an unordered\nJSON object.

\n", + "example": [], + "params": [ + { + "name": "str", + "description": "optional: populate the row with a\nstring of values, separated by the\nseparator", + "optional": 1, + "type": "String" + }, + { + "name": "separator", + "description": "comma separated values (csv) by default", + "optional": 1, + "type": "String" + } + ], + "is_constructor": 1, + "module": "IO", + "submodule": "Table" + }, + "p5.XML": { + "name": "p5.XML", + "file": "src/io/p5.XML.js", + "line": 51, + "description": "XML is a representation of an XML object, able to parse XML code. Use\nloadXML() to load external XML files and create XML objects.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let children = xml.getChildren('animal');\n\n for (let i = 0; i < children.length; i++) {\n let id = children[i].getNum('id');\n let coloring = children[i].getString('species');\n let name = children[i].getContent();\n print(id + ', ' + coloring + ', ' + name);\n }\n\n describe('no image displayed');\n}\n\n// Sketch prints:\n// 0, Capra hircus, Goat\n// 1, Panthera pardus, Leopard\n// 2, Equus zebra, Zebra\n
" + ], + "params": [], + "is_constructor": 1, + "module": "IO", + "submodule": "Input" + }, + "p5.Vector": { + "name": "p5.Vector", + "file": "src/math/p5.Vector.js", + "line": 94, + "description": "

A class to describe a two or three-dimensional vector. A vector is like an\narrow pointing in space. Vectors have both magnitude (length) and\ndirection.

\n

p5.Vector objects are often used to program motion because they simplify\nthe math. For example, a moving ball has a position and a velocity.\nPosition describes where the ball is in space. The ball's position vector\nextends from the origin to the ball's center. Velocity describes the ball's\nspeed and the direction it's moving. If the ball is moving straight up, its\nvelocity vector points straight up. Adding the ball's velocity vector to\nits position vector moves it, as in pos.add(vel). Vector math relies on\nmethods inside the p5.Vector class.

\n", + "example": [ + "
\n\nlet p1 = createVector(25, 25);\nlet p2 = createVector(75, 75);\n\nstrokeWeight(5);\npoint(p1);\npoint(p2.x, p2.y);\n\ndescribe('Two black dots on a gray square, one at the top left and the other at the bottom right.');\n\n
\n\n
\n\nlet pos;\nlet vel;\n\nfunction setup() {\n createCanvas(100, 100);\n pos = createVector(width / 2, height);\n vel = createVector(0, -1);\n}\n\nfunction draw() {\n background(200);\n\n pos.add(vel);\n\n if (pos.y < 0) {\n pos.y = height;\n }\n\n strokeWeight(5);\n point(pos);\n\n describe('A black dot moves from bottom to top on a gray square. The dot reappears at the bottom when it reaches the top.');\n}\n\n
" + ], + "params": [ + { + "name": "x", + "description": "x component of the vector.", + "optional": 1, + "type": "Number" + }, + { + "name": "y", + "description": "y component of the vector.", + "optional": 1, + "type": "Number" + }, + { + "name": "z", + "description": "z component of the vector.", + "optional": 1, + "type": "Number" + } + ], + "is_constructor": 1, + "module": "Math", + "submodule": "Vector" + }, + "p5.Font": { + "name": "p5.Font", + "file": "src/typography/p5.Font.js", + "line": 38, + "description": "A class to describe fonts.", + "example": [ + "
\n\nlet font;\n\nfunction preload() {\n // Creates a p5.Font object.\n font = loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n fill('deeppink');\n textFont(font);\n textSize(36);\n text('p5*js', 10, 50);\n\n describe('The text \"p5*js\" written in pink on a white background.');\n}\n\n
" + ], + "params": [ + { + "name": "pInst", + "description": "pointer to p5 instance.", + "optional": 1, + "type": "p5" + } + ], + "is_constructor": 1, + "module": "Typography", + "submodule": "Loading & Displaying" + }, + "p5.Camera": { + "name": "p5.Camera", + "file": "src/webgl/p5.Camera.js", + "line": 485, + "description": "

This class describes a camera for use in p5's\n\nWebGL mode. It contains camera position, orientation, and projection\ninformation necessary for rendering a 3D scene.

\n

New p5.Camera objects can be made through the\ncreateCamera() function and controlled through\nthe methods described below. A camera created in this way will use a default\nposition in the scene and a default perspective projection until these\nproperties are changed through the various methods available. It is possible\nto create multiple cameras, in which case the current camera\ncan be set through the setCamera() method.

\n

Note:\nThe methods below operate in two coordinate systems: the 'world' coordinate\nsystem describe positions in terms of their relationship to the origin along\nthe X, Y and Z axes whereas the camera's 'local' coordinate system\ndescribes positions from the camera's point of view: left-right, up-down,\nand forward-backward. The move() method,\nfor instance, moves the camera along its own axes, whereas the\nsetPosition()\nmethod sets the camera's position in world-space.

\n

The camera object properties\neyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ\nwhich describes camera position, orientation, and projection\nare also accessible via the camera object generated using\ncreateCamera()

\n", + "example": [ + "
\n\nlet cam;\nlet delta = 0.01;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n normalMaterial();\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n // set initial pan angle\n cam.pan(-0.8);\n describe(\n 'camera view pans left and right across a series of rotating 3D boxes.'\n );\n}\n\nfunction draw() {\n background(200);\n\n // pan camera according to angle 'delta'\n cam.pan(delta);\n\n // every 160 frames, switch direction\n if (frameCount % 160 === 0) {\n delta *= -1;\n }\n\n rotateX(frameCount * 0.01);\n translate(-100, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n}\n\n
" + ], + "alt": "camera view pans left and right across a series of rotating 3D boxes.", + "params": [ + { + "name": "rendererGL", + "description": "instance of WebGL renderer", + "type": "rendererGL" + } + ], + "is_constructor": 1, + "module": "3D", + "submodule": "Camera" + }, + "p5.Framebuffer": { + "name": "p5.Framebuffer", + "file": "src/webgl/p5.Framebuffer.js", + "line": 80, + "description": "An object that one can draw to and then read as a texture. While similar\nto a p5.Graphics, using a p5.Framebuffer as a texture will generally run\nmuch faster, as it lives within the same WebGL context as the canvas it\nis created on. It only works in WebGL mode.", + "example": [], + "params": [ + { + "name": "target", + "description": "A p5 global instance or p5.Graphics", + "type": "p5.Graphics|p5" + }, + { + "name": "settings", + "description": "A settings object", + "optional": 1, + "type": "Object" + } + ], + "is_constructor": 1, + "module": "Rendering" + }, + "p5.Geometry": { + "name": "p5.Geometry", + "file": "src/webgl/p5.Geometry.js", + "line": 21, + "description": "p5 Geometry class", + "example": [], + "params": [ + { + "name": "detailX", + "description": "number of vertices along the x-axis.", + "optional": 1, + "type": "Integer" + }, + { + "name": "detailY", + "description": "number of vertices along the y-axis.", + "optional": 1, + "type": "Integer" + }, + { + "name": "callback", + "description": "function to call upon object instantiation.", + "optional": 1, + "type": "function" + } + ], + "is_constructor": 1, + "module": "Shape", + "submodule": "3D Primitives" + }, + "p5.Shader": { + "name": "p5.Shader", + "file": "src/webgl/p5.Shader.js", + "line": 19, + "description": "Shader class for WEBGL Mode", + "example": [], + "params": [ + { + "name": "renderer", + "description": "an instance of p5.RendererGL that\nwill provide the GL context for this new p5.Shader", + "type": "p5.RendererGL" + }, + { + "name": "vertSrc", + "description": "source code for the vertex shader (as a string)", + "type": "String" + }, + { + "name": "fragSrc", + "description": "source code for the fragment shader (as a string)", + "type": "String" + } + ], + "is_constructor": 1, + "module": "3D", + "submodule": "Material" + } + }, + "classitems": [ + { + "itemtype": "property", + "name": "namedColors", + "file": "src/color/p5.Color.js", + "line": 17, + "description": "CSS named colors.", + "module": "Color", + "submodule": "Creating & Reading", + "class": "p5" + }, + { + "itemtype": "property", + "name": "WHITESPACE", + "file": "src/color/p5.Color.js", + "line": 176, + "description": "

These regular expressions are used to build up the patterns for matching\nviable CSS color strings: fragmenting the regexes in this way increases the\nlegibility and comprehensibility of the code.

\n

Note that RGB values of .9 are not parsed by IE, but are supported here for\ncolor string consistency.

\n", + "module": "Color", + "submodule": "Creating & Reading", + "class": "p5" + }, + { + "itemtype": "property", + "name": "colorPatterns", + "file": "src/color/p5.Color.js", + "line": 184, + "description": "Full color string patterns. The capture groups are necessary.", + "module": "Color", + "submodule": "Creating & Reading", + "class": "p5" + }, + { + "itemtype": "property", + "name": "VERSION", + "file": "src/core/constants.js", + "line": 14, + "type": "string", + "description": "Version of this p5.js.", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "P2D", + "file": "src/core/constants.js", + "line": 23, + "description": "The default, two-dimensional renderer.", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "WEBGL", + "file": "src/core/constants.js", + "line": 44, + "description": "

One of the two render modes in p5.js, used for computationally intensive tasks like 3D rendering and shaders.

\n

WEBGL differs from the default P2D renderer in the following ways:

\n

To learn more about WEBGL mode, check out all the interactive WEBGL tutorials in the \"Learn\" section of this website, or read the wiki article \"Getting started with WebGL in p5\".

\n", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "WEBGL2", + "file": "src/core/constants.js", + "line": 52, + "description": "One of the two possible values of a WebGL canvas (either WEBGL or WEBGL2),\nwhich can be used to determine what capabilities the rendering environment\nhas.", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "ARROW", + "file": "src/core/constants.js", + "line": 59, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "CROSS", + "file": "src/core/constants.js", + "line": 64, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "HAND", + "file": "src/core/constants.js", + "line": 69, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "MOVE", + "file": "src/core/constants.js", + "line": 74, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "TEXT", + "file": "src/core/constants.js", + "line": 79, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "WAIT", + "file": "src/core/constants.js", + "line": 84, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "HALF_PI", + "file": "src/core/constants.js", + "line": 105, + "description": "HALF_PI is a mathematical constant with the value\n1.57079632679489661923. It is half the ratio of the\ncircumference of a circle to its diameter. It is useful in\ncombination with the trigonometric functions sin() and cos().", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "PI", + "file": "src/core/constants.js", + "line": 123, + "description": "PI is a mathematical constant with the value\n3.14159265358979323846. It is the ratio of the circumference\nof a circle to its diameter. It is useful in combination with\nthe trigonometric functions sin() and cos().", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "QUARTER_PI", + "file": "src/core/constants.js", + "line": 141, + "description": "QUARTER_PI is a mathematical constant with the value 0.7853982.\nIt is one quarter the ratio of the circumference of a circle to\nits diameter. It is useful in combination with the trigonometric\nfunctions sin() and cos().", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "TAU", + "file": "src/core/constants.js", + "line": 159, + "description": "TAU is an alias for TWO_PI, a mathematical constant with the\nvalue 6.28318530717958647693. It is twice the ratio of the\ncircumference of a circle to its diameter. It is useful in\ncombination with the trigonometric functions sin() and cos().", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "TWO_PI", + "file": "src/core/constants.js", + "line": 177, + "description": "TWO_PI is a mathematical constant with the value\n6.28318530717958647693. It is twice the ratio of the\ncircumference of a circle to its diameter. It is useful in\ncombination with the trigonometric functions sin() and cos().", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "DEGREES", + "file": "src/core/constants.js", + "line": 191, + "description": "Constant to be used with the angleMode() function, to set the mode in\nwhich p5.js interprets and calculates angles (either DEGREES or RADIANS).", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "RADIANS", + "file": "src/core/constants.js", + "line": 205, + "description": "Constant to be used with the angleMode() function, to set the mode\nin which p5.js interprets and calculates angles (either RADIANS or DEGREES).", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "CORNER", + "file": "src/core/constants.js", + "line": 214, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "CORNERS", + "file": "src/core/constants.js", + "line": 219, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "RADIUS", + "file": "src/core/constants.js", + "line": 224, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "RIGHT", + "file": "src/core/constants.js", + "line": 229, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "LEFT", + "file": "src/core/constants.js", + "line": 234, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "CENTER", + "file": "src/core/constants.js", + "line": 239, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "TOP", + "file": "src/core/constants.js", + "line": 244, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "BOTTOM", + "file": "src/core/constants.js", + "line": 249, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "BASELINE", + "file": "src/core/constants.js", + "line": 255, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "POINTS", + "file": "src/core/constants.js", + "line": 261, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "LINES", + "file": "src/core/constants.js", + "line": 267, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "LINE_STRIP", + "file": "src/core/constants.js", + "line": 273, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "LINE_LOOP", + "file": "src/core/constants.js", + "line": 279, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "TRIANGLES", + "file": "src/core/constants.js", + "line": 285, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "TRIANGLE_FAN", + "file": "src/core/constants.js", + "line": 291, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "TRIANGLE_STRIP", + "file": "src/core/constants.js", + "line": 297, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "QUADS", + "file": "src/core/constants.js", + "line": 302, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "QUAD_STRIP", + "file": "src/core/constants.js", + "line": 308, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "TESS", + "file": "src/core/constants.js", + "line": 314, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "CLOSE", + "file": "src/core/constants.js", + "line": 319, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "OPEN", + "file": "src/core/constants.js", + "line": 324, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "CHORD", + "file": "src/core/constants.js", + "line": 329, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "PIE", + "file": "src/core/constants.js", + "line": 334, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "PROJECT", + "file": "src/core/constants.js", + "line": 340, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "SQUARE", + "file": "src/core/constants.js", + "line": 346, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "ROUND", + "file": "src/core/constants.js", + "line": 351, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "BEVEL", + "file": "src/core/constants.js", + "line": 356, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "MITER", + "file": "src/core/constants.js", + "line": 361, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "RGB", + "file": "src/core/constants.js", + "line": 368, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "HSB", + "file": "src/core/constants.js", + "line": 377, + "type": "string", + "description": "HSB (hue, saturation, brightness) is a type of color model.\nYou can learn more about it at\nHSB.", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "HSL", + "file": "src/core/constants.js", + "line": 382, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "AUTO", + "file": "src/core/constants.js", + "line": 393, + "type": "string", + "description": "AUTO allows us to automatically set the width or height of an element (but not both),\nbased on the current height and width of the element. Only one parameter can\nbe passed to the size function as AUTO, at a time.", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "ALT", + "file": "src/core/constants.js", + "line": 400, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "BACKSPACE", + "file": "src/core/constants.js", + "line": 405, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "CONTROL", + "file": "src/core/constants.js", + "line": 410, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "DELETE", + "file": "src/core/constants.js", + "line": 415, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "DOWN_ARROW", + "file": "src/core/constants.js", + "line": 420, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "ENTER", + "file": "src/core/constants.js", + "line": 425, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "ESCAPE", + "file": "src/core/constants.js", + "line": 430, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "LEFT_ARROW", + "file": "src/core/constants.js", + "line": 435, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "OPTION", + "file": "src/core/constants.js", + "line": 440, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "RETURN", + "file": "src/core/constants.js", + "line": 445, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "RIGHT_ARROW", + "file": "src/core/constants.js", + "line": 450, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "SHIFT", + "file": "src/core/constants.js", + "line": 455, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "TAB", + "file": "src/core/constants.js", + "line": 460, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "UP_ARROW", + "file": "src/core/constants.js", + "line": 465, + "type": "number", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "BLEND", + "file": "src/core/constants.js", + "line": 473, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "REMOVE", + "file": "src/core/constants.js", + "line": 479, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "ADD", + "file": "src/core/constants.js", + "line": 485, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "DARKEST", + "file": "src/core/constants.js", + "line": 492, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "LIGHTEST", + "file": "src/core/constants.js", + "line": 498, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "DIFFERENCE", + "file": "src/core/constants.js", + "line": 503, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "SUBTRACT", + "file": "src/core/constants.js", + "line": 508, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "EXCLUSION", + "file": "src/core/constants.js", + "line": 513, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "MULTIPLY", + "file": "src/core/constants.js", + "line": 518, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "SCREEN", + "file": "src/core/constants.js", + "line": 523, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "REPLACE", + "file": "src/core/constants.js", + "line": 529, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "OVERLAY", + "file": "src/core/constants.js", + "line": 534, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "HARD_LIGHT", + "file": "src/core/constants.js", + "line": 539, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "SOFT_LIGHT", + "file": "src/core/constants.js", + "line": 544, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "DODGE", + "file": "src/core/constants.js", + "line": 550, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "BURN", + "file": "src/core/constants.js", + "line": 556, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "THRESHOLD", + "file": "src/core/constants.js", + "line": 563, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "GRAY", + "file": "src/core/constants.js", + "line": 568, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "OPAQUE", + "file": "src/core/constants.js", + "line": 573, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "INVERT", + "file": "src/core/constants.js", + "line": 578, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "POSTERIZE", + "file": "src/core/constants.js", + "line": 583, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "DILATE", + "file": "src/core/constants.js", + "line": 588, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "ERODE", + "file": "src/core/constants.js", + "line": 593, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "BLUR", + "file": "src/core/constants.js", + "line": 598, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "NORMAL", + "file": "src/core/constants.js", + "line": 605, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "ITALIC", + "file": "src/core/constants.js", + "line": 610, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "BOLD", + "file": "src/core/constants.js", + "line": 615, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "BOLDITALIC", + "file": "src/core/constants.js", + "line": 620, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "CHAR", + "file": "src/core/constants.js", + "line": 625, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "WORD", + "file": "src/core/constants.js", + "line": 630, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "LINEAR", + "file": "src/core/constants.js", + "line": 642, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "QUADRATIC", + "file": "src/core/constants.js", + "line": 647, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "BEZIER", + "file": "src/core/constants.js", + "line": 652, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "CURVE", + "file": "src/core/constants.js", + "line": 657, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "STROKE", + "file": "src/core/constants.js", + "line": 664, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "FILL", + "file": "src/core/constants.js", + "line": 669, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "TEXTURE", + "file": "src/core/constants.js", + "line": 674, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "IMMEDIATE", + "file": "src/core/constants.js", + "line": 679, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "IMAGE", + "file": "src/core/constants.js", + "line": 687, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "NEAREST", + "file": "src/core/constants.js", + "line": 695, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "REPEAT", + "file": "src/core/constants.js", + "line": 700, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "CLAMP", + "file": "src/core/constants.js", + "line": 705, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "MIRROR", + "file": "src/core/constants.js", + "line": 710, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "FLAT", + "file": "src/core/constants.js", + "line": 717, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "SMOOTH", + "file": "src/core/constants.js", + "line": 722, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "LANDSCAPE", + "file": "src/core/constants.js", + "line": 729, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "PORTRAIT", + "file": "src/core/constants.js", + "line": 734, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "GRID", + "file": "src/core/constants.js", + "line": 744, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "AXES", + "file": "src/core/constants.js", + "line": 750, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "LABEL", + "file": "src/core/constants.js", + "line": 756, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "FALLBACK", + "file": "src/core/constants.js", + "line": 761, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "CONTAIN", + "file": "src/core/constants.js", + "line": 767, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "COVER", + "file": "src/core/constants.js", + "line": 773, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "UNSIGNED_BYTE", + "file": "src/core/constants.js", + "line": 779, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "UNSIGNED_INT", + "file": "src/core/constants.js", + "line": 785, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "FLOAT", + "file": "src/core/constants.js", + "line": 791, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "HALF_FLOAT", + "file": "src/core/constants.js", + "line": 797, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "RGBA", + "file": "src/core/constants.js", + "line": 803, + "type": "string", + "description": "", + "module": "Constants", + "submodule": "Constants", + "class": "p5" + }, + { + "itemtype": "property", + "name": "initialize", + "file": "src/core/internationalization.js", + "line": 125, + "description": "Set up our translation function, with loaded languages", + "class": "p5" + }, + { + "itemtype": "property", + "name": "availableTranslatorLanguages", + "file": "src/core/internationalization.js", + "line": 170, + "description": "Returns a list of languages we have translations loaded for", + "class": "p5" + }, + { + "itemtype": "property", + "name": "currentTranslatorLanguage", + "file": "src/core/internationalization.js", + "line": 177, + "description": "Returns the current language selected for translation", + "class": "p5" + }, + { + "itemtype": "property", + "name": "setTranslatorLanguage", + "file": "src/core/internationalization.js", + "line": 186, + "description": "Sets the current language for translation\nReturns a promise that resolved when loading is finished,\nor rejects if it fails.", + "class": "p5" + }, + { + "itemtype": "property", + "name": "languages", + "file": "s", + "line": 24, + "description": "This is a list of languages that we have added so far.\nIf you have just added a new language (yay!), add its key to the list below\n(en is english, es es español). Also add its export to\ndev.js, which is another file in this folder.", + "class": "p5" + }, + { + "itemtype": "property", + "name": "styleEmpty", + "file": "src/core/p5.Renderer2D.js", + "line": 11, + "type": "string", + "description": "p5.Renderer2D\nThe 2D graphics canvas renderer class.\nextends p5.Renderer", + "class": "p5" + }, + { + "itemtype": "property", + "name": "Filters", + "file": "src/image/filters.js", + "line": 16, + "description": "

This module defines the filters for use with image buffers.

\n

This module is basically a collection of functions stored in an object\nas opposed to modules. The functions are destructive, modifying\nthe passed in canvas rather than creating a copy.

\n

Generally speaking users of this module will use the Filters.apply method\non a canvas to create an effect.

\n

A number of functions are borrowed/adapted from\nhttp://www.html5rocks.com/en/tutorials/canvas/imagefilters/\nor the java processing implementation.

\n", + "class": "p5" + }, + { + "itemtype": "property", + "name": "frameCount", + "file": "src/core/environment.js", + "line": 117, + "type": "Integer", + "module": "Environment", + "submodule": "Environment", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Display the value of\n // frameCount.\n textSize(30);\n textAlign(CENTER, CENTER);\n text(frameCount, 50, 50);\n\n describe('The number 0 written in black in the middle of a gray square.');\n}\n\n
\n\n
\n\nfunction setup() {\n // Set the frameRate to 30.\n frameRate(30);\n\n textSize(30);\n textAlign(CENTER, CENTER);\n}\n\nfunction draw() {\n background(200);\n\n // Display the value of\n // frameCount.\n text(frameCount, 50, 50);\n\n describe('A number written in black in the middle of a gray square. Its value increases rapidly.');\n}\n\n
" + ], + "description": "

Tracks the number of frames drawn since the sketch started.

\n

frameCount's value is 0 inside setup(). It\nincrements by 1 each time the code in draw()\nfinishes executing.

\n" + }, + { + "itemtype": "property", + "name": "deltaTime", + "file": "src/core/environment.js", + "line": 162, + "type": "Integer", + "module": "Environment", + "submodule": "Environment", + "class": "p5", + "example": [ + "
\n\nlet x = 0;\nlet speed = 0.05;\n\nfunction setup() {\n // Set the frameRate to 30.\n frameRate(30);\n}\n\nfunction draw() {\n background(200);\n\n // Use deltaTime to calculate\n // a change in position.\n let deltaX = speed * deltaTime;\n\n // Update the x variable.\n x += deltaX;\n\n // Reset x to 0 if it's\n // greater than 100.\n if (x > 100) {\n x = 0;\n }\n\n // Use x to set the circle's\n // position.\n circle(x, 50, 20);\n\n describe('A white circle moves from left to right on a gray background. It reappears on the left side when it reaches the right side.');\n}\n\n
" + ], + "description": "Tracks the amount of time, in milliseconds, it took for\ndraw to draw the previous frame. deltaTime is\nuseful for simulating physics." + }, + { + "itemtype": "property", + "name": "focused", + "file": "src/core/environment.js", + "line": 191, + "type": "Boolean", + "module": "Environment", + "submodule": "Environment", + "class": "p5", + "example": [ + "
\n\n// Open this example in two separate browser\n// windows placed side-by-side to demonstrate.\n\nfunction draw() {\n // Change the background color\n // when the browser window\n // goes in/out of focus.\n if (focused === true) {\n background(0, 255, 0);\n } else {\n background(255, 0, 0);\n }\n\n describe('A square changes color from green to red when the browser window is out of focus.');\n}\n\n
" + ], + "description": "Tracks whether the browser window is focused and can receive user input.\nfocused is true if the window if focused and false if not." + }, + { + "itemtype": "property", + "name": "webglVersion", + "file": "src/core/environment.js", + "line": 545, + "type": "String", + "module": "Environment", + "submodule": "Environment", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Display the current WebGL version.\n text(webglVersion, 42, 54);\n\n describe('The text \"p2d\" written in black on a gray background.');\n}\n\n
\n\n
\n\nlet font;\n\nfunction preload() {\n // Load a font to use.\n font = loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n // Create a canvas using WEBGL mode.\n createCanvas(100, 50, WEBGL);\n background(200);\n\n // Display the current WebGL version.\n fill(0);\n textFont(font);\n text(webglVersion, -15, 5);\n\n describe('The text \"webgl2\" written in black on a gray background.');\n}\n\n
\n\n
\n\nlet font;\n\nfunction preload() {\n // Load a font to use.\n font = loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n // Create a canvas using WEBGL mode.\n createCanvas(100, 50, WEBGL);\n\n // Set WebGL to version 1.\n setAttributes({ version: 1 });\n\n background(200);\n\n // Display the current WebGL version.\n fill(0);\n textFont(font);\n text(webglVersion, -14, 5);\n\n describe('The text \"webgl\" written in black on a gray background.');\n}\n\n
" + ], + "description": "

A string variable with the WebGL version in use. Its value equals one of\nthe followin string constants:

\n

See setAttributes() for ways to set the\nWebGL version.

\n" + }, + { + "itemtype": "property", + "name": "displayWidth", + "file": "src/core/environment.js", + "line": 575, + "type": "Number", + "module": "Environment", + "submodule": "Environment", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n // Set the canvas' width and height\n // using the display's dimensions.\n createCanvas(displayWidth, displayHeight);\n\n background(200);\n\n describe('A gray canvas that is the same size as the display.');\n}\n\n
" + ], + "alt": "This example does not render anything.", + "description": "

A numeric variable that stores the width of the screen display. Its value\ndepends on the current pixelDensity().\ndisplayWidth is useful for running full-screen programs.

\n

Note: The actual screen width can be computed as\ndisplayWidth * pixelDensity().

\n" + }, + { + "itemtype": "property", + "name": "displayHeight", + "file": "src/core/environment.js", + "line": 605, + "type": "Number", + "module": "Environment", + "submodule": "Environment", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n // Set the canvas' width and height\n // using the display's dimensions.\n createCanvas(displayWidth, displayHeight);\n\n background(200);\n\n describe('A gray canvas that is the same size as the display.');\n}\n\n
" + ], + "alt": "This example does not render anything.", + "description": "

A numeric variable that stores the height of the screen display. Its value\ndepends on the current pixelDensity().\ndisplayHeight is useful for running full-screen programs.

\n

Note: The actual screen height can be computed as\ndisplayHeight * pixelDensity().

\n" + }, + { + "itemtype": "property", + "name": "windowWidth", + "file": "src/core/environment.js", + "line": 632, + "type": "Number", + "module": "Environment", + "submodule": "Environment", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n // Set the canvas' width and height\n // using the browser's dimensions.\n createCanvas(windowWidth, windowHeight);\n\n background(200);\n\n describe('A gray canvas that takes up the entire browser window.');\n}\n\n
" + ], + "alt": "This example does not render anything.", + "description": "A numeric variable that stores the width of the browser's\nlayout viewport.\nThis viewport is the area within the browser that's available for drawing." + }, + { + "itemtype": "property", + "name": "windowHeight", + "file": "src/core/environment.js", + "line": 659, + "type": "Number", + "module": "Environment", + "submodule": "Environment", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n // Set the canvas' width and height\n // using the browser's dimensions.\n createCanvas(windowWidth, windowHeight);\n\n background(200);\n\n describe('A gray canvas that takes up the entire browser window.');\n}\n\n
" + ], + "alt": "This example does not render anything.", + "description": "A numeric variable that stores the height of the browser's\nlayout viewport.\nThis viewport is the area within the browser that's available for drawing." + }, + { + "itemtype": "property", + "name": "width", + "file": "src/core/environment.js", + "line": 817, + "type": "Number", + "module": "Environment", + "submodule": "Environment", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Display the canvas' width.\n text(width, 42, 54);\n\n describe('The number 100 written in black on a gray square.');\n}\n\n
\n\n
\n\nfunction setup() {\n createCanvas(50, 100);\n\n background(200);\n\n // Display the canvas' width.\n text(width, 21, 54);\n\n describe('The number 50 written in black on a gray rectangle.');\n}\n\n
\n\n
\n\nfunction setup() {\n createCanvas(100, 100);\n\n background(200);\n\n // Display the canvas' width.\n text(width, 42, 54);\n\n describe('The number 100 written in black on a gray square. When the mouse is pressed, the square becomes a rectangle and the number becomes 50.');\n}\n\n// If the mouse is pressed, reisze\n// the canvas and display its new\n// width.\nfunction mousePressed() {\n if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {\n resizeCanvas(50, 100);\n background(200);\n text(width, 21, 54);\n }\n}\n\n
" + ], + "description": "

A numeric variable that stores the width of the drawing canvas. Its\ndefault value is 100.

\n

Calling createCanvas() or\nresizeCanvas() changes the value of\nwidth. Calling noCanvas() sets its value to\n0.

\n" + }, + { + "itemtype": "property", + "name": "height", + "file": "src/core/environment.js", + "line": 886, + "type": "Number", + "module": "Environment", + "submodule": "Environment", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Display the canvas' height.\n text(height, 42, 54);\n\n describe('The number 100 written in black on a gray square.');\n}\n\n
\n\n
\n\nfunction setup() {\n createCanvas(100, 50);\n\n background(200);\n\n // Display the canvas' height.\n text(height, 42, 27);\n\n describe('The number 50 written in black on a gray rectangle.');\n}\n\n
\n\n
\n\nfunction setup() {\n createCanvas(100, 100);\n\n background(200);\n\n // Display the canvas' height.\n text(height, 42, 54);\n\n describe('The number 100 written in black on a gray square. When the mouse is pressed, the square becomes a rectangle and the number becomes 50.');\n}\n\n// If the mouse is pressed, reisze\n// the canvas and display its new\n// height.\nfunction mousePressed() {\n if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {\n resizeCanvas(100, 50);\n background(200);\n text(height, 42, 27);\n }\n}\n\n
" + ], + "description": "

A numeric variable that stores the height of the drawing canvas. Its\ndefault value is 100.

\n

Calling createCanvas() or\nresizeCanvas() changes the value of\nheight. Calling noCanvas() sets its value to\n0.

\n" + }, + { + "itemtype": "property", + "name": "deviceOrientation", + "file": "src/events/acceleration.js", + "line": 21, + "type": "Constant", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [], + "description": "The system variable deviceOrientation always contains the orientation of\nthe device. The value of this variable will either be set 'landscape'\nor 'portrait'. If no data is available it will be set to 'undefined'.\neither LANDSCAPE or PORTRAIT." + }, + { + "itemtype": "property", + "name": "accelerationX", + "file": "src/events/acceleration.js", + "line": 44, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [ + "
\n\n// Move a touchscreen device to register\n// acceleration changes.\nfunction draw() {\n background(220, 50);\n fill('magenta');\n ellipse(width / 2, height / 2, accelerationX);\n describe('Magnitude of device acceleration is displayed as ellipse size.');\n}\n\n
" + ], + "description": "The system variable accelerationX always contains the acceleration of the\ndevice along the x axis. Value is represented as meters per second squared." + }, + { + "itemtype": "property", + "name": "accelerationY", + "file": "src/events/acceleration.js", + "line": 66, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [ + "
\n\n// Move a touchscreen device to register\n// acceleration changes.\nfunction draw() {\n background(220, 50);\n fill('magenta');\n ellipse(width / 2, height / 2, accelerationY);\n describe('Magnitude of device acceleration is displayed as ellipse size');\n}\n\n
" + ], + "description": "The system variable accelerationY always contains the acceleration of the\ndevice along the y axis. Value is represented as meters per second squared." + }, + { + "itemtype": "property", + "name": "accelerationZ", + "file": "src/events/acceleration.js", + "line": 89, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [ + "
\n\n// Move a touchscreen device to register\n// acceleration changes.\nfunction draw() {\n background(220, 50);\n fill('magenta');\n ellipse(width / 2, height / 2, accelerationZ);\n describe('Magnitude of device acceleration is displayed as ellipse size');\n}\n\n
" + ], + "description": "The system variable accelerationZ always contains the acceleration of the\ndevice along the z axis. Value is represented as meters per second squared." + }, + { + "itemtype": "property", + "name": "pAccelerationX", + "file": "src/events/acceleration.js", + "line": 99, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [], + "description": "The system variable pAccelerationX always contains the acceleration of the\ndevice along the x axis in the frame previous to the current frame. Value\nis represented as meters per second squared." + }, + { + "itemtype": "property", + "name": "pAccelerationY", + "file": "src/events/acceleration.js", + "line": 109, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [], + "description": "The system variable pAccelerationY always contains the acceleration of the\ndevice along the y axis in the frame previous to the current frame. Value\nis represented as meters per second squared." + }, + { + "itemtype": "property", + "name": "pAccelerationZ", + "file": "src/events/acceleration.js", + "line": 119, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [], + "description": "The system variable pAccelerationZ always contains the acceleration of the\ndevice along the z axis in the frame previous to the current frame. Value\nis represented as meters per second squared." + }, + { + "itemtype": "property", + "name": "rotationX", + "file": "src/events/acceleration.js", + "line": 163, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n}\n\nfunction draw() {\n background(200);\n //rotateZ(radians(rotationZ));\n rotateX(radians(rotationX));\n //rotateY(radians(rotationY));\n box(200, 200, 200);\n describe(`red horizontal line right, green vertical line bottom.\n black background.`);\n}\n\n
" + ], + "description": "

The system variable rotationX always contains the rotation of the\ndevice along the x axis. If the sketch \nangleMode() is set to DEGREES, the value will be -180 to 180. If\nit is set to RADIANS, the value will be -PI to PI.

\n

Note: The order the rotations are called is important, ie. if used\ntogether, it must be called in the order Z-X-Y or there might be\nunexpected behaviour.

\n" + }, + { + "itemtype": "property", + "name": "rotationY", + "file": "src/events/acceleration.js", + "line": 196, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n}\n\nfunction draw() {\n background(200);\n //rotateZ(radians(rotationZ));\n //rotateX(radians(rotationX));\n rotateY(radians(rotationY));\n box(200, 200, 200);\n describe(`red horizontal line right, green vertical line bottom.\n black background.`);\n}\n\n
" + ], + "description": "

The system variable rotationY always contains the rotation of the\ndevice along the y axis. If the sketch \nangleMode() is set to DEGREES, the value will be -90 to 90. If\nit is set to RADIANS, the value will be -PI/2 to PI/2.

\n

Note: The order the rotations are called is important, ie. if used\ntogether, it must be called in the order Z-X-Y or there might be\nunexpected behaviour.

\n" + }, + { + "itemtype": "property", + "name": "rotationZ", + "file": "src/events/acceleration.js", + "line": 233, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n}\n\nfunction draw() {\n background(200);\n rotateZ(radians(rotationZ));\n //rotateX(radians(rotationX));\n //rotateY(radians(rotationY));\n box(200, 200, 200);\n describe(`red horizontal line right, green vertical line bottom.\n black background.`);\n}\n\n
" + ], + "description": "

The system variable rotationZ always contains the rotation of the\ndevice along the z axis. If the sketch \nangleMode() is set to DEGREES, the value will be 0 to 360. If\nit is set to RADIANS, the value will be 0 to 2*PI.

\n

Unlike rotationX and rotationY, this variable is available for devices\nwith a built-in compass only.

\n

Note: The order the rotations are called is important, ie. if used\ntogether, it must be called in the order Z-X-Y or there might be\nunexpected behaviour.

\n" + }, + { + "itemtype": "property", + "name": "pRotationX", + "file": "src/events/acceleration.js", + "line": 277, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [ + "
\n\n// A simple if statement looking at whether\n// rotationX - pRotationX < 0 is true or not will be\n// sufficient for determining the rotate direction\n// in most cases.\n\n// Some extra logic is needed to account for cases where\n// the angles wrap around.\nlet rotateDirection = 'clockwise';\n\n// Simple range conversion to make things simpler.\n// This is not absolutely necessary but the logic\n// will be different in that case.\n\nlet rX = rotationX + 180;\nlet pRX = pRotationX + 180;\n\nif ((rX - pRX > 0 && rX - pRX < 270) || rX - pRX < -270) {\n rotateDirection = 'clockwise';\n} else if (rX - pRX < 0 || rX - pRX > 270) {\n rotateDirection = 'counter-clockwise';\n}\n\nprint(rotateDirection);\ndescribe('no image to display.');\n\n
" + ], + "description": "

The system variable pRotationX always contains the rotation of the\ndevice along the x axis in the frame previous to the current frame.\nIf the sketch angleMode() is set to DEGREES,\nthe value will be -180 to 180. If it is set to RADIANS, the value will\nbe -PI to PI.

\n

pRotationX can also be used with rotationX to determine the rotate\ndirection of the device along the X-axis.

\n" + }, + { + "itemtype": "property", + "name": "pRotationY", + "file": "src/events/acceleration.js", + "line": 320, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [ + "
\n\n// A simple if statement looking at whether\n// rotationY - pRotationY < 0 is true or not will be\n// sufficient for determining the rotate direction\n// in most cases.\n\n// Some extra logic is needed to account for cases where\n// the angles wrap around.\nlet rotateDirection = 'clockwise';\n\n// Simple range conversion to make things simpler.\n// This is not absolutely necessary but the logic\n// will be different in that case.\n\nlet rY = rotationY + 180;\nlet pRY = pRotationY + 180;\n\nif ((rY - pRY > 0 && rY - pRY < 270) || rY - pRY < -270) {\n rotateDirection = 'clockwise';\n} else if (rY - pRY < 0 || rY - pRY > 270) {\n rotateDirection = 'counter-clockwise';\n}\nprint(rotateDirection);\ndescribe('no image to display.');\n\n
" + ], + "description": "

The system variable pRotationY always contains the rotation of the\ndevice along the y axis in the frame previous to the current frame.\nIf the sketch angleMode() is set to DEGREES,\nthe value will be -90 to 90. If it is set to RADIANS, the value will\nbe -PI/2 to PI/2.

\n

pRotationY can also be used with rotationY to determine the rotate\ndirection of the device along the Y-axis.

\n" + }, + { + "itemtype": "property", + "name": "pRotationZ", + "file": "src/events/acceleration.js", + "line": 359, + "type": "Number", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [ + "
\n\n// A simple if statement looking at whether\n// rotationZ - pRotationZ < 0 is true or not will be\n// sufficient for determining the rotate direction\n// in most cases.\n\n// Some extra logic is needed to account for cases where\n// the angles wrap around.\nlet rotateDirection = 'clockwise';\n\nif (\n (rotationZ - pRotationZ > 0 && rotationZ - pRotationZ < 270) ||\n rotationZ - pRotationZ < -270\n) {\n rotateDirection = 'clockwise';\n} else if (rotationZ - pRotationZ < 0 || rotationZ - pRotationZ > 270) {\n rotateDirection = 'counter-clockwise';\n}\nprint(rotateDirection);\ndescribe('no image to display.');\n\n
" + ], + "description": "

The system variable pRotationZ always contains the rotation of the\ndevice along the z axis in the frame previous to the current frame.\nIf the sketch angleMode() is set to DEGREES,\nthe value will be 0 to 360. If it is set to RADIANS, the value will\nbe 0 to 2*PI.

\n

pRotationZ can also be used with rotationZ to determine the rotate\ndirection of the device along the Z-axis.

\n" + }, + { + "itemtype": "property", + "name": "turnAxis", + "file": "src/events/acceleration.js", + "line": 413, + "type": "String", + "module": "Events", + "submodule": "Acceleration", + "class": "p5", + "example": [ + "
\n\n// Run this example on a mobile device\n// Rotate the device by 90 degrees in the\n// X-axis to change the value.\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`50-by-50 black rect in center of canvas.\n turns white on mobile when device turns`);\n describe(`50-by-50 black rect in center of canvas.\n turns white on mobile when x-axis turns`);\n}\nfunction deviceTurned() {\n if (turnAxis === 'X') {\n if (value === 0) {\n value = 255;\n } else if (value === 255) {\n value = 0;\n }\n }\n}\n\n
" + ], + "description": "When a device is rotated, the axis that triggers the deviceTurned()\nmethod is stored in the turnAxis variable. The turnAxis variable is only defined within\nthe scope of deviceTurned()." + }, + { + "itemtype": "property", + "name": "keyIsPressed", + "file": "src/events/keyboard.js", + "line": 31, + "type": "Boolean", + "module": "Events", + "submodule": "Keyboard", + "class": "p5", + "example": [ + "
\n\nfunction draw() {\n if (keyIsPressed === true) {\n fill(0);\n } else {\n fill(255);\n }\n rect(25, 25, 50, 50);\n describe('50-by-50 white rect that turns black on keypress.');\n}\n\n
" + ], + "description": "The boolean system variable keyIsPressed is true if any key is pressed\nand false if no keys are pressed." + }, + { + "itemtype": "property", + "name": "key", + "file": "src/events/keyboard.js", + "line": 58, + "type": "String", + "module": "Events", + "submodule": "Keyboard", + "class": "p5", + "example": [ + "
\n// Click any key to display it!\n// (Not Guaranteed to be Case Sensitive)\nfunction setup() {\n fill(245, 123, 158);\n textSize(50);\n}\n\nfunction draw() {\n background(200);\n text(key, 33, 65); // Display last key pressed.\n describe('canvas displays any key value that is pressed in pink font.');\n}\n
" + ], + "description": "The system variable key always contains the value of the most recent\nkey on the keyboard that was typed. To get the proper capitalization, it\nis best to use it within keyTyped(). For non-ASCII keys, use the keyCode\nvariable." + }, + { + "itemtype": "property", + "name": "keyCode", + "file": "src/events/keyboard.js", + "line": 96, + "type": "Integer", + "module": "Events", + "submodule": "Keyboard", + "class": "p5", + "example": [ + "
\nlet fillVal = 126;\nfunction draw() {\n fill(fillVal);\n rect(25, 25, 50, 50);\n describe(`Grey rect center. turns white when up arrow pressed and black when down.\n Display key pressed and its keyCode in a yellow box.`);\n}\n\nfunction keyPressed() {\n if (keyCode === UP_ARROW) {\n fillVal = 255;\n } else if (keyCode === DOWN_ARROW) {\n fillVal = 0;\n }\n}\n
\n
\nfunction draw() {}\nfunction keyPressed() {\n background('yellow');\n text(`${key} ${keyCode}`, 10, 40);\n print(key, ' ', keyCode);\n}\n
" + ], + "description": "The variable keyCode is used to detect special keys such as BACKSPACE,\nDELETE, ENTER, RETURN, TAB, ESCAPE, SHIFT, CONTROL, OPTION, ALT, UP_ARROW,\nDOWN_ARROW, LEFT_ARROW, RIGHT_ARROW.\nYou can also check for custom keys by looking up the keyCode of any key\non a site like this: keycode.info." + }, + { + "itemtype": "property", + "name": "movedX", + "file": "src/events/mouse.js", + "line": 41, + "type": "Number", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\nlet x = 50;\nfunction setup() {\n rectMode(CENTER);\n}\n\nfunction draw() {\n if (x > 48) {\n x -= 2;\n } else if (x < 48) {\n x += 2;\n }\n x += floor(movedX / 5);\n background(237, 34, 93);\n fill(0);\n rect(x, 50, 50, 50);\n describe(`box moves left and right according to mouse movement\n then slowly back towards the center`);\n}\n\n
" + ], + "description": "The variable movedX contains the horizontal movement of the mouse since the last frame" + }, + { + "itemtype": "property", + "name": "movedY", + "file": "src/events/mouse.js", + "line": 71, + "type": "Number", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\nlet y = 50;\nfunction setup() {\n rectMode(CENTER);\n}\n\nfunction draw() {\n if (y > 48) {\n y -= 2;\n } else if (y < 48) {\n y += 2;\n }\n y += floor(movedY / 5);\n background(237, 34, 93);\n fill(0);\n rect(50, y, 50, 50);\n describe(`box moves up and down according to mouse movement then\n slowly back towards the center`);\n}\n\n
" + ], + "description": "The variable movedY contains the vertical movement of the mouse since the last frame" + }, + { + "itemtype": "property", + "name": "mouseX", + "file": "src/events/mouse.js", + "line": 102, + "type": "Number", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\n// Move the mouse across the canvas\nfunction draw() {\n background(244, 248, 252);\n line(mouseX, 0, mouseX, 100);\n describe('horizontal black line moves left and right with mouse x-position');\n}\n\n
" + ], + "description": "The system variable mouseX always contains the current horizontal\nposition of the mouse, relative to (0, 0) of the canvas. The value at\nthe top-left corner is (0, 0) for 2-D and (-width/2, -height/2) for WebGL.\nIf touch is used instead of mouse input, mouseX will hold the x value\nof the most recent touch point." + }, + { + "itemtype": "property", + "name": "mouseY", + "file": "src/events/mouse.js", + "line": 126, + "type": "Number", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\n// Move the mouse across the canvas\nfunction draw() {\n background(244, 248, 252);\n line(0, mouseY, 100, mouseY);\n describe('vertical black line moves up and down with mouse y-position');\n}\n\n
" + ], + "description": "The system variable mouseY always contains the current vertical\nposition of the mouse, relative to (0, 0) of the canvas. The value at\nthe top-left corner is (0, 0) for 2-D and (-width/2, -height/2) for WebGL.\nIf touch is used instead of mouse input, mouseY will hold the y value\nof the most recent touch point." + }, + { + "itemtype": "property", + "name": "pmouseX", + "file": "src/events/mouse.js", + "line": 157, + "type": "Number", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\n// Move the mouse across the canvas to leave a trail\nfunction setup() {\n //slow down the frameRate to make it more visible\n frameRate(10);\n}\n\nfunction draw() {\n background(244, 248, 252);\n line(mouseX, mouseY, pmouseX, pmouseY);\n print(pmouseX + ' -> ' + mouseX);\n describe(`line trail is created from cursor movements.\n faster movement make longer line.`);\n}\n\n
" + ], + "description": "The system variable pmouseX always contains the horizontal position of\nthe mouse or finger in the frame previous to the current frame, relative to\n(0, 0) of the canvas. The value at the top-left corner is (0, 0) for 2-D and\n(-width/2, -height/2) for WebGL. Note: pmouseX will be reset to the current mouseX\nvalue at the start of each touch event." + }, + { + "itemtype": "property", + "name": "pmouseY", + "file": "src/events/mouse.js", + "line": 187, + "type": "Number", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\nfunction draw() {\n background(237, 34, 93);\n fill(0);\n //draw a square only if the mouse is not moving\n if (mouseY === pmouseY && mouseX === pmouseX) {\n rect(20, 20, 60, 60);\n }\n\n print(pmouseY + ' -> ' + mouseY);\n describe(`60-by-60 black rect center, fuchsia background.\n rect flickers on mouse movement`);\n}\n\n
" + ], + "description": "The system variable pmouseY always contains the vertical position of\nthe mouse or finger in the frame previous to the current frame, relative to\n(0, 0) of the canvas. The value at the top-left corner is (0, 0) for 2-D and\n(-width/2, -height/2) for WebGL. Note: pmouseY will be reset to the current mouseY\nvalue at the start of each touch event." + }, + { + "itemtype": "property", + "name": "winMouseX", + "file": "src/events/mouse.js", + "line": 224, + "type": "Number", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\nlet myCanvas;\n\nfunction setup() {\n //use a variable to store a pointer to the canvas\n myCanvas = createCanvas(100, 100);\n let body = document.getElementsByTagName('body')[0];\n myCanvas.parent(body);\n}\n\nfunction draw() {\n background(237, 34, 93);\n fill(0);\n\n //move the canvas to the horizontal mouse position\n //relative to the window\n myCanvas.position(winMouseX + 1, windowHeight / 2);\n\n //the y of the square is relative to the canvas\n rect(20, mouseY, 60, 60);\n describe(`60-by-60 black rect y moves with mouse y and fuchsia\n canvas moves with mouse x`);\n}\n\n
" + ], + "description": "The system variable winMouseX always contains the current horizontal\nposition of the mouse, relative to (0, 0) of the window." + }, + { + "itemtype": "property", + "name": "winMouseY", + "file": "src/events/mouse.js", + "line": 261, + "type": "Number", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\nlet myCanvas;\n\nfunction setup() {\n //use a variable to store a pointer to the canvas\n myCanvas = createCanvas(100, 100);\n let body = document.getElementsByTagName('body')[0];\n myCanvas.parent(body);\n}\n\nfunction draw() {\n background(237, 34, 93);\n fill(0);\n\n //move the canvas to the vertical mouse position\n //relative to the window\n myCanvas.position(windowWidth / 2, winMouseY + 1);\n\n //the x of the square is relative to the canvas\n rect(mouseX, 20, 60, 60);\n describe(`60-by-60 black rect x moves with mouse x and\n fuchsia canvas y moves with mouse y`);\n}\n\n
" + ], + "description": "The system variable winMouseY always contains the current vertical\nposition of the mouse, relative to (0, 0) of the window." + }, + { + "itemtype": "property", + "name": "pwinMouseX", + "file": "src/events/mouse.js", + "line": 300, + "type": "Number", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\nlet myCanvas;\n\nfunction setup() {\n //use a variable to store a pointer to the canvas\n myCanvas = createCanvas(100, 100);\n noStroke();\n fill(237, 34, 93);\n}\n\nfunction draw() {\n clear();\n //the difference between previous and\n //current x position is the horizontal mouse speed\n let speed = abs(winMouseX - pwinMouseX);\n //change the size of the circle\n //according to the horizontal speed\n ellipse(50, 50, 10 + speed * 5, 10 + speed * 5);\n //move the canvas to the mouse position\n myCanvas.position(winMouseX + 1, winMouseY + 1);\n describe(`fuchsia ellipse moves with mouse x and y.\n Grows and shrinks with mouse speed`);\n}\n\n
" + ], + "description": "The system variable pwinMouseX always contains the horizontal position\nof the mouse in the frame previous to the current frame, relative to\n(0, 0) of the window. Note: pwinMouseX will be reset to the current winMouseX\nvalue at the start of each touch event." + }, + { + "itemtype": "property", + "name": "pwinMouseY", + "file": "src/events/mouse.js", + "line": 339, + "type": "Number", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\nlet myCanvas;\n\nfunction setup() {\n //use a variable to store a pointer to the canvas\n myCanvas = createCanvas(100, 100);\n noStroke();\n fill(237, 34, 93);\n}\n\nfunction draw() {\n clear();\n //the difference between previous and\n //current y position is the vertical mouse speed\n let speed = abs(winMouseY - pwinMouseY);\n //change the size of the circle\n //according to the vertical speed\n ellipse(50, 50, 10 + speed * 5, 10 + speed * 5);\n //move the canvas to the mouse position\n myCanvas.position(winMouseX + 1, winMouseY + 1);\n describe(`fuchsia ellipse moves with mouse x and y.\n Grows and shrinks with mouse speed`);\n}\n\n
" + ], + "description": "The system variable pwinMouseY always contains the vertical position of\nthe mouse in the frame previous to the current frame, relative to (0, 0)\nof the window. Note: pwinMouseY will be reset to the current winMouseY\nvalue at the start of each touch event." + }, + { + "itemtype": "property", + "name": "mouseButton", + "file": "src/events/mouse.js", + "line": 376, + "type": "Constant", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\nfunction draw() {\n background(237, 34, 93);\n fill(0);\n\n if (mouseIsPressed === true) {\n if (mouseButton === LEFT) {\n ellipse(50, 50, 50, 50);\n }\n if (mouseButton === RIGHT) {\n rect(25, 25, 50, 50);\n }\n if (mouseButton === CENTER) {\n triangle(23, 75, 50, 20, 78, 75);\n }\n }\n\n print(mouseButton);\n describe(`50-by-50 black ellipse appears on center of fuchsia\n canvas on mouse click/press.`);\n}\n\n
" + ], + "description": "p5 automatically tracks if the mouse button is pressed and which\nbutton is pressed. The value of the system variable mouseButton is either\nLEFT, RIGHT, or CENTER depending on which button was pressed last.\nWarning: different browsers may track mouseButton differently." + }, + { + "itemtype": "property", + "name": "mouseIsPressed", + "file": "src/events/mouse.js", + "line": 405, + "type": "Boolean", + "module": "Events", + "submodule": "Mouse", + "class": "p5", + "example": [ + "
\n\nfunction draw() {\n background(237, 34, 93);\n fill(0);\n\n if (mouseIsPressed === true) {\n ellipse(50, 50, 50, 50);\n } else {\n rect(25, 25, 50, 50);\n }\n\n print(mouseIsPressed);\n describe(`black 50-by-50 rect becomes ellipse with mouse click/press.\n fuchsia background.`);\n}\n\n
" + ], + "description": "The boolean system variable mouseIsPressed is true if the mouse is pressed\nand false if not." + }, + { + "itemtype": "property", + "name": "touches", + "file": "src/events/touch.js", + "line": 38, + "type": "Object[]", + "module": "Events", + "submodule": "Touch", + "class": "p5", + "example": [ + "
\n\n// On a touchscreen device, touch\n// the canvas using one or more fingers\n// at the same time\nfunction draw() {\n clear();\n let display = touches.length + ' touches';\n text(display, 5, 10);\n describe(`Number of touches currently registered are displayed\n on the canvas`);\n}\n\n
" + ], + "description": "

The system variable touches[] contains an array of the positions of all\ncurrent touch points, relative to (0, 0) of the canvas, and IDs identifying a\nunique touch as it moves. Each element in the array is an object with x, y,\nand id properties.

\n

The touches[] array is not supported on Safari and IE on touch-based\ndesktops (laptops).

\n" + }, + { + "itemtype": "property", + "name": "pixels", + "file": "src/image/pixels.js", + "line": 104, + "type": "Number[]", + "module": "Image", + "submodule": "Pixels", + "class": "p5", + "example": [ + "
\n\nloadPixels();\nlet x = 50;\nlet y = 50;\nlet d = pixelDensity();\nfor (let i = 0; i < d; i += 1) {\n for (let j = 0; j < d; j += 1) {\n let index = 4 * ((y * d + j) * width * d + (x * d + i));\n // Red.\n pixels[index] = 0;\n // Green.\n pixels[index + 1] = 0;\n // Blue.\n pixels[index + 2] = 0;\n // Alpha.\n pixels[index + 3] = 255;\n }\n}\nupdatePixels();\n\ndescribe('A black dot in the middle of a gray rectangle.');\n\n
\n\n
\n\nloadPixels();\nlet d = pixelDensity();\nlet halfImage = 4 * (d * width) * (d * height / 2);\nfor (let i = 0; i < halfImage; i += 4) {\n // Red.\n pixels[i] = 255;\n // Green.\n pixels[i + 1] = 0;\n // Blue.\n pixels[i + 2] = 0;\n // Alpha.\n pixels[i + 3] = 255;\n}\nupdatePixels();\n\ndescribe('A red rectangle drawn above a gray rectangle.');\n\n
\n\n
\n\nlet pink = color(255, 102, 204);\nloadPixels();\nlet d = pixelDensity();\nlet halfImage = 4 * (d * width) * (d * height / 2);\nfor (let i = 0; i < halfImage; i += 4) {\n pixels[i] = red(pink);\n pixels[i + 1] = green(pink);\n pixels[i + 2] = blue(pink);\n pixels[i + 3] = alpha(pink);\n}\nupdatePixels();\n\ndescribe('A pink rectangle drawn above a gray rectangle.');\n\n
" + ], + "description": "

An array containing the color of each pixel on the canvas. Colors are\nstored as numbers representing red, green, blue, and alpha (RGBA) values.\npixels is a one-dimensional array for performance reasons.

\n

Each pixel occupies four elements in the pixels array, one for each RGBA\nvalue. For example, the pixel at coordinates (0, 0) stores its RGBA values\nat pixels[0], pixels[1], pixels[2], and pixels[3], respectively.\nThe next pixel at coordinates (1, 0) stores its RGBA values at pixels[4],\npixels[5], pixels[6], and pixels[7]. And so on. The pixels array\nfor a 100×100 canvas has 100 × 100 × 4 = 40,000 elements.

\n

Some displays use several smaller pixels to set the color at a single\npoint. The pixelDensity() function returns\nthe pixel density of the canvas. High density displays often have a\npixelDensity() of 2. On such a display, the\npixels array for a 100×100 canvas has 200 × 200 × 4 =\n160,000 elements.

\n

Accessing the RGBA values for a point on the canvas requires a little math\nas shown below. The loadPixels() function\nmust be called before accessing the pixels array. The\nupdatePixels() function must be called\nafter any changes are made.

\n" + }, + { + "itemtype": "property", + "name": "disableFriendlyErrors", + "file": "src/core/main.js", + "line": 777, + "type": "Boolean", + "module": "Structure", + "submodule": "Structure", + "class": "p5", + "example": [ + "
\np5.disableFriendlyErrors = true;\n\nfunction setup() {\n createCanvas(100, 50);\n}\n
" + ], + "description": "

Turn off some features of the friendly error system (FES), which can give\na significant boost to performance when needed.

\n

Note that this will disable the parts of the FES that cause performance\nslowdown (like argument checking). Friendly errors that have no performance\ncost (like giving a descriptive error if a file load fails, or warning you\nif you try to override p5.js functions in the global space),\nwill remain in place.

\n

See \ndisabling the friendly error system.

\n" + }, + { + "itemtype": "property", + "name": "src", + "file": "src/dom/dom.js", + "line": 4958, + "module": "DOM", + "submodule": "DOM", + "class": "p5", + "example": [ + "
\n\nlet beat;\n\nfunction setup() {\n // Create a p5.MediaElement using createAudio().\n beat = createAudio('assets/beat.mp3');\n\n describe('The text \"https://p5js.org/reference/assets/beat.mp3\" written in black on a gray background.');\n}\n\nfunction draw() {\n background(200);\n\n textWrap(CHAR);\n text(beat.src, 10, 10, 80, 80);\n}\n\n
" + ], + "description": "Path to the media element's source as a string." + }, + { + "itemtype": "property", + "name": "let", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\nlet x = 2;\nconsole.log(x); // prints 2 to the console\nx = 1;\nconsole.log(x); // prints 1 to the console\n\n
" + ], + "alt": "This example does not render anything", + "description": "

Creates and names a new variable. A variable is a container for a value.

\n

Variables that are declared with let will have block-scope.\nThis means that the variable only exists within the\n\nblock that it is created within.

\n

From the MDN entry:\nDeclares a block scope local variable, optionally initializing it to a value.

\n" + }, + { + "itemtype": "property", + "name": "const", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\n// define myFavNumber as a constant and give it the value 7\nconst myFavNumber = 7;\nconsole.log('my favorite number is: ' + myFavNumber);\n\n
\n\n
\n\nconst bigCats = ['lion', 'tiger', 'panther'];\nbigCats.push('leopard');\nconsole.log(bigCats);\n// bigCats = ['cat']; // throws error as re-assigning not allowed for const\n\n
\n\n
\n\nconst wordFrequency = {};\nwordFrequency['hello'] = 2;\nwordFrequency['bye'] = 1;\nconsole.log(wordFrequency);\n// wordFrequency = { 'a': 2, 'b': 3}; // throws error here\n\n
" + ], + "alt": "These examples do not render anything", + "description": "

Creates and names a new constant. Like a variable created with let,\na constant that is created with const is a container for a value,\nhowever constants cannot be reassigned once they are declared. Although it is\nnoteworthy that for non-primitive data types like objects & arrays, their\nelements can still be changeable. So if a variable is assigned an array, you\ncan still add or remove elements from the array but cannot reassign another\narray to it. Also unlike let, you cannot declare variables without value\nusing const.

\n

Constants have block-scope. This means that the constant only exists within\nthe \nblock that it is created within. A constant cannot be redeclared within a scope in which it\nalready exists.

\n

From the MDN entry:\nDeclares a read-only named constant.\nConstants are block-scoped, much like variables defined using the 'let' statement.\nThe value of a constant can't be changed through reassignment, and it can't be redeclared.

\n" + }, + { + "itemtype": "property", + "name": "null", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\nconsole.log(100 <= 100); // prints true to the console\nconsole.log(99 <= 100); // prints true to the console\n\n
" + ], + "alt": "This example does not render anything", + "description": "

The less than or equal to operator <=\nevaluates to true if the left value is less than or equal to\nthe right value.

\n

There is more info on comparison operators on MDN.

\n" + }, + { + "itemtype": "property", + "name": "if-else", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\nlet a = 4;\nif (a > 0) {\n console.log('positive');\n} else {\n console.log('negative');\n}\n\n
" + ], + "alt": "This example does not render anything", + "description": "

The if-else statement helps control the flow of your code.

\n

A condition is placed between the parenthesis following 'if',\nwhen that condition evalues to truthy,\nthe code between the following curly braces is run.\nAlternatively, when the condition evaluates to falsy,\nthe code between the curly braces of 'else' block is run instead. Writing an\nelse block is optional.

\n

From the MDN entry:\nThe 'if' statement executes a statement if a specified condition is truthy.\nIf the condition is falsy, another statement can be executed

\n" + }, + { + "itemtype": "property", + "name": "function", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\nlet myName = 'Hridi';\nfunction sayHello(name) {\n console.log('Hello ' + name + '!');\n}\nsayHello(myName); // calling the function, prints \"Hello Hridi!\" to console.\n\n
\n\n
\n\nlet square = number => number * number;\nconsole.log(square(5));\n\n
" + ], + "alt": "This example does not render anything", + "description": "

Creates and names a function.\nA function is a set of statements that perform a task.

\n

Optionally, functions can have parameters. Parameters\nare variables that are scoped to the function, that can be assigned a value\nwhen calling the function.Multiple parameters can be given by seperating them\nwith commmas.

\n

From the MDN entry:\nDeclares a function with the specified parameters.

\n" + }, + { + "itemtype": "property", + "name": "return", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\nfunction calculateSquare(x) {\n return x * x;\n}\nconst result = calculateSquare(4); // returns 16\nconsole.log(result); // prints '16' to the console\n\n
" + ], + "alt": "This example does not render anything", + "description": "Specifies the value to be returned by a function.\nFor more info checkout \nthe MDN entry for return." + }, + { + "itemtype": "property", + "name": "boolean", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\nlet myBoolean = false;\nconsole.log(typeof myBoolean); // prints 'boolean' to the console\n\n
" + ], + "alt": "This example does not render anything", + "description": "

A boolean is one of the 7 primitive data types in Javascript.\nA boolean can only be true or false.

\n

From the MDN entry:\nBoolean represents a logical entity and can have two values: true, and false.

\n" + }, + { + "itemtype": "property", + "name": "string", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\nlet mood = 'chill';\nconsole.log(typeof mood); // prints 'string' to the console\n\n
" + ], + "alt": "This example does not render anything", + "description": "

A string is one of the 7 primitive data types in Javascript.\nA string is a series of text characters. In Javascript, a string value must\nbe surrounded by either single-quotation marks(') or double-quotation marks(\").

\n

From the MDN entry:\nA string is a sequence of characters used to represent text.

\n" + }, + { + "itemtype": "property", + "name": "number", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\nlet num = 46.5;\nconsole.log(typeof num); // prints 'number' to the console\n\n
" + ], + "alt": "This example does not render anything", + "description": "

A number is one of the 7 primitive data types in Javascript.\nA number can be a whole number or a decimal number.

\n

The MDN entry for number

\n" + }, + { + "itemtype": "property", + "name": "object", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\nlet author = {\n name: 'Ursula K Le Guin',\n books: [\n 'The Left Hand of Darkness',\n 'The Dispossessed',\n 'A Wizard of Earthsea'\n ]\n};\nconsole.log(author.name); // prints 'Ursula K Le Guin' to the console\n\n
" + ], + "alt": "This example does not render anything", + "description": "From MDN's object basics:\nAn object is a collection of related data and/or\nfunctionality (which usually consists of several variables and functions —\nwhich are called properties and methods when they are inside objects.)" + }, + { + "itemtype": "property", + "name": "class", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\nclass Rectangle {\n constructor(name, height, width) {\n this.name = name;\n this.height = height;\n this.width = width;\n }\n}\nlet square = new Rectangle('square', 1, 1); // creating new instance of Polygon Class.\nconsole.log(square.width); // prints '1' to the console\n\n
" + ], + "alt": "This example does not render anything", + "description": "

Creates and names a class which is a template for\nthe creation of objects.

\n

From the MDN entry:\nThe class declaration creates a new Class with a given name using\nprototype-based inheritance.

\n" + }, + { + "itemtype": "property", + "name": "for", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\nfor (let i = 0; i < 9; i++) {\n console.log(i);\n}\n\n
" + ], + "alt": "This example does not render anything", + "description": "

for creates a loop that is useful for executing one\nsection of code multiple times.

\n

A 'for loop' consists of three different expressions inside of a parenthesis,\nall of which are optional.These expressions are used to control the number of\ntimes the loop is run.The first expression is a statement that is used to set\nthe initial state for the loop.The second expression is a condition that you\nwould like to check before each loop. If this expression returns false then\nthe loop will exit.The third expression is executed at the end of each loop.\nThese expression are separated by ; (semi-colon).In case of an empty expression,\nonly a semi-colon is written.

\n

The code inside of the loop body (in between the curly braces) is executed between the evaluation of the second\nand third expression.

\n

As with any loop, it is important to ensure that the loop can 'exit', or that\nthe test condition will eventually evaluate to false. The test condition with a for loop\nis the second expression detailed above. Ensuring that this expression can eventually\nbecome false ensures that your loop doesn't attempt to run an infinite amount of times,\nwhich can crash your browser.

\n

From the MDN entry:\nCreates a loop that executes a specified statement until the test condition evaluates to false.\nThe condition is evaluated after executing the statement, resulting in the specified statement executing at least once.

\n" + }, + { + "itemtype": "property", + "name": "while", + "file": "src/core/reference.js", + "line": 1, + "module": "Foundation", + "submodule": "Foundation", + "class": "p5", + "example": [ + "
\n\n// This example logs the lines below to the console\n// 4\n// 3\n// 2\n// 1\n// 0\nlet num = 5;\nwhile (num > 0) {\n num = num - 1;\n console.log(num);\n}\n\n
" + ], + "alt": "This example does not render anything", + "description": "

while creates a loop that is useful for executing\none section of code multiple times.

\n

With a 'while loop', the code inside of the loop body (between the curly\nbraces) is run repeatedly until the test condition (inside of the parenthesis)\nevaluates to false. The condition is tested before executing the code body\nwith while, so if the condition is initially false\nthe loop body, or statement, will never execute.

\n

As with any loop, it is important to ensure that the loop can 'exit', or that\nthe test condition will eventually evaluate to false. This is to keep your loop\nfrom trying to run an infinite amount of times, which can crash your browser.

\n

From the MDN entry:\nThe while statement creates a loop that executes a specified statement as long\nas the test condition evaluates to true.The condition is evaluated before\nexecuting the statement.

\n" + }, + { + "itemtype": "property", + "name": "drawingContext", + "file": "src/core/rendering.js", + "line": 573, + "module": "Rendering", + "submodule": "Rendering", + "class": "p5", + "example": [ + "
\n\nfunction setup() {\n drawingContext.shadowOffsetX = 5;\n drawingContext.shadowOffsetY = -5;\n drawingContext.shadowBlur = 10;\n drawingContext.shadowColor = 'black';\n background(200);\n ellipse(width / 2, height / 2, 50, 50);\n}\n\n
" + ], + "alt": "white ellipse with shadow blur effect around edges", + "description": "The p5.js API provides a lot of functionality for creating graphics, but there is\nsome native HTML5 Canvas functionality that is not exposed by p5. You can still call\nit directly using the variable drawingContext, as in the example shown. This is\nthe equivalent of calling canvas.getContext('2d'); or canvas.getContext('webgl');.\nSee this\n\nreference for the native canvas API for possible drawing functions you can call." + }, + { + "itemtype": "property", + "name": "elt", + "file": "src/core/p5.Element.js", + "line": 83, + "module": "DOM", + "submodule": "DOM", + "class": "p5.Element", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Set the border style for the\n // canvas.\n cnv.elt.style.border = '5px dashed deeppink';\n\n describe('A gray square with a pink border drawn with dashed lines.');\n}\n\n
" + ], + "description": "Underlying\nHTMLElement\nobject. Its properties and methods can be used directly." + }, + { + "itemtype": "property", + "name": "width", + "file": "src/core/p5.Element.js", + "line": 96, + "module": "DOM", + "submodule": "DOM", + "class": "p5.Element", + "example": [], + "description": "" + }, + { + "itemtype": "property", + "name": "height", + "file": "src/core/p5.Element.js", + "line": 102, + "module": "DOM", + "submodule": "DOM", + "class": "p5.Element", + "example": [], + "description": "" + }, + { + "itemtype": "property", + "name": "file", + "file": "src/dom/dom.js", + "line": 5235, + "module": "DOM", + "submodule": "DOM", + "class": "p5.File", + "example": [ + "
\n\n// Use the file input to load a\n// file and display its info.\n\nfunction setup() {\n background(200);\n\n // Create a file input and place it beneath\n // the canvas. Call displayInfo() when\n // the file loads.\n let input = createFileInput(displayInfo);\n input.position(0, 100);\n\n describe('A gray square with a file input beneath it. If the user loads a file, its info is written in black.');\n}\n\n// Use the p5.File once\n// it loads.\nfunction displayInfo(file) {\n background(200);\n\n // Display the p5.File's name.\n text(file.name, 10, 10, 80, 40);\n // Display the p5.File's type and subtype.\n text(`${file.type}/${file.subtype}`, 10, 70);\n // Display the p5.File's size in bytes.\n text(file.size, 10, 90);\n}\n\n
" + ], + "description": "Underlying\nFile\nobject. All File properties and methods are accessible." + }, + { + "itemtype": "property", + "name": "type", + "file": "src/dom/dom.js", + "line": 5235, + "module": "DOM", + "submodule": "DOM", + "class": "p5.File", + "example": [ + "
\n\n// Use the file input to load a\n// file and display its info.\n\nfunction setup() {\n background(200);\n\n // Create a file input and place it beneath\n // the canvas. Call displayType() when\n // the file loads.\n let input = createFileInput(displayType);\n input.position(0, 100);\n\n describe('A gray square with a file input beneath it. If the user loads a file, its type is written in black.');\n}\n\n// Display the p5.File's type\n// once it loads.\nfunction displayType(file) {\n background(200);\n\n // Display the p5.File's type.\n text(`This is file's type is: ${file.type}`, 10, 10, 80, 80);\n}\n\n
" + ], + "description": "The file\nMIME type\nas a string. For example, 'image', 'text', and so on." + }, + { + "itemtype": "property", + "name": "subtype", + "file": "src/dom/dom.js", + "line": 5235, + "module": "DOM", + "submodule": "DOM", + "class": "p5.File", + "example": [ + "
\n\n// Use the file input to load a\n// file and display its info.\n\nfunction setup() {\n background(200);\n\n // Create a file input and place it beneath\n // the canvas. Call displaySubtype() when\n // the file loads.\n let input = createFileInput(displaySubtype);\n input.position(0, 100);\n\n describe('A gray square with a file input beneath it. If the user loads a file, its subtype is written in black.');\n}\n\n// Display the p5.File's type\n// once it loads.\nfunction displaySubtype(file) {\n background(200);\n\n // Display the p5.File's subtype.\n text(`This is file's subtype is: ${file.subtype}`, 10, 10, 80, 80);\n}\n\n
" + ], + "description": "The file subtype as a string. For example, a file with an 'image'\nMIME type\nmay have a subtype such as png or jpeg." + }, + { + "itemtype": "property", + "name": "name", + "file": "src/dom/dom.js", + "line": 5235, + "module": "DOM", + "submodule": "DOM", + "class": "p5.File", + "example": [ + "
\n\n// Use the file input to load a\n// file and display its info.\n\nfunction setup() {\n background(200);\n\n // Create a file input and place it beneath\n // the canvas. Call displayName() when\n // the file loads.\n let input = createFileInput(displayName);\n input.position(0, 100);\n\n describe('A gray square with a file input beneath it. If the user loads a file, its name is written in black.');\n}\n\n// Display the p5.File's name\n// once it loads.\nfunction displayName(file) {\n background(200);\n\n // Display the p5.File's name.\n text(`This is file's name is: ${file.name}`, 10, 10, 80, 80);\n}\n\n
" + ], + "description": "The file name as a string." + }, + { + "itemtype": "property", + "name": "size", + "file": "src/dom/dom.js", + "line": 5235, + "module": "DOM", + "submodule": "DOM", + "class": "p5.File", + "example": [ + "
\n\n// Use the file input to load a\n// file and display its info.\n\nfunction setup() {\n background(200);\n\n // Create a file input and place it beneath\n // the canvas. Call displaySize() when\n // the file loads.\n let input = createFileInput(displaySize);\n input.position(0, 100);\n\n describe('A gray square with a file input beneath it. If the user loads a file, its size in bytes is written in black.');\n}\n\n// Display the p5.File's size\n// in bytes once it loads.\nfunction displaySize(file) {\n background(200);\n\n // Display the p5.File's size.\n text(`This is file has ${file.size} bytes.`, 10, 10, 80, 80);\n}\n\n
" + ], + "description": "The number of bytes in the file." + }, + { + "itemtype": "property", + "name": "data", + "file": "src/dom/dom.js", + "line": 5235, + "module": "DOM", + "submodule": "DOM", + "class": "p5.File", + "example": [ + "
\n\n// Use the file input to load a\n// file and display its info.\n\nfunction setup() {\n background(200);\n\n // Create a file input and place it beneath\n // the canvas. Call displayData() when\n // the file loads.\n let input = createFileInput(displayData);\n input.position(0, 100);\n\n describe('A gray square with a file input beneath it. If the user loads a file, its data is written in black.');\n}\n\n// Display the p5.File's data\n// once it loads.\nfunction displayData(file) {\n background(200);\n\n // Display the p5.File's data,\n // which looks like a random\n // string of characters.\n text(file.data, 10, 10, 80, 80);\n}\n\n
" + ], + "description": "A string containing either the file's image data, text contents, or\na parsed object in the case of JSON and\np5.XML objects." + }, + { + "itemtype": "property", + "name": "width", + "file": "src/image/p5.Image.js", + "line": 1573, + "type": "Number", + "module": "Image", + "submodule": "Image", + "class": "p5.Image", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n let x = img.width / 2;\n let y = img.height / 2;\n let d = 20;\n circle(x, y, d);\n\n describe('An image of a mountain landscape with a white circle drawn in the middle.');\n}\n\n
" + ], + "description": "Image width." + }, + { + "itemtype": "property", + "name": "height", + "file": "src/image/p5.Image.js", + "line": 1573, + "module": "Image", + "submodule": "Image", + "class": "p5.Image", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n let x = img.width / 2;\n let y = img.height / 2;\n let d = 20;\n circle(x, y, d);\n\n describe('An image of a mountain landscape with a white circle drawn in the middle.');\n}\n\n
" + ], + "description": "Image height." + }, + { + "itemtype": "property", + "name": "pixels", + "file": "src/image/p5.Image.js", + "line": 1573, + "type": "Number[]", + "module": "Image", + "submodule": "Image", + "class": "p5.Image", + "example": [ + "
\n\nlet img = createImage(66, 66);\nimg.loadPixels();\nlet numPixels = 4 * img.width * img.height;\nfor (let i = 0; i < numPixels; i += 4) {\n // Red.\n img.pixels[i] = 0;\n // Green.\n img.pixels[i + 1] = 0;\n // Blue.\n img.pixels[i + 2] = 0;\n // Alpha.\n img.pixels[i + 3] = 255;\n}\nimg.updatePixels();\nimage(img, 17, 17);\n\ndescribe('A black square drawn in the middle of a gray square.');\n\n
\n\n
\n\nlet img = createImage(66, 66);\nimg.loadPixels();\nlet numPixels = 4 * img.width * img.height;\nfor (let i = 0; i < numPixels; i += 4) {\n // Red.\n img.pixels[i] = 255;\n // Green.\n img.pixels[i + 1] = 0;\n // Blue.\n img.pixels[i + 2] = 0;\n // Alpha.\n img.pixels[i + 3] = 255;\n}\nimg.updatePixels();\nimage(img, 17, 17);\n\ndescribe('A red square drawn in the middle of a gray square.');\n\n
" + ], + "description": "

An array containing the color of each pixel in the\np5.Image object. Colors are stored as numbers\nrepresenting red, green, blue, and alpha (RGBA) values. img.pixels is a\none-dimensional array for performance reasons.

\n

Each pixel occupies four elements in the pixels array, one for each\nRGBA value. For example, the pixel at coordinates (0, 0) stores its\nRGBA values at img.pixels[0], img.pixels[1], img.pixels[2],\nand img.pixels[3], respectively. The next pixel at coordinates (1, 0)\nstores its RGBA values at img.pixels[4], img.pixels[5],\nimg.pixels[6], and img.pixels[7]. And so on. The img.pixels array\nfor a 100×100 p5.Image object has\n100 × 100 × 4 = 40,000 elements.

\n

Accessing the RGBA values for a pixel in the\np5.Image object requires a little math as\nshown below. The img.loadPixels()\nmethod must be called before accessing the img.pixels array. The\nimg.updatePixels() method must be\ncalled after any changes are made.

\n" + }, + { + "itemtype": "property", + "name": "columns", + "file": "src/io/p5.Table.js", + "line": 1306, + "module": "IO", + "submodule": "Table", + "class": "p5.Table", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //print the column names\n for (let c = 0; c < table.getColumnCount(); c++) {\n print('column ' + c + ' is named ' + table.columns[c]);\n }\n}\n\n
" + ], + "description": "An array containing the names of the columns in the table, if the \"header\" the table is\nloaded with the \"header\" parameter." + }, + { + "itemtype": "property", + "name": "rows", + "file": "src/io/p5.Table.js", + "line": 1306, + "module": "IO", + "submodule": "Table", + "class": "p5.Table", + "example": [], + "description": "An array containing the p5.TableRow objects that make up the\nrows of the table. The same result as calling getRows()" + }, + { + "itemtype": "property", + "name": "x", + "file": "src/math/p5.Vector.js", + "line": 94, + "module": "Math", + "submodule": "Vector", + "class": "p5.Vector", + "example": [], + "description": "The x component of the vector" + }, + { + "itemtype": "property", + "name": "y", + "file": "src/math/p5.Vector.js", + "line": 94, + "module": "Math", + "submodule": "Vector", + "class": "p5.Vector", + "example": [], + "description": "The y component of the vector" + }, + { + "itemtype": "property", + "name": "z", + "file": "src/math/p5.Vector.js", + "line": 94, + "module": "Math", + "submodule": "Vector", + "class": "p5.Vector", + "example": [], + "description": "The z component of the vector" + }, + { + "itemtype": "property", + "name": "font", + "file": "src/typography/p5.Font.js", + "line": 577, + "module": "Typography", + "submodule": "Loading & Displaying", + "class": "p5.Font", + "example": [], + "description": "Underlying\nopentype.js\nfont object." + }, + { + "itemtype": "property", + "name": "eyeX", + "file": "src/webgl/p5.Camera.js", + "line": 788, + "type": "Number", + "module": "3D", + "submodule": "Camera", + "class": "p5.Camera", + "example": [ + "
\nlet cam, div;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n background(0);\n cam = createCamera();\n div = createDiv();\n div.position(0, 0);\n describe('An example showing the use of camera object properties');\n}\n\nfunction draw() {\n orbitControl();\n box(10);\n div.html('eyeX = ' + cam.eyeX);\n}\n
" + ], + "alt": "An example showing the use of camera object properties", + "description": "camera position value on x axis. default value is 0" + }, + { + "itemtype": "property", + "name": "eyeY", + "file": "src/webgl/p5.Camera.js", + "line": 788, + "type": "Number", + "module": "3D", + "submodule": "Camera", + "class": "p5.Camera", + "example": [ + "
\nlet cam, div;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n background(0);\n cam = createCamera();\n div = createDiv();\n div.position(0, 0);\n describe('An example showing the use of camera object properties');\n}\n\nfunction draw() {\n orbitControl();\n box(10);\n div.html('eyeY = ' + cam.eyeY);\n}\n
" + ], + "alt": "An example showing the use of camera object properties", + "description": "camera position value on y axis. default value is 0" + }, + { + "itemtype": "property", + "name": "eyeZ", + "file": "src/webgl/p5.Camera.js", + "line": 788, + "type": "Number", + "module": "3D", + "submodule": "Camera", + "class": "p5.Camera", + "example": [ + "
\nlet cam, div;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n background(0);\n cam = createCamera();\n div = createDiv();\n div.position(0, 0);\n describe('An example showing the use of camera object properties');\n}\n\nfunction draw() {\n orbitControl();\n box(10);\n div.html('eyeZ = ' + cam.eyeZ);\n}\n
" + ], + "alt": "An example showing the use of camera object properties", + "description": "camera position value on z axis. default value is 800" + }, + { + "itemtype": "property", + "name": "centerX", + "file": "src/webgl/p5.Camera.js", + "line": 788, + "type": "Number", + "module": "3D", + "submodule": "Camera", + "class": "p5.Camera", + "example": [ + "
\nlet cam, div;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n background(255);\n cam = createCamera();\n cam.lookAt(1, 0, 0);\n div = createDiv('centerX = ' + cam.centerX);\n div.position(0, 0);\n div.style('color', 'white');\n describe('An example showing the use of camera object properties');\n}\n\nfunction draw() {\n orbitControl();\n box(10);\n}\n
" + ], + "alt": "An example showing the use of camera object properties", + "description": "x coordinate representing center of the sketch" + }, + { + "itemtype": "property", + "name": "centerY", + "file": "src/webgl/p5.Camera.js", + "line": 788, + "type": "Number", + "module": "3D", + "submodule": "Camera", + "class": "p5.Camera", + "example": [ + "
\nlet cam, div;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n background(255);\n cam = createCamera();\n cam.lookAt(0, 1, 0);\n div = createDiv('centerY = ' + cam.centerY);\n div.position(0, 0);\n div.style('color', 'white');\n describe('An example showing the use of camera object properties');\n}\n\nfunction draw() {\n orbitControl();\n box(10);\n}\n
" + ], + "alt": "An example showing the use of camera object properties", + "description": "y coordinate representing center of the sketch" + }, + { + "itemtype": "property", + "name": "centerZ", + "file": "src/webgl/p5.Camera.js", + "line": 788, + "type": "Number", + "module": "3D", + "submodule": "Camera", + "class": "p5.Camera", + "example": [ + "
\nlet cam, div;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n background(255);\n cam = createCamera();\n cam.lookAt(0, 0, 1);\n div = createDiv('centerZ = ' + cam.centerZ);\n div.position(0, 0);\n div.style('color', 'white');\n describe('An example showing the use of camera object properties');\n}\n\nfunction draw() {\n orbitControl();\n box(10);\n}\n
" + ], + "alt": "An example showing the use of camera object properties", + "description": "z coordinate representing center of the sketch" + }, + { + "itemtype": "property", + "name": "upX", + "file": "src/webgl/p5.Camera.js", + "line": 788, + "type": "Number", + "module": "3D", + "submodule": "Camera", + "class": "p5.Camera", + "example": [ + "
\nlet cam, div;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n background(255);\n cam = createCamera();\n div = createDiv('upX = ' + cam.upX);\n div.position(0, 0);\n div.style('color', 'blue');\n div.style('font-size', '18px');\n describe('An example showing the use of camera object properties');\n}\n
" + ], + "alt": "An example showing the use of camera object properties", + "description": "x component of direction 'up' from camera" + }, + { + "itemtype": "property", + "name": "upY", + "file": "src/webgl/p5.Camera.js", + "line": 788, + "type": "Number", + "module": "3D", + "submodule": "Camera", + "class": "p5.Camera", + "example": [ + "
\nlet cam, div;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n background(255);\n cam = createCamera();\n div = createDiv('upY = ' + cam.upY);\n div.position(0, 0);\n div.style('color', 'blue');\n div.style('font-size', '18px');\n describe('An example showing the use of camera object properties');\n}\n
" + ], + "alt": "An example showing the use of camera object properties", + "description": "y component of direction 'up' from camera" + }, + { + "itemtype": "property", + "name": "upZ", + "file": "src/webgl/p5.Camera.js", + "line": 788, + "type": "Number", + "module": "3D", + "submodule": "Camera", + "class": "p5.Camera", + "example": [ + "
\nlet cam, div;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n background(255);\n cam = createCamera();\n div = createDiv('upZ = ' + cam.upZ);\n div.position(0, 0);\n div.style('color', 'blue');\n div.style('font-size', '18px');\n describe('An example showing the use of camera object properties');\n}\n
" + ], + "alt": "An example showing the use of camera object properties", + "description": "z component of direction 'up' from camera" + }, + { + "itemtype": "property", + "name": "color", + "file": "src/webgl/p5.Framebuffer.js", + "line": 1379, + "type": "p5.FramebufferTexture", + "module": "Rendering", + "class": "p5.Framebuffer", + "example": [ + "
\n\nlet framebuffer;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n framebuffer = createFramebuffer();\n noStroke();\n}\n\nfunction draw() {\n // Draw to the framebuffer\n framebuffer.begin();\n background(255);\n normalMaterial();\n sphere(20);\n framebuffer.end();\n\n // Draw the framebuffer to the main canvas\n image(framebuffer.color, -width/2, -height/2);\n}\n\n
" + ], + "alt": "A red, green, and blue sphere in the middle of the canvas", + "description": "

A texture with the color information of the framebuffer. Pass this (or the\nframebuffer itself) to texture() to draw it to\nthe canvas, or pass it to a shader with\nsetUniform() to read its data.

\n

Since Framebuffers are controlled by WebGL, their y coordinates are stored\nflipped compared to images and videos. When texturing with a framebuffer\ntexture, you may want to flip vertically, e.g. with\nplane(framebuffer.width, -framebuffer.height).

\n" + }, + { + "itemtype": "property", + "name": "depth", + "file": "src/webgl/p5.Framebuffer.js", + "line": 1379, + "type": "p5.FramebufferTexture|undefined", + "module": "Rendering", + "class": "p5.Framebuffer", + "example": [ + "
\n\nlet framebuffer;\nlet depthShader;\n\nconst vert = `\nprecision highp float;\nattribute vec3 aPosition;\nattribute vec2 aTexCoord;\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\nvarying vec2 vTexCoord;\nvoid main() {\n vec4 viewModelPosition = uModelViewMatrix * vec4(aPosition, 1.0);\n gl_Position = uProjectionMatrix * viewModelPosition;\n vTexCoord = aTexCoord;\n}\n`;\n\nconst frag = `\nprecision highp float;\nvarying vec2 vTexCoord;\nuniform sampler2D depth;\nvoid main() {\n float depthVal = texture2D(depth, vTexCoord).r;\n gl_FragColor = mix(\n vec4(1., 1., 0., 1.), // yellow\n vec4(0., 0., 1., 1.), // blue\n pow(depthVal, 6.)\n );\n}\n`;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n framebuffer = createFramebuffer();\n depthShader = createShader(vert, frag);\n noStroke();\n}\n\nfunction draw() {\n // Draw to the framebuffer\n framebuffer.begin();\n background(255);\n rotateX(frameCount * 0.01);\n box(20, 20, 100);\n framebuffer.end();\n\n push();\n shader(depthShader);\n depthShader.setUniform('depth', framebuffer.depth);\n plane(framebuffer.width, framebuffer.height);\n pop();\n}\n\n
" + ], + "alt": "A video of a rectangular prism rotating, with parts closest to the camera\nappearing yellow and colors getting progressively more blue the farther\nfrom the camera they go", + "description": "

A texture with the depth information of the framebuffer. If the framebuffer\nwas created with { depth: false } in its settings, then this property will\nbe undefined. Pass this to texture() to draw it to\nthe canvas, or pass it to a shader with\nsetUniform() to read its data.

\n

Since Framebuffers are controlled by WebGL, their y coordinates are stored\nflipped compared to images and videos. When texturing with a framebuffer\ntexture, you may want to flip vertically, e.g. with\nplane(framebuffer.width, -framebuffer.height).

\n" + }, + { + "itemtype": "property", + "name": "pixels", + "file": "src/webgl/p5.Framebuffer.js", + "line": 1379, + "type": "Number[]", + "module": "Rendering", + "class": "p5.Framebuffer", + "example": [], + "description": "

A Uint8ClampedArray\ncontaining the values for all the pixels in the Framebuffer.

\n

Like the main canvas pixels property, call\nloadPixels() before reading\nit, and call updatePixels()\nafterwards to update its data.

\n

Note that updating pixels via this property will be slower than\ndrawing to the framebuffer directly.\nConsider using a shader instead of looping over pixels.

\n" + }, + { + "name": "describe", + "file": "src/accessibility/describe.js", + "line": 120, + "itemtype": "method", + "description": "

Creates a screen reader-accessible description for the canvas.

\n

The first parameter, text, is the description of the canvas.

\n

The second parameter, display, is optional. It determines how the\ndescription is displayed. If LABEL is passed, as in\ndescribe('A description.', LABEL), the description will be visible in\na div element next to the canvas. If FALLBACK is passed, as in\ndescribe('A description.', FALLBACK), the description will only be\nvisible to screen readers. This is the default mode.

\n

Read\nHow to label your p5.js code to\nlearn more about making sketches accessible.

\n", + "example": [ + "
\n\nfunction setup() {\n background('pink');\n\n // Draw a heart.\n fill('red');\n noStroke();\n circle(67, 67, 20);\n circle(83, 67, 20);\n triangle(91, 73, 75, 95, 59, 73);\n\n // Add a general description of the canvas.\n describe('A pink square with a red heart in the bottom-right corner.');\n}\n\n
\n\n
\n\nfunction setup() {\n background('pink');\n\n // Draw a heart.\n fill('red');\n noStroke();\n circle(67, 67, 20);\n circle(83, 67, 20);\n triangle(91, 73, 75, 95, 59, 73);\n\n // Add a general description of the canvas\n // and display it for debugging.\n describe('A pink square with a red heart in the bottom-right corner.', LABEL);\n}\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n // The expression\n // frameCount % 100\n // causes x to increase from 0\n // to 99, then restart from 0.\n let x = frameCount % 100;\n\n // Draw the circle.\n fill(0, 255, 0);\n circle(x, 50, 40);\n\n // Add a general description of the canvas.\n describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`);\n}\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n // The expression\n // frameCount % 100\n // causes x to increase from 0\n // to 99, then restart from 0.\n let x = frameCount % 100;\n\n // Draw the circle.\n fill(0, 255, 0);\n circle(x, 50, 40);\n\n // Add a general description of the canvas\n // and display it for debugging.\n describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`, LABEL);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "text", + "description": "description of the canvas.", + "type": "String" + }, + { + "name": "display", + "description": "either LABEL or FALLBACK.", + "optional": 1, + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "describeElement", + "file": "src/accessibility/describe.js", + "line": 244, + "itemtype": "method", + "description": "

Creates a screen reader-accessible description for elements in the canvas.\nElements are shapes or groups of shapes that create meaning together.

\n

The first parameter, name, is the name of the element.

\n

The second parameter, text, is the description of the element.

\n

The third parameter, display, is optional. It determines how the\ndescription is displayed. If LABEL is passed, as in\ndescribe('A description.', LABEL), the description will be visible in\na div element next to the canvas. Using LABEL creates unhelpful\nduplicates for screen readers. Only use LABEL during development. If\nFALLBACK is passed, as in describe('A description.', FALLBACK), the\ndescription will only be visible to screen readers. This is the default\nmode.

\n

Read\nHow to label your p5.js code to\nlearn more about making sketches accessible.

\n", + "example": [ + "
\n\nfunction setup() {\n background('pink');\n\n // Describe the first element\n // and draw it.\n describeElement('Circle', 'A yellow circle in the top-left corner.');\n noStroke();\n fill('yellow');\n circle(25, 25, 40);\n\n // Describe the second element\n // and draw it.\n describeElement('Heart', 'A red heart in the bottom-right corner.');\n fill('red');\n circle(66.6, 66.6, 20);\n circle(83.2, 66.6, 20);\n triangle(91.2, 72.6, 75, 95, 58.6, 72.6);\n\n // Add a general description of the canvas.\n describe('A red heart and yellow circle over a pink background.');\n}\n\n
\n\n
\n\nfunction setup() {\n background('pink');\n\n // Describe the first element\n // and draw it. Display the\n // description for debugging.\n describeElement('Circle', 'A yellow circle in the top-left corner.', LABEL);\n noStroke();\n fill('yellow');\n circle(25, 25, 40);\n\n // Describe the second element\n // and draw it. Display the\n // description for debugging.\n describeElement('Heart', 'A red heart in the bottom-right corner.', LABEL);\n fill('red');\n circle(66.6, 66.6, 20);\n circle(83.2, 66.6, 20);\n triangle(91.2, 72.6, 75, 95, 58.6, 72.6);\n\n // Add a general description of the canvas.\n describe('A red heart and yellow circle over a pink background.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "name", + "description": "name of the element.", + "type": "String" + }, + { + "name": "text", + "description": "description of the element.", + "type": "String" + }, + { + "name": "display", + "description": "either LABEL or FALLBACK.", + "optional": 1, + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "textOutput", + "file": "src/accessibility/outputs.js", + "line": 126, + "itemtype": "method", + "description": "

Creates a screen reader-accessible description for shapes on the canvas.\ntextOutput() adds a general description, list of shapes, and\ntable of shapes to the web page.

\n

The general description includes the canvas size, canvas color, and number\nof shapes. For example,\nYour output is a, 100 by 100 pixels, gray canvas containing the following 2 shapes:.

\n

A list of shapes follows the general description. The list describes the\ncolor, location, and area of each shape. For example,\na red circle at middle covering 3% of the canvas. Each shape can be\nselected to get more details.

\n

textOutput() uses its table of shapes as a list. The table describes the\nshape, color, location, coordinates and area. For example,\nred circle location = middle area = 3%. This is different from\ngridOutput(), which uses its table as a grid.

\n

The display parameter is optional. It determines how the description is\ndisplayed. If LABEL is passed, as in textOutput(LABEL), the description\nwill be visible in a div element next to the canvas. Using LABEL creates\nunhelpful duplicates for screen readers. Only use LABEL during\ndevelopment. If FALLBACK is passed, as in textOutput(FALLBACK), the\ndescription will only be visible to screen readers. This is the default\nmode.

\n

Read\nHow to label your p5.js code to\nlearn more about making sketches accessible.

\n", + "example": [ + "
\n\nfunction setup() {\n // Add the text description.\n textOutput();\n\n // Draw a couple of shapes.\n background(200);\n fill(255, 0, 0);\n circle(20, 20, 20);\n fill(0, 0, 255);\n square(50, 50, 50);\n\n // Add a general description of the canvas.\n describe('A red circle and a blue square on a gray background.');\n}\n\n
\n\n
\n\nfunction setup() {\n // Add the text description and\n // display it for debugging.\n textOutput(LABEL);\n\n // Draw a couple of shapes.\n background(200);\n fill(255, 0, 0);\n circle(20, 20, 20);\n fill(0, 0, 255);\n square(50, 50, 50);\n\n // Add a general description of the canvas.\n describe('A red circle and a blue square on a gray background.');\n}\n\n
\n\n
\n\nfunction draw() {\n // Add the text description.\n textOutput();\n\n // Draw a moving circle.\n background(200);\n let x = frameCount * 0.1;\n fill(255, 0, 0);\n circle(x, 20, 20);\n fill(0, 0, 255);\n square(50, 50, 50);\n\n // Add a general description of the canvas.\n describe('A red circle moves from left to right above a blue square.');\n}\n\n
\n\n
\n\nfunction draw() {\n // Add the text description and\n // display it for debugging.\n textOutput(LABEL);\n\n // Draw a moving circle.\n background(200);\n let x = frameCount * 0.1;\n fill(255, 0, 0);\n circle(x, 20, 20);\n fill(0, 0, 255);\n square(50, 50, 50);\n\n // Add a general description of the canvas.\n describe('A red circle moves from left to right above a blue square.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "display", + "description": "either FALLBACK or LABEL.", + "optional": 1, + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "gridOutput", + "file": "src/accessibility/outputs.js", + "line": 262, + "itemtype": "method", + "description": "

Creates a screen reader-accessible description for shapes on the canvas.\ngridOutput() adds a general description, table of shapes, and list of\nshapes to the web page.

\n

The general description includes the canvas size, canvas color, and number of\nshapes. For example,\ngray canvas, 100 by 100 pixels, contains 2 shapes: 1 circle 1 square.

\n

gridOutput() uses its table of shapes as a grid. Each shape in the grid\nis placed in a cell whose row and column correspond to the shape's location\non the canvas. The grid cells describe the color and type of shape at that\nlocation. For example, red circle. These descriptions can be selected\nindividually to get more details. This is different from\ntextOutput(), which uses its table as a list.

\n

A list of shapes follows the table. The list describes the color, type,\nlocation, and area of each shape. For example,\nred circle, location = middle, area = 3 %.

\n

The display parameter is optional. It determines how the description is\ndisplayed. If LABEL is passed, as in gridOutput(LABEL), the description\nwill be visible in a div element next to the canvas. Using LABEL creates\nunhelpful duplicates for screen readers. Only use LABEL during\ndevelopment. If FALLBACK is passed, as in gridOutput(FALLBACK), the\ndescription will only be visible to screen readers. This is the default\nmode.

\n

Read\nHow to label your p5.js code to\nlearn more about making sketches accessible.

\n", + "example": [ + "
\n\nfunction setup() {\n // Add the grid description.\n gridOutput();\n\n // Draw a couple of shapes.\n background(200);\n fill(255, 0, 0);\n circle(20, 20, 20);\n fill(0, 0, 255);\n square(50, 50, 50);\n\n // Add a general description of the canvas.\n describe('A red circle and a blue square on a gray background.');\n}\n\n
\n\n
\n\nfunction setup() {\n // Add the grid description and\n // display it for debugging.\n gridOutput(LABEL);\n\n // Draw a couple of shapes.\n background(200);\n fill(255, 0, 0);\n circle(20, 20, 20);\n fill(0, 0, 255);\n square(50, 50, 50);\n\n // Add a general description of the canvas.\n describe('A red circle and a blue square on a gray background.');\n}\n\n
\n\n
\n\nfunction draw() {\n // Add the grid description.\n gridOutput();\n\n // Draw a moving circle.\n background(200);\n let x = frameCount * 0.1;\n fill(255, 0, 0);\n circle(x, 20, 20);\n fill(0, 0, 255);\n square(50, 50, 50);\n\n // Add a general description of the canvas.\n describe('A red circle moves from left to right above a blue square.');\n}\n\n
\n\n
\n\nfunction draw() {\n // Add the grid description and\n // display it for debugging.\n gridOutput(LABEL);\n\n // Draw a moving circle.\n background(200);\n let x = frameCount * 0.1;\n fill(255, 0, 0);\n circle(x, 20, 20);\n fill(0, 0, 255);\n square(50, 50, 50);\n\n // Add a general description of the canvas.\n describe('A red circle moves from left to right above a blue square.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "display", + "description": "either FALLBACK or LABEL.", + "optional": 1, + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "p5", + "file": "src/core/structure.js", + "line": 544, + "itemtype": "method", + "description": "

The p5() constructor enables you to activate \"instance mode\" instead of normal\n\"global mode\". This is an advanced topic. A short description and example is\nincluded below. Please see\n\nDan Shiffman's Coding Train video tutorial or this\ntutorial page\nfor more info.

\n

By default, all p5.js functions are in the global namespace (i.e. bound to the window\nobject), meaning you can call them simply ellipse(), fill(), etc. However, this\nmight be inconvenient if you are mixing with other JS libraries (synchronously or\nasynchronously) or writing long programs of your own. p5.js currently supports a\nway around this problem called \"instance mode\". In instance mode, all p5 functions\nare bound up in a single variable instead of polluting your global namespace.

\n

Optionally, you can specify a default container for the canvas and any other elements\nto append to with a second argument. You can give the ID of an element in your html,\nor an html node itself.

\n

Note that creating instances like this also allows you to have more than one p5 sketch on\na single web page, as they will each be wrapped up with their own set up variables. Of\ncourse, you could also use iframes to have multiple sketches in global mode.

\n", + "example": [ + "
\nconst s = p => {\n let x = 100;\n let y = 100;\n\n p.setup = function() {\n p.createCanvas(700, 410);\n };\n\n p.draw = function() {\n p.background(0);\n p.fill(255);\n p.rect(x, y, 50, 50);\n };\n};\n\nnew p5(s); // invoke p5\n
" + ], + "alt": "white rectangle on black background", + "overloads": [ + { + "params": [ + { + "name": "sketch", + "description": "a function containing a p5.js sketch", + "type": "Object" + }, + { + "name": "node", + "description": "ID or pointer to HTML DOM node to contain sketch in", + "type": "String|Object" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Structure", + "submodule": "Structure" + }, + { + "name": "alpha", + "file": "src/color/creating_reading.js", + "line": 41, + "itemtype": "method", + "description": "Extracts the alpha (transparency) value from a\np5.Color object, array of color components, or\nCSS color string.", + "example": [ + "
\n\nnoStroke();\nconst c = color(0, 126, 255, 102);\nfill(c);\nrect(15, 15, 35, 70);\n// Sets 'alphaValue' to 102.\nconst alphaValue = alpha(c);\nfill(alphaValue);\nrect(50, 15, 35, 70);\ndescribe('Two rectangles. The left one is light blue and the right one is charcoal gray.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "color", + "description": "p5.Color object, array of\ncolor components, or CSS color string.", + "type": "p5.Color|Number[]|String" + } + ], + "return": { + "description": "the alpha value.", + "type": "Number" + } + } + ], + "return": { + "description": "the alpha value.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "blue", + "file": "src/color/creating_reading.js", + "line": 69, + "itemtype": "method", + "description": "Extracts the blue value from a p5.Color object,\narray of color components, or CSS color string.", + "example": [ + "
\n\nconst c = color(175, 100, 220);\nfill(c);\nrect(15, 20, 35, 60);\n// Sets 'blueValue' to 220.\nconst blueValue = blue(c);\nfill(0, 0, blueValue);\nrect(50, 20, 35, 60);\ndescribe('Two rectangles. The left one is light purple and the right one is royal blue.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "color", + "description": "p5.Color object, array of\ncolor components, or CSS color string.", + "type": "p5.Color|Number[]|String" + } + ], + "return": { + "description": "the blue value.", + "type": "Number" + } + } + ], + "return": { + "description": "the blue value.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "brightness", + "file": "src/color/creating_reading.js", + "line": 115, + "itemtype": "method", + "description": "Extracts the HSB brightness value from a\np5.Color object, array of color components, or\nCSS color string.", + "example": [ + "
\n\nnoStroke();\ncolorMode(HSB, 255);\nconst c = color(0, 126, 255);\nfill(c);\nrect(15, 20, 35, 60);\n// Sets 'brightValue' to 255.\nconst brightValue = brightness(c);\nfill(brightValue);\nrect(50, 20, 35, 60);\ndescribe('Two rectangles. The left one is salmon pink and the right one is white.');\n\n
\n\n
\n\nnoStroke();\ncolorMode(HSB, 255);\nconst c = color('hsb(60, 100%, 50%)');\nfill(c);\nrect(15, 20, 35, 60);\n// Sets 'brightValue' to 127.5 (50% of 255)\nconst brightValue = brightness(c);\nfill(brightValue);\nrect(50, 20, 35, 60);\ndescribe('Two rectangles. The left one is olive and the right one is gray.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "color", + "description": "p5.Color object, array of\ncolor components, or CSS color string.", + "type": "p5.Color|Number[]|String" + } + ], + "return": { + "description": "the brightness value.", + "type": "Number" + } + } + ], + "return": { + "description": "the brightness value.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "color", + "file": "src/color/creating_reading.js", + "line": 293, + "itemtype": "method", + "description": "

Creates a p5.Color object. By default, the\nparameters are interpreted as RGB values. Calling color(255, 204, 0) will\nreturn a bright yellow color. The way these parameters are interpreted may\nbe changed with the colorMode() function.

\n

The version of color() with one parameter interprets the value one of two\nways. If the parameter is a number, it's interpreted as a grayscale value.\nIf the parameter is a string, it's interpreted as a CSS color string.

\n

The version of color() with two parameters interprets the first one as a\ngrayscale value. The second parameter sets the alpha (transparency) value.

\n

The version of color() with three parameters interprets them as RGB, HSB,\nor HSL colors, depending on the current colorMode().

\n

The version of color() with four parameters interprets them as RGBA, HSBA,\nor HSLA colors, depending on the current colorMode(). The last parameter\nsets the alpha (transparency) value.

\n", + "example": [ + "
\n\nconst c = color(255, 204, 0);\nfill(c);\nnoStroke();\nrect(30, 20, 55, 55);\ndescribe('A yellow rectangle on a gray canvas.');\n\n
\n\n
\n\n// RGB values.\nlet c = color(255, 204, 0);\nfill(c);\nnoStroke();\ncircle(25, 25, 80);\n// A grayscale value.\nc = color(65);\nfill(c);\ncircle(75, 75, 80);\ndescribe(\n 'Two ellipses. The circle in the top-left corner is yellow and the one at the bottom-right is gray.'\n);\n\n
\n\n
\n\n// A CSS named color.\nconst c = color('magenta');\nfill(c);\nnoStroke();\nsquare(20, 20, 60);\ndescribe('A magenta square on a gray canvas.');\n\n
\n\n
\n\n// CSS hex color codes.\nnoStroke();\nlet c = color('#0f0');\nfill(c);\nrect(0, 10, 45, 80);\nc = color('#00ff00');\nfill(c);\nrect(55, 10, 45, 80);\ndescribe('Two bright green rectangles on a gray canvas.');\n\n
\n\n
\n\n// RGB and RGBA color strings.\nnoStroke();\nlet c = color('rgb(0,0,255)');\nfill(c);\nsquare(10, 10, 35);\nc = color('rgb(0%, 0%, 100%)');\nfill(c);\nsquare(55, 10, 35);\nc = color('rgba(0, 0, 255, 1)');\nfill(c);\nsquare(10, 55, 35);\nc = color('rgba(0%, 0%, 100%, 1)');\nfill(c);\nsquare(55, 55, 35);\ndescribe('Four blue squares in corners of a gray canvas.');\n\n
\n\n
\n\n// HSL and HSLA color strings.\nlet c = color('hsl(160, 100%, 50%)');\nnoStroke();\nfill(c);\nrect(0, 10, 45, 80);\nc = color('hsla(160, 100%, 50%, 0.5)');\nfill(c);\nrect(55, 10, 45, 80);\ndescribe('Two sea green rectangles. A darker rectangle on the left and a brighter one on the right.');\n\n
\n\n
\n\n// HSB and HSBA color strings.\nlet c = color('hsb(160, 100%, 50%)');\nnoStroke();\nfill(c);\nrect(0, 10, 45, 80);\nc = color('hsba(160, 100%, 50%, 0.5)');\nfill(c);\nrect(55, 10, 45, 80);\ndescribe('Two green rectangles. A darker rectangle on the left and a brighter one on the right.');\n\n
\n\n
\n\n// Changing color modes.\nnoStroke();\nlet c = color(50, 55, 100);\nfill(c);\nrect(0, 10, 45, 80);\ncolorMode(HSB, 100);\nc = color(50, 55, 100);\nfill(c);\nrect(55, 10, 45, 80);\ndescribe('Two blue rectangles. A darker rectangle on the left and a brighter one on the right.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "gray", + "description": "number specifying value between white and black.", + "type": "Number" + }, + { + "name": "alpha", + "description": "alpha value relative to current color range\n(default is 0-255).", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "resulting color.", + "type": "p5.Color" + } + }, + { + "params": [ + { + "name": "v1", + "description": "red or hue value relative to\nthe current color range.", + "type": "Number" + }, + { + "name": "v2", + "description": "green or saturation value\nrelative to the current color range.", + "type": "Number" + }, + { + "name": "v3", + "description": "blue or brightness value\nrelative to the current color range.", + "type": "Number" + }, + { + "name": "alpha", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "", + "type": "p5.Color" + } + }, + { + "params": [ + { + "name": "value", + "description": "a color string.", + "type": "String" + } + ], + "return": { + "description": "", + "type": "p5.Color" + } + }, + { + "params": [ + { + "name": "values", + "description": "an array containing the red, green, blue,\nand alpha components of the color.", + "type": "Number[]" + } + ], + "return": { + "description": "", + "type": "p5.Color" + } + }, + { + "params": [ + { + "name": "color", + "type": "p5.Color" + } + ], + "return": { + "description": "", + "type": "p5.Color" + } + } + ], + "return": { + "description": "resulting color.", + "type": "p5.Color" + }, + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "green", + "file": "src/color/creating_reading.js", + "line": 325, + "itemtype": "method", + "description": "Extracts the green value from a p5.Color object,\narray of color components, or CSS color string.", + "example": [ + "
\n\nconst c = color(20, 75, 200);\nfill(c);\nrect(15, 20, 35, 60);\n// Sets 'greenValue' to 75.\nconst greenValue = green(c);\nfill(0, greenValue, 0);\nrect(50, 20, 35, 60);\ndescribe('Two rectangles. The rectangle on the left is blue and the one on the right is green.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "color", + "description": "p5.Color object, array of\ncolor components, or CSS color string.", + "type": "p5.Color|Number[]|String" + } + ], + "return": { + "description": "the green value.", + "type": "Number" + } + } + ], + "return": { + "description": "the green value.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "hue", + "file": "src/color/creating_reading.js", + "line": 363, + "itemtype": "method", + "description": "

Extracts the hue value from a\np5.Color object, array of color components, or\nCSS color string.

\n

Hue exists in both HSB and HSL. It describes a color's position on the\ncolor wheel. By default, this function returns the HSL-normalized hue. If\nthe colorMode() is set to HSB, it returns the\nHSB-normalized hue.

\n", + "example": [ + "
\n\nnoStroke();\ncolorMode(HSB, 255);\nconst c = color(0, 126, 255);\nfill(c);\nrect(15, 20, 35, 60);\n// Sets 'hueValue' to 0.\nconst hueValue = hue(c);\nfill(hueValue);\nrect(50, 20, 35, 60);\ndescribe(\n 'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.'\n);\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "color", + "description": "p5.Color object, array of\ncolor components, or CSS color string.", + "type": "p5.Color|Number[]|String" + } + ], + "return": { + "description": "the hue", + "type": "Number" + } + } + ], + "return": { + "description": "the hue", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "lerpColor", + "file": "src/color/creating_reading.js", + "line": 411, + "itemtype": "method", + "description": "

Blends two colors to find a third color between them. The amt parameter\nspecifies the amount to interpolate between the two values. 0 is equal to\nthe first color, 0.1 is very near the first color, 0.5 is halfway between\nthe two colors, and so on. Negative numbers are set to 0. Numbers greater\nthan 1 are set to 1. This differs from the behavior of\nlerp. It's necessary because numbers outside of the\ninterval [0, 1] will produce strange and unexpected colors.

\n

The way that colors are interpolated depends on the current\ncolorMode().

\n", + "example": [ + "
\n\ncolorMode(RGB);\nstroke(255);\nbackground(51);\nconst from = color(218, 165, 32);\nconst to = color(72, 61, 139);\ncolorMode(RGB);\nconst interA = lerpColor(from, to, 0.33);\nconst interB = lerpColor(from, to, 0.66);\nfill(from);\nrect(10, 20, 20, 60);\nfill(interA);\nrect(30, 20, 20, 60);\nfill(interB);\nrect(50, 20, 20, 60);\nfill(to);\nrect(70, 20, 20, 60);\ndescribe(\n 'Four rectangles with white edges. From left to right, the rectangles are tan, brown, brownish purple, and purple.'\n);\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "c1", + "description": "interpolate from this color.", + "type": "p5.Color" + }, + { + "name": "c2", + "description": "interpolate to this color.", + "type": "p5.Color" + }, + { + "name": "amt", + "description": "number between 0 and 1.", + "type": "Number" + } + ], + "return": { + "description": "interpolated color.", + "type": "p5.Color" + } + } + ], + "return": { + "description": "interpolated color.", + "type": "p5.Color" + }, + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "lightness", + "file": "src/color/creating_reading.js", + "line": 494, + "itemtype": "method", + "description": "Extracts the HSL lightness value from a\np5.Color object, array of color components, or\nCSS color string.", + "example": [ + "
\n\nnoStroke();\ncolorMode(HSL);\nconst c = color(156, 100, 50, 1);\nfill(c);\nrect(15, 20, 35, 60);\n// Sets 'lightValue' to 50.\nconst lightValue = lightness(c);\nfill(lightValue);\nrect(50, 20, 35, 60);\ndescribe('Two rectangles. The rectangle on the left is light green and the one on the right is gray.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "color", + "description": "p5.Color object, array of\ncolor components, or CSS color string.", + "type": "p5.Color|Number[]|String" + } + ], + "return": { + "description": "the lightness", + "type": "Number" + } + } + ], + "return": { + "description": "the lightness", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "red", + "file": "src/color/creating_reading.js", + "line": 524, + "itemtype": "method", + "description": "Extracts the red value from a\np5.Color object, array of color components, or\nCSS color string.", + "example": [ + "
\n\nconst c = color(255, 204, 0);\nfill(c);\nrect(15, 20, 35, 60);\n// Sets 'redValue' to 255.\nconst redValue = red(c);\nfill(redValue, 0, 0);\nrect(50, 20, 35, 60);\ndescribe(\n 'Two rectangles with black edges. The rectangle on the left is yellow and the one on the right is red.'\n);\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "color", + "description": "p5.Color object, array of\ncolor components, or CSS color string.", + "type": "p5.Color|Number[]|String" + } + ], + "return": { + "description": "the red value.", + "type": "Number" + } + } + ], + "return": { + "description": "the red value.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "saturation", + "file": "src/color/creating_reading.js", + "line": 560, + "itemtype": "method", + "description": "

Extracts the saturation value from a\np5.Color object, array of color components, or\nCSS color string.

\n

Saturation is scaled differently in HSB and HSL. By default, this function\nreturns the HSL saturation. If the colorMode()\nis set to HSB, it returns the HSB saturation.

\n", + "example": [ + "
\n\nnoStroke();\ncolorMode(HSB, 255);\nconst c = color(0, 126, 255);\nfill(c);\nrect(15, 20, 35, 60);\n// Sets 'satValue' to 126.\nconst satValue = saturation(c);\nfill(satValue);\nrect(50, 20, 35, 60);\ndescribe(\n 'Two rectangles. The rectangle on the left is deep pink and the one on the right is gray.'\n);\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "color", + "description": "p5.Color object, array of\ncolor components, or CSS color string.", + "type": "p5.Color|Number[]|String" + } + ], + "return": { + "description": "the saturation value", + "type": "Number" + } + } + ], + "return": { + "description": "the saturation value", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "beginClip", + "file": "src/color/setting.js", + "line": 104, + "itemtype": "method", + "description": "

Start defining a shape that will mask subsequent things drawn to the canvas.\nOnly opaque regions of the mask shape will allow content to be drawn.\nAny shapes drawn between this and endClip() will\ncontribute to the mask shape.

\n

The mask will apply to anything drawn after this call. To draw without a mask, contain\nthe code to apply the mask and to draw the masked content between\npush() and pop().

\n

Alternatively, rather than drawing the mask between this and\nendClip(), draw the mask in a callback function\npassed to clip().

\n

Options can include:

\n", + "example": [ + "
\n\nnoStroke();\n\n// Mask in some shapes\npush();\nbeginClip();\ntriangle(15, 37, 30, 13, 43, 37);\ncircle(45, 45, 7);\nendClip();\n\nfill('red');\nrect(5, 5, 45, 45);\npop();\n\ntranslate(50, 50);\n\n// Mask out the same shapes\npush();\nbeginClip({ invert: true });\ntriangle(15, 37, 30, 13, 43, 37);\ncircle(45, 45, 7);\nendClip();\n\nfill('red');\nrect(5, 5, 45, 45);\npop();\n\n
", + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n}\n\nfunction draw() {\n background(255);\n noStroke();\n\n beginClip();\n push();\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n scale(0.5);\n torus(30, 15);\n pop();\n endClip();\n\n beginShape(QUAD_STRIP);\n fill(0, 255, 255);\n vertex(-width/2, -height/2);\n vertex(width/2, -height/2);\n fill(100, 0, 100);\n vertex(-width/2, height/2);\n vertex(width/2, height/2);\n endShape();\n}\n\n
" + ], + "alt": "In the top left, a red triangle and circle. In the bottom right, a red\nsquare with a triangle and circle cut out of it.\nA silhouette of a rotating torus colored with a gradient from\ncyan to purple", + "overloads": [ + { + "params": [ + { + "name": "options", + "description": "An object containing clip settings.", + "optional": 1, + "type": "Object" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Setting" + }, + { + "name": "endClip", + "file": "src/color/setting.js", + "line": 116, + "itemtype": "method", + "description": "Finishes defining a shape that will mask subsequent things drawn to the canvas.\nOnly opaque regions of the mask shape will allow content to be drawn.\nAny shapes drawn between beginClip() and this\nwill contribute to the mask shape.", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Setting" + }, + { + "name": "clip", + "file": "src/color/setting.js", + "line": 209, + "itemtype": "method", + "description": "

Use the shape drawn by a callback function to mask subsequent things drawn to the canvas.\nOnly opaque regions of the mask shape will allow content to be drawn.

\n

The mask will apply to anything drawn after this call. To draw without a mask, contain\nthe code to apply the mask and to draw the masked content between\npush() and pop().

\n

Alternatively, rather than drawing the mask shape in a function, draw the\nshape between beginClip() and endClip().

\n

Options can include:

\n", + "example": [ + "
\n\nnoStroke();\n\n// Mask in some shapes\npush();\nclip(() => {\n triangle(15, 37, 30, 13, 43, 37);\n circle(45, 45, 7);\n});\n\nfill('red');\nrect(5, 5, 45, 45);\npop();\n\ntranslate(50, 50);\n\n// Mask out the same shapes\npush();\nclip(() => {\n triangle(15, 37, 30, 13, 43, 37);\n circle(45, 45, 7);\n}, { invert: true });\n\nfill('red');\nrect(5, 5, 45, 45);\npop();\n\n
", + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n}\n\nfunction draw() {\n background(255);\n noStroke();\n\n clip(() => {\n push();\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n scale(0.5);\n torus(30, 15);\n pop();\n });\n\n beginShape(QUAD_STRIP);\n fill(0, 255, 255);\n vertex(-width/2, -height/2);\n vertex(width/2, -height/2);\n fill(100, 0, 100);\n vertex(-width/2, height/2);\n vertex(width/2, height/2);\n endShape();\n}\n\n
" + ], + "alt": "In the top left, a red triangle and circle. In the bottom right, a red\nsquare with a triangle and circle cut out of it.\nA silhouette of a rotating torus colored with a gradient from\ncyan to purple", + "overloads": [ + { + "params": [ + { + "name": "callback", + "description": "A function that draws the mask shape.", + "type": "Function" + }, + { + "name": "options", + "description": "An object containing clip settings.", + "optional": 1, + "type": "Object" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Setting" + }, + { + "name": "background", + "file": "src/color/setting.js", + "line": 385, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the color used for the background of the canvas. By default, the\nbackground is transparent. This function is typically used within\ndraw() to clear the display window at the beginning\nof each frame. It can also be used inside setup() to\nset the background on the first frame of animation.

\n

The version of background() with one parameter interprets the value one of four\nways. If the parameter is a number, it's interpreted as a grayscale value.\nIf the parameter is a string, it's interpreted as a CSS color string. RGB, RGBA,\nHSL, HSLA, hex, and named color strings are supported. If the parameter is a\np5.Color object, it will be used as the background color.\nIf the parameter is a p5.Image object, it will be used as\nthe background image.

\n

The version of background() with two parameters interprets the first one as a\ngrayscale value. The second parameter sets the alpha (transparency) value.

\n

The version of background() with three parameters interprets them as RGB, HSB,\nor HSL colors, depending on the current colorMode().\nBy default, colors are specified in RGB values. Calling background(255, 204, 0)\nsets the background a bright yellow color.

\n", + "example": [ + "
\n\n// A grayscale integer value.\nbackground(51);\ndescribe('A canvas with a dark charcoal gray background.');\n\n
\n\n
\n\n// A grayscale integer value and an alpha value.\nbackground(51, 0.4);\ndescribe('A canvas with a transparent gray background.');\n\n
\n\n
\n\n// R, G & B integer values.\nbackground(255, 204, 0);\ndescribe('A canvas with a yellow background.');\n\n
\n\n
\n\n// H, S & B integer values.\ncolorMode(HSB);\nbackground(255, 204, 100);\ndescribe('A canvas with a royal blue background.');\n\n
\n\n
\n\n// A CSS named color.\nbackground('red');\ndescribe('A canvas with a red background.');\n\n
\n\n
\n\n// Three-digit hex RGB notation.\nbackground('#fae');\ndescribe('A canvas with a pink background.');\n\n
\n\n
\n\n// Six-digit hex RGB notation.\nbackground('#222222');\ndescribe('A canvas with a black background.');\n\n
\n\n
\n\n// Integer RGB notation.\nbackground('rgb(0,255,0)');\ndescribe('A canvas with a bright green background.');\n\n
\n\n
\n\n// Integer RGBA notation.\nbackground('rgba(0,255,0, 0.25)');\ndescribe('A canvas with a transparent green background.');\n\n
\n\n
\n\n// Percentage RGB notation.\nbackground('rgb(100%,0%,10%)');\ndescribe('A canvas with a red background.');\n\n
\n\n
\n\n// Percentage RGBA notation.\nbackground('rgba(100%,0%,100%,0.5)');\ndescribe('A canvas with a transparent purple background.');\n\n
\n\n
\n\n// A p5.Color object.\nlet c = color(0, 0, 255);\nbackground(c);\ndescribe('A canvas with a blue background.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "color", + "description": "any value created by the color() function", + "type": "p5.Color" + } + ] + }, + { + "params": [ + { + "name": "colorstring", + "description": "color string, possible formats include: integer\nrgb() or rgba(), percentage rgb() or rgba(),\n3-digit hex, 6-digit hex.", + "type": "String" + }, + { + "name": "a", + "description": "opacity of the background relative to current\ncolor range (default is 0-255).", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "gray", + "description": "specifies a value between white and black.", + "type": "Number" + }, + { + "name": "a", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "red value if color mode is RGB, or hue value if color mode is HSB.", + "type": "Number" + }, + { + "name": "v2", + "description": "green value if color mode is RGB, or saturation value if color mode is HSB.", + "type": "Number" + }, + { + "name": "v3", + "description": "blue value if color mode is RGB, or brightness value if color mode is HSB.", + "type": "Number" + }, + { + "name": "a", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "values", + "description": "an array containing the red, green, blue\nand alpha components of the color.", + "type": "Number[]" + } + ] + }, + { + "params": [ + { + "name": "image", + "description": "image created with loadImage()\nor createImage(),\nto set as background.\n(must be same size as the sketch window).", + "type": "p5.Image" + }, + { + "name": "a", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Setting" + }, + { + "name": "clear", + "file": "src/io/files.js", + "line": 1299, + "itemtype": "method", + "chainable": 1, + "description": "

Clears the pixels on the canvas. This function makes every pixel 100%\ntransparent. Calling clear() doesn't clear objects created by createX()\nfunctions such as createGraphics(),\ncreateVideo(), and\ncreateImg(). These objects will remain\nunchanged after calling clear() and can be redrawn.

\n

In WebGL mode, this function can clear the screen to a specific color. It\ninterprets four numeric parameters as normalized RGBA color values. It also\nclears the depth buffer. If you are not using the WebGL renderer, these\nparameters will have no effect.

\n", + "example": [ + "
\n\nfunction draw() {\n circle(mouseX, mouseY, 20);\n describe('A white circle is drawn at the mouse x- and y-coordinates.');\n}\n\nfunction mousePressed() {\n clear();\n background(128);\n describe('The canvas is cleared when the mouse is clicked.');\n}\n\n
\n\n
\n\nlet pg;\n\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n\n pg = createGraphics(60, 60);\n pg.background(200);\n pg.noStroke();\n pg.circle(pg.width / 2, pg.height / 2, 15);\n image(pg, 20, 20);\n describe('A white circle drawn on a gray square. The square gets smaller when the mouse is pressed.');\n}\n\nfunction mousePressed() {\n clear();\n image(pg, 20, 20);\n}\n\n
", + "
\n// create writer object\nlet writer = createWriter('newFile.txt');\nwriter.write(['clear me']);\n// clear writer object here\nwriter.clear();\n// close writer\nwriter.close();\n
\n
\n\nfunction setup() {\n button = createButton('CLEAR ME');\n button.position(21, 40);\n button.mousePressed(createFile);\n}\n\nfunction createFile() {\n let writer = createWriter('newFile.txt');\n writer.write(['clear me']);\n writer.clear();\n writer.close();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "r", + "description": "normalized red value.", + "optional": 1, + "type": "Number" + }, + { + "name": "g", + "description": "normalized green value.", + "optional": 1, + "type": "Number" + }, + { + "name": "b", + "description": "normalized blue value.", + "optional": 1, + "type": "Number" + }, + { + "name": "a", + "description": "normalized alpha value.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "colorMode", + "file": "src/color/setting.js", + "line": 558, + "itemtype": "method", + "chainable": 1, + "description": "

Changes the way p5.js interprets color data. By default, the numeric\nparameters for fill(),\nstroke(),\nbackground(), and\ncolor() are defined by values between 0 and 255\nusing the RGB color model. This is equivalent to calling\ncolorMode(RGB, 255). Pure red is color(255, 0, 0) in this model.

\n

Calling colorMode(RGB, 100) sets colors to be interpreted as RGB color\nvalues between 0 and 100. Pure red is color(100, 0, 0) in this model.

\n

Calling colorMode(HSB) or colorMode(HSL) changes to HSB or HSL system\ninstead of RGB.

\n

p5.Color objects remember the mode that they were\ncreated in. Changing modes doesn't affect their appearance.

\n", + "example": [ + "
\n\nnoStroke();\ncolorMode(RGB, 100);\nfor (let i = 0; i < 100; i += 1) {\n for (let j = 0; j < 100; j += 1) {\n stroke(i, j, 0);\n point(i, j);\n }\n}\ndescribe(\n 'A diagonal green to red gradient from bottom-left to top-right with shading transitioning to black at top-left corner.'\n);\n\n
\n\n
\n\nnoStroke();\ncolorMode(HSB, 100);\nfor (let i = 0; i < 100; i++) {\n for (let j = 0; j < 100; j++) {\n stroke(i, j, 100);\n point(i, j);\n }\n}\ndescribe('A rainbow gradient from left-to-right. Brightness transitions to white at the top.');\n\n
\n\n
\n\ncolorMode(RGB, 255);\nlet myColor = color(180, 175, 230);\nbackground(myColor);\ncolorMode(RGB, 1);\nlet redValue = red(myColor);\nlet greenValue = green(myColor);\nlet blueValue = blue(myColor);\ntext(`Red: ${redValue}`, 10, 10, 80, 80);\ntext(`Green: ${greenValue}`, 10, 40, 80, 80);\ntext(`Blue: ${blueValue}`, 10, 70, 80, 80);\ndescribe('A purple canvas with the red, green, and blue decimal values of the color written on it.');\n\n
\n\n
\n\nnoFill();\ncolorMode(RGB, 255, 255, 255, 1);\nbackground(255);\nstrokeWeight(4);\nstroke(255, 0, 10, 0.3);\ncircle(40, 40, 50);\ncircle(50, 60, 50);\ndescribe('Two overlapping translucent pink circle outlines.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "mode", + "description": "either RGB, HSB or HSL, corresponding to\nRed/Green/Blue and Hue/Saturation/Brightness\n(or Lightness).", + "type": "Constant" + }, + { + "name": "max", + "description": "range for all values.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "mode", + "type": "Constant" + }, + { + "name": "max1", + "description": "range for the red or hue depending on the\ncurrent color mode.", + "type": "Number" + }, + { + "name": "max2", + "description": "range for the green or saturation depending\non the current color mode.", + "type": "Number" + }, + { + "name": "max3", + "description": "range for the blue or brightness/lightness\ndepending on the current color mode.", + "type": "Number" + }, + { + "name": "maxA", + "description": "range for the alpha.", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Setting" + }, + { + "name": "fill", + "file": "src/color/setting.js", + "line": 740, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the color used to fill shapes. Calling fill(255, 165, 0) or\nfill('orange') means all shapes drawn after the fill command will be\nfilled with the color orange.

\n

The version of fill() with one parameter interprets the value one of\nthree ways. If the parameter is a number, it's interpreted as a grayscale\nvalue. If the parameter is a string, it's interpreted as a CSS color\nstring. A p5.Color object can also be provided to\nset the fill color.

\n

The version of fill() with three parameters interprets them as RGB, HSB,\nor HSL colors, depending on the current\ncolorMode(). The default color space is RGB,\nwith each value in the range from 0 to 255.

\n", + "example": [ + "
\n\n// Grayscale integer value.\nfill(51);\nsquare(20, 20, 60);\ndescribe('A dark charcoal gray square with a black outline.');\n\n
\n\n
\n\n// R, G & B integer values.\nfill(255, 204, 0);\nsquare(20, 20, 60);\ndescribe('A yellow square with a black outline.');\n\n
\n\n
\n\n// H, S & B integer values.\ncolorMode(HSB);\nfill(255, 204, 100);\nsquare(20, 20, 60);\ndescribe('A royal blue square with a black outline.');\n\n
\n\n
\n\n// A CSS named color.\nfill('red');\nsquare(20, 20, 60);\ndescribe('A red square with a black outline.');\n\n
\n\n
\n\n// Three-digit hex RGB notation.\nfill('#fae');\nsquare(20, 20, 60);\ndescribe('A pink square with a black outline.');\n\n
\n\n
\n\n// Six-digit hex RGB notation.\nfill('#A251FA');\nsquare(20, 20, 60);\ndescribe('A purple square with a black outline.');\n\n
\n\n
\n\n// Integer RGB notation.\nfill('rgb(0,255,0)');\nsquare(20, 20, 60);\ndescribe('A bright green square with a black outline.');\n\n
\n\n
\n\n// Integer RGBA notation.\nfill('rgba(0,255,0, 0.25)');\nsquare(20, 20, 60);\ndescribe('A soft green rectange with a black outline.');\n\n
\n\n
\n\n// Percentage RGB notation.\nfill('rgb(100%,0%,10%)');\nsquare(20, 20, 60);\ndescribe('A red square with a black outline.');\n\n
\n\n
\n\n// Percentage RGBA notation.\nfill('rgba(100%,0%,100%,0.5)');\nsquare(20, 20, 60);\ndescribe('A dark fuchsia square with a black outline.');\n\n
\n\n
\n\n// p5.Color object.\nlet c = color(0, 0, 255);\nfill(c);\nsquare(20, 20, 60);\ndescribe('A blue square with a black outline.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "red value if color mode is RGB or hue value if color mode is HSB.", + "type": "Number" + }, + { + "name": "v2", + "description": "green value if color mode is RGB or saturation value if color mode is HSB.", + "type": "Number" + }, + { + "name": "v3", + "description": "blue value if color mode is RGB or brightness value if color mode is HSB.", + "type": "Number" + }, + { + "name": "alpha", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "a color string.", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "gray", + "description": "a grayscale value.", + "type": "Number" + }, + { + "name": "alpha", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "values", + "description": "an array containing the red, green, blue &\nand alpha components of the color.", + "type": "Number[]" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "the fill color.", + "type": "p5.Color" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Setting" + }, + { + "name": "noFill", + "file": "src/color/setting.js", + "line": 784, + "itemtype": "method", + "chainable": 1, + "description": "Disables setting the interior color of shapes. This is the same as making\nthe fill completely transparent. If both\nnoStroke() and\nnoFill() are called, nothing will be drawn to the\nscreen.", + "example": [ + "
\n\nsquare(32, 10, 35);\nnoFill();\nsquare(32, 55, 35);\ndescribe('A white square on top of an empty square. Both squares have black outlines.');\n\n
\n\n
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n}\n\nfunction draw() {\n background(0);\n noFill();\n stroke(100, 100, 240);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n box(45, 45, 45);\n describe('A purple cube wireframe spinning on a black canvas.');\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Setting" + }, + { + "name": "noStroke", + "file": "src/color/setting.js", + "line": 824, + "itemtype": "method", + "chainable": 1, + "description": "Disables drawing the stroke (outline). If both\nnoStroke() and\nnoFill() are called, nothing will be drawn to the\nscreen.", + "example": [ + "
\n\nnoStroke();\nsquare(20, 20, 60);\ndescribe('A white square with no outline.');\n\n
\n\n
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n}\n\nfunction draw() {\n background(0);\n noStroke();\n fill(240, 150, 150);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n box(45, 45, 45);\n describe('A pink cube with no edge outlines spinning on a black canvas.');\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Setting" + }, + { + "name": "stroke", + "file": "src/color/setting.js", + "line": 998, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the color used to draw lines and borders around shapes. Calling\nstroke(255, 165, 0) or stroke('orange') means all shapes drawn after\nthe stroke() command will be filled with the color orange. The way these\nparameters are interpreted may be changed with the\ncolorMode() function.

\n

The version of stroke() with one parameter interprets the value one of\nthree ways. If the parameter is a number, it's interpreted as a grayscale\nvalue. If the parameter is a string, it's interpreted as a CSS color\nstring. A p5.Color object can also be provided to\nset the stroke color.

\n

The version of stroke() with two parameters interprets the first one as a\ngrayscale value. The second parameter sets the alpha (transparency) value.

\n

The version of stroke() with three parameters interprets them as RGB, HSB,\nor HSL colors, depending on the current colorMode().

\n

The version of stroke() with four parameters interprets them as RGBA, HSBA,\nor HSLA colors, depending on the current colorMode(). The last parameter\nsets the alpha (transparency) value.

\n", + "example": [ + "
\n\n// Grayscale integer value.\nstrokeWeight(4);\nstroke(51);\nrect(20, 20, 60, 60);\ndescribe('A white rectangle with a dark charcoal gray outline.');\n\n
\n\n
\n\n// R, G & B integer values.\nstroke(255, 204, 0);\nstrokeWeight(4);\nrect(20, 20, 60, 60);\ndescribe('A white rectangle with a yellow outline.');\n\n
\n\n
\n\n// H, S & B integer values.\ncolorMode(HSB);\nstrokeWeight(4);\nstroke(255, 204, 100);\nrect(20, 20, 60, 60);\ndescribe('A white rectangle with a royal blue outline.');\n\n
\n\n
\n\n// A CSS named color.\nstroke('red');\nstrokeWeight(4);\nrect(20, 20, 60, 60);\ndescribe('A white rectangle with a red outline.');\n\n
\n\n
\n\n// Three-digit hex RGB notation.\nstroke('#fae');\nstrokeWeight(4);\nrect(20, 20, 60, 60);\ndescribe('A white rectangle with a pink outline.');\n\n
\n\n
\n\n// Six-digit hex RGB notation.\nstroke('#222222');\nstrokeWeight(4);\nrect(20, 20, 60, 60);\ndescribe('A white rectangle with a black outline.');\n\n
\n\n
\n\n// Integer RGB notation.\nstroke('rgb(0,255,0)');\nstrokeWeight(4);\nrect(20, 20, 60, 60);\ndescribe('A whiite rectangle with a bright green outline.');\n\n
\n\n
\n\n// Integer RGBA notation.\nstroke('rgba(0,255,0,0.25)');\nstrokeWeight(4);\nrect(20, 20, 60, 60);\ndescribe('A white rectangle with a soft green outline.');\n\n
\n\n
\n\n// Percentage RGB notation.\nstroke('rgb(100%,0%,10%)');\nstrokeWeight(4);\nrect(20, 20, 60, 60);\ndescribe('A white rectangle with a red outline.');\n\n
\n\n
\n\n// Percentage RGBA notation.\nstroke('rgba(100%,0%,100%,0.5)');\nstrokeWeight(4);\nrect(20, 20, 60, 60);\ndescribe('A white rectangle with a dark fuchsia outline.');\n\n
\n\n
\n\n// p5.Color object.\nstroke(color(0, 0, 255));\nstrokeWeight(4);\nrect(20, 20, 60, 60);\ndescribe('A white rectangle with a blue outline.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "red value if color mode is RGB or hue value if color mode is HSB.", + "type": "Number" + }, + { + "name": "v2", + "description": "green value if color mode is RGB or saturation value if color mode is HSB.", + "type": "Number" + }, + { + "name": "v3", + "description": "blue value if color mode is RGB or brightness value if color mode is HSB.", + "type": "Number" + }, + { + "name": "alpha", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "a color string.", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "gray", + "description": "a grayscale value.", + "type": "Number" + }, + { + "name": "alpha", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "values", + "description": "an array containing the red, green, blue,\nand alpha components of the color.", + "type": "Number[]" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "the stroke color.", + "type": "p5.Color" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Setting" + }, + { + "name": "erase", + "file": "src/color/setting.js", + "line": 1080, + "itemtype": "method", + "chainable": 1, + "description": "

All drawing that follows erase() will subtract\nfrom the canvas, revealing the web page underneath. The erased areas will\nbecome transparent, allowing the content behind the canvas to show through.\nThe fill(), stroke(), and\nblendMode() have no effect once erase() is\ncalled.

\n

The erase() function has two optional parameters. The first parameter\nsets the strength of erasing by the shape's interior. A value of 0 means\nthat no erasing will occur. A value of 255 means that the shape's interior\nwill fully erase the content underneath. The default value is 255\n(full strength).

\n

The second parameter sets the strength of erasing by the shape's edge. A\nvalue of 0 means that no erasing will occur. A value of 255 means that the\nshape's edge will fully erase the content underneath. The default value is\n255 (full strength).

\n

To cancel the erasing effect, use the noErase()\nfunction.

\n

erase() has no effect on drawing done with the\nimage() and\nbackground() functions.

\n", + "example": [ + "
\n\nbackground(100, 100, 250);\nfill(250, 100, 100);\nsquare(20, 20, 60);\nerase();\ncircle(25, 30, 30);\nnoErase();\ndescribe('A purple canvas with a pink square in the middle. A circle is erased from the top-left, leaving a white hole.');\n\n
\n\n
\n\nlet p = createP('I am a DOM element');\np.style('font-size', '12px');\np.style('width', '65px');\np.style('text-align', 'center');\np.position(18, 26);\n\nbackground(100, 170, 210);\nerase(200, 100);\ncircle(50, 50, 77);\nnoErase();\ndescribe('A blue canvas with a circular hole in the center that reveals the message \"I am a DOM element\".');\n\n
\n\n
\n\nbackground(150, 250, 150);\nfill(100, 100, 250);\nsquare(20, 20, 60);\nstrokeWeight(5);\nerase(150, 255);\ntriangle(50, 10, 70, 50, 90, 10);\nnoErase();\ndescribe('A mint green canvas with a purple square in the center. A triangle in the top-right corner partially erases its interior and a fully erases its outline.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "strengthFill", + "description": "a number (0-255) for the strength of erasing under a shape's interior.\nDefaults to 255, which is full strength.", + "optional": 1, + "type": "Number" + }, + { + "name": "strengthStroke", + "description": "a number (0-255) for the strength of erasing under a shape's edge.\nDefaults to 255, which is full strength.", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Setting" + }, + { + "name": "noErase", + "file": "src/color/setting.js", + "line": 1109, + "itemtype": "method", + "chainable": 1, + "description": "Ends erasing that was started with erase().\nThe fill(), stroke(), and\nblendMode() settings will return to what they\nwere prior to calling erase().", + "example": [ + "
\n\nbackground(235, 145, 15);\nnoStroke();\nfill(30, 45, 220);\nrect(30, 10, 10, 80);\nerase();\ncircle(50, 50, 60);\nnoErase();\nrect(70, 10, 10, 80);\ndescribe('An orange canvas with two tall blue rectangles. A circular hole in the center erases the rectangle on the left but not the one on the right.');\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Color", + "submodule": "Setting" + }, + { + "name": "print", + "file": "src/io/files.js", + "line": 1265, + "itemtype": "method", + "description": "

Displays text in the web browser's console.

\n

print() is helpful for printing values while debugging. Each call to\nprint() creates a new line of text.

\n

Note: Call print('\\n') to print a blank line. Calling print() without\nan argument opens the browser's dialog for printing documents.

\n", + "example": [ + "
\n\nfunction setup() {\n // Prints \"hello, world\" to the console.\n print('hello, world');\n}\n\n
\n\n
\n\nfunction setup() {\n let name = 'ada';\n // Prints \"hello, ada\" to the console.\n print(`hello, ${name}`);\n}\n\n
", + "
\n\n// creates a file called 'newFile.txt'\nlet writer = createWriter('newFile.txt');\n// creates a file containing\n// My name is:\n// Teddy\nwriter.print('My name is:');\nwriter.print('Teddy');\n// close the PrintWriter and save the file\nwriter.close();\n\n
\n
\n\nlet writer;\n\nfunction setup() {\n createCanvas(400, 400);\n // create a PrintWriter\n writer = createWriter('newFile.txt');\n}\n\nfunction draw() {\n writer.print([mouseX, mouseY]);\n}\n\nfunction mouseClicked() {\n writer.close();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "contents", + "description": "content to print to the console.", + "type": "Any" + } + ] + }, + { + "params": [ + { + "name": "data", + "description": "all data to be printed by the PrintWriter", + "type": "Array" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "cursor", + "file": "src/core/environment.js", + "line": 272, + "itemtype": "method", + "description": "

Changes the cursor's appearance.

\n

The first parameter, type, sets the type of cursor to display. The\nbuilt-in options are ARROW, CROSS, HAND, MOVE, TEXT, and WAIT.\ncursor() also recognizes standard CSS cursor properties passed as\nstrings: 'help', 'wait', 'crosshair', 'not-allowed', 'zoom-in',\nand 'grab'. If the path to an image is passed, as in\ncursor('assets/target.png'), then the image will be used as the cursor.\nImages must be in .cur, .gif, .jpg, .jpeg, or .png format.

\n

The parameters x and y are optional. If an image is used for the\ncursor, x and y set the location pointed to within the image. They are\nboth 0 by default, so the cursor points to the image's top-left corner. x\nand y must be less than the image's width and height, respectively.

\n", + "example": [ + "
\n\nfunction draw() {\n background(200);\n\n // Set the cursor to crosshairs: +\n cursor(CROSS);\n\n describe('A gray square. The cursor appears as crosshairs.');\n}\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n // Divide the canvas into quadrants.\n line(50, 0, 50, 100);\n line(0, 50, 100, 50);\n\n // Change cursor based on mouse position.\n if (mouseX < 50 && mouseY < 50) {\n cursor(CROSS);\n } else if (mouseX > 50 && mouseY < 50) {\n cursor('progress');\n } else if (mouseX > 50 && mouseY > 50) {\n cursor('https://avatars0.githubusercontent.com/u/1617169?s=16');\n } else {\n cursor('grab');\n }\n\n describe('A gray square divided into quadrants. The cursor image changes when the mouse moves to each quadrant.');\n}\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n // Change the cursor's active spot\n // when the mouse is pressed.\n if (mouseIsPressed === true) {\n cursor('https://avatars0.githubusercontent.com/u/1617169?s=16', 8, 8);\n } else {\n cursor('https://avatars0.githubusercontent.com/u/1617169?s=16');\n }\n\n describe('An image of three purple curves follows the mouse. The image shifts when the mouse is pressed.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "type", + "description": "Built-in: either ARROW, CROSS, HAND, MOVE, TEXT, or WAIT.\nNative CSS properties: 'grab', 'progress', and so on.\nPath to cursor image.", + "type": "String|Constant" + }, + { + "name": "x", + "description": "horizontal active spot of the cursor.", + "optional": 1, + "type": "Number" + }, + { + "name": "y", + "description": "vertical active spot of the cursor.", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "frameRate", + "file": "src/core/environment.js", + "line": 372, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the number of frames to draw per second.

\n

Calling frameRate() with one numeric argument, as in frameRate(30),\nattempts to draw 30 frames per second (FPS). The target frame rate may not\nbe achieved depending on the sketch's processing needs. Most computers\ndefault to a frame rate of 60 FPS. Frame rates of 24 FPS and above are\nfast enough for smooth animations.

\n

Calling frameRate() without an argument returns the current frame rate.\nThe value returned is an approximation.

\n", + "example": [ + "
\n\nfunction draw() {\n background(200);\n\n // Set the x variable based\n // on the current frameCount.\n let x = frameCount % 100;\n\n // If the mouse is pressed,\n // decrease the frame rate.\n if (mouseIsPressed === true) {\n frameRate(10);\n } else {\n frameRate(60);\n }\n\n // Use x to set the circle's\n // position.\n circle(x, 50, 20);\n\n describe('A white circle on a gray background. The circle moves from left to right in a loop. It slows down when the mouse is pressed.');\n}\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n // If the mouse is pressed, do lots\n // of math to slow down drawing.\n if (mouseIsPressed === true) {\n for (let i = 0; i < 1000000; i += 1) {\n random();\n }\n }\n\n // Get the current frame rate\n // and display it.\n let fps = frameRate();\n text(fps, 50, 50);\n\n describe('A number written in black written on a gray background. The number decreases when the mouse is pressed.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fps", + "description": "number of frames to draw per second.", + "type": "Number" + } + ] + }, + { + "params": [], + "return": { + "description": "current frame rate.", + "type": "Number" + } + } + ], + "return": { + "description": "current frame rate.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "getTargetFrameRate", + "file": "src/core/environment.js", + "line": 436, + "itemtype": "method", + "description": "Returns the target frame rate. The value is either the system frame rate or\nthe last value passed to frameRate().", + "example": [ + "
\n\nfunction draw() {\n background(200);\n\n // Set the frame rate to 20.\n frameRate(20);\n\n // Get the target frame rate and\n // display it.\n let fps = getTargetFrameRate();\n text(fps, 43, 54);\n\n describe('The number 20 written in black on a gray background.');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "_targetFrameRate", + "type": "Number" + } + } + ], + "return": { + "description": "_targetFrameRate", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "noCursor", + "file": "src/core/environment.js", + "line": 462, + "itemtype": "method", + "description": "Hides the cursor from view.", + "example": [ + "
\n\nfunction setup() {\n // Hide the cursor.\n noCursor();\n}\n\nfunction draw() {\n background(200);\n\n circle(mouseX, mouseY, 10);\n\n describe('A white circle on a gray background. The circle follows the mouse as it moves. The cursor is hidden.');\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "windowResized", + "file": "src/core/environment.js", + "line": 719, + "itemtype": "method", + "description": "

The code in windowResized() is called once each time the browser window\nis resized. It's a good place to resize the canvas or make other\nadjustments to accommodate the new window size.

\n

The event parameter is optional. If added to the function definition, it\ncan be used for debugging or other purposes.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(windowWidth, windowHeight);\n}\n\nfunction draw() {\n background(200);\n\n describe('A gray canvas that takes up the entire browser window. It changes size to match the browser window.');\n}\n\n// Resize the canvas when the\n// browser's size changes.\nfunction windowResized() {\n resizeCanvas(windowWidth, windowHeight);\n}\n\n
" + ], + "alt": "This example does not render anything.\n\n
\n\nfunction setup() {\n createCanvas(windowWidth, windowHeight);\n}\n\nfunction draw() {\n background(200);\n\n describe('A gray canvas that takes up the entire browser window. It changes size to match the browser window.');\n}\n\nfunction windowResized(event) {\n // Resize the canvas when the\n // browser's size changes.\n resizeCanvas(windowWidth, windowHeight);\n\n // Print the resize event to the console for debugging.\n print(event);\n}\n\n
\nThis example does not render anything.", + "overloads": [ + { + "params": [ + { + "name": "event", + "description": "optional resize Event.", + "optional": 1, + "type": "UIEvent" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "fullscreen", + "file": "src/core/environment.js", + "line": 923, + "itemtype": "method", + "description": "

Toggles full-screen mode or returns the current mode.

\n

Calling fullscreen(true) makes the sketch full-screen. Calling\nfullscreen(false) makes the sketch its original size.

\n

Calling fullscreen() without an argument returns true if the sketch\nis in full-screen mode and false if not.

\n

Note: Due to browser restrictions, fullscreen() can only be called with\nuser input such as a mouse press.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n describe('A gray canvas that switches between default and full-screen display when clicked.');\n}\n\n// If the mouse is pressed,\n// toggle full-screen mode.\nfunction mousePressed() {\n if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {\n let fs = fullscreen();\n fullscreen(!fs);\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "val", + "description": "whether the sketch should be in fullscreen mode.", + "optional": 1, + "type": "Boolean" + } + ], + "return": { + "description": "current fullscreen state.", + "type": "Boolean" + } + } + ], + "return": { + "description": "current fullscreen state.", + "type": "Boolean" + }, + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "pixelDensity", + "file": "src/core/environment.js", + "line": 995, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the pixel scaling for high pixel density displays.

\n

By default, the pixel density is set to match display density. Calling\npixelDensity(1) turn this off.

\n

Calling pixelDensity() without an argument returns the current pixel\ndensity.

\n", + "example": [ + "
\n\nfunction setup() {\n // Set the pixel density to 1.\n pixelDensity(1);\n\n // Create a canvas and draw\n // a circle.\n createCanvas(100, 100);\n background(200);\n circle(50, 50, 70);\n\n describe('A fuzzy white circle on a gray canvas.');\n}\n\n
\n\n
\n\nfunction setup() {\n // Set the pixel density to 3.\n pixelDensity(3);\n\n // Create a canvas, paint the\n // background, and draw a\n // circle.\n createCanvas(100, 100);\n background(200);\n circle(50, 50, 70);\n\n describe('A sharp white circle on a gray canvas.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "val", + "description": "desired pixel density.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [], + "return": { + "description": "current pixel density of the sketch.", + "type": "Number" + } + } + ], + "return": { + "description": "current pixel density of the sketch.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "displayDensity", + "file": "src/core/environment.js", + "line": 1047, + "itemtype": "method", + "description": "Returns the display's current pixel density.", + "example": [ + "
\n\nfunction setup() {\n // Set the pixel density to 1.\n pixelDensity(1);\n\n // Create a canvas and draw\n // a circle.\n createCanvas(100, 100);\n background(200);\n circle(50, 50, 70);\n\n describe('A fuzzy white circle drawn on a gray background. The circle becomes sharper when the mouse is pressed.');\n}\n\nfunction mousePressed() {\n // Get the current display density.\n let d = displayDensity();\n\n // Use the display density to set\n // the sketch's pixel density.\n pixelDensity(d);\n\n // Paint the background and\n // draw a circle.\n background(200);\n circle(50, 50, 70);\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "current pixel density of the display.", + "type": "Number" + } + } + ], + "return": { + "description": "current pixel density of the display.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "getURL", + "file": "src/core/environment.js", + "line": 1105, + "itemtype": "method", + "description": "Returns the sketch's current\nURL\nas a string.", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Get the sketch's URL\n // and display it.\n let url = getURL();\n textWrap(CHAR);\n text(url, 0, 40, 100);\n\n describe('The URL \"https://p5js.org/reference/#/p5/getURL\" written in black on a gray background.');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "url", + "type": "String" + } + } + ], + "return": { + "description": "url", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "getURLPath", + "file": "src/core/environment.js", + "line": 1137, + "itemtype": "method", + "description": "

Returns the current\nURL\npath as an array of strings.

\n

For example, consider a sketch hosted at the URL\nhttps://example.com/sketchbook. Calling getURLPath() returns\n['sketchbook']. For a sketch hosted at the URL\nhttps://example.com/sketchbook/monday, getURLPath() returns\n['sketchbook', 'monday'].

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Get the sketch's URL path\n // and display the first\n // part.\n let path = getURLPath();\n text(path[0], 25, 54);\n\n describe('The word \"reference\" written in black on a gray background.');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "path components.", + "type": "String[]" + } + } + ], + "return": { + "description": "path components.", + "type": "String[]" + }, + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "getURLParams", + "file": "src/core/environment.js", + "line": 1176, + "itemtype": "method", + "description": "

Returns the current\nURL parameters\nin an Object.

\n

For example, calling getURLParams() in a sketch hosted at the URL\nhttp://p5js.org?year=2014&month=May&day=15 returns\n{ year: 2014, month: 'May', day: 15 }.

\n", + "example": [ + "
\n\n// Imagine this sketch is hosted at the following URL:\n// https://p5js.org?year=2014&month=May&day=15\n\nfunction setup() {\n background(200);\n\n // Get the sketch's URL\n // parameters and display\n // them.\n let params = getURLParams();\n text(params.day, 10, 20);\n text(params.month, 10, 40);\n text(params.year, 10, 60);\n\n describe('The text \"15\", \"May\", and \"2014\" written in black on separate lines.');\n}\n\n
" + ], + "alt": "This example does not render anything.", + "overloads": [ + { + "params": [], + "return": { + "description": "URL params", + "type": "Object" + } + } + ], + "return": { + "description": "URL params", + "type": "Object" + }, + "class": "p5", + "static": false, + "module": "Environment", + "submodule": "Environment" + }, + { + "name": "preload", + "file": "src/core/main.js", + "line": 752, + "itemtype": "method", + "description": "

Called directly before setup(), the preload() function is used to handle\nasynchronous loading of external files in a blocking way. If a preload\nfunction is defined, setup() will wait until any load calls within have\nfinished. Nothing besides load calls (loadImage, loadJSON, loadFont,\nloadStrings, etc.) should be inside the preload function. If asynchronous\nloading is preferred, the load methods can instead be called in setup()\nor anywhere else with the use of a callback parameter.

\n

By default the text \"loading...\" will be displayed. To make your own\nloading page, include an HTML element with id \"p5_loading\" in your\npage. More information here.

\n", + "example": [ + "
\nlet img;\nlet c;\nfunction preload() {\n // preload() runs once\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n // setup() waits until preload() is done\n img.loadPixels();\n // get color of middle pixel\n c = img.get(img.width / 2, img.height / 2);\n}\n\nfunction draw() {\n background(c);\n image(img, 25, 25, 50, 50);\n}\n
" + ], + "alt": "nothing displayed", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Structure", + "submodule": "Structure" + }, + { + "name": "setup", + "file": "src/core/main.js", + "line": 752, + "itemtype": "method", + "description": "

The setup() function is called once when the program starts. It's used to\ndefine initial environment properties such as screen size and background\ncolor and to load media such as images and fonts as the program starts.\nThere can only be one setup() function for each program and it shouldn't\nbe called again after its initial execution.

\n

Note: Variables declared within setup() are not accessible within other\nfunctions, including draw().

\n", + "example": [ + "
\nlet a = 0;\n\nfunction setup() {\n background(0);\n noStroke();\n fill(102);\n}\n\nfunction draw() {\n rect(a++ % width, 10, 2, 80);\n}\n
" + ], + "alt": "nothing displayed", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Structure", + "submodule": "Structure" + }, + { + "name": "draw", + "file": "src/core/main.js", + "line": 752, + "itemtype": "method", + "description": "

Called directly after setup(), the draw() function continuously executes\nthe lines of code contained inside its block until the program is stopped\nor noLoop() is called. Note if noLoop() is called in setup(), draw() will\nstill be executed once before stopping. draw() is called automatically and\nshould never be called explicitly.

\n

It should always be controlled with noLoop(), redraw() and loop(). After\nnoLoop() stops the code in draw() from executing, redraw() causes the\ncode inside draw() to execute once, and loop() will cause the code\ninside draw() to resume executing continuously.

\n

The number of times draw() executes in each second may be controlled with\nthe frameRate() function.

\n

There can only be one draw() function for each sketch, and draw() must\nexist if you want the code to run continuously, or to process events such\nas mousePressed(). Sometimes, you might have an empty call to draw() in\nyour program, as shown in the above example.

\n

It is important to note that the drawing coordinate system will be reset\nat the beginning of each draw() call. If any transformations are performed\nwithin draw() (ex: scale, rotate, translate), their effects will be\nundone at the beginning of draw(), so transformations will not accumulate\nover time. On the other hand, styling applied (ex: fill, stroke, etc) will\nremain in effect.

\n", + "example": [ + "
\nlet yPos = 0;\nfunction setup() {\n // setup() runs once\n frameRate(30);\n}\nfunction draw() {\n // draw() loops forever, until stopped\n background(204);\n yPos = yPos - 1;\n if (yPos < 0) {\n yPos = height;\n }\n line(0, yPos, width, yPos);\n}\n
" + ], + "alt": "nothing displayed", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Structure", + "submodule": "Structure" + }, + { + "name": "parent", + "file": "src/core/p5.Element.js", + "line": 216, + "itemtype": "method", + "chainable": 1, + "description": "

Attaches this element to a parent element.

\n

For example, a <div></div> element may be used as a box to\nhold two pieces of text, a header and a paragraph. The\n<div></div> is the parent element of both the header and\nparagraph.

\n

The parameter parent can have one of three types. parent can be a\nstring with the parent element's ID, as in\nmyElement.parent('container'). It can also be another\np5.Element object, as in\nmyElement.parent(myDiv). Finally, parent can be an HTMLElement\nobject, as in myElement.parent(anotherElement).

\n

Calling myElement.parent() without an argument returns this element's\nparent.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Create a div element.\n let div = createDiv();\n // Place the div in the top-left corner.\n div.position(10, 20);\n // Set its width and height.\n div.size(80, 60);\n // Set its background color to white\n div.style('background-color', 'white');\n // Align any text to the center.\n div.style('text-align', 'center');\n // Set its ID to \"container\".\n div.id('container');\n\n // Create a paragraph element.\n let p = createP('p5*js');\n // Make the div its parent\n // using its ID \"container\".\n p.parent('container');\n\n describe('The text \"p5*js\" written in black at the center of a white rectangle. The rectangle is inside a gray square.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create rectangular div element.\n let div = createDiv();\n // Place the div in the top-left corner.\n div.position(10, 20);\n // Set its width and height.\n div.size(80, 60);\n // Set its background color and align\n // any text to the center.\n div.style('background-color', 'white');\n div.style('text-align', 'center');\n\n // Create a paragraph element.\n let p = createP('p5*js');\n // Make the div its parent.\n p.parent(div);\n\n describe('The text \"p5*js\" written in black at the center of a white rectangle. The rectangle is inside a gray square.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create rectangular div element.\n let div = createDiv();\n // Place the div in the top-left corner.\n div.position(10, 20);\n // Set its width and height.\n div.size(80, 60);\n // Set its background color and align\n // any text to the center.\n div.style('background-color', 'white');\n div.style('text-align', 'center');\n\n // Create a paragraph element.\n let p = createP('p5*js');\n // Make the div its parent\n // using the underlying\n // HTMLElement.\n p.parent(div.elt);\n\n describe('The text \"p5*js\" written in black at the center of a white rectangle. The rectangle is inside a gray square.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "parent", + "description": "ID, p5.Element,\nor HTMLElement of desired parent element.", + "type": "String|p5.Element|Object" + } + ] + }, + { + "params": [], + "return": { + "description": "", + "type": "p5.Element" + } + } + ], + "return": { + "description": "", + "type": "p5.Element" + }, + "class": "p5.Element", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "id", + "file": "src/core/p5.Element.js", + "line": 269, + "itemtype": "method", + "chainable": 1, + "description": "

Sets this element's ID using a given string.

\n

Calling myElement.id() without an argument returns its ID as a string.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Set the canvas' ID\n // to \"mycanvas\".\n cnv.id('mycanvas');\n\n // Get the canvas' ID.\n let id = cnv.id();\n text(id, 24, 54);\n\n describe('The text \"mycanvas\" written in black on a gray background.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "id", + "description": "ID of the element.", + "type": "String" + } + ] + }, + { + "params": [], + "return": { + "description": "ID of the element.", + "type": "String" + } + } + ], + "return": { + "description": "ID of the element.", + "type": "String" + }, + "class": "p5.Element", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "class", + "file": "src/core/p5.Element.js", + "line": 320, + "itemtype": "method", + "chainable": 1, + "description": "

Adds a\nclass attribute\nto the element.

\n

Calling myElement.class() without an argument returns a string with its current classes.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Add the class \"small\" to the\n // canvas element.\n cnv.class('small');\n\n // Get the canvas element's class\n // and display it.\n let c = cnv.class();\n text(c, 35, 54);\n\n describe('The word \"small\" written in black on a gray canvas.');\n\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "class", + "description": "class to add.", + "type": "String" + } + ] + }, + { + "params": [], + "return": { + "description": "element's classes, if any.", + "type": "String" + } + } + ], + "return": { + "description": "element's classes, if any.", + "type": "String" + }, + "class": "p5.Element", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "mousePressed", + "file": "src/events/mouse.js", + "line": 653, + "itemtype": "method", + "chainable": 1, + "description": "

Calls a function when the mouse is pressed over the element.\nCalling myElement.mousePressed(false) disables the function.

\n

Note: Some mobile browsers may also trigger this event when the element\nreceives a quick tap.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call randomColor() when the canvas\n // is pressed.\n cnv.mousePressed(randomColor);\n\n describe('A gray square changes color when the mouse is pressed.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Paint the background either\n // red, yellow, blue, or green\n // when the canvas is pressed.\n cnv.mousePressed(() => {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n });\n\n describe('A gray square changes color when the mouse is pressed.');\n}\n\n
", + "
\n\n// Click anywhere in the webpage to change\n// the color value of the rectangle\n\nlet colorValue = 0;\nfunction draw() {\n fill(colorValue);\n rect(25, 25, 50, 50);\n describe('black 50-by-50 rect turns white with mouse click/press.');\n}\nfunction mousePressed() {\n if (colorValue === 0) {\n colorValue = 255;\n } else {\n colorValue = 0;\n }\n}\n\n
\n\n
\n\nfunction mousePressed() {\n ellipse(mouseX, mouseY, 5, 5);\n // prevent default\n return false;\n}\n\n
\n\n
\n\n// returns a MouseEvent object\n// as a callback argument\nfunction mousePressed(event) {\n console.log(event);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the mouse is\npressed over the element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + }, + { + "params": [ + { + "name": "event", + "description": "optional MouseEvent callback argument.", + "optional": 1, + "type": "MouseEvent" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "Events", + "submodule": "Mouse" + }, + { + "name": "doubleClicked", + "file": "src/events/mouse.js", + "line": 872, + "itemtype": "method", + "chainable": 1, + "description": "Calls a function when the mouse is pressed twice over the element.\nCalling myElement.doubleClicked(false) disables the function.", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call randomColor() when the\n // canvas is double-clicked.\n cnv.doubleClicked(randomColor);\n\n describe('A gray square changes color when the user double-clicks the canvas.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Paint the background either\n // red, yellow, blue, or green\n // when the canvas is double-clicked.\n cnv.doubleClicked(() => {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n });\n\n describe('A gray square changes color when the user double-clicks the canvas.');\n}\n\n
", + "
\n\n// Click within the image to change\n// the value of the rectangle\n// after the mouse has been double clicked\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe('black 50-by-50 rect turns white with mouse doubleClick/press.');\n}\n\nfunction doubleClicked() {\n if (value === 0) {\n value = 255;\n } else {\n value = 0;\n }\n}\n\n
\n\n
\n\nfunction doubleClicked() {\n ellipse(mouseX, mouseY, 5, 5);\n // prevent default\n return false;\n}\n\n
\n\n
\n\n// returns a MouseEvent object\n// as a callback argument\nfunction doubleClicked(event) {\n console.log(event);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the mouse is\ndouble clicked over the element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + }, + { + "params": [ + { + "name": "event", + "description": "optional MouseEvent callback argument.", + "optional": 1, + "type": "MouseEvent" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "Events", + "submodule": "Mouse" + }, + { + "name": "mouseWheel", + "file": "src/events/mouse.js", + "line": 938, + "itemtype": "method", + "chainable": 1, + "description": "

Calls a function when the mouse wheel scrolls over th element.

\n

The callback function, fxn, is passed an event object. event has\ntwo numeric properties, deltaY and deltaX. event.deltaY is\nnegative if the mouse wheel rotates away from the user. It's positive if\nthe mouse wheel rotates toward the user. event.deltaX is positive if\nthe mouse wheel moves to the right. It's negative if the mouse wheel moves\nto the left.

\n

Calling myElement.mouseWheel(false) disables the function.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call randomColor() when the\n // mouse wheel moves.\n cnv.mouseWheel(randomColor);\n\n describe('A gray square changes color when the user scrolls the mouse wheel over the canvas.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Paint the background either\n // red, yellow, blue, or green\n // when the mouse wheel moves.\n cnv.mouseWheel(() => {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n });\n\n describe('A gray square changes color when the user scrolls the mouse wheel over the canvas.');\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call changeBackground() when the\n // mouse wheel moves.\n cnv.mouseWheel(changeBackground);\n\n describe('A gray square. When the mouse wheel scrolls over the square, it changes color and displays shapes.');\n}\n\nfunction changeBackground(event) {\n // Change the background color\n // based on deltaY.\n if (event.deltaY > 0) {\n background('deeppink');\n } else if (event.deltaY < 0) {\n background('cornflowerblue');\n } else {\n background(200);\n }\n\n // Draw a shape based on deltaX.\n if (event.deltaX > 0) {\n circle(50, 50, 20);\n } else if (event.deltaX < 0) {\n square(40, 40, 20);\n }\n}\n\n
", + "
\n\nlet pos = 25;\n\nfunction draw() {\n background(237, 34, 93);\n fill(0);\n rect(25, pos, 50, 50);\n describe(`black 50-by-50 rect moves up and down with vertical scroll.\n fuchsia background`);\n}\n\nfunction mouseWheel(event) {\n print(event.delta);\n //move the square according to the vertical scroll amount\n pos += event.delta;\n //uncomment to block page scrolling\n //return false;\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the mouse wheel is\nscrolled over the element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + }, + { + "params": [ + { + "name": "event", + "description": "optional WheelEvent callback argument.", + "optional": 1, + "type": "WheelEvent" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "Events", + "submodule": "Mouse" + }, + { + "name": "mouseReleased", + "file": "src/events/mouse.js", + "line": 730, + "itemtype": "method", + "chainable": 1, + "description": "

Calls a function when the mouse is released over the element. Calling\nmyElement.mouseReleased(false) disables the function.

\n

Note: Some mobile browsers may also trigger this event when the element\nreceives a quick tap.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call randomColor() when a\n // mouse press ends.\n cnv.mouseReleased(randomColor);\n\n describe('A gray square changes color when the user releases a mouse press.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Paint the background either\n // red, yellow, blue, or green\n // when a mouse press ends.\n cnv.mouseReleased(() => {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n });\n\n describe('A gray square changes color when the user releases a mouse press.');\n}\n\n
", + "
\n\n// Click within the image to change\n// the value of the rectangle\n// after the mouse has been clicked\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe('black 50-by-50 rect turns white with mouse click/press.');\n}\nfunction mouseReleased() {\n if (value === 0) {\n value = 255;\n } else {\n value = 0;\n }\n}\n\n
\n\n
\n\nfunction mouseReleased() {\n ellipse(mouseX, mouseY, 5, 5);\n // prevent default\n return false;\n}\n\n
\n\n
\n\n// returns a MouseEvent object\n// as a callback argument\nfunction mouseReleased(event) {\n console.log(event);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the mouse is\npressed over the element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + }, + { + "params": [ + { + "name": "event", + "description": "optional MouseEvent callback argument.", + "optional": 1, + "type": "MouseEvent" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "Events", + "submodule": "Mouse" + }, + { + "name": "mouseClicked", + "file": "src/events/mouse.js", + "line": 806, + "itemtype": "method", + "chainable": 1, + "description": "

Calls a function when the mouse is pressed and released over the element.\nCalling myElement.mouseReleased(false) disables the function.

\n

Note: Some mobile browsers may also trigger this event when the element\nreceives a quick tap.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call randomColor() when a\n // mouse press ends.\n cnv.mouseClicked(randomColor);\n\n describe('A gray square changes color when the user releases a mouse press.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Paint the background either\n // red, yellow, blue, or green\n // when a mouse press ends.\n cnv.mouseClicked(() => {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n });\n\n describe('A gray square changes color when the user releases a mouse press.');\n}\n\n
", + "
\n\n// Click within the image to change\n// the value of the rectangle\n// after the mouse has been clicked\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe('black 50-by-50 rect turns white with mouse click/press.');\n}\n\nfunction mouseClicked() {\n if (value === 0) {\n value = 255;\n } else {\n value = 0;\n }\n}\n\n
\n\n
\n\nfunction mouseClicked() {\n ellipse(mouseX, mouseY, 5, 5);\n // prevent default\n return false;\n}\n\n
\n\n
\n\n// returns a MouseEvent object\n// as a callback argument\nfunction mouseClicked(event) {\n console.log(event);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the mouse is\npressed and released over the element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + }, + { + "params": [ + { + "name": "event", + "description": "optional MouseEvent callback argument.", + "optional": 1, + "type": "MouseEvent" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "Events", + "submodule": "Mouse" + }, + { + "name": "mouseMoved", + "file": "src/events/mouse.js", + "line": 573, + "itemtype": "method", + "chainable": 1, + "description": "Calls a function when the mouse moves over the element. Calling\nmyElement.mouseMoved(false) disables the function.", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call randomColor() when the\n // mouse moves.\n cnv.mouseMoved(randomColor);\n\n describe('A gray square changes color when the mouse moves over the canvas.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Paint the background either\n // red, yellow, blue, or green\n // when the mouse moves.\n cnv.mouseMoved(() => {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n });\n\n describe('A gray square changes color when the mouse moves over the canvas.');\n}\n\n
", + "
\n\n// Move the mouse across the page\n// to change its value\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`black 50-by-50 rect becomes lighter with mouse movements until\n white then resets no image displayed`);\n}\nfunction mouseMoved() {\n value = value + 5;\n if (value > 255) {\n value = 0;\n }\n}\n\n
\n\n
\n\nfunction mouseMoved() {\n ellipse(mouseX, mouseY, 5, 5);\n // prevent default\n return false;\n}\n\n
\n\n
\n\n// returns a MouseEvent object\n// as a callback argument\nfunction mouseMoved(event) {\n console.log(event);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the mouse\nmoves over the element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + }, + { + "params": [ + { + "name": "event", + "description": "optional MouseEvent callback argument.", + "optional": 1, + "type": "MouseEvent" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "Events", + "submodule": "Mouse" + }, + { + "name": "mouseOver", + "file": "src/core/p5.Element.js", + "line": 823, + "itemtype": "method", + "chainable": 1, + "description": "Calls a function when the mouse moves onto the element. Calling\nmyElement.mouseOver(false) disables the function.", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call randomColor() when the\n // mouse moves onto the canvas.\n cnv.mouseOver(randomColor);\n\n describe('A gray square changes color when the mouse moves onto the canvas.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Paint the background either\n // red, yellow, blue, or green\n // when the mouse moves onto\n // the canvas.\n cnv.mouseOver(() => {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n });\n\n describe('A gray square changes color when the mouse moves onto the canvas.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the mouse\nmoves onto the element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "mouseOut", + "file": "src/core/p5.Element.js", + "line": 886, + "itemtype": "method", + "chainable": 1, + "description": "Calls a function when the mouse moves off the element. Calling\nmyElement.mouseOut(false) disables the function.", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call randomColor() when the\n // mouse moves off the canvas.\n cnv.mouseOut(randomColor);\n\n describe('A gray square changes color when the mouse moves off the canvas.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Paint the background either\n // red, yellow, blue, or green\n // when the mouse moves off\n // the canvas.\n cnv.mouseOut(() => {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n });\n\n describe('A gray square changes color when the mouse moves off the canvas.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the mouse\nmoves off the element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "touchStarted", + "file": "src/events/touch.js", + "line": 124, + "itemtype": "method", + "chainable": 1, + "description": "

Calls a function when the element is touched. Calling\nmyElement.touchStarted(false) disables the function.

\n

Note: Touch functions only work on mobile devices.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call randomColor() when the\n // user touches the canvas.\n cnv.touchStarted(randomColor);\n\n describe('A gray square changes color when the user touches the canvas.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Paint the background either\n // red, yellow, blue, or green\n // when the user touches the\n // canvas.\n cnv.touchStarted(() => {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n });\n\n describe('A gray square changes color when the user touches the canvas.');\n}\n\n
", + "
\n\n// Touch within the image to change\n// the value of the rectangle\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe('50-by-50 black rect turns white with touch event.');\n}\nfunction touchStarted() {\n if (value === 0) {\n value = 255;\n } else {\n value = 0;\n }\n}\n\n
\n\n
\n\nfunction touchStarted() {\n ellipse(mouseX, mouseY, 5, 5);\n // prevent default\n return false;\n}\ndescribe('no image displayed');\n\n
\n\n
\n\n// returns a TouchEvent object\n// as a callback argument\nfunction touchStarted(event) {\n console.log(event);\n}\ndescribe('no image displayed');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the touch\nstarts.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + }, + { + "params": [ + { + "name": "event", + "description": "optional TouchEvent callback argument.", + "optional": 1, + "type": "TouchEvent" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "Events", + "submodule": "Touch" + }, + { + "name": "touchMoved", + "file": "src/events/touch.js", + "line": 202, + "itemtype": "method", + "chainable": 1, + "description": "

Calls a function when the user touches the element and moves their\nfinger. Calling myElement.touchMoved(false) disables the\nfunction.

\n

Note: Touch functions only work on mobile devices.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call randomColor() when the\n // user touches the canvas\n // and moves.\n cnv.touchMoved(randomColor);\n\n describe('A gray square changes color when the user touches the canvas and moves.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Paint the background either\n // red, yellow, blue, or green\n // when the user touches the\n // canvas and moves.\n cnv.touchMoved(() => {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n });\n\n describe('A gray square changes color when the user touches the canvas and moves.');\n}\n\n
", + "
\n\n// Move your finger across the page\n// to change its value\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe('50-by-50 black rect turns lighter with touch until white. resets');\n}\nfunction touchMoved() {\n value = value + 5;\n if (value > 255) {\n value = 0;\n }\n}\n\n
\n\n
\n\nfunction touchMoved() {\n ellipse(mouseX, mouseY, 5, 5);\n // prevent default\n return false;\n}\ndescribe('no image displayed');\n\n
\n\n
\n\n// returns a TouchEvent object\n// as a callback argument\nfunction touchMoved(event) {\n console.log(event);\n}\ndescribe('no image displayed');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the touch\nmoves over the element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + }, + { + "params": [ + { + "name": "event", + "description": "optional TouchEvent callback argument.", + "optional": 1, + "type": "TouchEvent" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "Events", + "submodule": "Touch" + }, + { + "name": "touchEnded", + "file": "src/events/touch.js", + "line": 274, + "itemtype": "method", + "chainable": 1, + "description": "

Calls a function when the user stops touching the element. Calling\nmyElement.touchMoved(false) disables the function.

\n

Note: Touch functions only work on mobile devices.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call randomColor() when the\n // user touches the canvas,\n // then lifts their finger.\n cnv.touchEnded(randomColor);\n\n describe('A gray square changes color when the user touches the canvas, then lifts their finger.');\n}\n\n// Paint the background either\n// red, yellow, blue, or green.\nfunction randomColor() {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n}\n\n
\n\n
\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Paint the background either\n // red, yellow, blue, or green\n // when the user touches the\n // canvas, then lifts their\n // finger.\n cnv.touchEnded(() => {\n let c = random(['red', 'yellow', 'blue', 'green']);\n background(c);\n });\n\n describe('A gray square changes color when the user touches the canvas, then lifts their finger.');\n}\n\n
", + "
\n\n// Release touch within the image to\n// change the value of the rectangle\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe('50-by-50 black rect turns white with touch.');\n}\nfunction touchEnded() {\n if (value === 0) {\n value = 255;\n } else {\n value = 0;\n }\n}\n\n
\n\n
\n\nfunction touchEnded() {\n ellipse(mouseX, mouseY, 5, 5);\n // prevent default\n return false;\n}\ndescribe('no image displayed');\n\n
\n\n
\n\n// returns a TouchEvent object\n// as a callback argument\nfunction touchEnded(event) {\n console.log(event);\n}\ndescribe('no image displayed');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the touch\nends.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + }, + { + "params": [ + { + "name": "event", + "description": "optional TouchEvent callback argument.", + "optional": 1, + "type": "TouchEvent" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "Events", + "submodule": "Touch" + }, + { + "name": "dragOver", + "file": "src/core/p5.Element.js", + "line": 1148, + "itemtype": "method", + "chainable": 1, + "description": "Calls a function when a file is dragged over the element. Calling\nmyElement.dragOver(false) disables the function.", + "example": [ + "
\n\n// Drag a file over the canvas to test.\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call helloFile() when a\n // file is dragged over\n // the canvas.\n cnv.dragOver(helloFile);\n\n describe('A gray square. The text \"hello, file\" appears when a file is dragged over the square.');\n}\n\nfunction helloFile() {\n text('hello, file', 50, 50);\n}\n\n
\n\n
\n\n// Drag a file over the canvas to test.\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Say \"hello, file\" when a\n // file is dragged over\n // the canvas.\n cnv.dragOver(() => {\n text('hello, file', 50, 50);\n });\n\n describe('A gray square. The text \"hello, file\" appears when a file is dragged over the square.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the file is\ndragged over the element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "dragLeave", + "file": "src/core/p5.Element.js", + "line": 1213, + "itemtype": "method", + "chainable": 1, + "description": "Calls a function when a file is dragged off the element. Calling\nCalling myElement.dragLeave(false) disables the function.", + "example": [ + "
\n\n// Drag a file over, then off\n// the canvas to test.\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Call byeFile() when a\n // file is dragged over,\n // then off the canvas.\n cnv.dragLeave(byeFile);\n\n describe('A gray square. The text \"bye, file\" appears when a file is dragged over, then off the square.');\n}\n\nfunction byeFile() {\n text('bye, file', 50, 50);\n}\n\n
\n\n
\n\n// Drag a file over, then off\n// the canvas to test.\n\nfunction setup() {\n // Create a canvas element and\n // assign it to cnv.\n let cnv = createCanvas(100, 100);\n\n background(200);\n\n // Say \"bye, file\" when a\n // file is dragged over,\n // then off the canvas.\n cnv.dragLeave(() => {\n text('bye, file', 50, 50);\n });\n\n describe('A gray square. The text \"bye, file\" appears when a file is dragged over, then off the square.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the file is\ndragged off the element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "calculateOffset", + "file": "src/core/p5.Renderer.js", + "line": 524, + "itemtype": "method", + "description": "Helper fxn to measure ascent and descent.\nAdapted from http://stackoverflow.com/a/25355178", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "log", + "file": "src/math/calculation.js", + "line": 314, + "itemtype": "method", + "description": "Calculates the natural logarithm (the base-e logarithm) of a number. This\nfunction expects the n parameter to be a value greater than 0.0.", + "example": [ + "
\n\nfunction draw() {\n // Invert the y-axis.\n scale(1, -1);\n translate(0, -height);\n\n let x = frameCount;\n let y = 15 * log(x);\n point(x, y);\n\n describe('A series of black dots that get higher slowly from left to right.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "number greater than 0.", + "type": "Number" + } + ], + "return": { + "description": "natural logarithm of n.", + "type": "Number" + } + } + ], + "return": { + "description": "natural logarithm of n.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "createCanvas", + "file": "src/core/rendering.js", + "line": 77, + "itemtype": "method", + "description": "

Creates a canvas element in the document and sets its dimensions\nin pixels. This method should be called only once at the start of setup().\nCalling createCanvas more than once in a\nsketch will result in very unpredictable behavior. If you want more than\none drawing canvas you could use createGraphics()\n(hidden by default but it can be shown).

\n

Important note: in 2D mode (i.e. when p5.Renderer is not set) the origin (0,0)\nis positioned at the top left of the screen. In 3D mode (i.e. when p5.Renderer\nis set to WEBGL), the origin is positioned at the center of the canvas.\nSee this issue for more information.

\n

A WebGL canvas will use a WebGL2 context if it is supported by the browser.\nCheck the webglVersion property to check what\nversion is being used, or call setAttributes({ version: 1 })\nto create a WebGL1 context.

\n

The system variables width and height are set by the parameters passed to this\nfunction. If createCanvas() is not used, the\nwindow will be given a default size of 100×100 pixels.

\n

Optionally, an existing canvas can be passed using a selector, ie. document.getElementById('').\nIf specified, avoid using setAttributes() afterwards, as this will remove and recreate the existing canvas.

\n

For more ways to position the canvas, see the\n\npositioning the canvas wiki page.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 50);\n background(153);\n line(0, 0, width, height);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "w", + "description": "width of the canvas", + "type": "Number" + }, + { + "name": "h", + "description": "height of the canvas", + "type": "Number" + }, + { + "name": "renderer", + "description": "either P2D or WEBGL", + "optional": 1, + "type": "Constant" + }, + { + "name": "canvas", + "description": "existing html canvas element", + "optional": 1, + "type": "HTMLCanvasElement" + } + ], + "return": { + "description": "pointer to p5.Renderer holding canvas", + "type": "p5.Renderer" + } + }, + { + "params": [ + { + "name": "w", + "type": "Number" + }, + { + "name": "h", + "type": "Number" + }, + { + "name": "canvas", + "optional": 1, + "type": "HTMLCanvasElement" + } + ], + "return": { + "description": "pointer to p5.Renderer holding canvas", + "type": "p5.Renderer" + } + } + ], + "return": { + "description": "pointer to p5.Renderer holding canvas", + "type": "p5.Renderer" + }, + "class": "p5", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "resizeCanvas", + "file": "src/core/rendering.js", + "line": 198, + "itemtype": "method", + "description": "Resizes the canvas to given width and height. The canvas will be cleared\nand draw will be called immediately, allowing the sketch to re-render itself\nin the resized canvas.", + "example": [ + "
\nfunction setup() {\n createCanvas(windowWidth, windowHeight);\n}\n\nfunction draw() {\n background(0, 100, 200);\n}\n\nfunction windowResized() {\n resizeCanvas(windowWidth, windowHeight);\n}\n
" + ], + "alt": "No image displayed.", + "overloads": [ + { + "params": [ + { + "name": "w", + "description": "width of the canvas", + "type": "Number" + }, + { + "name": "h", + "description": "height of the canvas", + "type": "Number" + }, + { + "name": "noRedraw", + "description": "don't redraw the canvas immediately", + "optional": 1, + "type": "Boolean" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "noCanvas", + "file": "src/core/rendering.js", + "line": 247, + "itemtype": "method", + "description": "Removes the default canvas for a p5 sketch that doesn't require a canvas", + "example": [ + "
\n\nfunction setup() {\n noCanvas();\n}\n\n
" + ], + "alt": "no image displayed", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "createGraphics", + "file": "src/core/rendering.js", + "line": 303, + "itemtype": "method", + "description": "

Creates and returns a new p5.Graphics object. Use this class if you need\nto draw into an off-screen graphics buffer. The two parameters define the\nwidth and height in pixels.

\n

A WebGL p5.Graphics will use a WebGL2 context if it is supported by the browser.\nCheck the pg.webglVersion property of the renderer\nto check what version is being used, or call pg.setAttributes({ version: 1 })\nto create a WebGL1 context.

\n

Optionally, an existing canvas can be passed using a selector, ie. document.getElementById('').\nBy default this canvas will be hidden (offscreen buffer), to make visible, set element's style to display:block;

\n", + "example": [ + "
\n\nlet pg;\nfunction setup() {\n createCanvas(100, 100);\n pg = createGraphics(100, 100);\n}\n\nfunction draw() {\n background(200);\n pg.background(100);\n pg.noStroke();\n pg.ellipse(pg.width / 2, pg.height / 2, 50, 50);\n image(pg, 50, 50);\n image(pg, 0, 0, 50, 50);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "w", + "description": "width of the offscreen graphics buffer", + "type": "Number" + }, + { + "name": "h", + "description": "height of the offscreen graphics buffer", + "type": "Number" + }, + { + "name": "renderer", + "description": "either P2D or WEBGL\nundefined defaults to p2d", + "optional": 1, + "type": "Constant" + }, + { + "name": "canvas", + "description": "existing html canvas element", + "optional": 1, + "type": "HTMLCanvasElement" + } + ], + "return": { + "description": "offscreen graphics buffer", + "type": "p5.Graphics" + } + }, + { + "params": [ + { + "name": "w", + "type": "Number" + }, + { + "name": "h", + "type": "Number" + }, + { + "name": "canvas", + "optional": 1, + "type": "HTMLCanvasElement" + } + ], + "return": { + "description": "offscreen graphics buffer", + "type": "p5.Graphics" + } + } + ], + "return": { + "description": "offscreen graphics buffer", + "type": "p5.Graphics" + }, + "class": "p5", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "createFramebuffer", + "file": "src/core/rendering.js", + "line": 387, + "itemtype": "method", + "description": "

Creates and returns a new p5.Framebuffer, a\nhigh-performance WebGL object that you can draw to and then use as a texture.

\n

Options can include:

\n

If width, height, or density are specified, then the framebuffer will\nkeep that size until manually changed. Otherwise, it will be autosized, and\nit will update to match the main canvas's size and density when the main\ncanvas changes.

\n", + "example": [ + "
\n\nlet prev, next;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n prev = createFramebuffer({ format: FLOAT });\n next = createFramebuffer({ format: FLOAT });\n noStroke();\n}\n\nfunction draw() {\n // Swap prev and next so that we can use the previous\n // frame as a texture when drawing the current frame\n [prev, next] = [next, prev];\n\n // Draw to the framebuffer\n next.begin();\n background(255);\n\n push();\n tint(255, 253);\n image(prev, -width/2, -height/2);\n // Make sure the image plane doesn't block you from seeing any part\n // of the scene\n clearDepth();\n pop();\n\n push();\n normalMaterial();\n translate(25*sin(frameCount * 0.014), 25*sin(frameCount * 0.02), 0);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n box(12);\n pop();\n next.end();\n\n image(next, -width/2, -height/2);\n}\n\n
" + ], + "alt": "A red, green, and blue box (using normalMaterial) moves and rotates around\nthe canvas, leaving a trail behind it that slowly grows and fades away.", + "overloads": [ + { + "params": [ + { + "name": "options", + "description": "An optional object with configuration", + "optional": 1, + "type": "Object" + } + ], + "return": { + "description": "", + "type": "p5.Framebuffer" + } + } + ], + "return": { + "description": "", + "type": "p5.Framebuffer" + }, + "class": "p5", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "clearDepth", + "file": "src/core/rendering.js", + "line": 456, + "itemtype": "method", + "description": "

This makes the canvas forget how far from the camera everything that has\nbeen drawn was. Use this if you want to make sure the next thing you draw\nwill not draw behind anything that is already on the canvas.

\n

This is useful for things like feedback effects, where you want the previous\nframe to act like a background for the next frame, and not like a plane in\n3D space in the scene.

\n

This method is only available in WebGL mode. Since 2D mode does not have\n3D depth, anything you draw will always go on top of the previous content on\nthe canvas anyway.

\n", + "example": [ + "
\n\nlet prev, next;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n prev = createFramebuffer({ format: FLOAT });\n next = createFramebuffer({ format: FLOAT });\n noStroke();\n}\n\nfunction draw() {\n // Swap prev and next so that we can use the previous\n // frame as a texture when drawing the current frame\n [prev, next] = [next, prev];\n\n // Draw to the framebuffer\n next.begin();\n background(255);\n\n push();\n tint(255, 253);\n image(prev, -width/2, -height/2);\n // Make sure the image plane doesn't block you from seeing any part\n // of the scene\n clearDepth();\n pop();\n\n push();\n normalMaterial();\n translate(25*sin(frameCount * 0.014), 25*sin(frameCount * 0.02), 0);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n box(12);\n pop();\n next.end();\n\n image(next, -width/2, -height/2);\n}\n\n
" + ], + "alt": "A red, green, and blue box (using normalMaterial) moves and rotates around\nthe canvas, leaving a trail behind it that slowly grows and fades away.", + "overloads": [ + { + "params": [ + { + "name": "depth", + "description": "The value, between 0 and 1, to reset the depth to, where\n0 corresponds to a value as close as possible to the camera before getting\nclipped, and 1 corresponds to a value as far away from the camera as possible.\nThe default value is 1.", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "blendMode", + "file": "src/core/rendering.js", + "line": 533, + "itemtype": "method", + "description": "

Blends the pixels in the display window according to the defined mode.\nThere is a choice of the following modes to blend the source pixels (A)\nwith the ones of pixels already in the display window (B):

\n

(2D) indicates that this blend mode only works in the 2D renderer.
\n(3D) indicates that this blend mode only works in the WEBGL renderer.

\n", + "example": [ + "
\n\nblendMode(LIGHTEST);\nstrokeWeight(30);\nstroke(80, 150, 255);\nline(25, 25, 75, 75);\nstroke(255, 50, 50);\nline(75, 25, 25, 75);\n\n
\n\n
\n\nblendMode(MULTIPLY);\nstrokeWeight(30);\nstroke(80, 150, 255);\nline(25, 25, 75, 75);\nstroke(255, 50, 50);\nline(75, 25, 25, 75);\n\n
" + ], + "alt": "translucent image thick red & blue diagonal rounded lines intersecting center\nThick red & blue diagonal rounded lines intersecting center. dark at overlap", + "overloads": [ + { + "params": [ + { + "name": "mode", + "description": "blend mode to set for canvas.\neither BLEND, DARKEST, LIGHTEST, DIFFERENCE, MULTIPLY,\nEXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,\nSOFT_LIGHT, DODGE, BURN, ADD, REMOVE or SUBTRACT", + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "arc", + "file": "src/core/shape/2d_primitives.js", + "line": 172, + "itemtype": "method", + "chainable": 1, + "description": "

Draws an arc to the canvas. Arcs are drawn along the outer edge of an ellipse\n(oval) defined by the x, y, w, and h parameters. Use the start and stop\nparameters to specify the angles (in radians) at which to draw the arc. Arcs are\nalways drawn clockwise from start to stop. The origin of the arc's ellipse may\nbe changed with the ellipseMode() function.

\n

The optional mode parameter determines the arc's fill style. The fill modes are\na semi-circle (OPEN), a closed semi-circle (CHORD), or a closed pie segment (PIE).

\n", + "example": [ + "
\n\narc(50, 55, 50, 50, 0, HALF_PI);\nnoFill();\narc(50, 55, 60, 60, HALF_PI, PI);\narc(50, 55, 70, 70, PI, PI + QUARTER_PI);\narc(50, 55, 80, 80, PI + QUARTER_PI, TWO_PI);\ndescribe(\n 'A shattered outline of an ellipse with a quarter of a white circle at the bottom-right.'\n);\n\n
\n\n
\n\narc(50, 50, 80, 80, 0, PI + QUARTER_PI);\ndescribe('A white ellipse with the top-right third missing. The bottom is outlined in black.');\n\n
\n\n
\n\narc(50, 50, 80, 80, 0, PI + QUARTER_PI, OPEN);\ndescribe(\n 'A white ellipse missing a section from the top-right. The bottom is outlined in black.'\n);\n\n
\n\n
\n\narc(50, 50, 80, 80, 0, PI + QUARTER_PI, CHORD);\ndescribe('A white ellipse with a black outline missing a section from the top-right.');\n\n
\n\n
\n\narc(50, 50, 80, 80, 0, PI + QUARTER_PI, PIE);\ndescribe('A white ellipse with a black outline. The top-right third is missing.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the arc's ellipse.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the arc's ellipse.", + "type": "Number" + }, + { + "name": "w", + "description": "width of the arc's ellipse by default.", + "type": "Number" + }, + { + "name": "h", + "description": "height of the arc's ellipse by default.", + "type": "Number" + }, + { + "name": "start", + "description": "angle to start the arc, specified in radians.", + "type": "Number" + }, + { + "name": "stop", + "description": "angle to stop the arc, specified in radians.", + "type": "Number" + }, + { + "name": "mode", + "description": "optional parameter to determine the way of drawing\nthe arc. either CHORD, PIE, or OPEN.", + "optional": 1, + "type": "Constant" + }, + { + "name": "detail", + "description": "optional parameter for WebGL mode only. This is to\nspecify the number of vertices that makes up the\nperimeter of the arc. Default value is 25. Won't\ndraw a stroke for a detail of more than 50.", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "2D Primitives" + }, + { + "name": "ellipse", + "file": "src/core/shape/2d_primitives.js", + "line": 269, + "itemtype": "method", + "chainable": 1, + "description": "

Draws an ellipse (oval) to the canvas. An ellipse with equal width and height\nis a circle. By default, the first two parameters set the location of the\ncenter of the ellipse. The third and fourth parameters set the shape's width\nand height, respectively. The origin may be changed with the\nellipseMode() function.

\n

If no height is specified, the value of width is used for both the width and\nheight. If a negative height or width is specified, the absolute value is\ntaken.

\n", + "example": [ + "
\n\nellipse(56, 46, 55, 55);\ndescribe('A white ellipse with black outline in middle of a gray canvas.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the center of the ellipse.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the center of the ellipse.", + "type": "Number" + }, + { + "name": "w", + "description": "width of the ellipse.", + "type": "Number" + }, + { + "name": "h", + "description": "height of the ellipse.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "w", + "type": "Number" + }, + { + "name": "h", + "type": "Number" + }, + { + "name": "detail", + "description": "optional parameter for WebGL mode only. This is to\nspecify the number of vertices that makes up the\nperimeter of the ellipse. Default value is 25. Won't\ndraw a stroke for a detail of more than 50.", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "2D Primitives" + }, + { + "name": "circle", + "file": "src/core/shape/2d_primitives.js", + "line": 295, + "itemtype": "method", + "chainable": 1, + "description": "Draws a circle to the canvas. A circle is a round shape. Every point on the\nedge of a circle is the same distance from its center. By default, the first\ntwo parameters set the location of the center of the circle. The third\nparameter sets the shape's width and height (diameter). The origin may be\nchanged with the ellipseMode() function.", + "example": [ + "
\n\ncircle(30, 30, 20);\ndescribe('A white circle with black outline in the middle of a gray canvas.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the center of the circle.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the center of the circle.", + "type": "Number" + }, + { + "name": "d", + "description": "diameter of the circle.", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "2D Primitives" + }, + { + "name": "line", + "file": "src/core/shape/2d_primitives.js", + "line": 401, + "itemtype": "method", + "chainable": 1, + "description": "

Draws a line, a straight path between two points. Its default width is one pixel.\nThe version of line() with four parameters draws the line in 2D. To color a line,\nuse the stroke() function. To change its width, use the\nstrokeWeight() function. A line\ncan't be filled, so the fill() function won't affect\nthe color of a line.

\n

The version of line() with six parameters allows the line to be drawn in 3D\nspace. Doing so requires adding the WEBGL argument to\ncreateCanvas().

\n", + "example": [ + "
\n\nline(30, 20, 85, 75);\ndescribe(\n 'A black line on a gray canvas running from top-center to bottom-right.'\n);\n\n
\n\n
\n\nline(30, 20, 85, 20);\nstroke(126);\nline(85, 20, 85, 75);\nstroke(255);\nline(85, 75, 30, 75);\ndescribe(\n 'Three lines drawn in grayscale on a gray canvas. They form the top, right, and bottom sides of a square.'\n);\n\n
\n\n
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('A black line drawn on a gray canvas.');\n}\n\nfunction draw() {\n background(220);\n line(0, 0, 0, 10, 10, 0);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x1", + "description": "the x-coordinate of the first point.", + "type": "Number" + }, + { + "name": "y1", + "description": "the y-coordinate of the first point.", + "type": "Number" + }, + { + "name": "x2", + "description": "the x-coordinate of the second point.", + "type": "Number" + }, + { + "name": "y2", + "description": "the y-coordinate of the second point.", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "x1", + "type": "Number" + }, + { + "name": "y1", + "type": "Number" + }, + { + "name": "z1", + "description": "the z-coordinate of the first point.", + "type": "Number" + }, + { + "name": "x2", + "type": "Number" + }, + { + "name": "y2", + "type": "Number" + }, + { + "name": "z2", + "description": "the z-coordinate of the second point.", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "2D Primitives" + }, + { + "name": "point", + "file": "src/core/shape/2d_primitives.js", + "line": 484, + "itemtype": "method", + "chainable": 1, + "description": "

Draws a point, a single coordinate in space. Its default size is one pixel. The first two\nparameters are the point's x- and y-coordinates, respectively. To color a point, use\nthe stroke() function. To change its size, use the\nstrokeWeight() function.

\n

The version of point() with three parameters allows the point to be drawn in 3D\nspace. Doing so requires adding the WEBGL argument to\ncreateCanvas().

\n

The version of point() with one parameter allows the point's location to be set with\na p5.Vector object.

\n", + "example": [ + "
\n\npoint(30, 20);\npoint(85, 20);\npoint(85, 75);\npoint(30, 75);\ndescribe(\n 'Four small, black points drawn on a gray canvas. The points form the corners of a square.'\n);\n\n
\n\n
\n\npoint(30, 20);\npoint(85, 20);\nstroke('purple');\nstrokeWeight(10);\npoint(85, 75);\npoint(30, 75);\ndescribe(\n 'Four points drawn on a gray canvas. Two are black and two are purple. The points form the corners of a square.'\n);\n\n
\n\n
\n\nlet a = createVector(10, 10);\npoint(a);\nlet b = createVector(10, 20);\npoint(b);\nlet c = createVector(20, 10);\npoint(c);\nlet d = createVector(20, 20);\npoint(d);\ndescribe(\n 'Four small, black points drawn on a gray canvas. The points form the corners of a square.'\n);\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "the x-coordinate.", + "type": "Number" + }, + { + "name": "y", + "description": "the y-coordinate.", + "type": "Number" + }, + { + "name": "z", + "description": "the z-coordinate (for WebGL mode).", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "coordinateVector", + "description": "the coordinate vector.", + "type": "p5.Vector" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "2D Primitives" + }, + { + "name": "quad", + "file": "src/core/shape/2d_primitives.js", + "line": 578, + "itemtype": "method", + "chainable": 1, + "description": "

Draws a quad to the canvas. A quad is a quadrilateral, a four-sided\npolygon. Some examples of quads include rectangles, squares, rhombuses,\nand trapezoids. The first pair of parameters (x1,y1) sets the quad's\nfirst point. The following pairs of parameters set the coordinates for\nits next three points. Parameters should proceed clockwise or\ncounter-clockwise around the shape.

\n

The version of quad() with twelve parameters allows the quad to be drawn\nin 3D space. Doing so requires adding the WEBGL argument to\ncreateCanvas().

\n", + "example": [ + "
\n\nquad(20, 20, 80, 20, 80, 80, 20, 80);\ndescribe('A white square with a black outline drawn on a gray canvas.');\n\n
\n\n
\n\nquad(20, 30, 80, 30, 80, 70, 20, 70);\ndescribe('A white rectangle with a black outline drawn on a gray canvas.');\n\n
\n\n
\n\nquad(50, 62, 86, 50, 50, 38, 14, 50);\ndescribe('A white rhombus with a black outline drawn on a gray canvas.');\n\n
\n\n
\n\nquad(20, 50, 80, 30, 80, 70, 20, 70);\ndescribe('A white trapezoid with a black outline drawn on a gray canvas.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x1", + "description": "the x-coordinate of the first point.", + "type": "Number" + }, + { + "name": "y1", + "description": "the y-coordinate of the first point.", + "type": "Number" + }, + { + "name": "x2", + "description": "the x-coordinate of the second point.", + "type": "Number" + }, + { + "name": "y2", + "description": "the y-coordinate of the second point.", + "type": "Number" + }, + { + "name": "x3", + "description": "the x-coordinate of the third point.", + "type": "Number" + }, + { + "name": "y3", + "description": "the y-coordinate of the third point.", + "type": "Number" + }, + { + "name": "x4", + "description": "the x-coordinate of the fourth point.", + "type": "Number" + }, + { + "name": "y4", + "description": "the y-coordinate of the fourth point.", + "type": "Number" + }, + { + "name": "detailX", + "description": "number of segments in the x-direction.", + "optional": 1, + "type": "Integer" + }, + { + "name": "detailY", + "description": "number of segments in the y-direction.", + "optional": 1, + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "x1", + "type": "Number" + }, + { + "name": "y1", + "type": "Number" + }, + { + "name": "z1", + "description": "the z-coordinate of the first point.", + "type": "Number" + }, + { + "name": "x2", + "type": "Number" + }, + { + "name": "y2", + "type": "Number" + }, + { + "name": "z2", + "description": "the z-coordinate of the second point.", + "type": "Number" + }, + { + "name": "x3", + "type": "Number" + }, + { + "name": "y3", + "type": "Number" + }, + { + "name": "z3", + "description": "the z-coordinate of the third point.", + "type": "Number" + }, + { + "name": "x4", + "type": "Number" + }, + { + "name": "y4", + "type": "Number" + }, + { + "name": "z4", + "description": "the z-coordinate of the fourth point.", + "type": "Number" + }, + { + "name": "detailX", + "optional": 1, + "type": "Integer" + }, + { + "name": "detailY", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "2D Primitives" + }, + { + "name": "rect", + "file": "src/core/shape/2d_primitives.js", + "line": 666, + "itemtype": "method", + "chainable": 1, + "description": "

Draws a rectangle to the canvas. A rectangle is a four-sided polygon with\nevery angle at ninety degrees. By default, the first two parameters set the\nlocation of the rectangle's upper-left corner. The third and fourth set the\nshape's the width and height, respectively. The way these parameters are\ninterpreted may be changed with the rectMode()\nfunction.

\n

The version of rect() with five parameters creates a rounded rectangle. The\nfifth parameter is used as the radius value for all four corners.

\n

The version of rect() with eight parameters also creates a rounded rectangle.\nWhen using eight parameters, the latter four set the radius of the arc at\neach corner separately. The radii start with the top-left corner and move\nclockwise around the rectangle. If any of these parameters are omitted, they\nare set to the value of the last specified corner radius.

\n", + "example": [ + "
\n\nrect(30, 20, 55, 55);\ndescribe('A white rectangle with a black outline on a gray canvas.');\n\n
\n\n
\n\nrect(30, 20, 55, 55, 20);\ndescribe(\n 'A white rectangle with a black outline and round edges on a gray canvas.'\n);\n\n
\n\n
\n\nrect(30, 20, 55, 55, 20, 15, 10, 5);\ndescribe('A white rectangle with a black outline and round edges of different radii.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the rectangle.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the rectangle.", + "type": "Number" + }, + { + "name": "w", + "description": "width of the rectangle.", + "type": "Number" + }, + { + "name": "h", + "description": "height of the rectangle.", + "optional": 1, + "type": "Number" + }, + { + "name": "tl", + "description": "optional radius of top-left corner.", + "optional": 1, + "type": "Number" + }, + { + "name": "tr", + "description": "optional radius of top-right corner.", + "optional": 1, + "type": "Number" + }, + { + "name": "br", + "description": "optional radius of bottom-right corner.", + "optional": 1, + "type": "Number" + }, + { + "name": "bl", + "description": "optional radius of bottom-left corner.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "w", + "type": "Number" + }, + { + "name": "h", + "type": "Number" + }, + { + "name": "detailX", + "description": "number of segments in the x-direction (for WebGL mode).", + "optional": 1, + "type": "Integer" + }, + { + "name": "detailY", + "description": "number of segments in the y-direction (for WebGL mode).", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "2D Primitives" + }, + { + "name": "square", + "file": "src/core/shape/2d_primitives.js", + "line": 721, + "itemtype": "method", + "chainable": 1, + "description": "

Draws a square to the canvas. A square is a four-sided polygon with every\nangle at ninety degrees and equal side lengths. By default, the first two\nparameters set the location of the square's upper-left corner. The third\nparameter sets its side size. The way these parameters are interpreted may\nbe changed with the rectMode() function.

\n

The version of square() with four parameters creates a rounded square. The\nfourth parameter is used as the radius value for all four corners.

\n

The version of square() with seven parameters also creates a rounded square.\nWhen using seven parameters, the latter four set the radius of the arc at\neach corner separately. The radii start with the top-left corner and move\nclockwise around the square. If any of these parameters are omitted, they\nare set to the value of the last specified corner radius.

\n", + "example": [ + "
\n\nsquare(30, 20, 55);\ndescribe('A white square with a black outline in on a gray canvas.');\n\n
\n\n
\n\nsquare(30, 20, 55, 20);\ndescribe(\n 'A white square with a black outline and round edges on a gray canvas.'\n);\n\n
\n\n
\n\nsquare(30, 20, 55, 20, 15, 10, 5);\ndescribe('A white square with a black outline and round edges of different radii.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the square.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the square.", + "type": "Number" + }, + { + "name": "s", + "description": "side size of the square.", + "type": "Number" + }, + { + "name": "tl", + "description": "optional radius of top-left corner.", + "optional": 1, + "type": "Number" + }, + { + "name": "tr", + "description": "optional radius of top-right corner.", + "optional": 1, + "type": "Number" + }, + { + "name": "br", + "description": "optional radius of bottom-right corner.", + "optional": 1, + "type": "Number" + }, + { + "name": "bl", + "description": "optional radius of bottom-left corner.", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "2D Primitives" + }, + { + "name": "triangle", + "file": "src/core/shape/2d_primitives.js", + "line": 782, + "itemtype": "method", + "chainable": 1, + "description": "Draws a triangle to the canvas. A triangle is a three-sided polygon. The\nfirst two parameters specify the triangle's first point (x1,y1). The middle\ntwo parameters specify its second point (x2,y2). And the last two parameters\nspecify its third point (x3, y3).", + "example": [ + "
\n\ntriangle(30, 75, 58, 20, 86, 75);\ndescribe('A white triangle with a black outline on a gray canvas.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x1", + "description": "x-coordinate of the first point.", + "type": "Number" + }, + { + "name": "y1", + "description": "y-coordinate of the first point.", + "type": "Number" + }, + { + "name": "x2", + "description": "x-coordinate of the second point.", + "type": "Number" + }, + { + "name": "y2", + "description": "y-coordinate of the second point.", + "type": "Number" + }, + { + "name": "x3", + "description": "x-coordinate of the third point.", + "type": "Number" + }, + { + "name": "y3", + "description": "y-coordinate of the third point.", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "2D Primitives" + }, + { + "name": "ellipseMode", + "file": "src/core/shape/attributes.js", + "line": 60, + "itemtype": "method", + "chainable": 1, + "description": "

Modifies the location from which ellipses, circles, and arcs are drawn. By default, the\nfirst two parameters are the x- and y-coordinates of the shape's center. The next\nparameters are its width and height. This is equivalent to calling ellipseMode(CENTER).

\n

ellipseMode(RADIUS) also uses the first two parameters to set the x- and y-coordinates\nof the shape's center. The next parameters are half of the shapes's width and height.\nCalling ellipse(0, 0, 10, 15) draws a shape with a width of 20 and height of 30.

\n

ellipseMode(CORNER) uses the first two parameters as the upper-left corner of the\nshape. The next parameters are its width and height.

\n

ellipseMode(CORNERS) uses the first two parameters as the location of one corner\nof the ellipse's bounding box. The third and fourth parameters are the location of the\nopposite corner.

\n

The argument passed to ellipseMode() must be written in ALL CAPS because the constants\nCENTER, RADIUS, CORNER, and CORNERS are defined this way. JavaScript is a\ncase-sensitive language.

\n", + "example": [ + "
\n\nellipseMode(RADIUS);\nfill(255);\nellipse(50, 50, 30, 30);\nellipseMode(CENTER);\nfill(100);\nellipse(50, 50, 30, 30);\ndescribe('A white circle with a gray circle at its center. Both circles have black outlines.');\n\n
\n\n
\n\nellipseMode(CORNER);\nfill(255);\nellipse(25, 25, 50, 50);\nellipseMode(CORNERS);\nfill(100);\nellipse(25, 25, 50, 50);\ndescribe('A white circle with a gray circle at its top-left corner. Both circles have black outlines.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "mode", + "description": "either CENTER, RADIUS, CORNER, or CORNERS", + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Attributes" + }, + { + "name": "noSmooth", + "file": "src/core/shape/attributes.js", + "line": 97, + "itemtype": "method", + "chainable": 1, + "description": "

Draws all geometry with jagged (aliased) edges.

\n

smooth() is active by default in 2D mode. It's necessary to call\nnoSmooth() to disable smoothing of geometry, images, and fonts.

\n

In WebGL mode, noSmooth() is active by default. It's necessary\nto call smooth() to draw smooth (antialiased) edges.

\n", + "example": [ + "
\n\nbackground(0);\nnoStroke();\nsmooth();\nellipse(30, 48, 36, 36);\nnoSmooth();\nellipse(70, 48, 36, 36);\ndescribe('Two pixelated white circles on a black background.');\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Attributes" + }, + { + "name": "rectMode", + "file": "src/core/shape/attributes.js", + "line": 161, + "itemtype": "method", + "chainable": 1, + "description": "

Modifies the location from which rectangles and squares are drawn. By default,\nthe first two parameters are the x- and y-coordinates of the shape's upper-left\ncorner. The next parameters are its width and height. This is equivalent to\ncalling rectMode(CORNER).

\n

rectMode(CORNERS) also uses the first two parameters as the location of one of\nthe corners. The third and fourth parameters are the location of the opposite\ncorner.

\n

rectMode(CENTER) uses the first two parameters as the x- and y-coordinates of\nthe shape's center. The next parameters are its width and height.

\n

rectMode(RADIUS) also uses the first two parameters as the x- and y-coordinates\nof the shape's center. The next parameters are half of the shape's width and\nheight.

\n

The argument passed to rectMode() must be written in ALL CAPS because the\nconstants CENTER, RADIUS, CORNER, and CORNERS are defined this way.\nJavaScript is a case-sensitive language.

\n", + "example": [ + "
\n\nrectMode(CORNER);\nfill(255);\nrect(25, 25, 50, 50);\n\nrectMode(CORNERS);\nfill(100);\nrect(25, 25, 50, 50);\n\ndescribe('A small gray square drawn at the top-left corner of a white square.');\n\n
\n\n
\n\nrectMode(RADIUS);\nfill(255);\nrect(50, 50, 30, 30);\n\nrectMode(CENTER);\nfill(100);\nrect(50, 50, 30, 30);\n\ndescribe('A small gray square drawn at the center of a white square.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "mode", + "description": "either CORNER, CORNERS, CENTER, or RADIUS", + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Attributes" + }, + { + "name": "smooth", + "file": "src/core/shape/attributes.js", + "line": 199, + "itemtype": "method", + "chainable": 1, + "description": "

Draws all geometry with smooth (anti-aliased) edges. smooth() will also\nimprove image quality of resized images.

\n

smooth() is active by default in 2D mode. It's necessary to call\nnoSmooth() to disable smoothing of geometry, images, and fonts.

\n

In WebGL mode, noSmooth() is active by default. It's necessary\nto call smooth() to draw smooth (antialiased) edges.

\n", + "example": [ + "
\n\nbackground(0);\nnoStroke();\nsmooth();\nellipse(30, 48, 36, 36);\nnoSmooth();\nellipse(70, 48, 36, 36);\ndescribe('Two pixelated white circles on a black background.');\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Attributes" + }, + { + "name": "strokeCap", + "file": "src/core/shape/attributes.js", + "line": 235, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the style for rendering line endings. These ends are either rounded\n(ROUND), squared (SQUARE), or extended (PROJECT). The default cap is\nROUND.

\n

The argument passed to strokeCap() must be written in ALL CAPS because\nthe constants ROUND, SQUARE, and PROJECT are defined this way.\nJavaScript is a case-sensitive language.

\n", + "example": [ + "
\n\nstrokeWeight(12.0);\nstrokeCap(ROUND);\nline(20, 30, 80, 30);\nstrokeCap(SQUARE);\nline(20, 50, 80, 50);\nstrokeCap(PROJECT);\nline(20, 70, 80, 70);\ndescribe('Three horizontal lines. The top line has rounded ends, the middle line has squared ends, and the bottom line has longer, squared ends.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "cap", + "description": "either ROUND, SQUARE, or PROJECT", + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Attributes" + }, + { + "name": "strokeJoin", + "file": "src/core/shape/attributes.js", + "line": 302, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the style of the joints which connect line segments. These joints are\neither mitered (MITER), beveled (BEVEL), or rounded (ROUND). The default\njoint is MITER in 2D mode and ROUND in WebGL mode.

\n

The argument passed to strokeJoin() must be written in ALL CAPS because\nthe constants MITER, BEVEL, and ROUND are defined this way.\nJavaScript is a case-sensitive language.

\n", + "example": [ + "
\n\nnoFill();\nstrokeWeight(10.0);\nstrokeJoin(MITER);\nbeginShape();\nvertex(35, 20);\nvertex(65, 50);\nvertex(35, 80);\nendShape();\ndescribe('A right-facing arrowhead shape with a pointed tip in center of canvas.');\n\n
\n\n
\n\nnoFill();\nstrokeWeight(10.0);\nstrokeJoin(BEVEL);\nbeginShape();\nvertex(35, 20);\nvertex(65, 50);\nvertex(35, 80);\nendShape();\ndescribe('A right-facing arrowhead shape with a flat tip in center of canvas.');\n\n
\n\n
\n\nnoFill();\nstrokeWeight(10.0);\nstrokeJoin(ROUND);\nbeginShape();\nvertex(35, 20);\nvertex(65, 50);\nvertex(35, 80);\nendShape();\ndescribe('A right-facing arrowhead shape with a rounded tip in center of canvas.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "join", + "description": "either MITER, BEVEL, or ROUND", + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Attributes" + }, + { + "name": "strokeWeight", + "file": "src/core/shape/attributes.js", + "line": 351, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the width of the stroke used for lines, points, and the border around\nshapes. All widths are set in units of pixels.

\n

Note that strokeWeight() is affected by any transformation or scaling that\nhas been applied previously.

\n", + "example": [ + "
\n\n// Default.\nline(20, 20, 80, 20);\n// Thicker.\nstrokeWeight(4);\nline(20, 40, 80, 40);\n// Beastly.\nstrokeWeight(10);\nline(20, 70, 80, 70);\ndescribe('Three horizontal black lines. The top line is thin, the middle is medium, and the bottom is thick.');\n\n
\n\n
\n\n// Default.\nline(20, 20, 80, 20);\n// Adding scale transformation.\nscale(5);\n// Coordinates adjusted for scaling.\nline(4, 8, 16, 8);\ndescribe('Two horizontal black lines. The top line is thin and the bottom is five times thicker than the top.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "weight", + "description": "the weight of the stroke (in pixels).", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Attributes" + }, + { + "name": "bezier", + "file": "src/core/shape/curves.js", + "line": 78, + "itemtype": "method", + "chainable": 1, + "description": "

Draws a cubic Bezier curve on the screen. These curves are defined by a\nseries of anchor and control points. The first two parameters specify\nthe first anchor point and the last two parameters specify the other\nanchor point, which become the first and last points on the curve. The\nmiddle parameters specify the two control points which define the shape\nof the curve. Approximately speaking, control points \"pull\" the curve\ntowards them.

\n

Bezier curves were developed by French automotive engineer Pierre Bezier,\nand are commonly used in computer graphics to define gently sloping curves.\nSee also curve().

\n", + "example": [ + "
\n\nnoFill();\nstroke(255, 102, 0);\nline(85, 20, 10, 10);\nline(90, 90, 15, 80);\nstroke(0, 0, 0);\nbezier(85, 20, 10, 10, 90, 90, 15, 80);\n\n
\n\n
\n\nbackground(0, 0, 0);\nnoFill();\nstroke(255);\nbezier(250, 250, 0, 100, 100, 0, 100, 0, 0, 0, 100, 0);\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x1", + "description": "x-coordinate for the first anchor point", + "type": "Number" + }, + { + "name": "y1", + "description": "y-coordinate for the first anchor point", + "type": "Number" + }, + { + "name": "x2", + "description": "x-coordinate for the first control point", + "type": "Number" + }, + { + "name": "y2", + "description": "y-coordinate for the first control point", + "type": "Number" + }, + { + "name": "x3", + "description": "x-coordinate for the second control point", + "type": "Number" + }, + { + "name": "y3", + "description": "y-coordinate for the second control point", + "type": "Number" + }, + { + "name": "x4", + "description": "x-coordinate for the second anchor point", + "type": "Number" + }, + { + "name": "y4", + "description": "y-coordinate for the second anchor point", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "x1", + "type": "Number" + }, + { + "name": "y1", + "type": "Number" + }, + { + "name": "z1", + "description": "z-coordinate for the first anchor point", + "type": "Number" + }, + { + "name": "x2", + "type": "Number" + }, + { + "name": "y2", + "type": "Number" + }, + { + "name": "z2", + "description": "z-coordinate for the first control point", + "type": "Number" + }, + { + "name": "x3", + "type": "Number" + }, + { + "name": "y3", + "type": "Number" + }, + { + "name": "z3", + "description": "z-coordinate for the second control point", + "type": "Number" + }, + { + "name": "x4", + "type": "Number" + }, + { + "name": "y4", + "type": "Number" + }, + { + "name": "z4", + "description": "z-coordinate for the second anchor point", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Curves" + }, + { + "name": "bezierDetail", + "file": "src/core/shape/curves.js", + "line": 125, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the resolution at which Bezier's curve is displayed. The default value is 20.

\n

Note, This function is only useful when using the WEBGL renderer\nas the default canvas renderer does not use this information.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n noFill();\n bezierDetail(5);\n}\n\nfunction draw() {\n background(200);\n bezier(\n -40, -40, 0,\n 90, -40, 0,\n -90, 40, 0,\n 40, 40, 0\n );\n}\n\n
" + ], + "alt": "stretched black s-shape with a low level of bezier detail", + "overloads": [ + { + "params": [ + { + "name": "detail", + "description": "resolution of the curves", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Curves" + }, + { + "name": "bezierPoint", + "file": "src/core/shape/curves.js", + "line": 174, + "itemtype": "method", + "description": "Given the x or y co-ordinate values of control and anchor points of a bezier\ncurve, it evaluates the x or y coordinate of the bezier at position t. The\nparameters a and d are the x or y coordinates of first and last points on the\ncurve while b and c are of the control points.The final parameter t is the\nposition of the resultant point which is given between 0 and 1.\nThis can be done once with the x coordinates and a second time\nwith the y coordinates to get the location of a bezier curve at t.", + "example": [ + "
\n\nnoFill();\nlet x1 = 85,\nx2 = 10,\nx3 = 90,\nx4 = 15;\nlet y1 = 20,\ny2 = 10,\ny3 = 90,\ny4 = 80;\nbezier(x1, y1, x2, y2, x3, y3, x4, y4);\nfill(255);\nlet steps = 10;\nfor (let i = 0; i <= steps; i++) {\n let t = i / steps;\n let x = bezierPoint(x1, x2, x3, x4, t);\n let y = bezierPoint(y1, y2, y3, y4, t);\n circle(x, y, 5);\n}\n\n
" + ], + "alt": "10 points plotted on a given bezier at equal distances.", + "overloads": [ + { + "params": [ + { + "name": "a", + "description": "coordinate of first point on the curve", + "type": "Number" + }, + { + "name": "b", + "description": "coordinate of first control point", + "type": "Number" + }, + { + "name": "c", + "description": "coordinate of second control point", + "type": "Number" + }, + { + "name": "d", + "description": "coordinate of second point on the curve", + "type": "Number" + }, + { + "name": "t", + "description": "value between 0 and 1", + "type": "Number" + } + ], + "return": { + "description": "the value of the Bezier at position t", + "type": "Number" + } + } + ], + "return": { + "description": "the value of the Bezier at position t", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Curves" + }, + { + "name": "bezierTangent", + "file": "src/core/shape/curves.js", + "line": 251, + "itemtype": "method", + "description": "Evaluates the tangent to the Bezier at position t for points a, b, c, d.\nThe parameters a and d are the first and last points\non the curve, and b and c are the control points.\nThe final parameter t varies between 0 and 1.", + "example": [ + "
\n\nnoFill();\nbezier(85, 20, 10, 10, 90, 90, 15, 80);\nlet steps = 6;\nfill(255);\nfor (let i = 0; i <= steps; i++) {\n let t = i / steps;\n // Get the location of the point\n let x = bezierPoint(85, 10, 90, 15, t);\n let y = bezierPoint(20, 10, 90, 80, t);\n // Get the tangent points\n let tx = bezierTangent(85, 10, 90, 15, t);\n let ty = bezierTangent(20, 10, 90, 80, t);\n // Calculate an angle from the tangent points\n let a = atan2(ty, tx);\n a += PI;\n stroke(255, 102, 0);\n line(x, y, cos(a) * 30 + x, sin(a) * 30 + y);\n // The following line of code makes a line\n // inverse of the above line\n //line(x, y, cos(a)*-30 + x, sin(a)*-30 + y);\n stroke(0);\n ellipse(x, y, 5, 5);\n}\n\n
\n\n
\n\nnoFill();\nbezier(85, 20, 10, 10, 90, 90, 15, 80);\nstroke(255, 102, 0);\nlet steps = 16;\nfor (let i = 0; i <= steps; i++) {\n let t = i / steps;\n let x = bezierPoint(85, 10, 90, 15, t);\n let y = bezierPoint(20, 10, 90, 80, t);\n let tx = bezierTangent(85, 10, 90, 15, t);\n let ty = bezierTangent(20, 10, 90, 80, t);\n let a = atan2(ty, tx);\n a -= HALF_PI;\n line(x, y, cos(a) * 8 + x, sin(a) * 8 + y);\n}\n\n
" + ], + "alt": "s-shaped line with 6 short orange lines showing the tangents at those points.\ns-shaped line with 6 short orange lines showing lines coming out the underside of the bezier.", + "overloads": [ + { + "params": [ + { + "name": "a", + "description": "coordinate of first point on the curve", + "type": "Number" + }, + { + "name": "b", + "description": "coordinate of first control point", + "type": "Number" + }, + { + "name": "c", + "description": "coordinate of second control point", + "type": "Number" + }, + { + "name": "d", + "description": "coordinate of second point on the curve", + "type": "Number" + }, + { + "name": "t", + "description": "value between 0 and 1", + "type": "Number" + } + ], + "return": { + "description": "the tangent at position t", + "type": "Number" + } + } + ], + "return": { + "description": "the tangent at position t", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Curves" + }, + { + "name": "curve", + "file": "src/core/shape/curves.js", + "line": 349, + "itemtype": "method", + "chainable": 1, + "description": "Draws a curved line on the screen between two points, given as the\nmiddle four parameters. The first two parameters are a control point, as\nif the curve came from this point even though it's not drawn. The last\ntwo parameters similarly describe the other control point.

\nLonger curves can be created by putting a series of curve() functions\ntogether or using curveVertex(). An additional function called\ncurveTightness() provides control for the visual quality of the curve.\nThe curve() function is an implementation of Catmull-Rom splines.", + "example": [ + "
\n\nnoFill();\nstroke(255, 102, 0);\ncurve(5, 26, 5, 26, 73, 24, 73, 61);\nstroke(0);\ncurve(5, 26, 73, 24, 73, 61, 15, 65);\nstroke(255, 102, 0);\ncurve(73, 24, 73, 61, 15, 65, 15, 65);\n\n
\n\n
\n\n// Define the curve points as JavaScript objects\nlet p1 = { x: 5, y: 26 };\nlet p2 = { x: 73, y: 24 };\nlet p3 = { x: 73, y: 61 };\nlet p4 = { x: 15, y: 65 };\nnoFill();\nstroke(255, 102, 0);\ncurve(p1.x, p1.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);\nstroke(0);\ncurve(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y);\nstroke(255, 102, 0);\ncurve(p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, p4.x, p4.y);\n\n
\n\n
\n\nnoFill();\nstroke(255, 102, 0);\ncurve(5, 26, 0, 5, 26, 0, 73, 24, 0, 73, 61, 0);\nstroke(0);\ncurve(5, 26, 0, 73, 24, 0, 73, 61, 0, 15, 65, 0);\nstroke(255, 102, 0);\ncurve(73, 24, 0, 73, 61, 0, 15, 65, 0, 15, 65, 0);\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x1", + "description": "x-coordinate for the beginning control point", + "type": "Number" + }, + { + "name": "y1", + "description": "y-coordinate for the beginning control point", + "type": "Number" + }, + { + "name": "x2", + "description": "x-coordinate for the first point", + "type": "Number" + }, + { + "name": "y2", + "description": "y-coordinate for the first point", + "type": "Number" + }, + { + "name": "x3", + "description": "x-coordinate for the second point", + "type": "Number" + }, + { + "name": "y3", + "description": "y-coordinate for the second point", + "type": "Number" + }, + { + "name": "x4", + "description": "x-coordinate for the ending control point", + "type": "Number" + }, + { + "name": "y4", + "description": "y-coordinate for the ending control point", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "x1", + "type": "Number" + }, + { + "name": "y1", + "type": "Number" + }, + { + "name": "z1", + "description": "z-coordinate for the beginning control point", + "type": "Number" + }, + { + "name": "x2", + "type": "Number" + }, + { + "name": "y2", + "type": "Number" + }, + { + "name": "z2", + "description": "z-coordinate for the first point", + "type": "Number" + }, + { + "name": "x3", + "type": "Number" + }, + { + "name": "y3", + "type": "Number" + }, + { + "name": "z3", + "description": "z-coordinate for the second point", + "type": "Number" + }, + { + "name": "x4", + "type": "Number" + }, + { + "name": "y4", + "type": "Number" + }, + { + "name": "z4", + "description": "z-coordinate for the ending control point", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Curves" + }, + { + "name": "curveDetail", + "file": "src/core/shape/curves.js", + "line": 389, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the resolution at which curves display. The default value is 20 while\nthe minimum value is 3.

\n

This function is only useful when using the WEBGL renderer\nas the default canvas renderer does not use this\ninformation.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n\n curveDetail(5);\n}\nfunction draw() {\n background(200);\n\n curve(250, 600, 0, -30, 40, 0, 30, 30, 0, -250, 600, 0);\n}\n\n
" + ], + "alt": "white arch shape with a low level of curve detail.", + "overloads": [ + { + "params": [ + { + "name": "resolution", + "description": "resolution of the curves", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Curves" + }, + { + "name": "curveTightness", + "file": "src/core/shape/curves.js", + "line": 439, + "itemtype": "method", + "chainable": 1, + "description": "Modifies the quality of forms created with curve()\nand curveVertex().The parameter tightness\ndetermines how the curve fits to the vertex points. The value 0.0 is the\ndefault value for tightness (this value defines the curves to be Catmull-Rom\nsplines) and the value 1.0 connects all the points with straight lines.\nValues within the range -5.0 and 5.0 will deform the curves but will leave\nthem recognizable and as values increase in magnitude, they will continue to deform.", + "example": [ + "
\n\n// Move the mouse left and right to see the curve change\nfunction setup() {\n createCanvas(100, 100);\n noFill();\n}\n\nfunction draw() {\n background(204);\n let t = map(mouseX, 0, width, -5, 5);\n curveTightness(t);\n beginShape();\n curveVertex(10, 26);\n curveVertex(10, 26);\n curveVertex(83, 24);\n curveVertex(83, 61);\n curveVertex(25, 65);\n curveVertex(25, 65);\n endShape();\n}\n\n
" + ], + "alt": "Line shaped like right-facing arrow,points move with mouse-x and warp shape.", + "overloads": [ + { + "params": [ + { + "name": "amount", + "description": "amount of deformation from the original vertices", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Curves" + }, + { + "name": "curvePoint", + "file": "src/core/shape/curves.js", + "line": 482, + "itemtype": "method", + "description": "Evaluates the curve at position t for points a, b, c, d.\nThe parameter t varies between 0 and 1, a and d are control points\nof the curve, and b and c are the start and end points of the curve.\nThis can be done once with the x coordinates and a second time\nwith the y coordinates to get the location of a curve at t.", + "example": [ + "
\n\nnoFill();\ncurve(5, 26, 5, 26, 73, 24, 73, 61);\ncurve(5, 26, 73, 24, 73, 61, 15, 65);\nfill(255);\nellipseMode(CENTER);\nlet steps = 6;\nfor (let i = 0; i <= steps; i++) {\n let t = i / steps;\n let x = curvePoint(5, 5, 73, 73, t);\n let y = curvePoint(26, 26, 24, 61, t);\n ellipse(x, y, 5, 5);\n x = curvePoint(5, 73, 73, 15, t);\n y = curvePoint(26, 24, 61, 65, t);\n ellipse(x, y, 5, 5);\n}\n\n
\n\nline hooking down to right-bottom with 13 5×5 white ellipse points" + ], + "overloads": [ + { + "params": [ + { + "name": "a", + "description": "coordinate of first control point of the curve", + "type": "Number" + }, + { + "name": "b", + "description": "coordinate of first point", + "type": "Number" + }, + { + "name": "c", + "description": "coordinate of second point", + "type": "Number" + }, + { + "name": "d", + "description": "coordinate of second control point", + "type": "Number" + }, + { + "name": "t", + "description": "value between 0 and 1", + "type": "Number" + } + ], + "return": { + "description": "Curve value at position t", + "type": "Number" + } + } + ], + "return": { + "description": "Curve value at position t", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Curves" + }, + { + "name": "curveTangent", + "file": "src/core/shape/curves.js", + "line": 529, + "itemtype": "method", + "description": "Evaluates the tangent to the curve at position t for points a, b, c, d.\nThe parameter t varies between 0 and 1, a and d are points on the curve,\nand b and c are the control points.", + "example": [ + "
\n\nnoFill();\ncurve(5, 26, 73, 24, 73, 61, 15, 65);\nlet steps = 6;\nfor (let i = 0; i <= steps; i++) {\n let t = i / steps;\n let x = curvePoint(5, 73, 73, 15, t);\n let y = curvePoint(26, 24, 61, 65, t);\n //ellipse(x, y, 5, 5);\n let tx = curveTangent(5, 73, 73, 15, t);\n let ty = curveTangent(26, 24, 61, 65, t);\n let a = atan2(ty, tx);\n a -= PI / 2.0;\n line(x, y, cos(a) * 8 + x, sin(a) * 8 + y);\n}\n\n
" + ], + "alt": "right curving line mid-right of canvas with 7 short lines radiating from it.", + "overloads": [ + { + "params": [ + { + "name": "a", + "description": "coordinate of first control point", + "type": "Number" + }, + { + "name": "b", + "description": "coordinate of first point on the curve", + "type": "Number" + }, + { + "name": "c", + "description": "coordinate of second point on the curve", + "type": "Number" + }, + { + "name": "d", + "description": "coordinate of second conrol point", + "type": "Number" + }, + { + "name": "t", + "description": "value between 0 and 1", + "type": "Number" + } + ], + "return": { + "description": "the tangent at position t", + "type": "Number" + } + } + ], + "return": { + "description": "the tangent at position t", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Curves" + }, + { + "name": "beginContour", + "file": "src/core/shape/vertex.js", + "line": 61, + "itemtype": "method", + "chainable": 1, + "description": "

Use the beginContour() and\nendContour() functions to create negative shapes\nwithin shapes such as the center of the letter 'O'. beginContour()\nbegins recording vertices for the shape and endContour() stops recording.\nThe vertices that define a negative shape must \"wind\" in the opposite direction\nfrom the exterior shape. First draw vertices for the exterior clockwise order, then for internal shapes, draw vertices\nshape in counter-clockwise.

\n

These functions can only be used within a beginShape()/endShape() pair and\ntransformations such as translate(), rotate(), and scale() do not work\nwithin a beginContour()/endContour() pair. It is also not possible to use\nother shapes, such as ellipse() or rect() within.

\n", + "example": [ + "
\n\ntranslate(50, 50);\nstroke(255, 0, 0);\nbeginShape();\n// Exterior part of shape, clockwise winding\nvertex(-40, -40);\nvertex(40, -40);\nvertex(40, 40);\nvertex(-40, 40);\n// Interior part of shape, counter-clockwise winding\nbeginContour();\nvertex(-20, -20);\nvertex(-20, 20);\nvertex(20, 20);\nvertex(20, -20);\nendContour();\nendShape(CLOSE);\n\n
" + ], + "alt": "white rect and smaller grey rect with red outlines in center of canvas.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Vertex" + }, + { + "name": "beginShape", + "file": "src/core/shape/vertex.js", + "line": 272, + "itemtype": "method", + "chainable": 1, + "description": "

Using the beginShape() and endShape() functions allow creating more\ncomplex forms. beginShape() begins recording vertices for a shape and\nendShape() stops recording. The value of the kind parameter tells it which\ntypes of shapes to create from the provided vertices. With no mode\nspecified, the shape can be any irregular polygon.

\n

The parameters available for beginShape() are:

\n

POINTS\nDraw a series of points

\n

LINES\nDraw a series of unconnected line segments (individual lines)

\n

TRIANGLES\nDraw a series of separate triangles

\n

TRIANGLE_FAN\nDraw a series of connected triangles sharing the first vertex in a fan-like fashion

\n

TRIANGLE_STRIP\nDraw a series of connected triangles in strip fashion

\n

QUADS\nDraw a series of separate quads

\n

QUAD_STRIP\nDraw quad strip using adjacent edges to form the next quad

\n

TESS (WEBGL only)\nHandle irregular polygon for filling curve by explicit tessellation

\n

After calling the beginShape() function, a series of vertex() commands must follow. To stop\ndrawing the shape, call endShape(). Each shape will be outlined with the\ncurrent stroke color and filled with the fill color.

\n

Transformations such as translate(), rotate(), and scale() do not work\nwithin beginShape(). It is also not possible to use other shapes, such as\nellipse() or rect() within beginShape().

\n", + "example": [ + "
\n\nbeginShape();\nvertex(30, 20);\nvertex(85, 20);\nvertex(85, 75);\nvertex(30, 75);\nendShape(CLOSE);\n\n
\n\n
\n\nbeginShape(POINTS);\nvertex(30, 20);\nvertex(85, 20);\nvertex(85, 75);\nvertex(30, 75);\nendShape();\n\n
\n\n
\n\nbeginShape(LINES);\nvertex(30, 20);\nvertex(85, 20);\nvertex(85, 75);\nvertex(30, 75);\nendShape();\n\n
\n\n
\n\nnoFill();\nbeginShape();\nvertex(30, 20);\nvertex(85, 20);\nvertex(85, 75);\nvertex(30, 75);\nendShape();\n\n
\n\n
\n\nnoFill();\nbeginShape();\nvertex(30, 20);\nvertex(85, 20);\nvertex(85, 75);\nvertex(30, 75);\nendShape(CLOSE);\n\n
\n\n
\n\nbeginShape(TRIANGLES);\nvertex(30, 75);\nvertex(40, 20);\nvertex(50, 75);\nvertex(60, 20);\nvertex(70, 75);\nvertex(80, 20);\nendShape();\n\n
\n\n
\n\nbeginShape(TRIANGLE_STRIP);\nvertex(30, 75);\nvertex(40, 20);\nvertex(50, 75);\nvertex(60, 20);\nvertex(70, 75);\nvertex(80, 20);\nvertex(90, 75);\nendShape();\n\n
\n\n
\n\nbeginShape(TRIANGLE_FAN);\nvertex(57.5, 50);\nvertex(57.5, 15);\nvertex(92, 50);\nvertex(57.5, 85);\nvertex(22, 50);\nvertex(57.5, 15);\nendShape();\n\n
\n\n
\n\nbeginShape(QUADS);\nvertex(30, 20);\nvertex(30, 75);\nvertex(50, 75);\nvertex(50, 20);\nvertex(65, 20);\nvertex(65, 75);\nvertex(85, 75);\nvertex(85, 20);\nendShape();\n\n
\n\n
\n\nbeginShape(QUAD_STRIP);\nvertex(30, 20);\nvertex(30, 75);\nvertex(50, 20);\nvertex(50, 75);\nvertex(65, 20);\nvertex(65, 75);\nvertex(85, 20);\nvertex(85, 75);\nendShape();\n\n
\n\n
\n\nbeginShape(TESS);\nvertex(20, 20);\nvertex(80, 20);\nvertex(80, 40);\nvertex(40, 40);\nvertex(40, 60);\nvertex(80, 60);\nvertex(80, 80);\nvertex(20, 80);\nendShape(CLOSE);\n\n
" + ], + "alt": "white square-shape with black outline in middle-right of canvas.\n4 black points in a square shape in middle-right of canvas.\n2 horizontal black lines. In the top-right and bottom-right of canvas.\n3 line shape with horizontal on top, vertical in middle and horizontal bottom.\nsquare line shape in middle-right of canvas.\n2 white triangle shapes mid-right canvas. left one pointing up and right down.\n5 horizontal interlocking and alternating white triangles in mid-right canvas.\n4 interlocking white triangles in 45 degree rotated square-shape.\n2 white rectangle shapes in mid-right canvas. Both 20×55.\n3 side-by-side white rectangles center rect is smaller in mid-right canvas.\nThick white l-shape with black outline mid-top-left of canvas.", + "overloads": [ + { + "params": [ + { + "name": "kind", + "description": "either POINTS, LINES, TRIANGLES, TRIANGLE_FAN\nTRIANGLE_STRIP, QUADS, QUAD_STRIP or TESS", + "optional": 1, + "type": "POINTS|LINES|TRIANGLES|TRIANGLE_FAN|TRIANGLE_STRIP|QUADS|QUAD_STRIP|TESS" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Vertex" + }, + { + "name": "bezierVertex", + "file": "src/core/shape/vertex.js", + "line": 392, + "itemtype": "method", + "chainable": 1, + "description": "

Specifies vertex coordinates for Bezier curves. Each call to\nbezierVertex() defines the position of two control points and\none anchor point of a Bezier curve, adding a new segment to a\nline or shape. For WebGL mode bezierVertex() can be used in 2D\nas well as 3D mode. 2D mode expects 6 parameters, while 3D mode\nexpects 9 parameters (including z coordinates).

\n

The first time bezierVertex() is used within a beginShape()\ncall, it must be prefaced with a call to vertex() to set the first anchor\npoint. This function must be used between beginShape() and endShape()\nand only when there is no MODE or POINTS parameter specified to\nbeginShape().

\n", + "example": [ + "
\n\nnoFill();\nbeginShape();\nvertex(30, 20);\nbezierVertex(80, 0, 80, 75, 30, 75);\nendShape();\n\n
\n\n
\n\nbeginShape();\nvertex(30, 20);\nbezierVertex(80, 0, 80, 75, 30, 75);\nbezierVertex(50, 80, 60, 25, 30, 20);\nendShape();\n\n
\n\n
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n setAttributes('antialias', true);\n}\nfunction draw() {\n orbitControl();\n background(50);\n strokeWeight(4);\n stroke(255);\n point(-25, 30);\n point(25, 30);\n point(25, -30);\n point(-25, -30);\n\n strokeWeight(1);\n noFill();\n\n beginShape();\n vertex(-25, 30);\n bezierVertex(25, 30, 25, -30, -25, -30);\n endShape();\n\n beginShape();\n vertex(-25, 30, 20);\n bezierVertex(25, 30, 20, 25, -30, 20, -25, -30, 20);\n endShape();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x2", + "description": "x-coordinate for the first control point", + "type": "Number" + }, + { + "name": "y2", + "description": "y-coordinate for the first control point", + "type": "Number" + }, + { + "name": "x3", + "description": "x-coordinate for the second control point", + "type": "Number" + }, + { + "name": "y3", + "description": "y-coordinate for the second control point", + "type": "Number" + }, + { + "name": "x4", + "description": "x-coordinate for the anchor point", + "type": "Number" + }, + { + "name": "y4", + "description": "y-coordinate for the anchor point", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "x2", + "type": "Number" + }, + { + "name": "y2", + "type": "Number" + }, + { + "name": "z2", + "description": "z-coordinate for the first control point (for WebGL mode)", + "type": "Number" + }, + { + "name": "x3", + "type": "Number" + }, + { + "name": "y3", + "type": "Number" + }, + { + "name": "z3", + "description": "z-coordinate for the second control point (for WebGL mode)", + "type": "Number" + }, + { + "name": "x4", + "type": "Number" + }, + { + "name": "y4", + "type": "Number" + }, + { + "name": "z4", + "description": "z-coordinate for the anchor point (for WebGL mode)", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Vertex" + }, + { + "name": "curveVertex", + "file": "src/core/shape/vertex.js", + "line": 517, + "itemtype": "method", + "chainable": 1, + "description": "

Specifies vertex coordinates for curves. This function may only\nbe used between beginShape() and endShape() and only when there\nis no MODE parameter specified to beginShape().\nFor WebGL mode curveVertex() can be used in 2D as well as 3D mode.\n2D mode expects 2 parameters, while 3D mode expects 3 parameters.

\n

The first and last points in a series of curveVertex() lines will be used to\nguide the beginning and end of the curve. A minimum of four\npoints is required to draw a tiny curve between the second and\nthird points. Adding a fifth point with curveVertex() will draw\nthe curve between the second, third, and fourth points. The\ncurveVertex() function is an implementation of Catmull-Rom\nsplines.

\n", + "example": [ + "
\n\nstrokeWeight(5);\npoint(84, 91);\npoint(68, 19);\npoint(21, 17);\npoint(32, 91);\nstrokeWeight(1);\n\nnoFill();\nbeginShape();\ncurveVertex(84, 91);\ncurveVertex(84, 91);\ncurveVertex(68, 19);\ncurveVertex(21, 17);\ncurveVertex(32, 91);\ncurveVertex(32, 91);\nendShape();\n\n
", + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n setAttributes('antialias', true);\n}\nfunction draw() {\n orbitControl();\n background(50);\n strokeWeight(4);\n stroke(255);\n\n point(-25, 25);\n point(-25, 25);\n point(-25, -25);\n point(25, -25);\n point(25, 25);\n point(25, 25);\n\n strokeWeight(1);\n noFill();\n\n beginShape();\n curveVertex(-25, 25);\n curveVertex(-25, 25);\n curveVertex(-25, -25);\n curveVertex(25, -25);\n curveVertex(25, 25);\n curveVertex(25, 25);\n endShape();\n\n beginShape();\n curveVertex(-25, 25, 20);\n curveVertex(-25, 25, 20);\n curveVertex(-25, -25, 20);\n curveVertex(25, -25, 20);\n curveVertex(25, 25, 20);\n curveVertex(25, 25, 20);\n endShape();\n}\n\n
" + ], + "alt": "Upside-down u-shape line, mid canvas with the same shape in positive z-axis.", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the vertex", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the vertex", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "z", + "description": "z-coordinate of the vertex (for WebGL mode)", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Vertex" + }, + { + "name": "endContour", + "file": "src/core/shape/vertex.js", + "line": 569, + "itemtype": "method", + "chainable": 1, + "description": "

Use the beginContour() and endContour() functions to create negative\nshapes within shapes such as the center of the letter 'O'. beginContour()\nbegins recording vertices for the shape and endContour() stops recording.\nThe vertices that define a negative shape must \"wind\" in the opposite\ndirection from the exterior shape. First draw vertices for the exterior\nclockwise order, then for internal shapes, draw vertices\nshape in counter-clockwise.

\n

These functions can only be used within a beginShape()/endShape() pair and\ntransformations such as translate(), rotate(), and scale() do not work\nwithin a beginContour()/endContour() pair. It is also not possible to use\nother shapes, such as ellipse() or rect() within.

\n", + "example": [ + "
\n\ntranslate(50, 50);\nstroke(255, 0, 0);\nbeginShape();\n// Exterior part of shape, clockwise winding\nvertex(-40, -40);\nvertex(40, -40);\nvertex(40, 40);\nvertex(-40, 40);\n// Interior part of shape, counter-clockwise winding\nbeginContour();\nvertex(-20, -20);\nvertex(-20, 20);\nvertex(20, 20);\nvertex(20, -20);\nendContour();\nendShape(CLOSE);\n\n
" + ], + "alt": "white rect and smaller grey rect with red outlines in center of canvas.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Vertex" + }, + { + "name": "endShape", + "file": "src/core/shape/vertex.js", + "line": 710, + "itemtype": "method", + "chainable": 1, + "description": "The endShape() function is the companion to beginShape() and may only be\ncalled after beginShape(). When endShape() is called, all of the image\ndata defined since the previous call to beginShape() is written into the image\nbuffer. The constant CLOSE is the value for the mode parameter to close\nthe shape (to connect the beginning and the end).\nWhen using instancing with endShape() the instancing will not apply to the strokes.\nWhen the count parameter is used with a value greater than 1, it enables instancing for shapes built when in WEBGL mode. Instancing\nis a feature that allows the GPU to efficiently draw multiples of the same shape. It's often used for particle effects or other\ntimes when you need a lot of repetition. In order to take advantage of instancing, you will also need to write your own custom\nshader using the gl_InstanceID keyword. You can read more about instancing\nhere or by working from the example on this\npage.", + "example": [ + "
\n\nnoFill();\n\nbeginShape();\nvertex(20, 20);\nvertex(45, 20);\nvertex(45, 80);\nendShape(CLOSE);\n\nbeginShape();\nvertex(50, 20);\nvertex(75, 20);\nvertex(75, 80);\nendShape();\n\n
", + "
\n\nlet fx;\nlet vs = `#version 300 es\n\nprecision mediump float;\n\nin vec3 aPosition;\nflat out int instanceID;\n\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\n\nvoid main() {\n\n // copy the instance ID to the fragment shader\n instanceID = gl_InstanceID;\n vec4 positionVec4 = vec4(aPosition, 1.0);\n\n // gl_InstanceID represents a numeric value for each instance\n // using gl_InstanceID allows us to move each instance separately\n // here we move each instance horizontally by id * 23\n float xOffset = float(gl_InstanceID) * 23.0;\n\n // apply the offset to the final position\n gl_Position = uProjectionMatrix * uModelViewMatrix * (positionVec4 -\n vec4(xOffset, 0.0, 0.0, 0.0));\n}\n`;\nlet fs = `#version 300 es\n\nprecision mediump float;\n\nout vec4 outColor;\nflat in int instanceID;\nuniform float numInstances;\n\nvoid main() {\n vec4 red = vec4(1.0, 0.0, 0.0, 1.0);\n vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);\n\n // Normalize the instance id\n float normId = float(instanceID) / numInstances;\n\n // Mix between two colors using the normalized instance id\n outColor = mix(red, blue, normId);\n}\n`;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n fx = createShader(vs, fs);\n}\n\nfunction draw() {\n background(220);\n\n // strokes aren't instanced, and are rather used for debug purposes\n shader(fx);\n fx.setUniform('numInstances', 4);\n\n // this doesn't have to do with instancing, this is just for centering the squares\n translate(25, -10);\n\n // here we draw the squares we want to instance\n beginShape();\n vertex(0, 0);\n vertex(0, 20);\n vertex(20, 20);\n vertex(20, 0);\n vertex(0, 0);\n endShape(CLOSE, 4);\n\n resetShader();\n}\n\n
" + ], + "alt": "Triangle line shape with smallest interior angle on bottom and upside-down L.", + "overloads": [ + { + "params": [ + { + "name": "mode", + "description": "use CLOSE to close the shape", + "optional": 1, + "type": "CLOSE" + }, + { + "name": "count", + "description": "number of times you want to draw/instance the shape (for WebGL mode).", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Vertex" + }, + { + "name": "quadraticVertex", + "file": "src/core/shape/vertex.js", + "line": 890, + "itemtype": "method", + "chainable": 1, + "description": "

Specifies vertex coordinates for quadratic Bezier curves. Each call to\nquadraticVertex() defines the position of one control points and one\nanchor point of a Bezier curve, adding a new segment to a line or shape.\nThe first time quadraticVertex() is used within a beginShape() call, it\nmust be prefaced with a call to vertex() to set the first anchor point.\nFor WebGL mode quadraticVertex() can be used in 2D as well as 3D mode.\n2D mode expects 4 parameters, while 3D mode expects 6 parameters\n(including z coordinates).

\n

This function must be used between beginShape() and endShape()\nand only when there is no MODE or POINTS parameter specified to\nbeginShape().

\n", + "example": [ + "
\n\nstrokeWeight(5);\npoint(20, 20);\npoint(80, 20);\npoint(50, 50);\n\nnoFill();\nstrokeWeight(1);\nbeginShape();\nvertex(20, 20);\nquadraticVertex(80, 20, 50, 50);\nendShape();\n\n
\n\n
\n\nstrokeWeight(5);\npoint(20, 20);\npoint(80, 20);\npoint(50, 50);\n\npoint(20, 80);\npoint(80, 80);\npoint(80, 60);\n\nnoFill();\nstrokeWeight(1);\nbeginShape();\nvertex(20, 20);\nquadraticVertex(80, 20, 50, 50);\nquadraticVertex(20, 80, 80, 80);\nvertex(80, 60);\nendShape();\n\n
", + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n setAttributes('antialias', true);\n}\nfunction draw() {\n orbitControl();\n background(50);\n strokeWeight(4);\n stroke(255);\n\n point(-35, -35);\n point(35, -35);\n point(0, 0);\n point(-35, 35);\n point(35, 35);\n point(35, 10);\n\n strokeWeight(1);\n noFill();\n\n beginShape();\n vertex(-35, -35);\n quadraticVertex(35, -35, 0, 0);\n quadraticVertex(-35, 35, 35, 35);\n vertex(35, 10);\n endShape();\n\n beginShape();\n vertex(-35, -35, 20);\n quadraticVertex(35, -35, 20, 0, 0, 20);\n quadraticVertex(-35, 35, 20, 35, 35, 20);\n vertex(35, 10, 20);\n endShape();\n}\n\n
" + ], + "alt": "backwards s-shaped black line with the same s-shaped line in positive z-axis.", + "overloads": [ + { + "params": [ + { + "name": "cx", + "description": "x-coordinate for the control point", + "type": "Number" + }, + { + "name": "cy", + "description": "y-coordinate for the control point", + "type": "Number" + }, + { + "name": "x3", + "description": "x-coordinate for the anchor point", + "type": "Number" + }, + { + "name": "y3", + "description": "y-coordinate for the anchor point", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "cx", + "type": "Number" + }, + { + "name": "cy", + "type": "Number" + }, + { + "name": "cz", + "description": "z-coordinate for the control point (for WebGL mode)", + "type": "Number" + }, + { + "name": "x3", + "type": "Number" + }, + { + "name": "y3", + "type": "Number" + }, + { + "name": "z3", + "description": "z-coordinate for the anchor point (for WebGL mode)", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Vertex" + }, + { + "name": "vertex", + "file": "src/core/shape/vertex.js", + "line": 1078, + "itemtype": "method", + "chainable": 1, + "description": "All shapes are constructed by connecting a series of vertices. vertex()\nis used to specify the vertex coordinates for points, lines, triangles,\nquads, and polygons. It is used exclusively within the beginShape() and\nendShape() functions.", + "example": [ + "
\n\nstrokeWeight(3);\nbeginShape(POINTS);\nvertex(30, 20);\nvertex(85, 20);\nvertex(85, 75);\nvertex(30, 75);\nendShape();\n\n
\n\n
\n\ncreateCanvas(100, 100, WEBGL);\nbackground(240, 240, 240);\nfill(237, 34, 93);\nnoStroke();\nbeginShape();\nvertex(0, 35);\nvertex(35, 0);\nvertex(0, -35);\nvertex(-35, 0);\nendShape();\n\n
\n\n
\n\ncreateCanvas(100, 100, WEBGL);\nbackground(240, 240, 240);\nfill(237, 34, 93);\nnoStroke();\nbeginShape();\nvertex(-10, 10);\nvertex(0, 35);\nvertex(10, 10);\nvertex(35, 0);\nvertex(10, -8);\nvertex(0, -35);\nvertex(-10, -8);\nvertex(-35, 0);\nendShape();\n\n
\n\n
\n\nstrokeWeight(3);\nstroke(237, 34, 93);\nbeginShape(LINES);\nvertex(10, 35);\nvertex(90, 35);\nvertex(10, 65);\nvertex(90, 65);\nvertex(35, 10);\nvertex(35, 90);\nvertex(65, 10);\nvertex(65, 90);\nendShape();\n\n
\n\n
\n\n// Click to change the number of sides.\n// In WebGL mode, custom shapes will only\n// display hollow fill sections when\n// all calls to vertex() use the same z-value.\n\nlet sides = 3;\nlet angle, px, py;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n setAttributes('antialias', true);\n fill(237, 34, 93);\n strokeWeight(3);\n}\n\nfunction draw() {\n background(200);\n rotateX(frameCount * 0.01);\n rotateZ(frameCount * 0.01);\n ngon(sides, 0, 0, 80);\n}\n\nfunction mouseClicked() {\n if (sides > 6) {\n sides = 3;\n } else {\n sides++;\n }\n}\n\nfunction ngon(n, x, y, d) {\n beginShape(TESS);\n for (let i = 0; i < n + 1; i++) {\n angle = TWO_PI / n * i;\n px = x + sin(angle) * d / 2;\n py = y - cos(angle) * d / 2;\n vertex(px, py, 0);\n }\n for (let i = 0; i < n + 1; i++) {\n angle = TWO_PI / n * i;\n px = x + sin(angle) * d / 4;\n py = y - cos(angle) * d / 4;\n vertex(px, py, 0);\n }\n endShape();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the vertex", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the vertex", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "z", + "description": "z-coordinate of the vertex.\nDefaults to 0 if not specified.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "z", + "optional": 1, + "type": "Number" + }, + { + "name": "u", + "description": "the vertex's texture u-coordinate", + "optional": 1, + "type": "Number" + }, + { + "name": "v", + "description": "the vertex's texture v-coordinate", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Vertex" + }, + { + "name": "normal", + "file": "src/core/shape/vertex.js", + "line": 1151, + "itemtype": "method", + "chainable": 1, + "description": "Sets the 3d vertex normal to use for subsequent vertices drawn with\nvertex(). A normal is a vector that is generally\nnearly perpendicular to a shape's surface which controls how much light will\nbe reflected from that part of the surface.", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n noStroke();\n}\n\nfunction draw() {\n background(255);\n rotateY(frameCount / 100);\n normalMaterial();\n beginShape(TRIANGLE_STRIP);\n normal(-0.4, 0.4, 0.8);\n vertex(-30, 30, 0);\n\n normal(0, 0, 1);\n vertex(-30, -30, 30);\n vertex(30, 30, 30);\n\n normal(0.4, -0.4, 0.8);\n vertex(30, -30, 0);\n endShape();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "vector", + "description": "A p5.Vector representing the vertex normal.", + "type": "Vector" + } + ] + }, + { + "params": [ + { + "name": "x", + "description": "The x component of the vertex normal.", + "type": "Number" + }, + { + "name": "y", + "description": "The y component of the vertex normal.", + "type": "Number" + }, + { + "name": "z", + "description": "The z component of the vertex normal.", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "Vertex" + }, + { + "name": "noLoop", + "file": "src/core/structure.js", + "line": 78, + "itemtype": "method", + "description": "

Stops p5.js from continuously executing the code within draw().\nIf loop() is called, the code in draw()\nbegins to run continuously again. If using noLoop()\nin setup(), it should be the last line inside the block.

\n

When noLoop() is used, it's not possible to manipulate\nor access the screen inside event handling functions such as\nmousePressed() or\nkeyPressed(). Instead, use those functions to\ncall redraw() or loop(),\nwhich will run draw(), which can update the screen\nproperly. This means that when noLoop() has been\ncalled, no drawing can happen, and functions like saveFrames()\nor loadPixels() may not be used.

\n

Note that if the sketch is resized, redraw() will\nbe called to update the sketch, even after noLoop()\nhas been specified. Otherwise, the sketch would enter an odd state until\nloop() was called.

\n

Use isLooping() to check the current state of loop().

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n noLoop();\n}\n\nfunction draw() {\n line(10, 10, 90, 90);\n}\n\n
\n\n
\n\nlet x = 0;\nfunction setup() {\n createCanvas(100, 100);\n}\n\nfunction draw() {\n background(204);\n x = x + 0.1;\n if (x > width) {\n x = 0;\n }\n line(x, 0, x, height);\n}\n\nfunction mousePressed() {\n noLoop();\n}\n\nfunction mouseReleased() {\n loop();\n}\n\n
" + ], + "alt": "113 pixel long line extending from top-left to bottom right of canvas.\nhorizontal line moves slowly from left. Loops but stops on mouse press.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Structure", + "submodule": "Structure" + }, + { + "name": "loop", + "file": "src/core/structure.js", + "line": 124, + "itemtype": "method", + "description": "

By default, p5.js loops through draw() continuously, executing the code within\nit. However, the draw() loop may be stopped by calling\nnoLoop(). In that case, the draw()\nloop can be resumed with loop().

\n

Avoid calling loop() from inside setup().

\n

Use isLooping() to check the current state of loop().

\n", + "example": [ + "
\n\nlet x = 0;\nfunction setup() {\n createCanvas(100, 100);\n noLoop();\n}\n\nfunction draw() {\n background(204);\n x = x + 0.1;\n if (x > width) {\n x = 0;\n }\n line(x, 0, x, height);\n}\n\nfunction mousePressed() {\n loop();\n}\n\nfunction mouseReleased() {\n noLoop();\n}\n\n
" + ], + "alt": "horizontal line moves slowly from left. Loops but stops on mouse press.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Structure", + "submodule": "Structure" + }, + { + "name": "isLooping", + "file": "src/core/structure.js", + "line": 188, + "itemtype": "method", + "description": "By default, p5.js loops through draw() continuously,\nexecuting the code within it. If the sketch is stopped with\nnoLoop() or resumed with loop(),\nisLooping() returns the current state for use within custom event handlers.", + "example": [ + "
\n\nlet checkbox, button, colBG, colFill;\n\nfunction setup() {\n createCanvas(100, 100);\n\n button = createButton('Colorize if loop()');\n button.position(0, 120);\n button.mousePressed(changeBG);\n\n checkbox = createCheckbox('loop()', true);\n checkbox.changed(checkLoop);\n\n colBG = color(0);\n colFill = color(255);\n}\n\nfunction changeBG() {\n if (isLooping()) {\n colBG = color(random(255), random(255), random(255));\n colFill = color(random(255), random(255), random(255));\n }\n}\n\nfunction checkLoop() {\n if (this.checked()) {\n loop();\n } else {\n noLoop();\n }\n}\n\nfunction draw() {\n background(colBG);\n fill(colFill);\n ellipse(frameCount % width, height / 2, 50);\n}\n\n
" + ], + "alt": "Ellipse moves slowly from left. Checkbox toggles loop()/noLoop().\nButton colorizes sketch if isLooping().", + "overloads": [ + { + "params": [], + "return": { + "description": "", + "type": "boolean" + } + } + ], + "return": { + "description": "", + "type": "boolean" + }, + "class": "p5", + "static": false, + "module": "Structure", + "submodule": "Structure" + }, + { + "name": "push", + "file": "src/core/structure.js", + "line": 281, + "itemtype": "method", + "description": "

The push() function saves the current drawing style\nsettings and transformations, while pop() restores these\nsettings. Note that these functions are always used together. They allow you to\nchange the style and transformation settings and later return to what you had.\nWhen a new state is started with push(), it builds on\nthe current style and transform information. The push()\nand pop() functions can be embedded to provide more\ncontrol. (See the second example for a demonstration.)

\n

push() stores information related to the current transformation state\nand style settings controlled by the following functions:\nfill(),\nnoFill(),\nnoStroke(),\nstroke(),\ntint(),\nnoTint(),\nstrokeWeight(),\nstrokeCap(),\nstrokeJoin(),\nimageMode(),\nrectMode(),\nellipseMode(),\ncolorMode(),\ntextAlign(),\ntextFont(),\ntextSize(),\ntextLeading(),\napplyMatrix(),\nresetMatrix(),\nrotate(),\nscale(),\nshearX(),\nshearY(),\ntranslate(),\nnoiseSeed().

\n

In WEBGL mode additional style settings are stored. These are controlled by the\nfollowing functions: setCamera(),\nambientLight(),\ndirectionalLight(),\npointLight(), texture(),\nspecularMaterial(),\nshininess(),\nnormalMaterial()\nand shader().

\n", + "example": [ + "
\n\nellipse(0, 50, 33, 33); // Left circle\n\npush(); // Start a new drawing state\nstrokeWeight(10);\nfill(204, 153, 0);\ntranslate(50, 0);\nellipse(0, 50, 33, 33); // Middle circle\npop(); // Restore original state\n\nellipse(100, 50, 33, 33); // Right circle\n\n
\n\n
\n\nellipse(0, 50, 33, 33); // Left circle\n\npush(); // Start a new drawing state\nstrokeWeight(10);\nfill(204, 153, 0);\nellipse(33, 50, 33, 33); // Left-middle circle\n\npush(); // Start another new drawing state\nstroke(0, 102, 153);\nellipse(66, 50, 33, 33); // Right-middle circle\npop(); // Restore previous state\n\npop(); // Restore original state\n\nellipse(100, 50, 33, 33); // Right circle\n\n
" + ], + "alt": "Gold ellipse + thick black outline @center 2 white ellipses on left and right.\n2 Gold ellipses left black right blue stroke. 2 white ellipses on left+right.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Structure", + "submodule": "Structure" + }, + { + "name": "pop", + "file": "src/core/structure.js", + "line": 381, + "itemtype": "method", + "description": "

The push() function saves the current drawing style\nsettings and transformations, while pop() restores\nthese settings. Note that these functions are always used together. They allow\nyou to change the style and transformation settings and later return to what\nyou had. When a new state is started with push(), it\nbuilds on the current style and transform information. The push()\nand pop() functions can be embedded to provide more\ncontrol. (See the second example for a demonstration.)

\n

push() stores information related to the current transformation state\nand style settings controlled by the following functions:\nfill(),\nnoFill(),\nnoStroke(),\nstroke(),\ntint(),\nnoTint(),\nstrokeWeight(),\nstrokeCap(),\nstrokeJoin(),\nimageMode(),\nrectMode(),\nellipseMode(),\ncolorMode(),\ntextAlign(),\ntextFont(),\ntextSize(),\ntextLeading(),\napplyMatrix(),\nresetMatrix(),\nrotate(),\nscale(),\nshearX(),\nshearY(),\ntranslate(),\nnoiseSeed().

\n

In WEBGL mode additional style settings are stored. These are controlled by\nthe following functions:\nsetCamera(),\nambientLight(),\ndirectionalLight(),\npointLight(),\ntexture(),\nspecularMaterial(),\nshininess(),\nnormalMaterial() and\nshader().

\n", + "example": [ + "
\n\nellipse(0, 50, 33, 33); // Left circle\n\npush(); // Start a new drawing state\ntranslate(50, 0);\nstrokeWeight(10);\nfill(204, 153, 0);\nellipse(0, 50, 33, 33); // Middle circle\npop(); // Restore original state\n\nellipse(100, 50, 33, 33); // Right circle\n\n
\n\n
\n\nellipse(0, 50, 33, 33); // Left circle\n\npush(); // Start a new drawing state\nstrokeWeight(10);\nfill(204, 153, 0);\nellipse(33, 50, 33, 33); // Left-middle circle\n\npush(); // Start another new drawing state\nstroke(0, 102, 153);\nellipse(66, 50, 33, 33); // Right-middle circle\npop(); // Restore previous state\n\npop(); // Restore original state\n\nellipse(100, 50, 33, 33); // Right circle\n\n
" + ], + "alt": "Gold ellipse + thick black outline @center 2 white ellipses on left and right.\n2 Gold ellipses left black right blue stroke. 2 white ellipses on left+right.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Structure", + "submodule": "Structure" + }, + { + "name": "redraw", + "file": "src/core/structure.js", + "line": 458, + "itemtype": "method", + "description": "

Executes the code within draw() one time. This\nfunction allows the program to update the display window only when necessary,\nfor example when an event registered by mousePressed()\nor keyPressed() occurs.

\n

In structuring a program, it only makes sense to call redraw()\nwithin events such as mousePressed(). This\nis because redraw() does not run\ndraw() immediately (it only sets a flag that indicates\nan update is needed).

\n

The redraw() function does not work properly when\ncalled inside draw().To enable/disable animations,\nuse loop() and noLoop().

\n

In addition you can set the number of redraws per method call. Just\nadd an integer as single parameter for the number of redraws.

\n", + "example": [ + "
\nlet x = 0;\n\nfunction setup() {\n createCanvas(100, 100);\n noLoop();\n}\n\nfunction draw() {\n background(204);\n line(x, 0, x, height);\n}\n\nfunction mousePressed() {\n x += 1;\n redraw();\n}\n\n
\n\n
\n\nlet x = 0;\n\nfunction setup() {\n createCanvas(100, 100);\n noLoop();\n}\n\nfunction draw() {\n background(204);\n x += 1;\n line(x, 0, x, height);\n}\n\nfunction mousePressed() {\n redraw(5);\n}\n\n
" + ], + "alt": "black line on far left of canvas\nblack line on far left of canvas", + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "Redraw for n-times. The default value is 1.", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Structure", + "submodule": "Structure" + }, + { + "name": "applyMatrix", + "file": "src/core/transform.js", + "line": 187, + "itemtype": "method", + "chainable": 1, + "description": "

Multiplies the current matrix by the one specified through the parameters.\nThis is a powerful operation that can perform the equivalent of translate,\nscale, shear and rotate all at once. You can learn more about transformation\nmatrices on \nWikipedia.

\n

The naming of the arguments here follows the naming of the \nWHATWG specification and corresponds to a\ntransformation matrix of the\nform:

\n

\n", + "example": [ + "
\n\nfunction setup() {\n frameRate(10);\n rectMode(CENTER);\n}\n\nfunction draw() {\n let step = frameCount % 20;\n background(200);\n // Equivalent to translate(x, y);\n applyMatrix(1, 0, 0, 1, 40 + step, 50);\n rect(0, 0, 50, 50);\n}\n\n
\n\n
\n\nfunction setup() {\n frameRate(10);\n rectMode(CENTER);\n}\n\nfunction draw() {\n let step = frameCount % 20;\n background(200);\n translate(50, 50);\n // Equivalent to scale(x, y);\n applyMatrix(1 / step, 0, 0, 1 / step, 0, 0);\n rect(0, 0, 50, 50);\n}\n\n
\n\n
\n\nfunction setup() {\n frameRate(10);\n rectMode(CENTER);\n}\n\nfunction draw() {\n let step = frameCount % 20;\n let angle = map(step, 0, 20, 0, TWO_PI);\n let cos_a = cos(angle);\n let sin_a = sin(angle);\n background(200);\n translate(50, 50);\n // Equivalent to rotate(angle);\n applyMatrix(cos_a, sin_a, -sin_a, cos_a, 0, 0);\n rect(0, 0, 50, 50);\n}\n\n
\n\n
\n\nfunction setup() {\n frameRate(10);\n rectMode(CENTER);\n}\n\nfunction draw() {\n let step = frameCount % 20;\n let angle = map(step, 0, 20, -PI / 4, PI / 4);\n background(200);\n translate(50, 50);\n // equivalent to shearX(angle);\n let shear_factor = 1 / tan(PI / 2 - angle);\n applyMatrix(1, 0, shear_factor, 1, 0, 0);\n rect(0, 0, 50, 50);\n}\n\n
\n\n
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n noFill();\n}\n\nfunction draw() {\n background(200);\n rotateY(PI / 6);\n stroke(153);\n box(35);\n let rad = millis() / 1000;\n // Set rotation angles\n let ct = cos(rad);\n let st = sin(rad);\n // Matrix for rotation around the Y axis\n applyMatrix(\n ct, 0.0, st, 0.0,\n 0.0, 1.0, 0.0, 0.0,\n -st, 0.0, ct, 0.0,\n 0.0, 0.0, 0.0, 1.0\n );\n stroke(255);\n box(50);\n}\n\n
\n\n
\n\nfunction draw() {\n background(200);\n let testMatrix = [1, 0, 0, 1, 0, 0];\n applyMatrix(testMatrix);\n rect(0, 0, 50, 50);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "arr", + "description": "an array of numbers - should be 6 or 16 length (2×3 or 4×4 matrix values)", + "type": "Array" + } + ] + }, + { + "params": [ + { + "name": "a", + "description": "numbers which define the 2×3 or 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "b", + "description": "numbers which define the 2×3 or 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "c", + "description": "numbers which define the 2×3 or 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "d", + "description": "numbers which define the 2×3 or 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "e", + "description": "numbers which define the 2×3 or 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "f", + "description": "numbers which define the 2×3 or 4×4 matrix to be multiplied", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "a", + "type": "Number" + }, + { + "name": "b", + "type": "Number" + }, + { + "name": "c", + "type": "Number" + }, + { + "name": "d", + "type": "Number" + }, + { + "name": "e", + "type": "Number" + }, + { + "name": "f", + "type": "Number" + }, + { + "name": "g", + "description": "numbers which define the 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "h", + "description": "numbers which define the 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "i", + "description": "numbers which define the 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "j", + "description": "numbers which define the 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "k", + "description": "numbers which define the 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "l", + "description": "numbers which define the 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "m", + "description": "numbers which define the 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "n", + "description": "numbers which define the 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "o", + "description": "numbers which define the 4×4 matrix to be multiplied", + "type": "Number" + }, + { + "name": "p", + "description": "numbers which define the 4×4 matrix to be multiplied", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Transform", + "submodule": "Transform" + }, + { + "name": "resetMatrix", + "file": "src/core/transform.js", + "line": 217, + "itemtype": "method", + "chainable": 1, + "description": "Replaces the current matrix with the identity matrix.", + "example": [ + "
\n\ntranslate(50, 50);\napplyMatrix(0.5, 0.5, -0.5, 0.5, 0, 0);\nrect(0, 0, 20, 20);\n// Note that the translate is also reset.\nresetMatrix();\nrect(0, 0, 20, 20);\n\n
" + ], + "alt": "A rotated rectangle in the center with another at the top left corner", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Transform", + "submodule": "Transform" + }, + { + "name": "rotate", + "file": "src/core/transform.js", + "line": 255, + "itemtype": "method", + "chainable": 1, + "description": "

Rotates a shape by the amount specified by the angle parameter. This\nfunction accounts for angleMode, so angles\ncan be entered in either RADIANS or DEGREES.

\n

Objects are always rotated around their relative position to the\norigin and positive numbers rotate objects in a clockwise direction.\nTransformations apply to everything that happens after and subsequent\ncalls to the function accumulate the effect. For example, calling\nrotate(HALF_PI) and then rotate(HALF_PI) is the same as rotate(PI).\nAll transformations are reset when draw() begins again.

\n

Technically, rotate() multiplies the current transformation matrix\nby a rotation matrix. This function can be further controlled by\npush() and pop().

\n", + "example": [ + "
\n\ntranslate(width / 2, height / 2);\nrotate(PI / 3.0);\nrect(-26, -26, 52, 52);\n\n
" + ], + "alt": "white 52×52 rect with black outline at center rotated counter 45 degrees", + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "the angle of rotation, specified in radians\nor degrees, depending on current angleMode", + "type": "Number" + }, + { + "name": "axis", + "description": "(in 3d) the axis to rotate around", + "optional": 1, + "type": "p5.Vector|Number[]" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Transform", + "submodule": "Transform" + }, + { + "name": "rotateX", + "file": "src/core/transform.js", + "line": 292, + "itemtype": "method", + "chainable": 1, + "description": "

Rotates a shape around X axis by the amount specified in angle parameter.\nThe angles can be entered in either RADIANS or DEGREES.

\n

Objects are always rotated around their relative position to the\norigin and positive numbers rotate objects in a clockwise direction.\nAll transformations are reset when draw() begins again.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n}\nfunction draw() {\n background(255);\n rotateX(millis() / 1000);\n box();\n}\n\n
" + ], + "alt": "3d box rotating around the x axis.", + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "the angle of rotation, specified in radians\nor degrees, depending on current angleMode", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Transform", + "submodule": "Transform" + }, + { + "name": "rotateY", + "file": "src/core/transform.js", + "line": 330, + "itemtype": "method", + "chainable": 1, + "description": "

Rotates a shape around Y axis by the amount specified in angle parameter.\nThe angles can be entered in either RADIANS or DEGREES.

\n

Objects are always rotated around their relative position to the\norigin and positive numbers rotate objects in a clockwise direction.\nAll transformations are reset when draw() begins again.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n}\nfunction draw() {\n background(255);\n rotateY(millis() / 1000);\n box();\n}\n\n
" + ], + "alt": "3d box rotating around the y axis.", + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "the angle of rotation, specified in radians\nor degrees, depending on current angleMode", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Transform", + "submodule": "Transform" + }, + { + "name": "rotateZ", + "file": "src/core/transform.js", + "line": 370, + "itemtype": "method", + "chainable": 1, + "description": "

Rotates a shape around Z axis by the amount specified in angle parameter.\nThe angles can be entered in either RADIANS or DEGREES.

\n

This method works in WEBGL mode only.

\n

Objects are always rotated around their relative position to the\norigin and positive numbers rotate objects in a clockwise direction.\nAll transformations are reset when draw() begins again.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n}\nfunction draw() {\n background(255);\n rotateZ(millis() / 1000);\n box();\n}\n\n
" + ], + "alt": "3d box rotating around the z axis.", + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "the angle of rotation, specified in radians\nor degrees, depending on current angleMode", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Transform", + "submodule": "Transform" + }, + { + "name": "scale", + "file": "src/core/transform.js", + "line": 426, + "itemtype": "method", + "chainable": 1, + "description": "

Increases or decreases the size of a shape by expanding or contracting\nvertices. Objects always scale from their relative origin to the\ncoordinate system. Scale values are specified as decimal percentages.\nFor example, the function call scale(2.0) increases the dimension of a\nshape by 200%.

\n

Transformations apply to everything that happens after and subsequent\ncalls to the function multiply the effect. For example, calling scale(2.0)\nand then scale(1.5) is the same as scale(3.0). If scale() is called\nwithin draw(), the transformation is reset when the loop begins again.

\n

Using this function with the z parameter is only available in WEBGL mode.\nThis function can be further controlled with push() and pop().

\n", + "example": [ + "
\n\nrect(30, 20, 50, 50);\nscale(0.5);\nrect(30, 20, 50, 50);\n\n
\n\n
\n\nrect(30, 20, 50, 50);\nscale(0.5, 1.3);\nrect(30, 20, 50, 50);\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "s", + "description": "percent to scale the object, or percentage to\nscale the object in the x-axis if multiple arguments\nare given", + "type": "Number|p5.Vector|Number[]" + }, + { + "name": "y", + "description": "percent to scale the object in the y-axis", + "optional": 1, + "type": "Number" + }, + { + "name": "z", + "description": "percent to scale the object in the z-axis (webgl only)", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "scales", + "description": "per-axis percents to scale the object", + "type": "p5.Vector|Number[]" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Transform", + "submodule": "Transform" + }, + { + "name": "shearX", + "file": "src/core/transform.js", + "line": 483, + "itemtype": "method", + "chainable": 1, + "description": "

Shears a shape around the x-axis by the amount specified by the angle\nparameter. Angles should be specified in the current angleMode.\nObjects are always sheared around their relative position to the origin\nand positive numbers shear objects in a clockwise direction.

\n

Transformations apply to everything that happens after and subsequent\ncalls to the function accumulates the effect. For example, calling\nshearX(PI/2) and then shearX(PI/2) is the same as shearX(PI).\nIf shearX() is called within the draw(),\nthe transformation is reset when the loop begins again.

\n

Technically, shearX() multiplies the current\ntransformation matrix by a rotation matrix. This function can be further\ncontrolled by the push() and pop() functions.

\n", + "example": [ + "
\n\ntranslate(width / 4, height / 4);\nshearX(PI / 4.0);\nrect(0, 0, 30, 30);\n\n
" + ], + "alt": "white irregular quadrilateral with black outline at top middle.", + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "angle of shear specified in radians or degrees,\ndepending on current angleMode", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Transform", + "submodule": "Transform" + }, + { + "name": "shearY", + "file": "src/core/transform.js", + "line": 522, + "itemtype": "method", + "chainable": 1, + "description": "

Shears a shape around the y-axis the amount specified by the angle\nparameter. Angles should be specified in the current angleMode. Objects\nare always sheared around their relative position to the origin and\npositive numbers shear objects in a clockwise direction.

\n

Transformations apply to everything that happens after and subsequent\ncalls to the function accumulates the effect. For example, calling\nshearY(PI/2) and then shearY(PI/2) is the same as shearY(PI). If\nshearY() is called within the draw(), the transformation is reset when\nthe loop begins again.

\n

Technically, shearY() multiplies the current transformation matrix by a\nrotation matrix. This function can be further controlled by the\npush() and pop() functions.

\n", + "example": [ + "
\n\ntranslate(width / 4, height / 4);\nshearY(PI / 4.0);\nrect(0, 0, 30, 30);\n\n
" + ], + "alt": "white irregular quadrilateral with black outline at middle bottom.", + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "angle of shear specified in radians or degrees,\ndepending on current angleMode", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Transform", + "submodule": "Transform" + }, + { + "name": "translate", + "file": "src/core/transform.js", + "line": 587, + "itemtype": "method", + "chainable": 1, + "description": "

Specifies an amount to displace objects within the display window.\nThe x parameter specifies left/right translation, the y parameter\nspecifies up/down translation.

\n

Transformations are cumulative and apply to everything that happens after\nand subsequent calls to the function accumulates the effect. For example,\ncalling translate(50, 0) and then translate(20, 0) is the same as\ntranslate(70, 0). If translate() is called within draw(), the\ntransformation is reset when the loop begins again. This function can be\nfurther controlled by using push() and pop().

\n", + "example": [ + "
\n\ntranslate(30, 20);\nrect(0, 0, 55, 55);\n\n
\n\n
\n\nrect(0, 0, 55, 55); // Draw rect at original 0,0\ntranslate(30, 20);\nrect(0, 0, 55, 55); // Draw rect at new 0,0\ntranslate(14, 14);\nrect(0, 0, 55, 55); // Draw rect at new 0,0\n\n
\n\n\n
\n\nfunction draw() {\n background(200);\n rectMode(CENTER);\n translate(width / 2, height / 2);\n translate(p5.Vector.fromAngle(millis() / 1000, 40));\n rect(0, 0, 20, 20);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "left/right translation", + "type": "Number" + }, + { + "name": "y", + "description": "up/down translation", + "type": "Number" + }, + { + "name": "z", + "description": "forward/backward translation (WEBGL only)", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "vector", + "description": "the vector to translate by", + "type": "p5.Vector" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Transform", + "submodule": "Transform" + }, + { + "name": "storeItem", + "file": "src/data/local_storage.js", + "line": 58, + "itemtype": "method", + "description": "

Stores a value in local storage under the key name.\nLocal storage is saved in the browser and persists\nbetween browsing sessions and page reloads.\nThe key can be the name of the variable but doesn't\nhave to be. To retrieve stored items\nsee getItem.

\n

Sensitive data such as passwords or personal information\nshould not be stored in local storage.

\n", + "example": [ + "
\n// Type to change the letter in the\n// center of the canvas.\n// If you reload the page, it will\n// still display the last key you entered\n\nlet myText;\n\nfunction setup() {\n createCanvas(100, 100);\n myText = getItem('myText');\n if (myText === null) {\n myText = '';\n }\n describe(`When you type the key name is displayed as black text on white background.\n If you reload the page, the last letter typed is still displaying.`);\n}\n\nfunction draw() {\n textSize(40);\n background(255);\n text(myText, width / 2, height / 2);\n}\n\nfunction keyPressed() {\n myText = key;\n storeItem('myText', myText);\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "key", + "type": "String" + }, + { + "name": "value", + "type": "String|Number|Object|Boolean|p5.Color|p5.Vector" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Data", + "submodule": "LocalStorage" + }, + { + "name": "getItem", + "file": "src/data/local_storage.js", + "line": 139, + "itemtype": "method", + "description": "Returns the value of an item that was stored in local storage\nusing storeItem()", + "example": [ + "
\n// Click the mouse to change\n// the color of the background\n// Once you have changed the color\n// it will stay changed even when you\n// reload the page.\n\nlet myColor;\n\nfunction setup() {\n createCanvas(100, 100);\n myColor = getItem('myColor');\n}\n\nfunction draw() {\n if (myColor !== null) {\n background(myColor);\n }\n describe(`If you click, the canvas changes to a random color.·\n If you reload the page, the canvas is still the color it was when the\n page was previously loaded.`);\n}\n\nfunction mousePressed() {\n myColor = color(random(255), random(255), random(255));\n storeItem('myColor', myColor);\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "key", + "description": "name that you wish to use to store in local storage", + "type": "String" + } + ], + "return": { + "description": "Value of stored item", + "type": "Number|Object|String|Boolean|p5.Color|p5.Vector" + } + } + ], + "return": { + "description": "Value of stored item", + "type": "Number|Object|String|Boolean|p5.Color|p5.Vector" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "LocalStorage" + }, + { + "name": "clearStorage", + "file": "src/data/local_storage.js", + "line": 197, + "itemtype": "method", + "description": "Clears all local storage items set with storeItem()\nfor the current domain.", + "example": [ + "
\n\nfunction setup() {\n let myNum = 10;\n let myBool = false;\n storeItem('myNum', myNum);\n storeItem('myBool', myBool);\n print(getItem('myNum')); // logs 10 to the console\n print(getItem('myBool')); // logs false to the console\n clearStorage();\n print(getItem('myNum')); // logs null to the console\n print(getItem('myBool')); // logs null to the console\n}\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Data", + "submodule": "LocalStorage" + }, + { + "name": "removeItem", + "file": "src/data/local_storage.js", + "line": 221, + "itemtype": "method", + "description": "Removes an item that was stored with storeItem()", + "example": [ + "
\n\nfunction setup() {\n let myVar = 10;\n storeItem('myVar', myVar);\n print(getItem('myVar')); // logs 10 to the console\n removeItem('myVar');\n print(getItem('myVar')); // logs null to the console\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "key", + "type": "String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Data", + "submodule": "LocalStorage" + }, + { + "name": "createStringDict", + "file": "src/data/p5.TypedDict.js", + "line": 43, + "itemtype": "method", + "description": "Creates a new instance of p5.StringDict using the key-value pair\nor the object you provide.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createStringDict('p5', 'js');\n print(myDictionary.hasKey('p5')); // logs true to console\n\n let anotherDictionary = createStringDict({ happy: 'coding' });\n print(anotherDictionary.hasKey('happy')); // logs true to console\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "key", + "type": "String" + }, + { + "name": "value", + "type": "String" + } + ], + "return": { + "description": "", + "type": "p5.StringDict" + } + }, + { + "params": [ + { + "name": "object", + "description": "object", + "type": "Object" + } + ], + "return": { + "description": "", + "type": "p5.StringDict" + } + } + ], + "return": { + "description": "", + "type": "p5.StringDict" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "createNumberDict", + "file": "src/data/p5.TypedDict.js", + "line": 77, + "itemtype": "method", + "description": "Creates a new instance of p5.NumberDict using the key-value pair\nor object you provide.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createNumberDict(100, 42);\n print(myDictionary.hasKey(100)); // logs true to console\n\n let anotherDictionary = createNumberDict({ 200: 84 });\n print(anotherDictionary.hasKey(200)); // logs true to console\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "key", + "type": "Number" + }, + { + "name": "value", + "type": "Number" + } + ], + "return": { + "description": "", + "type": "p5.NumberDict" + } + }, + { + "params": [ + { + "name": "object", + "description": "object", + "type": "Object" + } + ], + "return": { + "description": "", + "type": "p5.NumberDict" + } + } + ], + "return": { + "description": "", + "type": "p5.NumberDict" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "select", + "file": "src/dom/dom.js", + "line": 92, + "itemtype": "method", + "description": "

Searches the page for the first element that matches the given\nCSS selector string.\nThe string can be an ID, class, tag name, or a combination. select()\nreturns a p5.Element object if it finds a match\nand null otherwise.

\n

The second parameter, container, is optional. It specifies a container to\nsearch within. container can be CSS selector string, a\np5.Element object, or an\nHTMLElement object.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n\n // Select the canvas by its tag.\n let cnv = select('canvas');\n cnv.style('border', '5px deeppink dashed');\n\n describe('A gray square with a dashed pink border.');\n}\n\n
\n\n
\n\nfunction setup() {\n let cnv = createCanvas(100, 100);\n // Add a class attribute to the canvas.\n cnv.class('pinkborder');\n\n background(200);\n\n // Select the canvas by its class.\n cnv = select('.pinkborder');\n // Style its border.\n cnv.style('border', '5px deeppink dashed');\n\n describe('A gray square with a dashed pink border.');\n}\n\n
\n\n
\n\nfunction setup() {\n let cnv = createCanvas(100, 100);\n // Set the canvas ID.\n cnv.id('mycanvas');\n\n background(200);\n\n // Select the canvas by its ID.\n cnv = select('#mycanvas');\n // Style its border.\n cnv.style('border', '5px deeppink dashed');\n\n describe('A gray square with a dashed pink border.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "selectors", + "description": "CSS selector string of element to search for.", + "type": "String" + }, + { + "name": "container", + "description": "CSS selector string, p5.Element, or\nHTMLElement to search within.", + "optional": 1, + "type": "String|p5.Element|HTMLElement" + } + ], + "return": { + "description": "p5.Element containing the element.", + "type": "p5.Element|" + } + } + ], + "return": { + "description": "p5.Element containing the element.", + "type": "p5.Element|" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "selectAll", + "file": "src/dom/dom.js", + "line": 177, + "itemtype": "method", + "description": "

Searches the page for all elements that matches the given\nCSS selector string.\nThe string can be an ID, class, tag name, or a combination. selectAll()\nreturns an array of p5.Element objects if it\nfinds any matches and an empty array otherwise.

\n

The second parameter, container, is optional. It specifies a container to\nsearch within. container can be CSS selector string, a\np5.Element object, or an\nHTMLElement object.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create three buttons.\n createButton('1');\n createButton('2');\n createButton('3');\n\n // Select the buttons by their tag.\n let buttons = selectAll('button');\n\n // Position the buttons.\n for (let i = 0; i < 3; i += 1) {\n buttons[i].position(0, i * 30);\n }\n\n describe('Three buttons stacked vertically. The buttons are labeled, \"1\", \"2\", and \"3\".');\n}\n\n
\n\n
\n\nfunction setup() {\n // Create three buttons and position them.\n let b1 = createButton('1');\n b1.position(0, 0);\n let b2 = createButton('2');\n b2.position(0, 30);\n let b3 = createButton('3');\n b3.position(0, 60);\n\n // Add a class attribute to each button.\n b1.class('btn');\n b2.class('btn btn-pink');\n b3.class('btn');\n\n // Select the buttons by their class.\n let buttons = selectAll('.btn');\n let pinkButtons = selectAll('.btn-pink');\n\n // Style the selected buttons.\n buttons.forEach(btn => {\n btn.style('font-family', 'Comic Sans MS');\n });\n\n pinkButtons.forEach(btn => {\n btn.style('background', 'deeppink');\n btn.style('color', 'white');\n });\n\n describe('Three buttons stacked vertically. The buttons are labeled, \"1\", \"2\", and \"3\". Buttons \"1\" and \"3\" are gray. Button \"2\" is pink.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "selectors", + "description": "CSS selector string of element to search for.", + "type": "String" + }, + { + "name": "container", + "description": "CSS selector string, p5.Element, or\nHTMLElement to search within.", + "optional": 1, + "type": "String|p5.Element|HTMLElement" + } + ], + "return": { + "description": "array of p5.Elements containing any elements found.", + "type": "p5.Element[]" + } + } + ], + "return": { + "description": "array of p5.Elements containing any elements found.", + "type": "p5.Element[]" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "removeElements", + "file": "src/dom/dom.js", + "line": 301, + "itemtype": "method", + "description": "Removes all elements created by p5.js, including any event handlers.\nThere are two exceptions:\ncanvas elements created by createCanvas\nand p5.Render objects created by\ncreateGraphics.", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n\n // Create a paragraph element and place\n // it in the middle of the canvas.\n let p = createP('p5*js');\n p.position(25, 25);\n\n describe('A gray square with the text \"p5*js\" written in its center. The text disappears when the mouse is pressed.');\n}\n\nfunction mousePressed() {\n removeElements();\n}\n\n
\n\n
\n\nlet slider;\n\nfunction setup() {\n createCanvas(100, 100);\n\n // Create a paragraph element and place\n // it at the top of the canvas.\n let p = createP('p5*js');\n p.position(25, 25);\n\n // Create a slider element and place it\n // beneath the canvas.\n slider = createSlider(0, 255, 200);\n slider.position(0, 100);\n\n describe('A gray square with the text \"p5*js\" written in its center and a range slider beneath it. The square changes color when the slider is moved. The text and slider disappear when the square is double-clicked.');\n}\n\nfunction draw() {\n // Use the slider value to change the background color.\n let g = slider.value();\n background(g);\n}\n\nfunction doubleClicked() {\n removeElements();\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "changed", + "file": "src/dom/dom.js", + "line": 366, + "itemtype": "method", + "chainable": 1, + "description": "myElement.changed() sets a function to call when the value of the\np5.Element object changes. Calling\nmyElement.changed(false) disables the function.", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Create a dropdown menu and add a few color options.\n let drop = createSelect();\n drop.position(0, 0);\n drop.option('red');\n drop.option('green');\n drop.option('blue');\n\n // When the color option changes, paint the background with\n // that color.\n drop.changed(() => {\n let c = drop.value();\n background(c);\n });\n\n describe('A gray square with a dropdown menu at the top. The square changes color when an option is selected.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create a checkbox and place it beneath the canvas.\n let checkbox = createCheckbox(' circle');\n checkbox.position(0, 100);\n\n // When the checkbox changes, paint the background gray\n // and determine whether to draw a circle.\n checkbox.changed(() => {\n background(200);\n if (checkbox.checked() === true) {\n circle(50, 50, 30);\n }\n });\n\n describe('A gray square with a checkbox underneath it that says \"circle\". A white circle appears when the box is checked and disappears otherwise.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when the element changes.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "input", + "file": "src/dom/dom.js", + "line": 426, + "itemtype": "method", + "chainable": 1, + "description": "myElement.input() sets a function to call when input is detected within\nthe p5.Element object. It's often used to with\ntext inputs and sliders. Calling myElement.input(false) disables the\nfunction.", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Create a slider and place it beneath the canvas.\n let slider = createSlider(0, 255, 200);\n slider.position(0, 100);\n\n // When the slider changes, use its value to paint\n // the background.\n slider.input(() => {\n let g = slider.value();\n background(g);\n });\n\n describe('A gray square with a range slider underneath it. The background changes shades of gray when the slider is moved.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create an input and place it beneath the canvas.\n let inp = createInput('');\n inp.position(0, 100);\n\n // When input is detected, paint the background gray\n // and display the text.\n inp.input(() => {\n background(200);\n let msg = inp.value();\n text(msg, 5, 50);\n });\n\n describe('A gray square with a text input bar beneath it. Any text written in the input appears in the middle of the square.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fxn", + "description": "function to call when input is detected within\nthe element.\nfalse disables the function.", + "type": "Function|Boolean" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "addElement", + "file": "src/dom/dom.js", + "line": 434, + "itemtype": "method", + "description": "Helpers for create methods.", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createDiv", + "file": "src/dom/dom.js", + "line": 482, + "itemtype": "method", + "description": "

Creates a <div></div> element. It's commonly used as a\ncontainer for other elements.

\n

The parameter html is optional. It accepts a string that sets the\ninner HTML of the new <div></div>.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n let div = createDiv('p5*js');\n div.position(25, 35);\n\n describe('A gray square with the text \"p5*js\" written in its center.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create an h3 element within the div.\n let div = createDiv('

p5*js

');\n div.position(20, 5);\n\n describe('A gray square with the text \"p5*js\" written in its center.');\n}\n
\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "html", + "description": "inner HTML for the new <div></div> element.", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createP", + "file": "src/dom/dom.js", + "line": 512, + "itemtype": "method", + "description": "

Creates a <p></p> element. It's commonly used for\nparagraph-length text.

\n

The parameter html is optional. It accepts a string that sets the\ninner HTML of the new <p></p>.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n let p = createP('Tell me a story.');\n p.position(5, 0);\n\n describe('A gray square displaying the text \"Tell me a story.\" written in black.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "html", + "description": "inner HTML for the new <p></p> element.", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createSpan", + "file": "src/dom/dom.js", + "line": 577, + "itemtype": "method", + "description": "

Creates a <span></span> element. It's commonly used as a\ncontainer for inline elements. For example, a <span></span>\ncan hold part of a sentence that's a\ndifferent style.

\n

The parameter html is optional. It accepts a string that sets the\ninner HTML of the new <span></span>.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Create a span element.\n let span = createSpan('p5*js');\n span.position(25, 35);\n\n describe('A gray square with the text \"p5*js\" written in its center.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create a div element as\n // a container.\n let div = createDiv();\n // Place the div at the\n // center.\n div.position(25, 35);\n\n // Create a span element.\n let s1 = createSpan('p5');\n // Create a second span element.\n let s2 = createSpan('*');\n // Set the span's font color.\n s2.style('color', 'deeppink');\n // Create a third span element.\n let s3 = createSpan('js');\n\n // Add all the spans to the\n // container div.\n s1.parent(div);\n s2.parent(div);\n s3.parent(div);\n\n describe('A gray square with the text \"p5*js\" written in black at its center. The asterisk is pink.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "html", + "description": "inner HTML for the new <span></span> element.", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createImg", + "file": "src/dom/dom.js", + "line": 633, + "itemtype": "method", + "description": "

Creates an <img> element that can appear outside of the canvas.

\n

The first parameter, src, is a string with the path to the image file.\nsrc should be a relative path, as in 'assets/image.png', or a URL, as\nin 'https://example.com/image.png'.

\n

The second parameter, alt, is a string with the\nalternate text\nfor the image. An empty string '' can be used for images that aren't displayed.

\n

The third parameter, crossOrigin, is optional. It's a string that sets the\ncrossOrigin property\nof the image. Use 'anonymous' or 'use-credentials' to fetch the image\nwith cross-origin access.

\n

The fourth parameter, callback, is also optional. It sets a function to\ncall after the image loads. The new image is passed to the callback\nfunction as a p5.Element object.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n let img = createImg(\n 'https://p5js.org/assets/img/asterisk-01.png',\n 'The p5.js magenta asterisk.'\n );\n img.position(0, -10);\n\n describe('A gray square with a magenta asterisk in its center.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "src", + "description": "relative path or URL for the image.", + "type": "String" + }, + { + "name": "alt", + "description": "alternate text for the image.", + "type": "String" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + }, + { + "params": [ + { + "name": "src", + "type": "String" + }, + { + "name": "alt", + "type": "String" + }, + { + "name": "crossOrigin", + "description": "crossOrigin property to use when fetching the image.", + "optional": 1, + "type": "String" + }, + { + "name": "successCallback", + "description": "function to call once the image loads. The new image will be passed\nto the function as a p5.Element object.", + "optional": 1, + "type": "Function" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createA", + "file": "src/dom/dom.js", + "line": 706, + "itemtype": "method", + "description": "

Creates an <a></a> element that links to another web page.

\n

The first parmeter, href, is a string that sets the URL of the linked\npage.

\n

The second parameter, html, is a string that sets the inner HTML of the\nlink. It's common to use text, images, or buttons as links.

\n

The third parameter, target, is optional. It's a string that tells the\nweb browser where to open the link. By default, links open in the current\nbrowser tab. Passing '_blank' will cause the link to open in a new\nbrowser tab. MDN describes a few\nother options.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Create an anchor element that links to p5js.org.\n let a = createA('http://p5js.org/', 'p5*js');\n a.position(25, 35);\n\n describe('The text \"p5*js\" written at the center of a gray square.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create an anchor element that links to p5js.org.\n // Open the link in a new tab.\n let a = createA('http://p5js.org/', 'p5*js', '_blank');\n a.position(25, 35);\n\n describe('The text \"p5*js\" written at the center of a gray square.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "href", + "description": "URL of linked page.", + "type": "String" + }, + { + "name": "html", + "description": "inner HTML of link element to display.", + "type": "String" + }, + { + "name": "target", + "description": "target where the new link should open,\neither '_blank', '_self', '_parent', or '_top'.", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createSlider", + "file": "src/dom/dom.js", + "line": 827, + "itemtype": "method", + "description": "

Creates a slider <input></input> element. Range sliders are\nuseful for quickly selecting numbers from a given range.

\n

The first two parameters, min and max, are numbers that set the\nslider's minimum and maximum.

\n

The third parameter, value, is optional. It's a number that sets the\nslider's default value.

\n

The fourth parameter, step, is also optional. It's a number that sets the\nspacing between each value in the slider's range. Setting step to 0\nallows the slider to move smoothly from min to max.

\n", + "example": [ + "
\n\nlet slider;\n\nfunction setup() {\n // Create a slider and place it at the top of the canvas.\n slider = createSlider(0, 255);\n slider.position(10, 10);\n slider.size(80);\n\n describe('A dark gray square with a range slider at the top. The square changes color when the slider is moved.');\n}\n\nfunction draw() {\n // Use the slider as a grayscale value.\n let g = slider.value();\n background(g);\n}\n\n
\n\n
\n\nlet slider;\n\nfunction setup() {\n // Create a slider and place it at the top of the canvas.\n // Set its default value to 0.\n slider = createSlider(0, 255, 0);\n slider.position(10, 10);\n slider.size(80);\n\n describe('A black square with a range slider at the top. The square changes color when the slider is moved.');\n}\n\nfunction draw() {\n // Use the slider as a grayscale value.\n let g = slider.value();\n background(g);\n}\n\n
\n\n
\n\nlet slider;\n\nfunction setup() {\n // Create a slider and place it at the top of the canvas.\n // Set its default value to 0.\n // Set its step size to 50.\n slider = createSlider(0, 255, 0, 50);\n slider.position(10, 10);\n slider.size(80);\n\n describe('A black square with a range slider at the top. The square changes color when the slider is moved.');\n}\n\nfunction draw() {\n // Use the slider as a grayscale value.\n let g = slider.value();\n background(g);\n}\n\n
\n\n
\n\nlet slider;\n\nfunction setup() {\n // Create a slider and place it at the top of the canvas.\n // Set its default value to 0.\n // Set its step size to 0 so that it moves smoothly.\n slider = createSlider(0, 255, 0, 0);\n slider.position(10, 10);\n slider.size(80);\n\n describe('A black square with a range slider at the top. The square changes color when the slider is moved.');\n}\n\nfunction draw() {\n // Use the slider as a grayscale value.\n let g = slider.value();\n background(g);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "min", + "description": "minimum value of the slider.", + "type": "Number" + }, + { + "name": "max", + "description": "maximum value of the slider.", + "type": "Number" + }, + { + "name": "value", + "description": "default value of the slider.", + "optional": 1, + "type": "Number" + }, + { + "name": "step", + "description": "size for each step in the slider's range.", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createButton", + "file": "src/dom/dom.js", + "line": 906, + "itemtype": "method", + "description": "

Creates a <button></button> element.

\n

The first parameter, label, is a string that sets the label displayed on\nthe button.

\n

The second parameter, value, is optional. It's a string that sets the\nbutton's value. See\nMDN\nfor more details.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Create a button and place it beneath the canvas.\n let button = createButton('click me');\n button.position(0, 100);\n\n // Use the button to change the background color.\n button.mousePressed(() => {\n let g = random(255);\n background(g);\n });\n\n describe('A gray square with a button that says \"click me\" beneath it. The square changes color when the button is clicked.');\n}\n\n
\n\n
\n\nlet button;\n\nfunction setup() {\n // Create a button and set its value to 0.\n // Place the button beneath the canvas.\n button = createButton('click me', 'red');\n button.position(0, 100);\n\n // Change the button's value when the mouse\n // is pressed.\n button.mousePressed(() => {\n let c = random(['red', 'green', 'blue', 'yellow']);\n button.value(c);\n });\n\n describe('A red square with a button that says \"click me\" beneath it. The square changes color when the button is clicked.');\n}\n\nfunction draw() {\n // Use the button's value to set the background color.\n let c = button.value();\n background(c);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "label", + "description": "label displayed on the button.", + "type": "String" + }, + { + "name": "value", + "description": "value of the button.", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createCheckbox", + "file": "src/dom/dom.js", + "line": 1002, + "itemtype": "method", + "description": "

Creates a checkbox <input></input> element. Checkboxes extend\nthe p5.Element class with a checked() method.\nCalling myBox.checked() returns true if it the box is checked and\nfalse otherwise.

\n

The first parameter, label, is optional. It's a string that sets the label\nto display next to the checkbox.

\n

The second parameter, value, is also optional. It's a boolean that sets the\ncheckbox's value.

\n", + "example": [ + "
\n\nlet checkbox;\n\nfunction setup() {\n // Create a checkbox and place it beneath the canvas.\n checkbox = createCheckbox();\n checkbox.position(0, 100);\n\n describe('A black square with a checkbox beneath it. The square turns white when the box is checked.');\n}\n\nfunction draw() {\n // Use the checkbox to set the background color.\n if (checkbox.checked()) {\n background(255);\n } else {\n background(0);\n }\n}\n\n
\n\n
\n\nlet checkbox;\n\nfunction setup() {\n // Create a checkbox and place it beneath the canvas.\n // Label the checkbox \"white\".\n checkbox = createCheckbox(' white');\n checkbox.position(0, 100);\n\n describe('A black square with a checkbox labeled \"white\" beneath it. The square turns white when the box is checked.');\n}\n\nfunction draw() {\n // Use the checkbox to set the background color.\n if (checkbox.checked()) {\n background(255);\n } else {\n background(0);\n }\n}\n\n
\n\n
\n\nlet checkbox;\n\nfunction setup() {\n // Create a checkbox and place it beneath the canvas.\n // Label the checkbox \"white\" and set its value to true.\n checkbox = createCheckbox(' white', true);\n checkbox.position(0, 100);\n\n describe('A white square with a checkbox labeled \"white\" beneath it. The square turns black when the box is unchecked.');\n}\n\nfunction draw() {\n // Use the checkbox to set the background color.\n if (checkbox.checked()) {\n background(255);\n } else {\n background(0);\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "label", + "description": "label displayed after the checkbox.", + "optional": 1, + "type": "String" + }, + { + "name": "value", + "description": "value of the checkbox. Checked is true and unchecked is false.", + "optional": 1, + "type": "boolean" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createSelect", + "file": "src/dom/dom.js", + "line": 1214, + "itemtype": "method", + "description": "

Creates a dropdown menu <select></select> element.

\n

The parameter is optional. If true is passed, as in\nlet mySelect = createSelect(true), then the dropdown will support\nmultiple selections. If an existing <select></select> element\nis passed, as in let mySelect = createSelect(otherSelect), the existing\nelement will be wrapped in a new p5.Element\nobject.

\n

Dropdowns extend the p5.Element class with a few\nhelpful methods for managing options:

\n", + "example": [ + "
\n\nlet mySelect;\n\nfunction setup() {\n // Create a dropdown and place it beneath the canvas.\n mySelect = createSelect();\n mySelect.position(0, 100);\n\n // Add color options.\n mySelect.option('red');\n mySelect.option('green');\n mySelect.option('blue');\n mySelect.option('yellow');\n\n // Set the selected option to \"red\".\n mySelect.selected('red');\n\n describe('A red square with a dropdown menu beneath it. The square changes color when a new color is selected.');\n}\n\nfunction draw() {\n // Use the selected value to paint the background.\n let c = mySelect.selected();\n background(c);\n}\n\n
\n\n
\n\nlet mySelect;\n\nfunction setup() {\n // Create a dropdown and place it beneath the canvas.\n mySelect = createSelect();\n mySelect.position(0, 100);\n\n // Add color options.\n mySelect.option('red');\n mySelect.option('green');\n mySelect.option('blue');\n mySelect.option('yellow');\n\n // Set the selected option to \"red\".\n mySelect.selected('red');\n\n // Disable the \"yellow\" option.\n mySelect.disable('yellow');\n\n describe('A red square with a dropdown menu beneath it. The square changes color when a new color is selected.');\n}\n\nfunction draw() {\n // Use the selected value to paint the background.\n let c = mySelect.selected();\n background(c);\n}\n\n
\n\n
\n\nlet mySelect;\n\nfunction setup() {\n // Create a dropdown and place it beneath the canvas.\n mySelect = createSelect();\n mySelect.position(0, 100);\n\n // Add color options with names and values.\n mySelect.option('one', 'red');\n mySelect.option('two', 'green');\n mySelect.option('three', 'blue');\n mySelect.option('four', 'yellow');\n\n // Set the selected option to \"one\".\n mySelect.selected('one');\n\n describe('A red square with a dropdown menu beneath it. The square changes color when a new color is selected.');\n}\n\nfunction draw() {\n // Use the selected value to paint the background.\n let c = mySelect.selected();\n background(c);\n}\n\n
\n\n
\n\n// Hold CTRL to select multiple options on Windows and Linux.\n// Hold CMD to select multiple options on macOS.\nlet mySelect;\n\nfunction setup() {\n // Create a dropdown and allow multiple selections.\n // Place it beneath the canvas.\n mySelect = createSelect(true);\n mySelect.position(0, 100);\n\n // Add color options.\n mySelect.option('red');\n mySelect.option('green');\n mySelect.option('blue');\n mySelect.option('yellow');\n\n describe('A gray square with a dropdown menu beneath it. Colorful circles appear when their color is selected.');\n}\n\nfunction draw() {\n background(200);\n\n // Use the selected value(s) to draw circles.\n let colors = mySelect.selected();\n colors.forEach((c, index) => {\n let x = 10 + index * 20;\n fill(c);\n circle(x, 50, 20);\n });\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "multiple", + "description": "support multiple selections.", + "optional": 1, + "type": "boolean" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + }, + { + "params": [ + { + "name": "existing", + "description": "select element to wrap, either as a p5.Element or\na HTMLSelectElement.", + "type": "Object" + } + ], + "return": { + "description": "", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createRadio", + "file": "src/dom/dom.js", + "line": 1456, + "itemtype": "method", + "description": "

Creates a radio button element.

\n

The parameter is optional. If a string is passed, as in\nlet myRadio = createSelect('food'), then each radio option will\nhave \"food\" as its name parameter: <input name=\"food\"></input>.\nIf an existing <div></div> or <span></span>\nelement is passed, as in let myRadio = createSelect(container), it will\nbecome the radio button's parent element.

\n

Radio buttons extend the p5.Element class with a few\nhelpful methods for managing options:

\n", + "example": [ + "
\n\nlet myRadio;\n\nfunction setup() {\n // Create a radio button element and place it\n // in the top-left corner.\n myRadio = createRadio();\n myRadio.position(0, 0);\n myRadio.size(60);\n\n // Add a few color options.\n myRadio.option('red');\n myRadio.option('yellow');\n myRadio.option('blue');\n\n // Choose a default option.\n myRadio.selected('yellow');\n\n describe('A yellow square with three color options listed, \"red\", \"yellow\", and \"blue\". The square changes color when the user selects a new option.');\n}\n\nfunction draw() {\n // Set the background color using the radio button.\n let g = myRadio.value();\n background(g);\n}\n\n
\n\n
\n\nlet myRadio;\n\nfunction setup() {\n // Create a radio button element and place it\n // in the top-left corner.\n myRadio = createRadio();\n myRadio.position(0, 0);\n myRadio.size(50);\n\n // Add a few color options.\n // Color values are labeled with\n // emotions they evoke.\n myRadio.option('red', 'love');\n myRadio.option('yellow', 'joy');\n myRadio.option('blue', 'trust');\n\n // Choose a default option.\n myRadio.selected('yellow');\n\n describe('A yellow square with three options listed, \"love\", \"joy\", and \"trust\". The square changes color when the user selects a new option.');\n}\n\nfunction draw() {\n // Set the background color using the radio button.\n let c = myRadio.value();\n background(c);\n}\n\n
\n\n
\n\nlet myRadio;\n\nfunction setup() {\n // Create a radio button element and place it\n // in the top-left corner.\n myRadio = createRadio();\n myRadio.position(0, 0);\n myRadio.size(50);\n\n // Add a few color options.\n myRadio.option('red');\n myRadio.option('yellow');\n myRadio.option('blue');\n\n // Choose a default option.\n myRadio.selected('yellow');\n\n // Create a button and place it beneath the canvas.\n let btn = createButton('disable');\n btn.position(0, 100);\n\n // Use the button to disable the radio button.\n btn.mousePressed(() => {\n myRadio.disable(true);\n });\n\n describe('A yellow square with three options listed, \"red\", \"yellow\", and \"blue\". The square changes color when the user selects a new option. A \"disable\" button beneath the canvas disables the color options when pressed.');\n}\n\nfunction draw() {\n // Set the background color using the radio button.\n let c = myRadio.value();\n background(c);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "containerElement", + "description": "container HTML Element, either a <div></div>\nor <span></span>.", + "optional": 1, + "type": "Object" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + }, + { + "params": [ + { + "name": "name", + "description": "name parameter assigned to each option's <input></input> element.", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + }, + { + "params": [], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createColorPicker", + "file": "src/dom/dom.js", + "line": 1672, + "itemtype": "method", + "description": "

Creates a color picker element.

\n

The parameter, value, is optional. If a color string or\np5.Color object is passed, it will set the default\ncolor.

\n

Color pickers extend the p5.Element class with a\ncouple of helpful methods for managing colors:

\n", + "example": [ + "
\n\nlet myPicker;\n\nfunction setup() {\n myPicker = createColorPicker('deeppink');\n myPicker.position(0, 100);\n\n describe('A pink square with a color picker beneath it. The square changes color when the user picks a new color.');\n}\n\nfunction draw() {\n // Use the color picker to paint the background.\n let c = myPicker.color();\n background(c);\n}\n\n
\n\n
\n\nlet myPicker;\n\nfunction setup() {\n myPicker = createColorPicker('deeppink');\n myPicker.position(0, 100);\n\n describe('A number with the format \"#rrggbb\" is displayed on a pink canvas. The background color and number change when the user picks a new color.');\n}\n\nfunction draw() {\n // Use the color picker to paint the background.\n let c = myPicker.value();\n background(c);\n\n // Display the current color as a hex string.\n text(c, 25, 55);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "default color as a CSS color string.", + "optional": 1, + "type": "String|p5.Color" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createInput", + "file": "src/dom/dom.js", + "line": 1777, + "itemtype": "method", + "description": "

Creates a text <input></input> element. Call myInput.size()\nto set the length of the text box.

\n

The first parameter, value, is optional. It's a string that sets the\ninput's default value. The input is blank by default.

\n

The second parameter, type, is also optional. It's a string that\nspecifies the type of text being input. See MDN for a full\nlist of options.\nThe default is 'text'.

\n", + "example": [ + "
\n\nlet myInput;\n\nfunction setup() {\n // Create an input element and place it\n // beneath the canvas.\n myInput = createInput();\n myInput.position(0, 100);\n\n describe('A gray square with a text box beneath it. The text in the square changes when the user types something new in the input bar.');\n}\n\nfunction draw() {\n background(200);\n\n // Use the input to display a message.\n let msg = myInput.value();\n text(msg, 25, 55);\n}\n\n
\n\n
\n\nlet myInput;\n\nfunction setup() {\n // Create an input element and place it\n // beneath the canvas. Set its default\n // text to \"hello!\".\n myInput = createInput('hello!');\n myInput.position(0, 100);\n\n describe('The text \"hello!\" written at the center of a gray square. A text box beneath the square also says \"hello!\". The text in the square changes when the user types something new in the input bar.');\n}\n\nfunction draw() {\n background(200);\n\n // Use the input to display a message.\n let msg = myInput.value();\n text(msg, 25, 55);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "default value of the input box. Defaults to an empty string ''.", + "optional": 1, + "type": "String" + }, + { + "name": "type", + "description": "type of input. Defaults to 'text'.", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + }, + { + "params": [ + { + "name": "value", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createFileInput", + "file": "src/dom/dom.js", + "line": 1879, + "itemtype": "method", + "description": "

Creates an <input></input> element of type 'file'.\nThis allows users to select local files for use in a sketch.

\n

The first parameter, callback, is a function that's called when the file\nloads. The callback function should have one parameter, file, that's a\np5.File object.

\n

The second parameter, multiple, is optional. It's a boolean value that\nallows loading multiple files if set to true. If true, callback\nwill be called once per file.

\n", + "example": [ + "
\n\n// Use the file input to select an image to\n// load and display.\nlet input;\nlet img;\n\nfunction setup() {\n // Create a file input and place it beneath\n // the canvas.\n input = createFileInput(handleImage);\n input.position(0, 100);\n\n describe('A gray square with a file input beneath it. If the user selects an image file to load, it is displayed on the square.');\n}\n\nfunction draw() {\n background(200);\n\n // Draw the image if loaded.\n if (img) {\n image(img, 0, 0, width, height);\n }\n}\n\n// Create an image if the file is an image.\nfunction handleImage(file) {\n if (file.type === 'image') {\n img = createImg(file.data, '');\n img.hide();\n } else {\n img = null;\n }\n}\n\n
\n\n
\n\n// Use the file input to select multiple images\n// to load and display.\nlet input;\nlet images = [];\n\nfunction setup() {\n // Create a file input and place it beneath\n // the canvas. Allow it to load multiple files.\n input = createFileInput(handleImage, true);\n input.position(0, 100);\n}\n\nfunction draw() {\n background(200);\n\n // Draw the images if loaded. Each image\n // is drawn 20 pixels lower than the\n // previous image.\n images.forEach((img, index) => {\n let y = index * 20;\n image(img, 0, y, width, height);\n });\n\n describe('A gray square with a file input beneath it. If the user selects multiple image files to load, they are displayed on the square.');\n}\n\n// Create an image if the file is an image,\n// then add it to the images array.\nfunction handleImage(file) {\n if (file.type === 'image') {\n let img = createImg(file.data, '');\n img.hide();\n images.push(img);\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "callback", + "description": "function to call once the file loads.", + "type": "Function" + }, + { + "name": "multiple", + "description": "allow multiple files to be selected.", + "optional": 1, + "type": "Boolean" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createMedia", + "file": "src/dom/dom.js", + "line": 1906, + "itemtype": "method", + "description": "VIDEO STUFF *", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createVideo", + "file": "src/dom/dom.js", + "line": 2036, + "itemtype": "method", + "description": "

Creates a <video> element for simple audio/video playback.\nReturns a new p5.MediaElement object.

\n

Videos are shown by default. They can be hidden by calling video.hide()\nand drawn to the canvas using image().

\n

The first parameter, src, is the path the video. If a single string is\npassed, as in 'assets/topsecret.mp4', a single video is loaded. An array\nof strings can be used to load the same video in different formats. For\nexample, ['assets/topsecret.mp4', 'assets/topsecret.ogv', 'assets/topsecret.webm'].\nThis is useful for ensuring that the video can play across different browsers with\ndifferent capabilities. See\nMDN\nfor more information about supported formats.

\n

The second parameter, callback, is optional. It's a function to call once\nthe video is ready to play.

\n", + "example": [ + "
\n\nfunction setup() {\n noCanvas();\n\n // Load a video and add it to the page.\n // Note: this may not work in some browsers.\n let video = createVideo('assets/small.mp4');\n // Show the default video controls.\n video.showControls();\n\n describe('A video of a toy robot with playback controls beneath it.');\n}\n\n
\n\n
\n\nfunction setup() {\n noCanvas();\n\n // Load a video and add it to the page.\n // Provide an array options for different file formats.\n let video = createVideo(\n ['assets/small.mp4', 'assets/small.ogv', 'assets/small.webm']\n );\n // Show the default video controls.\n video.showControls();\n\n describe('A video of a toy robot with playback controls beneath it.');\n}\n\n
\n\n
\n\nlet video;\n\nfunction setup() {\n noCanvas();\n\n // Load a video and add it to the page.\n // Provide an array options for different file formats.\n // Call mute() once the video loads.\n video = createVideo(\n ['assets/small.mp4', 'assets/small.ogv', 'assets/small.webm'],\n muteVideo\n );\n // Show the default video controls.\n video.showControls();\n\n describe('A video of a toy robot with playback controls beneath it.');\n}\n\n// Mute the video once it loads.\nfunction muteVideo() {\n video.volume(0);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "src", + "description": "path to a video file, or an array of paths for\nsupporting different browsers.", + "type": "String|String[]" + }, + { + "name": "callback", + "description": "function to call once the video is ready to play.", + "optional": 1, + "type": "Function" + } + ], + "return": { + "description": "new p5.MediaElement object.", + "type": "p5.MediaElement" + } + } + ], + "return": { + "description": "new p5.MediaElement object.", + "type": "p5.MediaElement" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createAudio", + "file": "src/dom/dom.js", + "line": 2080, + "itemtype": "method", + "description": "

Creates a hidden <audio> element for simple audio playback.\nReturns a new p5.MediaElement object.

\n

The first parameter, src, is the path the video. If a single string is\npassed, as in 'assets/video.mp4', a single video is loaded. An array\nof strings can be used to load the same video in different formats. For\nexample, ['assets/video.mp4', 'assets/video.ogv', 'assets/video.webm'].\nThis is useful for ensuring that the video can play across different\nbrowsers with different capabilities. See\nMDN\nfor more information about supported formats.

\n

The second parameter, callback, is optional. It's a function to call once\nthe audio is ready to play.

\n", + "example": [ + "
\n\nfunction setup() {\n noCanvas();\n\n // Load the audio.\n let beat = createAudio('assets/beat.mp3');\n // Show the default audio controls.\n beat.showControls();\n\n describe('An audio beat plays when the user double-clicks the square.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "src", + "description": "path to an audio file, or an array of paths\nfor supporting different browsers.", + "optional": 1, + "type": "String|String[]" + }, + { + "name": "callback", + "description": "function to call once the audio is ready to play.", + "optional": 1, + "type": "Function" + } + ], + "return": { + "description": "new p5.MediaElement object.", + "type": "p5.MediaElement" + } + } + ], + "return": { + "description": "new p5.MediaElement object.", + "type": "p5.MediaElement" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createCapture", + "file": "src/dom/dom.js", + "line": 2212, + "itemtype": "method", + "description": "

Creates a <video> element that \"captures\" the audio/video stream from\nthe webcam and microphone. Returns a new\np5.Element object.

\n

Videos are shown by default. They can be hidden by calling capture.hide()\nand drawn to the canvas using image().

\n

The first parameter, type, is optional. It sets the type of capture to\nuse. By default, createCapture() captures both audio and video. If VIDEO\nis passed, as in createCapture(VIDEO), only video will be captured.\nIf AUDIO is passed, as in createCapture(AUDIO), only audio will be\ncaptured. A constraints object can also be passed to customize the stream.\nSee the \nW3C documentation for possible properties. Different browsers support different\nproperties.

\n

The second parameter, callback, is optional. It's a function to call once\nthe capture is ready for use. The callback function should have one\nparameter, stream, that's a\nMediaStream object.

\n

Note: createCapture() only works when running a sketch locally or using HTTPS. Learn more\nhere\nand here.

\n", + "example": [ + "
\n\nfunction setup() {\n noCanvas();\n createCapture(VIDEO);\n\n describe('A video stream from the webcam.');\n}\n\n
\n\n
\n\nlet capture;\n\nfunction setup() {\n // Create the video capture and hide the element.\n capture = createCapture(VIDEO);\n capture.hide();\n\n describe('A video stream from the webcam with inverted colors.');\n}\n\nfunction draw() {\n // Draw the video capture within the canvas.\n image(capture, 0, 0, width, width * capture.height / capture.width);\n // Invert the colors in the stream.\n filter(INVERT);\n}\n\n
\n\n
\n\nfunction setup() {\n createCanvas(480, 120);\n\n // Create a constraints object.\n let constraints = {\n video: {\n mandatory: {\n minWidth: 1280,\n minHeight: 720\n },\n optional: [{ maxFrameRate: 10 }]\n },\n audio: false\n };\n\n // Create the video capture.\n createCapture(constraints);\n\n describe('A video stream from the webcam.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "type", + "description": "type of capture, either AUDIO or VIDEO,\nor a constraints object. Both video and audio\naudio streams are captured by default.", + "optional": 1, + "type": "String|Constant|Object" + }, + { + "name": "callback", + "description": "function to call once the stream\nhas loaded.", + "optional": 1, + "type": "Function" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createElement", + "file": "src/dom/dom.js", + "line": 2311, + "itemtype": "method", + "description": "

Creates a new p5.Element object.

\n

The first parameter, tag, is a string an HTML tag such as 'h5'.

\n

The second parameter, content, is optional. It's a string that sets the\nHTML content to insert into the new element. New elements have no content\nby default.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Create an h5 element with nothing in it.\n createElement('h5');\n\n describe('A gray square.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create an h5 element with the content\n // \"p5*js\".\n let h5 = createElement('h5', 'p5*js');\n // Set the element's style and position.\n h5.style('color', 'deeppink');\n h5.position(30, 15);\n\n describe('The text \"p5*js\" written in pink in the middle of a gray square.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "tag", + "description": "tag for the new element.", + "type": "String" + }, + { + "name": "content", + "description": "HTML content to insert into the element.", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + } + } + ], + "return": { + "description": "new p5.Element object.", + "type": "p5.Element" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "addClass", + "file": "src/dom/dom.js", + "line": 2337, + "itemtype": "method", + "chainable": 1, + "description": "Adds specified class to the element.", + "example": [ + "
\nlet div = createDiv('div');\ndiv.addClass('myClass');\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "class", + "description": "name of class to add", + "type": "String" + } + ] + } + ], + "class": "p5.Element", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "removeClass", + "file": "src/dom/dom.js", + "line": 2373, + "itemtype": "method", + "chainable": 1, + "description": "Removes specified class from the element.", + "example": [ + "
\n// In this example, a class is set when the div is created\n// and removed when mouse is pressed. This could link up\n// with a CSS style rule to toggle style properties.\n\nlet div;\n\nfunction setup() {\n div = createDiv('div');\n div.addClass('myClass');\n}\n\nfunction mousePressed() {\n div.removeClass('myClass');\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "class", + "description": "name of class to remove", + "type": "String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "hasClass", + "file": "src/dom/dom.js", + "line": 2404, + "itemtype": "method", + "description": "Checks if specified class is already applied to element.", + "example": [ + "
\nlet div;\n\nfunction setup() {\n div = createDiv('div');\n div.addClass('show');\n}\n\nfunction mousePressed() {\n if (div.hasClass('show')) {\n div.addClass('show');\n } else {\n div.removeClass('show');\n }\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "c", + "description": "{String} class name of class to check" + } + ], + "return": { + "description": "a boolean value if element has specified class", + "type": "boolean" + } + } + ], + "return": { + "description": "a boolean value if element has specified class", + "type": "boolean" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "toggleClass", + "file": "src/dom/dom.js", + "line": 2429, + "itemtype": "method", + "chainable": 1, + "description": "Toggles element class.", + "example": [ + "
\nlet div;\n\nfunction setup() {\n div = createDiv('div');\n div.addClass('show');\n}\n\nfunction mousePressed() {\n div.toggleClass('show');\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "c", + "description": "{String} class name to toggle" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "child", + "file": "src/dom/dom.js", + "line": 2475, + "itemtype": "method", + "chainable": 1, + "description": "Attaches the element as a child to the parent specified.\nAccepts either a string ID, DOM node, or p5.Element.\nIf no argument is specified, an array of children DOM nodes is returned.", + "example": [ + "
\nlet div0 = createDiv('this is the parent');\nlet div1 = createDiv('this is the child');\ndiv0.child(div1); // use p5.Element\n
\n
\nlet div0 = createDiv('this is the parent');\nlet div1 = createDiv('this is the child');\ndiv1.id('apples');\ndiv0.child('apples'); // use id\n
\n
\n// this example assumes there is a div already on the page\n// with id \"myChildDiv\"\nlet div0 = createDiv('this is the parent');\nlet elt = document.getElementById('myChildDiv');\ndiv0.child(elt); // use element from page\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "an array of child nodes", + "type": "Node[]" + } + }, + { + "params": [ + { + "name": "child", + "description": "the ID, DOM node, or p5.Element\nto add to the current element", + "optional": 1, + "type": "String|p5.Element" + } + ] + } + ], + "return": { + "description": "an array of child nodes", + "type": "Node[]" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "center", + "file": "src/dom/dom.js", + "line": 2513, + "itemtype": "method", + "chainable": 1, + "description": "Centers a p5.Element either vertically, horizontally,\nor both, relative to its parent or according to\nthe body if the p5.Element has no parent. If no argument is passed\nthe p5.Element is aligned both vertically and horizontally.", + "example": [ + "
\nfunction setup() {\n let div = createDiv('').size(10, 10);\n div.style('background-color', 'orange');\n div.center();\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "align", + "description": "passing 'vertical', 'horizontal' aligns element accordingly", + "optional": 1, + "type": "String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "html", + "file": "src/dom/dom.js", + "line": 2572, + "itemtype": "method", + "chainable": 1, + "description": "If an argument is given, sets the inner HTML of the element,\nreplacing any existing HTML. If true is included as a second\nargument, HTML is appended instead of replacing existing HTML.\nIf no arguments are given, returns\nthe inner HTML of the element.", + "example": [ + "
\nlet div = createDiv('').size(100, 100);\ndiv.html('hi');\n
\n
\nlet div = createDiv('Hello ').size(100, 100);\ndiv.html('World', true);\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "the inner HTML of the element", + "type": "String" + } + }, + { + "params": [ + { + "name": "html", + "description": "the HTML to be placed inside the element", + "optional": 1, + "type": "String" + }, + { + "name": "append", + "description": "whether to append HTML to existing", + "optional": 1, + "type": "boolean" + } + ] + } + ], + "return": { + "description": "the inner HTML of the element", + "type": "String" + }, + "class": "p5.Element", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "position", + "file": "src/dom/dom.js", + "line": 2624, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the position of the element. If no position type argument is given, the\nposition will be relative to (0, 0) of the window.\nEssentially, this sets position:absolute and left and top\nproperties of style. If an optional third argument specifying position type is given,\nthe x and y-coordinates will be interpreted based on the positioning scheme.\nIf no arguments given, the function returns the x and y position of the element.

\n

found documentation on how to be more specific with object type\nhttps://stackoverflow.com/questions/14714314/how-do-i-comment-object-literals-in-yuidoc

\n", + "example": [ + "
\nfunction setup() {\n let cnv = createCanvas(100, 100);\n // positions canvas 50px to the right and 100px\n // below upper left corner of the window\n cnv.position(50, 100);\n}\n
\n
\nfunction setup() {\n let cnv = createCanvas(100, 100);\n // positions canvas at upper left corner of the window\n // with a 'fixed' position type\n cnv.position(0, 0, 'fixed');\n}\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "object of form { x: 0, y: 0 } containing the position of the element in an object", + "type": "Object" + } + }, + { + "params": [ + { + "name": "x", + "description": "x-position relative to upper left of window (optional)", + "optional": 1, + "type": "Number" + }, + { + "name": "y", + "description": "y-position relative to upper left of window (optional)", + "optional": 1, + "type": "Number" + }, + { + "name": "positionType", + "description": "it can be static, fixed, relative, sticky, initial or inherit (optional)", + "optional": 1, + "type": "String" + } + ] + } + ], + "return": { + "description": "object of form { x: 0, y: 0 } containing the position of the element in an object", + "type": "Object" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "style", + "file": "src/dom/dom.js", + "line": 2809, + "itemtype": "method", + "chainable": 1, + "description": "

Applies a style to an element by adding a\nCSS declaration.

\n

The first parameter, property, is a string. If the name of a style\nproperty is passed, as in myElement.style('color'), the method returns\nthe current value as a string or null if it hasn't been set. If a\nproperty:style string is passed, as in\nmyElement.style('color:deeppink'), the method sets property to\nvalue.

\n

The second parameter, value, is optional. It sets the property's value.\nvalue can be a string, as in\nmyElement.style('color', 'deeppink'), or a\np5.Color object, as in\nmyElement.style('color', myColor).

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Create a paragraph element and\n // set its font color to \"deeppink\".\n let p = createP('p5*js');\n p.position(25, 20);\n p.style('color', 'deeppink');\n\n describe('The text p5*js written in pink on a gray background.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create a p5.Color object.\n let c = color('deeppink');\n\n // Create a paragraph element and\n // set its font color using a\n // p5.Color object.\n let p = createP('p5*js');\n p.position(25, 20);\n p.style('color', c);\n\n describe('The text p5*js written in pink on a gray background.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create a paragraph element and\n // set its font color to \"deeppink\"\n // using property:value syntax.\n let p = createP('p5*js');\n p.position(25, 20);\n p.style('color:deeppink');\n\n describe('The text p5*js written in pink on a gray background.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create an empty paragraph element\n // and set its font color to \"deeppink\".\n let p = createP();\n p.position(5, 5);\n p.style('color', 'deeppink');\n\n // Get the element's color as an\n // RGB color string.\n let c = p.style('color');\n\n // Set the element's inner HTML\n // using the RGB color string.\n p.html(c);\n\n describe('The text \"rgb(255, 20, 147)\" written in pink on a gray background.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "property", + "description": "style property to set.", + "type": "String" + } + ], + "return": { + "description": "value of the property.", + "type": "String" + } + }, + { + "params": [ + { + "name": "property", + "type": "String" + }, + { + "name": "value", + "description": "value to assign to the property.", + "type": "String|p5.Color" + } + ], + "return": { + "description": "value of the property.", + "type": "String" + } + } + ], + "return": { + "description": "value of the property.", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "attribute", + "file": "src/dom/dom.js", + "line": 2925, + "itemtype": "method", + "chainable": 1, + "description": "

Adds an\nattribute\nto the element. This method is useful for advanced tasks.

\n

Most commonly-used attributes, such as id, can be set with their\ndedicated methods. For example, nextButton.id('next') sets an element's\nid attribute.

\n

The first parameter, attr, is the attribute's name as a string. Calling\nmyElement.attribute('align') returns the attribute's current value as a\nstring or null if it hasn't been set.

\n

The second parameter, value, is optional. It's a string used to set the\nattribute's value. For example, calling\nmyElement.attribute('align', 'center') sets the element's horizontal\nalignment to center.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create a container div and\n // place it at the top-left\n // corner.\n let container = createDiv();\n container.position(0, 0);\n\n // Create a paragraph element\n // and place it within the container.\n // Set its horizontal alignment to\n // \"left\".\n let p1 = createP('hi');\n p1.parent(container);\n p1.attribute('align', 'left');\n\n // Create a paragraph element\n // and place it within the container.\n // Set its horizontal alignment to\n // \"center\".\n let p2 = createP('hi');\n p2.parent(container);\n p2.attribute('align', 'center');\n\n // Create a paragraph element\n // and place it within the container.\n // Set its horizontal alignment to\n // \"right\".\n let p3 = createP('hi');\n p3.parent(container);\n p3.attribute('align', 'right');\n\n describe('A gray square with the text \"hi\" written on three separate lines, each placed further to the right.');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "value of the attribute.", + "type": "String" + } + }, + { + "params": [ + { + "name": "attr", + "description": "attribute to set.", + "type": "String" + }, + { + "name": "value", + "description": "value to assign to the attribute.", + "type": "String" + } + ] + } + ], + "return": { + "description": "value of the attribute.", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "removeAttribute", + "file": "src/dom/dom.js", + "line": 2983, + "itemtype": "method", + "chainable": 1, + "description": "

Removes an attribute from the element.

\n

The parameter attr is the attribute's name as a string. For example,\ncalling myElement.removeAttribute('align') removes its align\nattribute if it's been set.

\n", + "example": [ + "
\n\nlet p;\n\nfunction setup() {\n background(200);\n\n // Create a paragraph element and place it\n // in the center of the canvas.\n // Set its \"align\" attribute to \"center\".\n p = createP('hi');\n p.position(0, 20);\n p.attribute('align', 'center');\n\n describe('The text \"hi\" written in black at the center of a gray square. The text moves to the left edge when double-clicked.');\n}\n\nfunction doubleClicked() {\n p.removeAttribute('align');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "attr", + "description": "attribute to remove.", + "type": "String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "value", + "file": "src/dom/dom.js", + "line": 3066, + "itemtype": "method", + "chainable": 1, + "description": "

Returns or sets the element's value.

\n

Calling myElement.value() returns the element's current value.

\n

The parameter, value, is an optional number or string. If provided,\nas in myElement.value(123), it's used to set the element's value.

\n", + "example": [ + "
\n\nlet inp;\n\nfunction setup() {\n // Create a text input and place it\n // beneath the canvas. Set its default\n // value to \"hello\".\n inp = createInput('hello');\n inp.position(0, 100);\n\n describe('The text from an input box is displayed on a gray square.');\n}\n\nfunction draw() {\n background(200);\n\n // Use the input's value to display a message.\n let msg = inp.value();\n text(msg, 0, 55);\n}\n\n
\n\n
\n\nlet inp;\n\nfunction setup() {\n // Create a text input and place it\n // beneath the canvas. Set its default\n // value to \"hello\".\n inp = createInput('hello');\n inp.position(0, 100);\n\n describe('The text from an input box is displayed on a gray square. The text resets to \"hello\" when the user double-clicks the square.');\n}\n\nfunction draw() {\n background(200);\n\n // Use the input's value to display a message.\n let msg = inp.value();\n text(msg, 0, 55);\n}\n\n// Reset the input's value.\nfunction doubleClicked() {\n inp.value('hello');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "value of the element.", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "value", + "type": "String|Number" + } + ] + } + ], + "return": { + "description": "value of the element.", + "type": "String|Number" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "show", + "file": "src/dom/dom.js", + "line": 3105, + "itemtype": "method", + "chainable": 1, + "description": "Shows the current element.", + "example": [ + "
\n\nlet p;\n\nfunction setup() {\n background(200);\n\n // Create a paragraph element and hide it.\n p = createP('p5*js');\n p.position(10, 10);\n p.hide();\n\n describe('A gray square. The text \"p5*js\" appears when the user double-clicks the square.');\n}\n\n// Show the paragraph when double-clicked.\nfunction doubleClicked() {\n p.show();\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "hide", + "file": "src/dom/dom.js", + "line": 3135, + "itemtype": "method", + "chainable": 1, + "description": "Hides the current element.", + "example": [ + "let p;\n\nfunction setup() {\n background(200);\n\n // Create a paragraph element.\n p = createP('p5*js');\n p.position(10, 10);\n\n describe('The text \"p5*js\" at the center of a gray square. The text disappears when the user double-clicks the square.');\n}\n\n// Hide the paragraph when double-clicked.\nfunction doubleClicked() {\n p.hide();\n}\n\n" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "size", + "file": "src/dom/dom.js", + "line": 3250, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the element's width and height.

\n

Calling myElement.size() without an argument returns the element's size\nas an object with the properties width and height. For example,\n{ width: 20, height: 10 }.

\n

The first parameter, width, is optional. It's a number used to set the\nelement's width. Calling myElement.size(10)

\n

The second parameter, 'height, is also optional. It's a\nnumber used to set the element's height. For example, calling\nmyElement.size(20, 10)` sets the element's width to 20 pixels and height\nto 10 pixels.

\n

The constant AUTO can be used to adjust one dimension at a time while\nmaintaining the aspect ratio, which is width / height. For example,\nconsider an element that's 200 pixels wide and 100 pixels tall. Calling\nmyElement.size(20, AUTO) sets the width to 20 pixels and height to 10\npixels.

\n

Note: In the case of elements that need to load data, such as images, wait\nto call myElement.size() until after the data loads.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n // Create a pink div element and place it at\n // the top-left corner.\n let div = createDiv();\n div.position(10, 10);\n div.style('background-color', 'deeppink');\n\n // Set the div's width to 80 pixels and\n // height to 20 pixels.\n div.size(80, 20);\n\n describe('A gray square with a pink rectangle near its top.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Create a pink div element and place it at\n // the top-left corner.\n let div = createDiv();\n div.position(10, 10);\n div.style('background-color', 'deeppink');\n\n // Set the div's width to 80 pixels and\n // height to 40 pixels.\n div.size(80, 40);\n\n // Get the div's size as an object.\n let s = div.size();\n // Write the div's dimensions.\n div.html(`${s.width} x ${s.height}`);\n\n describe('A gray square with a pink rectangle near its top. The text \"80 x 40\" is written within the rectangle.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n // Load an image of an astronaut on the moon\n // and place it at the top-left of the canvas.\n let img1 = createImg(\n 'assets/moonwalk.jpg',\n 'An astronaut walking on the moon',\n ''\n );\n img1.position(0, 0);\n\n // Load an image of an astronaut on the moon\n // and place it at the top-left of the canvas.\n // Resize the image once it's loaded.\n let img2 = createImg(\n 'assets/moonwalk.jpg',\n 'An astronaut walking on the moon',\n '',\n () => {\n img2.size(50, AUTO);\n }\n );\n img2.position(0, 0);\n\n describe('A gray square two copies of a space image at the top-left. The copy in front is smaller.');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "width and height of the element in an object.", + "type": "Object" + } + }, + { + "params": [ + { + "name": "w", + "description": "width of the element, either AUTO, or a number.", + "type": "Number|Constant" + }, + { + "name": "h", + "description": "height of the element, either AUTO, or a number.", + "optional": 1, + "type": "Number|Constant" + } + ] + } + ], + "return": { + "description": "width and height of the element in an object.", + "type": "Object" + }, + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "remove", + "file": "src/dom/dom.js", + "line": 3326, + "itemtype": "method", + "description": "Removes the element, stops all audio/video streams, and removes all\ncallback functions.", + "example": [ + "
\n\nlet p;\n\nfunction setup() {\n background(200);\n\n // Create a paragraph element.\n p = createP('p5*js');\n p.position(10, 10);\n\n describe('The text \"p5*js\" written at the center of a gray square. ');\n}\n\n// Remove the paragraph when double-clicked.\nfunction doubleClicked() {\n p.remove();\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "drop", + "file": "src/dom/dom.js", + "line": 3466, + "itemtype": "method", + "chainable": 1, + "description": "

Sets a function to call when the user drops a file on the element.

\n

The first parameter, callback, is a function to call once the file loads.\nThe callback function should have one parameter, file, that's a\np5.File object. If the user drops multiple files on\nthe element, callback, is called once for each file.

\n

The second parameter, fxn, is a function to call when the browser detects\none or more dropped files. The callback function should have one\nparameter, event, that's a\nDragEvent.

\n", + "example": [ + "
\n\n// Drop an image on the canvas to view\n// this example.\nlet img;\n\nfunction setup() {\n let c = createCanvas(100, 100);\n\n background(200);\n\n // Call a function when a file\n // dropped on the canvas has\n // loaded.\n c.drop(file => {\n // Remove the current image, if any.\n if (img) {\n img.remove();\n }\n\n // Create an element with the\n // dropped file.\n img = createImg(file.data, '');\n img.hide();\n\n // Draw the image.\n image(img, 0, 0, width, height);\n });\n\n describe('A gray square. When the user drops an image on the square, it is displayed.');\n}\n\n
\n\n
\n\n// Drop an image on the canvas to view\n// this example.\nlet img;\nlet msg;\n\nfunction setup() {\n let c = createCanvas(100, 100);\n\n background(200);\n\n // Call functions when the user\n // drops a file on the canvas\n // and when the file loads.\n c.drop(handleFile, handleDrop);\n\n describe('A gray square. When the user drops an image on the square, it is displayed. The id attribute of canvas element is also displayed.');\n}\n\nfunction handleFile(file) {\n // Remove the current image, if any.\n if (img) {\n img.remove();\n }\n\n // Create an element with the\n // dropped file.\n img = createImg(file.data, '');\n img.hide();\n\n // Draw the image.\n image(img, 0, 0, width, height);\n}\n\nfunction handleDrop(event) {\n // Remove current paragraph, if any.\n if (msg) {\n msg.remove();\n }\n\n // Use event to get the drop\n // target's id.\n let id = event.target.id;\n\n // Write the canvas' id\n // beneath it.\n msg = createP(id);\n msg.position(0, 100);\n\n // Set the font color\n // randomly for each drop.\n let c = random(['red', 'green', 'blue']);\n msg.style('color', c);\n msg.style('font-size', '12px');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "callback", + "description": "called when a file loads. Called once for each file dropped.", + "type": "Function" + }, + { + "name": "fxn", + "description": "called once when any files are dropped.", + "optional": 1, + "type": "Function" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "draggable", + "file": "src/dom/dom.js", + "line": 3557, + "itemtype": "method", + "chainable": 1, + "description": "Turns p5.Element into a draggable item. If an argument is given, it will drag that p5.Element instead, ie. drag a entire GUI panel (parent container) with the panel's title bar.", + "example": [ + "
\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n\n createDiv('Post-It')\n .position(5, 5)\n .size(75, 20)\n .style('font-size', '16px')\n .style('background', 'yellow')\n .style('color', '#000')\n .style('border', '1px solid #aaaa00')\n .style('padding', '5px')\n .draggable();\n // .style('cursor', 'help') // override cursor\n\n let gui = createDiv('')\n .position(5, 40)\n .size(85, 50)\n .style('font-size', '16px')\n .style('background', 'yellow')\n .style('z-index', '100')\n .style('border', '1px solid #00aaaa');\n\n createDiv('= PANEL =')\n .parent(gui)\n .style('background', 'cyan')\n .style('padding', '2px')\n .style('text-align', 'center')\n .draggable(gui);\n\n createSlider(0, 100, random(100))\n .style('cursor', 'pointer')\n .size(80, 5)\n .parent(gui);\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "elmnt", + "description": "pass another p5.Element", + "optional": 1, + "type": "p5.Element" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "volume", + "file": "src/dom/dom.js", + "line": 4145, + "itemtype": "method", + "chainable": 1, + "description": "", + "example": [], + "overloads": [ + { + "params": [ + { + "name": "val", + "description": "volume between 0.0 and 1.0.", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "setMoveThreshold", + "file": "src/events/acceleration.js", + "line": 455, + "itemtype": "method", + "description": "The setMoveThreshold() function is used to set the movement threshold for\nthe deviceMoved() function. The default threshold is set to 0.5.", + "example": [ + "
\n\n// Run this example on a mobile device\n// You will need to move the device incrementally further\n// the closer the square's color gets to white in order to change the value.\n\nlet value = 0;\nlet threshold = 0.5;\nfunction setup() {\n setMoveThreshold(threshold);\n}\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`50-by-50 black rect in center of canvas.\n turns white on mobile when device moves`);\n}\nfunction deviceMoved() {\n value = value + 5;\n threshold = threshold + 0.1;\n if (value > 255) {\n value = 0;\n threshold = 30;\n }\n setMoveThreshold(threshold);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "The threshold value", + "type": "number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Acceleration" + }, + { + "name": "setShakeThreshold", + "file": "src/events/acceleration.js", + "line": 497, + "itemtype": "method", + "description": "The setShakeThreshold() function is used to set the movement threshold for\nthe deviceShaken() function. The default threshold is set to 30.", + "example": [ + "
\n\n// Run this example on a mobile device\n// You will need to shake the device more firmly\n// the closer the box's fill gets to white in order to change the value.\n\nlet value = 0;\nlet threshold = 30;\nfunction setup() {\n setShakeThreshold(threshold);\n}\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`50-by-50 black rect in center of canvas.\n turns white on mobile when device is being shaked`);\n}\nfunction deviceMoved() {\n value = value + 5;\n threshold = threshold + 5;\n if (value > 255) {\n value = 0;\n threshold = 30;\n }\n setShakeThreshold(threshold);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "The threshold value", + "type": "number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Acceleration" + }, + { + "name": "deviceMoved", + "file": "src/events/acceleration.js", + "line": 620, + "itemtype": "method", + "description": "The deviceMoved() function is called when the device is moved by more than\nthe threshold value along X, Y or Z axis. The default threshold is set to 0.5.\nThe threshold value can be changed using setMoveThreshold().", + "example": [ + "
\n\n// Run this example on a mobile device\n// Move the device around\n// to change the value.\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`50-by-50 black rect in center of canvas.\n turns white on mobile when device moves`);\n}\nfunction deviceMoved() {\n value = value + 5;\n if (value > 255) {\n value = 0;\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Acceleration" + }, + { + "name": "deviceTurned", + "file": "src/events/acceleration.js", + "line": 620, + "itemtype": "method", + "description": "

The deviceTurned() function is called when the device rotates by\nmore than 90 degrees continuously.

\n

The axis that triggers the deviceTurned() method is stored in the turnAxis\nvariable. The deviceTurned() method can be locked to trigger on any axis:\nX, Y or Z by comparing the turnAxis variable to 'X', 'Y' or 'Z'.

\n", + "example": [ + "
\n\n// Run this example on a mobile device\n// Rotate the device by 90 degrees\n// to change the value.\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`50-by-50 black rect in center of canvas.\n turns white on mobile when device turns`);\n}\nfunction deviceTurned() {\n if (value === 0) {\n value = 255;\n } else if (value === 255) {\n value = 0;\n }\n}\n\n
\n
\n\n// Run this example on a mobile device\n// Rotate the device by 90 degrees in the\n// X-axis to change the value.\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`50-by-50 black rect in center of canvas.\n turns white on mobile when x-axis turns`);\n}\nfunction deviceTurned() {\n if (turnAxis === 'X') {\n if (value === 0) {\n value = 255;\n } else if (value === 255) {\n value = 0;\n }\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Acceleration" + }, + { + "name": "deviceShaken", + "file": "src/events/acceleration.js", + "line": 620, + "itemtype": "method", + "description": "The deviceShaken() function is called when the device total acceleration\nchanges of accelerationX and accelerationY values is more than\nthe threshold value. The default threshold is set to 30.\nThe threshold value can be changed using setShakeThreshold().", + "example": [ + "
\n\n// Run this example on a mobile device\n// Shake the device to change the value.\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`50-by-50 black rect in center of canvas.\n turns white on mobile when device shakes`);\n}\nfunction deviceShaken() {\n value = value + 5;\n if (value > 255) {\n value = 0;\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Acceleration" + }, + { + "name": "keyPressed", + "file": "src/events/keyboard.js", + "line": 167, + "itemtype": "method", + "description": "

The keyPressed() function is called once every time a key is pressed. The\nkeyCode for the key that was pressed is stored in the keyCode variable.

\n

For non-ASCII keys, use the keyCode variable. You can check if the keyCode\nequals BACKSPACE, DELETE, ENTER, RETURN, TAB, ESCAPE, SHIFT, CONTROL,\nOPTION, ALT, UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW.

\n

For ASCII keys, the key that was pressed is stored in the key variable. However, it\ndoes not distinguish between uppercase and lowercase. For this reason, it\nis recommended to use keyTyped() to read the key variable, in which the\ncase of the variable will be distinguished.

\n

Because of how operating systems handle key repeats, holding down a key\nmay cause multiple calls to keyTyped() (and keyReleased() as well). The\nrate of repeat is set by the operating system and how each computer is\nconfigured.

\nBrowsers may have different default\nbehaviors attached to various key events. To prevent any default\nbehavior for this event, add \"return false\" to the end of the method.

\n", + "example": [ + "
\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`black rect center. turns white when key pressed and black\n when released.`);\n}\nfunction keyPressed() {\n if (value === 0) {\n value = 255;\n } else {\n value = 0;\n }\n}\n\n
\n
\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`black rect center. turns white when left arrow pressed and\n black when right.`);\n}\nfunction keyPressed() {\n if (keyCode === LEFT_ARROW) {\n value = 255;\n } else if (keyCode === RIGHT_ARROW) {\n value = 0;\n }\n}\n\n
\n
\n\nfunction keyPressed() {\n // Do something\n return false; // prevent any default behaviour\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "event", + "description": "optional KeyboardEvent callback argument.", + "optional": 1, + "type": "KeyboardEvent" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Keyboard" + }, + { + "name": "keyReleased", + "file": "src/events/keyboard.js", + "line": 215, + "itemtype": "method", + "description": "The keyReleased() function is called once every time a key is released.\nSee key and keyCode for more information.

\nBrowsers may have different default\nbehaviors attached to various key events. To prevent any default\nbehavior for this event, add \"return false\" to the end of the function.", + "example": [ + "
\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`black rect center. turns white when key pressed and black\n when pressed again`);\n}\nfunction keyReleased() {\n if (value === 0) {\n value = 255;\n } else {\n value = 0;\n }\n return false; // prevent any default behavior\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "event", + "description": "optional KeyboardEvent callback argument.", + "optional": 1, + "type": "KeyboardEvent" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Keyboard" + }, + { + "name": "keyTyped", + "file": "src/events/keyboard.js", + "line": 275, + "itemtype": "method", + "description": "

The keyTyped() function is called once every time a key is pressed, but\naction keys such as Backspace, Delete, Ctrl, Shift, and Alt are ignored. If you are trying to detect\na keyCode for one of these keys, use the keyPressed() function instead.\nThe most recent key typed will be stored in the key variable.

\n

Because of how operating systems handle key repeats, holding down a key\nwill cause multiple calls to keyTyped() (and keyReleased() as well). The\nrate of repeat is set by the operating system and how each computer is\nconfigured.

\nBrowsers may have different default behaviors attached to various key\nevents. To prevent any default behavior for this event, add \"return false\"\nto the end of the function.

\n", + "example": [ + "
\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`black rect center. turns white when 'a' key typed and\n black when 'b' pressed`);\n}\nfunction keyTyped() {\n if (key === 'a') {\n value = 255;\n } else if (key === 'b') {\n value = 0;\n }\n // uncomment to prevent any default behavior\n // return false;\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "event", + "description": "optional KeyboardEvent callback argument.", + "optional": 1, + "type": "KeyboardEvent" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Keyboard" + }, + { + "name": "keyIsDown", + "file": "src/events/keyboard.js", + "line": 372, + "itemtype": "method", + "description": "The keyIsDown() function checks if the key is currently down, i.e. pressed.\nIt can be used if you have an object that moves, and you want several keys\nto be able to affect its behaviour simultaneously, such as moving a\nsprite diagonally. You can put in any number representing the keyCode of\nthe key, or use any of the variable keyCode names listed\nhere.", + "example": [ + "
\nlet x = 100;\nlet y = 100;\n\nfunction setup() {\n createCanvas(512, 512);\n fill(255, 0, 0);\n}\n\nfunction draw() {\n if (keyIsDown(LEFT_ARROW)) {\n x -= 5;\n }\n\n if (keyIsDown(RIGHT_ARROW)) {\n x += 5;\n }\n\n if (keyIsDown(UP_ARROW)) {\n y -= 5;\n }\n\n if (keyIsDown(DOWN_ARROW)) {\n y += 5;\n }\n\n clear();\n ellipse(x, y, 50, 50);\n describe(`50-by-50 red ellipse moves left, right, up, and\n down with arrow presses.`);\n}\n
\n\n
\nlet diameter = 50;\n\nfunction setup() {\n createCanvas(512, 512);\n}\n\nfunction draw() {\n // 107 and 187 are keyCodes for \"+\"\n if (keyIsDown(107) || keyIsDown(187)) {\n diameter += 1;\n }\n\n // 109 and 189 are keyCodes for \"-\"\n if (keyIsDown(109) || keyIsDown(189)) {\n diameter -= 1;\n }\n\n clear();\n fill(255, 0, 0);\n ellipse(50, 50, diameter, diameter);\n describe(`50-by-50 red ellipse gets bigger or smaller when\n + or - are pressed.`);\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "code", + "description": "The key to check for.", + "type": "Number" + } + ], + "return": { + "description": "whether key is down or not", + "type": "Boolean" + } + } + ], + "return": { + "description": "whether key is down or not", + "type": "Boolean" + }, + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Keyboard" + }, + { + "name": "mouseDragged", + "file": "src/events/mouse.js", + "line": 573, + "itemtype": "method", + "description": "The mouseDragged() function is called once every time the mouse moves and\na mouse button is pressed. If no mouseDragged() function is defined, the\ntouchMoved() function will be called instead if it is defined.

\nBrowsers may have different default\nbehaviors attached to various mouse events. To prevent any default\nbehavior for this event, add \"return false\" to the end of the function.", + "example": [ + "
\n\n// Drag the mouse across the page\n// to change its value\n\nlet value = 0;\nfunction draw() {\n fill(value);\n rect(25, 25, 50, 50);\n describe(`black 50-by-50 rect turns lighter with mouse click and\n drag until white, resets`);\n}\nfunction mouseDragged() {\n value = value + 5;\n if (value > 255) {\n value = 0;\n }\n}\n\n
\n\n
\n\nfunction mouseDragged() {\n ellipse(mouseX, mouseY, 5, 5);\n // prevent default\n return false;\n}\n\n
\n\n
\n\n// returns a MouseEvent object\n// as a callback argument\nfunction mouseDragged(event) {\n console.log(event);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "event", + "description": "optional MouseEvent callback argument.", + "optional": 1, + "type": "MouseEvent" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Mouse" + }, + { + "name": "requestPointerLock", + "file": "src/events/mouse.js", + "line": 982, + "itemtype": "method", + "description": "The function requestPointerLock()\nlocks the pointer to its current position and makes it invisible.\nUse movedX and movedY to get the difference the mouse was moved since\nthe last call of draw.\nNote that not all browsers support this feature.\nThis enables you to create experiences that aren't limited by the mouse moving out of the screen\neven if it is repeatedly moved into one direction.\nFor example, a first person perspective experience.", + "example": [ + "
\n\nlet cam;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n requestPointerLock();\n cam = createCamera();\n}\n\nfunction draw() {\n background(255);\n cam.pan(-movedX * 0.001);\n cam.tilt(movedY * 0.001);\n sphere(25);\n describe(`3D scene moves according to mouse mouse movement in a\n first person perspective`);\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Mouse" + }, + { + "name": "exitPointerLock", + "file": "src/events/mouse.js", + "line": 1023, + "itemtype": "method", + "description": "The function exitPointerLock()\nexits a previously triggered pointer Lock\nfor example to make ui elements usable etc", + "example": [ + "
\n\n//click the canvas to lock the pointer\n//click again to exit (otherwise escape)\nlet locked = false;\nfunction draw() {\n background(237, 34, 93);\n describe('cursor gets locked / unlocked on mouse-click');\n}\nfunction mouseClicked() {\n if (!locked) {\n locked = true;\n requestPointerLock();\n } else {\n exitPointerLock();\n locked = false;\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Events", + "submodule": "Mouse" + }, + { + "name": "createImage", + "file": "src/image/image.js", + "line": 92, + "itemtype": "method", + "description": "

Creates a new p5.Image object. The new image is\ntransparent by default.

\n

createImage() uses the width and height paremeters to set the new\np5.Image object's dimensions in pixels. The new\np5.Image can be modified by updating its\npixels array or by calling its\nget() and\nset() methods. The\nloadPixels() method must be called\nbefore reading or modifying pixel values. The\nupdatePixels() method must be called\nfor updates to take effect.

\n", + "example": [ + "
\n\nlet img = createImage(66, 66);\nimg.loadPixels();\nfor (let x = 0; x < img.width; x += 1) {\n for (let y = 0; y < img.height; y += 1) {\n img.set(x, y, 0);\n }\n}\nimg.updatePixels();\nimage(img, 17, 17);\n\ndescribe('A black square drawn in the middle of a gray square.');\n\n
\n\n
\n\nlet img = createImage(66, 66);\nimg.loadPixels();\nfor (let x = 0; x < img.width; x += 1) {\n for (let y = 0; y < img.height; y += 1) {\n let a = map(x, 0, img.width, 0, 255);\n let c = color(0, a);\n img.set(x, y, c);\n }\n}\nimg.updatePixels();\nimage(img, 17, 17);\n\ndescribe('A square with a horizontal color gradient that transitions from gray to black.');\n\n
\n\n
\n\nlet img = createImage(66, 66);\nimg.loadPixels();\nlet d = pixelDensity();\nlet halfImage = 4 * (d * img.width) * (d * img.height / 2);\nfor (let i = 0; i < halfImage; i += 4) {\n // Red.\n img.pixels[i] = 0;\n // Green.\n img.pixels[i + 1] = 0;\n // Blue.\n img.pixels[i + 2] = 0;\n // Alpha.\n img.pixels[i + 3] = 255;\n}\nimg.updatePixels();\nimage(img, 17, 17);\n\ndescribe('A black square drawn in the middle of a gray square.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "width", + "description": "width in pixels.", + "type": "Integer" + }, + { + "name": "height", + "description": "height in pixels.", + "type": "Integer" + } + ], + "return": { + "description": "new p5.Image object.", + "type": "p5.Image" + } + } + ], + "return": { + "description": "new p5.Image object.", + "type": "p5.Image" + }, + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "saveCanvas", + "file": "src/image/image.js", + "line": 173, + "itemtype": "method", + "description": "Saves the current canvas as an image. The browser will either save the\nfile immediately or prompt the user with a dialogue window.", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100);\n background(255);\n saveCanvas();\n}\n\n
\n\n
\n\nfunction setup() {\n createCanvas(100, 100);\n background(255);\n saveCanvas('myCanvas.jpg');\n}\n\n
\n\n
\n\nfunction setup() {\n createCanvas(100, 100);\n background(255);\n saveCanvas('myCanvas', 'jpg');\n}\n\n
\n\n
\n\nfunction setup() {\n let cnv = createCanvas(100, 100);\n background(255);\n saveCanvas(cnv);\n}\n\n
\n\n
\n\nfunction setup() {\n let cnv = createCanvas(100, 100);\n background(255);\n saveCanvas(cnv, 'myCanvas.jpg');\n}\n\n
\n\n
\n\nfunction setup() {\n let cnv = createCanvas(100, 100);\n background(255);\n saveCanvas(cnv, 'myCanvas', 'jpg');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "selectedCanvas", + "description": "reference to a\nspecific HTML5 canvas element.", + "type": "p5.Framebuffer|p5.Element|HTMLCanvasElement" + }, + { + "name": "filename", + "description": "file name. Defaults to 'untitled'.", + "optional": 1, + "type": "String" + }, + { + "name": "extension", + "description": "file extension, either 'jpg' or 'png'. Defaults to 'png'.", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "filename", + "optional": 1, + "type": "String" + }, + { + "name": "extension", + "optional": 1, + "type": "String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "saveFrames", + "file": "src/image/image.js", + "line": 534, + "itemtype": "method", + "description": "

Captures a sequence of frames from the canvas that can be used to create a\nmovie. Frames are downloaded as individual image files by default.

\n

The first parameter, filename, sets the prefix for the file names. For\nexample, setting the prefix to 'frame' would generate the image files\nframe0.png, frame1.png, and so on. The second parameter, extension,\nsets the file type to either 'png' or 'jpg'.

\n

The third parameter, duration, sets the duration to record in seconds.\nThe maximum duration is 15 seconds. The fourth parameter, framerate, sets\nthe number of frames to record per second. The maximum frame rate value is\n22. Limits are placed on duration and framerate to avoid using too much\nmemory. Recording large canvases can easily crash sketches or even web\nbrowsers.

\n

The fifth parameter, callback, is optional. If a function is passed,\nimage files won't be saved by default. The callback function can be used\nto process an array containing the data for each captured frame. The array\nof image data contains a sequence of objects with three properties for each\nframe: imageData, filename, and extension.

\n", + "example": [ + "
\n\nfunction draw() {\n let r = frameCount % 255;\n let g = 50;\n let b = 100;\n background(r, g, b);\n\n describe('A square repeatedly changes color from blue to pink.');\n}\n\nfunction keyPressed() {\n if (key === 's') {\n saveFrames('frame', 'png', 1, 5);\n }\n}\n\n
\n\n
\n\nfunction draw() {\n let r = frameCount % 255;\n let g = 50;\n let b = 100;\n background(r, g, b);\n\n describe('A square repeatedly changes color from blue to pink.');\n}\n\nfunction mousePressed() {\n saveFrames('frame', 'png', 1, 5, data => {\n // Prints an array of objects containing raw image data,\n // filenames, and extensions.\n print(data);\n });\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "filename", + "description": "prefix of file name.", + "type": "String" + }, + { + "name": "extension", + "description": "file extension, either 'jpg' or 'png'.", + "type": "String" + }, + { + "name": "duration", + "description": "duration in seconds to record. This parameter will be constrained to be less or equal to 15.", + "type": "Number" + }, + { + "name": "framerate", + "description": "number of frames to save per second. This parameter will be constrained to be less or equal to 22.", + "type": "Number" + }, + { + "name": "callback", + "description": "callback function that will be executed\nto handle the image data. This function\nshould accept an array as argument. The\narray will contain the specified number of\nframes of objects. Each object has three\nproperties: imageData, filename, and extension.", + "optional": 1 + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "loadImage", + "file": "src/image/loading_displaying.js", + "line": 92, + "itemtype": "method", + "description": "

Loads an image to create a p5.Image object.

\n

loadImage() interprets the first parameter one of three ways. If the path\nto an image file is provided, loadImage() will load it. Paths to local\nfiles should be relative, such as 'assets/thundercat.jpg'. URLs such as\n'https://example.com/thundercat.jpg' may be blocked due to browser\nsecurity. Raw image data can also be passed as a base64 encoded image in\nthe form 'data:image/png;base64,arandomsequenceofcharacters'.

\n

The second parameter is optional. If a function is passed, it will be\ncalled once the image has loaded. The callback function can optionally use\nthe new p5.Image object.

\n

The third parameter is also optional. If a function is passed, it will be\ncalled if the image fails to load. The callback function can optionally use\nthe event error.

\n

Images can take time to load. Calling loadImage() in\npreload() ensures images load before they're\nused in setup() or draw().

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n describe('Image of the underside of a white umbrella and a gridded ceiling.');\n}\n\n
\n\n
\n\nfunction setup() {\n loadImage('assets/laDefense.jpg', img => {\n image(img, 0, 0);\n });\n describe('Image of the underside of a white umbrella and a gridded ceiling.');\n}\n\n
\n\n
\n\nfunction setup() {\n loadImage('assets/laDefense.jpg', success, failure);\n}\n\nfunction success(img) {\n image(img, 0, 0);\n describe('Image of the underside of a white umbrella and a gridded ceiling.');\n}\n\nfunction failure(event) {\n console.error('Oops!', event);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "path of the image to be loaded or base64 encoded image.", + "type": "String" + }, + { + "name": "successCallback", + "description": "function called with\np5.Image once it\nloads.", + "optional": 1 + }, + { + "name": "failureCallback", + "description": "function called with event\nerror if the image fails to load.", + "optional": 1 + } + ], + "return": { + "description": "the p5.Image object.", + "type": "p5.Image" + } + } + ], + "return": { + "description": "the p5.Image object.", + "type": "p5.Image" + }, + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Loading & Displaying" + }, + { + "name": "saveGif", + "file": "src/image/loading_displaying.js", + "line": 247, + "itemtype": "method", + "description": "

Generates a gif from a sketch and saves it to a file. saveGif() may be\ncalled in setup() or at any point while a sketch\nis running.

\n

The first parameter, fileName, sets the gif's file name. The second\nparameter, duration, sets the gif's duration in seconds.

\n

The third parameter, options, is optional. If an object is passed,\nsaveGif() will use its properties to customize the gif. saveGif()\nrecognizes the properties delay, units, silent,\nnotificationDuration, and notificationID.

\n", + "example": [ + "
\n\nfunction draw() {\n background(200);\n let c = frameCount % 255;\n fill(c);\n circle(50, 50, 25);\n\n describe('A circle drawn in the middle of a gray square. The circle changes color from black to white, then repeats.');\n}\n\nfunction keyPressed() {\n if (key === 's') {\n saveGif('mySketch', 5);\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "filename", + "description": "file name of gif.", + "type": "String" + }, + { + "name": "duration", + "description": "duration in seconds to capture from the sketch.", + "type": "Number" + }, + { + "name": "options", + "description": "an object that can contain five more properties:\ndelay, a Number specifying how much time to wait before recording;\nunits, a String that can be either 'seconds' or 'frames'. By default it's 'seconds’;\nsilent, a Boolean that defines presence of progress notifications. By default it’s false;\nnotificationDuration, a Number that defines how long in seconds the final notification\nwill live. By default it's 0, meaning the notification will never be removed;\nnotificationID, a String that specifies the id of the notification's DOM element. By default it’s 'progressBar’.", + "optional": 1, + "type": "Object" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Loading & Displaying" + }, + { + "name": "image", + "file": "src/image/loading_displaying.js", + "line": 1017, + "itemtype": "method", + "description": "

Draws a source image to the canvas.

\n

The first parameter, img, is the source image to be drawn. The second and\nthird parameters, dx and dy, set the coordinates of the destination\nimage's top left corner. See imageMode() for\nother ways to position images.

\n

Here's a diagram that explains how optional parameters work in image():

\n

\n

The fourth and fifth parameters, dw and dh, are optional. They set the\nthe width and height to draw the destination image. By default, image()\ndraws the full source image at its original size.

\n

The sixth and seventh parameters, sx and sy, are also optional.\nThese coordinates define the top left corner of a subsection to draw from\nthe source image.

\n

The eighth and ninth parameters, sw and sh, are also optional.\nThey define the width and height of a subsection to draw from the source\nimage. By default, image() draws the full subsection that begins at\n(sx, sy) and extends to the edges of the source image.

\n

The ninth parameter, fit, is also optional. It enables a subsection of\nthe source image to be drawn without affecting its aspect ratio. If\nCONTAIN is passed, the full subsection will appear within the destination\nrectangle. If COVER is passed, the subsection will completely cover the\ndestination rectangle. This may have the effect of zooming into the\nsubsection.

\n

The tenth and eleventh paremeters, xAlign and yAlign, are also\noptional. They determine how to align the fitted subsection. xAlign can\nbe set to either LEFT, RIGHT, or CENTER. yAlign can be set to\neither TOP, BOTTOM, or CENTER. By default, both xAlign and yAlign\nare set to CENTER.

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n background(50);\n image(img, 0, 0);\n\n describe('An image of the underside of a white umbrella with a gridded ceiling above.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n background(50);\n image(img, 10, 10);\n\n describe('An image of the underside of a white umbrella with a gridded ceiling above. The image has dark gray borders on its left and top.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n background(50);\n image(img, 0, 0, 50, 50);\n\n describe('An image of the underside of a white umbrella with a gridded ceiling above. The image is drawn in the top left corner of a dark gray square.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n background(50);\n image(img, 25, 25, 50, 50, 25, 25, 50, 50);\n\n describe('An image of a gridded ceiling drawn in the center of a dark gray square.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/moonwalk.jpg');\n}\n\nfunction setup() {\n background(50);\n image(img, 0, 0, width, height, 0, 0, img.width, img.height, CONTAIN);\n\n describe('An image of an astronaut on the moon. The top and bottom borders of the image are dark gray.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n // Image is 50 x 50 pixels.\n img = loadImage('assets/laDefense50.png');\n}\n\nfunction setup() {\n background(50);\n image(img, 0, 0, width, height, 0, 0, img.width, img.height, COVER);\n\n describe('A pixelated image of the underside of a white umbrella with a gridded ceiling above.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "img", + "description": "image to display.", + "type": "p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture" + }, + { + "name": "x", + "description": "x-coordinate of the top-left corner of the image.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the top-left corner of the image.", + "type": "Number" + }, + { + "name": "width", + "description": "width to draw the image.", + "optional": 1, + "type": "Number" + }, + { + "name": "height", + "description": "height to draw the image.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "img", + "type": "p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture" + }, + { + "name": "dx", + "description": "the x-coordinate of the destination\nrectangle in which to draw the source image", + "type": "Number" + }, + { + "name": "dy", + "description": "the y-coordinate of the destination\nrectangle in which to draw the source image", + "type": "Number" + }, + { + "name": "dWidth", + "description": "the width of the destination rectangle", + "type": "Number" + }, + { + "name": "dHeight", + "description": "the height of the destination rectangle", + "type": "Number" + }, + { + "name": "sx", + "description": "the x-coordinate of the subsection of the source\nimage to draw into the destination rectangle", + "type": "Number" + }, + { + "name": "sy", + "description": "the y-coordinate of the subsection of the source\nimage to draw into the destination rectangle", + "type": "Number" + }, + { + "name": "sWidth", + "description": "the width of the subsection of the\nsource image to draw into the destination\nrectangle", + "optional": 1, + "type": "Number" + }, + { + "name": "sHeight", + "description": "the height of the subsection of the\nsource image to draw into the destination rectangle", + "optional": 1, + "type": "Number" + }, + { + "name": "fit", + "description": "either CONTAIN or COVER", + "optional": 1, + "type": "Constant" + }, + { + "name": "xAlign", + "description": "either LEFT, RIGHT or CENTER default is CENTER", + "optional": 1, + "type": "Constant" + }, + { + "name": "yAlign", + "description": "either TOP, BOTTOM or CENTER default is CENTER", + "optional": 1, + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Loading & Displaying" + }, + { + "name": "tint", + "file": "src/image/loading_displaying.js", + "line": 1232, + "itemtype": "method", + "description": "

Tints images using a specified color.

\n

The version of tint() with one parameter interprets it one of four ways.\nIf the parameter is a number, it's interpreted as a grayscale value. If the\nparameter is a string, it's interpreted as a CSS color string. An array of\n[R, G, B, A] values or a p5.Color object can\nalso be used to set the tint color.

\n

The version of tint() with two parameters uses the first one as a\ngrayscale value and the second as an alpha value. For example, calling\ntint(255, 128) will make an image 50% transparent.

\n

The version of tint() with three parameters interprets them as RGB or\nHSB values, depending on the current\ncolorMode(). The optional fourth parameter\nsets the alpha value. For example, tint(255, 0, 0, 100) will give images\na red tint and make them transparent.

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n tint('red');\n image(img, 50, 0);\n\n describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a red tint.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n tint(255, 0, 0);\n image(img, 50, 0);\n\n describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a red tint.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n tint(255, 0, 0, 100);\n image(img, 50, 0);\n\n describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a transparent red tint.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n tint(255, 180);\n image(img, 50, 0);\n\n describe('Two images of an umbrella and a ceiling side-by-side. The image on the right is transparent.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "red or hue value.", + "type": "Number" + }, + { + "name": "v2", + "description": "green or saturation value.", + "type": "Number" + }, + { + "name": "v3", + "description": "blue or brightness.", + "type": "Number" + }, + { + "name": "alpha", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "CSS color string.", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "gray", + "description": "grayscale value.", + "type": "Number" + }, + { + "name": "alpha", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "values", + "description": "array containing the red, green, blue &\nalpha components of the color.", + "type": "Number[]" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "the tint color", + "type": "p5.Color" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Loading & Displaying" + }, + { + "name": "noTint", + "file": "src/image/loading_displaying.js", + "line": 1262, + "itemtype": "method", + "description": "Removes the current tint set by tint() and restores\nimages to their original colors.", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\nfunction setup() {\n tint('red');\n image(img, 0, 0);\n noTint();\n image(img, 50, 0);\n\n describe('Two images of an umbrella and a ceiling side-by-side. The image on the left has a red tint.');\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Loading & Displaying" + }, + { + "name": "imageMode", + "file": "src/image/loading_displaying.js", + "line": 1353, + "itemtype": "method", + "description": "

Changes the location from which images are drawn when\nimage() is called.

\n

By default, the first\ntwo parameters of image() are the x- and\ny-coordinates of the image's upper-left corner. The next parameters are\nits width and height. This is the same as calling imageMode(CORNER).

\n

imageMode(CORNERS) also uses the first two parameters of\nimage() as the x- and y-coordinates of the image's\ntop-left corner. The third and fourth parameters are the coordinates of its\nbottom-right corner.

\n

imageMode(CENTER) uses the first two parameters of\nimage() as the x- and y-coordinates of the image's\ncenter. The next parameters are its width and height.

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n background(200);\n imageMode(CORNER);\n image(img, 10, 10, 50, 50);\n\n describe('A square image of a brick wall is drawn at the top left of a gray square.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n background(200);\n imageMode(CORNERS);\n image(img, 10, 10, 90, 40);\n\n describe('An image of a brick wall is drawn on a gray square. The image is squeezed into a small rectangular area.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n background(200);\n imageMode(CENTER);\n image(img, 50, 50, 80, 80);\n\n describe('A square image of a brick wall is drawn on a gray square.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "mode", + "description": "either CORNER, CORNERS, or CENTER.", + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Loading & Displaying" + }, + { + "name": "blend", + "file": "src/image/pixels.js", + "line": 198, + "itemtype": "method", + "description": "Copies a region of pixels from one image to another. The blendMode\nparameter blends the images' colors to create different effects.", + "example": [ + "
\n\nlet img0;\nlet img1;\n\nfunction preload() {\n img0 = loadImage('assets/rockies.jpg');\n img1 = loadImage('assets/bricks_third.jpg');\n}\n\nfunction setup() {\n background(img0);\n image(img1, 0, 0);\n blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST);\n\n describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears faded on the right of the image.');\n}\n\n
\n\n
\n\nlet img0;\nlet img1;\n\nfunction preload() {\n img0 = loadImage('assets/rockies.jpg');\n img1 = loadImage('assets/bricks_third.jpg');\n}\n\nfunction setup() {\n background(img0);\n image(img1, 0, 0);\n blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST);\n\n describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears transparent on the right of the image.');\n}\n\n
\n\n
\n\nlet img0;\nlet img1;\n\nfunction preload() {\n img0 = loadImage('assets/rockies.jpg');\n img1 = loadImage('assets/bricks_third.jpg');\n}\n\nfunction setup() {\n background(img0);\n image(img1, 0, 0);\n blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, ADD);\n\n describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears washed out on the right of the image.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "srcImage", + "description": "source image.", + "type": "p5.Image" + }, + { + "name": "sx", + "description": "x-coordinate of the source's upper-left corner.", + "type": "Integer" + }, + { + "name": "sy", + "description": "y-coordinate of the source's upper-left corner.", + "type": "Integer" + }, + { + "name": "sw", + "description": "source image width.", + "type": "Integer" + }, + { + "name": "sh", + "description": "source image height.", + "type": "Integer" + }, + { + "name": "dx", + "description": "x-coordinate of the destination's upper-left corner.", + "type": "Integer" + }, + { + "name": "dy", + "description": "y-coordinate of the destination's upper-left corner.", + "type": "Integer" + }, + { + "name": "dw", + "description": "destination image width.", + "type": "Integer" + }, + { + "name": "dh", + "description": "destination image height.", + "type": "Integer" + }, + { + "name": "blendMode", + "description": "the blend mode. either\nBLEND, DARKEST, LIGHTEST, DIFFERENCE,\nMULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,\nSOFT_LIGHT, DODGE, BURN, ADD or NORMAL.", + "type": "Constant" + } + ] + }, + { + "params": [ + { + "name": "sx", + "type": "Integer" + }, + { + "name": "sy", + "type": "Integer" + }, + { + "name": "sw", + "type": "Integer" + }, + { + "name": "sh", + "type": "Integer" + }, + { + "name": "dx", + "type": "Integer" + }, + { + "name": "dy", + "type": "Integer" + }, + { + "name": "dw", + "type": "Integer" + }, + { + "name": "dh", + "type": "Integer" + }, + { + "name": "blendMode", + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Pixels" + }, + { + "name": "copy", + "file": "src/image/pixels.js", + "line": 257, + "itemtype": "method", + "description": "Copies pixels from a source image to a region of the canvas. The source\nimage can be the canvas itself or a p5.Image\nobject. copy() will scale pixels from the source region if it isn't the\nsame size as the destination region.", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n background(img);\n copy(img, 7, 22, 10, 10, 35, 25, 50, 50);\n // Show copied region.\n stroke(255);\n noFill();\n square(7, 22, 10);\n\n describe('An image of a mountain landscape. A square region is outlined in white. A larger square contains a pixelated view of the outlined region.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "srcImage", + "description": "source image.", + "type": "p5.Image|p5.Element" + }, + { + "name": "sx", + "description": "x-coordinate of the source's upper-left corner.", + "type": "Integer" + }, + { + "name": "sy", + "description": "y-coordinate of the source's upper-left corner.", + "type": "Integer" + }, + { + "name": "sw", + "description": "source image width.", + "type": "Integer" + }, + { + "name": "sh", + "description": "source image height.", + "type": "Integer" + }, + { + "name": "dx", + "description": "x-coordinate of the destination's upper-left corner.", + "type": "Integer" + }, + { + "name": "dy", + "description": "y-coordinate of the destination's upper-left corner.", + "type": "Integer" + }, + { + "name": "dw", + "description": "destination image width.", + "type": "Integer" + }, + { + "name": "dh", + "description": "destination image height.", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "sx", + "type": "Integer" + }, + { + "name": "sy", + "type": "Integer" + }, + { + "name": "sw", + "type": "Integer" + }, + { + "name": "sh", + "type": "Integer" + }, + { + "name": "dx", + "type": "Integer" + }, + { + "name": "dy", + "type": "Integer" + }, + { + "name": "dw", + "type": "Integer" + }, + { + "name": "dh", + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Pixels" + }, + { + "name": "filter", + "file": "src/image/pixels.js", + "line": 571, + "itemtype": "method", + "description": "

Applies an image filter to the canvas. The preset options are:

\n

INVERT\nInverts the colors in the image. No parameter is used.

\n

GRAY\nConverts the image to grayscale. No parameter is used.

\n

THRESHOLD\nConverts the image to black and white. Pixels with a grayscale value\nabove a given threshold are converted to white. The rest are converted to\nblack. The threshold must be between 0.0 (black) and 1.0 (white). If no\nvalue is specified, 0.5 is used.

\n

OPAQUE\nSets the alpha channel to entirely opaque. No parameter is used.

\n

POSTERIZE\nLimits the number of colors in the image. Each color channel is limited to\nthe number of colors specified. Values between 2 and 255 are valid, but\nresults are most noticeable with lower values. The default value is 4.

\n

BLUR\nBlurs the image. The level of blurring is specified by a blur radius. Larger\nvalues increase the blur. The default value is 4. A gaussian blur is used\nin P2D mode. A box blur is used in WEBGL mode.

\n

ERODE\nReduces the light areas. No parameter is used.

\n

DILATE\nIncreases the light areas. No parameter is used.

\n

filter() uses WebGL in the background by default because it's faster.\nThis can be disabled in P2D mode by adding a false argument, as in\nfilter(BLUR, false). This may be useful to keep computation off the GPU\nor to work around a lack of WebGL support.

\n

In WEBGL mode, filter() can also use custom shaders. See\ncreateFilterShader() for more\ninformation.

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n filter(INVERT);\n\n describe('A blue brick wall.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n filter(GRAY);\n\n describe('A brick wall drawn in grayscale.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n filter(THRESHOLD);\n\n describe('A brick wall drawn in black and white.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n filter(OPAQUE);\n\n describe('A red brick wall.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n filter(POSTERIZE, 3);\n\n describe('An image of a red brick wall drawn with limited color palette.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n filter(BLUR, 3);\n\n describe('A blurry image of a red brick wall.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n filter(DILATE);\n\n describe('A red brick wall with bright lines between each brick.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n filter(ERODE);\n\n describe('A red brick wall with faint lines between each brick.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n // Don't use WebGL.\n filter(BLUR, 3, false);\n\n describe('A blurry image of a red brick wall.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "filterType", + "description": "either THRESHOLD, GRAY, OPAQUE, INVERT,\nPOSTERIZE, BLUR, ERODE, DILATE or BLUR.", + "type": "Constant" + }, + { + "name": "filterParam", + "description": "parameter unique to each filter.", + "optional": 1, + "type": "Number" + }, + { + "name": "useWebGL", + "description": "flag to control whether to use fast\nWebGL filters (GPU) or original image\nfilters (CPU); defaults to true.", + "optional": 1, + "type": "Boolean" + } + ] + }, + { + "params": [ + { + "name": "filterType", + "type": "Constant" + }, + { + "name": "filterParam", + "optional": 1, + "type": "Number" + }, + { + "name": "useWebGL", + "optional": 1, + "type": "Boolean" + } + ] + }, + { + "params": [ + { + "name": "shaderFilter", + "description": "shader that's been loaded, with the\nfrag shader using a tex0 uniform.", + "type": "p5.Shader" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Pixels" + }, + { + "name": "get", + "file": "src/io/p5.TableRow.js", + "line": 223, + "itemtype": "method", + "description": "

Gets a pixel or a region of pixels from the canvas.

\n

get() is easy to use but it's not as fast as\npixels. Use pixels\nto read many pixel values.

\n

The version of get() with no parameters returns the entire canvas.

\n

The version of get() with two parameters interprets them as\ncoordinates. It returns an array with the [R, G, B, A] values of the\npixel at the given point.

\n

The version of get() with four parameters interprets them as coordinates\nand dimensions. It returns a subsection of the canvas as a\np5.Image object. The first two parameters are the\ncoordinates for the upper-left corner of the subsection. The last two\nparameters are the width and height of the subsection.

\n

Use p5.Image.get() to work directly with\np5.Image objects.

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n let c = get();\n image(c, width / 2, 0);\n\n describe('Two identical mountain landscapes shown side-by-side.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n let c = get(50, 90);\n fill(c);\n noStroke();\n square(25, 25, 50);\n\n describe('A mountain landscape with an olive green square in its center.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n let c = get(0, 0, width / 2, height / 2);\n image(c, width / 2, height / 2);\n\n describe('A mountain landscape drawn on top of another mountain landscape.');\n}\n\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let names = [];\n let rows = table.getRows();\n for (let r = 0; r < rows.length; r++) {\n names.push(rows[r].get('name'));\n }\n\n print(names);\n\n describe('no image displayed');\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the pixel.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the pixel.", + "type": "Number" + }, + { + "name": "w", + "description": "width of the subsection to be returned.", + "type": "Number" + }, + { + "name": "h", + "description": "height of the subsection to be returned.", + "type": "Number" + } + ], + "return": { + "description": "subsection as a p5.Image object.", + "type": "p5.Image" + } + }, + { + "params": [], + "return": { + "description": "whole canvas as a p5.Image.", + "type": "p5.Image" + } + }, + { + "params": [ + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + } + ], + "return": { + "description": "color of the pixel at (x, y) in array format [R, G, B, A].", + "type": "Number[]" + } + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + } + ], + "return": { + "description": "subsection as a p5.Image object.", + "type": "p5.Image" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "loadPixels", + "file": "src/image/pixels.js", + "line": 797, + "itemtype": "method", + "description": "Loads the current value of each pixel on the canvas into the\npixels array. This\nfunction must be called before reading from or writing to\npixels.", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0, width, height);\n let d = pixelDensity();\n let halfImage = 4 * (d * width) * (d * height / 2);\n loadPixels();\n for (let i = 0; i < halfImage; i += 1) {\n pixels[i + halfImage] = pixels[i];\n }\n updatePixels();\n\n describe('Two identical images of mountain landscapes, one on top of the other.');\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Pixels" + }, + { + "name": "set", + "file": "src/io/p5.TableRow.js", + "line": 75, + "itemtype": "method", + "description": "

Sets the color of a pixel or draws an image to the canvas.

\n

set() is easy to use but it's not as fast as\npixels. Use pixels\nto set many pixel values.

\n

set() interprets the first two parameters as x- and y-coordinates. It\ninterprets the last parameter as a grayscale value, a [R, G, B, A] pixel\narray, a p5.Color object, or a\np5.Image object. If an image is passed, the first\ntwo parameters set the coordinates for the image's upper-left corner,\nregardless of the current imageMode().

\n

updatePixels() must be called after using\nset() for changes to appear.

\n", + "example": [ + "
\n\nset(30, 20, 0);\nset(85, 20, 0);\nset(85, 75, 0);\nset(30, 75, 0);\nupdatePixels();\n\ndescribe('Four black dots arranged in a square drawn on a gray background.');\n\n
\n\n
\n\nlet black = color(0);\nset(30, 20, black);\nset(85, 20, black);\nset(85, 75, black);\nset(30, 75, black);\nupdatePixels();\n\ndescribe('Four black dots arranged in a square drawn on a gray background.');\n\n
\n\n
\n\nfor (let x = 0; x < width; x += 1) {\n for (let y = 0; y < height; y += 1) {\n let c = map(x, 0, width, 0, 255);\n set(x, y, c);\n }\n}\nupdatePixels();\n\ndescribe('A horiztonal color gradient from black to white.');\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n set(0, 0, img);\n updatePixels();\n\n describe('An image of a mountain landscape.');\n}\n\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n print(table.getArray());\n\n describe('no image displayed');\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the pixel.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the pixel.", + "type": "Number" + }, + { + "name": "c", + "description": "grayscale value | pixel array |\np5.Color object | p5.Image to copy.", + "type": "Number|Number[]|Object" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (Number)\nor Title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "The value to be stored", + "type": "String|Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "updatePixels", + "file": "src/image/pixels.js", + "line": 925, + "itemtype": "method", + "description": "

Updates the canvas with the RGBA values in the\npixels array.

\n

updatePixels() only needs to be called after changing values in the\npixels array. Such changes can be made directly\nafter calling loadPixels() or by calling\nset().

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0, width, height);\n let d = pixelDensity();\n let halfImage = 4 * (d * width) * (d * height / 2);\n loadPixels();\n for (let i = 0; i < halfImage; i += 1) {\n pixels[i + halfImage] = pixels[i];\n }\n updatePixels();\n\n describe('Two identical images of mountain landscapes, one on top of the other.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the upper-left corner of region\nto update.", + "optional": 1, + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the upper-left corner of region\nto update.", + "optional": 1, + "type": "Number" + }, + { + "name": "w", + "description": "width of region to update.", + "optional": 1, + "type": "Number" + }, + { + "name": "h", + "description": "height of region to update.", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Image", + "submodule": "Pixels" + }, + { + "name": "loadJSON", + "file": "src/io/files.js", + "line": 114, + "itemtype": "method", + "description": "

Loads a JSON file from a file or a URL, and returns an Object.\nNote that even if the JSON file contains an Array, an Object will be\nreturned with index numbers as keys.

\n

This method is asynchronous, meaning it may not finish before the next\nline in your sketch is executed. JSONP is supported via a polyfill and you\ncan pass in as the second argument an object with definitions of the json\ncallback following the syntax specified here.

\n

This method is suitable for fetching files up to size of 64MB.

\n", + "example": [ + "Calling loadJSON() inside preload() guarantees to complete the\noperation before setup() and draw() are called.\n\n
\n// Examples use USGS Earthquake API:\n// https://earthquake.usgs.gov/fdsnws/event/1/#methods\nlet earthquakes;\nfunction preload() {\n // Get the most recent earthquake in the database\n let url =\n'https://earthquake.usgs.gov/earthquakes/feed/v1.0/' +\n 'summary/all_day.geojson';\n earthquakes = loadJSON(url);\n}\n\nfunction setup() {\n noLoop();\n}\n\nfunction draw() {\n background(200);\n // Get the magnitude and name of the earthquake out of the loaded JSON\n let earthquakeMag = earthquakes.features[0].properties.mag;\n let earthquakeName = earthquakes.features[0].properties.place;\n ellipse(width / 2, height / 2, earthquakeMag * 10, earthquakeMag * 10);\n textAlign(CENTER);\n text(earthquakeName, 0, height - 30, width, 30);\n describe(`50×50 ellipse that changes from black to white\n depending on the current humidity`);\n}\n
\n\nOutside of preload(), you may supply a callback function to handle the\nobject:\n
\nfunction setup() {\n noLoop();\n let url =\n'https://earthquake.usgs.gov/earthquakes/feed/v1.0/' +\n 'summary/all_day.geojson';\n loadJSON(url, drawEarthquake);\n}\n\nfunction draw() {\n background(200);\n describe(`50×50 ellipse that changes from black to white\n depending on the current humidity`);\n}\n\nfunction drawEarthquake(earthquakes) {\n // Get the magnitude and name of the earthquake out of the loaded JSON\n let earthquakeMag = earthquakes.features[0].properties.mag;\n let earthquakeName = earthquakes.features[0].properties.place;\n ellipse(width / 2, height / 2, earthquakeMag * 10, earthquakeMag * 10);\n textAlign(CENTER);\n text(earthquakeName, 0, height - 30, width, 30);\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "name of the file or url to load", + "type": "String" + }, + { + "name": "jsonpOptions", + "description": "options object for jsonp related settings", + "optional": 1, + "type": "Object" + }, + { + "name": "datatype", + "description": "\"json\" or \"jsonp\"", + "optional": 1, + "type": "String" + }, + { + "name": "callback", + "description": "function to be executed after\nloadJSON() completes, data is passed\nin as first argument", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "description": "function to be executed if\nthere is an error, response is passed\nin as first argument", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "JSON data", + "type": "Object|Array" + } + }, + { + "params": [ + { + "name": "path", + "type": "String" + }, + { + "name": "datatype", + "type": "String" + }, + { + "name": "callback", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "", + "type": "Object|Array" + } + }, + { + "params": [ + { + "name": "path", + "type": "String" + }, + { + "name": "callback", + "type": "function" + }, + { + "name": "errorCallback", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "", + "type": "Object|Array" + } + } + ], + "return": { + "description": "JSON data", + "type": "Object|Array" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "loadStrings", + "file": "src/io/files.js", + "line": 229, + "itemtype": "method", + "description": "

Reads the contents of a file and creates a String array of its individual\nlines. If the name of the file is used as the parameter, as in the above\nexample, the file must be located in the sketch directory/folder.

\n

Alternatively, the file may be loaded from anywhere on the local\ncomputer using an absolute path (something that starts with / on Unix and\nLinux, or a drive letter on Windows), or the filename parameter can be a\nURL for a file found on a network.

\n

This method is asynchronous, meaning it may not finish before the next\nline in your sketch is executed.

\n

This method is suitable for fetching files up to size of 64MB.

\n", + "example": [ + "Calling loadStrings() inside preload() guarantees to complete the\noperation before setup() and draw() are called.\n\n
\nlet result;\nfunction preload() {\n result = loadStrings('assets/test.txt');\n}\n\nfunction setup() {\n background(200);\n text(random(result), 10, 10, 80, 80);\n describe(`randomly generated text from a file,\n for example \"i smell like butter\"`);\n}\n
\n\nOutside of preload(), you may supply a callback function to handle the\nobject:\n\n
\nfunction setup() {\n loadStrings('assets/test.txt', pickString);\n describe(`randomly generated text from a file,\n for example \"i have three feet\"`);\n}\n\nfunction pickString(result) {\n background(200);\n text(random(result), 10, 10, 80, 80);\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "filename", + "description": "name of the file or url to load", + "type": "String" + }, + { + "name": "callback", + "description": "function to be executed after loadStrings()\ncompletes, Array is passed in as first\nargument", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "description": "function to be executed if\nthere is an error, response is passed\nin as first argument", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "Array of Strings", + "type": "String[]" + } + } + ], + "return": { + "description": "Array of Strings", + "type": "String[]" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "loadTable", + "file": "src/io/files.js", + "line": 361, + "itemtype": "method", + "description": "

Reads the contents of a file or URL and creates a p5.Table object with\nits values. If a file is specified, it must be located in the sketch's\n\"data\" folder. The filename parameter can also be a URL to a file found\nonline. By default, the file is assumed to be comma-separated (in CSV\nformat). Table only looks for a header row if the 'header' option is\nincluded.

\n

This method is asynchronous, meaning it may not finish before the next\nline in your sketch is executed. Calling loadTable() inside preload()\nguarantees to complete the operation before setup() and draw() are called.\nOutside of preload(), you may supply a callback function to handle the\nobject:

\n

All files loaded and saved use UTF-8 encoding. This method is suitable for fetching files up to size of 64MB.

\n", + "example": [ + "
\n\n// Given the following CSV file called \"mammals.csv\"\n// located in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n //the file can be remote\n //table = loadTable(\"http://p5js.org/reference/assets/mammals.csv\",\n // \"csv\", \"header\");\n}\n\nfunction setup() {\n //count the columns\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n\n print(table.getColumn('name'));\n //[\"Goat\", \"Leopard\", \"Zebra\"]\n\n //cycle through the table\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(table.getString(r, c));\n }\n describe(`randomly generated text from a file,\n for example \"i smell like butter\"`);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "filename", + "description": "name of the file or URL to load", + "type": "String" + }, + { + "name": "extension", + "description": "parse the table by comma-separated values \"csv\", semicolon-separated\nvalues \"ssv\", or tab-separated values \"tsv\"", + "optional": 1, + "type": "String" + }, + { + "name": "header", + "description": "\"header\" to indicate table has header row", + "optional": 1, + "type": "String" + }, + { + "name": "callback", + "description": "function to be executed after\nloadTable() completes. On success, the\nTable object is passed in as the\nfirst argument.", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "description": "function to be executed if\nthere is an error, response is passed\nin as first argument", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "Table object containing data", + "type": "Object" + } + } + ], + "return": { + "description": "Table object containing data", + "type": "Object" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "loadXML", + "file": "src/io/files.js", + "line": 629, + "itemtype": "method", + "description": "

Reads the contents of a file and creates an XML object with its values.\nIf the name of the file is used as the parameter, as in the above example,\nthe file must be located in the sketch directory/folder.

\n

Alternatively, the file maybe be loaded from anywhere on the local\ncomputer using an absolute path (something that starts with / on Unix and\nLinux, or a drive letter on Windows), or the filename parameter can be a\nURL for a file found on a network.

\n

This method is asynchronous, meaning it may not finish before the next\nline in your sketch is executed. Calling loadXML() inside preload()\nguarantees to complete the operation before setup() and draw() are called.

\n

Outside of preload(), you may supply a callback function to handle the\nobject.

\n

This method is suitable for fetching files up to size of 64MB.

\n", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let children = xml.getChildren('animal');\n\n for (let i = 0; i < children.length; i++) {\n let id = children[i].getNum('id');\n let coloring = children[i].getString('species');\n let name = children[i].getContent();\n print(id + ', ' + coloring + ', ' + name);\n }\n describe('no image displayed');\n}\n\n// Sketch prints:\n// 0, Capra hircus, Goat\n// 1, Panthera pardus, Leopard\n// 2, Equus zebra, Zebra\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "filename", + "description": "name of the file or URL to load", + "type": "String" + }, + { + "name": "callback", + "description": "function to be executed after loadXML()\ncompletes, XML object is passed in as\nfirst argument", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "description": "function to be executed if\nthere is an error, response is passed\nin as first argument", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "XML object containing data", + "type": "Object" + } + } + ], + "return": { + "description": "XML object containing data", + "type": "Object" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "loadBytes", + "file": "src/io/files.js", + "line": 700, + "itemtype": "method", + "description": "This method is suitable for fetching files up to size of 64MB.", + "example": [ + "
\nlet data;\n\nfunction preload() {\n data = loadBytes('assets/mammals.xml');\n}\n\nfunction setup() {\n for (let i = 0; i < 5; i++) {\n console.log(data.bytes[i].toString(16));\n }\n describe('no image displayed');\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "file", + "description": "name of the file or URL to load", + "type": "string" + }, + { + "name": "callback", + "description": "function to be executed after loadBytes()\ncompletes", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "description": "function to be executed if there\nis an error", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "an object whose 'bytes' property will be the loaded buffer", + "type": "Object" + } + } + ], + "return": { + "description": "an object whose 'bytes' property will be the loaded buffer", + "type": "Object" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "httpGet", + "file": "src/io/files.js", + "line": 800, + "itemtype": "method", + "description": "Method for executing an HTTP GET request. If data type is not specified,\np5 will try to guess based on the URL, defaulting to text. This is equivalent to\ncalling httpDo(path, 'GET'). The 'binary' datatype will return\na Blob object, and the 'arrayBuffer' datatype will return an ArrayBuffer\nwhich can be used to initialize typed arrays (such as Uint8Array).", + "example": [ + "
\n// Examples use USGS Earthquake API:\n// https://earthquake.usgs.gov/fdsnws/event/1/#methods\nlet earthquakes;\nfunction preload() {\n // Get the most recent earthquake in the database\n let url =\n'https://earthquake.usgs.gov/fdsnws/event/1/query?' +\n 'format=geojson&limit=1&orderby=time';\n httpGet(url, 'json', function(response) {\n // when the HTTP request completes, populate the variable that holds the\n // earthquake data used in the visualization.\n earthquakes = response;\n });\n}\n\nfunction draw() {\n if (!earthquakes) {\n // Wait until the earthquake data has loaded before drawing.\n return;\n }\n background(200);\n // Get the magnitude and name of the earthquake out of the loaded JSON\n let earthquakeMag = earthquakes.features[0].properties.mag;\n let earthquakeName = earthquakes.features[0].properties.place;\n ellipse(width / 2, height / 2, earthquakeMag * 10, earthquakeMag * 10);\n textAlign(CENTER);\n text(earthquakeName, 0, height - 30, width, 30);\n noLoop();\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "name of the file or url to load", + "type": "String" + }, + { + "name": "datatype", + "description": "\"json\", \"jsonp\", \"binary\", \"arrayBuffer\",\n\"xml\", or \"text\"", + "optional": 1, + "type": "String" + }, + { + "name": "data", + "description": "param data passed sent with request", + "optional": 1, + "type": "Object|Boolean" + }, + { + "name": "callback", + "description": "function to be executed after\nhttpGet() completes, data is passed in\nas first argument", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "description": "function to be executed if\nthere is an error, response is passed\nin as first argument", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "A promise that resolves with the data when the operation\ncompletes successfully or rejects with the error after\none occurs.", + "type": "Promise" + } + }, + { + "params": [ + { + "name": "path", + "type": "String" + }, + { + "name": "data", + "type": "Object|Boolean" + }, + { + "name": "callback", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "", + "type": "Promise" + } + }, + { + "params": [ + { + "name": "path", + "type": "String" + }, + { + "name": "callback", + "type": "function" + }, + { + "name": "errorCallback", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "", + "type": "Promise" + } + } + ], + "return": { + "description": "A promise that resolves with the data when the operation\ncompletes successfully or rejects with the error after\none occurs.", + "type": "Promise" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "httpPost", + "file": "src/io/files.js", + "line": 890, + "itemtype": "method", + "description": "Method for executing an HTTP POST request. If data type is not specified,\np5 will try to guess based on the URL, defaulting to text. This is equivalent to\ncalling httpDo(path, 'POST').", + "example": [ + "
\n\n// Examples use jsonplaceholder.typicode.com for a Mock Data API\n\nlet url = 'https://jsonplaceholder.typicode.com/posts';\nlet postData = { userId: 1, title: 'p5 Clicked!', body: 'p5.js is very cool.' };\n\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n}\n\nfunction mousePressed() {\n httpPost(url, 'json', postData, function(result) {\n strokeWeight(2);\n text(result.body, mouseX, mouseY);\n });\n}\n\n
\n\n
\nlet url = 'ttps://invalidURL'; // A bad URL that will cause errors\nlet postData = { title: 'p5 Clicked!', body: 'p5.js is very cool.' };\n\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n}\n\nfunction mousePressed() {\n httpPost(\n url,\n 'json',\n postData,\n function(result) {\n // ... won't be called\n },\n function(error) {\n strokeWeight(2);\n text(error.toString(), mouseX, mouseY);\n }\n );\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "name of the file or url to load", + "type": "String" + }, + { + "name": "datatype", + "description": "\"json\", \"jsonp\", \"xml\", or \"text\".\nIf omitted, httpPost() will guess.", + "optional": 1, + "type": "String" + }, + { + "name": "data", + "description": "param data passed sent with request", + "optional": 1, + "type": "Object|Boolean" + }, + { + "name": "callback", + "description": "function to be executed after\nhttpPost() completes, data is passed in\nas first argument", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "description": "function to be executed if\nthere is an error, response is passed\nin as first argument", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "A promise that resolves with the data when the operation\ncompletes successfully or rejects with the error after\none occurs.", + "type": "Promise" + } + }, + { + "params": [ + { + "name": "path", + "type": "String" + }, + { + "name": "data", + "type": "Object|Boolean" + }, + { + "name": "callback", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "", + "type": "Promise" + } + }, + { + "params": [ + { + "name": "path", + "type": "String" + }, + { + "name": "callback", + "type": "function" + }, + { + "name": "errorCallback", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "", + "type": "Promise" + } + } + ], + "return": { + "description": "A promise that resolves with the data when the operation\ncompletes successfully or rejects with the error after\none occurs.", + "type": "Promise" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "httpDo", + "file": "src/io/files.js", + "line": 979, + "itemtype": "method", + "description": "Method for executing an HTTP request. If data type is not specified,\np5 will try to guess based on the URL, defaulting to text.

\nFor more advanced use, you may also pass in the path as the first argument\nand a object as the second argument, the signature follows the one specified\nin the Fetch API specification.\nThis method is suitable for fetching files up to size of 64MB when \"GET\" is used.", + "example": [ + "
\n\n// Examples use USGS Earthquake API:\n// https://earthquake.usgs.gov/fdsnws/event/1/#methods\n\n// displays an animation of all USGS earthquakes\nlet earthquakes;\nlet eqFeatureIndex = 0;\n\nfunction preload() {\n let url = 'https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson';\n httpDo(\n url,\n {\n method: 'GET',\n // Other Request options, like special headers for apis\n headers: { authorization: 'Bearer secretKey' }\n },\n function(res) {\n earthquakes = res;\n }\n );\n}\n\nfunction draw() {\n // wait until the data is loaded\n if (!earthquakes || !earthquakes.features[eqFeatureIndex]) {\n return;\n }\n clear();\n\n let feature = earthquakes.features[eqFeatureIndex];\n let mag = feature.properties.mag;\n let rad = mag / 11 * ((width + height) / 2);\n fill(255, 0, 0, 100);\n ellipse(width / 2 + random(-2, 2), height / 2 + random(-2, 2), rad, rad);\n\n if (eqFeatureIndex >= earthquakes.features.length) {\n eqFeatureIndex = 0;\n } else {\n eqFeatureIndex += 1;\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "name of the file or url to load", + "type": "String" + }, + { + "name": "method", + "description": "either \"GET\", \"POST\", or \"PUT\",\ndefaults to \"GET\"", + "optional": 1, + "type": "String" + }, + { + "name": "datatype", + "description": "\"json\", \"jsonp\", \"xml\", or \"text\"", + "optional": 1, + "type": "String" + }, + { + "name": "data", + "description": "param data passed sent with request", + "optional": 1, + "type": "Object" + }, + { + "name": "callback", + "description": "function to be executed after\nhttpGet() completes, data is passed in\nas first argument", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "description": "function to be executed if\nthere is an error, response is passed\nin as first argument", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "A promise that resolves with the data when the operation\ncompletes successfully or rejects with the error after\none occurs.", + "type": "Promise" + } + }, + { + "params": [ + { + "name": "path", + "type": "String" + }, + { + "name": "options", + "description": "Request object options as documented in the\n\"fetch\" API\nreference", + "type": "Object" + }, + { + "name": "callback", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "", + "type": "Promise" + } + } + ], + "return": { + "description": "A promise that resolves with the data when the operation\ncompletes successfully or rejects with the error after\none occurs.", + "type": "Promise" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "createWriter", + "file": "src/io/files.js", + "line": 1140, + "itemtype": "method", + "description": "", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n text('click here to save', 10, 10, 70, 80);\n}\n\nfunction mousePressed() {\n if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {\n const writer = createWriter('squares.txt');\n for (let i = 0; i < 10; i++) {\n writer.print(i * i);\n }\n writer.close();\n writer.clear();\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "name", + "description": "name of the file to be created", + "type": "String" + }, + { + "name": "extension", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "p5.PrintWriter" + } + } + ], + "return": { + "description": "", + "type": "p5.PrintWriter" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "write", + "file": "src/io/files.js", + "line": 1224, + "itemtype": "method", + "description": "Writes data to the PrintWriter stream", + "example": [ + "
\n\n// creates a file called 'newFile.txt'\nlet writer = createWriter('newFile.txt');\n// write 'Hello world!'' to the file\nwriter.write(['Hello world!']);\n// close the PrintWriter and save the file\nwriter.close();\n\n
\n
\n\n// creates a file called 'newFile2.txt'\nlet writer = createWriter('newFile2.txt');\n// write 'apples,bananas,123' to the file\nwriter.write(['apples', 'bananas', 123]);\n// close the PrintWriter and save the file\nwriter.close();\n\n
\n
\n\n// creates a file called 'newFile3.txt'\nlet writer = createWriter('newFile3.txt');\n// write 'My name is: Teddy' to the file\nwriter.write('My name is:');\nwriter.write(' Teddy');\n// close the PrintWriter and save the file\nwriter.close();\n\n
\n
\n\nfunction setup() {\n createCanvas(100, 100);\n button = createButton('SAVE FILE');\n button.position(21, 40);\n button.mousePressed(createFile);\n}\n\nfunction createFile() {\n // creates a file called 'newFile.txt'\n let writer = createWriter('newFile.txt');\n // write 'Hello world!'' to the file\n writer.write(['Hello world!']);\n // close the PrintWriter and save the file\n writer.close();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "data", + "description": "all data to be written by the PrintWriter", + "type": "Array" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "close", + "file": "src/io/files.js", + "line": 1325, + "itemtype": "method", + "description": "Closes the PrintWriter", + "example": [ + "
\n\n// create a file called 'newFile.txt'\nlet writer = createWriter('newFile.txt');\n// close the PrintWriter and save the file\nwriter.close();\n\n
\n
\n\n// create a file called 'newFile2.txt'\nlet writer = createWriter('newFile2.txt');\n// write some data to the file\nwriter.write([100, 101, 102]);\n// close the PrintWriter and save the file\nwriter.close();\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "save", + "file": "src/io/files.js", + "line": 1448, + "itemtype": "method", + "description": "Saves a given element(image, text, json, csv, wav, or html) to the client's\ncomputer. The first parameter can be a pointer to element we want to save.\nThe element can be one of p5.Element,an Array of\nStrings, an Array of JSON, a JSON object, a p5.Table\n, a p5.Image, or a p5.SoundFile (requires\np5.sound). The second parameter is a filename (including extension).The\nthird parameter is for options specific to this type of object. This method\nwill save a file that fits the given parameters.\nIf it is called without specifying an element, by default it will save the\nwhole canvas as an image file. You can optionally specify a filename as\nthe first parameter in such a case.\nNote that it is not recommended to\ncall this method within draw, as it will open a new save dialog on every\nrender.", + "example": [ + "
\n// Saves the canvas as an image\ncnv = createCanvas(300, 300);\nsave(cnv, 'myCanvas.jpg');\n\n// Saves the canvas as an image by default\nsave('myCanvas.jpg');\ndescribe('An example for saving a canvas as an image.');\n
\n\n
\n// Saves p5.Image as an image\nimg = createImage(10, 10);\nsave(img, 'myImage.png');\ndescribe('An example for saving a p5.Image element as an image.');\n
\n\n
\n// Saves p5.Renderer object as an image\nobj = createGraphics(100, 100);\nsave(obj, 'myObject.png');\ndescribe('An example for saving a p5.Renderer element.');\n
\n\n
\nlet myTable = new p5.Table();\n// Saves table as html file\nsave(myTable, 'myTable.html');\n\n// Comma Separated Values\nsave(myTable, 'myTable.csv');\n\n// Tab Separated Values\nsave(myTable, 'myTable.tsv');\n\ndescribe(`An example showing how to save a table in formats of\n HTML, CSV and TSV.`);\n
\n\n
\nlet myJSON = { a: 1, b: true };\n\n// Saves pretty JSON\nsave(myJSON, 'my.json');\n\n// Optimizes JSON filesize\nsave(myJSON, 'my.json', true);\n\ndescribe('An example for saving JSON to a txt file with some extra arguments.');\n
\n\n
\n// Saves array of strings to text file with line breaks after each item\nlet arrayOfStrings = ['a', 'b'];\nsave(arrayOfStrings, 'my.txt');\ndescribe(`An example for saving an array of strings to text file\n with line breaks.`);\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "objectOrFilename", + "description": "If filename is provided, will\nsave canvas as an image with\neither png or jpg extension\ndepending on the filename.\nIf object is provided, will\nsave depending on the object\nand filename (see examples\nabove).", + "optional": 1, + "type": "Object|String" + }, + { + "name": "filename", + "description": "If an object is provided as the first\nparameter, then the second parameter\nindicates the filename,\nand should include an appropriate\nfile extension (see examples above).", + "optional": 1, + "type": "String" + }, + { + "name": "options", + "description": "Additional options depend on\nfiletype. For example, when saving JSON,\ntrue indicates that the\noutput will be optimized for filesize,\nrather than readability.", + "optional": 1, + "type": "Boolean|String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "saveJSON", + "file": "src/io/files.js", + "line": 1536, + "itemtype": "method", + "description": "Writes the contents of an Array or a JSON object to a .json file.\nThe file saving process and location of the saved file will\nvary between web browsers.", + "example": [ + "
\nlet json = {}; // new JSON Object\n\njson.id = 0;\njson.species = 'Panthera leo';\njson.name = 'Lion';\n\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n text('click here to save', 10, 10, 70, 80);\n describe('no image displayed');\n}\n\nfunction mousePressed() {\n if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {\n saveJSON(json, 'lion.json');\n }\n}\n\n// saves the following to a file called \"lion.json\":\n// {\n// \"id\": 0,\n// \"species\": \"Panthera leo\",\n// \"name\": \"Lion\"\n// }\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "json", + "type": "Array|Object" + }, + { + "name": "filename", + "type": "String" + }, + { + "name": "optimize", + "description": "If true, removes line breaks\nand spaces from the output\nfile to optimize filesize\n(but not readability).", + "optional": 1, + "type": "Boolean" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "saveStrings", + "file": "src/io/files.js", + "line": 1588, + "itemtype": "method", + "description": "Writes an array of Strings to a text file, one line per String.\nThe file saving process and location of the saved file will\nvary between web browsers.", + "example": [ + "
\nlet words = 'apple bear cat dog';\n\n// .split() outputs an Array\nlet list = split(words, ' ');\n\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n text('click here to save', 10, 10, 70, 80);\n describe('no image displayed');\n}\n\nfunction mousePressed() {\n if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {\n saveStrings(list, 'nouns.txt');\n }\n}\n\n// Saves the following to a file called 'nouns.txt':\n//\n// apple\n// bear\n// cat\n// dog\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "list", + "description": "string array to be written", + "type": "String[]" + }, + { + "name": "filename", + "description": "filename for output", + "type": "String" + }, + { + "name": "extension", + "description": "the filename's extension", + "optional": 1, + "type": "String" + }, + { + "name": "isCRLF", + "description": "if true, change line-break to CRLF", + "optional": 1, + "type": "Boolean" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "saveTable", + "file": "src/io/files.js", + "line": 1650, + "itemtype": "method", + "description": "Writes the contents of a Table object to a file. Defaults to a\ntext file with comma-separated-values ('csv') but can also\nuse tab separation ('tsv'), or generate an HTML table ('html').\nThe file saving process and location of the saved file will\nvary between web browsers.", + "example": [ + "
\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('id');\n table.addColumn('species');\n table.addColumn('name');\n\n let newRow = table.addRow();\n newRow.setNum('id', table.getRowCount() - 1);\n newRow.setString('species', 'Panthera leo');\n newRow.setString('name', 'Lion');\n\n // To save, un-comment next line then click 'run'\n // saveTable(table, 'new.csv');\n\n describe('no image displayed');\n}\n\n// Saves the following to a file called 'new.csv':\n// id,species,name\n// 0,Panthera leo,Lion\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "Table", + "description": "the Table object to save to a file", + "type": "p5.Table" + }, + { + "name": "filename", + "description": "the filename to which the Table should be saved", + "type": "String" + }, + { + "name": "options", + "description": "can be one of \"tsv\", \"csv\", or \"html\"", + "optional": 1, + "type": "String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "abs", + "file": "src/math/calculation.js", + "line": 36, + "itemtype": "method", + "description": "Calculates the absolute value of a number. A number's absolute value is its\ndistance from zero on the number line. -5 and 5 are both five units away\nfrom zero, so calling abs(-5) and abs(5) both return 5. The absolute\nvalue of a number is always positive.", + "example": [ + "
\n\nfunction draw() {\n // Invert the y-axis.\n scale(1, -1);\n translate(0, -height);\n\n let centerX = width / 2;\n let x = frameCount;\n let y = abs(x - centerX);\n point(x, y);\n\n describe('A series of black dots that form a \"V\" shape.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "number to compute.", + "type": "Number" + } + ], + "return": { + "description": "absolute value of given number.", + "type": "Number" + } + } + ], + "return": { + "description": "absolute value of given number.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "ceil", + "file": "src/math/calculation.js", + "line": 66, + "itemtype": "method", + "description": "Calculates the closest integer value that is greater than or equal to the\nparameter's value. For example, calling ceil(9.03) returns the value\n10.", + "example": [ + "
\n\n// Set the range for RGB values from 0 to 1.\ncolorMode(RGB, 1);\nnoStroke();\n\nlet r = 0.3;\nfill(r, 0, 0);\nrect(0, 0, width / 2, height);\n\n// Round r up to 1.\nr = ceil(r);\nfill(r, 0, 0);\nrect(width / 2, 0, width / 2, height);\n\ndescribe('Two rectangles. The one on the left is dark red and the one on the right is bright red.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "number to round up.", + "type": "Number" + } + ], + "return": { + "description": "rounded up number.", + "type": "Integer" + } + } + ], + "return": { + "description": "rounded up number.", + "type": "Integer" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "constrain", + "file": "src/math/calculation.js", + "line": 118, + "itemtype": "method", + "description": "Constrains a number between a minimum and maximum value.", + "example": [ + "
\n\nfunction draw() {\n background(200);\n\n let x = constrain(mouseX, 33, 67);\n let y = 50;\n\n strokeWeight(5);\n point(x, y);\n\n describe('A black dot drawn on a gray square follows the mouse from left to right. Its movement is constrained to the middle third of the square.');\n}\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n // Set boundaries and draw them.\n let leftWall = width * 0.25;\n let rightWall = width * 0.75;\n line(leftWall, 0, leftWall, height);\n line(rightWall, 0, rightWall, height);\n\n // Draw a circle that follows the mouse freely.\n fill(255);\n circle(mouseX, height / 3, 9);\n\n // Draw a circle that's constrained.\n let xc = constrain(mouseX, leftWall, rightWall);\n fill(0);\n circle(xc, 2 * height / 3, 9);\n\n describe('Two vertical lines. Two circles move horizontally with the mouse. One circle stops at the vertical lines.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "number to constrain.", + "type": "Number" + }, + { + "name": "low", + "description": "minimum limit.", + "type": "Number" + }, + { + "name": "high", + "description": "maximum limit.", + "type": "Number" + } + ], + "return": { + "description": "constrained number.", + "type": "Number" + } + } + ], + "return": { + "description": "constrained number.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "dist", + "file": "src/math/calculation.js", + "line": 172, + "itemtype": "method", + "description": "

Calculates the distance between two points.

\n

The version of dist() with four parameters calculates distance in two\ndimensions.

\n

The version of dist() with six parameters calculates distance in three\ndimensions.

\n

Use p5.Vector.dist() to calculate the\ndistance between two p5.Vector objects.

\n", + "example": [ + "
\n\nlet x1 = 10;\nlet y1 = 50;\nlet x2 = 90;\nlet y2 = 50;\n\nline(x1, y1, x2, y2);\nstrokeWeight(5);\npoint(x1, y1);\npoint(x2, y2);\n\nlet d = dist(x1, y1, x2, y2);\ntext(d, 43, 40);\n\ndescribe('Two dots connected by a horizontal line. The number 80 is written above the center of the line.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x1", + "description": "x-coordinate of the first point.", + "type": "Number" + }, + { + "name": "y1", + "description": "y-coordinate of the first point.", + "type": "Number" + }, + { + "name": "x2", + "description": "x-coordinate of the second point.", + "type": "Number" + }, + { + "name": "y2", + "description": "y-coordinate of the second point.", + "type": "Number" + } + ], + "return": { + "description": "distance between the two points.", + "type": "Number" + } + }, + { + "params": [ + { + "name": "x1", + "type": "Number" + }, + { + "name": "y1", + "type": "Number" + }, + { + "name": "z1", + "description": "z-coordinate of the first point.", + "type": "Number" + }, + { + "name": "x2", + "type": "Number" + }, + { + "name": "y2", + "type": "Number" + }, + { + "name": "z2", + "description": "z-coordinate of the second point.", + "type": "Number" + } + ], + "return": { + "description": "distance between the two points.", + "type": "Number" + } + } + ], + "return": { + "description": "distance between the two points.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "exp", + "file": "src/math/calculation.js", + "line": 209, + "itemtype": "method", + "description": "Returns Euler's number e (2.71828...) raised to the power of the n\nparameter.", + "example": [ + "
\n\nfunction draw() {\n // Invert the y-axis.\n scale(1, -1);\n translate(0, -height);\n\n let x = frameCount;\n let y = 0.005 * exp(x * 0.1);\n point(x, y);\n\n describe('A series of black dots that grow exponentially from left to right.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "exponent to raise.", + "type": "Number" + } + ], + "return": { + "description": "e^n", + "type": "Number" + } + } + ], + "return": { + "description": "e^n", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "floor", + "file": "src/math/calculation.js", + "line": 238, + "itemtype": "method", + "description": "Calculates the closest integer value that is less than or equal to the\nvalue of the n parameter.", + "example": [ + "
\n\n// Set the range for RGB values from 0 to 1.\ncolorMode(RGB, 1);\nnoStroke();\n\nlet r = 0.8;\nfill(r, 0, 0);\nrect(0, 0, width / 2, height);\n\n// Round r down to 0.\nr = floor(r);\nfill(r, 0, 0);\nrect(width / 2, 0, width / 2, height);\n\ndescribe('Two rectangles. The one on the left is bright red and the one on the right is black.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "number to round down.", + "type": "Number" + } + ], + "return": { + "description": "rounded down number.", + "type": "Integer" + } + } + ], + "return": { + "description": "rounded down number.", + "type": "Integer" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "lerp", + "file": "src/math/calculation.js", + "line": 285, + "itemtype": "method", + "description": "

Calculates a number between two numbers at a specific increment. The amt\nparameter is the amount to interpolate between the two numbers. 0.0 is\nequal to the first number, 0.1 is very near the first number, 0.5 is\nhalf-way in between, and 1.0 is equal to the second number. The lerp()\nfunction is convenient for creating motion along a straight path and for\ndrawing dotted lines.

\n

If the value of amt is less than 0 or more than 1, lerp() will return a\nnumber outside of the original interval. For example, calling\nlerp(0, 10, 1.5) will return 15.

\n", + "example": [ + "
\n\nlet a = 20;\nlet b = 80;\nlet c = lerp(a, b, 0.2);\nlet d = lerp(a, b, 0.5);\nlet e = lerp(a, b, 0.8);\n\nlet y = 50;\n\nstrokeWeight(5);\n\n// Draw the original points in black.\nstroke(0);\npoint(a, y);\npoint(b, y);\n\n// Draw the lerped points in gray.\nstroke(100);\npoint(c, y);\npoint(d, y);\npoint(e, y);\n\ndescribe('Five points in a horizontal line. The outer points are black and the inner points are gray.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "start", + "description": "first value.", + "type": "Number" + }, + { + "name": "stop", + "description": "second value.", + "type": "Number" + }, + { + "name": "amt", + "description": "number.", + "type": "Number" + } + ], + "return": { + "description": "lerped value.", + "type": "Number" + } + } + ], + "return": { + "description": "lerped value.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "mag", + "file": "src/math/calculation.js", + "line": 343, + "itemtype": "method", + "description": "

Calculates the magnitude, or length, of a vector. A vector is like an arrow\npointing in space. Vectors are commonly used for programming motion.

\n

Vectors don't have a \"start\" position because the same arrow can be drawn\nanywhere. A vector's magnitude can be thought of as the distance from the\norigin (0, 0) to its tip at (x, y). mag(x, y) is a shortcut for calling\ndist(0, 0, x, y).

\n", + "example": [ + "
\n\nlet x = 30;\nlet y = 40;\nlet m = mag(x, y);\n\nline(0, 0, x, y);\ntext(m, x, y);\n\ndescribe('A diagonal line is drawn from the top left of the canvas. The number 50 is written at the end of the line.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "first component.", + "type": "Number" + }, + { + "name": "y", + "description": "second component.", + "type": "Number" + } + ], + "return": { + "description": "magnitude of vector from (0,0) to (x,y).", + "type": "Number" + } + } + ], + "return": { + "description": "magnitude of vector from (0,0) to (x,y).", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "map", + "file": "src/math/calculation.js", + "line": 398, + "itemtype": "method", + "description": "

Re-maps a number from one range to another.

\n

For example, calling map(2, 0, 10, 0, 100) returns 20. The first three\narguments set the original value to 2 and the original range from 0 to 10.\nThe last two arguments set the target range from 0 to 100. 20's position\nin the target range [0, 100] is proportional to 2's position in the\noriginal range [0, 10].

\n", + "example": [ + "
\n\nlet n = map(7, 0, 10, 0, 100);\ntext(n, 50, 50);\n\ndescribe('The number 70 written in the middle of a gray square.');\n\n
\n\n
\n\nlet x = map(2, 0, 10, 0, width);\ncircle(x, 50, 10);\n\ndescribe('A white circle drawn on the left side of a gray square.');\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let c = map(mouseX, 0, width, 0, 255);\n fill(c);\n circle(50, 50, 20);\n\n describe('A circle changes color from black to white as the mouse moves from left to right.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "the incoming value to be converted.", + "type": "Number" + }, + { + "name": "start1", + "description": "lower bound of the value's current range.", + "type": "Number" + }, + { + "name": "stop1", + "description": "upper bound of the value's current range.", + "type": "Number" + }, + { + "name": "start2", + "description": "lower bound of the value's target range.", + "type": "Number" + }, + { + "name": "stop2", + "description": "upper bound of the value's target range.", + "type": "Number" + }, + { + "name": "withinBounds", + "description": "constrain the value to the newly mapped range.", + "optional": 1, + "type": "Boolean" + } + ], + "return": { + "description": "remapped number.", + "type": "Number" + } + } + ], + "return": { + "description": "remapped number.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "max", + "file": "src/math/calculation.js", + "line": 473, + "itemtype": "method", + "description": "

Returns the largest value in a sequence of numbers.

\n

The version of max() with one parameter interprets it as an array of\nnumbers and returns the largest number.

\n

The version of max() with two or more parameters interprets them as\nindividual numbers and returns the largest number.

\n", + "example": [ + "
\n\nlet m = max(10, 20);\ntext(m, 50, 50);\n\ndescribe('The number 20 written in the middle of a gray square.');\n\n
\n\n
\n\nlet m = max([10, 20]);\ntext(m, 50, 50);\n\ndescribe('The number 20 written in the middle of a gray square.');\n\n
\n\n
\n\nlet numbers = [2, 1, 5, 4, 8, 9];\n\n// Draw all of the numbers in the array.\nnoStroke();\nlet spacing = 15;\nnumbers.forEach((n, index) => {\n let x = index * spacing;\n let y = 25;\n text(n, x, y);\n});\n\n// Draw the maximum value in the array.\nlet m = max(numbers);\nlet maxX = 33;\nlet maxY = 80;\n\ntextSize(32);\ntext(m, maxX, maxY);\n\ndescribe('The numbers 2 1 5 4 8 9 are written in small text at the top of a gray square. The number 9 is written in large text at the center of the square.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n0", + "description": "first number to compare.", + "type": "Number" + }, + { + "name": "n1", + "description": "second number to compare.", + "type": "Number" + } + ], + "return": { + "description": "maximum number.", + "type": "Number" + } + }, + { + "params": [ + { + "name": "nums", + "description": "numbers to compare.", + "type": "Number[]" + } + ], + "return": { + "description": "", + "type": "Number" + } + } + ], + "return": { + "description": "maximum number.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "min", + "file": "src/math/calculation.js", + "line": 551, + "itemtype": "method", + "description": "

Returns the smallest value in a sequence of numbers.

\n

The version of min() with one parameter interprets it as an array of\nnumbers and returns the smallest number.

\n

The version of min() with two or more parameters interprets them as\nindividual numbers and returns the smallest number.

\n", + "example": [ + "
\n\nlet m = min(10, 20);\ntext(m, 50, 50);\n\ndescribe('The number 10 written in the middle of a gray square.');\n\n
\n\n
\n\nlet m = min([10, 20]);\ntext(m, 50, 50);\n\ndescribe('The number 10 written in the middle of a gray square.');\n\n
\n\n
\n\nlet numbers = [2, 1, 5, 4, 8, 9];\n\n// Draw all of the numbers in the array.\nnoStroke();\nlet spacing = 15;\nnumbers.forEach((n, index) => {\n let x = index * spacing;\n let y = 25;\n text(n, x, y);\n});\n\n// Draw the minimum value in the array.\nlet m = min(numbers);\nlet minX = 33;\nlet minY = 80;\n\ntextSize(32);\ntext(m, minX, minY);\n\ndescribe('The numbers 2 1 5 4 8 9 are written in small text at the top of a gray square. The number 1 is written in large text at the center of the square.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n0", + "description": "first number to compare.", + "type": "Number" + }, + { + "name": "n1", + "description": "second number to compare.", + "type": "Number" + } + ], + "return": { + "description": "minimum number.", + "type": "Number" + } + }, + { + "params": [ + { + "name": "nums", + "description": "numbers to compare.", + "type": "Number[]" + } + ], + "return": { + "description": "", + "type": "Number" + } + } + ], + "return": { + "description": "minimum number.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "norm", + "file": "src/math/calculation.js", + "line": 597, + "itemtype": "method", + "description": "

Maps a number from one range to a value between 0 and 1.

\n

For example, norm(2, 0, 10) returns 0.2. 2's position in the original\nrange [0, 10] is proportional to 0.2's position in the range [0, 1]. This\nis equivalent to calling map(2, 0, 10, 0, 1).

\n

Numbers outside of the original range are not constrained between 0 and 1.\nOut-of-range values are often intentional and useful.

\n", + "example": [ + "
\n\nfunction draw() {\n // Set the range for RGB values from 0 to 1.\n colorMode(RGB, 1);\n\n let r = norm(mouseX, 0, width);\n background(r, 0, 0);\n\n describe('A square changes color from black to red as the mouse moves from left to right.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "incoming value to be normalized.", + "type": "Number" + }, + { + "name": "start", + "description": "lower bound of the value's current range.", + "type": "Number" + }, + { + "name": "stop", + "description": "upper bound of the value's current range.", + "type": "Number" + } + ], + "return": { + "description": "normalized number.", + "type": "Number" + } + } + ], + "return": { + "description": "normalized number.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "pow", + "file": "src/math/calculation.js", + "line": 634, + "itemtype": "method", + "description": "

Calculates exponential expressions such as 2^3.

\n

For example, pow(2, 3) is equivalent to the expression\n2 × 2 × 2. pow(2, -3) is equivalent to 1 ÷\n(2 × 2 × 2).

\n", + "example": [ + "
\n\nlet base = 3;\n\nlet d = pow(base, 1);\ncircle(10, 10, d);\n\nd = pow(base, 2);\ncircle(20, 20, d);\n\nd = pow(base, 3);\ncircle(40, 40, d);\n\nd = pow(base, 4);\ncircle(80, 80, d);\n\ndescribe('A series of circles that grow exponentially from top left to bottom right.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "base of the exponential expression.", + "type": "Number" + }, + { + "name": "e", + "description": "power by which to raise the base.", + "type": "Number" + } + ], + "return": { + "description": "n^e.", + "type": "Number" + } + } + ], + "return": { + "description": "n^e.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "round", + "file": "src/math/calculation.js", + "line": 663, + "itemtype": "method", + "description": "Calculates the integer closest to the n parameter. For example,\nround(133.8) returns the value 134.", + "example": [ + "
\n\nlet x = round(3.7);\ntext(x, width / 2, height / 2);\n\ndescribe('The number 4 written in middle of canvas.');\n\n
\n\n
\n\nlet x = round(12.782383, 2);\ntext(x, width / 2, height / 2);\n\ndescribe('The number 12.78 written in middle of canvas.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "number to round.", + "type": "Number" + }, + { + "name": "decimals", + "description": "number of decimal places to round to, default is 0.", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "rounded number.", + "type": "Integer" + } + } + ], + "return": { + "description": "rounded number.", + "type": "Integer" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "sq", + "file": "src/math/calculation.js", + "line": 699, + "itemtype": "method", + "description": "

Squares a number, which means multiplying the number by itself. The value\nreturned is always a positive number.

\n

For example, sq(3) evaluates 3 × 3 which is 9. sq(-3) evaluates\n-3 × -3 which is also 9. Multiplying two negative numbers produces\na positive number.

\n", + "example": [ + "
\n\nfunction draw() {\n // Invert the y-axis.\n scale(1, -1);\n translate(0, -height);\n\n let x = frameCount;\n let y = 0.01 * sq(x);\n point(x, y);\n\n describe('A series of black dots that get higher quickly from left to right.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "number to square.", + "type": "Number" + } + ], + "return": { + "description": "squared number.", + "type": "Number" + } + } + ], + "return": { + "description": "squared number.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "sqrt", + "file": "src/math/calculation.js", + "line": 729, + "itemtype": "method", + "description": "

Calculates the square root of a number. A number's square root can be\nmultiplied by itself to produce the original number.

\n

For example, sqrt(9) returns 3 because 3 × 3 = 9. sqrt() always\nreturns a positive value. sqrt() doesn't work with negative arguments\nsuch as sqrt(-9).

\n", + "example": [ + "
\n\nfunction draw() {\n // Invert the y-axis.\n scale(1, -1);\n translate(0, -height);\n\n let x = frameCount;\n let y = 5 * sqrt(x);\n point(x, y);\n\n describe('A series of black dots that get higher slowly from left to right.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "non-negative number to square root.", + "type": "Number" + } + ], + "return": { + "description": "square root of number.", + "type": "Number" + } + } + ], + "return": { + "description": "square root of number.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "fract", + "file": "src/math/calculation.js", + "line": 750, + "itemtype": "method", + "description": "Calculates the fractional part of a number. For example,\nfract(12.34) returns 0.34.", + "example": [ + "
\n\nlet n = 56.78;\ntext(n, 20, 33);\nlet f = fract(n);\ntext(f, 20, 66);\n\ndescribe('The number 56.78 written above the number 0.78.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "number whose fractional part will be found.", + "type": "Number" + } + ], + "return": { + "description": "fractional part of n.", + "type": "Number" + } + } + ], + "return": { + "description": "fractional part of n.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Calculation" + }, + { + "name": "createVector", + "file": "src/math/math.js", + "line": 74, + "itemtype": "method", + "description": "

Creates a new p5.Vector object. A vector is like\nan arrow pointing in space. Vectors have both magnitude (length)\nand direction. Calling createVector() without arguments sets the new\nvector's components to 0.

\n

p5.Vector objects are often used to program\nmotion because they simplify the math. For example, a moving ball has a\nposition and a velocity. Position describes where the ball is in space. The\nball's position vector extends from the origin to the ball's center.\nVelocity describes the ball's speed and the direction it's moving. If the\nball is moving straight up, its velocity vector points straight up. Adding\nthe ball's velocity vector to its position vector moves it, as in\npos.add(vel). Vector math relies on methods inside the\np5.Vector class.

\n", + "example": [ + "
\n\nlet p1 = createVector(25, 25);\nlet p2 = createVector(50, 50);\nlet p3 = createVector(75, 75);\n\nstrokeWeight(5);\npoint(p1);\npoint(p2);\npoint(p3);\n\ndescribe('Three black dots form a diagonal line from top left to bottom right.');\n\n
\n\n
\n\nlet pos;\nlet vel;\n\nfunction setup() {\n createCanvas(100, 100);\n pos = createVector(width / 2, height);\n vel = createVector(0, -1);\n}\n\nfunction draw() {\n background(200);\n\n pos.add(vel);\n\n if (pos.y < 0) {\n pos.y = height;\n }\n\n strokeWeight(5);\n point(pos);\n\n describe('A black dot moves from bottom to top on a gray square. The dot reappears at the bottom when it reaches the top.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x component of the vector.", + "optional": 1, + "type": "Number" + }, + { + "name": "y", + "description": "y component of the vector.", + "optional": 1, + "type": "Number" + }, + { + "name": "z", + "description": "z component of the vector.", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "new p5.Vector object.", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "new p5.Vector object.", + "type": "p5.Vector" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "noise", + "file": "src/math/noise.js", + "line": 198, + "itemtype": "method", + "description": "

Returns random numbers that can be tuned to feel more organic. The values\nreturned will always be between 0 and 1.

\n

Values returned by random() and\nrandomGaussian() can change by large\namounts between function calls. By contrast, values returned by noise()\ncan be made \"smooth\". Calls to noise() with similar inputs will produce\nsimilar outputs. noise() is used to create textures, motion, shapes,\nterrains, and so on. Ken Perlin invented noise() while animating the\noriginal Tron film in the 1980s.

\n

noise() returns the same value for a given input while a sketch is\nrunning. It produces different results each time a sketch runs. The\nnoiseSeed() function can be used to generate\nthe same sequence of Perlin noise values each time a sketch runs.

\n

The character of the noise can be adjusted in two ways. The first way is to\nscale the inputs. noise() interprets inputs as coordinates. The sequence\nof noise values will be smoother when the input coordinates are closer. The\nsecond way is to use the noiseDetail()\nfunction.

\n

The version of noise() with one parameter computes noise values in one\ndimension. This dimension can be thought of as space, as in noise(x), or\ntime, as in noise(t).

\n

The version of noise() with two parameters computes noise values in two\ndimensions. These dimensions can be thought of as space, as in\nnoise(x, y), or space and time, as in noise(x, t).

\n

The version of noise() with three parameters computes noise values in\nthree dimensions. These dimensions can be thought of as space, as in\nnoise(x, y, z), or space and time, as in noise(x, y, t).

\n", + "example": [ + "
\n\nfunction draw() {\n background(200);\n\n let x = 100 * noise(0.005 * frameCount);\n let y = 100 * noise(0.005 * frameCount + 10000);\n\n strokeWeight(5);\n point(x, y);\n\n describe('A black dot moves randomly on a gray square.');\n}\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let noiseLevel = 100;\n let noiseScale = 0.005;\n // Scale input coordinate.\n let nt = noiseScale * frameCount;\n // Compute noise value.\n let x = noiseLevel * noise(nt);\n let y = noiseLevel * noise(nt + 10000);\n // Render.\n strokeWeight(5);\n point(x, y);\n\n describe('A black dot moves randomly on a gray square.');\n}\n\n
\n\n
\n\nfunction draw() {\n let noiseLevel = 100;\n let noiseScale = 0.02;\n // Scale input coordinate.\n let x = frameCount;\n let nx = noiseScale * x;\n // Compute noise value.\n let y = noiseLevel * noise(nx);\n // Render.\n line(x, 0, x, y);\n\n describe('A hilly terrain drawn in gray against a black sky.');\n}\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let noiseLevel = 100;\n let noiseScale = 0.002;\n for (let x = 0; x < width; x += 1) {\n // Scale input coordinates.\n let nx = noiseScale * x;\n let nt = noiseScale * frameCount;\n // Compute noise value.\n let y = noiseLevel * noise(nx, nt);\n // Render.\n line(x, 0, x, y);\n }\n\n describe('A calm sea drawn in gray against a black sky.');\n}\n\n
\n\n
\n\nlet noiseLevel = 255;\nlet noiseScale = 0.01;\nfor (let y = 0; y < height; y += 1) {\n for (let x = 0; x < width; x += 1) {\n // Scale input coordinates.\n let nx = noiseScale * x;\n let ny = noiseScale * y;\n // Compute noise value.\n let c = noiseLevel * noise(nx, ny);\n // Render.\n stroke(c);\n point(x, y);\n }\n}\n\ndescribe('A gray cloudy pattern.');\n\n
\n\n
\n\nfunction draw() {\n let noiseLevel = 255;\n let noiseScale = 0.009;\n for (let y = 0; y < height; y += 1) {\n for (let x = 0; x < width; x += 1) {\n // Scale input coordinates.\n let nx = noiseScale * x;\n let ny = noiseScale * y;\n let nt = noiseScale * frameCount;\n // Compute noise value.\n let c = noiseLevel * noise(nx, ny, nt);\n // Render.\n stroke(c);\n point(x, y);\n }\n }\n\n describe('A gray cloudy pattern that changes.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate in noise space.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate in noise space.", + "optional": 1, + "type": "Number" + }, + { + "name": "z", + "description": "z-coordinate in noise space.", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "Perlin noise value at specified coordinates.", + "type": "Number" + } + } + ], + "return": { + "description": "Perlin noise value at specified coordinates.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Noise" + }, + { + "name": "noiseDetail", + "file": "src/math/noise.js", + "line": 327, + "itemtype": "method", + "description": "

Adjusts the character of the noise produced by the\nnoise() function.

\n

Perlin noise values are created by adding layers of noise together. The\nnoise layers, called octaves, are similar to harmonics in music. Lower\noctaves contribute more to the output signal. They define the overall\nintensity of the noise. Higher octaves create finer-grained details.

\n

By default, noise values are created by combining four octaves. Each higher\noctave contributes half as much (50% less) compared to its predecessor.\nnoiseDetail() changes the number of octaves and the falloff amount. For\nexample, calling noiseDetail(6, 0.25) ensures that\nnoise() will use six octaves. Each higher octave\nwill contribute 25% as much (75% less) compared to its predecessor. Falloff\nvalues between 0 and 1 are valid. However, falloff values greater than 0.5\nmight result in noise values greater than 1.

\n", + "example": [ + "
\n\nlet noiseLevel = 255;\nlet noiseScale = 0.02;\nfor (let y = 0; y < height; y += 1) {\n for (let x = 0; x < width / 2; x += 1) {\n // Scale input coordinates.\n let nx = noiseScale * x;\n let ny = noiseScale * y;\n\n // Compute noise value.\n noiseDetail(6, 0.25);\n let c = noiseLevel * noise(nx, ny);\n // Render left side.\n stroke(c);\n point(x, y);\n\n // Compute noise value.\n noiseDetail(4, 0.5);\n c = noiseLevel * noise(nx, ny);\n // Render right side.\n stroke(c);\n point(x + width / 2, y);\n }\n}\n\ndescribe('Two gray cloudy patterns. The pattern on the right is cloudier than the pattern on the left.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "lod", + "description": "number of octaves to be used by the noise.", + "type": "Number" + }, + { + "name": "falloff", + "description": "falloff factor for each octave.", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Noise" + }, + { + "name": "noiseSeed", + "file": "src/math/noise.js", + "line": 368, + "itemtype": "method", + "description": "Sets the seed value for noise(). By default,\nnoise() produces different results each time\na sketch is run. Calling noiseSeed() with a constant\nargument, such as noiseSeed(99), makes noise()\nproduce the same results each time a sketch is run.", + "example": [ + "
\n\nfunction setup() {\n noiseSeed(99);\n background(255);\n}\n\nfunction draw() {\n let noiseLevel = 100;\n let noiseScale = 0.005;\n // Scale input coordinate.\n let nt = noiseScale * frameCount;\n // Compute noise value.\n let x = noiseLevel * noise(nt);\n // Render.\n line(x, 0, x, height);\n\n describe('A black rectangle that grows randomly, first to the right and then to the left.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "seed", + "description": "seed value.", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Noise" + }, + { + "name": "randomSeed", + "file": "src/math/random.js", + "line": 64, + "itemtype": "method", + "description": "Sets the seed value for random() and\nrandomGaussian(). By default,\nrandom() and\nrandomGaussian() produce different\nresults each time a sketch is run. Calling randomSeed() with a constant\nargument, such as randomSeed(99), makes these functions produce the same\nresults each time a sketch is run.", + "example": [ + "
\n\nlet x = random(width);\nlet y = random(height);\ncircle(x, y, 10);\n\nrandomSeed(99);\nx = random(width);\ny = random(height);\nfill(0);\ncircle(x, y, 10);\n\ndescribe('A white circle appears at a random position. A black circle appears at (27.4, 25.8).');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "seed", + "description": "seed value.", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Random" + }, + { + "name": "random", + "file": "src/math/random.js", + "line": 164, + "itemtype": "method", + "description": "

Returns a random number or a random element from an array.

\n

random() follows uniform distribution, which means that all outcomes are\nequally likely. When random() is used to generate numbers, all\nnumbers in the output range are equally likely to be returned. When\nrandom() is used to select elements from an array, all elements are\nequally likely to be chosen.

\n

By default, random() produces different results each time a sketch runs.\nThe randomSeed() function can be used to\ngenerate the same sequence of numbers or choices each time a sketch runs.

\n

The version of random() with no parameters returns a random number from 0\nup to but not including 1.

\n

The version of random() with one parameter works one of two ways. If the\nargument passed is a number, random() returns a random number from 0 up\nto but not including the number. For example, calling random(5) returns\nvalues between 0 and 5. If the argument passed is an array, random()\nreturns a random element from that array. For example, calling\nrandom(['🦁', '🐯', '🐻']) returns either a lion, tiger, or bear emoji.

\n

The version of random() with two parameters returns a random number from\na given range. The arguments passed set the range's lower and upper bounds.\nFor example, calling random(-5, 10.2) returns values from -5 up to but\nnot including 10.2.

\n", + "example": [ + "
\n\nlet x = random(width);\nlet y = random(height);\n\nstrokeWeight(5);\npoint(x, y);\n\ndescribe('A black dot appears in a random position on a gray square.');\n\n
\n\n
\n\nlet animals = ['🦁', '🐯', '🐻'];\nlet animal = random(animals);\ntext(animal, 50, 50);\n\ndescribe('An animal face is displayed at random. Either a lion, tiger, or bear.');\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n frameRate(5);\n let x = random(width);\n let y = random(height);\n\n strokeWeight(5);\n point(x, y);\n\n describe('A black dot moves around randomly on a gray square.');\n}\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n frameRate(5);\n let x = random(45, 55);\n let y = random(45, 55);\n\n strokeWeight(5);\n point(x, y);\n\n describe('A black dot moves around randomly in the middle of a gray square.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "min", + "description": "lower bound (inclusive).", + "optional": 1, + "type": "Number" + }, + { + "name": "max", + "description": "upper bound (exclusive).", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "random number.", + "type": "Number" + } + }, + { + "params": [ + { + "name": "choices", + "description": "array to choose from.", + "type": "Array" + } + ], + "return": { + "description": "random element from the array." + } + } + ], + "return": { + "description": "random number.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Random" + }, + { + "name": "randomGaussian", + "file": "src/math/random.js", + "line": 246, + "itemtype": "method", + "description": "

Returns a random number fitting a Gaussian, or normal, distribution. Normal\ndistributions look like bell curves when plotted. Values from a normal\ndistribution cluster around a central value called the mean. The cluster's\nstandard deviation describes its spread.

\n

By default, randomGaussian() produces different results each time a\nsketch runs. The randomSeed() function can be\nused to generate the same sequence of numbers each time a sketch runs.

\n

There's no minimum or maximum value that randomGaussian() might return.\nValues far from the mean are very unlikely and values near the mean are\nvery likely.

\n

The version of randomGaussian() with no parameters returns values with a\nmean of 0 and standard deviation of 1.

\n

The version of randomGaussian() with one parameter interprets the\nargument passed as the mean. The standard deviation is 1.

\n

The version of randomGaussian() with two parameters interprets the first\nargument passed as the mean and the second as the standard deviation.

\n", + "example": [ + "
\n\nfunction draw() {\n noStroke();\n fill(0, 10);\n\n // Uniform distribution.\n let x = random(width);\n let y = 25;\n circle(x, y, 5);\n\n // Gaussian distribution with sd = 1.\n x = randomGaussian(50);\n y = 50;\n circle(x, y, 5);\n\n // Gaussian distribution with sd = 10.\n x = randomGaussian(50, 10);\n y = 75;\n circle(x, y, 5);\n\n describe('Three horizontal black lines are filled in randomly. The top line spans entire canvas. The middle line is very short. The bottom line spans two-thirds of the canvas.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "mean", + "description": "mean.", + "optional": 1, + "type": "Number" + }, + { + "name": "sd", + "description": "standard deviation.", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "random number.", + "type": "Number" + } + } + ], + "return": { + "description": "random number.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Random" + }, + { + "name": "acos", + "file": "src/math/trigonometry.js", + "line": 56, + "itemtype": "method", + "description": "The inverse of cos(), returns the arc cosine of a\nvalue. This function expects arguments in the range -1 to 1. By default,\nacos() returns values in the range 0 to π (about 3.14). If the\nangleMode() is DEGREES, then values are\nreturned in the range 0 to 180.", + "example": [ + "
\n\nlet a = PI + QUARTER_PI;\nlet c = cos(a);\nlet ac = acos(c);\ntext(`${round(a, 3)}`, 35, 25);\ntext(`${round(c, 3)}`, 35, 50);\ntext(`${round(ac, 3)}`, 35, 75);\n\ndescribe('The numbers 3.142, -1, and 3.142 written on separate rows.');\n\n
\n\n
\n\nlet a = PI;\nlet c = cos(a);\nlet ac = acos(c);\ntext(`${round(a, 3)}`, 35, 25);\ntext(`${round(c, 3)}`, 35, 50);\ntext(`${round(ac, 3)}`, 35, 75);\n\ndescribe('The numbers 3.927, -0.707, and 2.356 written on separate rows.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "value whose arc cosine is to be returned.", + "type": "Number" + } + ], + "return": { + "description": "arc cosine of the given value.", + "type": "Number" + } + } + ], + "return": { + "description": "arc cosine of the given value.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Trigonometry" + }, + { + "name": "asin", + "file": "src/math/trigonometry.js", + "line": 99, + "itemtype": "method", + "description": "The inverse of sin(), returns the arc sine of a\nvalue. This function expects input values in the range of -1 to 1. By\ndefault, asin() returns values in the range -π ÷ 2\n(about -1.57) to π ÷ 2 (about 1.57). If the\nangleMode() is DEGREES then values are\nreturned in the range -90 to 90.", + "example": [ + "
\n\nlet a = PI / 3;\nlet s = sin(a);\nlet as = asin(s);\ntext(`${round(a, 3)}`, 35, 25);\ntext(`${round(s, 3)}`, 35, 50);\ntext(`${round(as, 3)}`, 35, 75);\n\ndescribe('The numbers 1.047, 0.866, and 1.047 written on separate rows.');\n\n
\n\n
\n\nlet a = PI + PI / 3;\nlet s = sin(a);\nlet as = asin(s);\ntext(`${round(a, 3)}`, 35, 25);\ntext(`${round(s, 3)}`, 35, 50);\ntext(`${round(as, 3)}`, 35, 75);\n\ndescribe('The numbers 4.189, -0.866, and -1.047 written on separate rows.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "value whose arc sine is to be returned.", + "type": "Number" + } + ], + "return": { + "description": "arc sine of the given value.", + "type": "Number" + } + } + ], + "return": { + "description": "arc sine of the given value.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Trigonometry" + }, + { + "name": "atan", + "file": "src/math/trigonometry.js", + "line": 142, + "itemtype": "method", + "description": "The inverse of tan(), returns the arc tangent of a\nvalue. This function expects input values in the range of -Infinity to\nInfinity. By default, atan() returns values in the range -π ÷ 2\n(about -1.57) to π ÷ 2 (about 1.57). If the\nangleMode() is DEGREES then values are\nreturned in the range -90 to 90.", + "example": [ + "
\n\nlet a = PI / 3;\nlet t = tan(a);\nlet at = atan(t);\ntext(`${round(a, 3)}`, 35, 25);\ntext(`${round(t, 3)}`, 35, 50);\ntext(`${round(at, 3)}`, 35, 75);\n\ndescribe('The numbers 1.047, 1.732, and 1.047 written on separate rows.');\n\n
\n\n
\n\nlet a = PI + PI / 3;\nlet t = tan(a);\nlet at = atan(t);\ntext(`${round(a, 3)}`, 35, 25);\ntext(`${round(t, 3)}`, 35, 50);\ntext(`${round(at, 3)}`, 35, 75);\n\ndescribe('The numbers 4.189, 1.732, and 1.047 written on separate rows.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "value whose arc tangent is to be returned.", + "type": "Number" + } + ], + "return": { + "description": "arc tangent of the given value.", + "type": "Number" + } + } + ], + "return": { + "description": "arc tangent of the given value.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Trigonometry" + }, + { + "name": "atan2", + "file": "src/math/trigonometry.js", + "line": 179, + "itemtype": "method", + "description": "

Calculates the angle formed by a specified point, the origin, and the\npositive x-axis. By default, atan2() returns values in the range\n-π (about -3.14) to π (3.14). If the\nangleMode() is DEGREES, then values are\nreturned in the range -180 to 180. The atan2() function is most often\nused for orienting geometry to the mouse's position.

\n

Note: The y-coordinate of the point is the first parameter and the\nx-coordinate is the second parameter.

\n", + "example": [ + "
\n\nfunction draw() {\n background(200);\n translate(width / 2, height / 2);\n let x = mouseX - width / 2;\n let y = mouseY - height / 2;\n let a = atan2(y, x);\n rotate(a);\n rect(-30, -5, 60, 10);\n\n describe('A rectangle at the center of the canvas rotates with mouse movements.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "y", + "description": "y-coordinate of the point.", + "type": "Number" + }, + { + "name": "x", + "description": "x-coordinate of the point.", + "type": "Number" + } + ], + "return": { + "description": "arc tangent of the given point.", + "type": "Number" + } + } + ], + "return": { + "description": "arc tangent of the given point.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Trigonometry" + }, + { + "name": "cos", + "file": "src/math/trigonometry.js", + "line": 235, + "itemtype": "method", + "description": "Calculates the cosine of an angle. cos() is useful for many geometric\ntasks in creative coding. The values returned oscillate between -1 and 1\nas the input angle increases. cos() takes into account the current\nangleMode.", + "example": [ + "
\n\nfunction draw() {\n background(200);\n\n let t = frameCount;\n let x = 30 * cos(t * 0.05) + 50;\n let y = 50;\n line(50, y, x, y);\n circle(x, y, 20);\n\n describe('A white ball on a string oscillates left and right.');\n}\n\n
\n\n
\n\nfunction draw() {\n let x = frameCount;\n let y = 30 * cos(x * 0.1) + 50;\n point(x, y);\n\n describe('A series of black dots form a wave pattern.');\n}\n\n
\n\n
\n\nfunction draw() {\n let t = frameCount;\n let x = 30 * cos(t * 0.1) + 50;\n let y = 10 * sin(t * 0.2) + 50;\n point(x, y);\n\n describe('A series of black dots form an infinity symbol.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "the angle.", + "type": "Number" + } + ], + "return": { + "description": "cosine of the angle.", + "type": "Number" + } + } + ], + "return": { + "description": "cosine of the angle.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Trigonometry" + }, + { + "name": "sin", + "file": "src/math/trigonometry.js", + "line": 291, + "itemtype": "method", + "description": "Calculates the sine of an angle. sin() is useful for many geometric tasks\nin creative coding. The values returned oscillate between -1 and 1 as the\ninput angle increases. sin() takes into account the current\nangleMode.", + "example": [ + "
\n\nfunction draw() {\n background(200);\n\n let t = frameCount;\n let x = 50;\n let y = 30 * sin(t * 0.05) + 50;\n line(x, 50, x, y);\n circle(x, y, 20);\n\n describe('A white ball on a string oscillates up and down.');\n}\n\n
\n\n
\n\nfunction draw() {\n let x = frameCount;\n let y = 30 * sin(x * 0.1) + 50;\n point(x, y);\n\n describe('A series of black dots form a wave pattern.');\n}\n\n
\n\n
\n\nfunction draw() {\n let t = frameCount;\n let x = 30 * cos(t * 0.1) + 50;\n let y = 10 * sin(t * 0.2) + 50;\n point(x, y);\n\n describe('A series of black dots form an infinity symbol.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "the angle.", + "type": "Number" + } + ], + "return": { + "description": "sine of the angle.", + "type": "Number" + } + } + ], + "return": { + "description": "sine of the angle.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Trigonometry" + }, + { + "name": "tan", + "file": "src/math/trigonometry.js", + "line": 318, + "itemtype": "method", + "description": "Calculates the tangent of an angle. tan() is useful for many geometric\ntasks in creative coding. The values returned range from -Infinity\nto Infinity and repeat periodically as the input angle increases. tan()\ntakes into account the current angleMode.", + "example": [ + "
\n\nfunction draw() {\n let x = frameCount;\n let y = 5 * tan(x * 0.1) + 50;\n point(x, y);\n\n describe('A series of identical curves drawn with black dots. Each curve starts from the top of the canvas, continues down at a slight angle, flattens out at the middle of the canvas, then continues to the bottom.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "the angle.", + "type": "Number" + } + ], + "return": { + "description": "tangent of the angle.", + "type": "Number" + } + } + ], + "return": { + "description": "tangent of the angle.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Trigonometry" + }, + { + "name": "degrees", + "file": "src/math/trigonometry.js", + "line": 345, + "itemtype": "method", + "description": "Converts an angle measurement in radians to its corresponding value in\ndegrees. Degrees and radians are two ways of measuring the same thing.\nThere are 360 degrees in a circle and 2 × π (about 6.28)\nradians in a circle. For example, 90° = π ÷ 2 (about 1.57)\nradians. This function doesn't take into account the current\nangleMode().", + "example": [ + "
\n\nlet rad = QUARTER_PI;\nlet deg = degrees(rad);\ntext(`${round(rad, 2)} rad = ${deg}˚`, 10, 50);\n\ndescribe('The text \"0.79 rad = 45˚\".');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "radians", + "description": "radians value to convert to degrees.", + "type": "Number" + } + ], + "return": { + "description": "converted angle.", + "type": "Number" + } + } + ], + "return": { + "description": "converted angle.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Trigonometry" + }, + { + "name": "radians", + "file": "src/math/trigonometry.js", + "line": 370, + "itemtype": "method", + "description": "Converts an angle measurement in degrees to its corresponding value in\nradians. Degrees and radians are two ways of measuring the same thing.\nThere are 360 degrees in a circle and 2 × π (about 6.28)\nradians in a circle. For example, 90° = π ÷ 2 (about 1.57)\nradians. This function doesn't take into account the current\nangleMode().", + "example": [ + "
\n\nlet deg = 45;\nlet rad = radians(deg);\ntext(`${deg}˚ = ${round(rad, 3)} rad`, 10, 50);\n\ndescribe('The text \"45˚ = 0.785 rad\".');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "degrees", + "description": "degree value to convert to radians.", + "type": "Number" + } + ], + "return": { + "description": "converted angle.", + "type": "Number" + } + } + ], + "return": { + "description": "converted angle.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Trigonometry" + }, + { + "name": "angleMode", + "file": "src/math/trigonometry.js", + "line": 405, + "itemtype": "method", + "description": "

Changes the way trigonometric functions interpret angle values. By default,\nthe mode is RADIANS.

\n

Calling angleMode() with no arguments returns current angle mode.

\n", + "example": [ + "
\n\nlet r = 40;\npush();\nrotate(PI / 6);\nline(0, 0, r, 0);\ntext('0.524 rad', r, 0);\npop();\n\nangleMode(DEGREES);\npush();\nrotate(60);\nline(0, 0, r, 0);\ntext('60˚', r, 0);\npop();\n\ndescribe('Two diagonal lines radiating from the top left corner of a square. The lines are oriented 30 degrees from the edges of the square and 30 degrees apart from each other.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "mode", + "description": "either RADIANS or DEGREES.", + "type": "Constant" + } + ] + }, + { + "params": [], + "return": { + "description": "mode either RADIANS or DEGREES", + "type": "Constant" + } + } + ], + "return": { + "description": "mode either RADIANS or DEGREES", + "type": "Constant" + }, + "class": "p5", + "static": false, + "module": "Math", + "submodule": "Trigonometry" + }, + { + "name": "textAlign", + "file": "src/typography/attributes.js", + "line": 80, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the way text is aligned when text() is called.

\n

By default, calling text('hi', 10, 20) places the bottom-left corner of\nthe text's bounding box at (10, 20).

\n

The first parameter, horizAlign, changes the way\ntext() interprets x-coordinates. By default, the\nx-coordinate sets the left edge of the bounding box. textAlign() accepts\nthe following values for horizAlign: LEFT, CENTER, or RIGHT.

\n

The second parameter, vertAlign, is optional. It changes the way\ntext() interprets y-coordinates. By default, the\ny-coordinate sets the bottom edge of the bounding box. textAlign()\naccepts the following values for vertAlign: TOP, BOTTOM, CENTER,\nor BASELINE.

\n", + "example": [ + "
\n\nstrokeWeight(0.5);\nline(50, 0, 50, 100);\n\ntextSize(16);\ntextAlign(RIGHT);\ntext('ABCD', 50, 30);\ntextAlign(CENTER);\ntext('EFGH', 50, 50);\ntextAlign(LEFT);\ntext('IJKL', 50, 70);\n\ndescribe('The letters ABCD displayed at top-left, EFGH at center, and IJKL at bottom-right. A vertical line divides the canvas in half.');\n\n
\n\n
\n\nstrokeWeight(0.5);\n\nline(0, 12, width, 12);\ntextAlign(CENTER, TOP);\ntext('TOP', 50, 12);\n\nline(0, 37, width, 37);\ntextAlign(CENTER, CENTER);\ntext('CENTER', 50, 37);\n\nline(0, 62, width, 62);\ntextAlign(CENTER, BASELINE);\ntext('BASELINE', 50, 62);\n\nline(0, 97, width, 97);\ntextAlign(CENTER, BOTTOM);\ntext('BOTTOM', 50, 97);\n\ndescribe('The words \"TOP\", \"CENTER\", \"BASELINE\", and \"BOTTOM\" each drawn relative to a horizontal line. Their positions demonstrate different vertical alignments.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "horizAlign", + "description": "horizontal alignment, either LEFT,\nCENTER, or RIGHT.", + "type": "Constant" + }, + { + "name": "vertAlign", + "description": "vertical alignment, either TOP,\nBOTTOM, CENTER, or BASELINE.", + "optional": 1, + "type": "Constant" + } + ] + }, + { + "params": [], + "return": { + "description": "", + "type": "Object" + } + } + ], + "return": { + "description": "", + "type": "Object" + }, + "class": "p5", + "static": false, + "module": "Typography", + "submodule": "Attributes" + }, + { + "name": "textLeading", + "file": "src/typography/attributes.js", + "line": 114, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the spacing between lines of text when\ntext() is called. Spacing is measured in pixels.

\n

Calling textLeading() without an argument returns the current spacing.

\n", + "example": [ + "
\n\n// \"\\n\" starts a new line of text.\nlet lines = 'one\\ntwo';\n\ntext(lines, 10, 25);\n\ntextLeading(30);\ntext(lines, 70, 25);\n\ndescribe('The words \"one\" and \"two\" written on separate lines twice. The words on the left have less vertical spacing than the words on the right.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "leading", + "description": "spacing between lines of text in units of pixels.", + "type": "Number" + } + ] + }, + { + "params": [], + "return": { + "description": "", + "type": "Number" + } + } + ], + "return": { + "description": "", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Typography", + "submodule": "Attributes" + }, + { + "name": "textSize", + "file": "src/typography/attributes.js", + "line": 147, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the font size when\ntext() is called. Font size is measured in pixels.

\n

Calling textSize() without an arugment returns the current size.

\n", + "example": [ + "
\n\ntextSize(12);\ntext('Font Size 12', 10, 30);\ntextSize(14);\ntext('Font Size 14', 10, 60);\ntextSize(16);\ntext('Font Size 16', 10, 90);\n\ndescribe('The text \"Font Size 12\" drawn small, \"Font Size 14\" drawn medium, and \"Font Size 16\" drawn large.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "size", + "description": "size of the letters in units of pixels", + "type": "Number" + } + ] + }, + { + "params": [], + "return": { + "description": "", + "type": "Number" + } + } + ], + "return": { + "description": "", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Typography", + "submodule": "Attributes" + }, + { + "name": "textStyle", + "file": "src/typography/attributes.js", + "line": 187, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the style for system fonts when\ntext() is called. textStyle() accepts the\nfollowing values: NORMAL, ITALIC, BOLD or BOLDITALIC.

\n

textStyle() may be overridden by CSS styling. This function doesn't\naffect fonts loaded with loadFont().

\n", + "example": [ + "
\n\ntextSize(12);\ntextAlign(CENTER);\n\ntextStyle(NORMAL);\ntext('Normal', 50, 15);\ntextStyle(ITALIC);\ntext('Italic', 50, 40);\ntextStyle(BOLD);\ntext('Bold', 50, 65);\ntextStyle(BOLDITALIC);\ntext('Bold Italic', 50, 90);\n\ndescribe('The words \"Normal\" displayed normally, \"Italic\" in italic, \"Bold\" in bold, and \"Bold Italic\" in bold italics.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "style", + "description": "styling for text, either NORMAL,\nITALIC, BOLD or BOLDITALIC", + "type": "Constant" + } + ] + }, + { + "params": [], + "return": { + "description": "", + "type": "String" + } + } + ], + "return": { + "description": "", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Typography", + "submodule": "Attributes" + }, + { + "name": "textWidth", + "file": "src/typography/attributes.js", + "line": 235, + "itemtype": "method", + "description": "Returns the maximum width of a string of text drawn when\ntext() is called.", + "example": [ + "
\n\nfunction setup() {\n background(200);\n\n textSize(28);\n strokeWeight(0.5);\n let s = 'yoyo';\n let w = textWidth(s);\n text(s, 22, 55);\n line(22, 55, 22 + w, 55);\n\n describe('The word \"yoyo\" underlined.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n\n textSize(28);\n strokeWeight(0.5);\n // \"\\n\" starts a new line.\n let s = 'yo\\nyo';\n let w = textWidth(s);\n text(s, 22, 55);\n line(22, 55, 22 + w, 55);\n\n describe('The word \"yo\" written twice, one copy beneath the other. The words are divided by a horizontal line.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "str", + "description": "string of text to measure.", + "type": "String" + } + ], + "return": { + "description": "width measured in units of pixels.", + "type": "Number" + } + } + ], + "return": { + "description": "width measured in units of pixels.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Typography", + "submodule": "Attributes" + }, + { + "name": "textAscent", + "file": "src/typography/attributes.js", + "line": 305, + "itemtype": "method", + "description": "Returns the ascent of the current font at its current size. The ascent\nrepresents the distance, in pixels, of the tallest character above\nthe baseline.", + "example": [ + "
\n\nlet font;\n\nfunction preload() {\n font = loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n background(200);\n textFont(font);\n\n // Different for each font.\n let fontScale = 0.8;\n\n let baseY = 75;\n strokeWeight(0.5);\n\n // Draw small text.\n textSize(24);\n text('dp', 0, baseY);\n // Draw baseline and ascent.\n let a = textAscent() * fontScale;\n line(0, baseY, 23, baseY);\n line(23, baseY - a, 23, baseY);\n\n // Draw large text.\n textSize(48);\n text('dp', 45, baseY);\n // Draw baseline and ascent.\n a = textAscent() * fontScale;\n line(45, baseY, 91, baseY);\n line(91, baseY - a, 91, baseY);\n\n describe('The letters \"dp\" written twice in different sizes. Each version has a horizontal baseline. A vertical line extends upward from each baseline to the top of the \"d\".');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "ascent measured in units of pixels.", + "type": "Number" + } + } + ], + "return": { + "description": "ascent measured in units of pixels.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Typography", + "submodule": "Attributes" + }, + { + "name": "textDescent", + "file": "src/typography/attributes.js", + "line": 357, + "itemtype": "method", + "description": "Returns the descent of the current font at its current size. The descent\nrepresents the distance, in pixels, of the character with the longest\ndescender below the baseline.", + "example": [ + "
\n\nlet font;\n\nfunction preload() {\n font = loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n background(200);\n textFont(font);\n\n // Different for each font.\n let fontScale = 0.9;\n\n let baseY = 75;\n strokeWeight(0.5);\n\n // Draw small text.\n textSize(24);\n text('dp', 0, baseY);\n // Draw baseline and descent.\n let d = textDescent() * fontScale;\n line(0, baseY, 23, baseY);\n line(23, baseY, 23, baseY + d);\n\n // Draw large text.\n textSize(48);\n text('dp', 45, baseY);\n // Draw baseline and descent.\n d = textDescent() * fontScale;\n line(45, baseY, 91, baseY);\n line(91, baseY, 91, baseY + d);\n\n describe('The letters \"dp\" written twice in different sizes. Each version has a horizontal baseline. A vertical line extends downward from each baseline to the bottom of the \"p\".');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "descent measured in units of pixels.", + "type": "Number" + } + } + ], + "return": { + "description": "descent measured in units of pixels.", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Typography", + "submodule": "Attributes" + }, + { + "name": "textWrap", + "file": "src/typography/attributes.js", + "line": 414, + "itemtype": "method", + "description": "

Sets the style for wrapping text when\ntext() is called. textWrap() accepts the\nfollowing values:

\n

WORD starts new lines of text at spaces. If a string of text doesn't\nhave spaces, it may overflow the text box and the canvas. This is the\ndefault style.

\n

CHAR starts new lines as needed to stay within the text box.

\n

textWrap() only works when the maximum width is set for a text box. For\nexample, calling text('Have a wonderful day', 0, 10, 100) sets the\nmaximum width to 100 pixels.

\n

Calling textWrap() without an argument returns the current style.

\n", + "example": [ + "
\n\ntextSize(20);\ntextWrap(WORD);\ntext('Have a wonderful day', 0, 10, 100);\n\n
\n\n
\n\ntextSize(20);\ntextWrap(CHAR);\ntext('Have a wonderful day', 0, 10, 100);\n\n
\n\n
\n\ntextSize(20);\ntextWrap(CHAR);\ntext('祝你有美好的一天', 0, 10, 100);\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "style", + "description": "text wrapping style, either WORD or CHAR.", + "type": "Constant" + } + ], + "return": { + "description": "style", + "type": "String" + } + } + ], + "return": { + "description": "style", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Typography", + "submodule": "Attributes" + }, + { + "name": "loadFont", + "file": "src/typography/loading_displaying.js", + "line": 129, + "itemtype": "method", + "description": "

Loads a font and creates a p5.Font object.\nloadFont() can load fonts in either .otf or .ttf format. Loaded fonts can\nbe used to style text on the canvas and in HTML elements.

\n

The first parameter, path, is the path to a font file.\nPaths to local files should be relative. For example,\n'assets/inconsolata.otf'. The Inconsolata font used in the following\nexamples can be downloaded for free\nhere.\nPaths to remote files should be URLs. For example,\n'https://example.com/inconsolata.otf'. URLs may be blocked due to browser\nsecurity.

\n

The second parameter, successCallback, is optional. If a function is\npassed, it will be called once the font has loaded. The callback function\nmay use the new p5.Font object if needed.

\n

The third parameter, failureCallback, is also optional. If a function is\npassed, it will be called if the font fails to load. The callback function\nmay use the error\nEvent\nobject if needed.

\n

Fonts can take time to load. Calling loadFont() in\npreload() ensures fonts load before they're\nused in setup() or\ndraw().

\n", + "example": [ + "
\n\nlet font;\n\nfunction preload() {\n font = loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n fill('deeppink');\n textFont(font);\n textSize(36);\n text('p5*js', 10, 50);\n\n describe('The text \"p5*js\" written in pink on a white background.');\n}\n\n
\n\n
\n\nfunction setup() {\n loadFont('assets/inconsolata.otf', font => {\n fill('deeppink');\n textFont(font);\n textSize(36);\n text('p5*js', 10, 50);\n\n describe('The text \"p5*js\" written in pink on a white background.');\n });\n}\n\n
\n\n
\n\nfunction setup() {\n loadFont('assets/inconsolata.otf', success, failure);\n}\n\nfunction success(font) {\n fill('deeppink');\n textFont(font);\n textSize(36);\n text('p5*js', 10, 50);\n\n describe('The text \"p5*js\" written in pink on a white background.');\n}\n\nfunction failure(event) {\n console.error('Oops!', event);\n}\n\n
\n\n
\n\nfunction preload() {\n loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n let p = createP('p5*js');\n p.style('color', 'deeppink');\n p.style('font-family', 'Inconsolata');\n p.style('font-size', '36px');\n p.position(10, 50);\n\n describe('The text \"p5*js\" written in pink on a white background.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "path of the font to be loaded.", + "type": "String" + }, + { + "name": "successCallback", + "description": "function called with the\np5.Font object after it\nloads.", + "optional": 1, + "type": "Function" + }, + { + "name": "failureCallback", + "description": "function called with the error\nEvent\nobject if the font fails to load.", + "optional": 1, + "type": "Function" + } + ], + "return": { + "description": "p5.Font object.", + "type": "p5.Font" + } + } + ], + "return": { + "description": "p5.Font object.", + "type": "p5.Font" + }, + "class": "p5", + "static": false, + "module": "Typography", + "submodule": "Loading & Displaying" + }, + { + "name": "text", + "file": "src/typography/loading_displaying.js", + "line": 328, + "itemtype": "method", + "chainable": 1, + "description": "

Draws text to the canvas.

\n

The first parameter, str, is the text to be drawn. The second and third\nparameters, x and y, set the coordinates of the text's bottom-left\ncorner. See textAlign() for other ways to\nalign text.

\n

The fourth and fifth parameters, maxWidth and maxHeight, are optional.\nThey set the dimensions of the invisible rectangle containing the text. By\ndefault, they set its maximum width and height. See\nrectMode() for other ways to define the\nrectangular text box. Text will wrap to fit within the text box. Text\noutside of the box won't be drawn.

\n

Text can be styled a few ways. Call the fill()\nfunction to set the text's fill color. Call\nstroke() and\nstrokeWeight() to set the text's outline.\nCall textSize() and\ntextFont() to set the text's size and font,\nrespectively.

\n

Note: WEBGL mode only supports fonts loaded with\nloadFont(). Calling\nstroke() has no effect in WEBGL mode.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n text('hi', 50, 50);\n\n describe('The text \"hi\" written in black in the middle of a gray square.');\n}\n\n
\n\n
\n\nfunction setup() {\n background('skyblue');\n textSize(100);\n text('🌈', 0, 100);\n\n describe('A rainbow in a blue sky.');\n}\n\n
\n\n
\n\nfunction setup() {\n textSize(32);\n fill(255);\n stroke(0);\n strokeWeight(4);\n text('hi', 50, 50);\n\n describe('The text \"hi\" written in white with a black outline.');\n}\n\n
\n\n
\n\nfunction setup() {\n background('black');\n textSize(22);\n fill('yellow');\n text('rainbows', 6, 20);\n fill('cornflowerblue');\n text('rainbows', 6, 45);\n fill('tomato');\n text('rainbows', 6, 70);\n fill('limegreen');\n text('rainbows', 6, 95);\n\n describe('The text \"rainbows\" written on several lines, each in a different color.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n let s = 'The quick brown fox jumps over the lazy dog.';\n text(s, 10, 10, 70, 80);\n\n describe('The sample text \"The quick brown fox...\" written in black across several lines.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n rectMode(CENTER);\n let s = 'The quick brown fox jumps over the lazy dog.';\n text(s, 50, 50, 70, 80);\n\n describe('The sample text \"The quick brown fox...\" written in black across several lines.');\n}\n\n
\n\n
\n\nlet font;\n\nfunction preload() {\n font = loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n textFont(font);\n textSize(32);\n textAlign(CENTER, CENTER);\n}\n\nfunction draw() {\n background(0);\n rotateY(frameCount / 30);\n text('p5*js', 0, 0);\n\n describe('The text \"p5*js\" written in white and spinning in 3D.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "str", + "description": "text to be displayed.", + "type": "String|Object|Array|Number|Boolean" + }, + { + "name": "x", + "description": "x-coordinate of the text box.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the text box.", + "type": "Number" + }, + { + "name": "maxWidth", + "description": "maximum width of the text box. See\nrectMode() for\nother options.", + "optional": 1, + "type": "Number" + }, + { + "name": "maxHeight", + "description": "maximum height of the text box. See\nrectMode() for\nother options.", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Typography", + "submodule": "Loading & Displaying" + }, + { + "name": "textFont", + "file": "src/typography/loading_displaying.js", + "line": 424, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the font used by the text() function.

\n

The first parameter, font, sets the font. textFont() recognizes either\na p5.Font object or a string with the name of a\nsystem font. For example, 'Courier New'.

\n

The second parameter, size, is optional. It sets the font size in pixels.\nThis has the same effect as calling textSize().

\n

Note: WEBGL mode only supports fonts loaded with\nloadFont().

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n textFont('Courier New');\n textSize(24);\n text('hi', 35, 55);\n\n describe('The text \"hi\" written in a black, monospace font on a gray background.');\n}\n\n
\n\n
\n\nfunction setup() {\n background('black');\n fill('palegreen');\n textFont('Courier New', 10);\n text('You turn to the left and see a door. Do you enter?', 5, 5, 90, 90);\n text('>', 5, 70);\n\n describe('A text prompt from a game is written in a green, monospace font on a black background.');\n}\n\n
\n\n
\n\nfunction setup() {\n background(200);\n textFont('Verdana');\n let currentFont = textFont();\n text(currentFont, 25, 50);\n\n describe('The text \"Verdana\" written in a black, sans-serif font on a gray background.');\n}\n\n
\n\n
\n\nlet fontRegular;\nlet fontItalic;\nlet fontBold;\n\nfunction preload() {\n fontRegular = loadFont('assets/Regular.otf');\n fontItalic = loadFont('assets/Italic.ttf');\n fontBold = loadFont('assets/Bold.ttf');\n}\n\nfunction setup() {\n background(200);\n textFont(fontRegular);\n text('I am Normal', 10, 30);\n textFont(fontItalic);\n text('I am Italic', 10, 50);\n textFont(fontBold);\n text('I am Bold', 10, 70);\n\n describe('The statements \"I am Normal\", \"I am Italic\", and \"I am Bold\" written in black on separate lines. The statements have normal, italic, and bold fonts, respectively.');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "current font or p5 Object.", + "type": "Object" + } + }, + { + "params": [ + { + "name": "font", + "description": "font as a p5.Font object or a string.", + "type": "Object|String" + }, + { + "name": "size", + "description": "font size in pixels.", + "optional": 1, + "type": "Number" + } + ] + } + ], + "return": { + "description": "current font or p5 Object.", + "type": "Object" + }, + "class": "p5", + "static": false, + "module": "Typography", + "submodule": "Loading & Displaying" + }, + { + "name": "append", + "file": "src/utilities/array_functions.js", + "line": 30, + "itemtype": "method", + "description": "Adds a value to the end of an array. Extends the length of\nthe array by one. Maps to Array.push().", + "example": [ + "
\nfunction setup() {\n let myArray = ['Mango', 'Apple', 'Papaya'];\n print(myArray); // ['Mango', 'Apple', 'Papaya']\n\n append(myArray, 'Peach');\n print(myArray); // ['Mango', 'Apple', 'Papaya', 'Peach']\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "array", + "description": "Array to append", + "type": "Array" + }, + { + "name": "value", + "description": "to be added to the Array", + "type": "any" + } + ], + "return": { + "description": "the array that was appended to", + "type": "Array" + } + } + ], + "return": { + "description": "the array that was appended to", + "type": "Array" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Array Functions" + }, + { + "name": "arrayCopy", + "file": "src/utilities/array_functions.js", + "line": 80, + "itemtype": "method", + "description": "

Copies an array (or part of an array) to another array. The src array is\ncopied to the dst array, beginning at the position specified by\nsrcPosition and into the position specified by dstPosition. The number of\nelements to copy is determined by length. Note that copying values\noverwrites existing values in the destination array. To append values\ninstead of overwriting them, use concat().

\n

The simplified version with only two arguments, arrayCopy(src, dst),\ncopies an entire array to another of the same size. It is equivalent to\narrayCopy(src, 0, dst, 0, src.length).

\n

Using this function is far more efficient for copying array data than\niterating through a for() loop and copying each element individually.

\n", + "example": [ + "
\nlet src = ['A', 'B', 'C'];\nlet dst = [1, 2, 3];\nlet srcPosition = 1;\nlet dstPosition = 0;\nlet length = 2;\n\nprint(src); // ['A', 'B', 'C']\nprint(dst); // [ 1 , 2 , 3 ]\n\narrayCopy(src, srcPosition, dst, dstPosition, length);\nprint(dst); // ['B', 'C', 3]\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "src", + "description": "the source Array", + "type": "Array" + }, + { + "name": "srcPosition", + "description": "starting position in the source Array", + "type": "Integer" + }, + { + "name": "dst", + "description": "the destination Array", + "type": "Array" + }, + { + "name": "dstPosition", + "description": "starting position in the destination Array", + "type": "Integer" + }, + { + "name": "length", + "description": "number of Array elements to be copied", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "src", + "type": "Array" + }, + { + "name": "dst", + "type": "Array" + }, + { + "name": "length", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Array Functions" + }, + { + "name": "concat", + "file": "src/utilities/array_functions.js", + "line": 139, + "itemtype": "method", + "description": "Concatenates two arrays, maps to Array.concat(). Does not modify the\ninput arrays.", + "example": [ + "
\nfunction setup() {\n let arr1 = ['A', 'B', 'C'];\n let arr2 = [1, 2, 3];\n\n print(arr1); // ['A','B','C']\n print(arr2); // [1,2,3]\n\n let arr3 = concat(arr1, arr2);\n\n print(arr1); // ['A','B','C']\n print(arr2); // [1, 2, 3]\n print(arr3); // ['A','B','C', 1, 2, 3]\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "a", + "description": "first Array to concatenate", + "type": "Array" + }, + { + "name": "b", + "description": "second Array to concatenate", + "type": "Array" + } + ], + "return": { + "description": "concatenated array", + "type": "Array" + } + } + ], + "return": { + "description": "concatenated array", + "type": "Array" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Array Functions" + }, + { + "name": "reverse", + "file": "src/utilities/array_functions.js", + "line": 159, + "itemtype": "method", + "description": "Reverses the order of an array, maps to Array.reverse()", + "example": [ + "
\nfunction setup() {\n let myArray = ['A', 'B', 'C'];\n print(myArray); // ['A','B','C']\n\n reverse(myArray);\n print(myArray); // ['C','B','A']\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "list", + "description": "Array to reverse", + "type": "Array" + } + ], + "return": { + "description": "the reversed list", + "type": "Array" + } + } + ], + "return": { + "description": "the reversed list", + "type": "Array" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Array Functions" + }, + { + "name": "shorten", + "file": "src/utilities/array_functions.js", + "line": 180, + "itemtype": "method", + "description": "Decreases an array by one element and returns the shortened array,\nmaps to Array.pop().", + "example": [ + "
\nfunction setup() {\n let myArray = ['A', 'B', 'C'];\n print(myArray); // ['A', 'B', 'C']\n let newArray = shorten(myArray);\n print(myArray); // ['A','B','C']\n print(newArray); // ['A','B']\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "list", + "description": "Array to shorten", + "type": "Array" + } + ], + "return": { + "description": "shortened Array", + "type": "Array" + } + } + ], + "return": { + "description": "shortened Array", + "type": "Array" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Array Functions" + }, + { + "name": "shuffle", + "file": "src/utilities/array_functions.js", + "line": 209, + "itemtype": "method", + "description": "Randomizes the order of the elements of an array. Implements\n\nFisher-Yates Shuffle Algorithm.", + "example": [ + "
\nfunction setup() {\n let regularArr = ['ABC', 'def', createVector(), TAU, Math.E];\n print(regularArr);\n shuffle(regularArr, true); // force modifications to passed array\n print(regularArr);\n\n // By default shuffle() returns a shuffled cloned array:\n let newArr = shuffle(regularArr);\n print(regularArr);\n print(newArr);\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "array", + "description": "Array to shuffle", + "type": "Array" + }, + { + "name": "bool", + "description": "modify passed array", + "optional": 1, + "type": "Boolean" + } + ], + "return": { + "description": "shuffled Array", + "type": "Array" + } + } + ], + "return": { + "description": "shuffled Array", + "type": "Array" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Array Functions" + }, + { + "name": "sort", + "file": "src/utilities/array_functions.js", + "line": 262, + "itemtype": "method", + "description": "Sorts an array of numbers from smallest to largest, or puts an array of\nwords in alphabetical order. The original array is not modified; a\nre-ordered array is returned. The count parameter states the number of\nelements to sort. For example, if there are 12 elements in an array and\ncount is set to 5, only the first 5 elements in the array will be sorted.", + "example": [ + "
\nfunction setup() {\n let words = ['banana', 'apple', 'pear', 'lime'];\n print(words); // ['banana', 'apple', 'pear', 'lime']\n let count = 4; // length of array\n\n words = sort(words, count);\n print(words); // ['apple', 'banana', 'lime', 'pear']\n}\n
\n
\nfunction setup() {\n let numbers = [2, 6, 1, 5, 14, 9, 8, 12];\n print(numbers); // [2, 6, 1, 5, 14, 9, 8, 12]\n let count = 5; // Less than the length of the array\n\n numbers = sort(numbers, count);\n print(numbers); // [1,2,5,6,14,9,8,12]\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "list", + "description": "Array to sort", + "type": "Array" + }, + { + "name": "count", + "description": "number of elements to sort, starting from 0", + "optional": 1, + "type": "Integer" + } + ], + "return": { + "description": "the sorted list", + "type": "Array" + } + } + ], + "return": { + "description": "the sorted list", + "type": "Array" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Array Functions" + }, + { + "name": "splice", + "file": "src/utilities/array_functions.js", + "line": 301, + "itemtype": "method", + "description": "Inserts a value or an array of values into an existing array. The first\nparameter specifies the initial array to be modified, and the second\nparameter defines the data to be inserted. The third parameter is an index\nvalue which specifies the array position from which to insert data.\n(Remember that array index numbering starts at zero, so the first position\nis 0, the second position is 1, and so on.)", + "example": [ + "
\nfunction setup() {\n let myArray = [0, 1, 2, 3, 4];\n let insArray = ['A', 'B', 'C'];\n print(myArray); // [0, 1, 2, 3, 4]\n print(insArray); // ['A','B','C']\n\n splice(myArray, insArray, 3);\n print(myArray); // [0,1,2,'A','B','C',3,4]\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "list", + "description": "Array to splice into", + "type": "Array" + }, + { + "name": "value", + "description": "value to be spliced in", + "type": "any" + }, + { + "name": "position", + "description": "in the array from which to insert data", + "type": "Integer" + } + ], + "return": { + "description": "the list", + "type": "Array" + } + } + ], + "return": { + "description": "the list", + "type": "Array" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Array Functions" + }, + { + "name": "subset", + "file": "src/utilities/array_functions.js", + "line": 336, + "itemtype": "method", + "description": "Extracts an array of elements from an existing array. The list parameter\ndefines the array from which the elements will be copied, and the start\nand count parameters specify which elements to extract. If no count is\ngiven, elements will be extracted from the start to the end of the array.\nWhen specifying the start, remember that the first array element is 0.\nThis function does not change the source array.", + "example": [ + "
\nfunction setup() {\n let myArray = [1, 2, 3, 4, 5];\n print(myArray); // [1, 2, 3, 4, 5]\n\n let sub1 = subset(myArray, 0, 3);\n let sub2 = subset(myArray, 2, 2);\n print(sub1); // [1,2,3]\n print(sub2); // [3,4]\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "list", + "description": "Array to extract from", + "type": "Array" + }, + { + "name": "start", + "description": "position to begin", + "type": "Integer" + }, + { + "name": "count", + "description": "number of values to extract", + "optional": 1, + "type": "Integer" + } + ], + "return": { + "description": "Array of extracted elements", + "type": "Array" + } + } + ], + "return": { + "description": "Array of extracted elements", + "type": "Array" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Array Functions" + }, + { + "name": "float", + "file": "src/utilities/conversion.js", + "line": 35, + "itemtype": "method", + "description": "

Converts a string to its floating point representation. The contents of a\nstring must resemble a number, or NaN (not a number) will be returned.\nFor example, float(\"1234.56\") evaluates to 1234.56, but float(\"giraffe\")\nwill return NaN.

\n

When an array of values is passed in, then an array of floats of the same\nlength is returned.

\n", + "example": [ + "
\nlet str = '20';\nlet diameter = float(str);\nellipse(width / 2, height / 2, diameter, diameter);\ndescribe('20-by-20 white ellipse in the center of the canvas');\n
\n
\nprint(float('10.31')); // 10.31\nprint(float('Infinity')); // Infinity\nprint(float('-Infinity')); // -Infinity\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "str", + "description": "float string to parse", + "type": "String" + } + ], + "return": { + "description": "floating point representation of string", + "type": "Number" + } + } + ], + "return": { + "description": "floating point representation of string", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Conversion" + }, + { + "name": "int", + "file": "src/utilities/conversion.js", + "line": 70, + "itemtype": "method", + "description": "Converts a boolean, string, or float to its integer representation.\nWhen an array of values is passed in, then an int array of the same length\nis returned.", + "example": [ + "
\nprint(int('10')); // 10\nprint(int(10.31)); // 10\nprint(int(-10)); // -10\nprint(int(true)); // 1\nprint(int(false)); // 0\nprint(int([false, true, '10.3', 9.8])); // [0, 1, 10, 9]\nprint(int(Infinity)); // Infinity\nprint(int('-Infinity')); // -Infinity\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "value to parse", + "type": "String|Boolean|Number" + }, + { + "name": "radix", + "description": "the radix to convert to (default: 10)", + "optional": 1, + "type": "Integer" + } + ], + "return": { + "description": "integer representation of value", + "type": "Number" + } + }, + { + "params": [ + { + "name": "ns", + "description": "values to parse", + "type": "Array" + }, + { + "name": "radix", + "optional": 1, + "type": "Integer" + } + ], + "return": { + "description": "integer representation of values", + "type": "Number[]" + } + } + ], + "return": { + "description": "integer representation of value", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Conversion" + }, + { + "name": "str", + "file": "src/utilities/conversion.js", + "line": 104, + "itemtype": "method", + "description": "Converts a boolean, string or number to its string representation.\nWhen an array of values is passed in, then an array of strings of the same\nlength is returned.", + "example": [ + "
\nprint(str('10')); // \"10\"\nprint(str(10.31)); // \"10.31\"\nprint(str(-10)); // \"-10\"\nprint(str(true)); // \"true\"\nprint(str(false)); // \"false\"\nprint(str([true, '10.3', 9.8])); // [ \"true\", \"10.3\", \"9.8\" ]\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "value to parse", + "type": "String|Boolean|Number|Array" + } + ], + "return": { + "description": "string representation of value", + "type": "String" + } + } + ], + "return": { + "description": "string representation of value", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Conversion" + }, + { + "name": "boolean", + "file": "src/utilities/conversion.js", + "line": 132, + "itemtype": "method", + "description": "Converts a number or string to its boolean representation.\nFor a number, any non-zero value (positive or negative) evaluates to true,\nwhile zero evaluates to false. For a string, the value \"true\" evaluates to\ntrue, while any other value evaluates to false. When an array of number or\nstring values is passed in, then a array of booleans of the same length is\nreturned.", + "example": [ + "
\nprint(boolean(0)); // false\nprint(boolean(1)); // true\nprint(boolean('true')); // true\nprint(boolean('abcd')); // false\nprint(boolean([0, 12, 'true'])); // [false, true, true]\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "value to parse", + "type": "String|Boolean|Number|Array" + } + ], + "return": { + "description": "boolean representation of value", + "type": "Boolean" + } + } + ], + "return": { + "description": "boolean representation of value", + "type": "Boolean" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Conversion" + }, + { + "name": "byte", + "file": "src/utilities/conversion.js", + "line": 171, + "itemtype": "method", + "description": "Converts a number, string representation of a number, or boolean to its byte\nrepresentation. A byte can be only a whole number between -128 and 127, so\nwhen a value outside of this range is converted, it wraps around to the\ncorresponding byte representation. When an array of number, string or boolean\nvalues is passed in, then an array of bytes the same length is returned.", + "example": [ + "
\nprint(byte(127)); // 127\nprint(byte(128)); // -128\nprint(byte(23.4)); // 23\nprint(byte('23.4')); // 23\nprint(byte('hello')); // NaN\nprint(byte(true)); // 1\nprint(byte([0, 255, '100'])); // [0, -1, 100]\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "value to parse", + "type": "String|Boolean|Number" + } + ], + "return": { + "description": "byte representation of value", + "type": "Number" + } + }, + { + "params": [ + { + "name": "ns", + "description": "values to parse", + "type": "Array" + } + ], + "return": { + "description": "array of byte representation of values", + "type": "Number[]" + } + } + ], + "return": { + "description": "byte representation of value", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Conversion" + }, + { + "name": "char", + "file": "src/utilities/conversion.js", + "line": 204, + "itemtype": "method", + "description": "Converts a number or string to its corresponding single-character\nstring representation. If a string parameter is provided, it is first\nparsed as an integer and then translated into a single-character string.\nWhen an array of number or string values is passed in, then an array of\nsingle-character strings of the same length is returned.", + "example": [ + "
\nprint(char(65)); // \"A\"\nprint(char('65')); // \"A\"\nprint(char([65, 66, 67])); // [ \"A\", \"B\", \"C\" ]\nprint(join(char([65, 66, 67]), '')); // \"ABC\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "value to parse", + "type": "String|Number" + } + ], + "return": { + "description": "string representation of value", + "type": "String" + } + }, + { + "params": [ + { + "name": "ns", + "description": "values to parse", + "type": "Array" + } + ], + "return": { + "description": "array of string representation of values", + "type": "String[]" + } + } + ], + "return": { + "description": "string representation of value", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Conversion" + }, + { + "name": "unchar", + "file": "src/utilities/conversion.js", + "line": 235, + "itemtype": "method", + "description": "Converts a single-character string to its corresponding integer\nrepresentation. When an array of single-character string values is passed\nin, then an array of integers of the same length is returned.", + "example": [ + "
\nprint(unchar('A')); // 65\nprint(unchar(['A', 'B', 'C'])); // [ 65, 66, 67 ]\nprint(unchar(split('ABC', ''))); // [ 65, 66, 67 ]\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "value to parse", + "type": "String" + } + ], + "return": { + "description": "integer representation of value", + "type": "Number" + } + }, + { + "params": [ + { + "name": "ns", + "description": "values to parse", + "type": "Array" + } + ], + "return": { + "description": "integer representation of values", + "type": "Number[]" + } + } + ], + "return": { + "description": "integer representation of value", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Conversion" + }, + { + "name": "hex", + "file": "src/utilities/conversion.js", + "line": 269, + "itemtype": "method", + "description": "Converts a number to a string in its equivalent hexadecimal notation. If a\nsecond parameter is passed, it is used to set the number of characters to\ngenerate in the hexadecimal notation. When an array is passed in, an\narray of strings in hexadecimal notation of the same length is returned.", + "example": [ + "
\nprint(hex(255)); // \"000000FF\"\nprint(hex(255, 6)); // \"0000FF\"\nprint(hex([0, 127, 255], 6)); // [ \"000000\", \"00007F\", \"0000FF\" ]\nprint(Infinity); // \"FFFFFFFF\"\nprint(-Infinity); // \"00000000\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "value to parse", + "type": "Number" + }, + { + "name": "digits", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "hexadecimal string representation of value", + "type": "String" + } + }, + { + "params": [ + { + "name": "ns", + "description": "array of values to parse", + "type": "Number[]" + }, + { + "name": "digits", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "hexadecimal string representation of values", + "type": "String[]" + } + } + ], + "return": { + "description": "hexadecimal string representation of value", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Conversion" + }, + { + "name": "unhex", + "file": "src/utilities/conversion.js", + "line": 314, + "itemtype": "method", + "description": "Converts a string representation of a hexadecimal number to its equivalent\ninteger value. When an array of strings in hexadecimal notation is passed\nin, an array of integers of the same length is returned.", + "example": [ + "
\nprint(unhex('A')); // 10\nprint(unhex('FF')); // 255\nprint(unhex(['FF', 'AA', '00'])); // [ 255, 170, 0 ]\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "value to parse", + "type": "String" + } + ], + "return": { + "description": "integer representation of hexadecimal value", + "type": "Number" + } + }, + { + "params": [ + { + "name": "ns", + "description": "values to parse", + "type": "Array" + } + ], + "return": { + "description": "integer representations of hexadecimal value", + "type": "Number[]" + } + } + ], + "return": { + "description": "integer representation of hexadecimal value", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "Conversion" + }, + { + "name": "join", + "file": "src/utilities/string_functions.js", + "line": 34, + "itemtype": "method", + "description": "Combines an array of Strings into one String, each separated by the\ncharacter(s) used for the separator parameter. To join arrays of ints or\nfloats, it's necessary to first convert them to Strings using nf() or\nnfs().", + "example": [ + "
\n\nlet array = ['Hello', 'world!'];\nlet separator = ' ';\nlet message = join(array, separator);\ntext(message, 5, 50);\ndescribe('“Hello world!” displayed middle left of canvas.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "list", + "description": "array of Strings to be joined", + "type": "Array" + }, + { + "name": "separator", + "description": "String to be placed between each item", + "type": "String" + } + ], + "return": { + "description": "joined String", + "type": "String" + } + } + ], + "return": { + "description": "joined String", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "String Functions" + }, + { + "name": "match", + "file": "src/utilities/string_functions.js", + "line": 72, + "itemtype": "method", + "description": "

This function is used to apply a regular expression to a piece of text,\nand return matching groups (elements found inside parentheses) as a\nString array. If there are no matches, a null value will be returned.\nIf no groups are specified in the regular expression, but the sequence\nmatches, an array of length 1 (with the matched text as the first element\nof the array) will be returned.

\n

To use the function, first check to see if the result is null. If the\nresult is null, then the sequence did not match at all. If the sequence\ndid match, an array is returned.

\n

If there are groups (specified by sets of parentheses) in the regular\nexpression, then the contents of each will be returned in the array.\nElement [0] of a regular expression match returns the entire matching\nstring, and the match groups start at element [1] (the first group is [1],\nthe second [2], and so on).

\n", + "example": [ + "
\n\nlet string = 'Hello p5js*!';\nlet regexp = 'p5js\\\\*';\nlet m = match(string, regexp);\ntext(m, 5, 50);\ndescribe('“p5js*” displayed middle left of canvas.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "str", + "description": "the String to be searched", + "type": "String" + }, + { + "name": "regexp", + "description": "the regexp to be used for matching", + "type": "String" + } + ], + "return": { + "description": "Array of Strings found", + "type": "String[]" + } + } + ], + "return": { + "description": "Array of Strings found", + "type": "String[]" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "String Functions" + }, + { + "name": "matchAll", + "file": "src/utilities/string_functions.js", + "line": 109, + "itemtype": "method", + "description": "

This function is used to apply a regular expression to a piece of text,\nand return a list of matching groups (elements found inside parentheses)\nas a two-dimensional String array. If there are no matches, a null value\nwill be returned. If no groups are specified in the regular expression,\nbut the sequence matches, a two dimensional array is still returned, but\nthe second dimension is only of length one.

\n

To use the function, first check to see if the result is null. If the\nresult is null, then the sequence did not match at all. If the sequence\ndid match, a 2D array is returned.

\n

If there are groups (specified by sets of parentheses) in the regular\nexpression, then the contents of each will be returned in the array.\nAssuming a loop with counter variable i, element [i][0] of a regular\nexpression match returns the entire matching string, and the match groups\nstart at element [i][1] (the first group is [i][1], the second [i][2],\nand so on).

\n", + "example": [ + "
\n\nlet string = 'Hello p5js*! Hello world!';\nlet regexp = 'Hello';\nmatchAll(string, regexp);\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "str", + "description": "the String to be searched", + "type": "String" + }, + { + "name": "regexp", + "description": "the regexp to be used for matching", + "type": "String" + } + ], + "return": { + "description": "2d Array of Strings found", + "type": "String[]" + } + } + ], + "return": { + "description": "2d Array of Strings found", + "type": "String[]" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "String Functions" + }, + { + "name": "nf", + "file": "src/utilities/string_functions.js", + "line": 176, + "itemtype": "method", + "description": "

Utility function for formatting numbers into strings. There are two\nversions: one for formatting floats, and one for formatting ints.

\n

The values for the digits, left, and right parameters should always\nbe positive integers.

\n

(NOTE): Be cautious when using left and right parameters as it prepends numbers of 0's if the parameter\nif greater than the current length of the number.

\n

For example if number is 123.2 and left parameter passed is 4 which is greater than length of 123\n(integer part) i.e 3 than result will be 0123.2. Same case for right parameter i.e. if right is 3 than\nthe result will be 123.200.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n let num1 = 321;\n let num2 = -1321;\n\n noStroke();\n fill(0);\n textSize(16);\n\n text(nf(num1, 4, 2), 10, 30);\n text(nf(num2, 4, 2), 10, 80);\n // Draw dividing line\n stroke(120);\n line(0, 50, width, 50);\n\n describe('“0321.00” middle top, “-1321.00” middle bottom canvas');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "num", + "description": "the Number to format", + "type": "Number|String" + }, + { + "name": "left", + "description": "number of digits to the left of the\ndecimal point", + "optional": 1, + "type": "Integer|String" + }, + { + "name": "right", + "description": "number of digits to the right of the\ndecimal point", + "optional": 1, + "type": "Integer|String" + } + ], + "return": { + "description": "formatted String", + "type": "String" + } + }, + { + "params": [ + { + "name": "nums", + "description": "the Numbers to format", + "type": "Array" + }, + { + "name": "left", + "optional": 1, + "type": "Integer|String" + }, + { + "name": "right", + "optional": 1, + "type": "Integer|String" + } + ], + "return": { + "description": "formatted Strings", + "type": "String[]" + } + } + ], + "return": { + "description": "formatted String", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "String Functions" + }, + { + "name": "nfc", + "file": "src/utilities/string_functions.js", + "line": 257, + "itemtype": "method", + "description": "Utility function for formatting numbers into strings and placing\nappropriate commas to mark units of 1000. There are two versions: one\nfor formatting ints, and one for formatting an array of ints. The value\nfor the right parameter should always be a positive integer.", + "example": [ + "
\n\nfunction setup() {\n background(200);\n let num = 11253106.115;\n let numArr = [1, 1, 2];\n\n noStroke();\n fill(0);\n textSize(12);\n\n // Draw formatted numbers\n text(nfc(num, 4), 10, 30);\n text(nfc(numArr, 2), 10, 80);\n\n // Draw dividing line\n stroke(120);\n line(0, 50, width, 50);\n\n describe('“11,253,106.115” top middle and “1.00,1.00,2.00” displayed bottom mid');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "num", + "description": "the Number to format", + "type": "Number|String" + }, + { + "name": "right", + "description": "number of digits to the right of the\ndecimal point", + "optional": 1, + "type": "Integer|String" + } + ], + "return": { + "description": "formatted String", + "type": "String" + } + }, + { + "params": [ + { + "name": "nums", + "description": "the Numbers to format", + "type": "Array" + }, + { + "name": "right", + "optional": 1, + "type": "Integer|String" + } + ], + "return": { + "description": "formatted Strings", + "type": "String[]" + } + } + ], + "return": { + "description": "formatted String", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "String Functions" + }, + { + "name": "nfp", + "file": "src/utilities/string_functions.js", + "line": 334, + "itemtype": "method", + "description": "Utility function for formatting numbers into strings. Similar to nf() but\nputs a \"+\" in front of positive numbers and a \"-\" in front of negative\nnumbers. There are two versions: one for formatting floats, and one for\nformatting ints. The values for left, and right parameters\nshould always be positive integers.", + "example": [ + "
\n\nfunction setup() {\n background(200);\n let num1 = 11253106.115;\n let num2 = -11253106.115;\n\n noStroke();\n fill(0);\n textSize(12);\n\n // Draw formatted numbers\n text(nfp(num1, 4, 2), 10, 30);\n text(nfp(num2, 4, 2), 10, 80);\n\n // Draw dividing line\n stroke(120);\n line(0, 50, width, 50);\n\n describe('“+11253106.11” top middle and “-11253106.11” displayed bottom middle');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "num", + "description": "the Number to format", + "type": "Number" + }, + { + "name": "left", + "description": "number of digits to the left of the decimal\npoint", + "optional": 1, + "type": "Integer" + }, + { + "name": "right", + "description": "number of digits to the right of the\ndecimal point", + "optional": 1, + "type": "Integer" + } + ], + "return": { + "description": "formatted String", + "type": "String" + } + }, + { + "params": [ + { + "name": "nums", + "description": "the Numbers to format", + "type": "Number[]" + }, + { + "name": "left", + "optional": 1, + "type": "Integer" + }, + { + "name": "right", + "optional": 1, + "type": "Integer" + } + ], + "return": { + "description": "formatted Strings", + "type": "String[]" + } + } + ], + "return": { + "description": "formatted String", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "String Functions" + }, + { + "name": "nfs", + "file": "src/utilities/string_functions.js", + "line": 411, + "itemtype": "method", + "description": "

Utility function for formatting numbers into strings. Similar to nf() but\nputs an additional \"_\" (space) in front of positive numbers just in case to align it with negative\nnumbers which includes \"-\" (minus) sign.

\n

The main usecase of nfs() can be seen when one wants to align the digits (place values) of a non-negative\nnumber with some negative number (See the example to get a clear picture).\nThere are two versions: one for formatting float, and one for formatting int.

\n

The values for the digits, left, and right parameters should always be positive integers.

\n

(IMP): The result on the canvas basically the expected alignment can vary based on the typeface you are using.

\n

(NOTE): Be cautious when using left and right parameters as it prepends numbers of 0's if the parameter\nif greater than the current length of the number.

\n

For example if number is 123.2 and left parameter passed is 4 which is greater than length of 123\n(integer part) i.e 3 than result will be 0123.2. Same case for right parameter i.e. if right is 3 than\nthe result will be 123.200.

\n", + "example": [ + "
\n\nfunction setup() {\n background(200);\n let num1 = 321;\n let num2 = -1321;\n\n noStroke();\n fill(0);\n textSize(16);\n\n // nfs() aligns num1 (positive number) with num2 (negative number) by\n // adding a blank space in front of the num1 (positive number)\n // [left = 4] in num1 add one 0 in front, to align the digits with num2\n // [right = 2] in num1 and num2 adds two 0's after both numbers\n // To see the differences check the example of nf() too.\n text(nfs(num1, 4, 2), 10, 30);\n text(nfs(num2, 4, 2), 10, 80);\n // Draw dividing line\n stroke(120);\n line(0, 50, width, 50);\n\n describe('“0321.00” top middle and “-1321.00” displayed bottom middle');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "num", + "description": "the Number to format", + "type": "Number" + }, + { + "name": "left", + "description": "number of digits to the left of the decimal\npoint", + "optional": 1, + "type": "Integer" + }, + { + "name": "right", + "description": "number of digits to the right of the\ndecimal point", + "optional": 1, + "type": "Integer" + } + ], + "return": { + "description": "formatted String", + "type": "String" + } + }, + { + "params": [ + { + "name": "nums", + "description": "the Numbers to format", + "type": "Array" + }, + { + "name": "left", + "optional": 1, + "type": "Integer" + }, + { + "name": "right", + "optional": 1, + "type": "Integer" + } + ], + "return": { + "description": "formatted Strings", + "type": "String[]" + } + } + ], + "return": { + "description": "formatted String", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "String Functions" + }, + { + "name": "split", + "file": "src/utilities/string_functions.js", + "line": 451, + "itemtype": "method", + "description": "

The split() function maps to String.split(), it breaks a String into\npieces using a character or string as the delimiter. The delim parameter\nspecifies the character or characters that mark the boundaries between\neach piece. A String[] array is returned that contains each of the pieces.

\n

The splitTokens() function works in a similar fashion, except that it\nsplits using a range of characters instead of a specific character or\nsequence.

\n", + "example": [ + "
\n\nlet names = 'Pat,Xio,Alex';\nlet splitString = split(names, ',');\ntext(splitString[0], 5, 30);\ntext(splitString[1], 5, 50);\ntext(splitString[2], 5, 70);\ndescribe('“Pat” top left, “Xio” mid left, and “Alex” displayed bottom left');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "the String to be split", + "type": "String" + }, + { + "name": "delim", + "description": "the String used to separate the data", + "type": "String" + } + ], + "return": { + "description": "Array of Strings", + "type": "String[]" + } + } + ], + "return": { + "description": "Array of Strings", + "type": "String[]" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "String Functions" + }, + { + "name": "splitTokens", + "file": "src/utilities/string_functions.js", + "line": 482, + "itemtype": "method", + "description": "

The splitTokens() function splits a String at one or many character\ndelimiters or \"tokens.\" The delim parameter specifies the character or\ncharacters to be used as a boundary.

\n

If no delim characters are specified, any whitespace character is used to\nsplit. Whitespace characters include tab (\\t), line feed (\\n), carriage\nreturn (\\r), form feed (\\f), and space.

\n", + "example": [ + "
\n\nfunction setup() {\n let myStr = 'Mango, Banana, Lime';\n let myStrArr = splitTokens(myStr, ',');\n\n print(myStrArr); // prints : [\"Mango\",\" Banana\",\" Lime\"]\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "the String to be split", + "type": "String" + }, + { + "name": "delim", + "description": "list of individual Strings that will be used as\nseparators", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "Array of Strings", + "type": "String[]" + } + } + ], + "return": { + "description": "Array of Strings", + "type": "String[]" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "String Functions" + }, + { + "name": "trim", + "file": "src/utilities/string_functions.js", + "line": 532, + "itemtype": "method", + "description": "Removes whitespace characters from the beginning and end of a String. In\naddition to standard whitespace characters such as space, carriage return,\nand tab, this function also removes the Unicode \"nbsp\" character.", + "example": [ + "
\n\nlet string = trim(' No new lines\\n ');\ntext(string + ' here', 2, 50);\ndescribe('“No new lines here” displayed center canvas');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "str", + "description": "a String to be trimmed", + "type": "String" + } + ], + "return": { + "description": "a trimmed String", + "type": "String" + } + }, + { + "params": [ + { + "name": "strs", + "description": "an Array of Strings to be trimmed", + "type": "Array" + } + ], + "return": { + "description": "an Array of trimmed Strings", + "type": "String[]" + } + } + ], + "return": { + "description": "a trimmed String", + "type": "String" + }, + "class": "p5", + "static": false, + "module": "Data", + "submodule": "String Functions" + }, + { + "name": "day", + "file": "src/utilities/time_date.js", + "line": 25, + "itemtype": "method", + "description": "p5.js communicates with the clock on your computer. The day() function\nreturns the current day as a value from 1 - 31.", + "example": [ + "
\n\nlet d = day();\ntext('Current day: \\n' + d, 5, 50);\ndescribe('Current day is displayed');\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "the current day", + "type": "Integer" + } + } + ], + "return": { + "description": "the current day", + "type": "Integer" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Time & Date" + }, + { + "name": "hour", + "file": "src/utilities/time_date.js", + "line": 44, + "itemtype": "method", + "description": "p5.js communicates with the clock on your computer. The hour() function\nreturns the current hour as a value from 0 - 23.", + "example": [ + "
\n\nlet h = hour();\ntext('Current hour:\\n' + h, 5, 50);\ndescribe('Current hour is displayed');\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "the current hour", + "type": "Integer" + } + } + ], + "return": { + "description": "the current hour", + "type": "Integer" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Time & Date" + }, + { + "name": "minute", + "file": "src/utilities/time_date.js", + "line": 63, + "itemtype": "method", + "description": "p5.js communicates with the clock on your computer. The minute() function\nreturns the current minute as a value from 0 - 59.", + "example": [ + "
\n\nlet m = minute();\ntext('Current minute: \\n' + m, 5, 50);\ndescribe('Current minute is displayed');\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "the current minute", + "type": "Integer" + } + } + ], + "return": { + "description": "the current minute", + "type": "Integer" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Time & Date" + }, + { + "name": "millis", + "file": "src/utilities/time_date.js", + "line": 83, + "itemtype": "method", + "description": "Returns the number of milliseconds (thousandths of a second) since\nstarting the sketch (when setup() is called). This information is often\nused for timing events and animation sequences.", + "example": [ + "
\n\nlet millisecond = millis();\ntext('Milliseconds \\nrunning: \\n' + millisecond, 5, 40);\ndescribe('number of milliseconds since sketch has started displayed');\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "the number of milliseconds since starting the sketch", + "type": "Number" + } + } + ], + "return": { + "description": "the number of milliseconds since starting the sketch", + "type": "Number" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Time & Date" + }, + { + "name": "month", + "file": "src/utilities/time_date.js", + "line": 107, + "itemtype": "method", + "description": "p5.js communicates with the clock on your computer. The month() function\nreturns the current month as a value from 1 - 12.", + "example": [ + "
\n\nlet m = month();\ntext('Current month: \\n' + m, 5, 50);\ndescribe('Current month is displayed');\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "the current month", + "type": "Integer" + } + } + ], + "return": { + "description": "the current month", + "type": "Integer" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Time & Date" + }, + { + "name": "second", + "file": "src/utilities/time_date.js", + "line": 127, + "itemtype": "method", + "description": "p5.js communicates with the clock on your computer. The second() function\nreturns the current second as a value from 0 - 59.", + "example": [ + "
\n\nlet s = second();\ntext('Current second: \\n' + s, 5, 50);\ndescribe('Current second is displayed');\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "the current second", + "type": "Integer" + } + } + ], + "return": { + "description": "the current second", + "type": "Integer" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Time & Date" + }, + { + "name": "year", + "file": "src/utilities/time_date.js", + "line": 146, + "itemtype": "method", + "description": "p5.js communicates with the clock on your computer. The year() function\nreturns the current year as an integer (2014, 2015, 2016, etc).", + "example": [ + "
\n\nlet y = year();\ntext('Current year: \\n' + y, 5, 50);\ndescribe('Current year is displayed');\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "the current year", + "type": "Integer" + } + } + ], + "return": { + "description": "the current year", + "type": "Integer" + }, + "class": "p5", + "static": false, + "module": "IO", + "submodule": "Time & Date" + }, + { + "name": "beginGeometry", + "file": "src/webgl/3d_primitives.js", + "line": 85, + "itemtype": "method", + "description": "

Starts creating a new p5.Geometry. Subsequent shapes drawn will be added\nto the geometry and then returned when\nendGeometry() is called. One can also use\nbuildGeometry() to pass a function that\ndraws shapes.

\n

If you need to draw complex shapes every frame which don't change over time,\ncombining them upfront with beginGeometry() and endGeometry() and then\ndrawing that will run faster than repeatedly drawing the individual pieces.

\n", + "example": [ + "
\n\nlet shapes;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n makeShapes();\n}\n\nfunction makeShapes() {\n beginGeometry();\n scale(0.18);\n\n push();\n translate(100, -50);\n scale(0.5);\n rotateX(PI/4);\n cone();\n pop();\n cone();\n\n beginShape();\n vertex(-20, -50);\n quadraticVertex(\n -40, -70,\n 0, -60\n );\n endShape();\n\n beginShape(TRIANGLE_STRIP);\n for (let y = 20; y <= 60; y += 10) {\n for (let x of [20, 60]) {\n vertex(x, y);\n }\n }\n endShape();\n\n beginShape();\n vertex(-100, -120);\n vertex(-120, -110);\n vertex(-105, -100);\n endShape();\n\n shapes = endGeometry();\n}\n\nfunction draw() {\n background(255);\n lights();\n orbitControl();\n model(shapes);\n}\n\n
" + ], + "alt": "A series of different flat, curved, and 3D shapes floating in space.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "endGeometry", + "file": "src/webgl/3d_primitives.js", + "line": 98, + "itemtype": "method", + "description": "Finishes creating a new p5.Geometry that was\nstarted using beginGeometry(). One can also\nuse buildGeometry() to pass a function that\ndraws shapes.", + "example": [], + "overloads": [ + { + "params": [], + "return": { + "description": "The model that was built.", + "type": "p5.Geometry" + } + } + ], + "return": { + "description": "The model that was built.", + "type": "p5.Geometry" + }, + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "buildGeometry", + "file": "src/webgl/3d_primitives.js", + "line": 163, + "itemtype": "method", + "description": "

Creates a new p5.Geometry that contains all\nthe shapes drawn in a provided callback function. The returned combined shape\ncan then be drawn all at once using model().

\n

If you need to draw complex shapes every frame which don't change over time,\ncombining them with buildGeometry() once and then drawing that will run\nfaster than repeatedly drawing the individual pieces.

\n

One can also draw shapes directly between\nbeginGeometry() and\nendGeometry() instead of using a callback\nfunction.

\n", + "example": [ + "
\n\nlet particles;\nlet button;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n button = createButton('New');\n button.mousePressed(makeParticles);\n makeParticles();\n}\n\nfunction makeParticles() {\n if (particles) freeGeometry(particles);\n\n particles = buildGeometry(() => {\n for (let i = 0; i < 60; i++) {\n push();\n translate(\n randomGaussian(0, 20),\n randomGaussian(0, 20),\n randomGaussian(0, 20)\n );\n sphere(5);\n pop();\n }\n });\n}\n\nfunction draw() {\n background(255);\n noStroke();\n lights();\n orbitControl();\n model(particles);\n}\n\n
" + ], + "alt": "A cluster of spheres.", + "overloads": [ + { + "params": [ + { + "name": "callback", + "description": "A function that draws shapes.", + "type": "Function" + } + ], + "return": { + "description": "The model that was built from the callback function.", + "type": "p5.Geometry" + } + } + ], + "return": { + "description": "The model that was built from the callback function.", + "type": "p5.Geometry" + }, + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "freeGeometry", + "file": "src/webgl/3d_primitives.js", + "line": 179, + "itemtype": "method", + "description": "

Clears the resources of a model to free up browser memory. A model whose\nresources have been cleared can still be drawn, but the first time it is\ndrawn again, it might take longer.

\n

This method works on models generated with\nbuildGeometry() as well as those loaded\nfrom loadModel().

\n", + "example": [], + "overloads": [ + { + "params": [ + { + "name": "geometry", + "description": "The geometry whose resources should be freed", + "type": "p5.Geometry" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "plane", + "file": "src/webgl/3d_primitives.js", + "line": 219, + "itemtype": "method", + "chainable": 1, + "description": "Draw a plane with given a width and height", + "example": [ + "
\n\n// draw a plane\n// with width 50 and height 50\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('a white plane with black wireframe lines');\n}\n\nfunction draw() {\n background(200);\n plane(50, 50);\n}\n\n
" + ], + "alt": "Nothing displayed on canvas\nRotating interior view of a box with sides that change color.\n3d red and green gradient.\nRotating interior view of a cylinder with sides that change color.\nRotating view of a cylinder with sides that change color.\n3d red and green gradient.\nrotating view of a multi-colored cylinder with concave sides.", + "overloads": [ + { + "params": [ + { + "name": "width", + "description": "width of the plane", + "optional": 1, + "type": "Number" + }, + { + "name": "height", + "description": "height of the plane", + "optional": 1, + "type": "Number" + }, + { + "name": "detailX", + "description": "Optional number of triangle\nsubdivisions in x-dimension", + "optional": 1, + "type": "Integer" + }, + { + "name": "detailY", + "description": "Optional number of triangle\nsubdivisions in y-dimension", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "box", + "file": "src/webgl/3d_primitives.js", + "line": 300, + "itemtype": "method", + "chainable": 1, + "description": "Draw a box with given width, height and depth", + "example": [ + "
\n\n// draw a spinning box\n// with width, height and depth of 50\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n describe('a white box rotating in 3D space');\n}\n\nfunction draw() {\n background(200);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n box(50);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "width", + "description": "width of the box", + "optional": 1, + "type": "Number" + }, + { + "name": "height", + "description": "height of the box", + "optional": 1, + "type": "Number" + }, + { + "name": "depth", + "description": "depth of the box", + "optional": 1, + "type": "Number" + }, + { + "name": "detailX", + "description": "Optional number of triangle\nsubdivisions in x-dimension", + "optional": 1, + "type": "Integer" + }, + { + "name": "detailY", + "description": "Optional number of triangle\nsubdivisions in y-dimension", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "sphere", + "file": "src/webgl/3d_primitives.js", + "line": 464, + "itemtype": "method", + "chainable": 1, + "description": "

Draw a sphere with given radius.

\n

DetailX and detailY determines the number of subdivisions in the x-dimension\nand the y-dimension of a sphere. More subdivisions make the sphere seem\nsmoother. The recommended maximum values are both 24. Using a value greater\nthan 24 may cause a warning or slow down the browser.

\n", + "example": [ + "
\n\n// draw a sphere with radius 40\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('a white sphere with black wireframe lines');\n}\n\nfunction draw() {\n background(205, 102, 94);\n sphere(40);\n}\n\n
", + "
\n\nlet detailX;\n// slide to see how detailX works\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n detailX = createSlider(3, 24, 3);\n detailX.position(10, height + 5);\n detailX.style('width', '80px');\n describe(\n 'a white sphere with low detail on the x-axis, including a slider to adjust detailX'\n );\n}\n\nfunction draw() {\n background(205, 105, 94);\n rotateY(millis() / 1000);\n sphere(40, detailX.value(), 16);\n}\n\n
", + "
\n\nlet detailY;\n// slide to see how detailY works\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n detailY = createSlider(3, 16, 3);\n detailY.position(10, height + 5);\n detailY.style('width', '80px');\n describe(\n 'a white sphere with low detail on the y-axis, including a slider to adjust detailY'\n );\n}\n\nfunction draw() {\n background(205, 105, 94);\n rotateY(millis() / 1000);\n sphere(40, 16, detailY.value());\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "radius", + "description": "radius of circle", + "optional": 1, + "type": "Number" + }, + { + "name": "detailX", + "description": "optional number of subdivisions in x-dimension", + "optional": 1, + "type": "Integer" + }, + { + "name": "detailY", + "description": "optional number of subdivisions in y-dimension", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "cylinder", + "file": "src/webgl/3d_primitives.js", + "line": 683, + "itemtype": "method", + "chainable": 1, + "description": "

Draw a cylinder with given radius and height

\n

DetailX and detailY determines the number of subdivisions in the x-dimension\nand the y-dimension of a cylinder. More subdivisions make the cylinder seem smoother.\nThe recommended maximum value for detailX is 24. Using a value greater than 24\nmay cause a warning or slow down the browser.

\n", + "example": [ + "
\n\n// draw a spinning cylinder\n// with radius 20 and height 50\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('a rotating white cylinder');\n}\n\nfunction draw() {\n background(205, 105, 94);\n rotateX(frameCount * 0.01);\n rotateZ(frameCount * 0.01);\n cylinder(20, 50);\n}\n\n
", + "
\n\n// slide to see how detailX works\nlet detailX;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n detailX = createSlider(3, 24, 3);\n detailX.position(10, height + 5);\n detailX.style('width', '80px');\n describe(\n 'a rotating white cylinder with limited X detail, with a slider that adjusts detailX'\n );\n}\n\nfunction draw() {\n background(205, 105, 94);\n rotateY(millis() / 1000);\n cylinder(20, 75, detailX.value(), 1);\n}\n\n
", + "
\n\n// slide to see how detailY works\nlet detailY;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n detailY = createSlider(1, 16, 1);\n detailY.position(10, height + 5);\n detailY.style('width', '80px');\n describe(\n 'a rotating white cylinder with limited Y detail, with a slider that adjusts detailY'\n );\n}\n\nfunction draw() {\n background(205, 105, 94);\n rotateY(millis() / 1000);\n cylinder(20, 75, 16, detailY.value());\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "radius", + "description": "radius of the surface", + "optional": 1, + "type": "Number" + }, + { + "name": "height", + "description": "height of the cylinder", + "optional": 1, + "type": "Number" + }, + { + "name": "detailX", + "description": "number of subdivisions in x-dimension;\ndefault is 24", + "optional": 1, + "type": "Integer" + }, + { + "name": "detailY", + "description": "number of subdivisions in y-dimension;\ndefault is 1", + "optional": 1, + "type": "Integer" + }, + { + "name": "bottomCap", + "description": "whether to draw the bottom of the cylinder", + "optional": 1, + "type": "Boolean" + }, + { + "name": "topCap", + "description": "whether to draw the top of the cylinder", + "optional": 1, + "type": "Boolean" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "cone", + "file": "src/webgl/3d_primitives.js", + "line": 825, + "itemtype": "method", + "chainable": 1, + "description": "

Draw a cone with given radius and height

\n

DetailX and detailY determine the number of subdivisions in the x-dimension and\nthe y-dimension of a cone. More subdivisions make the cone seem smoother. The\nrecommended maximum value for detailX is 24. Using a value greater than 24\nmay cause a warning or slow down the browser.

\n", + "example": [ + "
\n\n// draw a spinning cone\n// with radius 40 and height 70\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('a rotating white cone');\n}\n\nfunction draw() {\n background(200);\n rotateX(frameCount * 0.01);\n rotateZ(frameCount * 0.01);\n cone(40, 70);\n}\n\n
", + "
\n\n// slide to see how detailx works\nlet detailX;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n detailX = createSlider(3, 16, 3);\n detailX.position(10, height + 5);\n detailX.style('width', '80px');\n describe(\n 'a rotating white cone with limited X detail, with a slider that adjusts detailX'\n );\n}\n\nfunction draw() {\n background(205, 102, 94);\n rotateY(millis() / 1000);\n cone(30, 65, detailX.value(), 16);\n}\n\n
", + "
\n\n// slide to see how detailY works\nlet detailY;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n detailY = createSlider(3, 16, 3);\n detailY.position(10, height + 5);\n detailY.style('width', '80px');\n describe(\n 'a rotating white cone with limited Y detail, with a slider that adjusts detailY'\n );\n}\n\nfunction draw() {\n background(205, 102, 94);\n rotateY(millis() / 1000);\n cone(30, 65, 16, detailY.value());\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "radius", + "description": "radius of the bottom surface", + "optional": 1, + "type": "Number" + }, + { + "name": "height", + "description": "height of the cone", + "optional": 1, + "type": "Number" + }, + { + "name": "detailX", + "description": "number of segments,\nthe more segments the smoother geometry\ndefault is 24", + "optional": 1, + "type": "Integer" + }, + { + "name": "detailY", + "description": "number of segments,\nthe more segments the smoother geometry\ndefault is 1", + "optional": 1, + "type": "Integer" + }, + { + "name": "cap", + "description": "whether to draw the base of the cone", + "optional": 1, + "type": "Boolean" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "ellipsoid", + "file": "src/webgl/3d_primitives.js", + "line": 946, + "itemtype": "method", + "chainable": 1, + "description": "

Draw an ellipsoid with given radius

\n

DetailX and detailY determine the number of subdivisions in the x-dimension and\nthe y-dimension of a cone. More subdivisions make the ellipsoid appear to be smoother.\nAvoid detail number above 150, it may crash the browser.

\n", + "example": [ + "
\n\n// draw an ellipsoid\n// with radius 30, 40 and 40.\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('a white 3d ellipsoid');\n}\n\nfunction draw() {\n background(205, 105, 94);\n ellipsoid(30, 40, 40);\n}\n\n
", + "
\n\n// slide to see how detailX works\nlet detailX;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n detailX = createSlider(2, 24, 12);\n detailX.position(10, height + 5);\n detailX.style('width', '80px');\n describe(\n 'a rotating white ellipsoid with limited X detail, with a slider that adjusts detailX'\n );\n}\n\nfunction draw() {\n background(205, 105, 94);\n rotateY(millis() / 1000);\n ellipsoid(30, 40, 40, detailX.value(), 8);\n}\n\n
", + "
\n\n// slide to see how detailY works\nlet detailY;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n detailY = createSlider(2, 24, 6);\n detailY.position(10, height + 5);\n detailY.style('width', '80px');\n describe(\n 'a rotating white ellipsoid with limited Y detail, with a slider that adjusts detailY'\n );\n}\n\nfunction draw() {\n background(205, 105, 9);\n rotateY(millis() / 1000);\n ellipsoid(30, 40, 40, 12, detailY.value());\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "radiusx", + "description": "x-radius of ellipsoid", + "optional": 1, + "type": "Number" + }, + { + "name": "radiusy", + "description": "y-radius of ellipsoid", + "optional": 1, + "type": "Number" + }, + { + "name": "radiusz", + "description": "z-radius of ellipsoid", + "optional": 1, + "type": "Number" + }, + { + "name": "detailX", + "description": "number of segments,\nthe more segments the smoother geometry\ndefault is 24. Avoid detail number above\n150, it may crash the browser.", + "optional": 1, + "type": "Integer" + }, + { + "name": "detailY", + "description": "number of segments,\nthe more segments the smoother geometry\ndefault is 16. Avoid detail number above\n150, it may crash the browser.", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "torus", + "file": "src/webgl/3d_primitives.js", + "line": 1095, + "itemtype": "method", + "chainable": 1, + "description": "

Draw a torus with given radius and tube radius

\n

DetailX and detailY determine the number of subdivisions in the x-dimension and\nthe y-dimension of a torus. More subdivisions make the torus appear to be smoother.\nThe default and maximum values for detailX and detailY are 24 and 16, respectively.\nSetting them to relatively small values like 4 and 6 allows you to create new\nshapes other than a torus.

\n", + "example": [ + "
\n\n// draw a spinning torus\n// with ring radius 30 and tube radius 15\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n describe('a rotating white torus');\n}\n\nfunction draw() {\n background(205, 102, 94);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n torus(30, 15);\n}\n\n
", + "
\n\n// slide to see how detailX works\nlet detailX;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n detailX = createSlider(3, 24, 3);\n detailX.position(10, height + 5);\n detailX.style('width', '80px');\n describe(\n 'a rotating white torus with limited X detail, with a slider that adjusts detailX'\n );\n}\n\nfunction draw() {\n background(205, 102, 94);\n rotateY(millis() / 1000);\n torus(30, 15, detailX.value(), 12);\n}\n\n
", + "
\n\n// slide to see how detailY works\nlet detailY;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n detailY = createSlider(3, 16, 3);\n detailY.position(10, height + 5);\n detailY.style('width', '80px');\n describe(\n 'a rotating white torus with limited Y detail, with a slider that adjusts detailY'\n );\n}\n\nfunction draw() {\n background(205, 102, 94);\n rotateY(millis() / 1000);\n torus(30, 15, 16, detailY.value());\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "radius", + "description": "radius of the whole ring", + "optional": 1, + "type": "Number" + }, + { + "name": "tubeRadius", + "description": "radius of the tube", + "optional": 1, + "type": "Number" + }, + { + "name": "detailX", + "description": "number of segments in x-dimension,\nthe more segments the smoother geometry\ndefault is 24", + "optional": 1, + "type": "Integer" + }, + { + "name": "detailY", + "description": "number of segments in y-dimension,\nthe more segments the smoother geometry\ndefault is 16", + "optional": 1, + "type": "Integer" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "orbitControl", + "file": "src/webgl/interaction.js", + "line": 71, + "itemtype": "method", + "chainable": 1, + "description": "Allows movement around a 3D sketch using a mouse or trackpad or touch.\nLeft-clicking and dragging or swipe motion will rotate the camera position\nabout the center of the sketch, right-clicking and dragging or multi-swipe\nwill pan the camera position without rotation, and using the mouse wheel\n(scrolling) or pinch in/out will move the camera further or closer\nfrom the center of the sketch. This function can be called with parameters\ndictating sensitivity to mouse/touch movement along the X and Y axes.\nCalling this function without parameters is equivalent to calling\norbitControl(1,1). To reverse direction of movement in either axis,\nenter a negative number for sensitivity.", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n normalMaterial();\n describe(\n 'Camera orbits around a box when mouse is hold-clicked & then moved.'\n );\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n}\nfunction draw() {\n background(200);\n\n // If you execute the line commented out instead of next line, the direction of rotation\n // will be the direction the mouse or touch pointer moves, not around the X or Y axis.\n orbitControl();\n // orbitControl(1, 1, 1, {freeRotation: true});\n\n rotateY(0.5);\n box(30, 50);\n}\n\n
" + ], + "alt": "Camera orbits around a box when mouse is hold-clicked & then moved.", + "overloads": [ + { + "params": [ + { + "name": "sensitivityX", + "description": "sensitivity to mouse movement along X axis", + "optional": 1, + "type": "Number" + }, + { + "name": "sensitivityY", + "description": "sensitivity to mouse movement along Y axis", + "optional": 1, + "type": "Number" + }, + { + "name": "sensitivityZ", + "description": "sensitivity to scroll movement along Z axis", + "optional": 1, + "type": "Number" + }, + { + "name": "options", + "description": "An optional object that can contain additional settings,\ndisableTouchActions - Boolean, default value is true.\nSetting this to true makes mobile interactions smoother by preventing\naccidental interactions with the page while orbiting. But if you're already\ndoing it via css or want the default touch actions, consider setting it to false.\nfreeRotation - Boolean, default value is false.\nBy default, horizontal movement of the mouse or touch pointer rotates the camera\naround the y-axis, and vertical movement rotates the camera around the x-axis.\nBut if setting this option to true, the camera always rotates in the direction\nthe pointer is moving. For zoom and move, the behavior is the same regardless of\ntrue/false.", + "optional": 1, + "type": "Object" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Interaction" + }, + { + "name": "debugMode", + "file": "src/webgl/interaction.js", + "line": 564, + "itemtype": "method", + "description": "

debugMode() helps visualize 3D space by adding a grid to indicate where the\n‘ground’ is in a sketch and an axes icon which indicates the +X, +Y, and +Z\ndirections. This function can be called without parameters to create a\ndefault grid and axes icon, or it can be called according to the examples\nabove to customize the size and position of the grid and/or axes icon. The\ngrid is drawn using the most recently set stroke color and weight. To\nspecify these parameters, add a call to stroke() and strokeWeight()\njust before the end of the draw() loop.

\n

By default, the grid will run through the origin (0,0,0) of the sketch\nalong the XZ plane\nand the axes icon will be offset from the origin. Both the grid and axes\nicon will be sized according to the current canvas size. Note that because the\ngrid runs parallel to the default camera view, it is often helpful to use\ndebugMode along with orbitControl to allow full view of the grid.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, -30, 100, 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n normalMaterial();\n debugMode();\n describe(\n 'a 3D box is centered on a grid in a 3D sketch. an icon indicates the direction of each axis: a red line points +X, a green line +Y, and a blue line +Z. the grid and icon disappear when the spacebar is pressed.'\n );\n}\n\nfunction draw() {\n background(200);\n orbitControl();\n box(15, 30);\n // Press the spacebar to turn debugMode off!\n if (keyIsDown(32)) {\n noDebugMode();\n }\n}\n\n
", + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, -30, 100, 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n normalMaterial();\n debugMode(GRID);\n describe('a 3D box is centered on a grid in a 3D sketch.');\n}\n\nfunction draw() {\n background(200);\n orbitControl();\n box(15, 30);\n}\n\n
", + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, -30, 100, 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n normalMaterial();\n debugMode(AXES);\n describe(\n 'a 3D box is centered in a 3D sketch. an icon indicates the direction of each axis: a red line points +X, a green line +Y, and a blue line +Z.'\n );\n}\n\nfunction draw() {\n background(200);\n orbitControl();\n box(15, 30);\n}\n\n
", + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, -30, 100, 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n normalMaterial();\n debugMode(GRID, 100, 10, 0, 0, 0);\n describe('a 3D box is centered on a grid in a 3D sketch');\n}\n\nfunction draw() {\n background(200);\n orbitControl();\n box(15, 30);\n}\n\n
", + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, -30, 100, 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n normalMaterial();\n debugMode(100, 10, 0, 0, 0, 20, 0, -40, 0);\n describe(\n 'a 3D box is centered on a grid in a 3D sketch. an icon indicates the direction of each axis: a red line points +X, a green line +Y, and a blue line +Z.'\n );\n}\n\nfunction draw() {\n noStroke();\n background(200);\n orbitControl();\n box(15, 30);\n // set the stroke color and weight for the grid!\n stroke(255, 0, 150);\n strokeWeight(0.8);\n}\n\n
" + ], + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "mode", + "description": "either GRID or AXES", + "type": "Constant" + } + ] + }, + { + "params": [ + { + "name": "mode", + "type": "Constant" + }, + { + "name": "gridSize", + "description": "size of one side of the grid", + "optional": 1, + "type": "Number" + }, + { + "name": "gridDivisions", + "description": "number of divisions in the grid", + "optional": 1, + "type": "Number" + }, + { + "name": "xOff", + "description": "X axis offset from origin (0,0,0)", + "optional": 1, + "type": "Number" + }, + { + "name": "yOff", + "description": "Y axis offset from origin (0,0,0)", + "optional": 1, + "type": "Number" + }, + { + "name": "zOff", + "description": "Z axis offset from origin (0,0,0)", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "mode", + "type": "Constant" + }, + { + "name": "axesSize", + "description": "size of axes icon", + "optional": 1, + "type": "Number" + }, + { + "name": "xOff", + "optional": 1, + "type": "Number" + }, + { + "name": "yOff", + "optional": 1, + "type": "Number" + }, + { + "name": "zOff", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "gridSize", + "optional": 1, + "type": "Number" + }, + { + "name": "gridDivisions", + "optional": 1, + "type": "Number" + }, + { + "name": "gridXOff", + "optional": 1, + "type": "Number" + }, + { + "name": "gridYOff", + "optional": 1, + "type": "Number" + }, + { + "name": "gridZOff", + "optional": 1, + "type": "Number" + }, + { + "name": "axesSize", + "optional": 1, + "type": "Number" + }, + { + "name": "axesXOff", + "optional": 1, + "type": "Number" + }, + { + "name": "axesYOff", + "optional": 1, + "type": "Number" + }, + { + "name": "axesZOff", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Interaction" + }, + { + "name": "noDebugMode", + "file": "src/webgl/interaction.js", + "line": 636, + "itemtype": "method", + "description": "Turns off debugMode() in a 3D sketch.", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, -30, 100, 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n normalMaterial();\n debugMode();\n describe(\n 'a 3D box is centered on a grid in a 3D sketch. an icon indicates the direction of each axis: a red line points +X, a green line +Y, and a blue line +Z. the grid and icon disappear when the spacebar is pressed.'\n );\n}\n\nfunction draw() {\n background(200);\n orbitControl();\n box(15, 30);\n // Press the spacebar to turn debugMode off!\n if (keyIsDown(32)) {\n noDebugMode();\n }\n}\n\n
" + ], + "alt": "a 3D box is centered on a grid in a 3D sketch. an icon\nindicates the direction of each axis: a red line points +X,\na green line +Y, and a blue line +Z. the grid and icon disappear when the\nspacebar is pressed.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Interaction" + }, + { + "name": "ambientLight", + "file": "src/webgl/light.js", + "line": 118, + "itemtype": "method", + "chainable": 1, + "description": "

Creates an ambient light with the given color.

\n

Ambient light does not come from a specific direction.\nObjects are evenly lit from all sides. Ambient lights are\nalmost always used in combination with other types of lights.

\n

Note: lights need to be called (whether directly or indirectly)\nwithin draw() to remain persistent in a looping program.\nPlacing them in setup() will cause them to only have an effect\nthe first time through the loop.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n noStroke();\n describe('sphere with coral color under black light');\n}\nfunction draw() {\n background(100);\n ambientLight(0); // black light (no light)\n ambientMaterial(255, 127, 80); // coral material\n sphere(40);\n}\n\n
", + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n noStroke();\n describe('sphere with coral color under white light');\n}\nfunction draw() {\n background(100);\n ambientLight(255); // white light\n ambientMaterial(255, 127, 80); // coral material\n sphere(40);\n}\n\n
", + "
\n\nfunction setup() {\n createCanvas(100,100,WEBGL);\n camera(0,-100,300);\n}\nfunction draw() {\n background(230);\n ambientMaterial(237,34,93); //Pink Material\n ambientLight(mouseY);\n //As you move the mouse up to down it changes from no ambient light to full ambient light.\n rotateY(millis()/2000);\n box(100);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "red or hue value relative to\nthe current color range", + "type": "Number" + }, + { + "name": "v2", + "description": "green or saturation value\nrelative to the current color range", + "type": "Number" + }, + { + "name": "v3", + "description": "blue or brightness value\nrelative to the current color range", + "type": "Number" + }, + { + "name": "alpha", + "description": "alpha value relative to current\ncolor range (default is 0-255)", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "gray", + "description": "number specifying value between\nwhite and black", + "type": "Number" + }, + { + "name": "alpha", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "a color string", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "values", + "description": "an array containing the red,green,blue &\nand alpha components of the color", + "type": "Number[]" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "color as a p5.Color", + "type": "p5.Color" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Lights" + }, + { + "name": "specularColor", + "file": "src/webgl/light.js", + "line": 232, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the color of the specular highlight of a non-ambient light\n(i.e. all lights except ambientLight()).

\n

specularColor() affects only the lights which are created after\nit in the code.

\n

This function is used in combination with\nspecularMaterial().\nIf a geometry does not use specularMaterial(), this function\nwill have no effect.

\n

The default color is white (255, 255, 255), which is used if\nspecularColor() is not explicitly called.

\n

Note: specularColor is equivalent to the Processing function\nlightSpecular.

\n", + "example": [ + "
\n\nlet setRedSpecularColor = true;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n noStroke();\n describe(\n 'Sphere with specular highlight. Clicking the mouse toggles the specular highlight color between red and the default white.'\n );\n}\n\nfunction draw() {\n background(0);\n\n ambientLight(60);\n\n // add a point light to showcase specular color\n // -- use mouse location to position the light\n let lightPosX = mouseX - width / 2;\n let lightPosY = mouseY - height / 2;\n // -- set the light's specular color\n if (setRedSpecularColor) {\n specularColor(255, 0, 0); // red specular highlight\n }\n // -- create the light\n pointLight(200, 200, 200, lightPosX, lightPosY, 50); // white light\n\n // use specular material with high shininess\n specularMaterial(150);\n shininess(50);\n\n sphere(30, 64, 64);\n}\n\nfunction mouseClicked() {\n setRedSpecularColor = !setRedSpecularColor;\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "red or hue value relative to\nthe current color range", + "type": "Number" + }, + { + "name": "v2", + "description": "green or saturation value\nrelative to the current color range", + "type": "Number" + }, + { + "name": "v3", + "description": "blue or brightness value\nrelative to the current color range", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "gray", + "description": "number specifying value between\nwhite and black", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "color as a CSS string", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "values", + "description": "color as an array containing the\nred, green, and blue components", + "type": "Number[]" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "color as a p5.Color", + "type": "p5.Color" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Lights" + }, + { + "name": "directionalLight", + "file": "src/webgl/light.js", + "line": 330, + "itemtype": "method", + "chainable": 1, + "description": "

Creates a directional light with the given color and direction.

\n

Directional light comes from one direction.\nThe direction is specified as numbers inclusively between -1 and 1.\nFor example, setting the direction as (0, -1, 0) will cause the\ngeometry to be lit from below (since the light will be facing\ndirectly upwards). Similarly, setting the direction as (1, 0, 0)\nwill cause the geometry to be lit from the left (since the light\nwill be facing directly rightwards).

\n

Directional lights do not have a specific point of origin, and\ntherefore cannot be positioned closer or farther away from a geometry.

\n

A maximum of 5 directional lights can be active at once.

\n

Note: lights need to be called (whether directly or indirectly)\nwithin draw() to remain persistent in a looping program.\nPlacing them in setup() will cause them to only have an effect\nthe first time through the loop.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe(\n 'scene with sphere and directional light. The direction of the light is controlled with the mouse position.'\n );\n}\nfunction draw() {\n background(0);\n //move your mouse to change light direction\n let dirX = (mouseX / width - 0.5) * 2;\n let dirY = (mouseY / height - 0.5) * 2;\n directionalLight(250, 250, 250, -dirX, -dirY, -1);\n noStroke();\n sphere(40);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "red or hue value relative to the current\ncolor range", + "type": "Number" + }, + { + "name": "v2", + "description": "green or saturation value relative to the\ncurrent color range", + "type": "Number" + }, + { + "name": "v3", + "description": "blue or brightness value relative to the\ncurrent color range", + "type": "Number" + }, + { + "name": "x", + "description": "x component of direction (inclusive range of -1 to 1)", + "type": "Number" + }, + { + "name": "y", + "description": "y component of direction (inclusive range of -1 to 1)", + "type": "Number" + }, + { + "name": "z", + "description": "z component of direction (inclusive range of -1 to 1)", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "v1", + "type": "Number" + }, + { + "name": "v2", + "type": "Number" + }, + { + "name": "v3", + "type": "Number" + }, + { + "name": "direction", + "description": "direction of light as a\np5.Vector", + "type": "p5.Vector" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "color as a p5.Color,\nas an array, or as a CSS string", + "type": "p5.Color|Number[]|String" + }, + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "z", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "color", + "type": "p5.Color|Number[]|String" + }, + { + "name": "direction", + "type": "p5.Vector" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Lights" + }, + { + "name": "pointLight", + "file": "src/webgl/light.js", + "line": 457, + "itemtype": "method", + "chainable": 1, + "description": "

Creates a point light with the given color and position.

\n

A point light emits light from a single point in all directions.\nBecause the light is emitted from a specific point (position),\nit has a different effect when it is positioned farther vs. nearer\nan object.

\n

A maximum of 5 point lights can be active at once.

\n

Note: lights need to be called (whether directly or indirectly)\nwithin draw() to remain persistent in a looping program.\nPlacing them in setup() will cause them to only have an effect\nthe first time through the loop.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe(\n 'scene with sphere and point light. The position of the light is controlled with the mouse position.'\n );\n}\nfunction draw() {\n background(0);\n // move your mouse to change light position\n let locX = mouseX - width / 2;\n let locY = mouseY - height / 2;\n // to set the light position,\n // think of the world's coordinate as:\n // -width/2,-height/2 ----------- width/2,-height/2\n // | |\n // | 0,0 |\n // | |\n // -width/2,height/2 ----------- width/2,height/2\n pointLight(250, 250, 250, locX, locY, 50);\n noStroke();\n sphere(40);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "red or hue value relative to the current\ncolor range", + "type": "Number" + }, + { + "name": "v2", + "description": "green or saturation value relative to the\ncurrent color range", + "type": "Number" + }, + { + "name": "v3", + "description": "blue or brightness value relative to the\ncurrent color range", + "type": "Number" + }, + { + "name": "x", + "description": "x component of position", + "type": "Number" + }, + { + "name": "y", + "description": "y component of position", + "type": "Number" + }, + { + "name": "z", + "description": "z component of position", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "v1", + "type": "Number" + }, + { + "name": "v2", + "type": "Number" + }, + { + "name": "v3", + "type": "Number" + }, + { + "name": "position", + "description": "of light as a p5.Vector", + "type": "p5.Vector" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "color as a p5.Color,\nas an array, or as a CSS string", + "type": "p5.Color|Number[]|String" + }, + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "z", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "color", + "type": "p5.Color|Number[]|String" + }, + { + "name": "position", + "type": "p5.Vector" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Lights" + }, + { + "name": "imageLight", + "file": "src/webgl/light.js", + "line": 595, + "itemtype": "method", + "description": "

Creates an image light with the given image.

\n

The image light simulates light from all the directions.\nThis is done by using the image as a texture for an infinitely\nlarge sphere light. This sphere contains\nor encapsulates the whole scene/drawing.\nIt will have different effect for varying shininess of the\nobject in the drawing.\nUnder the hood it is mainly doing 2 types of calculations,\nthe first one is creating an irradiance map the\nenvironment map of the input image.\nThe second one is managing reflections based on the shininess\nor roughness of the material used in the scene.

\n

Note: The image's diffuse light will be affected by fill()\nand the specular reflections will be affected by specularMaterial()\nand shininess().

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/outdoor_image.jpg');\n}\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0 ,1, 0);\n perspective(PI/3, 1, 5, 500);\n}\nfunction draw() {\n background(220);\n\n push();\n camera(0, 0, 1, 0, 0, 0, 0, 1, 0);\n ortho(-1, 1, -1, 1, 0, 1);\n noLights();\n noStroke();\n texture(img);\n plane(2);\n pop();\n\n ambientLight(50);\n imageLight(img);\n specularMaterial(20);\n noStroke();\n rotateX(frameCount * 0.005);\n rotateY(frameCount * 0.005);\n box(50);\n}\n\n
", + "
\n\nlet img;\nlet slider;\n\nfunction preload() {\n img = loadImage('assets/outdoor_spheremap.jpg');\n}\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n slider = createSlider(0, 400, 100, 1);\n slider.position(0, height);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0 ,1, 0);\n perspective(PI/3, 1, 5, 500);\n}\nfunction draw() {\n background(220);\n\n push();\n camera(0, 0, 1, 0, 0, 0, 0, 1, 0);\n ortho(-1, 1, -1, 1, 0, 1);\n noLights();\n noStroke();\n texture(img);\n plane(2);\n pop();\n\n ambientLight(50);\n imageLight(img);\n specularMaterial(20);\n shininess(slider.value());\n noStroke();\n sphere(30);\n}\n\n
" + ], + "alt": "image light example\nlight with slider having a slider for varying roughness", + "overloads": [ + { + "params": [ + { + "name": "img", + "description": "image for the background", + "type": "p5.image" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Lights" + }, + { + "name": "lights", + "file": "src/webgl/light.js", + "line": 637, + "itemtype": "method", + "chainable": 1, + "description": "

Places an ambient and directional light in the scene.\nThe lights are set to ambientLight(128, 128, 128) and\ndirectionalLight(128, 128, 128, 0, 0, -1).

\n

Note: lights need to be called (whether directly or indirectly)\nwithin draw() to remain persistent in a looping program.\nPlacing them in setup() will cause them to only have an effect\nthe first time through the loop.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n describe('the light is partially ambient and partially directional');\n}\nfunction draw() {\n background(0);\n lights();\n rotateX(millis() / 1000);\n rotateY(millis() / 1000);\n rotateZ(millis() / 1000);\n box();\n}\n\n
" + ], + "alt": "the light is partially ambient and partially directional", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Lights" + }, + { + "name": "lightFalloff", + "file": "src/webgl/light.js", + "line": 702, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the falloff rate for pointLight()\nand spotLight().

\n

lightFalloff() affects only the lights which are created after it\nin the code.

\n

The constant, linear, an quadratic parameters are used to calculate falloff as follows:

\n

d = distance from light position to vertex position

\n

falloff = 1 / (CONSTANT + d * LINEAR + (d * d) * QUADRATIC)

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n noStroke();\n describe(\n 'Two spheres with different falloff values show different intensity of light'\n );\n}\nfunction draw() {\n ortho();\n background(0);\n\n let locX = mouseX - width / 2;\n let locY = mouseY - height / 2;\n locX /= 2; // half scale\n\n lightFalloff(1, 0, 0);\n push();\n translate(-25, 0, 0);\n pointLight(250, 250, 250, locX - 25, locY, 50);\n sphere(20);\n pop();\n\n lightFalloff(0.97, 0.03, 0);\n push();\n translate(25, 0, 0);\n pointLight(250, 250, 250, locX + 25, locY, 50);\n sphere(20);\n pop();\n}\n\n
" + ], + "alt": "Two spheres with different falloff values show different intensity of light", + "overloads": [ + { + "params": [ + { + "name": "constant", + "description": "CONSTANT value for determining falloff", + "type": "Number" + }, + { + "name": "linear", + "description": "LINEAR value for determining falloff", + "type": "Number" + }, + { + "name": "quadratic", + "description": "QUADRATIC value for determining falloff", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Lights" + }, + { + "name": "spotLight", + "file": "src/webgl/light.js", + "line": 896, + "itemtype": "method", + "chainable": 1, + "description": "

Creates a spot light with the given color, position,\nlight direction, angle, and concentration.

\n

Like a pointLight(), a spotLight()\nemits light from a specific point (position). It has a different effect\nwhen it is positioned farther vs. nearer an object.

\n

However, unlike a pointLight(), the light is emitted in one direction\nalong a conical shape. The shape of the cone can be controlled using\nthe angle and concentration parameters.

\n

The angle parameter is used to\ndetermine the radius of the cone. And the concentration\nparameter is used to focus the light towards the center of\nthe cone. Both parameters are optional, however if you want\nto specify concentration, you must also specify angle.\nThe minimum concentration value is 1.

\n

A maximum of 5 spot lights can be active at once.

\n

Note: lights need to be called (whether directly or indirectly)\nwithin draw() to remain persistent in a looping program.\nPlacing them in setup() will cause them to only have an effect\nthe first time through the loop.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe(\n 'scene with sphere and spot light. The position of the light is controlled with the mouse position.'\n );\n}\nfunction draw() {\n background(0);\n // move your mouse to change light position\n let locX = mouseX - width / 2;\n let locY = mouseY - height / 2;\n // to set the light position,\n // think of the world's coordinate as:\n // -width/2,-height/2 ----------- width/2,-height/2\n // | |\n // | 0,0 |\n // | |\n // -width/2,height/2 ----------- width/2,height/2\n ambientLight(50);\n spotLight(0, 250, 0, locX, locY, 100, 0, 0, -1, Math.PI / 16);\n noStroke();\n sphere(40);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "red or hue value relative to the current color range", + "type": "Number" + }, + { + "name": "v2", + "description": "green or saturation value relative to the current color range", + "type": "Number" + }, + { + "name": "v3", + "description": "blue or brightness value relative to the current color range", + "type": "Number" + }, + { + "name": "x", + "description": "x component of position", + "type": "Number" + }, + { + "name": "y", + "description": "y component of position", + "type": "Number" + }, + { + "name": "z", + "description": "z component of position", + "type": "Number" + }, + { + "name": "rx", + "description": "x component of light direction (inclusive range of -1 to 1)", + "type": "Number" + }, + { + "name": "ry", + "description": "y component of light direction (inclusive range of -1 to 1)", + "type": "Number" + }, + { + "name": "rz", + "description": "z component of light direction (inclusive range of -1 to 1)", + "type": "Number" + }, + { + "name": "angle", + "description": "angle of cone. Defaults to PI/3", + "optional": 1, + "type": "Number" + }, + { + "name": "concentration", + "description": "concentration of cone. Defaults to 100", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "color as a p5.Color,\nas an array, or as a CSS string", + "type": "p5.Color|Number[]|String" + }, + { + "name": "position", + "description": "position of light as a p5.Vector", + "type": "p5.Vector" + }, + { + "name": "direction", + "description": "direction of light as a p5.Vector", + "type": "p5.Vector" + }, + { + "name": "angle", + "optional": 1, + "type": "Number" + }, + { + "name": "concentration", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "v1", + "type": "Number" + }, + { + "name": "v2", + "type": "Number" + }, + { + "name": "v3", + "type": "Number" + }, + { + "name": "position", + "type": "p5.Vector" + }, + { + "name": "direction", + "type": "p5.Vector" + }, + { + "name": "angle", + "optional": 1, + "type": "Number" + }, + { + "name": "concentration", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "color", + "type": "p5.Color|Number[]|String" + }, + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "z", + "type": "Number" + }, + { + "name": "direction", + "type": "p5.Vector" + }, + { + "name": "angle", + "optional": 1, + "type": "Number" + }, + { + "name": "concentration", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "color", + "type": "p5.Color|Number[]|String" + }, + { + "name": "position", + "type": "p5.Vector" + }, + { + "name": "rx", + "type": "Number" + }, + { + "name": "ry", + "type": "Number" + }, + { + "name": "rz", + "type": "Number" + }, + { + "name": "angle", + "optional": 1, + "type": "Number" + }, + { + "name": "concentration", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "v1", + "type": "Number" + }, + { + "name": "v2", + "type": "Number" + }, + { + "name": "v3", + "type": "Number" + }, + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "z", + "type": "Number" + }, + { + "name": "direction", + "type": "p5.Vector" + }, + { + "name": "angle", + "optional": 1, + "type": "Number" + }, + { + "name": "concentration", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "v1", + "type": "Number" + }, + { + "name": "v2", + "type": "Number" + }, + { + "name": "v3", + "type": "Number" + }, + { + "name": "position", + "type": "p5.Vector" + }, + { + "name": "rx", + "type": "Number" + }, + { + "name": "ry", + "type": "Number" + }, + { + "name": "rz", + "type": "Number" + }, + { + "name": "angle", + "optional": 1, + "type": "Number" + }, + { + "name": "concentration", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "color", + "type": "p5.Color|Number[]|String" + }, + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "z", + "type": "Number" + }, + { + "name": "rx", + "type": "Number" + }, + { + "name": "ry", + "type": "Number" + }, + { + "name": "rz", + "type": "Number" + }, + { + "name": "angle", + "optional": 1, + "type": "Number" + }, + { + "name": "concentration", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Lights" + }, + { + "name": "noLights", + "file": "src/webgl/light.js", + "line": 1157, + "itemtype": "method", + "chainable": 1, + "description": "

Removes all lights present in a sketch.

\n

All subsequent geometry is rendered without lighting (until a new\nlight is created with a call to one of the lighting functions\n(lights(),\nambientLight(),\ndirectionalLight(),\npointLight(),\nspotLight()).

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe(\n 'Three white spheres. Each appears as a different color due to lighting.'\n );\n}\nfunction draw() {\n background(200);\n noStroke();\n\n ambientLight(255, 0, 0);\n translate(-30, 0, 0);\n ambientMaterial(255);\n sphere(13);\n\n noLights();\n translate(30, 0, 0);\n ambientMaterial(255);\n sphere(13);\n\n ambientLight(0, 255, 0);\n translate(30, 0, 0);\n ambientMaterial(255);\n sphere(13);\n}\n\n
" + ], + "alt": "Three white spheres. Each appears as a different\ncolor due to lighting.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Lights" + }, + { + "name": "loadModel", + "file": "src/webgl/loading.js", + "line": 127, + "itemtype": "method", + "description": "

Load a 3d model from an OBJ or STL file.

\n

loadModel() should be placed inside of preload().\nThis allows the model to load fully before the rest of your code is run.

\n

One of the limitations of the OBJ and STL format is that it doesn't have a built-in\nsense of scale. This means that models exported from different programs might\nbe very different sizes. If your model isn't displaying, try calling\nloadModel() with the normalized parameter set to true. This will resize the\nmodel to a scale appropriate for p5. You can also make additional changes to\nthe final size of your model with the scale() function.

\n

Also, the support for colored STL files is not present. STL files with color will be\nrendered without color properties.

\n

Options can include:

\n", + "example": [ + "
\n\n//draw a spinning octahedron\nlet octahedron;\n\nfunction preload() {\n octahedron = loadModel('assets/octahedron.obj');\n}\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('Vertically rotating 3-d octahedron.');\n}\n\nfunction draw() {\n background(200);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n model(octahedron);\n}\n\n
", + "
\n\n//draw a spinning teapot\nlet teapot;\n\nfunction preload() {\n // Load model with normalise parameter set to true\n teapot = loadModel('assets/teapot.obj', true);\n}\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('Vertically rotating 3-d teapot with red, green and blue gradient.');\n}\n\nfunction draw() {\n background(200);\n scale(0.4); // Scaled to make model fit into canvas\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n normalMaterial(); // For effect\n model(teapot);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "Path of the model to be loaded", + "type": "String" + }, + { + "name": "normalize", + "description": "If true, scale the model to a\nstandardized size when loading", + "type": "Boolean" + }, + { + "name": "successCallback", + "description": "Function to be called\nonce the model is loaded. Will be passed\nthe 3D model object.", + "optional": 1 + }, + { + "name": "failureCallback", + "description": "called with event error if\nthe model fails to load.", + "optional": 1 + }, + { + "name": "fileType", + "description": "The file extension of the model\n(.stl, .obj).", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "the p5.Geometry object", + "type": "p5.Geometry" + } + }, + { + "params": [ + { + "name": "path", + "type": "String" + }, + { + "name": "successCallback", + "optional": 1 + }, + { + "name": "failureCallback", + "optional": 1 + }, + { + "name": "fileType", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "the p5.Geometry object", + "type": "p5.Geometry" + } + }, + { + "params": [ + { + "name": "path", + "type": "String" + }, + { + "name": "options", + "optional": 1, + "type": "Object" + }, + { + "name": "options.successCallback", + "description": "", + "optional": 1 + }, + { + "name": "options.failureCallback", + "description": "", + "optional": 1 + }, + { + "name": "options.fileType", + "description": "", + "optional": 1, + "type": "String" + }, + { + "name": "options.normalize", + "description": "", + "optional": 1, + "type": "boolean" + }, + { + "name": "options.flipU", + "description": "", + "optional": 1, + "type": "boolean" + }, + { + "name": "options.flipV", + "description": "", + "optional": 1, + "type": "boolean" + } + ], + "return": { + "description": "the p5.Geometry object", + "type": "p5.Geometry" + } + } + ], + "return": { + "description": "the p5.Geometry object", + "type": "p5.Geometry" + }, + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Models" + }, + { + "name": "parseObj", + "file": "src/webgl/loading.js", + "line": 238, + "itemtype": "method", + "description": "

Parse OBJ lines into model. For reference, this is what a simple model of a\nsquare might look like:

\n

v -0.5 -0.5 0.5\nv -0.5 -0.5 -0.5\nv -0.5 0.5 -0.5\nv -0.5 0.5 0.5

\n

f 4 3 2 1

\n", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Models" + }, + { + "name": "parseSTL", + "file": "src/webgl/loading.js", + "line": 344, + "itemtype": "method", + "description": "

STL files can be of two types, ASCII and Binary,

\n

We need to convert the arrayBuffer to an array of strings,\nto parse it as an ASCII file.

\n", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Models" + }, + { + "name": "isBinary", + "file": "src/webgl/loading.js", + "line": 378, + "itemtype": "method", + "description": "

This function checks if the file is in ASCII format or in Binary format

\n

It is done by searching keyword solid at the start of the file.

\n

An ASCII STL data must begin with solid as the first six bytes.\nHowever, ASCII STLs lacking the SPACE after the d are known to be\nplentiful. So, check the first 5 bytes for solid.

\n

Several encodings, such as UTF-8, precede the text with up to 5 bytes:\nhttps://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding\nSearch for solid to start anywhere after those prefixes.

\n", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Models" + }, + { + "name": "matchDataViewAt", + "file": "src/webgl/loading.js", + "line": 395, + "itemtype": "method", + "description": "This function matches the query at the provided offset", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Models" + }, + { + "name": "parseBinarySTL", + "file": "src/webgl/loading.js", + "line": 410, + "itemtype": "method", + "description": "

This function parses the Binary STL files.\nhttps://en.wikipedia.org/wiki/STL_%28file_format%29#Binary_STL

\n

Currently there is no support for the colors provided in STL files.

\n", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Models" + }, + { + "name": "parseASCIISTL", + "file": "src/webgl/loading.js", + "line": 502, + "itemtype": "method", + "description": "ASCII STL file starts with solid 'nameOfFile'\nThen contain the normal of the face, starting with facet normal\nNext contain a keyword indicating the start of face vertex, outer loop\nNext comes the three vertex, starting with vertex x y z\nVertices ends with endloop\nFace ends with endfacet\nNext face starts with facet normal\nThe end of the file is indicated by endsolid", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Models" + }, + { + "name": "model", + "file": "src/webgl/loading.js", + "line": 668, + "itemtype": "method", + "description": "Render a 3d model to the screen.", + "example": [ + "
\n\n//draw a spinning octahedron\nlet octahedron;\n\nfunction preload() {\n octahedron = loadModel('assets/octahedron.obj');\n}\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('Vertically rotating 3-d octahedron.');\n}\n\nfunction draw() {\n background(200);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n model(octahedron);\n}\n\n
" + ], + "alt": "Vertically rotating 3-d octahedron.", + "overloads": [ + { + "params": [ + { + "name": "model", + "description": "Loaded 3d model to be rendered", + "type": "p5.Geometry" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Shape", + "submodule": "3D Models" + }, + { + "name": "loadShader", + "file": "src/webgl/material.js", + "line": 64, + "itemtype": "method", + "description": "

Creates a new p5.Shader object\nfrom the provided vertex and fragment shader files.

\n

The shader files are loaded asynchronously in the\nbackground, so this method should be used in preload().

\n

Shaders can alter the positioning of shapes drawn with them.\nTo ensure consistency in rendering, it's recommended to use the vertex shader in the createShader example.

\n

Note, shaders can only be used in WEBGL mode.

\n", + "example": [ + "
\n\nlet mandel;\nfunction preload() {\n // load the shader definitions from files\n mandel = loadShader('assets/shader.vert', 'assets/shader.frag');\n}\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n // use the shader\n shader(mandel);\n noStroke();\n mandel.setUniform('p', [-0.74364388703, 0.13182590421]);\n describe('zooming Mandelbrot set. a colorful, infinitely detailed fractal.');\n}\n\nfunction draw() {\n mandel.setUniform('r', 1.5 * exp(-6.5 * (1 + sin(millis() / 2000))));\n quad(-1, -1, 1, -1, 1, 1, -1, 1);\n}\n\n
" + ], + "alt": "zooming Mandelbrot set. a colorful, infinitely detailed fractal.", + "overloads": [ + { + "params": [ + { + "name": "vertFilename", + "description": "path to file containing vertex shader\nsource code", + "type": "String" + }, + { + "name": "fragFilename", + "description": "path to file containing fragment shader\nsource code", + "type": "String" + }, + { + "name": "callback", + "description": "callback to be executed after loadShader\ncompletes. On success, the p5.Shader object is passed as the first argument.", + "optional": 1, + "type": "function" + }, + { + "name": "errorCallback", + "description": "callback to be executed when an error\noccurs inside loadShader. On error, the error is passed as the first\nargument.", + "optional": 1, + "type": "function" + } + ], + "return": { + "description": "a shader object created from the provided\nvertex and fragment shader files.", + "type": "p5.Shader" + } + } + ], + "return": { + "description": "a shader object created from the provided\nvertex and fragment shader files.", + "type": "p5.Shader" + }, + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "createShader", + "file": "src/webgl/material.js", + "line": 197, + "itemtype": "method", + "description": "

Creates a new p5.Shader object\nfrom the provided vertex and fragment shader code.

\n

Note, shaders can only be used in WEBGL mode.

\n

Shaders can alter the positioning of shapes drawn with them.\nTo ensure consistency in rendering, it's recommended to use the vertex shader shown in the example below.

\n", + "example": [ + "
\n\n\n// the vertex shader is called for each vertex\nlet vs = `\nprecision highp float;\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\n\nattribute vec3 aPosition;\nattribute vec2 aTexCoord;\nvarying vec2 vTexCoord;\n\nvoid main() {\n vTexCoord = aTexCoord;\n vec4 positionVec4 = vec4(aPosition, 1.0);\n gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;\n }\n`;\n\n\n// the fragment shader is called for each pixel\nlet fs = `\n precision highp float;\n uniform vec2 p;\n uniform float r;\n const int I = 500;\n varying vec2 vTexCoord;\n void main() {\n vec2 c = p + gl_FragCoord.xy * r, z = c;\n float n = 0.0;\n for (int i = I; i > 0; i --) {\n if(z.x*z.x+z.y*z.y > 4.0) {\n n = float(i)/float(I);\n break;\n }\n z = vec2(z.x*z.x-z.y*z.y, 2.0*z.x*z.y) + c;\n }\n gl_FragColor = vec4(0.5-cos(n*17.0)/2.0,0.5-cos(n*13.0)/2.0,0.5-cos(n*23.0)/2.0,1.0);\n }`;\n\nlet mandel;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n\n // create and initialize the shader\n mandel = createShader(vs, fs);\n shader(mandel);\n noStroke();\n\n // 'p' is the center point of the Mandelbrot image\n mandel.setUniform('p', [-0.74364388703, 0.13182590421]);\n describe('zooming Mandelbrot set. a colorful, infinitely detailed fractal.');\n}\n\nfunction draw() {\n // 'r' is the size of the image in Mandelbrot-space\n mandel.setUniform('r', 1.5 * exp(-6.5 * (1 + sin(millis() / 2000))));\n plane(width, height);\n}\n\n
" + ], + "alt": "zooming Mandelbrot set. a colorful, infinitely detailed fractal.", + "overloads": [ + { + "params": [ + { + "name": "vertSrc", + "description": "source code for the vertex shader", + "type": "String" + }, + { + "name": "fragSrc", + "description": "source code for the fragment shader", + "type": "String" + } + ], + "return": { + "description": "a shader object created from the provided\nvertex and fragment shaders.", + "type": "p5.Shader" + } + } + ], + "return": { + "description": "a shader object created from the provided\nvertex and fragment shaders.", + "type": "p5.Shader" + }, + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "createFilterShader", + "file": "src/webgl/material.js", + "line": 279, + "itemtype": "method", + "description": "

Creates a new p5.Shader using only a fragment shader, as a convenience method for creating image effects.\nIt's like createShader() but with a default vertex shader included.

\n

createFilterShader() is intended to be used along with filter() for filtering the contents of a canvas.\nA filter shader will not be applied to any geometries.

\n

The fragment shader receives some uniforms:

\n

For more info about filters and shaders, see Adam Ferriss' repo of shader examples\nor the introduction to shaders page.

\n", + "example": [ + "
\n\nfunction setup() {\n let fragSrc = `precision highp float;\n void main() {\n gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);\n }`;\n\n createCanvas(100, 100, WEBGL);\n let s = createFilterShader(fragSrc);\n filter(s);\n describe('a yellow canvas');\n}\n\n
\n\n
\n\nlet img, s;\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\nfunction setup() {\n let fragSrc = `precision highp float;\n\n // x,y coordinates, given from the vertex shader\n varying vec2 vTexCoord;\n\n // the canvas contents, given from filter()\n uniform sampler2D tex0;\n // other useful information from the canvas\n uniform vec2 texelSize;\n uniform vec2 canvasSize;\n // a custom variable from this sketch\n uniform float darkness;\n\n void main() {\n // get the color at current pixel\n vec4 color = texture2D(tex0, vTexCoord);\n // set the output color\n color.b = 1.0;\n color *= darkness;\n gl_FragColor = vec4(color.rgb, 1.0);\n }`;\n\n createCanvas(100, 100, WEBGL);\n s = createFilterShader(fragSrc);\n}\nfunction draw() {\n image(img, -50, -50);\n s.setUniform('darkness', 0.5);\n filter(s);\n describe('a image of bricks tinted dark blue');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "fragSrc", + "description": "source code for the fragment shader", + "type": "String" + } + ], + "return": { + "description": "a shader object created from the provided\nfragment shader.", + "type": "p5.Shader" + } + } + ], + "return": { + "description": "a shader object created from the provided\nfragment shader.", + "type": "p5.Shader" + }, + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "shader", + "file": "src/webgl/material.js", + "line": 424, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the p5.Shader object to\nbe used to render subsequent shapes.

\n

Shaders can alter the positioning of shapes drawn with them.\nTo ensure consistency in rendering, it's recommended to use the vertex shader in the createShader example.

\n

Custom shaders can be created using the\ncreateShader() and\nloadShader() functions.

\n

Use resetShader() to\nrestore the default shaders.

\n

Additional Information:\nThe shader will be used for:

\n

Note, shaders can only be used in WEBGL mode.

\n", + "example": [ + "
\n\n// Click within the image to toggle\n// the shader used by the quad shape\n// Note: for an alternative approach to the same example,\n// involving changing uniforms please refer to:\n// https://p5js.org/reference/#/p5.Shader/setUniform\n\nlet redGreen;\nlet orangeBlue;\nlet showRedGreen = false;\n\nfunction preload() {\n // note that we are using two instances\n // of the same vertex and fragment shaders\n redGreen = loadShader('assets/shader.vert', 'assets/shader-gradient.frag');\n orangeBlue = loadShader('assets/shader.vert', 'assets/shader-gradient.frag');\n}\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n\n // initialize the colors for redGreen shader\n shader(redGreen);\n redGreen.setUniform('colorCenter', [1.0, 0.0, 0.0]);\n redGreen.setUniform('colorBackground', [0.0, 1.0, 0.0]);\n\n // initialize the colors for orangeBlue shader\n shader(orangeBlue);\n orangeBlue.setUniform('colorCenter', [1.0, 0.5, 0.0]);\n orangeBlue.setUniform('colorBackground', [0.226, 0.0, 0.615]);\n\n noStroke();\n\n describe(\n 'canvas toggles between a circular gradient of orange and blue vertically. and a circular gradient of red and green moving horizontally when mouse is clicked/pressed.'\n );\n}\n\nfunction draw() {\n // update the offset values for each shader,\n // moving orangeBlue in vertical and redGreen\n // in horizontal direction\n orangeBlue.setUniform('offset', [0, sin(millis() / 2000) + 1]);\n redGreen.setUniform('offset', [sin(millis() / 2000), 1]);\n\n if (showRedGreen === true) {\n shader(redGreen);\n } else {\n shader(orangeBlue);\n }\n quad(-1, -1, 1, -1, 1, 1, -1, 1);\n}\n\nfunction mouseClicked() {\n showRedGreen = !showRedGreen;\n}\n\n
" + ], + "alt": "canvas toggles between a circular gradient of orange and blue vertically. and a circular gradient of red and green moving horizontally when mouse is clicked/pressed.", + "overloads": [ + { + "params": [ + { + "name": "s", + "description": "the p5.Shader object\nto use for rendering shapes.", + "type": "p5.Shader" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "resetShader", + "file": "src/webgl/material.js", + "line": 524, + "itemtype": "method", + "chainable": 1, + "description": "Restores the default shaders. Code that runs after resetShader()\nwill not be affected by the shader previously set by\nshader()", + "example": [ + "
\n\n// This variable will hold our shader object\nlet shaderProgram;\n\n// This variable will hold our vertex shader source code\nlet vertSrc = `\n attribute vec3 aPosition;\n attribute vec2 aTexCoord;\n uniform mat4 uProjectionMatrix;\n uniform mat4 uModelViewMatrix;\n varying vec2 vTexCoord;\n\n void main() {\n vTexCoord = aTexCoord;\n vec4 position = vec4(aPosition, 1.0);\n gl_Position = uProjectionMatrix * uModelViewMatrix * position;\n }\n`;\n\n// This variable will hold our fragment shader source code\nlet fragSrc = `\n precision mediump float;\n\n varying vec2 vTexCoord;\n\n void main() {\n vec2 uv = vTexCoord;\n vec3 color = vec3(uv.x, uv.y, min(uv.x + uv.y, 1.0));\n gl_FragColor = vec4(color, 1.0);\n }\n`;\n\nfunction setup() {\n // Shaders require WEBGL mode to work\n createCanvas(100, 100, WEBGL);\n\n // Create our shader\n shaderProgram = createShader(vertSrc, fragSrc);\n\n describe(\n 'Two rotating cubes. The left one is painted using a custom (user-defined) shader, while the right one is painted using the default fill shader.'\n );\n}\n\nfunction draw() {\n // Clear the scene\n background(200);\n\n // Draw a box using our shader\n // shader() sets the active shader with our shader\n shader(shaderProgram);\n push();\n translate(-width / 4, 0, 0);\n rotateX(millis() * 0.00025);\n rotateY(millis() * 0.0005);\n box(width / 4);\n pop();\n\n // Draw a box using the default fill shader\n // resetShader() restores the default fill shader\n resetShader();\n fill(255, 0, 0);\n push();\n translate(width / 4, 0, 0);\n rotateX(millis() * 0.00025);\n rotateY(millis() * 0.0005);\n box(width / 4);\n pop();\n}\n\n
" + ], + "alt": "Two rotating cubes. The left one is painted using a custom (user-defined) shader,\nwhile the right one is painted using the default fill shader.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "texture", + "file": "src/webgl/material.js", + "line": 659, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the texture that will be used to render subsequent shapes.

\n

A texture is like a \"skin\" that wraps around a 3D geometry. Currently\nsupported textures are images, video, and offscreen renders.

\n

To texture a geometry created with beginShape(),\nyou will need to specify uv coordinates in vertex().

\n

Note, texture() can only be used in WEBGL mode.

\n

You can view more materials in this\nexample.

\n", + "example": [ + "
\n\nlet img;\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('spinning cube with a texture from an image');\n}\n\nfunction draw() {\n background(0);\n rotateZ(frameCount * 0.01);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n //pass image as texture\n texture(img);\n box(width / 2);\n}\n\n
", + "
\n\nlet pg;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n pg = createGraphics(200, 200);\n pg.textSize(75);\n describe('plane with a texture from an image created by createGraphics()');\n}\n\nfunction draw() {\n background(0);\n pg.background(255);\n pg.text('hello!', 0, 100);\n //pass image as texture\n texture(pg);\n rotateX(0.5);\n noStroke();\n plane(50);\n}\n\n
", + "
\n\nlet vid;\nfunction preload() {\n vid = createVideo('assets/fingers.mov');\n vid.hide();\n}\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('rectangle with video as texture');\n}\n\nfunction draw() {\n background(0);\n //pass video frame as texture\n texture(vid);\n rect(-40, -40, 80, 80);\n}\n\nfunction mousePressed() {\n vid.loop();\n}\n\n
", + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('quad with a texture, mapped using normalized coordinates');\n}\n\nfunction draw() {\n background(0);\n texture(img);\n textureMode(NORMAL);\n beginShape();\n vertex(-40, -40, 0, 0);\n vertex(40, -40, 1, 0);\n vertex(40, 40, 1, 1);\n vertex(-40, 40, 0, 1);\n endShape();\n}\n\n
" + ], + "alt": "spinning cube with a texture from an image\nplane with a texture from an image created by createGraphics()\nrectangle with video as texture\nquad with a texture, mapped using normalized coordinates", + "overloads": [ + { + "params": [ + { + "name": "tex", + "description": "image to use as texture", + "type": "p5.Image|p5.MediaElement|p5.Graphics|p5.Texture|p5.Framebuffer|p5.FramebufferTexture" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "textureMode", + "file": "src/webgl/material.js", + "line": 742, + "itemtype": "method", + "description": "

Sets the coordinate space for texture mapping. The default mode is IMAGE\nwhich refers to the actual coordinates of the image.\nNORMAL refers to a normalized space of values ranging from 0 to 1.

\n

With IMAGE, if an image is 100×200 pixels, mapping the image onto the entire\nsize of a quad would require the points (0,0) (100, 0) (100,200) (0,200).\nThe same mapping in NORMAL is (0,0) (1,0) (1,1) (0,1).

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('quad with a texture, mapped using normalized coordinates');\n}\n\nfunction draw() {\n texture(img);\n textureMode(NORMAL);\n beginShape();\n vertex(-50, -50, 0, 0);\n vertex(50, -50, 1, 0);\n vertex(50, 50, 1, 1);\n vertex(-50, 50, 0, 1);\n endShape();\n}\n\n
", + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('quad with a texture, mapped using image coordinates');\n}\n\nfunction draw() {\n texture(img);\n textureMode(IMAGE);\n beginShape();\n vertex(-50, -50, 0, 0);\n vertex(50, -50, img.width, 0);\n vertex(50, 50, img.width, img.height);\n vertex(-50, 50, 0, img.height);\n endShape();\n}\n\n
" + ], + "alt": "quad with a texture, mapped using normalized coordinates\nquad with a texture, mapped using image coordinates", + "overloads": [ + { + "params": [ + { + "name": "mode", + "description": "either IMAGE or NORMAL", + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "textureWrap", + "file": "src/webgl/material.js", + "line": 815, + "itemtype": "method", + "description": "

Sets the global texture wrapping mode. This controls how textures behave\nwhen their uv's go outside of the 0 to 1 range. There are three options:\nCLAMP, REPEAT, and MIRROR.

\n

CLAMP causes the pixels at the edge of the texture to extend to the bounds.\nREPEAT causes the texture to tile repeatedly until reaching the bounds.\nMIRROR works similarly to REPEAT but it flips the texture with every new tile.

\n

REPEAT & MIRROR are only available if the texture\nis a power of two size (128, 256, 512, 1024, etc.).

\n

This method will affect all textures in your sketch until a subsequent\ntextureWrap() call is made.

\n

If only one argument is provided, it will be applied to both the\nhorizontal and vertical axes.

\n", + "example": [ + "
\n\nlet img;\nfunction preload() {\n img = loadImage('assets/rockies128.jpg');\n}\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n textureWrap(MIRROR);\n describe('an image of the rocky mountains repeated in mirrored tiles');\n}\n\nfunction draw() {\n background(0);\n\n let dX = mouseX;\n let dY = mouseY;\n\n let u = lerp(1.0, 2.0, dX);\n let v = lerp(1.0, 2.0, dY);\n\n scale(width / 2);\n\n texture(img);\n\n beginShape(TRIANGLES);\n vertex(-1, -1, 0, 0, 0);\n vertex(1, -1, 0, u, 0);\n vertex(1, 1, 0, u, v);\n\n vertex(1, 1, 0, u, v);\n vertex(-1, 1, 0, 0, v);\n vertex(-1, -1, 0, 0, 0);\n endShape();\n}\n\n
" + ], + "alt": "an image of the rocky mountains repeated in mirrored tiles", + "overloads": [ + { + "params": [ + { + "name": "wrapX", + "description": "either CLAMP, REPEAT, or MIRROR", + "type": "Constant" + }, + { + "name": "wrapY", + "description": "either CLAMP, REPEAT, or MIRROR", + "optional": 1, + "type": "Constant" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "normalMaterial", + "file": "src/webgl/material.js", + "line": 856, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the current material as a normal material.

\n

A normal material is not affected by light. It is often used as\na placeholder material when debugging.

\n

Surfaces facing the X-axis become red, those facing the Y-axis\nbecome green, and those facing the Z-axis become blue.

\n

You can view more materials in this\nexample.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('Sphere with normal material');\n}\n\nfunction draw() {\n background(200);\n normalMaterial();\n sphere(40);\n}\n\n
" + ], + "alt": "Sphere with normal material", + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "ambientMaterial", + "file": "src/webgl/material.js", + "line": 969, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the ambient color of the material.

\n

The ambientMaterial() color represents the components\nof the ambientLight() color that the object reflects.

\n

Consider an ambientMaterial() with the color yellow (255, 255, 0).\nIf the ambientLight() emits the color white (255, 255, 255), then the object\nwill appear yellow as it will reflect the red and green components\nof the light. If the ambientLight() emits the color red (255, 0, 0), then\nthe object will appear red as it will reflect the red component\nof the light. If the ambientLight() emits the color blue (0, 0, 255),\nthen the object will appear black, as there is no component of\nthe light that it can reflect.

\n

You can view more materials in this\nexample.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('sphere reflecting red, blue, and green light');\n}\nfunction draw() {\n background(0);\n noStroke();\n ambientLight(255);\n ambientMaterial(70, 130, 230);\n sphere(40);\n}\n\n
", + "
\n\n// ambientLight is both red and blue (magenta),\n// so object only reflects it's red and blue components\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('box reflecting only red and blue light');\n}\nfunction draw() {\n background(70);\n ambientLight(255, 0, 255); // magenta light\n ambientMaterial(255); // white material\n box(30);\n}\n\n
", + "
\n\n// ambientLight is green. Since object does not contain\n// green, it does not reflect any light\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('box reflecting no light');\n}\nfunction draw() {\n background(70);\n ambientLight(0, 255, 0); // green light\n ambientMaterial(255, 0, 255); // magenta material\n box(30);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "red or hue value relative to the current\ncolor range", + "type": "Number" + }, + { + "name": "v2", + "description": "green or saturation value relative to the\ncurrent color range", + "type": "Number" + }, + { + "name": "v3", + "description": "blue or brightness value relative to the\ncurrent color range", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "gray", + "description": "number specifying value between\nwhite and black", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "color as a p5.Color,\nas an array, or as a CSS string", + "type": "p5.Color|Number[]|String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "emissiveMaterial", + "file": "src/webgl/material.js", + "line": 1040, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the emissive color of the material.

\n

An emissive material will display the emissive color at\nfull strength regardless of lighting. This can give the\nappearance that the object is glowing.

\n

Note, \"emissive\" is a misnomer in the sense that the material\ndoes not actually emit light that will affect surrounding objects.

\n

You can view more materials in this\nexample.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('sphere with green emissive material');\n}\nfunction draw() {\n background(0);\n noStroke();\n ambientLight(0);\n emissiveMaterial(130, 230, 0);\n sphere(40);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "red or hue value relative to the current\ncolor range", + "type": "Number" + }, + { + "name": "v2", + "description": "green or saturation value relative to the\ncurrent color range", + "type": "Number" + }, + { + "name": "v3", + "description": "blue or brightness value relative to the\ncurrent color range", + "type": "Number" + }, + { + "name": "alpha", + "description": "alpha value relative to current color\nrange (default is 0-255)", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "gray", + "description": "number specifying value between\nwhite and black", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "color as a p5.Color,\nas an array, or as a CSS string", + "type": "p5.Color|Number[]|String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "specularMaterial", + "file": "src/webgl/material.js", + "line": 1126, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the specular color of the material.

\n

A specular material is reflective (shiny). The shininess can be\ncontrolled by the shininess() function.

\n

Like ambientMaterial(),\nthe specularMaterial() color is the color the object will reflect\nunder ambientLight().\nHowever unlike ambientMaterial(), for all other types of lights\n(directionalLight(),\npointLight(),\nspotLight()),\na specular material will reflect the color of the light source.\nThis is what gives it its \"shiny\" appearance.

\n

You can view more materials in this\nexample.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n noStroke();\n describe('torus with specular material');\n}\n\nfunction draw() {\n background(0);\n\n ambientLight(60);\n\n // add point light to showcase specular material\n let locX = mouseX - width / 2;\n let locY = mouseY - height / 2;\n pointLight(255, 255, 255, locX, locY, 50);\n\n specularMaterial(250);\n shininess(50);\n torus(30, 10, 64, 64);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "gray", + "description": "number specifying value between white and black.", + "type": "Number" + }, + { + "name": "alpha", + "description": "alpha value relative to current color range\n(default is 0-255)", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "red or hue value relative to\nthe current color range", + "type": "Number" + }, + { + "name": "v2", + "description": "green or saturation value\nrelative to the current color range", + "type": "Number" + }, + { + "name": "v3", + "description": "blue or brightness value\nrelative to the current color range", + "type": "Number" + }, + { + "name": "alpha", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "color as a p5.Color,\nas an array, or as a CSS string", + "type": "p5.Color|Number[]|String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "shininess", + "file": "src/webgl/material.js", + "line": 1175, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the amount of gloss (\"shininess\") of a\nspecularMaterial().

\n

The default and minimum value is 1.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n describe('two spheres, one more shiny than the other');\n}\nfunction draw() {\n background(0);\n noStroke();\n let locX = mouseX - width / 2;\n let locY = mouseY - height / 2;\n ambientLight(60, 60, 60);\n pointLight(255, 255, 255, locX, locY, 50);\n specularMaterial(250);\n translate(-25, 0, 0);\n shininess(1);\n sphere(20);\n translate(50, 0, 0);\n shininess(20);\n sphere(20);\n}\n\n
" + ], + "alt": "two spheres, one more shiny than the other", + "overloads": [ + { + "params": [ + { + "name": "shine", + "description": "degree of shininess", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "metalness", + "file": "src/webgl/material.js", + "line": 1271, + "itemtype": "method", + "description": "

Sets the metalness property of a material used in 3D rendering.

\n

The metalness property controls the degree to which the material\nappears metallic. A higher metalness value makes the material look\nmore metallic, while a lower value makes it appear less metallic.

\n

The default and minimum value is 0, indicating a non-metallic appearance.

\n

Unlike other materials, metals exclusively rely on reflections, particularly\nthose produced by specular lights (mirrorLike lights). They don't incorporate\ndiffuse or ambient lighting. Metals use a fill color to influence the overall\ncolor of their reflections. Pick a fill color, and you can easily change the\nappearance of the metal surfaces. When no fill color is provided, it defaults\nto using white.

\n", + "example": [ + "
\n\nlet img;\nlet slider;\nlet slider2;\nfunction preload() {\n img = loadImage('assets/outdoor_spheremap.jpg');\n}\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n slider = createSlider(0, 300, 100, 1);\n let sliderLabel = createP('Metalness');\n sliderLabel.position(100, height - 25);\n slider2 = createSlider(0, 350, 100);\n slider2.position(0, height + 20);\n slider2Label = createP('Shininess');\n slider2Label.position(100, height);\n}\nfunction draw() {\n background(220);\n imageMode(CENTER);\n push();\n image(img, 0, 0, width, height);\n clearDepth();\n pop();\n imageLight(img);\n fill('gray');\n specularMaterial('gray');\n shininess(slider2.value());\n metalness(slider.value());\n noStroke();\n sphere(30);\n}\n\n
", + "
\n\nlet slider;\nlet slider2;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n slider = createSlider(0, 200, 100);\n let sliderLabel = createP('Metalness');\n sliderLabel.position(100, height - 25);\n slider2 = createSlider(0, 200, 2);\n slider2.position(0, height + 25);\n let slider2Label = createP('Shininess');\n slider2Label.position(100, height);\n}\nfunction draw() {\n noStroke();\n background(100);\n fill(255, 215, 0);\n pointLight(255, 255, 255, 5000, 5000, 75);\n specularMaterial('gray');\n ambientLight(100);\n shininess(slider2.value());\n metalness(slider.value());\n rotateY(frameCount * 0.01);\n torus(20, 10);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "metallic", + "description": "The degree of metalness.", + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "camera", + "file": "src/webgl/p5.Camera.js", + "line": 113, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the position of the current camera in a 3D sketch.\nParameters for this function define the camera's position,\nthe center of the sketch (where the camera is pointing),\nand an up direction (the orientation of the camera).

\n

This function simulates the movements of the camera, allowing objects to be\nviewed from various angles. Remember, it does not move the objects themselves\nbut the camera instead. For example when the centerX value is positive,\nand the camera is rotating to the right side of the sketch,\nthe object will seem like it's moving to the left.

\n

See this example\nto view the position of your camera.

\n

If no parameters are given, the following default is used:\ncamera(0, 0, 800, 0, 0, 0, 0, 1, 0)

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n describe('a square moving closer and then away from the camera.');\n}\nfunction draw() {\n background(204);\n //move the camera away from the plane by a sin wave\n camera(0, 0, 20 + sin(frameCount * 0.01) * 10, 0, 0, 0, 0, 1, 0);\n plane(10, 10);\n}\n\n
", + "
\n\n//move slider to see changes!\n//sliders control the first 6 parameters of camera()\nlet sliderGroup = [];\nlet X;\nlet Y;\nlet Z;\nlet centerX;\nlet centerY;\nlet centerZ;\nlet h = 20;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n //create sliders\n for (var i = 0; i < 6; i++) {\n if (i === 2) {\n sliderGroup[i] = createSlider(10, 400, 200);\n } else {\n sliderGroup[i] = createSlider(-400, 400, 0);\n }\n h = map(i, 0, 6, 5, 85);\n sliderGroup[i].position(10, height + h);\n sliderGroup[i].style('width', '80px');\n }\n describe(\n 'White square repeatedly grows to fill canvas and then shrinks. An interactive example of a red cube with 3 sliders for moving it across x, y, z axis and 3 sliders for shifting its center.'\n );\n}\n\nfunction draw() {\n background(60);\n // assigning sliders' value to each parameters\n X = sliderGroup[0].value();\n Y = sliderGroup[1].value();\n Z = sliderGroup[2].value();\n centerX = sliderGroup[3].value();\n centerY = sliderGroup[4].value();\n centerZ = sliderGroup[5].value();\n camera(X, Y, Z, centerX, centerY, centerZ, 0, 1, 0);\n stroke(255);\n fill(255, 102, 94);\n box(85);\n}\n\n
" + ], + "alt": "White square repeatedly grows to fill canvas and then shrinks.\nAn interactive example of a red cube with 3 sliders for moving it across x, y,\nz axis and 3 sliders for shifting its center.", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "camera position value on x axis", + "optional": 1, + "type": "Number" + }, + { + "name": "y", + "description": "camera position value on y axis", + "optional": 1, + "type": "Number" + }, + { + "name": "z", + "description": "camera position value on z axis", + "optional": 1, + "type": "Number" + }, + { + "name": "centerX", + "description": "x coordinate representing center of the sketch", + "optional": 1, + "type": "Number" + }, + { + "name": "centerY", + "description": "y coordinate representing center of the sketch", + "optional": 1, + "type": "Number" + }, + { + "name": "centerZ", + "description": "z coordinate representing center of the sketch", + "optional": 1, + "type": "Number" + }, + { + "name": "upX", + "description": "x component of direction 'up' from camera", + "optional": 1, + "type": "Number" + }, + { + "name": "upY", + "description": "y component of direction 'up' from camera", + "optional": 1, + "type": "Number" + }, + { + "name": "upZ", + "description": "z component of direction 'up' from camera", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "perspective", + "file": "src/webgl/p5.Camera.js", + "line": 188, + "itemtype": "method", + "chainable": 1, + "description": "

Sets a perspective projection for the current camera in a 3D sketch.\nThis projection represents depth through foreshortening: objects\nthat are close to the camera appear their actual size while those\nthat are further away from the camera appear smaller.

\n

The parameters to this function define the viewing frustum\n(the truncated pyramid within which objects are seen by the camera) through\nvertical field of view, aspect ratio (usually width/height), and near and far\nclipping planes.

\n

If no parameters are given, the default values are used as:

\n

If you prefer a fixed field of view, follow these steps:

\n", + "example": [ + "
\n\n//drag the mouse to look around!\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n perspective(PI / 3.0, width / height, 0.1, 500);\n describe(\n 'two colored 3D boxes move back and forth, rotating as mouse is dragged.'\n );\n}\nfunction draw() {\n background(200);\n orbitControl();\n normalMaterial();\n\n rotateX(-0.3);\n rotateY(-0.2);\n translate(0, 0, -50);\n\n push();\n translate(-15, 0, sin(frameCount / 30) * 65);\n box(30);\n pop();\n push();\n translate(15, 0, sin(frameCount / 30 + PI) * 65);\n box(30);\n pop();\n}\n\n
" + ], + "alt": "two colored 3D boxes move back and forth, rotating as mouse is dragged.", + "overloads": [ + { + "params": [ + { + "name": "fovy", + "description": "camera frustum vertical field of view,\nfrom bottom to top of view, in angleMode units", + "optional": 1, + "type": "Number" + }, + { + "name": "aspect", + "description": "camera frustum aspect ratio", + "optional": 1, + "type": "Number" + }, + { + "name": "near", + "description": "frustum near plane length", + "optional": 1, + "type": "Number" + }, + { + "name": "far", + "description": "frustum far plane length", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "ortho", + "file": "src/webgl/p5.Camera.js", + "line": 252, + "itemtype": "method", + "chainable": 1, + "description": "

Sets an orthographic projection for the current camera in a 3D sketch\nand defines a box-shaped viewing frustum within which objects are seen.\nIn this projection, all objects with the same dimension appear the same\nsize, regardless of whether they are near or far from the camera.

\n

The parameters to this function specify the viewing frustum where\nleft and right are the minimum and maximum x values, top and bottom are\nthe minimum and maximum y values, and near and far are the minimum and\nmaximum z values.

\n

If no parameters are given, the following default is used:\northo(-width/2, width/2, -height/2, height/2, 0, max(width, height)).

\n", + "example": [ + "
\n\n//drag the mouse to look around!\n//there's no vanishing point\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n ortho(-width / 2, width / 2, height / 2, -height / 2, 0, 500);\n describe(\n 'two 3D boxes move back and forth along same plane, rotating as mouse is dragged.'\n );\n}\nfunction draw() {\n background(200);\n orbitControl();\n normalMaterial();\n\n rotateX(0.2);\n rotateY(-0.2);\n push();\n translate(-15, 0, sin(frameCount / 30) * 65);\n box(30);\n pop();\n push();\n translate(15, 0, sin(frameCount / 30 + PI) * 65);\n box(30);\n pop();\n}\n\n
" + ], + "alt": "two 3D boxes move back and forth along same plane, rotating as mouse is dragged.", + "overloads": [ + { + "params": [ + { + "name": "left", + "description": "camera frustum left plane", + "optional": 1, + "type": "Number" + }, + { + "name": "right", + "description": "camera frustum right plane", + "optional": 1, + "type": "Number" + }, + { + "name": "bottom", + "description": "camera frustum bottom plane", + "optional": 1, + "type": "Number" + }, + { + "name": "top", + "description": "camera frustum top plane", + "optional": 1, + "type": "Number" + }, + { + "name": "near", + "description": "camera frustum near plane", + "optional": 1, + "type": "Number" + }, + { + "name": "far", + "description": "camera frustum far plane", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "frustum", + "file": "src/webgl/p5.Camera.js", + "line": 320, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the frustum of the current camera as defined by\nthe parameters.

\n

A frustum is a geometric form: a pyramid with its top\ncut off. With the viewer's eye at the imaginary top of\nthe pyramid, the six planes of the frustum act as clipping\nplanes when rendering a 3D view. Thus, any form inside the\nclipping planes is visible; anything outside\nthose planes is not visible.

\n

Setting the frustum changes the perspective of the scene being rendered.\nThis can be achieved more simply in many cases by using\nperspective().

\n

If no parameters are given, the following default is used:\nfrustum(-width/20, width/20, height/20, -height/20, eyeZ/10, eyeZ*10),\nwhere eyeZ is equal to 800.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n setAttributes('antialias', true);\n camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n frustum(-0.1, 0.1, -0.1, 0.1, 0.1, 200);\n describe(\n 'two 3D boxes move back and forth along same plane, rotating as mouse is dragged.'\n );\n}\nfunction draw() {\n background(200);\n orbitControl();\n normalMaterial();\n\n rotateY(-0.2);\n rotateX(-0.3);\n push();\n translate(-15, 0, sin(frameCount / 30) * 25);\n box(30);\n pop();\n push();\n translate(15, 0, sin(frameCount / 30 + PI) * 25);\n box(30);\n pop();\n}\n\n
" + ], + "alt": "two 3D boxes move back and forth along same plane, rotating as mouse is dragged.", + "overloads": [ + { + "params": [ + { + "name": "left", + "description": "camera frustum left plane", + "optional": 1, + "type": "Number" + }, + { + "name": "right", + "description": "camera frustum right plane", + "optional": 1, + "type": "Number" + }, + { + "name": "bottom", + "description": "camera frustum bottom plane", + "optional": 1, + "type": "Number" + }, + { + "name": "top", + "description": "camera frustum top plane", + "optional": 1, + "type": "Number" + }, + { + "name": "near", + "description": "camera frustum near plane", + "optional": 1, + "type": "Number" + }, + { + "name": "far", + "description": "camera frustum far plane", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "createCamera", + "file": "src/webgl/p5.Camera.js", + "line": 387, + "itemtype": "method", + "description": "

Creates a new p5.Camera object and sets it\nas the current (active) camera.

\n

The new camera is initialized with a default position\n(see camera())\nand a default perspective projection\n(see perspective()).\nIts properties can be controlled with the p5.Camera\nmethods.

\n

Note: Every 3D sketch starts with a default camera initialized.\nThis camera can be controlled with the global methods\ncamera(),\nperspective(), ortho(),\nand frustum() if it is the only camera\nin the scene.

\n", + "example": [ + "
\n// Creates a camera object and animates it around a box.\nlet camera;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n background(0);\n camera = createCamera();\n camera.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n camera.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n describe('An example that creates a camera and moves it around the box.');\n}\n\nfunction draw() {\n background(0);\n // The camera will automatically\n // rotate to look at [0, 0, 0].\n camera.lookAt(0, 0, 0);\n\n // The camera will move on the\n // x axis.\n camera.setPosition(sin(frameCount / 60) * 200, 0, 100);\n box(20);\n\n // A 'ground' box to give the viewer\n // a better idea of where the camera\n // is looking.\n translate(0, 50, 0);\n rotateX(HALF_PI);\n box(150, 150, 20);\n}\n
" + ], + "alt": "An example that creates a camera and moves it around the box.", + "overloads": [ + { + "params": [], + "return": { + "description": "The newly created camera object.", + "type": "p5.Camera" + } + } + ], + "return": { + "description": "The newly created camera object.", + "type": "p5.Camera" + }, + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "setCamera", + "file": "src/webgl/p5.Camera.js", + "line": 2282, + "itemtype": "method", + "description": "Sets the current (active) camera of a 3D sketch.\nAllows for switching between multiple cameras.", + "example": [ + "
\n\nlet cam1, cam2;\nlet currentCamera;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n normalMaterial();\n\n cam1 = createCamera();\n cam1.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam1.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n cam2 = createCamera();\n cam2.setPosition(30, 0, 50);\n cam2.lookAt(0, 0, 0);\n cam2.ortho(-50, 50, -50, 50, 0, 200);\n\n // set variable for previously active camera:\n currentCamera = 1;\n\n describe(\n 'Canvas switches between two camera views, each showing a series of spinning 3D boxes.'\n );\n}\n\nfunction draw() {\n background(200);\n\n // every 100 frames, switch between the two cameras\n if (frameCount % 100 === 0) {\n if (currentCamera === 1) {\n setCamera(cam1);\n currentCamera = 0;\n } else {\n setCamera(cam2);\n currentCamera = 1;\n }\n }\n\n // camera 1:\n cam1.lookAt(0, 0, 0);\n cam1.setPosition(sin(frameCount / 60) * 200, 0, 100);\n\n drawBoxes();\n}\n\nfunction drawBoxes() {\n rotateX(frameCount * 0.01);\n translate(-100, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n}\n\n
" + ], + "alt": "Canvas switches between two camera views, each showing a series of spinning\n3D boxes.", + "overloads": [ + { + "params": [ + { + "name": "cam", + "description": "p5.Camera object", + "type": "p5.Camera" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "flipU", + "file": "src/webgl/p5.Geometry.js", + "line": 205, + "itemtype": "method", + "description": "Flips the U texture coordinates of the model.", + "example": [ + "
\n\nlet img;\nlet model1;\nlet model2;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n createCanvas(150, 150, WEBGL);\n background(200);\n\n model1 = createShape(50, 50);\n model2 = createShape(50, 50);\n model2.flipU();\n}\n\nfunction draw() {\n background(0);\n\n // original\n push();\n translate(-40, 0, 0);\n texture(img);\n noStroke();\n plane(50);\n model(model1);\n pop();\n\n // flipped\n push();\n translate(40, 0, 0);\n texture(img);\n noStroke();\n plane(50);\n model(model2);\n pop();\n}\n\nfunction createShape(w, h) {\n return buildGeometry(() => {\n textureMode(NORMAL);\n beginShape();\n vertex(-w / 2, -h / 2, 0, 0);\n vertex(w / 2, -h / 2, 1, 0);\n vertex(w / 2, h / 2, 1, 1);\n vertex(-w / 2, h / 2, 0, 1);\n endShape(CLOSE);\n });\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "", + "type": "p5.Geometry" + } + } + ], + "return": { + "description": "", + "type": "p5.Geometry" + }, + "class": "p5.Geometry", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "flipV", + "file": "src/webgl/p5.Geometry.js", + "line": 277, + "itemtype": "method", + "description": "Flips the V texture coordinates of the model.", + "example": [ + "
\n\nlet img;\nlet model1;\nlet model2;\n\nfunction preload() {\n img = loadImage('assets/laDefense.jpg');\n}\n\nfunction setup() {\n createCanvas(150, 150, WEBGL);\n background(200);\n\n model1 = createShape(50, 50);\n model2 = createShape(50, 50);\n model2.flipV();\n}\n\nfunction draw() {\n background(0);\n\n // original\n push();\n translate(-40, 0, 0);\n texture(img);\n noStroke();\n plane(50);\n model(model1);\n pop();\n\n // flipped\n push();\n translate(40, 0, 0);\n texture(img);\n noStroke();\n plane(50);\n model(model2);\n pop();\n}\n\nfunction createShape(w, h) {\n return buildGeometry(() => {\n textureMode(NORMAL);\n beginShape();\n vertex(-w / 2, -h / 2, 0, 0);\n vertex(w / 2, -h / 2, 1, 0);\n vertex(w / 2, h / 2, 1, 1);\n vertex(-w / 2, h / 2, 0, 1);\n endShape(CLOSE);\n });\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "", + "type": "p5.Geometry" + } + } + ], + "return": { + "description": "", + "type": "p5.Geometry" + }, + "class": "p5.Geometry", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "setAttributes", + "file": "src/webgl/p5.RendererGL.js", + "line": 261, + "itemtype": "method", + "description": "

Set attributes for the WebGL Drawing context.\nThis is a way of adjusting how the WebGL\nrenderer works to fine-tune the display and performance.

\n

Note that this will reinitialize the drawing context\nif called after the WebGL canvas is made.

\n

If an object is passed as the parameter, all attributes\nnot declared in the object will be set to defaults.

\n

The available attributes are:\n
\nalpha - indicates if the canvas contains an alpha buffer\ndefault is true

\n

depth - indicates whether the drawing buffer has a depth buffer\nof at least 16 bits - default is true

\n

stencil - indicates whether the drawing buffer has a stencil buffer\nof at least 8 bits

\n

antialias - indicates whether or not to perform anti-aliasing\ndefault is false (true in Safari)

\n

premultipliedAlpha - indicates that the page compositor will assume\nthe drawing buffer contains colors with pre-multiplied alpha\ndefault is true

\n

preserveDrawingBuffer - if true the buffers will not be cleared and\nand will preserve their values until cleared or overwritten by author\n(note that p5 clears automatically on draw loop)\ndefault is true

\n

perPixelLighting - if true, per-pixel lighting will be used in the\nlighting shader otherwise per-vertex lighting is used.\ndefault is true.

\n

version - either 1 or 2, to specify which WebGL version to ask for. By\ndefault, WebGL 2 will be requested. If WebGL2 is not available, it will\nfall back to WebGL 1. You can check what version is used with by looking at\nthe global webglVersion property.

\n", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n}\n\nfunction draw() {\n background(255);\n push();\n rotateZ(frameCount * 0.02);\n rotateX(frameCount * 0.02);\n rotateY(frameCount * 0.02);\n fill(0, 0, 0);\n box(50);\n pop();\n}\n\n
\n
\nNow with the antialias attribute set to true.\n
\n
\n\nfunction setup() {\n setAttributes('antialias', true);\n createCanvas(100, 100, WEBGL);\n}\n\nfunction draw() {\n background(255);\n push();\n rotateZ(frameCount * 0.02);\n rotateX(frameCount * 0.02);\n rotateY(frameCount * 0.02);\n fill(0, 0, 0);\n box(50);\n pop();\n}\n\n
\n\n
\n\n// press the mouse button to disable perPixelLighting\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n noStroke();\n fill(255);\n}\n\nlet lights = [\n { c: '#f00', t: 1.12, p: 1.91, r: 0.2 },\n { c: '#0f0', t: 1.21, p: 1.31, r: 0.2 },\n { c: '#00f', t: 1.37, p: 1.57, r: 0.2 },\n { c: '#ff0', t: 1.12, p: 1.91, r: 0.7 },\n { c: '#0ff', t: 1.21, p: 1.31, r: 0.7 },\n { c: '#f0f', t: 1.37, p: 1.57, r: 0.7 }\n];\n\nfunction draw() {\n let t = millis() / 1000 + 1000;\n background(0);\n directionalLight(color('#222'), 1, 1, 1);\n\n for (let i = 0; i < lights.length; i++) {\n let light = lights[i];\n pointLight(\n color(light.c),\n p5.Vector.fromAngles(t * light.t, t * light.p, width * light.r)\n );\n }\n\n specularMaterial(255);\n sphere(width * 0.1);\n\n rotateX(t * 0.77);\n rotateY(t * 0.83);\n rotateZ(t * 0.91);\n torus(width * 0.3, width * 0.07, 24, 10);\n}\n\nfunction mousePressed() {\n setAttributes('perPixelLighting', false);\n noStroke();\n fill(255);\n}\nfunction mouseReleased() {\n setAttributes('perPixelLighting', true);\n noStroke();\n fill(255);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "key", + "description": "Name of attribute", + "type": "String" + }, + { + "name": "value", + "description": "New value of named attribute", + "type": "Boolean" + } + ] + }, + { + "params": [ + { + "name": "obj", + "description": "object with key-value pairs", + "type": "Object" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "update", + "file": "src/webgl/p5.Texture.js", + "line": 201, + "itemtype": "method", + "description": "Checks if the source data for this texture has changed (if it's\neasy to do so) and reuploads the texture if necessary. If it's not\npossible or to expensive to do a calculation to determine wheter or\nnot the data has occurred, this method simply re-uploads the texture.", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "bindTexture", + "file": "src/webgl/p5.Texture.js", + "line": 300, + "itemtype": "method", + "description": "Binds the texture to the appropriate GL target.", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "unbindTexture", + "file": "src/webgl/p5.Texture.js", + "line": 313, + "itemtype": "method", + "description": "Unbinds the texture from the appropriate GL target.", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "setInterpolation", + "file": "src/webgl/p5.Texture.js", + "line": 338, + "itemtype": "method", + "description": "Sets how a texture is be interpolated when upscaled or downscaled.\nNearest filtering uses nearest neighbor scaling when interpolating\nLinear filtering uses WebGL's linear scaling when interpolating", + "example": [], + "overloads": [ + { + "params": [ + { + "name": "downScale", + "description": "Specifies the texture filtering when\ntextures are shrunk. Options are LINEAR or NEAREST", + "type": "String" + }, + { + "name": "upScale", + "description": "Specifies the texture filtering when\ntextures are magnified. Options are LINEAR or NEAREST", + "type": "String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "setWrapMode", + "file": "src/webgl/p5.Texture.js", + "line": 368, + "itemtype": "method", + "description": "Sets the texture wrapping mode. This controls how textures behave\nwhen their uv's go outside of the 0 - 1 range. There are three options:\nCLAMP, REPEAT, and MIRROR. REPEAT & MIRROR are only available if the texture\nis a power of two size (128, 256, 512, 1024, etc.).", + "example": [], + "overloads": [ + { + "params": [ + { + "name": "wrapX", + "description": "Controls the horizontal texture wrapping behavior", + "type": "String" + }, + { + "name": "wrapY", + "description": "Controls the vertical texture wrapping behavior", + "type": "String" + } + ] + } + ], + "class": "p5", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "toString", + "file": "src/color/p5.Color.js", + "line": 394, + "itemtype": "method", + "description": "Returns the color formatted as a string. Doing so can be useful for\ndebugging, or for using p5.js with other libraries.", + "example": [ + "
\n\ncreateCanvas(200, 100);\nstroke(255);\nconst myColor = color(100, 100, 250);\nfill(myColor);\nrotate(HALF_PI);\ntext(myColor.toString(), 0, -5);\ntext(myColor.toString('#rrggbb'), 0, -30);\ntext(myColor.toString('rgba%'), 0, -55);\ndescribe('Three text representation of a color written sideways.');\n\n
\n\n
\n\nconst myColor = color(100, 130, 250);\ntext(myColor.toString('#rrggbb'), 25, 25);\ndescribe('A hexadecimal representation of a color.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "format", + "description": "how the color string will be formatted.\nLeaving this empty formats the string as rgba(r, g, b, a).\n'#rgb' '#rgba' '#rrggbb' and '#rrggbbaa' format as hexadecimal color codes.\n'rgb' 'hsb' and 'hsl' return the color formatted in the specified color mode.\n'rgba' 'hsba' and 'hsla' are the same as above but with alpha channels.\n'rgb%' 'hsb%' 'hsl%' 'rgba%' 'hsba%' and 'hsla%' format as percentages.", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "the formatted string.", + "type": "String" + } + } + ], + "return": { + "description": "the formatted string.", + "type": "String" + }, + "class": "p5.Color", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "setRed", + "file": "src/color/p5.Color.js", + "line": 585, + "itemtype": "method", + "description": "Sets the red component of a color. The range depends on the\ncolorMode(). In the default RGB mode it's\nbetween 0 and 255.", + "example": [ + "
\n\nlet backgroundColor;\n\nfunction setup() {\n backgroundColor = color(100, 50, 150);\n}\n\nfunction draw() {\n backgroundColor.setRed(128 + 128 * sin(millis() / 1000));\n background(backgroundColor);\n describe('A canvas with a gradually changing background color.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "red", + "description": "the new red value.", + "type": "Number" + } + ] + } + ], + "class": "p5.Color", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "setGreen", + "file": "src/color/p5.Color.js", + "line": 614, + "itemtype": "method", + "description": "Sets the green component of a color. The range depends on the\ncolorMode(). In the default RGB mode it's\nbetween 0 and 255.", + "example": [ + "
\n\nlet backgroundColor;\n\nfunction setup() {\n backgroundColor = color(100, 50, 150);\n}\n\nfunction draw() {\n backgroundColor.setGreen(128 + 128 * sin(millis() / 1000));\n background(backgroundColor);\n describe('A canvas with a gradually changing background color.');\n}\n\n
\n*" + ], + "overloads": [ + { + "params": [ + { + "name": "green", + "description": "the new green value.", + "type": "Number" + } + ] + } + ], + "class": "p5.Color", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "setBlue", + "file": "src/color/p5.Color.js", + "line": 643, + "itemtype": "method", + "description": "Sets the blue component of a color. The range depends on the\ncolorMode(). In the default RGB mode it's\nbetween 0 and 255.", + "example": [ + "
\n\nlet backgroundColor;\n\nfunction setup() {\n backgroundColor = color(100, 50, 150);\n}\n\nfunction draw() {\n backgroundColor.setBlue(128 + 128 * sin(millis() / 1000));\n background(backgroundColor);\n describe('A canvas with a gradually changing background color.');\n}\n\n
\n*" + ], + "overloads": [ + { + "params": [ + { + "name": "blue", + "description": "the new blue value.", + "type": "Number" + } + ] + } + ], + "class": "p5.Color", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "setAlpha", + "file": "src/color/p5.Color.js", + "line": 672, + "itemtype": "method", + "description": "Sets the alpha (transparency) value of a color. The range depends on the\ncolorMode(). In the default RGB mode it's\nbetween 0 and 255.", + "example": [ + "
\n\nfunction draw() {\n clear();\n background(200);\n const squareColor = color(100, 50, 100);\n squareColor.setAlpha(128 + 128 * sin(millis() / 1000));\n fill(squareColor);\n rect(13, 13, width - 26, height - 26);\n describe(\n 'A purple square with gradually changing opacity drawn on a gray background.'\n );\n}\n\n
\n*" + ], + "overloads": [ + { + "params": [ + { + "name": "alpha", + "description": "the new alpha value.", + "type": "Number" + } + ] + } + ], + "class": "p5.Color", + "static": false, + "module": "Color", + "submodule": "Creating & Reading" + }, + { + "name": "reset", + "file": "src/core/p5.Graphics.js", + "line": 117, + "itemtype": "method", + "description": "Resets certain values such as those modified by functions in the Transform category\nand in the Lights category that are not automatically reset\nwith graphics buffer objects. Calling this in draw() will copy the behavior\nof the standard canvas.", + "example": [ + "
\nlet pg;\nfunction setup() {\n createCanvas(100, 100);\n background(0);\n pg = createGraphics(50, 100);\n pg.fill(0);\n frameRate(5);\n}\n\nfunction draw() {\n image(pg, width / 2, 0);\n pg.background(255);\n // p5.Graphics object behave a bit differently in some cases\n // The normal canvas on the left resets the translate\n // with every loop through draw()\n // the graphics object on the right doesn't automatically reset\n // so translate() is additive and it moves down the screen\n rect(0, 0, width / 2, 5);\n pg.rect(0, 0, width / 2, 5);\n translate(0, 5, 0);\n pg.translate(0, 5, 0);\n}\nfunction mouseClicked() {\n // if you click you will see that\n // reset() resets the translate back to the initial state\n // of the Graphics object\n pg.reset();\n}\n
" + ], + "alt": "A white line on a black background stays still on the top-left half.\nA black line animates from top to bottom on a white background on the right half.\nWhen clicked, the black line starts back over at the top.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Graphics", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "remove", + "file": "src/core/p5.Graphics.js", + "line": 176, + "itemtype": "method", + "description": "Removes a Graphics object from the page and frees any resources\nassociated with it.", + "example": [ + "
\nlet bg;\nfunction setup() {\n bg = createCanvas(100, 100);\n bg.background(0);\n image(bg, 0, 0);\n bg.remove();\n}\n
\n\n
\nlet bg;\nfunction setup() {\n pixelDensity(1);\n createCanvas(100, 100);\n stroke(255);\n fill(0);\n\n // create and draw the background image\n bg = createGraphics(100, 100);\n bg.background(200);\n bg.ellipse(50, 50, 80, 80);\n}\nfunction draw() {\n let t = millis() / 1000;\n // draw the background\n if (bg) {\n image(bg, frameCount % 100, 0);\n image(bg, frameCount % 100 - 100, 0);\n }\n // draw the foreground\n let p = p5.Vector.fromAngle(t, 35).add(50, 50);\n ellipse(p.x, p.y, 30);\n}\nfunction mouseClicked() {\n // remove the background\n if (bg) {\n bg.remove();\n bg = null;\n }\n}\n
" + ], + "alt": "no image\na multi-colored circle moving back and forth over a scrolling background.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Graphics", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "createFramebuffer", + "file": "src/core/p5.Graphics.js", + "line": 198, + "itemtype": "method", + "description": "

Creates and returns a new p5.Framebuffer\ninside a p5.Graphics WebGL context.

\n

This takes the same parameters as the global\ncreateFramebuffer function.

\n", + "example": [], + "overloads": [ + { + "params": [], + "return": { + "description": "", + "type": "p5.Framebuffer" + } + } + ], + "return": { + "description": "", + "type": "p5.Framebuffer" + }, + "class": "p5.Graphics", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "resize", + "file": "src/core/p5.Renderer.js", + "line": 122, + "itemtype": "method", + "description": "Resize our canvas element.", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Renderer", + "static": false, + "module": "Rendering", + "submodule": "Rendering" + }, + { + "name": "size", + "file": "src/data/p5.TypedDict.js", + "line": 116, + "itemtype": "method", + "description": "Returns the number of key-value pairs currently stored in the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createNumberDict(1, 10);\n myDictionary.create(2, 20);\n myDictionary.create(3, 30);\n print(myDictionary.size()); // logs 3 to the console\n}\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "the number of key-value pairs in the Dictionary", + "type": "Integer" + } + } + ], + "return": { + "description": "the number of key-value pairs in the Dictionary", + "type": "Integer" + }, + "class": "p5.TypedDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "hasKey", + "file": "src/data/p5.TypedDict.js", + "line": 137, + "itemtype": "method", + "description": "Returns true if the given key exists in the Dictionary,\notherwise returns false.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createStringDict('p5', 'js');\n print(myDictionary.hasKey('p5')); // logs true to console\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "key", + "description": "that you want to look up", + "type": "Number|String" + } + ], + "return": { + "description": "whether that key exists in Dictionary", + "type": "Boolean" + } + } + ], + "return": { + "description": "whether that key exists in Dictionary", + "type": "Boolean" + }, + "class": "p5.TypedDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "get", + "file": "src/data/p5.TypedDict.js", + "line": 158, + "itemtype": "method", + "description": "Returns the value stored at the given key.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createStringDict('p5', 'js');\n let myValue = myDictionary.get('p5');\n print(myValue === 'js'); // logs true to console\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "the", + "description": "key you want to access", + "type": "Number|String" + } + ], + "return": { + "description": "the value stored at that key", + "type": "Number|String" + } + } + ], + "return": { + "description": "the value stored at that key", + "type": "Number|String" + }, + "class": "p5.TypedDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "set", + "file": "src/data/p5.TypedDict.js", + "line": 184, + "itemtype": "method", + "description": "Updates the value associated with the given key in case it already exists\nin the Dictionary. Otherwise a new key-value pair is added.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createStringDict('p5', 'js');\n myDictionary.set('p5', 'JS');\n myDictionary.print(); // logs \"key: p5 - value: JS\" to console\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "key", + "type": "Number|String" + }, + { + "name": "value", + "type": "Number|String" + } + ] + } + ], + "class": "p5.TypedDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "create", + "file": "src/data/p5.TypedDict.js", + "line": 223, + "itemtype": "method", + "description": "Creates a new key-value pair in the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createStringDict('p5', 'js');\n myDictionary.create('happy', 'coding');\n myDictionary.print();\n // above logs \"key: p5 - value: js, key: happy - value: coding\" to console\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "key", + "type": "Number|String" + }, + { + "name": "value", + "type": "Number|String" + } + ] + }, + { + "params": [ + { + "name": "obj", + "description": "key/value pair", + "type": "Object" + } + ] + } + ], + "class": "p5.TypedDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "clear", + "file": "src/data/p5.TypedDict.js", + "line": 251, + "itemtype": "method", + "description": "Removes all previously stored key-value pairs from the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createStringDict('p5', 'js');\n print(myDictionary.hasKey('p5')); // prints 'true'\n myDictionary.clear();\n print(myDictionary.hasKey('p5')); // prints 'false'\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.TypedDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "remove", + "file": "src/data/p5.TypedDict.js", + "line": 274, + "itemtype": "method", + "description": "Removes the key-value pair stored at the given key from the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createStringDict('p5', 'js');\n myDictionary.create('happy', 'coding');\n myDictionary.print();\n // above logs \"key: p5 - value: js, key: happy - value: coding\" to console\n myDictionary.remove('p5');\n myDictionary.print();\n // above logs \"key: happy value: coding\" to console\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "key", + "description": "for the pair to remove", + "type": "Number|String" + } + ] + } + ], + "class": "p5.TypedDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "print", + "file": "src/data/p5.TypedDict.js", + "line": 297, + "itemtype": "method", + "description": "Logs the set of items currently stored in the Dictionary to the console.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createStringDict('p5', 'js');\n myDictionary.create('happy', 'coding');\n myDictionary.print();\n // above logs \"key: p5 - value: js, key: happy - value: coding\" to console\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.TypedDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "saveTable", + "file": "src/data/p5.TypedDict.js", + "line": 328, + "itemtype": "method", + "description": "Converts the Dictionary into a CSV file for local download.", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n text('click here to save', 10, 10, 70, 80);\n}\n\nfunction mousePressed() {\n if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {\n createStringDict({\n john: 1940,\n paul: 1942,\n george: 1943,\n ringo: 1940\n }).saveTable('beatles');\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.TypedDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "saveJSON", + "file": "src/data/p5.TypedDict.js", + "line": 364, + "itemtype": "method", + "description": "Converts the Dictionary into a JSON file for local download.", + "example": [ + "
\n\nfunction setup() {\n createCanvas(100, 100);\n background(200);\n text('click here to save', 10, 10, 70, 80);\n}\n\nfunction mousePressed() {\n if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {\n createStringDict({\n john: 1940,\n paul: 1942,\n george: 1943,\n ringo: 1940\n }).saveJSON('beatles');\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.TypedDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "add", + "file": "src/data/p5.TypedDict.js", + "line": 432, + "itemtype": "method", + "description": "Add the given number to the value currently stored at the given key.\nThe sum then replaces the value previously stored in the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createNumberDict(2, 5);\n myDictionary.add(2, 2);\n print(myDictionary.get(2)); // logs 7 to console.\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "Key", + "description": "for the value you wish to add to", + "type": "Number" + }, + { + "name": "Number", + "description": "to add to the value", + "type": "Number" + } + ] + } + ], + "class": "p5.NumberDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "sub", + "file": "src/data/p5.TypedDict.js", + "line": 457, + "itemtype": "method", + "description": "Subtract the given number from the value currently stored at the given key.\nThe difference then replaces the value previously stored in the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createNumberDict(2, 5);\n myDictionary.sub(2, 2);\n print(myDictionary.get(2)); // logs 3 to console.\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "Key", + "description": "for the value you wish to subtract from", + "type": "Number" + }, + { + "name": "Number", + "description": "to subtract from the value", + "type": "Number" + } + ] + } + ], + "class": "p5.NumberDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "mult", + "file": "src/data/p5.TypedDict.js", + "line": 478, + "itemtype": "method", + "description": "Multiply the given number with the value currently stored at the given key.\nThe product then replaces the value previously stored in the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createNumberDict(2, 4);\n myDictionary.mult(2, 2);\n print(myDictionary.get(2)); // logs 8 to console.\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "Key", + "description": "for value you wish to multiply", + "type": "Number" + }, + { + "name": "Amount", + "description": "to multiply the value by", + "type": "Number" + } + ] + } + ], + "class": "p5.NumberDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "div", + "file": "src/data/p5.TypedDict.js", + "line": 503, + "itemtype": "method", + "description": "Divide the given number with the value currently stored at the given key.\nThe quotient then replaces the value previously stored in the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createNumberDict(2, 8);\n myDictionary.div(2, 2);\n print(myDictionary.get(2)); // logs 4 to console.\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "Key", + "description": "for value you wish to divide", + "type": "Number" + }, + { + "name": "Amount", + "description": "to divide the value by", + "type": "Number" + } + ] + } + ], + "class": "p5.NumberDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "minValue", + "file": "src/data/p5.TypedDict.js", + "line": 548, + "itemtype": "method", + "description": "Return the lowest number currently stored in the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 });\n let lowestValue = myDictionary.minValue(); // value is -10\n print(lowestValue);\n}\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "", + "type": "Number" + } + } + ], + "return": { + "description": "", + "type": "Number" + }, + "class": "p5.NumberDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "maxValue", + "file": "src/data/p5.TypedDict.js", + "line": 566, + "itemtype": "method", + "description": "Return the highest number currently stored in the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 });\n let highestValue = myDictionary.maxValue(); // value is 3\n print(highestValue);\n}\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "", + "type": "Number" + } + } + ], + "return": { + "description": "", + "type": "Number" + }, + "class": "p5.NumberDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "minKey", + "file": "src/data/p5.TypedDict.js", + "line": 605, + "itemtype": "method", + "description": "Return the lowest key currently used in the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 });\n let lowestKey = myDictionary.minKey(); // value is 1.2\n print(lowestKey);\n}\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "", + "type": "Number" + } + } + ], + "return": { + "description": "", + "type": "Number" + }, + "class": "p5.NumberDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "maxKey", + "file": "src/data/p5.TypedDict.js", + "line": 623, + "itemtype": "method", + "description": "Return the highest key currently used in the Dictionary.", + "example": [ + "
\n\nfunction setup() {\n let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 });\n let highestKey = myDictionary.maxKey(); // value is 4\n print(highestKey);\n}\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "", + "type": "Number" + } + } + ], + "return": { + "description": "", + "type": "Number" + }, + "class": "p5.NumberDict", + "static": false, + "module": "Data", + "submodule": "Dictionary" + }, + { + "name": "play", + "file": "src/dom/dom.js", + "line": 3755, + "itemtype": "method", + "chainable": 1, + "description": "Play audio or video from a media element.", + "example": [ + "
\n\nlet beat;\n\nfunction setup() {\n background(200);\n\n textAlign(CENTER);\n text('Click to play', 50, 50);\n\n // Create a p5.MediaElement using createAudio().\n beat = createAudio('assets/beat.mp3');\n\n describe('The text \"Click to play\" written in black on a gray background. A beat plays when the user clicks the square.');\n}\n\n// Play the beat when the user\n// presses the mouse.\nfunction mousePressed() {\n beat.play();\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "stop", + "file": "src/dom/dom.js", + "line": 3832, + "itemtype": "method", + "chainable": 1, + "description": "Stops a media element and sets its current time to zero. Calling\nmedia.play() will restart playing audio/video from the beginning.", + "example": [ + "
\n\nlet beat;\nlet isStopped = true;\n\nfunction setup() {\n // Create a p5.MediaElement using createAudio().\n beat = createAudio('assets/beat.mp3');\n\n describe('The text \"Click to start\" written in black on a gray background. The beat starts or stops when the user presses the mouse.');\n}\n\nfunction draw() {\n background(200);\n\n textAlign(CENTER);\n if (isStopped === true) {\n text('Click to start', 50, 50);\n } else {\n text('Click to stop', 50, 50);\n }\n}\n\n// Adjust playback when the user\n// presses the mouse.\nfunction mousePressed() {\n if (isStopped === true) {\n // If the beat is stopped,\n // play it.\n beat.play();\n isStopped = false;\n } else {\n // If the beat is playing,\n // stop it.\n beat.stop();\n isStopped = true;\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "pause", + "file": "src/dom/dom.js", + "line": 3887, + "itemtype": "method", + "chainable": 1, + "description": "Pauses a media element. Calling media.play() will resume playing\naudio/video from the moment it paused.", + "example": [ + "
\n\nlet beat;\nlet isPaused = true;\n\nfunction setup() {\n // Create a p5.MediaElement using createAudio().\n beat = createAudio('assets/beat.mp3');\n\n describe('The text \"Click to play\" written in black on a gray background. The beat plays or pauses when the user clicks the square.');\n}\n\nfunction draw() {\n background(200);\n\n // Display different instructions\n // based on playback.\n textAlign(CENTER);\n if (isPaused === true) {\n text('Click to play', 50, 50);\n } else {\n text('Click to pause', 50, 50);\n }\n}\n\n// Adjust playback when the user\n// presses the mouse.\nfunction mousePressed() {\n if (isPaused === true) {\n // If the beat is paused,\n // play it.\n beat.play();\n isPaused = false;\n } else {\n // If the beat is playing,\n // pause it.\n beat.pause();\n isPaused = true;\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "loop", + "file": "src/dom/dom.js", + "line": 3942, + "itemtype": "method", + "chainable": 1, + "description": "Play the audio/video repeatedly in a loop.", + "example": [ + "
\n\nlet beat;\nlet isLooping = false;\n\nfunction setup() {\n background(200);\n\n // Create a p5.MediaElement using createAudio().\n beat = createAudio('assets/beat.mp3');\n\n describe('The text \"Click to loop\" written in black on a gray background. A beat plays repeatedly in a loop when the user clicks. The beat stops when the user clicks again.');\n}\n\nfunction draw() {\n background(200);\n\n // Display different instructions\n // based on playback.\n textAlign(CENTER);\n if (isLooping === true) {\n text('Click to stop', 50, 50);\n } else {\n text('Click to loop', 50, 50);\n }\n}\n\n// Adjust playback when the user\n// presses the mouse.\nfunction mousePressed() {\n if (isLooping === true) {\n // If the beat is looping,\n // stop it.\n beat.stop();\n isLooping = false;\n } else {\n // If the beat is stopped,\n // loop it.\n beat.loop();\n isLooping = true;\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "noLoop", + "file": "src/dom/dom.js", + "line": 3998, + "itemtype": "method", + "chainable": 1, + "description": "Stops the audio/video from playing in a loop. It will stop when it\nreaches the end.", + "example": [ + "
\n\nlet beat;\nlet isPlaying = false;\n\nfunction setup() {\n background(200);\n\n // Create a p5.MediaElement using createAudio().\n beat = createAudio('assets/beat.mp3');\n\n describe('The text \"Click to play\" written in black on a gray background. A beat plays when the user clicks. The beat stops when the user clicks again.');\n}\n\nfunction draw() {\n background(200);\n\n // Display different instructions\n // based on playback.\n textAlign(CENTER);\n if (isPlaying === true) {\n text('Click to stop', 50, 50);\n } else {\n text('Click to play', 50, 50);\n }\n}\n\n// Adjust playback when the user\n// presses the mouse.\nfunction mousePressed() {\n if (isPlaying === true) {\n // If the beat is playing,\n // stop it.\n beat.stop();\n isPlaying = false;\n } else {\n // If the beat is stopped,\n // play it.\n beat.play();\n isPlaying = true;\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "autoplay", + "file": "src/dom/dom.js", + "line": 4071, + "itemtype": "method", + "chainable": 1, + "description": "

Sets the audio/video to play once it's loaded.

\n

The parameter, shouldAutoplay, is optional. Calling\nmedia.autoplay() without an argument causes the media to play\nautomatically. If true is passed, as in media.autoplay(true), the\nmedia will automatically play. If false is passed, as in\nmedia.autoPlay(false), it won't play automatically.

\n", + "example": [ + "
\n\nfunction setup() {\n noCanvas();\n\n // Load a video and play it automatically.\n let video = createVideo('assets/fingers.mov', () => {\n video.autoplay();\n video.size(100, 100);\n });\n\n describe('A video of fingers walking on a treadmill.');\n}\n\n
\n\n
\n\nfunction setup() {\n noCanvas();\n\n // Load a video, but don't play it automatically.\n let video = createVideo('assets/fingers.mov', () => {\n video.autoplay(false);\n video.size(100, 100);\n });\n\n // Play the video when the user clicks on it.\n video.mousePressed(() => {\n video.play();\n });\n\n describe('An image of fingers on a treadmill. They start walking when the user double-clicks on them.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "shouldAutoplay", + "description": "whether the element should autoplay.", + "optional": 1, + "type": "Boolean" + } + ] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "volume", + "file": "src/dom/dom.js", + "line": 4145, + "itemtype": "method", + "description": "

Manages the audio/video volume.

\n

Calling media.volume() without an argument returns the current volume\nas a number in the range 0 (off) to 1 (maximum).

\n

The parameter, val, is optional. It's a number that sets the volume\nfrom 0 (off) to 1 (maximum). For example, calling media.volume(0.5)\nsets the volume to half of its maximum.

\n", + "example": [ + "
\n\nlet dragon;\n\nfunction setup() {\n // Create a p5.MediaElement using createAudio().\n dragon = createAudio('assets/lucky_dragons.mp3');\n // Show the default media controls.\n dragon.showControls();\n\n describe('The text \"Volume: V\" on a gray square with media controls beneath it. The number \"V\" oscillates between 0 and 1 as the music plays.');\n}\n\nfunction draw() {\n background(200);\n\n // Produce a number between 0 and 1.\n let n = 0.5 * sin(frameCount * 0.01) + 0.5;\n // Use n to set the volume.\n dragon.volume(n);\n\n // Get the current volume\n // and display it.\n let v = dragon.volume();\n // Round v to 1 decimal place\n // for display.\n v = round(v, 1);\n textAlign(CENTER);\n text(`Volume: ${v}`, 50, 50);\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "current volume.", + "type": "Number" + } + } + ], + "return": { + "description": "current volume.", + "type": "Number" + }, + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "speed", + "file": "src/dom/dom.js", + "line": 4205, + "itemtype": "method", + "chainable": 1, + "description": "

Manages the audio/video playback speed. Calling media.speed() returns\nthe current speed as a number.

\n

The parameter, val, is optional. It's a number that sets the playback\nspeed. 1 plays the media at normal speed, 0.5 plays it at half speed, 2\nplays it at double speed, and so on. -1 plays the media at normal speed\nin reverse.

\n

Note: Not all browsers support backward playback. Even if they do,\nplayback might not be smooth.

\n", + "example": [ + "
\n\nlet dragon;\n\nfunction setup() {\n // Create a p5.MediaElement using createAudio().\n dragon = createAudio('assets/lucky_dragons.mp3');\n\n // Show the default media controls.\n dragon.showControls();\n\n describe('The text \"Speed: S\" on a gray square with media controls beneath it. The number \"S\" oscillates between 0 and 1 as the music plays.');\n}\n\nfunction draw() {\n background(200);\n\n // Produce a number between 0 and 2.\n let n = sin(frameCount * 0.01) + 1;\n // Use n to set the playback speed.\n dragon.speed(n);\n\n // Get the current speed\n // and display it.\n let s = dragon.speed();\n // Round s to 1 decimal place\n // for display.\n s = round(s, 1);\n textAlign(CENTER);\n text(`Speed: ${s}`, 50, 50);\n}\n" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "current playback speed.", + "type": "Number" + } + }, + { + "params": [ + { + "name": "speed", + "description": "speed multiplier for playback.", + "type": "Number" + } + ] + } + ], + "return": { + "description": "current playback speed.", + "type": "Number" + }, + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "time", + "file": "src/dom/dom.js", + "line": 4290, + "itemtype": "method", + "chainable": 1, + "description": "

Manages the media element's playback time. Calling media.time()\nreturns the number of seconds the audio/video has played. Time resets to\n0 when the looping media restarts.

\n

The parameter, time, is optional. It's a number that specifies the\ntime, in seconds, to jump to when playback begins.

\n", + "example": [ + "
\n\nlet dragon;\n\nfunction setup() {\n // Create a p5.MediaElement using createAudio().\n dragon = createAudio('assets/lucky_dragons.mp3');\n // Show the default media controls.\n dragon.showControls();\n\n describe('The text \"S seconds\" on a gray square with media controls beneath it. The number \"S\" increases as the song plays.');\n}\n\nfunction draw() {\n background(200);\n\n // Display the current time.\n let s = dragon.time();\n // Round s to 1 decimal place\n // for display.\n s = round(s, 1);\n textAlign(CENTER);\n text(`${s} seconds`, 50, 50);\n}\n\n
\n\n
\n\nlet dragon;\n\nfunction setup() {\n // Create a p5.MediaElement using createAudio().\n dragon = createAudio('assets/lucky_dragons.mp3');\n // Show the default media controls.\n dragon.showControls();\n\n // Jump to 2 seconds\n // to start.\n dragon.time(2);\n\n describe('The text \"S seconds\" on a gray square with media controls beneath it. The number \"S\" increases as the song plays.');\n}\n\nfunction draw() {\n background(200);\n\n // Display the current time.\n let s = dragon.time();\n // Round s to 1 decimal place\n // for display.\n s = round(s, 1);\n textAlign(CENTER);\n text(`${s} seconds`, 50, 50);\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "current time (in seconds).", + "type": "Number" + } + }, + { + "params": [ + { + "name": "time", + "description": "time to jump to (in seconds).", + "type": "Number" + } + ] + } + ], + "return": { + "description": "current time (in seconds).", + "type": "Number" + }, + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "duration", + "file": "src/dom/dom.js", + "line": 4336, + "itemtype": "method", + "description": "Returns the audio/video's duration in seconds.", + "example": [ + "
\n\nlet dragon;\n\nfunction setup() {\n background(200);\n\n // Create a p5.MediaElement using createAudio().\n dragon = createAudio('assets/lucky_dragons.mp3');\n // Show the default media controls.\n dragon.showControls();\n\n describe('The text \"S seconds left\" on a gray square with media controls beneath it. The number \"S\" decreases as the song plays.');\n}\n\nfunction draw() {\n background(200);\n\n // Calculate the time remaining.\n let s = dragon.duration() - dragon.time();\n // Round s to 1 decimal place\n // for display.\n s = round(s, 1);\n\n // Display the time remaining.\n textAlign(CENTER);\n text(`${s} seconds left`, 50, 50);\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "duration (in seconds).", + "type": "Number" + } + } + ], + "return": { + "description": "duration (in seconds).", + "type": "Number" + }, + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "onended", + "file": "src/dom/dom.js", + "line": 4496, + "itemtype": "method", + "chainable": 1, + "description": "

Calls a function when the audio/video reaches the end of its playback\nThe function won't be called if the media is looping.

\n

The p5.MediaElement is passed as an argument to the callback function.

\n", + "example": [ + "
\n\nlet beat;\nlet isPlaying = false;\nlet isDone = false;\n\nfunction setup() {\n\n // Create a p5.MediaElement using createAudio().\n beat = createAudio('assets/beat.mp3');\n\n // Set isDone to false when\n // the beat finishes.\n beat.onended(() => {\n isDone = true;\n });\n\n describe('The text \"Click to play\" written in black on a gray square. A beat plays when the user clicks. The text \"Done!\" appears when the beat finishes playing.');\n}\n\nfunction draw() {\n background(200);\n\n // Display different messages\n // based on playback.\n textAlign(CENTER);\n if (isDone === true) {\n text('Done!', 50, 50);\n } else if (isPlaying === false) {\n text('Click to play', 50, 50);\n } else {\n text('Playing...', 50, 50);\n }\n}\n\n// Play the beat when the\n// user presses the mouse.\nfunction mousePressed() {\n if (isPlaying === false) {\n isPlaying = true;\n beat.play();\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "callback", + "description": "function to call when playback ends.\nThe p5.MediaElement is passed as\nthe argument.", + "type": "Function" + } + ] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "connect", + "file": "src/dom/dom.js", + "line": 4514, + "itemtype": "method", + "description": "

Send the audio output of this element to a specified audioNode or\np5.sound object. If no element is provided, connects to p5's main\noutput. That connection is established when this method is first called.\nAll connections are removed by the .disconnect() method.

\n

This method is meant to be used with the p5.sound.js addon library.

\n", + "example": [], + "overloads": [ + { + "params": [ + { + "name": "audioNode", + "description": "AudioNode from the Web Audio API,\nor an object from the p5.sound library", + "type": "AudioNode|Object" + } + ] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "disconnect", + "file": "src/dom/dom.js", + "line": 4556, + "itemtype": "method", + "description": "Disconnect all Web Audio routing, including to main output.\nThis is useful if you want to re-route the output through\naudio effects, for example.", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "showControls", + "file": "src/dom/dom.js", + "line": 4591, + "itemtype": "method", + "description": "Show the default\nHTMLMediaElement\ncontrols. These vary between web browser.", + "example": [ + "
\n\nfunction setup() {\n background('cornflowerblue');\n\n textAlign(CENTER);\n textSize(50);\n text('🐉', 50, 50);\n\n // Create a p5.MediaElement using createAudio().\n let dragon = createAudio('assets/lucky_dragons.mp3');\n // Show the default media controls.\n dragon.showControls();\n\n describe('A dragon emoji, 🐉, drawn in the center of a blue square. A song plays in the background. Audio controls are displayed beneath the canvas.');\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "hideControls", + "file": "src/dom/dom.js", + "line": 4643, + "itemtype": "method", + "description": "Hide the default\nHTMLMediaElement\ncontrols.", + "example": [ + "
\n\nlet dragon;\nlet isHidden = false;\n\nfunction setup() {\n // Create a p5.MediaElement using createAudio().\n dragon = createAudio('assets/lucky_dragons.mp3');\n // Show the default media controls.\n dragon.showControls();\n\n describe('The text \"Double-click to hide controls\" written in the middle of a gray square. A song plays in the background. Audio controls are displayed beneath the canvas. The controls appear/disappear when the user double-clicks the square.');\n}\n\nfunction draw() {\n background(200);\n\n // Display a different message when\n // controls are hidden or shown.\n textAlign(CENTER);\n if (isHidden === true) {\n text('Double-click to show controls', 10, 20, 80, 80);\n } else {\n text('Double-click to hide controls', 10, 20, 80, 80);\n }\n}\n\n// Show/hide controls based on a double-click.\nfunction doubleClicked() {\n if (isHidden === true) {\n dragon.showControls();\n isHidden = false;\n } else {\n dragon.hideControls();\n isHidden = true;\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "addCue", + "file": "src/dom/dom.js", + "line": 4693, + "itemtype": "method", + "description": "

Schedules a function to call when the audio/video reaches a specific time\nduring its playback.

\n

The first parameter, time, is the time, in seconds, when the function\nshould run. This value is passed to callback as its first argument.

\n

The second parameter, callback, is the function to call at the specified\ncue time.

\n

The third parameter, value, is optional and can be any type of value.\nvalue is passed to callback.

\n

Calling media.addCue() returns an ID as a string. This is useful for\nremoving the cue later.

\n", + "example": [ + "
\n\nfunction setup() {\n // Create a p5.MediaElement using createAudio().\n let beat = createAudio('assets/beat.mp3');\n // Play the beat in a loop.\n beat.loop();\n\n // Schedule a few events.\n beat.addCue(0, changeBackground, 'red');\n beat.addCue(2, changeBackground, 'deeppink');\n beat.addCue(4, changeBackground, 'orchid');\n beat.addCue(6, changeBackground, 'lavender');\n\n describe('A red square with a beat playing in the background. Its color changes every 2 seconds while the audio plays.');\n}\n\nfunction changeBackground(c) {\n background(c);\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "time", + "description": "cue time to run the callback function.", + "type": "Number" + }, + { + "name": "callback", + "description": "function to call at the cue time.", + "type": "Function" + }, + { + "name": "value", + "description": "object to pass as the argument to\ncallback.", + "optional": 1, + "type": "Object" + } + ], + "return": { + "description": "id ID of this cue,\nuseful for media.removeCue(id).", + "type": "Number" + } + } + ], + "return": { + "description": "id ID of this cue,\nuseful for media.removeCue(id).", + "type": "Number" + }, + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "removeCue", + "file": "src/dom/dom.js", + "line": 4756, + "itemtype": "method", + "description": "Remove a callback based on its ID.", + "example": [ + "
\n\nlet lavenderID;\nlet isRemoved = false;\n\nfunction setup() {\n // Create a p5.MediaElement using createAudio().\n let beat = createAudio('assets/beat.mp3');\n // Play the beat in a loop.\n beat.loop();\n\n // Schedule a few events.\n beat.addCue(0, changeBackground, 'red');\n beat.addCue(2, changeBackground, 'deeppink');\n beat.addCue(4, changeBackground, 'orchid');\n\n // Record the ID of the \"lavender\" callback.\n lavenderID = beat.addCue(6, changeBackground, 'lavender');\n\n describe('The text \"Double-click to remove lavender.\" written on a red square. The color changes every 2 seconds while the audio plays. The lavender option is removed when the user double-clicks the square.');\n}\n\nfunction draw() {\n if (isRemoved === false) {\n text('Double-click to remove lavender.', 10, 10, 80, 80);\n } else {\n text('No more lavender.', 10, 10, 80, 80);\n }\n}\n\nfunction changeBackground(c) {\n background(c);\n}\n\n// Remove the lavender color-change cue\n// when the user double-clicks.\nfunction doubleClicked() {\n if (isRemoved === false) {\n beat.removeCue(lavenderID);\n isRemoved = true;\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "id", + "description": "ID of the cue, created by media.addCue().", + "type": "Number" + } + ] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "clearCues", + "file": "src/dom/dom.js", + "line": 4818, + "itemtype": "method", + "description": "Removes all functions scheduled with media.addCue().", + "example": [ + "
\n\nlet isChanging = true;\n\nfunction setup() {\n background(200);\n\n // Create a p5.MediaElement using createAudio().\n let beat = createAudio('assets/beat.mp3');\n // Play the beat in a loop.\n beat.loop();\n\n // Schedule a few events.\n beat.addCue(0, changeBackground, 'red');\n beat.addCue(2, changeBackground, 'deeppink');\n beat.addCue(4, changeBackground, 'orchid');\n beat.addCue(6, changeBackground, 'lavender');\n\n describe('The text \"Double-click to stop changing.\" written on a square. The color changes every 2 seconds while the audio plays. The color stops changing when the user double-clicks the square.');\n}\n\nfunction draw() {\n if (isChanging === true) {\n text('Double-click to stop changing.', 10, 10, 80, 80);\n } else {\n text('No more changes.', 10, 10, 80, 80);\n }\n}\n\nfunction changeBackground(c) {\n background(c);\n}\n\n// Remove cued functions and stop\n// changing colors when the user\n// double-clicks.\nfunction doubleClicked() {\n if (isChanging === true) {\n beat.clearCues();\n isChanging = false;\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.MediaElement", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "pixelDensity", + "file": "src/image/p5.Image.js", + "line": 116, + "itemtype": "method", + "description": "

Gets or sets the pixel density for high pixel density displays. By default,\nthe density will be set to 1.

\n

Call this method with no arguments to get the default density, or pass\nin a number to set the density. If a non-positive number is provided,\nit defaults to 1.

\n", + "example": [], + "overloads": [ + { + "params": [ + { + "name": "density", + "description": "A scaling factor for the number of pixels per\nside", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "The current density if called without arguments, or the instance for chaining if setting density.", + "type": "Number" + } + } + ], + "return": { + "description": "The current density if called without arguments, or the instance for chaining if setting density.", + "type": "Number" + }, + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "loadPixels", + "file": "src/image/p5.Image.js", + "line": 228, + "itemtype": "method", + "description": "Loads the current value of each pixel in the\np5.Image object into the img.pixels array.\nThis method must be called before reading or modifying pixel values.", + "example": [ + "
\n\nlet img = createImage(66, 66);\nimg.loadPixels();\nfor (let x = 0; x < img.width; x += 1) {\n for (let y = 0; y < img.height; y += 1) {\n img.set(x, y, 0);\n }\n}\nimg.updatePixels();\nimage(img, 17, 17);\n\ndescribe('A black square drawn in the middle of a gray square.');\n\n
\n\n
\n\nlet img = createImage(66, 66);\nimg.loadPixels();\nlet numPixels = 4 * img.width * img.height;\nfor (let i = 0; i < numPixels; i += 4) {\n // Red.\n img.pixels[i] = 0;\n // Green.\n img.pixels[i + 1] = 0;\n // Blue.\n img.pixels[i + 2] = 0;\n // Alpha.\n img.pixels[i + 3] = 255;\n}\nimg.updatePixels();\nimage(img, 17, 17);\n\ndescribe('A black square drawn in the middle of a gray square.');\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "updatePixels", + "file": "src/image/p5.Image.js", + "line": 298, + "itemtype": "method", + "description": "

Updates the canvas with the RGBA values in the\nimg.pixels array.

\n

img.updatePixels() only needs to be called after changing values in\nthe img.pixels array. Such changes can be\nmade directly after calling\nimg.loadPixels() or by calling\nimg.set().

\n

The optional parameters x, y, width, and height define a\nsubsection of the p5.Image object to update.\nDoing so can improve performance in some cases.

\n

If the p5.Image object was loaded from a GIF,\nthen calling img.updatePixels() will update the pixels in current\nframe.

\n", + "example": [ + "
\n\nlet img = createImage(66, 66);\nimg.loadPixels();\nfor (let x = 0; x < img.width; x += 1) {\n for (let y = 0; y < img.height; y += 1) {\n img.set(x, y, 0);\n }\n}\nimg.updatePixels();\nimage(img, 17, 17);\n\ndescribe('A black square drawn in the middle of a gray square.');\n\n
\n\n
\n\nlet img = createImage(66, 66);\nimg.loadPixels();\nlet numPixels = 4 * img.width * img.height;\nfor (let i = 0; i < numPixels; i += 4) {\n // Red.\n img.pixels[i] = 0;\n // Green.\n img.pixels[i + 1] = 0;\n // Blue.\n img.pixels[i + 2] = 0;\n // Alpha.\n img.pixels[i + 3] = 255;\n}\nimg.updatePixels();\nimage(img, 17, 17);\n\ndescribe('A black square drawn in the middle of a gray square.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the upper-left corner\nof the subsection to update.", + "type": "Integer" + }, + { + "name": "y", + "description": "y-coordinate of the upper-left corner\nof the subsection to update.", + "type": "Integer" + }, + { + "name": "w", + "description": "width of the subsection to update.", + "type": "Integer" + }, + { + "name": "h", + "description": "height of the subsection to update.", + "type": "Integer" + } + ] + }, + { + "params": [] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "get", + "file": "src/image/p5.Image.js", + "line": 396, + "itemtype": "method", + "description": "

Gets a pixel or a region of pixels from a\np5.Image object.

\n

img.get() is easy to use but it's not as fast as\nimg.pixels. Use\nimg.pixels to read many pixel values.

\n

The version of img.get() with no parameters returns the entire image.

\n

The version of img.get() with two parameters interprets them as\ncoordinates. It returns an array with the [R, G, B, A] values of the\npixel at the given point.

\n

The version of img.get() with four parameters interprets them as\ncoordinates and dimensions. It returns a subsection of the canvas as a\np5.Image object. The first two parameters are\nthe coordinates for the upper-left corner of the subsection. The last two\nparameters are the width and height of the subsection.

\n

Use img.get() to work directly with\np5.Image objects.

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n let img2 = get();\n image(img2, width / 2, 0);\n\n describe('Two identical mountain landscapes shown side-by-side.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n let c = img.get(50, 90);\n fill(c);\n noStroke();\n square(25, 25, 50);\n\n describe('A mountain landscape with an olive green square in its center.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n let img2 = img.get(0, 0, img.width / 2, img.height / 2);\n image(img2, width / 2, height / 2);\n\n describe('A mountain landscape drawn on top of another mountain landscape.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the pixel.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the pixel.", + "type": "Number" + }, + { + "name": "w", + "description": "width of the subsection to be returned.", + "type": "Number" + }, + { + "name": "h", + "description": "height of the subsection to be returned.", + "type": "Number" + } + ], + "return": { + "description": "subsection as a p5.Image object.", + "type": "p5.Image" + } + }, + { + "params": [], + "return": { + "description": "whole p5.Image", + "type": "p5.Image" + } + }, + { + "params": [ + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + } + ], + "return": { + "description": "color of the pixel at (x, y) in array format [R, G, B, A].", + "type": "Number[]" + } + } + ], + "return": { + "description": "subsection as a p5.Image object.", + "type": "p5.Image" + }, + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "set", + "file": "src/image/p5.Image.js", + "line": 490, + "itemtype": "method", + "description": "

Sets the color of one or more pixels within a\np5.Image object.

\n

img.set() is easy to use but it's not as fast as\nimg.pixels. Use\nimg.pixels to set many pixel values.

\n

img.set() interprets the first two parameters as x- and y-coordinates. It\ninterprets the last parameter as a grayscale value, a [R, G, B, A] pixel\narray, a p5.Color object, or another\np5.Image object.

\n

img.updatePixels() must be called\nafter using img.set() for changes to appear.

\n", + "example": [ + "
\n\nlet img = createImage(100, 100);\nimg.set(30, 20, 0);\nimg.set(85, 20, 0);\nimg.set(85, 75, 0);\nimg.set(30, 75, 0);\nimg.updatePixels();\nimage(img, 0, 0);\n\ndescribe('Four black dots arranged in a square drawn on a gray background.');\n\n
\n\n
\n\nlet img = createImage(100, 100);\nlet black = color(0);\nimg.set(30, 20, black);\nimg.set(85, 20, black);\nimg.set(85, 75, black);\nimg.set(30, 75, black);\nimg.updatePixels();\nimage(img, 0, 0);\n\ndescribe('Four black dots arranged in a square drawn on a gray background.');\n\n
\n\n
\n\nlet img = createImage(66, 66);\nfor (let x = 0; x < img.width; x += 1) {\n for (let y = 0; y < img.height; y += 1) {\n let c = map(x, 0, img.width, 0, 255);\n img.set(x, y, c);\n }\n}\nimg.updatePixels();\nimage(img, 17, 17);\n\ndescribe('A square with a horiztonal color gradient from black to white drawn on a gray background.');\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n let img2 = createImage(100, 100);\n img2.set(0, 0, img);\n image(img2, 0, 0);\n\n describe('An image of a mountain landscape.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the pixel.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the pixel.", + "type": "Number" + }, + { + "name": "a", + "description": "grayscale value | pixel array |\np5.Color object |\np5.Image to copy.", + "type": "Number|Number[]|Object" + } + ] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "resize", + "file": "src/image/p5.Image.js", + "line": 559, + "itemtype": "method", + "description": "Resizes the p5.Image object to a given width\nand height. The image's original aspect ratio can be kept by passing 0\nfor either width or height. For example, calling img.resize(50, 0)\non an image that was 500 × 300 pixels will resize it to\n50 × 30 pixels.", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n img.resize(50, 100);\n image(img, 0, 0);\n\n describe('Two images of a mountain landscape. One copy of the image is squeezed horizontally.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n img.resize(0, 30);\n image(img, 0, 0);\n\n describe('Two images of a mountain landscape. The small copy of the image covers the top-left corner of the larger image.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n image(img, 0, 0);\n img.resize(60, 0);\n image(img, 0, 0);\n\n describe('Two images of a mountain landscape. The small copy of the image covers the top-left corner of the larger image.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "width", + "description": "resized image width.", + "type": "Number" + }, + { + "name": "height", + "description": "resized image height.", + "type": "Number" + } + ] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "copy", + "file": "src/image/p5.Image.js", + "line": 707, + "itemtype": "method", + "description": "Copies pixels from a source p5.Image\nto this one. Calling img.copy() will scale pixels from the source\nregion if it isn't the same size as the destination region.", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction setup() {\n img.copy(7, 22, 10, 10, 35, 25, 50, 50);\n image(img, 0, 0);\n // Outline copied region.\n stroke(255);\n noFill();\n square(7, 22, 10);\n\n describe('An image of a mountain landscape. A square region is outlined in white. A larger square contains a pixelated view of the outlined region.');\n}\n\n
\n\n
\n\nlet mountains;\nlet bricks;\n\nfunction preload() {\n mountains = loadImage('assets/rockies.jpg');\n bricks = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n let x = bricks.width / 2;\n let y = bricks.height / 2;\n mountains.copy(bricks, 0, 0, x, y, 0, 0, x, y);\n image(mountains, 0, 0);\n\n describe('An image of a brick wall drawn at the top-left of an image of a mountain landscape.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "srcImage", + "description": "source image.", + "type": "p5.Image|p5.Element" + }, + { + "name": "sx", + "description": "x-coordinate of the source's upper-left corner.", + "type": "Integer" + }, + { + "name": "sy", + "description": "y-coordinate of the source's upper-left corner.", + "type": "Integer" + }, + { + "name": "sw", + "description": "source image width.", + "type": "Integer" + }, + { + "name": "sh", + "description": "source image height.", + "type": "Integer" + }, + { + "name": "dx", + "description": "x-coordinate of the destination's upper-left corner.", + "type": "Integer" + }, + { + "name": "dy", + "description": "y-coordinate of the destination's upper-left corner.", + "type": "Integer" + }, + { + "name": "dw", + "description": "destination image width.", + "type": "Integer" + }, + { + "name": "dh", + "description": "destination image height.", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "sx", + "type": "Integer" + }, + { + "name": "sy", + "type": "Integer" + }, + { + "name": "sw", + "type": "Integer" + }, + { + "name": "sh", + "type": "Integer" + }, + { + "name": "dx", + "type": "Integer" + }, + { + "name": "dy", + "type": "Integer" + }, + { + "name": "dw", + "type": "Integer" + }, + { + "name": "dh", + "type": "Integer" + } + ] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "mask", + "file": "src/image/p5.Image.js", + "line": 740, + "itemtype": "method", + "description": "Masks part of an image from displaying by loading another\nimage and using its alpha channel as an alpha channel for\nthis image. Masks are cumulative, once applied to an image\nobject, they cannot be removed.", + "example": [ + "
\n\nlet photo;\nlet maskImage;\n\nfunction preload() {\n photo = loadImage('assets/rockies.jpg');\n maskImage = loadImage('assets/mask2.png');\n}\n\nfunction setup() {\n photo.mask(maskImage);\n image(photo, 0, 0);\n\n describe('An image of a mountain landscape. The right side of the image has a faded patch of white.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "srcImage", + "description": "source image.", + "type": "p5.Image" + } + ] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "filter", + "file": "src/image/p5.Image.js", + "line": 966, + "itemtype": "method", + "description": "

Applies an image filter to the p5.Image object.\nThe preset options are:

\n

INVERT\nInverts the colors in the image. No parameter is used.

\n

GRAY\nConverts the image to grayscale. No parameter is used.

\n

THRESHOLD\nConverts the image to black and white. Pixels with a grayscale value\nabove a given threshold are converted to white. The rest are converted to\nblack. The threshold must be between 0.0 (black) and 1.0 (white). If no\nvalue is specified, 0.5 is used.

\n

OPAQUE\nSets the alpha channel to be entirely opaque. No parameter is used.

\n

POSTERIZE\nLimits the number of colors in the image. Each color channel is limited to\nthe number of colors specified. Values between 2 and 255 are valid, but\nresults are most noticeable with lower values. The default value is 4.

\n

BLUR\nBlurs the image. The level of blurring is specified by a blur radius. Larger\nvalues increase the blur. The default value is 4. A gaussian blur is used\nin P2D mode. A box blur is used in WEBGL mode.

\n

ERODE\nReduces the light areas. No parameter is used.

\n

DILATE\nIncreases the light areas. No parameter is used.

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n img.filter(INVERT);\n image(img, 0, 0);\n\n describe('A blue brick wall.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n img.filter(GRAY);\n image(img, 0, 0);\n\n describe('A brick wall drawn in grayscale.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n img.filter(THRESHOLD);\n image(img, 0, 0);\n\n describe('A brick wall drawn in black and white.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n img.filter(OPAQUE);\n image(img, 0, 0);\n\n describe('A red brick wall.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n img.filter(POSTERIZE, 3);\n image(img, 0, 0);\n\n describe('An image of a red brick wall drawn with a limited color palette.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n img.filter(BLUR, 3);\n image(img, 0, 0);\n\n describe('A blurry image of a red brick wall.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n img.filter(DILATE);\n image(img, 0, 0);\n\n describe('A red brick wall with bright lines between each brick.');\n}\n\n
\n\n
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/bricks.jpg');\n}\n\nfunction setup() {\n img.filter(ERODE);\n image(img, 0, 0);\n\n describe('A red brick wall with faint lines between each brick.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "filterType", + "description": "either THRESHOLD, GRAY, OPAQUE, INVERT,\nPOSTERIZE, ERODE, DILATE or BLUR.", + "type": "Constant" + }, + { + "name": "filterParam", + "description": "parameter unique to each filter.", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "blend", + "file": "src/image/p5.Image.js", + "line": 1068, + "itemtype": "method", + "description": "Copies a region of pixels from another\np5.Image object into this one. The blendMode\nparameter blends the images' colors to create different effects.", + "example": [ + "
\n\nlet mountains;\nlet bricks;\n\nfunction preload() {\n mountains = loadImage('assets/rockies.jpg');\n bricks = loadImage('assets/bricks_third.jpg');\n}\n\nfunction setup() {\n mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, ADD);\n image(mountains, 0, 0);\n image(bricks, 0, 0);\n\n describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears faded on the right of the image.');\n}\n\n
\n\n
\n\nlet mountains;\nlet bricks;\n\nfunction preload() {\n mountains = loadImage('assets/rockies.jpg');\n bricks = loadImage('assets/bricks_third.jpg');\n}\n\nfunction setup() {\n mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST);\n image(mountains, 0, 0);\n image(bricks, 0, 0);\n\n describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears transparent on the right of the image.');\n}\n\n
\n\n
\n\nlet mountains;\nlet bricks;\n\nfunction preload() {\n mountains = loadImage('assets/rockies.jpg');\n bricks = loadImage('assets/bricks_third.jpg');\n}\n\nfunction setup() {\n mountains.blend(bricks, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST);\n image(mountains, 0, 0);\n image(bricks, 0, 0);\n\n describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears washed out on the right of the image.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "srcImage", + "description": "source image", + "type": "p5.Image" + }, + { + "name": "sx", + "description": "x-coordinate of the source's upper-left corner.", + "type": "Integer" + }, + { + "name": "sy", + "description": "y-coordinate of the source's upper-left corner.", + "type": "Integer" + }, + { + "name": "sw", + "description": "source image width.", + "type": "Integer" + }, + { + "name": "sh", + "description": "source image height.", + "type": "Integer" + }, + { + "name": "dx", + "description": "x-coordinate of the destination's upper-left corner.", + "type": "Integer" + }, + { + "name": "dy", + "description": "y-coordinate of the destination's upper-left corner.", + "type": "Integer" + }, + { + "name": "dw", + "description": "destination image width.", + "type": "Integer" + }, + { + "name": "dh", + "description": "destination image height.", + "type": "Integer" + }, + { + "name": "blendMode", + "description": "

the blend mode. either\nBLEND, DARKEST, LIGHTEST, DIFFERENCE,\nMULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,\nSOFT_LIGHT, DODGE, BURN, ADD or NORMAL.

\n

Available blend modes are: normal | multiply | screen | overlay |\ndarken | lighten | color-dodge | color-burn | hard-light |\nsoft-light | difference | exclusion | hue | saturation |\ncolor | luminosity

\n

http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/

\n", + "type": "Constant" + } + ] + }, + { + "params": [ + { + "name": "sx", + "type": "Integer" + }, + { + "name": "sy", + "type": "Integer" + }, + { + "name": "sw", + "type": "Integer" + }, + { + "name": "sh", + "type": "Integer" + }, + { + "name": "dx", + "type": "Integer" + }, + { + "name": "dy", + "type": "Integer" + }, + { + "name": "dw", + "type": "Integer" + }, + { + "name": "dh", + "type": "Integer" + }, + { + "name": "blendMode", + "type": "Constant" + } + ] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "save", + "file": "src/image/p5.Image.js", + "line": 1145, + "itemtype": "method", + "description": "

Saves the p5.Image object to a file.\nThe browser will either save the file immediately or prompt the user\nwith a dialogue window.

\n

By default, calling img.save() will save the image as untitled.png.

\n

Calling img.save() with one argument, as in img.save('photo.png'),\nwill set the image's filename and type together.

\n

Calling img.save() with two arguments, as in\nimage.save('photo', 'png'), will set the image's filename and type\nseparately.

\n

The image will only be downloaded as an animated GIF if the\np5.Image object was loaded from a GIF file.\nSee saveGif() to create new GIFs.

\n", + "example": [ + "
\n\nlet img;\n\nfunction preload() {\n img = loadImage('assets/rockies.jpg');\n}\n\nfunction draw() {\n image(img, 0, 0);\n\n describe('An image of a mountain landscape.');\n}\n\nfunction keyPressed() {\n if (key === 's') {\n img.save();\n } else if (key === 'j') {\n img.save('rockies.jpg');\n } else if (key === 'p') {\n img.save('rockies', 'png');\n }\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "filename", + "description": "filename. Defaults to 'untitled'.", + "type": "String" + }, + { + "name": "extension", + "description": "file extension, either 'png' or 'jpg'.\nDefaults to 'png'.", + "optional": 1, + "type": "String" + } + ] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "reset", + "file": "src/image/p5.Image.js", + "line": 1179, + "itemtype": "method", + "description": "Restarts an animated GIF at its first frame.", + "example": [ + "
\n\nlet gif;\n\nfunction preload() {\n gif = loadImage('assets/arnott-wallace-wink-loop-once.gif');\n}\n\nfunction draw() {\n background(255);\n image(gif, 0, 0);\n\n describe('A cartoon face winks once and then freezes. Clicking resets the face and makes it wink again.');\n}\n\nfunction mousePressed() {\n gif.reset();\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "getCurrentFrame", + "file": "src/image/p5.Image.js", + "line": 1215, + "itemtype": "method", + "description": "Gets the index of the current frame in an animated GIF.", + "example": [ + "
\n\nlet gif;\n\nfunction preload() {\n gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif');\n}\n\nfunction draw() {\n let index = gif.getCurrentFrame();\n image(gif, 0, 0);\n text(index, 10, 90);\n\n describe('A cartoon eye repeatedly looks around, then outwards. A number displayed in the bottom-left corner increases from 0 to 124, then repeats.');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "index of the GIF's current frame.", + "type": "Number" + } + } + ], + "return": { + "description": "index of the GIF's current frame.", + "type": "Number" + }, + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "setFrame", + "file": "src/image/p5.Image.js", + "line": 1253, + "itemtype": "method", + "description": "Sets the current frame in an animated GIF.", + "example": [ + "
\n\nlet gif;\nlet frameSlider;\n\nfunction preload() {\n gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif');\n}\n\nfunction setup() {\n let maxFrame = gif.numFrames() - 1;\n frameSlider = createSlider(0, maxFrame);\n frameSlider.position(10, 80);\n frameSlider.size(80);\n}\n\nfunction draw() {\n let index = frameSlider.value();\n gif.setFrame(index);\n image(gif, 0, 0);\n\n describe('A cartoon eye looks around when a slider is moved.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "index", + "description": "index of the frame to display.", + "type": "Number" + } + ] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "numFrames", + "file": "src/image/p5.Image.js", + "line": 1294, + "itemtype": "method", + "description": "Returns the number of frames in an animated GIF.", + "example": [ + "
\n\nlet gif;\n\nfunction preload() {\n gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif');\n}\n\nfunction draw() {\n image(gif, 0, 0);\n let total = gif.numFrames();\n let index = gif.getCurrentFrame();\n text(`${index} / ${total}`, 30, 90);\n\n describe('A cartoon eye looks around. The text \"n / 125\" is shown at the bottom of the canvas.');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "number of frames in the GIF.", + "type": "Number" + } + } + ], + "return": { + "description": "number of frames in the GIF.", + "type": "Number" + }, + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "play", + "file": "src/image/p5.Image.js", + "line": 1330, + "itemtype": "method", + "description": "Plays an animated GIF that was paused with\nimg.pause().", + "example": [ + "
\n\nlet gif;\n\nfunction preload() {\n gif = loadImage('assets/nancy-liang-wind-loop-forever.gif');\n}\n\nfunction draw() {\n background(255);\n image(gif, 0, 0);\n\n describe('A drawing of a child with hair blowing in the wind. The animation freezes when clicked and resumes when released.');\n}\n\nfunction mousePressed() {\n gif.pause();\n}\n\nfunction mouseReleased() {\n gif.play();\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "pause", + "file": "src/image/p5.Image.js", + "line": 1366, + "itemtype": "method", + "description": "Pauses an animated GIF. The GIF can be resumed by calling\nimg.play().", + "example": [ + "
\n\nlet gif;\n\nfunction preload() {\n gif = loadImage('assets/nancy-liang-wind-loop-forever.gif');\n}\n\nfunction draw() {\n background(255);\n image(gif, 0, 0);\n\n describe('A drawing of a child with hair blowing in the wind. The animation freezes when clicked and resumes when released.');\n}\n\nfunction mousePressed() {\n gif.pause();\n}\n\nfunction mouseReleased() {\n gif.play();\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "delay", + "file": "src/image/p5.Image.js", + "line": 1429, + "itemtype": "method", + "description": "

Changes the delay between frames in an animated GIF.

\n

The second parameter, index, is optional. If provided, only the frame\nat index will have its delay modified. All other frames will keep\ntheir default delay.

\n", + "example": [ + "
\n\nlet gifFast;\nlet gifSlow;\n\nfunction preload() {\n gifFast = loadImage('assets/arnott-wallace-eye-loop-forever.gif');\n gifSlow = loadImage('assets/arnott-wallace-eye-loop-forever.gif');\n}\n\nfunction setup() {\n gifFast.resize(50, 50);\n gifSlow.resize(50, 50);\n gifFast.delay(10);\n gifSlow.delay(100);\n}\n\nfunction draw() {\n image(gifFast, 0, 0);\n image(gifSlow, 50, 0);\n\n describe('Two animated eyes looking around. The eye on the left moves faster than the eye on the right.');\n}\n\n
\n\n
\n\nlet gif;\n\nfunction preload() {\n gif = loadImage('assets/arnott-wallace-eye-loop-forever.gif');\n}\n\nfunction setup() {\n gif.delay(3000, 67);\n}\n\nfunction draw() {\n image(gif, 0, 0);\n\n describe('An animated eye looking around. It pauses for three seconds while it looks down.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "d", + "description": "delay in milliseconds between switching frames.", + "type": "Number" + }, + { + "name": "index", + "description": "index of the frame that will have its delay modified.", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5.Image", + "static": false, + "module": "Image", + "submodule": "Image" + }, + { + "name": "getParent", + "file": "src/io/p5.XML.js", + "line": 94, + "itemtype": "method", + "description": "Gets a copy of the element's parent. Returns the parent as another\np5.XML object.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let children = xml.getChildren('animal');\n let parent = children[1].getParent();\n print(parent.getName());\n}\n\n// Sketch prints:\n// mammals\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "element parent", + "type": "p5.XML" + } + } + ], + "return": { + "description": "element parent", + "type": "p5.XML" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "getName", + "file": "src/io/p5.XML.js", + "line": 128, + "itemtype": "method", + "description": "Gets the element's full name, which is returned as a String.", + "example": [ + "<animal\n
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n print(xml.getName());\n}\n\n// Sketch prints:\n// mammals\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "the name of the node", + "type": "String" + } + } + ], + "return": { + "description": "the name of the node", + "type": "String" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "setName", + "file": "src/io/p5.XML.js", + "line": 165, + "itemtype": "method", + "description": "Sets the element's name, which is specified as a String.", + "example": [ + "<animal\n
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n print(xml.getName());\n xml.setName('fish');\n print(xml.getName());\n}\n\n// Sketch prints:\n// mammals\n// fish\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "the", + "description": "new name of the node", + "type": "String" + } + ] + } + ], + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "hasChildren", + "file": "src/io/p5.XML.js", + "line": 208, + "itemtype": "method", + "description": "Checks whether or not the element has any children, and returns the result\nas a boolean.", + "example": [ + "<animal\n
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n print(xml.hasChildren());\n}\n\n// Sketch prints:\n// true\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "", + "type": "boolean" + } + } + ], + "return": { + "description": "", + "type": "boolean" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "listChildren", + "file": "src/io/p5.XML.js", + "line": 244, + "itemtype": "method", + "description": "Get the names of all of the element's children, and returns the names as an\narray of Strings. This is the same as looping through and calling getName()\non each child element individually.", + "example": [ + "<animal\n
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n print(xml.listChildren());\n}\n\n// Sketch prints:\n// [\"animal\", \"animal\", \"animal\"]\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "names of the children of the element", + "type": "String[]" + } + } + ], + "return": { + "description": "names of the children of the element", + "type": "String[]" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "getChildren", + "file": "src/io/p5.XML.js", + "line": 291, + "itemtype": "method", + "description": "Returns all of the element's children as an array of p5.XML objects. When\nthe name parameter is specified, then it will return all children that match\nthat name.", + "example": [ + "<animal\n
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let animals = xml.getChildren('animal');\n\n for (let i = 0; i < animals.length; i++) {\n print(animals[i].getContent());\n }\n}\n\n// Sketch prints:\n// \"Goat\"\n// \"Leopard\"\n// \"Zebra\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "name", + "description": "element name", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "children of the element", + "type": "p5.XML[]" + } + } + ], + "return": { + "description": "children of the element", + "type": "p5.XML[]" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "getChild", + "file": "src/io/p5.XML.js", + "line": 350, + "itemtype": "method", + "description": "Returns the first of the element's children that matches the name parameter\nor the child of the given index.It returns undefined if no matching\nchild is found.", + "example": [ + "<animal\n
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let firstChild = xml.getChild('animal');\n print(firstChild.getContent());\n}\n\n// Sketch prints:\n// \"Goat\"\n
\n
\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let secondChild = xml.getChild(1);\n print(secondChild.getContent());\n}\n\n// Sketch prints:\n// \"Leopard\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "name", + "description": "element name or index", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "p5.XML" + } + } + ], + "return": { + "description": "", + "type": "p5.XML" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "addChild", + "file": "src/io/p5.XML.js", + "line": 403, + "itemtype": "method", + "description": "Appends a new child to the element. The child can be specified with\neither a String, which will be used as the new tag's name, or as a\nreference to an existing p5.XML object.\nA reference to the newly created child is returned as an p5.XML object.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let child = new p5.XML();\n child.setName('animal');\n child.setAttribute('id', '3');\n child.setAttribute('species', 'Ornithorhynchus anatinus');\n child.setContent('Platypus');\n xml.addChild(child);\n\n let animals = xml.getChildren('animal');\n print(animals[animals.length - 1].getContent());\n}\n\n// Sketch prints:\n// \"Goat\"\n// \"Leopard\"\n// \"Zebra\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "node", + "description": "a p5.XML Object which will be the child to be added", + "type": "p5.XML" + } + ] + } + ], + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "removeChild", + "file": "src/io/p5.XML.js", + "line": 465, + "itemtype": "method", + "description": "Removes the element specified by name or index.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n xml.removeChild('animal');\n let children = xml.getChildren();\n for (let i = 0; i < children.length; i++) {\n print(children[i].getContent());\n }\n}\n\n// Sketch prints:\n// \"Leopard\"\n// \"Zebra\"\n
\n
\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n xml.removeChild(1);\n let children = xml.getChildren();\n for (let i = 0; i < children.length; i++) {\n print(children[i].getContent());\n }\n}\n\n// Sketch prints:\n// \"Goat\"\n// \"Zebra\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "name", + "description": "element name or index", + "type": "String|Integer" + } + ] + } + ], + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "getAttributeCount", + "file": "src/io/p5.XML.js", + "line": 513, + "itemtype": "method", + "description": "Counts the specified element's number of attributes, returned as an Number.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let firstChild = xml.getChild('animal');\n print(firstChild.getAttributeCount());\n}\n\n// Sketch prints:\n// 2\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "", + "type": "Integer" + } + } + ], + "return": { + "description": "", + "type": "Integer" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "listAttributes", + "file": "src/io/p5.XML.js", + "line": 549, + "itemtype": "method", + "description": "Gets all of the specified element's attributes, and returns them as an\narray of Strings.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let firstChild = xml.getChild('animal');\n print(firstChild.listAttributes());\n}\n\n// Sketch prints:\n// [\"id\", \"species\"]\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "an array of strings containing the names of attributes", + "type": "String[]" + } + } + ], + "return": { + "description": "an array of strings containing the names of attributes", + "type": "String[]" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "hasAttribute", + "file": "src/io/p5.XML.js", + "line": 593, + "itemtype": "method", + "description": "Checks whether or not an element has the specified attribute.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let firstChild = xml.getChild('animal');\n print(firstChild.hasAttribute('species'));\n print(firstChild.hasAttribute('color'));\n}\n\n// Sketch prints:\n// true\n// false\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "the", + "description": "attribute to be checked", + "type": "String" + } + ], + "return": { + "description": "true if attribute found else false", + "type": "boolean" + } + } + ], + "return": { + "description": "true if attribute found else false", + "type": "boolean" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "getNum", + "file": "src/io/p5.XML.js", + "line": 639, + "itemtype": "method", + "description": "Returns an attribute value of the element as an Number. If the defaultValue\nparameter is specified and the attribute doesn't exist, then defaultValue\nis returned. If no defaultValue is specified and the attribute doesn't\nexist, the value 0 is returned.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let firstChild = xml.getChild('animal');\n print(firstChild.getNum('id'));\n}\n\n// Sketch prints:\n// 0\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "name", + "description": "the non-null full name of the attribute", + "type": "String" + }, + { + "name": "defaultValue", + "description": "the default value of the attribute", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "", + "type": "Number" + } + } + ], + "return": { + "description": "", + "type": "Number" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "getString", + "file": "src/io/p5.XML.js", + "line": 685, + "itemtype": "method", + "description": "Returns an attribute value of the element as an String. If the defaultValue\nparameter is specified and the attribute doesn't exist, then defaultValue\nis returned. If no defaultValue is specified and the attribute doesn't\nexist, null is returned.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let firstChild = xml.getChild('animal');\n print(firstChild.getString('species'));\n}\n\n// Sketch prints:\n// \"Capra hircus\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "name", + "description": "the non-null full name of the attribute", + "type": "String" + }, + { + "name": "defaultValue", + "description": "the default value of the attribute", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "", + "type": "String" + } + } + ], + "return": { + "description": "", + "type": "String" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "setAttribute", + "file": "src/io/p5.XML.js", + "line": 731, + "itemtype": "method", + "description": "Sets the content of an element's attribute. The first parameter specifies\nthe attribute name, while the second specifies the new content.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let firstChild = xml.getChild('animal');\n print(firstChild.getString('species'));\n firstChild.setAttribute('species', 'Jamides zebra');\n print(firstChild.getString('species'));\n}\n\n// Sketch prints:\n// \"Capra hircus\"\n// \"Jamides zebra\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "name", + "description": "the full name of the attribute", + "type": "String" + }, + { + "name": "value", + "description": "the value of the attribute", + "type": "Number|String|Boolean" + } + ] + } + ], + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "getContent", + "file": "src/io/p5.XML.js", + "line": 768, + "itemtype": "method", + "description": "Returns the content of an element. If there is no such content,\ndefaultValue is returned if specified, otherwise null is returned.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let firstChild = xml.getChild('animal');\n print(firstChild.getContent());\n}\n\n// Sketch prints:\n// \"Goat\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "defaultValue", + "description": "value returned if no content is found", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "String" + } + } + ], + "return": { + "description": "", + "type": "String" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "setContent", + "file": "src/io/p5.XML.js", + "line": 809, + "itemtype": "method", + "description": "Sets the element's content.", + "example": [ + "
\n// The following short XML file called \"mammals.xml\" is parsed\n// in the code below.\n//\n// \n// <mammals>\n// <animal id=\"0\" species=\"Capra hircus\">Goat</animal>\n// <animal id=\"1\" species=\"Panthera pardus\">Leopard</animal>\n// <animal id=\"2\" species=\"Equus zebra\">Zebra</animal>\n// </mammals>\n\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n let firstChild = xml.getChild('animal');\n print(firstChild.getContent());\n firstChild.setContent('Mountain Goat');\n print(firstChild.getContent());\n}\n\n// Sketch prints:\n// \"Goat\"\n// \"Mountain Goat\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "text", + "description": "the new content", + "type": "String" + } + ] + } + ], + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "serialize", + "file": "src/io/p5.XML.js", + "line": 840, + "itemtype": "method", + "description": "Serializes the element into a string. This function is useful for preparing\nthe content to be sent over a http request or saved to file.", + "example": [ + "
\nlet xml;\n\nfunction preload() {\n xml = loadXML('assets/mammals.xml');\n}\n\nfunction setup() {\n print(xml.serialize());\n}\n\n// Sketch prints:\n// \n// Goat\n// Leopard\n// Zebra\n// \n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "Serialized string of the element", + "type": "String" + } + } + ], + "return": { + "description": "Serialized string of the element", + "type": "String" + }, + "class": "p5.XML", + "static": false, + "module": "IO", + "submodule": "Input" + }, + { + "name": "toString", + "file": "src/math/p5.Vector.js", + "line": 132, + "itemtype": "method", + "description": "Returns a string representation of a vector. This method is useful for\nprinting vectors to the console while debugging.", + "example": [ + "
\n\nfunction setup() {\n let v = createVector(20, 30);\n // Prints 'p5.Vector Object : [20, 30, 0]'.\n print(v.toString());\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "string representation of the vector.", + "type": "String" + } + } + ], + "return": { + "description": "string representation of the vector.", + "type": "String" + }, + "class": "p5.Vector", + "static": false, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "set", + "file": "src/math/p5.Vector.js", + "line": 176, + "itemtype": "method", + "chainable": 1, + "description": "Sets the x, y, and z components of the vector using separate numbers,\na p5.Vector object, or an array of numbers.\nCalling set() with no arguments sets the vector's components to 0.", + "example": [ + "
\n\nstrokeWeight(5);\n\n// Top left.\nlet pos = createVector(25, 25);\npoint(pos);\n\n// Top right.\npos.set(75, 25);\npoint(pos);\n\n// Bottom right.\nlet p2 = createVector(75, 75);\npos.set(p2);\npoint(pos);\n\n// Bottom left.\nlet arr = [25, 75];\npos.set(arr);\npoint(pos);\n\ndescribe('Four black dots arranged in a square on a gray background.');\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x component of the vector.", + "optional": 1, + "type": "Number" + }, + { + "name": "y", + "description": "y component of the vector.", + "optional": 1, + "type": "Number" + }, + { + "name": "z", + "description": "z component of the vector.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "vector to set.", + "type": "p5.Vector|Number[]" + } + ] + } + ], + "class": "p5.Vector", + "static": false, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "copy", + "file": "src/math/p5.Vector.js", + "line": 2491, + "itemtype": "method", + "description": "Returns a copy of the p5.Vector object.", + "example": [ + "
\n\nlet pos = createVector(50, 50);\nlet pc = pos.copy();\n\nstrokeWeight(5);\npoint(pc);\n\ndescribe('A black point drawn in the middle of a gray square.');\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "copy of the p5.Vector object.", + "type": "p5.Vector" + } + }, + { + "params": [ + { + "name": "v", + "description": "the p5.Vector to create a copy of", + "type": "p5.Vector" + } + ], + "return": { + "description": "the copy of the p5.Vector object", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "copy of the p5.Vector object.", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "add", + "file": "src/math/p5.Vector.js", + "line": 2504, + "itemtype": "method", + "chainable": 1, + "description": "

Adds to a vector's x, y, and z components using separate numbers,\nanother p5.Vector object, or an array of numbers.\nCalling add() with no arguments has no effect.

\n

The static version of add(), as in p5.Vector.add(v2, v1), returns a new\np5.Vector object and doesn't change the\noriginals.

\n", + "example": [ + "
\n\nstrokeWeight(5);\n\n// Top left.\nlet pos = createVector(25, 25);\npoint(pos);\n\n// Top right.\npos.add(50, 0);\npoint(pos);\n\n// Bottom right.\nlet p2 = createVector(0, 50);\npos.add(p2);\npoint(pos);\n\n// Bottom left.\nlet arr = [-50, 0];\npos.add(arr);\npoint(pos);\n\ndescribe('Four black dots arranged in a square on a gray background.');\n\n
\n\n
\n\n// Top left.\nlet p1 = createVector(25, 25);\n\n// Center.\nlet p2 = createVector(50, 50);\n\n// Bottom right.\nlet p3 = p5.Vector.add(p1, p2);\n\nstrokeWeight(5);\npoint(p1);\npoint(p2);\npoint(p3);\n\ndescribe('Three black dots in a diagonal line from top left to bottom right.');\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let origin = createVector(0, 0);\n let v1 = createVector(50, 50);\n drawArrow(origin, v1, 'red');\n\n let v2 = createVector(-30, 20);\n drawArrow(v1, v2, 'blue');\n\n let v3 = p5.Vector.add(v1, v2);\n drawArrow(origin, v3, 'purple');\n\n describe('Three arrows drawn on a gray square. A red arrow extends from the top left corner to the center. A blue arrow extends from the tip of the red arrow. A purple arrow extends from the origin to the tip of the blue arrow.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x component of the vector to be added.", + "type": "Number" + }, + { + "name": "y", + "description": "y component of the vector to be added.", + "optional": 1, + "type": "Number" + }, + { + "name": "z", + "description": "z component of the vector to be added.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "The vector to add", + "type": "p5.Vector|Number[]" + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "A p5.Vector to add", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "A p5.Vector to add", + "type": "p5.Vector" + }, + { + "name": "target", + "description": "vector to receive the result.", + "optional": 1, + "type": "p5.Vector" + } + ], + "return": { + "description": "resulting p5.Vector.", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "resulting p5.Vector.", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "rem", + "file": "src/math/p5.Vector.js", + "line": 2532, + "itemtype": "method", + "chainable": 1, + "description": "

Performs modulo (remainder) division with a vector's x, y, and z\ncomponents using separate numbers, another\np5.Vector object, or an array of numbers.

\n

The static version of rem() as in p5.Vector.rem(v2, v1), returns a new\np5.Vector object and doesn't change the\noriginals.

\n", + "example": [ + "
\n\nlet v = createVector(3, 4, 5);\nv.rem(2, 3, 4);\n// Prints 'p5.Vector Object : [1, 1, 1]'.\nprint(v.toString());\n\n
\n\n
\n\nlet v1 = createVector(3, 4, 5);\nlet v2 = createVector(2, 3, 4);\nv1.rem(v2);\n\n// Prints 'p5.Vector Object : [1, 1, 1]'.\nprint(v1.toString());\n\n
\n\n
\n\nlet v = createVector(3, 4, 5);\nlet arr = [2, 3, 4];\nv.rem(arr);\n\n// Prints 'p5.Vector Object : [1, 1, 1]'.\nprint(v.toString());\n\n
\n\n
\n\nlet v1 = createVector(3, 4, 5);\nlet v2 = createVector(2, 3, 4);\nlet v3 = p5.Vector.rem(v1, v2);\n\n// Prints 'p5.Vector Object : [1, 1, 1]'.\nprint(v3.toString());\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x component of divisor vector.", + "type": "Number" + }, + { + "name": "y", + "description": "y component of divisor vector.", + "type": "Number" + }, + { + "name": "z", + "description": "z component of divisor vector.", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "divisor vector.", + "type": "p5.Vector|Number[]" + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "The dividend p5.Vector", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "The divisor p5.Vector", + "type": "p5.Vector" + } + ] + }, + { + "params": [ + { + "name": "v1", + "type": "p5.Vector" + }, + { + "name": "v2", + "type": "p5.Vector" + } + ], + "return": { + "description": "The resulting p5.Vector", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "The resulting p5.Vector", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "sub", + "file": "src/math/p5.Vector.js", + "line": 2552, + "itemtype": "method", + "chainable": 1, + "description": "

Subtracts from a vector's x, y, and z components using separate\nnumbers, another p5.Vector object, or an array of\nnumbers. Calling sub() with no arguments has no effect.

\n

The static version of sub(), as in p5.Vector.sub(v2, v1), returns a new\np5.Vector object and doesn't change the\noriginals.

\n", + "example": [ + "
\n\nstrokeWeight(5);\n\n// Bottom right.\nlet pos = createVector(75, 75);\npoint(pos);\n\n// Top right.\npos.sub(0, 50);\npoint(pos);\n\n// Top left.\nlet p2 = createVector(50, 0);\npos.sub(p2);\npoint(pos);\n\n// Bottom left.\nlet arr = [0, -50];\npos.sub(arr);\npoint(pos);\n\ndescribe('Four black dots arranged in a square on a gray background.');\n\n
\n\n
\n\n// Bottom right.\nlet p1 = createVector(75, 75);\n\n// Center.\nlet p2 = createVector(50, 50);\n\n// Top left.\nlet p3 = p5.Vector.sub(p1, p2);\n\nstrokeWeight(5);\npoint(p1);\npoint(p2);\npoint(p3);\n\ndescribe('Three black dots in a diagonal line from top left to bottom right.');\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let origin = createVector(0, 0);\n let v1 = createVector(50, 50);\n drawArrow(origin, v1, 'red');\n\n let v2 = createVector(20, 70);\n drawArrow(origin, v2, 'blue');\n\n let v3 = p5.Vector.sub(v2, v1);\n drawArrow(v1, v3, 'purple');\n\n describe('Three arrows drawn on a gray square. A red and a blue arrow extend from the top left. A purple arrow extends from the tip of the red arrow to the tip of the blue arrow.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x component of the vector to subtract.", + "type": "Number" + }, + { + "name": "y", + "description": "y component of the vector to subtract.", + "optional": 1, + "type": "Number" + }, + { + "name": "z", + "description": "z component of the vector to subtract.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "the vector to subtract", + "type": "p5.Vector|Number[]" + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "A p5.Vector to subtract from", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "A p5.Vector to subtract", + "type": "p5.Vector" + }, + { + "name": "target", + "description": "vector to receive the result.", + "optional": 1, + "type": "p5.Vector" + } + ], + "return": { + "description": "The resulting p5.Vector", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "The resulting p5.Vector", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "mult", + "file": "src/math/p5.Vector.js", + "line": 2600, + "itemtype": "method", + "chainable": 1, + "description": "

Multiplies a vector's x, y, and z components by the same number,\nseparate numbers, the components of another\np5.Vector object, or an array of numbers. Calling\nmult() with no arguments has no effect.

\n

The static version of mult(), as in p5.Vector.mult(v, 2), returns a new\np5.Vector object and doesn't change the\noriginals.

\n", + "example": [ + "
\n\nstrokeWeight(5);\n\nlet p = createVector(25, 25);\npoint(p);\n\np.mult(2);\npoint(p);\n\ndescribe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the center.');\n\n
\n\n
\n\nstrokeWeight(5);\n\nlet p = createVector(25, 25);\npoint(p);\n\np.mult(2, 3);\npoint(p);\n\ndescribe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');\n\n
\n\n
\n\nstrokeWeight(5);\n\nlet p = createVector(25, 25);\npoint(p);\n\nlet arr = [2, 3];\np.mult(arr);\npoint(p);\n\ndescribe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');\n\n
\n\n
\n\nstrokeWeight(5);\n\nlet p = createVector(25, 25);\npoint(p);\n\nlet p2 = createVector(2, 3);\np.mult(p2);\npoint(p);\n\ndescribe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');\n\n
\n\n
\n\nstrokeWeight(5);\n\nlet p = createVector(25, 25);\npoint(p);\n\nlet p2 = createVector(2, 3);\nlet p3 = p5.Vector.mult(p, p2);\npoint(p3);\n\ndescribe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let origin = createVector(0, 0);\n let v1 = createVector(25, 25);\n drawArrow(origin, v1, 'red');\n\n let v2 = p5.Vector.mult(v1, 2);\n drawArrow(origin, v2, 'blue');\n\n describe('Two arrows extending from the top left corner. The blue arrow is twice the length of the red arrow.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "The number to multiply with the vector", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "x", + "description": "number to multiply with the x component of the vector.", + "type": "Number" + }, + { + "name": "y", + "description": "number to multiply with the y component of the vector.", + "type": "Number" + }, + { + "name": "z", + "description": "number to multiply with the z component of the vector.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "arr", + "description": "array to multiply with the components of the vector.", + "type": "Number[]" + } + ] + }, + { + "params": [ + { + "name": "v", + "description": "vector to multiply with the components of the original vector.", + "type": "p5.Vector" + } + ] + }, + { + "params": [] + }, + { + "params": [ + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "z", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "resulting new p5.Vector.", + "type": "p5.Vector" + } + }, + { + "params": [ + { + "name": "v", + "type": "p5.Vector" + }, + { + "name": "n", + "type": "Number" + }, + { + "name": "target", + "description": "vector to receive the result.", + "optional": 1, + "type": "p5.Vector" + } + ] + }, + { + "params": [ + { + "name": "v0", + "type": "p5.Vector" + }, + { + "name": "v1", + "type": "p5.Vector" + }, + { + "name": "target", + "optional": 1, + "type": "p5.Vector" + } + ] + }, + { + "params": [ + { + "name": "v0", + "type": "p5.Vector" + }, + { + "name": "arr", + "type": "Number[]" + }, + { + "name": "target", + "optional": 1, + "type": "p5.Vector" + } + ] + } + ], + "return": { + "description": "resulting new p5.Vector.", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "div", + "file": "src/math/p5.Vector.js", + "line": 2674, + "itemtype": "method", + "chainable": 1, + "description": "

Divides a vector's x, y, and z components by the same number,\nseparate numbers, the components of another\np5.Vector object, or an array of numbers. Calling\ndiv() with no arguments has no effect.

\n

The static version of div(), as in p5.Vector.div(v, 2), returns a new\np5.Vector object and doesn't change the\noriginals.

\n", + "example": [ + "
\n\nstrokeWeight(5);\n\nlet p = createVector(50, 50);\npoint(p);\n\np.div(2);\npoint(p);\n\ndescribe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the center.');\n\n
\n\n
\n\nstrokeWeight(5);\n\nlet p = createVector(50, 75);\npoint(p);\n\np.div(2, 3);\npoint(p);\n\ndescribe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');\n\n
\n\n
\n\nstrokeWeight(5);\n\nlet p = createVector(50, 75);\npoint(p);\n\nlet arr = [2, 3];\np.div(arr);\npoint(p);\n\ndescribe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');\n\n
\n\n
\n\nstrokeWeight(5);\n\nlet p = createVector(50, 75);\npoint(p);\n\nlet p2 = createVector(2, 3);\np.div(p2);\npoint(p);\n\ndescribe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');\n\n
\n\n
\n\nstrokeWeight(5);\n\nlet p = createVector(50, 75);\npoint(p);\n\nlet p2 = createVector(2, 3);\nlet p3 = p5.Vector.div(p, p2);\npoint(p3);\n\ndescribe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let origin = createVector(0, 0);\n let v1 = createVector(50, 50);\n drawArrow(origin, v1, 'red');\n\n let v2 = p5.Vector.div(v1, 2);\n drawArrow(origin, v2, 'blue');\n\n describe('Two arrows extending from the top left corner. The blue arrow is half the length of the red arrow.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "The number to divide the vector by", + "type": "number" + } + ] + }, + { + "params": [ + { + "name": "x", + "description": "number to divide with the x component of the vector.", + "type": "Number" + }, + { + "name": "y", + "description": "number to divide with the y component of the vector.", + "type": "Number" + }, + { + "name": "z", + "description": "number to divide with the z component of the vector.", + "optional": 1, + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "arr", + "description": "array to divide the components of the vector by.", + "type": "Number[]" + } + ] + }, + { + "params": [ + { + "name": "v", + "description": "vector to divide the components of the original vector by.", + "type": "p5.Vector" + } + ] + }, + { + "params": [] + }, + { + "params": [ + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + }, + { + "name": "z", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "The resulting new p5.Vector", + "type": "p5.Vector" + } + }, + { + "params": [ + { + "name": "v", + "type": "p5.Vector" + }, + { + "name": "n", + "type": "Number" + }, + { + "name": "target", + "description": "The vector to receive the result", + "optional": 1, + "type": "p5.Vector" + } + ] + }, + { + "params": [ + { + "name": "v0", + "type": "p5.Vector" + }, + { + "name": "v1", + "type": "p5.Vector" + }, + { + "name": "target", + "optional": 1, + "type": "p5.Vector" + } + ] + }, + { + "params": [ + { + "name": "v0", + "type": "p5.Vector" + }, + { + "name": "arr", + "type": "Number[]" + }, + { + "name": "target", + "optional": 1, + "type": "p5.Vector" + } + ] + } + ], + "return": { + "description": "The resulting new p5.Vector", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "mag", + "file": "src/math/p5.Vector.js", + "line": 2798, + "itemtype": "method", + "description": "Returns the magnitude (length) of the vector.", + "example": [ + "
\n\nlet p = createVector(30, 40);\nline(0, 0, p.x, p.y);\n\nlet m = p.mag();\ntext(m, p.x, p.y);\n\ndescribe('A diagonal black line extends from the top left corner of a gray square. The number 50 is written at the end of the line.');\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "magnitude of the vector.", + "type": "Number" + } + }, + { + "params": [] + }, + { + "params": [ + { + "name": "vecT", + "description": "The vector to return the magnitude of", + "type": "p5.Vector" + } + ], + "return": { + "description": "The magnitude of vecT", + "type": "Number" + } + } + ], + "return": { + "description": "magnitude of the vector.", + "type": "Number" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "magSq", + "file": "src/math/p5.Vector.js", + "line": 2813, + "itemtype": "method", + "description": "Returns the magnitude (length) of the vector squared.", + "example": [ + "
\n\nlet p = createVector(30, 40);\nline(0, 0, p.x, p.y);\n\nlet m = p.magSq();\ntext(m, p.x, p.y);\n\ndescribe('A diagonal black line extends from the top left corner of a gray square. The number 2500 is written at the end of the line.');\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "squared magnitude of the vector.", + "type": "number" + } + }, + { + "params": [] + }, + { + "params": [ + { + "name": "vecT", + "description": "the vector to return the squared magnitude of", + "type": "p5.Vector" + } + ], + "return": { + "description": "the squared magnitude of vecT", + "type": "Number" + } + } + ], + "return": { + "description": "squared magnitude of the vector.", + "type": "number" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "dot", + "file": "src/math/p5.Vector.js", + "line": 2700, + "itemtype": "method", + "description": "

Returns the dot product of two vectors. The dot product is a number that\ndescribes the overlap between two vectors. Visually, the dot product can be\nthought of as the \"shadow\" one vector casts on another. The dot product's\nmagnitude is largest when two vectors point in the same or opposite\ndirections. Its magnitude is 0 when two vectors form a right angle.

\n

The version of dot() with one parameter interprets it as another\np5.Vector object.

\n

The version of dot() with multiple parameters interprets them as the\nx, y, and z components of another vector.

\n

The static version of dot(), as in p5.Vector.dot(v1, v2), is the same\nas calling v1.dot(v2).

\n", + "example": [ + "
\n\nlet v1 = createVector(3, 4);\nlet v2 = createVector(3, 0);\nlet dp = v1.dot(v2);\n// Prints \"9\" to the console.\nprint(dp);\n\n
\n\n
\n\nlet v1 = createVector(1, 0);\nlet v2 = createVector(0, 1);\nlet dp = p5.Vector.dot(v1, v2);\n// Prints \"0\" to the console.\nprint(dp);\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let v0 = createVector(width / 2, height / 2);\n let v1 = createVector(30, 0);\n drawArrow(v0, v1, 'black');\n\n let v2 = createVector(mouseX - width / 2, mouseY - height / 2);\n drawArrow(v0, v2, 'red');\n\n let dp = v2.dot(v1);\n text(`v2 • v1 = ${dp}`, 15, 20);\n\n describe('Two arrows drawn on a gray square. A black arrow points to the right and a red arrow follows the mouse. The text \"v1 • v2 = something\" changes as the mouse moves.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x component of the vector.", + "type": "Number" + }, + { + "name": "y", + "description": "y component of the vector.", + "optional": 1, + "type": "Number" + }, + { + "name": "z", + "description": "z component of the vector.", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "dot product.", + "type": "Number" + } + }, + { + "params": [ + { + "name": "v", + "description": "p5.Vector to be dotted.", + "type": "p5.Vector" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v1", + "description": "first p5.Vector.", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "second p5.Vector.", + "type": "p5.Vector" + } + ], + "return": { + "description": "dot product.", + "type": "Number" + } + } + ], + "return": { + "description": "dot product.", + "type": "Number" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "cross", + "file": "src/math/p5.Vector.js", + "line": 2713, + "itemtype": "method", + "description": "

Returns the cross product of two vectors. The cross product is a vector\nthat points straight out of the plane created by two vectors. The cross\nproduct's magnitude is the area of the parallelogram formed by the original\ntwo vectors.

\n

The static version of cross(), as in p5.Vector.cross(v1, v2), is the same\nas calling v1.cross(v2).

\n", + "example": [ + "
\n\nlet v1 = createVector(1, 0);\nlet v2 = createVector(3, 4);\nlet cp = v1.cross(v2);\n// Prints \"p5.Vector Object : [0, 0, 4]\" to the console.\nprint(cp.toString());\n\n
\n\n
\n\nlet v1 = createVector(1, 0);\nlet v2 = createVector(3, 4);\nlet cp = p5.Vector.cross(v1, v2);\n// Prints \"p5.Vector Object : [0, 0, 4]\" to the console.\nprint(cp.toString());\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v", + "description": "p5.Vector to be crossed.", + "type": "p5.Vector" + } + ], + "return": { + "description": "cross product as a p5.Vector.", + "type": "p5.Vector" + } + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v1", + "description": "first p5.Vector.", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "second p5.Vector.", + "type": "p5.Vector" + } + ], + "return": { + "description": "cross product.", + "type": "Number" + } + } + ], + "return": { + "description": "cross product as a p5.Vector.", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "dist", + "file": "src/math/p5.Vector.js", + "line": 2727, + "itemtype": "method", + "description": "

Returns the distance between two points represented by vectors. A point's\ncoordinates can be thought of as a vector's components.

\n

The static version of dist(), as in p5.Vector.dist(v1, v2), is the same\nas calling v1.dist(v2).

\n

Use dist() to calculate the distance between points\nusing coordinates as in dist(x1, y1, x2, y2).

\n", + "example": [ + "
\n\nlet v1 = createVector(1, 0);\nlet v2 = createVector(0, 1);\nlet d = v1.dist(v2);\n// Prints \"1.414...\" to the console.\nprint(d);\n\n
\n\n
\n\nlet v1 = createVector(1, 0);\nlet v2 = createVector(0, 1);\nlet d = p5.Vector.dist(v1, v2);\n// Prints \"1.414...\" to the console.\nprint(d);\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let origin = createVector(0, 0);\n let v1 = createVector(50, 50);\n drawArrow(origin, v1, 'red');\n\n let v2 = createVector(20, 70);\n drawArrow(origin, v2, 'blue');\n\n let v3 = p5.Vector.sub(v2, v1);\n drawArrow(v1, v3, 'purple');\n\n let m = floor(v3.mag());\n text(m, 50, 75);\n\n describe('Three arrows drawn on a gray square. A red and a blue arrow extend from the top left. A purple arrow extends from the tip of the red arrow to the tip of the blue arrow. The number 36 is written in black near the purple arrow.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v", + "description": "x, y, and z coordinates of a p5.Vector.", + "type": "p5.Vector" + } + ], + "return": { + "description": "distance.", + "type": "Number" + } + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v1", + "description": "The first p5.Vector", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "The second p5.Vector", + "type": "p5.Vector" + } + ], + "return": { + "description": "The distance", + "type": "Number" + } + } + ], + "return": { + "description": "distance.", + "type": "Number" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "normalize", + "file": "src/math/p5.Vector.js", + "line": 2826, + "itemtype": "method", + "description": "

Scales the components of a p5.Vector object so\nthat its magnitude is 1.

\n

The static version of normalize(), as in p5.Vector.normalize(v),\nreturns a new p5.Vector object and doesn't change\nthe original.

\n", + "example": [ + "
\n\nlet v = createVector(10, 20, 2);\nv.normalize();\n// Prints \"p5.Vector Object : [0.445..., 0.890..., 0.089...]\" to the console.\nprint(v.toString());\n\n
\n\n
\n\nlet v0 = createVector(10, 20, 2);\nlet v1 = p5.Vector.normalize(v0);\n// Prints \"p5.Vector Object : [10, 20, 2]\" to the console.\nprint(v0.toString());\n// Prints \"p5.Vector Object : [0.445..., 0.890..., 0.089...]\" to the console.\nprint(v1.toString());\n\n
\n\n
\n\nfunction draw() {\n background(240);\n\n let v0 = createVector(50, 50);\n let v1 = createVector(mouseX - 50, mouseY - 50);\n\n let r = 25;\n drawArrow(v0, v1, 'red');\n v1.normalize();\n drawArrow(v0, v1.mult(r), 'blue');\n\n noFill();\n circle(50, 50, r * 2);\n\n describe(\"A red and blue arrow extend from the center of a circle. Both arrows follow the mouse, but the blue arrow's length is fixed to the circle's radius.\");\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "normalized p5.Vector.", + "type": "p5.Vector" + } + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v", + "description": "The vector to normalize", + "type": "p5.Vector" + }, + { + "name": "target", + "description": "The vector to receive the result", + "optional": 1, + "type": "p5.Vector" + } + ], + "return": { + "description": "The vector v, normalized to a length of 1", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "normalized p5.Vector.", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "limit", + "file": "src/math/p5.Vector.js", + "line": 2852, + "itemtype": "method", + "chainable": 1, + "description": "

Limits a vector's magnitude to a maximum value.

\n

The static version of limit(), as in p5.Vector.limit(v, 5), returns a\nnew p5.Vector object and doesn't change the\noriginal.

\n", + "example": [ + "
\n\nlet v = createVector(10, 20, 2);\nv.limit(5);\n// Prints \"p5.Vector Object : [2.227..., 4.454..., 0.445...]\" to the console.\nprint(v.toString());\n\n
\n\n
\n\nlet v0 = createVector(10, 20, 2);\nlet v1 = p5.Vector.limit(v0, 5);\n// Prints \"p5.Vector Object : [2.227..., 4.454..., 0.445...]\" to the console.\nprint(v1.toString());\n\n
\n\n
\n\nfunction draw() {\n background(240);\n\n let v0 = createVector(50, 50);\n let v1 = createVector(mouseX - 50, mouseY - 50);\n\n let r = 25;\n drawArrow(v0, v1, 'red');\n drawArrow(v0, v1.limit(r), 'blue');\n\n noFill();\n circle(50, 50, r * 2);\n\n describe(\"A red and blue arrow extend from the center of a circle. Both arrows follow the mouse, but the blue arrow never crosses the circle's edge.\");\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "max", + "description": "maximum magnitude for the vector.", + "type": "Number" + } + ] + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v", + "description": "the vector to limit", + "type": "p5.Vector" + }, + { + "name": "max", + "type": "Number" + }, + { + "name": "target", + "description": "the vector to receive the result (Optional)", + "optional": 1, + "type": "p5.Vector" + } + ], + "return": { + "description": "v with a magnitude limited to max", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "v with a magnitude limited to max", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "setMag", + "file": "src/math/p5.Vector.js", + "line": 2878, + "itemtype": "method", + "chainable": 1, + "description": "

Sets a vector's magnitude to a given value.

\n

The static version of setMag(), as in p5.Vector.setMag(v, 10), returns\na new p5.Vector object and doesn't change the\noriginal.

\n", + "example": [ + "
\n\nlet v = createVector(3, 4, 0);\n// Prints \"5\" to the console.\nprint(v.mag());\n\nv.setMag(10);\n// Prints \"p5.Vector Object : [6, 8, 0]\" to the console.\nprint(v.toString());\n\n
\n\n
\n\nlet v0 = createVector(3, 4, 0);\nlet v1 = p5.Vector.setMag(v0, 10);\n// Prints \"5\" to the console.\nprint(v0.mag());\n// Prints \"p5.Vector Object : [6, 8, 0]\" to the console.\nprint(v1.toString());\n\n
\n\n
\n\nfunction draw() {\n background(240);\n\n let origin = createVector(0, 0);\n let v = createVector(50, 50);\n\n drawArrow(origin, v, 'red');\n\n v.setMag(30);\n drawArrow(origin, v, 'blue');\n\n describe('Two arrows extend from the top left corner of a square toward its center. The red arrow reaches the center and the blue arrow only extends part of the way.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "len", + "description": "new length for this vector.", + "type": "number" + } + ] + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v", + "description": "the vector to set the magnitude of", + "type": "p5.Vector" + }, + { + "name": "len", + "type": "number" + }, + { + "name": "target", + "description": "the vector to receive the result (Optional)", + "optional": 1, + "type": "p5.Vector" + } + ], + "return": { + "description": "v with a magnitude set to len", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "v with a magnitude set to len", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "heading", + "file": "src/math/p5.Vector.js", + "line": 2904, + "itemtype": "method", + "description": "

Calculates the angle a 2D vector makes with the positive x-axis. Angles\nincrease in the clockwise direction.

\n

If the vector was created with\ncreateVector(), heading() returns angles\nin the units of the current angleMode().

\n

The static version of heading(), as in p5.Vector.heading(v), works the\nsame way.

\n", + "example": [ + "
\n\nlet v = createVector(1, 1);\n// Prints \"0.785...\" to the console.\nprint(v.heading());\n\nangleMode(DEGREES);\n// Prints \"45\" to the console.\nprint(v.heading());\n\n
\n\n
\n\nlet v = createVector(1, 1);\n// Prints \"0.785...\" to the console.\nprint(p5.Vector.heading(v));\n\nangleMode(DEGREES);\n// Prints \"45\" to the console.\nprint(p5.Vector.heading(v));\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let origin = createVector(0, 0);\n let v = createVector(50, 50);\n\n drawArrow(origin, v, 'black');\n\n angleMode(RADIANS);\n let h = round(v.heading(), 2);\n text(`Radians: ${h}`, 20, 70);\n angleMode(DEGREES);\n h = v.heading();\n text(`Degrees: ${h}`, 20, 85);\n\n describe('A black arrow extends from the top left of a square to its center. The text \"Radians: 0.79\" and \"Degrees: 45\" is written near the tip of the arrow.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "angle of rotation.", + "type": "Number" + } + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v", + "description": "the vector to find the angle of", + "type": "p5.Vector" + } + ], + "return": { + "description": "the angle of rotation", + "type": "Number" + } + } + ], + "return": { + "description": "angle of rotation.", + "type": "Number" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "setHeading", + "file": "src/math/p5.Vector.js", + "line": 1633, + "itemtype": "method", + "chainable": 1, + "description": "

Rotates a 2D vector to a specific angle without changing its magnitude.\nBy convention, the positive x-axis has an angle of 0. Angles increase in\nthe clockwise direction.

\n

If the vector was created with\ncreateVector(), setHeading() uses\nthe units of the current angleMode().

\n", + "example": [ + "
\n\nlet v = createVector(0, 1);\n// Prints \"1.570...\" to the console.\nprint(v.heading());\n\nv.setHeading(PI);\n// Prints \"3.141...\" to the console.\nprint(v.heading());\n\n
\n\n
\n\nangleMode(DEGREES);\nlet v = createVector(0, 1);\n// Prints \"90\" to the console.\nprint(v.heading());\n\nv.setHeading(180);\n// Prints \"180\" to the console.\nprint(v.heading());\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let v0 = createVector(50, 50);\n let v1 = createVector(30, 0);\n\n drawArrow(v0, v1, 'red');\n\n v1.setHeading(HALF_PI);\n drawArrow(v0, v1, 'blue');\n\n describe('Two arrows extend from the center of a gray square. The red arrow points to the right and the blue arrow points down.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "angle of rotation.", + "type": "number" + } + ] + } + ], + "class": "p5.Vector", + "static": false, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "rotate", + "file": "src/math/p5.Vector.js", + "line": 2626, + "itemtype": "method", + "chainable": 1, + "description": "

Rotates a 2D vector by an angle without changing its magnitude.\nBy convention, the positive x-axis has an angle of 0. Angles increase in\nthe clockwise direction.

\n

If the vector was created with\ncreateVector(), rotate() uses\nthe units of the current angleMode().

\n

The static version of rotate(), as in p5.Vector.rotate(v, PI),\nreturns a new p5.Vector object and doesn't change\nthe original.

\n", + "example": [ + "
\n\nlet v = createVector(1, 0);\n// Prints \"p5.Vector Object : [1, 0, 0]\" to the console.\nprint(v.toString());\nv.rotate(HALF_PI);\n// Prints \"p5.Vector Object : [0, 1, 0]\" to the console.\nprint(v.toString());\n\n
\n\n
\n\nangleMode(DEGREES);\nlet v = createVector(1, 0);\n// Prints \"p5.Vector Object : [1, 0, 0]\" to the console.\nprint(v.toString());\nv.rotate(90);\n// Prints \"p5.Vector Object : [0, 1, 0]\" to the console.\nprint(v.toString());\n\n
\n\n
\n\nlet v0 = createVector(1, 0);\nlet v1 = p5.Vector.rotate(v0, HALF_PI);\n// Prints \"p5.Vector Object : [1, 0, 0]\" to the console.\nprint(v0.toString());\n// Prints \"p5.Vector Object : [0, 1, 0]\" to the console.\nprint(v1.toString());\n\n
\n\n
\n\nangleMode(DEGREES);\nlet v0 = createVector(1, 0);\nlet v1 = p5.Vector.rotate(v0, 90);\n// Prints \"p5.Vector Object : [1, 0, 0]\" to the console.\nprint(v0.toString());\n// Prints \"p5.Vector Object : [0, 1, 0]\" to the console.\nprint(v1.toString());\n\n
\n\n
\n\nlet v0;\nlet v1;\n\nfunction setup() {\n v0 = createVector(50, 50);\n v1 = createVector(30, 0);\n}\n\nfunction draw() {\n background(240);\n\n v1.rotate(0.01);\n\n drawArrow(v0, v1, 'black');\n\n describe('A black arrow extends from the center of a gray square. The arrow rotates counterclockwise.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "angle of rotation.", + "type": "number" + } + ] + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v", + "type": "p5.Vector" + }, + { + "name": "angle", + "type": "Number" + }, + { + "name": "target", + "description": "The vector to receive the result", + "optional": 1, + "type": "p5.Vector" + } + ] + } + ], + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "angleBetween", + "file": "src/math/p5.Vector.js", + "line": 2919, + "itemtype": "method", + "description": "

Returns the angle between two vectors. The angles returned are signed,\nwhich means that v1.angleBetween(v2) === -v2.angleBetween(v1).

\n

If the vector was created with\ncreateVector(), angleBetween() returns\nangles in the units of the current\nangleMode().

\n", + "example": [ + "
\n\nlet v0 = createVector(1, 0);\nlet v1 = createVector(0, 1);\n// Prints \"1.570...\" to the console.\nprint(v0.angleBetween(v1));\n// Prints \"-1.570...\" to the console.\nprint(v1.angleBetween(v0));\n\n
\n\n
\n\nangleMode(DEGREES);\nlet v0 = createVector(1, 0);\nlet v1 = createVector(0, 1);\n// Prints \"90\" to the console.\nprint(v0.angleBetween(v1));\n// Prints \"-90\" to the console.\nprint(v1.angleBetween(v0));\n\n
\n\n
\n\nlet v0 = createVector(1, 0);\nlet v1 = createVector(0, 1);\n// Prints \"1.570...\" to the console.\nprint(p5.Vector.angleBetween(v0, v1));\n// Prints \"-1.570...\" to the console.\nprint(p5.Vector.angleBetween(v1, v0));\n\n
\n\n
\n\nangleMode(DEGREES);\nlet v0 = createVector(1, 0);\nlet v1 = createVector(0, 1);\n// Prints \"90\" to the console.\nprint(p5.Vector.angleBetween(v0, v1));\n// Prints \"-90\" to the console.\nprint(p5.Vector.angleBetween(v1, v0));\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let v0 = createVector(50, 50);\n let v1 = createVector(30, 0);\n let v2 = createVector(0, 30);\n\n drawArrow(v0, v1, 'red');\n drawArrow(v0, v2, 'blue');\n\n angleMode(RADIANS);\n let angle = round(v1.angleBetween(v2), 2);\n text(`Radians: ${angle}`, 20, 20);\n angleMode(DEGREES);\n angle = round(v1.angleBetween(v2), 2);\n text(`Degrees: ${angle}`, 20, 35);\n\n describe('Two arrows extend from the center of a gray square. A red arrow points to the right and a blue arrow points down. The text \"Radians: 1.57\" and \"Degrees: 90\" is written above the arrows.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "x, y, and z components of a p5.Vector.", + "type": "p5.Vector" + } + ], + "return": { + "description": "angle between the vectors.", + "type": "Number" + } + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v1", + "description": "the first vector.", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "the second vector.", + "type": "p5.Vector" + } + ], + "return": { + "description": "angle between the two vectors.", + "type": "Number" + } + } + ], + "return": { + "description": "angle between the vectors.", + "type": "Number" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "lerp", + "file": "src/math/p5.Vector.js", + "line": 2743, + "itemtype": "method", + "chainable": 1, + "description": "

Calculates new x, y, and z components that are proportionally the\nsame distance between two vectors. The amt parameter is the amount to\ninterpolate between the old vector and the new vector. 0.0 keeps all\ncomponents equal to the old vector's, 0.5 is halfway between, and 1.0 sets\nall components equal to the new vector's.

\n

The static version of lerp(), as in p5.Vector.lerp(v0, v1, 0.5),\nreturns a new p5.Vector object and doesn't change\nthe original.

\n", + "example": [ + "
\n\nlet v0 = createVector(1, 1, 1);\nlet v1 = createVector(3, 3, 3);\nv0.lerp(v1, 0.5);\n// Prints \"p5.Vector Object : [2, 2, 2]\" to the console.\nprint(v0.toString());\n\n
\n\n
\n\nlet v = createVector(1, 1, 1);\nv.lerp(3, 3, 3, 0.5);\n// Prints \"p5.Vector Object : [2, 2, 2]\" to the console.\nprint(v.toString());\n\n
\n\n
\n\nlet v0 = createVector(1, 1, 1);\nlet v1 = createVector(3, 3, 3);\nlet v2 = p5.Vector.lerp(v0, v1, 0.5);\n// Prints \"p5.Vector Object : [2, 2, 2]\" to the console.\nprint(v2.toString());\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let v0 = createVector(50, 50);\n let v1 = createVector(30, 0);\n let v2 = createVector(0, 30);\n let v3 = p5.Vector.lerp(v1, v2, 0.5);\n\n drawArrow(v0, v1, 'red');\n drawArrow(v0, v2, 'blue');\n drawArrow(v0, v3, 'purple');\n\n describe('Three arrows extend from the center of a gray square. A red arrow points to the right, a blue arrow points down, and a purple arrow points to the bottom right.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x component.", + "type": "Number" + }, + { + "name": "y", + "description": "y component.", + "type": "Number" + }, + { + "name": "z", + "description": "z component.", + "type": "Number" + }, + { + "name": "amt", + "description": "amount of interpolation between 0.0 (old vector)\nand 1.0 (new vector). 0.5 is halfway between.", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "v", + "description": "p5.Vector to lerp toward.", + "type": "p5.Vector" + }, + { + "name": "amt", + "type": "Number" + } + ] + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v1", + "type": "p5.Vector" + }, + { + "name": "v2", + "type": "p5.Vector" + }, + { + "name": "amt", + "type": "Number" + }, + { + "name": "target", + "description": "The vector to receive the result", + "optional": 1, + "type": "p5.Vector" + } + ], + "return": { + "description": "The lerped value", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "The lerped value", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "slerp", + "file": "src/math/p5.Vector.js", + "line": 2773, + "itemtype": "method", + "description": "

Calculates a new heading and magnitude that are between two vectors. The\namt parameter is the amount to interpolate between the old vector and\nthe new vector. 0.0 keeps the heading and magnitude equal to the old\nvector's, 0.5 sets them halfway between, and 1.0 sets the heading and\nmagnitude equal to the new vector's.

\n

slerp() differs from lerp() because\nit interpolates magnitude. Calling v0.slerp(v1, 0.5) sets v0's\nmagnitude to a value halfway between its original magnitude and v1's.\nCalling v0.lerp(v1, 0.5) makes no such guarantee.

\n

The static version of slerp(), as in p5.Vector.slerp(v0, v1, 0.5),\nreturns a new p5.Vector object and doesn't change\nthe original.

\n", + "example": [ + "
\n\nlet v0 = createVector(3, 0);\n// Prints \"3\" to the console.\nprint(v0.mag());\n// Prints \"0\" to the console.\nprint(v0.heading());\n\nlet v1 = createVector(0, 1);\n// Prints \"1\" to the console.\nprint(v1.mag());\n// Prints \"1.570...\" to the console.\nprint(v1.heading());\n\nv0.slerp(v1, 0.5);\n// Prints \"2\" to the console.\nprint(v0.mag());\n// Prints \"0.785...\" to the console.\nprint(v0.heading());\n\n
\n\n
\n\nlet v0 = createVector(3, 0);\n// Prints \"3\" to the console.\nprint(v0.mag());\n// Prints \"0\" to the console.\nprint(v0.heading());\n\nlet v1 = createVector(0, 1);\n// Prints \"1\" to the console.\nprint(v1.mag());\n// Prints \"1.570...\" to the console.\nprint(v1.heading());\n\nlet v3 = p5.Vector.slerp(v0, v1, 0.5);\n// Prints \"2\" to the console.\nprint(v3.mag());\n// Prints \"0.785...\" to the console.\nprint(v3.heading());\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let v0 = createVector(50, 50);\n let v1 = createVector(20, 0);\n let v2 = createVector(-40, 0);\n let v3 = p5.Vector.slerp(v1, v2, 0.5);\n\n drawArrow(v0, v1, 'red');\n drawArrow(v0, v2, 'blue');\n drawArrow(v0, v3, 'purple');\n\n describe('Three arrows extend from the center of a gray square. A red arrow points to the right, a blue arrow points to the left, and a purple arrow points down.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "v", + "description": "p5.Vector to slerp toward.", + "type": "p5.Vector" + }, + { + "name": "amt", + "description": "amount of interpolation between 0.0 (old vector)\nand 1.0 (new vector). 0.5 is halfway between.", + "type": "Number" + } + ], + "return": { + "description": "", + "type": "p5.Vector" + } + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v1", + "description": "old vector.", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "new vector.", + "type": "p5.Vector" + }, + { + "name": "amt", + "type": "Number" + }, + { + "name": "target", + "description": "vector to receive the result.", + "optional": 1, + "type": "p5.Vector" + } + ], + "return": { + "description": "slerped vector between v1 and v2", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "reflect", + "file": "src/math/p5.Vector.js", + "line": 2934, + "itemtype": "method", + "chainable": 1, + "description": "

Reflects a vector about a line in 2D or a plane in 3D. The orientation of\nthe line or plane is described by a normal vector that points away from the\nshape.

\n

The static version of reflect(), as in p5.Vector.reflect(v, n),\nreturns a new p5.Vector object and doesn't change\nthe original.

\n", + "example": [ + "
\n\nlet n = createVector(0, 1);\nlet v = createVector(4, 6);\nv.reflect(n);\n// Prints \"p5.Vector Object : [4, -6, 0]\" to the console.\nprint(v.toString());\n\n
\n\n
\n\nlet n = createVector(0, 1);\nlet v0 = createVector(4, 6);\nlet v1 = p5.Vector.reflect(v0, n);\n// Prints \"p5.Vector Object : [4, -6, 0]\" to the console.\nprint(v1.toString());\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n line(50, 0, 50, 100);\n let n = createVector(1, 0);\n\n let v0 = createVector(50, 50);\n let v1 = createVector(30, 40);\n let v2 = p5.Vector.reflect(v1, n);\n\n n.setMag(30);\n drawArrow(v0, n, 'black');\n drawArrow(v0, v1, 'red');\n drawArrow(v0, v2, 'blue');\n\n describe('Three arrows extend from the center of a gray square with a vertical line down its middle. A black arrow points to the right, a blue arrow points to the bottom left, and a red arrow points to the bottom right.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "surfaceNormal", + "description": "p5.Vector\nto reflect about.", + "type": "p5.Vector" + } + ] + }, + { + "params": [] + }, + { + "params": [ + { + "name": "incidentVector", + "description": "vector to be reflected.", + "type": "p5.Vector" + }, + { + "name": "surfaceNormal", + "type": "p5.Vector" + }, + { + "name": "target", + "description": "vector to receive the result.", + "optional": 1, + "type": "p5.Vector" + } + ], + "return": { + "description": "the reflected vector", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "the reflected vector", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "array", + "file": "src/math/p5.Vector.js", + "line": 2960, + "itemtype": "method", + "description": "Returns the vector's components as an array of numbers.", + "example": [ + "
\n\nlet v = createVector(20, 30);\n// Prints \"[20, 30, 0]\" to the console.\nprint(v.array());\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "array with the vector's components.", + "type": "Number[]" + } + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v", + "description": "the vector to convert to an array", + "type": "p5.Vector" + } + ], + "return": { + "description": "an Array with the 3 values", + "type": "Number[]" + } + } + ], + "return": { + "description": "array with the vector's components.", + "type": "Number[]" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "equals", + "file": "src/math/p5.Vector.js", + "line": 2973, + "itemtype": "method", + "description": "

Returns true if the vector's components are all the same as another\nvector's and false if not.

\n

The version of equals() with one parameter interprets it as another\np5.Vector object.

\n

The version of equals() with multiple parameters interprets them as the\ncomponents of another vector. Any missing parameters are assigned the value\n0.

\n

The static version of equals(), as in p5.Vector.equals(v0, v1),\ninterprets both parameters as p5.Vector objects.

\n", + "example": [ + "
\n\nlet v0 = createVector(10, 20, 30);\nlet v1 = createVector(10, 20, 30);\nlet v2 = createVector(0, 0, 0);\n\n// Prints \"true\" to the console.\nprint(v0.equals(v1));\n// Prints \"false\" to the console.\nprint(v0.equals(v2));\n\n
\n\n
\n\nlet v0 = createVector(5, 10, 20);\nlet v1 = createVector(5, 10, 20);\nlet v2 = createVector(13, 10, 19);\n\n// Prints \"true\" to the console.\nprint(v0.equals(v1.x, v1.y, v1.z));\n// Prints \"false\" to the console.\nprint(v0.equals(v2.x, v2.y, v2.z));\n\n
\n\n
\n\nlet v0 = createVector(10, 20, 30);\nlet v1 = createVector(10, 20, 30);\nlet v2 = createVector(0, 0, 0);\n\n// Prints \"true\" to the console.\nprint(p5.Vector.equals(v0, v1));\n// Prints \"false\" to the console.\nprint(p5.Vector.equals(v0, v2));\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x component of the vector.", + "optional": 1, + "type": "Number" + }, + { + "name": "y", + "description": "y component of the vector.", + "optional": 1, + "type": "Number" + }, + { + "name": "z", + "description": "z component of the vector.", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "whether the vectors are equal.", + "type": "Boolean" + } + }, + { + "params": [ + { + "name": "value", + "description": "vector to compare.", + "type": "p5.Vector|Array" + } + ], + "return": { + "description": "", + "type": "Boolean" + } + }, + { + "params": [] + }, + { + "params": [ + { + "name": "v1", + "description": "the first vector to compare", + "type": "p5.Vector|Array" + }, + { + "name": "v2", + "description": "the second vector to compare", + "type": "p5.Vector|Array" + } + ], + "return": { + "description": "", + "type": "Boolean" + } + } + ], + "return": { + "description": "whether the vectors are equal.", + "type": "Boolean" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "fromAngle", + "file": "src/math/p5.Vector.js", + "line": 2340, + "itemtype": "method", + "description": "Make a new 2D vector from an angle.", + "example": [ + "
\n\nlet v = p5.Vector.fromAngle(0);\n// Prints \"p5.Vector Object : [1, 0, 0]\" to the console.\nprint(v.toString());\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n let v0 = createVector(50, 50);\n let v1 = p5.Vector.fromAngle(0, 30);\n\n drawArrow(v0, v1, 'black');\n\n describe('A black arrow extends from the center of a gray square. It points to the right.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "desired angle, in radians. Unaffected by angleMode().", + "type": "Number" + }, + { + "name": "length", + "description": "length of the new vector (defaults to 1).", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "new p5.Vector object.", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "new p5.Vector object.", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "fromAngles", + "file": "src/math/p5.Vector.js", + "line": 2394, + "itemtype": "method", + "description": "Make a new 3D vector from a pair of ISO spherical angles.", + "example": [ + "
\n\nlet v = p5.Vector.fromAngles(0, 0);\n// Prints \"p5.Vector Object : [0, -1, 0]\" to the console.\nprint(v.toString());\n\n
\n\n
\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n}\n\nfunction draw() {\n background(0);\n\n fill(255);\n noStroke();\n\n let theta = frameCount * 0.05;\n let phi = 0;\n let v = p5.Vector.fromAngles(theta, phi, 100);\n let c = color('deeppink');\n pointLight(c, v);\n\n sphere(35);\n\n describe('A light shines on a pink sphere as it orbits.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "theta", + "description": "polar angle in radians (zero is up).", + "type": "Number" + }, + { + "name": "phi", + "description": "azimuthal angle in radians\n(zero is out of the screen).", + "type": "Number" + }, + { + "name": "length", + "description": "length of the new vector (defaults to 1).", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "new p5.Vector object.", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "new p5.Vector object.", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "random2D", + "file": "src/math/p5.Vector.js", + "line": 2456, + "itemtype": "method", + "description": "Make a new 2D unit vector with a random heading.", + "example": [ + "
\n\nlet v = p5.Vector.random2D();\n// Prints \"p5.Vector Object : [x, y, 0]\" to the console\n// where x and y are small random numbers.\nprint(v.toString());\n\n
\n\n
\n\nfunction draw() {\n background(200);\n\n frameRate(1);\n\n let v0 = createVector(50, 50);\n let v1 = p5.Vector.random2D();\n v1.mult(30);\n drawArrow(v0, v1, 'black');\n\n describe('A black arrow in extends from the center of a gray square. It changes direction once per second.');\n}\n\nfunction drawArrow(base, vec, myColor) {\n push();\n stroke(myColor);\n strokeWeight(3);\n fill(myColor);\n translate(base.x, base.y);\n line(0, 0, vec.x, vec.y);\n rotate(vec.heading());\n let arrowSize = 7;\n translate(vec.mag() - arrowSize, 0);\n triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);\n pop();\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "new p5.Vector object.", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "new p5.Vector object.", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "random3D", + "file": "src/math/p5.Vector.js", + "line": 2475, + "itemtype": "method", + "description": "Make a new 3D unit vector with a random heading.", + "example": [ + "
\n\nlet v = p5.Vector.random3D();\n// Prints \"p5.Vector Object : [x, y, z]\" to the console\n// where x, y, and z are small random numbers.\nprint(v.toString());\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "new p5.Vector object.", + "type": "p5.Vector" + } + } + ], + "return": { + "description": "new p5.Vector object.", + "type": "p5.Vector" + }, + "class": "p5.Vector", + "static": 1, + "module": "Math", + "submodule": "Vector" + }, + { + "name": "textBounds", + "file": "src/typography/p5.Font.js", + "line": 139, + "itemtype": "method", + "description": "

Returns the bounding box for a string of text written using this\np5.Font.

\n

The first parameter, str, is a string of text. The second and third\nparameters, x and y, are the text's position. By default, they set the\ncoordinates of the bounding box's bottom-left corner. See\ntextAlign() for more ways to align text.

\n

The fourth parameter, fontSize, is optional. It sets the font size used to\ndetermine the bounding box. By default, font.textBounds() will use the\ncurrent textSize().

\n", + "example": [ + "
\n\nlet font;\n\nfunction preload() {\n font = loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n background(200);\n\n let bbox = font.textBounds('p5*js', 35, 53);\n rect(bbox.x, bbox.y, bbox.w, bbox.h);\n\n textFont(font);\n text('p5*js', 35, 53);\n\n describe('The text \"p5*js\" written in black inside a white rectangle.');\n}\n\n
\n\n
\n\nlet font;\n\nfunction preload() {\n font = loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n background(200);\n\n textFont(font);\n textSize(15);\n textAlign(CENTER, CENTER);\n\n let bbox = font.textBounds('p5*js', 50, 50);\n rect(bbox.x, bbox.y, bbox.w, bbox.h);\n\n text('p5*js', 50, 50);\n\n describe('The text \"p5*js\" written in black inside a white rectangle.');\n}\n\n
\n\n
\n\nlet font;\n\nfunction preload() {\n font = loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n background(200);\n\n let bbox = font.textBounds('p5*js', 31, 53, 15);\n rect(bbox.x, bbox.y, bbox.w, bbox.h);\n\n textFont(font);\n textSize(15);\n text('p5*js', 31, 53);\n\n describe('The text \"p5*js\" written in black inside a white rectangle.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "str", + "description": "string of text.", + "type": "String" + }, + { + "name": "x", + "description": "x-coordinate of the text.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the text.", + "type": "Number" + }, + { + "name": "fontSize", + "description": "font size. Defaults to the current\ntextSize().", + "optional": 1, + "type": "Number" + } + ], + "return": { + "description": "object describing the bounding box with\nproperties x, y, w, and h.", + "type": "Object" + } + } + ], + "return": { + "description": "object describing the bounding box with\nproperties x, y, w, and h.", + "type": "Object" + }, + "class": "p5.Font", + "static": false, + "module": "Typography", + "submodule": "Loading & Displaying" + }, + { + "name": "textToPoints", + "file": "src/typography/p5.Font.js", + "line": 293, + "itemtype": "method", + "description": "

Returns an array of points outlining a string of text written using this\np5.Font.

\n

The first parameter, str, is a string of text. The second and third\nparameters, x and y, are the text's position. By default, they set the\ncoordinates of the bounding box's bottom-left corner. See\ntextAlign() for more ways to align text.

\n

The fourth parameter, fontSize, is optional. It sets the text's font\nsize. By default, font.textToPoints() will use the current\ntextSize().

\n

The fifth parameter, options, is also optional. font.textToPoints()\nexpects an object with the following properties:

\n

sampleFactor is the ratio of the text's path length to the number of\nsamples. It defaults to 0.1. Higher values produce more points along the\npath and are more precise.

\n

simplifyThreshold removes collinear points if it's set to a number other\nthan 0. The value represents the threshold angle to use when determining\nwhether two edges are collinear.

\n", + "example": [ + "
\n\nlet font;\n\nfunction preload() {\n font = loadFont('assets/inconsolata.otf');\n}\n\nfunction setup() {\n background(200);\n let points = font.textToPoints('p5*js', 6, 60, 35, { sampleFactor: 0.5 });\n points.forEach(p => {\n point(p.x, p.y);\n });\n\n describe('A set of black dots outlining the text \"p5*js\" on a gray background.');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "str", + "description": "string of text.", + "type": "String" + }, + { + "name": "x", + "description": "x-coordinate of the text.", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the text.", + "type": "Number" + }, + { + "name": "fontSize", + "description": "font size. Defaults to the current\ntextSize().", + "optional": 1, + "type": "Number" + }, + { + "name": "options", + "description": "object with sampleFactor and simplifyThreshold\nproperties.", + "optional": 1, + "type": "Object" + } + ], + "return": { + "description": "array of point objects, each with x, y, and alpha (path angle) properties.", + "type": "Array" + } + } + ], + "return": { + "description": "array of point objects, each with x, y, and alpha (path angle) properties.", + "type": "Array" + }, + "class": "p5.Font", + "static": false, + "module": "Typography", + "submodule": "Loading & Displaying" + }, + { + "name": "perspective", + "file": "src/webgl/p5.Camera.js", + "line": 788, + "itemtype": "method", + "description": "Sets a perspective projection.\nAccepts the same parameters as the global\nperspective().\nMore information on this function can be found there.", + "example": [ + "
\n\n// drag the mouse to look around!\n\nlet cam;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n // create a camera\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n // give it a perspective projection\n cam.perspective(PI / 3.0, width / height, 0.1, 500);\n}\n\nfunction draw() {\n background(200);\n orbitControl();\n normalMaterial();\n\n rotateX(-0.3);\n rotateY(-0.2);\n translate(0, 0, -50);\n\n push();\n translate(-15, 0, sin(frameCount / 30) * 65);\n box(30);\n pop();\n push();\n translate(15, 0, sin(frameCount / 30 + PI) * 65);\n box(30);\n pop();\n}\n\n
" + ], + "alt": "two colored 3D boxes move back and forth, rotating as mouse is dragged.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Camera", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "ortho", + "file": "src/webgl/p5.Camera.js", + "line": 888, + "itemtype": "method", + "description": "Sets an orthographic projection.\nAccepts the same parameters as the global\northo().\nMore information on this function can be found there.", + "example": [ + "
\n\n// drag the mouse to look around!\n// there's no vanishing point\n\nlet cam;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n // create a camera\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n // give it an orthographic projection\n cam.ortho(-width / 2, width / 2, height / 2, -height / 2, 0, 500);\n}\nfunction draw() {\n background(200);\n orbitControl();\n normalMaterial();\n\n rotateX(0.2);\n rotateY(-0.2);\n push();\n translate(-15, 0, sin(frameCount / 30) * 65);\n box(30);\n pop();\n push();\n translate(15, 0, sin(frameCount / 30 + PI) * 65);\n box(30);\n pop();\n}\n\n
" + ], + "alt": "two 3D boxes move back and forth along same plane, rotating as mouse is dragged.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Camera", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "frustum", + "file": "src/webgl/p5.Camera.js", + "line": 970, + "itemtype": "method", + "description": "Sets the camera's frustum.\nAccepts the same parameters as the global\nfrustum().\nMore information on this function can be found there.", + "example": [ + "
\n\nlet cam;\n\nfunction setup() {\n x = createCanvas(100, 100, WEBGL);\n setAttributes('antialias', true);\n // create a camera\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n // set its frustum\n cam.frustum(-0.1, 0.1, -0.1, 0.1, 0.1, 200);\n}\n\nfunction draw() {\n background(200);\n orbitControl();\n normalMaterial();\n\n rotateY(-0.2);\n rotateX(-0.3);\n push();\n translate(-15, 0, sin(frameCount / 30) * 25);\n box(30);\n pop();\n push();\n translate(15, 0, sin(frameCount / 30 + PI) * 25);\n box(30);\n pop();\n}\n\n
" + ], + "alt": "two 3D boxes move back and forth along same plane, rotating as mouse is dragged.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Camera", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "pan", + "file": "src/webgl/p5.Camera.js", + "line": 1111, + "itemtype": "method", + "description": "Panning rotates the camera view to the left and right.", + "example": [ + "
\n\nlet cam;\nlet delta = 0.01;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n normalMaterial();\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n // set initial pan angle\n cam.pan(-0.8);\n}\n\nfunction draw() {\n background(200);\n\n // pan camera according to angle 'delta'\n cam.pan(delta);\n\n // every 160 frames, switch direction\n if (frameCount % 160 === 0) {\n delta *= -1;\n }\n\n rotateX(frameCount * 0.01);\n translate(-100, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n}\n\n
" + ], + "alt": "camera view pans left and right across a series of rotating 3D boxes.", + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "amount to rotate camera in current\nangleMode units.\nGreater than 0 values rotate counterclockwise (to the left).", + "type": "Number" + } + ] + } + ], + "class": "p5.Camera", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "tilt", + "file": "src/webgl/p5.Camera.js", + "line": 1170, + "itemtype": "method", + "description": "Tilting rotates the camera view up and down.", + "example": [ + "
\n\nlet cam;\nlet delta = 0.01;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n normalMaterial();\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n // set initial tilt\n cam.tilt(-0.8);\n}\n\nfunction draw() {\n background(200);\n\n // pan camera according to angle 'delta'\n cam.tilt(delta);\n\n // every 160 frames, switch direction\n if (frameCount % 160 === 0) {\n delta *= -1;\n }\n\n rotateY(frameCount * 0.01);\n translate(0, -100, 0);\n box(20);\n translate(0, 35, 0);\n box(20);\n translate(0, 35, 0);\n box(20);\n translate(0, 35, 0);\n box(20);\n translate(0, 35, 0);\n box(20);\n translate(0, 35, 0);\n box(20);\n translate(0, 35, 0);\n box(20);\n}\n\n
" + ], + "alt": "camera view tilts up and down across a series of rotating 3D boxes.", + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "amount to rotate camera in current\nangleMode units.\nGreater than 0 values rotate counterclockwise (to the left).", + "type": "Number" + } + ] + } + ], + "class": "p5.Camera", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "lookAt", + "file": "src/webgl/p5.Camera.js", + "line": 1225, + "itemtype": "method", + "description": "Reorients the camera to look at a position in world space.", + "example": [ + "
\n\nlet cam;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n normalMaterial();\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n}\n\nfunction draw() {\n background(200);\n\n // look at a new random point every 60 frames\n if (frameCount % 60 === 0) {\n cam.lookAt(random(-100, 100), random(-50, 50), 0);\n }\n\n rotateX(frameCount * 0.01);\n translate(-100, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n translate(35, 0, 0);\n box(20);\n}\n\n
" + ], + "alt": "camera view of rotating 3D cubes changes to look at a new random\npoint every second .", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x position of a point in world space", + "type": "Number" + }, + { + "name": "y", + "description": "y position of a point in world space", + "type": "Number" + }, + { + "name": "z", + "description": "z position of a point in world space", + "type": "Number" + } + ] + } + ], + "class": "p5.Camera", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "camera", + "file": "src/webgl/p5.Camera.js", + "line": 1327, + "itemtype": "method", + "description": "Sets the camera's position and orientation.\nAccepts the same parameters as the global\ncamera().\nMore information on this function can be found there.", + "example": [ + "
\n\nlet cam;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n // Create a camera.\n // createCamera() sets the newly created camera as\n // the current (active) camera.\n cam = createCamera();\n}\n\nfunction draw() {\n background(204);\n // Move the camera away from the plane by a sin wave\n cam.camera(0, 0, 20 + sin(frameCount * 0.01) * 10, 0, 0, 0, 0, 1, 0);\n plane(10, 10);\n}\n\n
", + "
\n\n// move slider to see changes!\n// sliders control the first 6 parameters of camera()\n\nlet sliderGroup = [];\nlet X;\nlet Y;\nlet Z;\nlet centerX;\nlet centerY;\nlet centerZ;\nlet h = 20;\nlet cam;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n // create a camera\n cam = createCamera();\n cam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n // create sliders\n for (var i = 0; i < 6; i++) {\n if (i === 2) {\n sliderGroup[i] = createSlider(10, 400, 200);\n } else {\n sliderGroup[i] = createSlider(-400, 400, 0);\n }\n h = map(i, 0, 6, 5, 85);\n sliderGroup[i].position(10, height + h);\n sliderGroup[i].style('width', '80px');\n }\n}\n\nfunction draw() {\n background(60);\n // assigning sliders' value to each parameters\n X = sliderGroup[0].value();\n Y = sliderGroup[1].value();\n Z = sliderGroup[2].value();\n centerX = sliderGroup[3].value();\n centerY = sliderGroup[4].value();\n centerZ = sliderGroup[5].value();\n cam.camera(X, Y, Z, centerX, centerY, centerZ, 0, 1, 0);\n stroke(255);\n fill(255, 102, 94);\n box(85);\n}\n\n
" + ], + "alt": "White square repeatedly grows to fill canvas and then shrinks.\nAn interactive example of a red cube with 3 sliders for moving it across x, y,\nz axis and 3 sliders for shifting its center.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Camera", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "move", + "file": "src/webgl/p5.Camera.js", + "line": 1441, + "itemtype": "method", + "description": "Move camera along its local axes while maintaining current camera orientation.", + "example": [ + "
\n\n// see the camera move along its own axes while maintaining its orientation\nlet cam;\nlet delta = 0.5;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n normalMaterial();\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n}\n\nfunction draw() {\n background(200);\n\n // move the camera along its local axes\n cam.move(delta, delta, 0);\n\n // every 100 frames, switch direction\n if (frameCount % 150 === 0) {\n delta *= -1;\n }\n\n translate(-10, -10, 0);\n box(50, 8, 50);\n translate(15, 15, 0);\n box(50, 8, 50);\n translate(15, 15, 0);\n box(50, 8, 50);\n translate(15, 15, 0);\n box(50, 8, 50);\n translate(15, 15, 0);\n box(50, 8, 50);\n translate(15, 15, 0);\n box(50, 8, 50);\n}\n\n
" + ], + "alt": "camera view moves along a series of 3D boxes, maintaining the same\norientation throughout the move", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "amount to move along camera's left-right axis", + "type": "Number" + }, + { + "name": "y", + "description": "amount to move along camera's up-down axis", + "type": "Number" + }, + { + "name": "z", + "description": "amount to move along camera's forward-backward axis", + "type": "Number" + } + ] + } + ], + "class": "p5.Camera", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "setPosition", + "file": "src/webgl/p5.Camera.js", + "line": 1508, + "itemtype": "method", + "description": "Set camera position in world-space while maintaining current camera\norientation.", + "example": [ + "
\n\n// press '1' '2' or '3' keys to set camera position\n\nlet cam;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n normalMaterial();\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n}\n\nfunction draw() {\n background(200);\n\n // '1' key\n if (keyIsDown(49)) {\n cam.setPosition(30, 0, 80);\n }\n // '2' key\n if (keyIsDown(50)) {\n cam.setPosition(0, 0, 80);\n }\n // '3' key\n if (keyIsDown(51)) {\n cam.setPosition(-30, 0, 80);\n }\n\n box(20);\n}\n\n
" + ], + "alt": "camera position changes as the user presses keys, altering view of a 3D box", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x position of a point in world space", + "type": "Number" + }, + { + "name": "y", + "description": "y position of a point in world space", + "type": "Number" + }, + { + "name": "z", + "description": "z position of a point in world space", + "type": "Number" + } + ] + } + ], + "class": "p5.Camera", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "set", + "file": "src/webgl/p5.Camera.js", + "line": 1576, + "itemtype": "method", + "description": "Copies information about the argument camera's view and projection to\nthe target camera. If the target camera is active, it will be reflected\non the screen.", + "example": [ + "
\n\nlet cam, initialCam;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n strokeWeight(3);\n\n // Set the initial state to initialCamera and set it to the camera\n // used for drawing. Then set cam to be the active camera.\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n initialCam = createCamera();\n initialCam.camera(100, 100, 100, 0, 0, 0, 0, 0, -1);\n initialCam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n cam.set(initialCam);\n\n setCamera(cam);\n}\n\nfunction draw() {\n orbitControl();\n background(255);\n box(50);\n translate(0, 0, -25);\n plane(100);\n}\n\nfunction doubleClicked(){\n // Double-click to return the camera to its initial position.\n cam.set(initialCam);\n}\n\n
" + ], + "alt": "Prepare two cameras. One is the camera that sets the initial state,\nand the other is the camera that moves with interaction.\nDraw a plane and a box on top of it, operate the camera using orbitControl().\nDouble-click to set the camera in the initial state and return to\nthe initial state.", + "overloads": [ + { + "params": [ + { + "name": "cam", + "description": "source camera", + "type": "p5.Camera" + } + ] + } + ], + "class": "p5.Camera", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "slerp", + "file": "src/webgl/p5.Camera.js", + "line": 1718, + "itemtype": "method", + "description": "For the cameras cam0 and cam1 with the given arguments, their view are combined\nwith the parameter amt that represents the quantity, and the obtained view is applied.\nFor example, if cam0 is looking straight ahead and cam1 is looking straight\nto the right and amt is 0.5, the applied camera will look to the halfway\nbetween front and right.\nIf the applied camera is active, the applied result will be reflected on the screen.\nWhen applying this function, all cameras involved must have exactly the same projection\nsettings. For example, if one is perspective, ortho, frustum, the other two must also be\nperspective, ortho, frustum respectively. However, if all cameras have ortho settings,\ninterpolation is possible if the ratios of left, right, top and bottom are equal to each other.\nFor example, when it is changed by orbitControl().", + "example": [ + "
\n\nlet cam0, cam1, cam;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n strokeWeight(3);\n\n // camera for slerp.\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n // cam0 is looking at the cube from the front.\n cam0 = createCamera();\n cam0.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam0.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n // cam1 is pointing straight to the right in the cube\n // at the same position as cam0 by doing a pan(-PI/2).\n cam1 = createCamera();\n cam1.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam1.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n cam1.pan(-PI/2);\n\n // we only use cam.\n setCamera(cam);\n}\n\nfunction draw() {\n // calculate amount.\n const amt = 0.5 - 0.5 * cos(frameCount * TAU / 120);\n // slerp cam0 and cam1 with amt, set to cam.\n // When amt moves from 0 to 1, cam moves from cam0 to cam1,\n // shaking the camera to the right.\n cam.slerp(cam0, cam1, amt);\n\n background(255);\n // Every time the camera turns right, the cube drifts left.\n box(40);\n}\n\n
", + "
\n\nlet cam, lastCam, initialCam;\nlet countForReset = 30;\n// This sample uses orbitControl() to move the camera.\n// Double-clicking the canvas restores the camera to its initial state.\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n strokeWeight(3);\n\n // main camera\n cam = createCamera();\n cam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n cam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n // Camera for recording loc info before reset\n lastCam = createCamera();\n lastCam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n lastCam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n // Camera for recording the initial state\n initialCam = createCamera();\n initialCam.camera(0, 0, 50*sqrt(3), 0, 0, 0, 0, 1, 0);\n initialCam.perspective(PI/3, 1, 5*sqrt(3), 500*sqrt(3));\n\n setCamera(cam); // set main camera\n}\n\nfunction draw() {\n if (countForReset < 30) {\n // if the reset count is less than 30,\n // it will move closer to the original camera as it increases.\n countForReset++;\n cam.slerp(lastCam, initialCam, countForReset / 30);\n } else {\n // if the count is 30,\n // you can freely move the main camera with orbitControl().\n orbitControl();\n }\n\n background(255);\n box(40);\n}\n// A double-click sets countForReset to 0 and initiates a reset.\nfunction doubleClicked() {\n if (countForReset === 30) {\n countForReset = 0;\n lastCam.set(cam);\n }\n}\n\n
" + ], + "alt": "Prepare two cameras. One camera is facing straight ahead to the cube and the other\ncamera is in the same position and looking straight to the right.\nIf you use a camera which interpolates these with slerp(), the facing direction\nof the camera will change smoothly between the front and the right.\nThere is a camera, drawing a cube. The camera can be moved freely with\norbitControl(). Double-click to smoothly return the camera to its initial state.\nThe camera cannot be moved during that time.", + "overloads": [ + { + "params": [ + { + "name": "cam0", + "description": "first p5.Camera", + "type": "p5.Camera" + }, + { + "name": "cam1", + "description": "second p5.Camera", + "type": "p5.Camera" + }, + { + "name": "amt", + "description": "amount to use for interpolation during slerp", + "type": "Number" + } + ] + } + ], + "class": "p5.Camera", + "static": false, + "module": "3D", + "submodule": "Camera" + }, + { + "name": "resize", + "file": "src/webgl/p5.Framebuffer.js", + "line": 205, + "itemtype": "method", + "description": "Resizes the framebuffer to the given width and height.", + "example": [ + "
\n\nlet framebuffer;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n framebuffer = createFramebuffer();\n noStroke();\n}\n\nfunction mouseMoved() {\n framebuffer.resize(\n max(20, mouseX),\n max(20, mouseY)\n );\n}\n\nfunction draw() {\n // Draw to the framebuffer\n framebuffer.begin();\n background(255);\n normalMaterial();\n sphere(20);\n framebuffer.end();\n\n background(100);\n // Draw the framebuffer to the main canvas\n image(framebuffer, -width/2, -height/2);\n}\n\n
" + ], + "alt": "A red, green, and blue sphere is drawn in the middle of a white rectangle\nwhich starts in the top left of the canvas and whose bottom right is at\nthe user's mouse", + "overloads": [ + { + "params": [ + { + "name": "width", + "type": "Number" + }, + { + "name": "height", + "type": "Number" + } + ] + } + ], + "class": "p5.Framebuffer", + "static": false, + "module": "Rendering" + }, + { + "name": "pixelDensity", + "file": "src/webgl/p5.Framebuffer.js", + "line": 223, + "itemtype": "method", + "description": "

Gets or sets the pixel scaling for high pixel density displays. By\ndefault, the density will match that of the canvas the framebuffer was\ncreated on, which will match the display density.

\n

Call this method with no arguments to get the current density, or pass\nin a number to set the density.

\n", + "example": [], + "overloads": [ + { + "params": [ + { + "name": "density", + "description": "A scaling factor for the number of pixels per\nside of the framebuffer", + "optional": 1, + "type": "Number" + } + ] + } + ], + "class": "p5.Framebuffer", + "static": false, + "module": "Rendering" + }, + { + "name": "autoSized", + "file": "src/webgl/p5.Framebuffer.js", + "line": 243, + "itemtype": "method", + "description": "

Gets or sets whether or not this framebuffer will automatically resize\nalong with the canvas it's attached to in order to match its size.

\n

Call this method with no arguments to see if it is currently auto-sized,\nor pass in a boolean to set this property.

\n", + "example": [], + "overloads": [ + { + "params": [ + { + "name": "autoSized", + "description": "Whether or not the framebuffer should resize\nalong with the canvas it's attached to", + "optional": 1, + "type": "Boolean" + } + ] + } + ], + "class": "p5.Framebuffer", + "static": false, + "module": "Rendering" + }, + { + "name": "createCamera", + "file": "src/webgl/p5.Framebuffer.js", + "line": 681, + "itemtype": "method", + "description": "Creates and returns a new\np5.FramebufferCamera to be used\nwhile drawing to this framebuffer. The camera will be set as the\ncurrently active camera.", + "example": [], + "overloads": [ + { + "params": [], + "return": { + "description": "A new camera", + "type": "p5.Camera" + } + } + ], + "return": { + "description": "A new camera", + "type": "p5.Camera" + }, + "class": "p5.Framebuffer", + "static": false, + "module": "Rendering" + }, + { + "name": "remove", + "file": "src/webgl/p5.Framebuffer.js", + "line": 745, + "itemtype": "method", + "description": "Removes the framebuffer and frees its resources.", + "example": [ + "
\n\nlet framebuffer;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n}\n\nfunction draw() {\n const useFramebuffer = (frameCount % 120) > 60;\n if (useFramebuffer && !framebuffer) {\n // Create a new framebuffer for us to use\n framebuffer = createFramebuffer();\n } else if (!useFramebuffer && framebuffer) {\n // Free the old framebuffer's resources\n framebuffer.remove();\n framebuffer = undefined;\n }\n\n background(255);\n if (useFramebuffer) {\n // Draw to the framebuffer\n framebuffer.begin();\n background(255);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n fill(255, 0, 0);\n box(30);\n framebuffer.end();\n\n image(framebuffer, -width/2, -height/2);\n }\n}\n\n
" + ], + "alt": "A rotating red cube blinks on and off every second.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Framebuffer", + "static": false, + "module": "Rendering" + }, + { + "name": "begin", + "file": "src/webgl/p5.Framebuffer.js", + "line": 803, + "itemtype": "method", + "description": "Begin drawing to this framebuffer. Subsequent drawing functions to the\ncanvas the framebuffer is attached to will not be immediately visible, and\nwill instead be drawn to the framebuffer's texture. Call\nend() when finished to make draw\nfunctions go right to the canvas again and to be able to read the\ncontents of the framebuffer's texture.", + "example": [ + "
\n\nlet framebuffer;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n framebuffer = createFramebuffer();\n noStroke();\n}\n\nfunction draw() {\n // Draw to the framebuffer\n framebuffer.begin();\n background(255);\n translate(0, 10*sin(frameCount * 0.01), 0);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n fill(255, 0, 0);\n box(50);\n framebuffer.end();\n\n background(100);\n // Draw the framebuffer to the main canvas\n image(framebuffer, -50, -50, 25, 25);\n image(framebuffer, 0, 0, 35, 35);\n}\n\n
" + ], + "alt": "A video of a floating and rotating red cube is pasted twice on the\ncanvas: once in the top left, and again, larger, in the bottom right.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Framebuffer", + "static": false, + "module": "Rendering" + }, + { + "name": "end", + "file": "src/webgl/p5.Framebuffer.js", + "line": 892, + "itemtype": "method", + "description": "After having previously called\nbegin(), this method stops drawing\nfunctions from going to the framebuffer's texture, allowing them to go\nright to the canvas again. After this, one can read from the framebuffer's\ntexture.", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Framebuffer", + "static": false, + "module": "Rendering" + }, + { + "name": "draw", + "file": "src/webgl/p5.Framebuffer.js", + "line": 955, + "itemtype": "method", + "description": "Run a function while drawing to the framebuffer rather than to its canvas.\nThis is equivalent to calling framebuffer.begin(), running the function,\nand then calling framebuffer.end(), but ensures that one never\naccidentally forgets begin or end.", + "example": [ + "
\n\nlet framebuffer;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n framebuffer = createFramebuffer();\n noStroke();\n}\n\nfunction draw() {\n // Draw to the framebuffer\n framebuffer.draw(function() {\n background(255);\n translate(0, 10*sin(frameCount * 0.01), 0);\n rotateX(frameCount * 0.01);\n rotateY(frameCount * 0.01);\n fill(255, 0, 0);\n box(50);\n });\n\n background(100);\n // Draw the framebuffer to the main canvas\n image(framebuffer, -50, -50, 25, 25);\n image(framebuffer, 0, 0, 35, 35);\n}\n\n
" + ], + "alt": "A video of a floating and rotating red cube is pasted twice on the\ncanvas: once in the top left, and again, larger, in the bottom right.", + "overloads": [ + { + "params": [ + { + "name": "callback", + "description": "A function to run that draws to the canvas. The\nfunction will immediately be run, but it will draw to the framebuffer\ninstead of the canvas.", + "type": "Function" + } + ] + } + ], + "class": "p5.Framebuffer", + "static": false, + "module": "Rendering" + }, + { + "name": "loadPixels", + "file": "src/webgl/p5.Framebuffer.js", + "line": 967, + "itemtype": "method", + "description": "Call this befpre updating pixels\nand calling updatePixels\nto replace the content of the framebuffer with the data in the pixels\narray.", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Framebuffer", + "static": false, + "module": "Rendering" + }, + { + "name": "get", + "file": "src/webgl/p5.Framebuffer.js", + "line": 1018, + "itemtype": "method", + "description": "

Get a region of pixels from the canvas in the form of a\np5.Image, or a single pixel as an array of\nnumbers.

\n

Returns an array of [R,G,B,A] values for any pixel or grabs a section of\nan image. If the Framebuffer has been set up to not store alpha values, then\nonly [R,G,B] will be returned. If no parameters are specified, the entire\nimage is returned.\nUse the x and y parameters to get the value of one pixel. Get a section of\nthe display window by specifying additional w and h parameters. When\ngetting an image, the x and y parameters define the coordinates for the\nupper-left corner of the image, regardless of the current imageMode().

\n", + "example": [], + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "x-coordinate of the pixel", + "type": "Number" + }, + { + "name": "y", + "description": "y-coordinate of the pixel", + "type": "Number" + }, + { + "name": "w", + "description": "width of the section to be returned", + "type": "Number" + }, + { + "name": "h", + "description": "height of the section to be returned", + "type": "Number" + } + ], + "return": { + "description": "the rectangle p5.Image", + "type": "p5.Image" + } + }, + { + "params": [], + "return": { + "description": "the whole p5.Image", + "type": "p5.Image" + } + }, + { + "params": [ + { + "name": "x", + "type": "Number" + }, + { + "name": "y", + "type": "Number" + } + ], + "return": { + "description": "color of pixel at x,y in array format [R, G, B, A]", + "type": "Number[]" + } + } + ], + "return": { + "description": "the rectangle p5.Image", + "type": "p5.Image" + }, + "class": "p5.Framebuffer", + "static": false, + "module": "Rendering" + }, + { + "name": "updatePixels", + "file": "src/webgl/p5.Framebuffer.js", + "line": 1168, + "itemtype": "method", + "description": "

Call this after initially calling \nloadPixels() and updating pixels\nto replace the content of the framebuffer with the data in the pixels\narray.

\n

This will also clear the depth buffer so that any future drawing done\nafterwards will go on top.

\n", + "example": [ + "
\n\nlet framebuffer;\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n framebuffer = createFramebuffer();\n}\n\nfunction draw() {\n noStroke();\n lights();\n\n // Draw a sphere to the framebuffer\n framebuffer.begin();\n background(0);\n sphere(25);\n framebuffer.end();\n\n // Load its pixels and draw a gradient over the lower half of the canvas\n framebuffer.loadPixels();\n for (let y = height/2; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const idx = (y * width + x) * 4;\n framebuffer.pixels[idx] = (x / width) * 255;\n framebuffer.pixels[idx + 1] = (y / height) * 255;\n framebuffer.pixels[idx + 2] = 255;\n framebuffer.pixels[idx + 3] = 255;\n }\n }\n framebuffer.updatePixels();\n\n // Draw a cube on top of the pixels we just wrote\n framebuffer.begin();\n push();\n translate(20, 20);\n rotateX(0.5);\n rotateY(0.5);\n box(20);\n pop();\n framebuffer.end();\n\n image(framebuffer, -width/2, -height/2);\n noLoop();\n}\n\n
" + ], + "alt": "A sphere partly occluded by a gradient from cyan to white to magenta on\nthe lower half of the canvas, with a 3D cube drawn on top of that in the\nlower right corner.", + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Framebuffer", + "static": false, + "module": "Rendering" + }, + { + "name": "clearColors", + "file": "src/webgl/p5.Geometry.js", + "line": 138, + "itemtype": "method", + "description": "Removes the internal colors of p5.Geometry.\nUsing clearColors(), you can use fill() to supply new colors before drawing each shape.\nIf clearColors() is not used, the shapes will use their internal colors by ignoring fill().", + "example": [ + "
\n\nlet shape01;\nlet shape02;\nlet points = [];\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n points.push(new p5.Vector(-1, -1, 0), new p5.Vector(-1, 1, 0),\n new p5.Vector(1, -1, 0), new p5.Vector(-1, -1, 0));\n buildShape01();\n buildShape02();\n}\nfunction draw() {\n background(0);\n fill('pink'); // shape01 retains its internal blue color, so it won't turn pink.\n model(shape01);\n fill('yellow'); // Now, shape02 is yellow.\n model(shape02);\n}\n\nfunction buildShape01() {\n beginGeometry();\n fill('blue'); // shape01's color is blue because its internal colors remain.\n beginShape();\n for (let vec of points) vertex(vec.x * 100, vec.y * 100, vec.z * 100);\n endShape(CLOSE);\n shape01 = endGeometry();\n}\n\nfunction buildShape02() {\n beginGeometry();\n fill('red'); // shape02.clearColors() removes its internal colors. Now, shape02 is red.\n beginShape();\n for (let vec of points) vertex(vec.x * 200, vec.y * 200, vec.z * 200);\n endShape(CLOSE);\n shape02 = endGeometry();\n shape02.clearColors(); // Resets shape02's colors.\n}\n\n
" + ], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Geometry", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "computeFaces", + "file": "src/webgl/p5.Geometry.js", + "line": 290, + "itemtype": "method", + "chainable": 1, + "description": "computes faces for geometry objects based on the vertices.", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Geometry", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "computeNormals", + "file": "src/webgl/p5.Geometry.js", + "line": 423, + "itemtype": "method", + "chainable": 1, + "description": "

This function calculates normals for each face, where each vertex's normal is the average of the normals of all faces it's connected to.\ni.e computes smooth normals per vertex as an average of each face.

\n

When using FLAT shading, vertices are disconnected/duplicated i.e each face has its own copy of vertices.\nWhen using SMOOTH shading, vertices are connected/deduplicated i.e each face has its vertices shared with other faces.

\n

Options can include:

\n", + "example": [ + "
\n\nlet helix;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n\n helix = buildGeometry(() => {\n beginShape();\n\n for (let i = 0; i < TWO_PI * 3; i += 0.6) {\n let radius = 20;\n let x = cos(i) * radius;\n let y = sin(i) * radius;\n let z = map(i, 0, TWO_PI * 3, -30, 30);\n vertex(x, y, z);\n }\n endShape();\n });\n helix.computeNormals();\n}\nfunction draw() {\n background(255);\n stroke(0);\n fill(150, 200, 250);\n lights();\n rotateX(PI*0.2);\n orbitControl();\n model(helix);\n}\n\n
", + "
\n\nlet star;\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n\n star = buildGeometry(() => {\n beginShape();\n for (let i = 0; i < TWO_PI; i += PI / 5) {\n let outerRadius = 60;\n let innerRadius = 30;\n let xOuter = cos(i) * outerRadius;\n let yOuter = sin(i) * outerRadius;\n let zOuter = random(-20, 20);\n vertex(xOuter, yOuter, zOuter);\n\n let nextI = i + PI / 5 / 2;\n let xInner = cos(nextI) * innerRadius;\n let yInner = sin(nextI) * innerRadius;\n let zInner = random(-20, 20);\n vertex(xInner, yInner, zInner);\n }\n endShape(CLOSE);\n });\n star.computeNormals(SMOOTH);\n}\nfunction draw() {\n background(255);\n stroke(0);\n fill(150, 200, 250);\n lights();\n rotateX(PI*0.2);\n orbitControl();\n model(star);\n}\n\n
" + ], + "alt": "A 3D helix using the computeNormals() function by default uses `FLAT` to create a flat shading effect on the helix.\nA star-like geometry, here the computeNormals(SMOOTH) is applied for a smooth shading effect.\nThis helps to avoid the faceted appearance that can occur with flat shading.", + "overloads": [ + { + "params": [ + { + "name": "shadingType", + "description": "shading type (FLAT for flat shading or SMOOTH for smooth shading) for buildGeometry() outputs. Defaults to FLAT.", + "optional": 1, + "type": "String" + }, + { + "name": "options", + "description": "An optional object with configuration.", + "optional": 1, + "type": "Object" + } + ] + } + ], + "class": "p5.Geometry", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "averageNormals", + "file": "src/webgl/p5.Geometry.js", + "line": 503, + "itemtype": "method", + "chainable": 1, + "description": "Averages the vertex normals. Used in curved\nsurfaces", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Geometry", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "averagePoleNormals", + "file": "src/webgl/p5.Geometry.js", + "line": 522, + "itemtype": "method", + "chainable": 1, + "description": "Averages pole normals. Used in spherical primitives", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Geometry", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "normalize", + "file": "src/webgl/p5.Geometry.js", + "line": 831, + "itemtype": "method", + "chainable": 1, + "description": "Modifies all vertices to be centered within the range -100 to 100.", + "example": [], + "overloads": [ + { + "params": [] + } + ], + "class": "p5.Geometry", + "static": false, + "module": "Shape", + "submodule": "3D Primitives" + }, + { + "name": "copyToContext", + "file": "src/webgl/p5.Shader.js", + "line": 127, + "itemtype": "method", + "description": "

Shaders belong to the main canvas or a p5.Graphics. Once they are compiled,\nthey can only be used in the context they were compiled on.

\n

Use this method to make a new copy of a shader that gets compiled on a\ndifferent context.

\n", + "example": [ + "
\n\nlet graphic = createGraphics(200, 200, WEBGL);\nlet graphicShader = graphic.createShader(vert, frag);\ngraphic.shader(graphicShader); // Use graphicShader on the graphic\n\nlet mainShader = graphicShader.copyToContext(window);\nshader(mainShader); // Use `mainShader` on the main canvas\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "context", + "description": "The graphic or instance to copy this shader to.\nPass window if you need to copy to the main canvas.", + "type": "p5|p5.Graphics" + } + ], + "return": { + "description": "A new shader on the target context.", + "type": "p5.Shader" + } + } + ], + "return": { + "description": "A new shader on the target context.", + "type": "p5.Shader" + }, + "class": "p5.Shader", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "setUniform", + "file": "src/webgl/p5.Shader.js", + "line": 438, + "itemtype": "method", + "chainable": 1, + "description": "

Used to set the uniforms of a\np5.Shader object.

\n

Uniforms are used as a way to provide shader programs\n(which run on the GPU) with values from a sketch\n(which runs on the CPU).

\n

Here are some examples of uniforms you can make:

\n", + "example": [ + "
\n\n// Click within the image to toggle the value of uniforms\n// Note: for an alternative approach to the same example,\n// involving toggling between shaders please refer to:\n// https://p5js.org/reference/#/p5/shader\n\nlet grad;\nlet showRedGreen = false;\n\nfunction preload() {\n // note that we are using two instances\n // of the same vertex and fragment shaders\n grad = loadShader('assets/shader.vert', 'assets/shader-gradient.frag');\n}\n\nfunction setup() {\n createCanvas(100, 100, WEBGL);\n shader(grad);\n noStroke();\n\n describe(\n 'canvas toggles between a circular gradient of orange and blue vertically. and a circular gradient of red and green moving horizontally when mouse is clicked/pressed.'\n );\n}\n\nfunction draw() {\n // update the offset values for each scenario,\n // moving the \"grad\" shader in either vertical or\n // horizontal direction each with differing colors\n\n if (showRedGreen === true) {\n grad.setUniform('colorCenter', [1, 0, 0]);\n grad.setUniform('colorBackground', [0, 1, 0]);\n grad.setUniform('offset', [sin(millis() / 2000), 1]);\n } else {\n grad.setUniform('colorCenter', [1, 0.5, 0]);\n grad.setUniform('colorBackground', [0.226, 0, 0.615]);\n grad.setUniform('offset', [0, sin(millis() / 2000) + 1]);\n }\n quad(-1, -1, 1, -1, 1, 1, -1, 1);\n}\n\nfunction mouseClicked() {\n showRedGreen = !showRedGreen;\n}\n\n
" + ], + "alt": "canvas toggles between a circular gradient of orange and blue vertically. and a circular gradient of red and green moving horizontally when mouse is clicked/pressed.", + "overloads": [ + { + "params": [ + { + "name": "uniformName", + "description": "the name of the uniform.\nMust correspond to the name used in the vertex and fragment shaders", + "type": "String" + }, + { + "name": "data", + "description": "The value to assign to the uniform. This can be\na boolean (true/false), a number, an array of numbers, or\nan image (p5.Image, p5.Graphics, p5.MediaElement, p5.Texture)", + "type": "Boolean|Number|Number[]|p5.Image|p5.Graphics|p5.MediaElement|p5.Texture" + } + ] + } + ], + "class": "p5.Shader", + "static": false, + "module": "3D", + "submodule": "Material" + }, + { + "name": "remove", + "file": "src/core/main.js", + "line": 341, + "itemtype": "method", + "description": "Removes the entire p5 sketch. This will remove the canvas and any\nelements created by p5.js. It will also stop the draw loop and unbind\nany properties or methods from the window global scope. It will\nleave a variable p5 in case you wanted to create a new p5 sketch.\nIf you like, you can set p5 = null to erase it. While all functions and\nvariables and objects created by the p5 library will be removed, any\nother global variables created by your code will remain.", + "example": [ + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
", + "
\nfunction draw() {\n ellipse(50, 50, 10, 10);\n}\n\nfunction mousePressed() {\n remove(); // remove whole sketch on mouse press\n}\n
" + ], + "alt": "nothing displayed", + "overloads": [ + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "Structure", + "submodule": "Structure" + }, + { + "name": "createSlider", + "file": "src/dom/dom.js", + "line": 827, + "itemtype": "method", + "description": "INPUT *", + "example": [], + "overloads": [ + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "createAudio", + "file": "src/dom/dom.js", + "line": 2080, + "itemtype": "method", + "description": "AUDIO STUFF *", + "example": [], + "overloads": [ + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + } + ], + "class": "p5", + "static": false, + "module": "DOM", + "submodule": "DOM" + }, + { + "name": "addRow", + "file": "src/io/p5.Table.js", + "line": 95, + "itemtype": "method", + "description": "

Use addRow() to add a new row of data to a p5.Table object. By default,\nan empty row is created. Typically, you would store a reference to\nthe new row in a TableRow object (see newRow in the example above),\nand then set individual values using set().

\n

If a p5.TableRow object is included as a parameter, then that row is\nduplicated and added to the table.

\n", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "row", + "description": "row to be added to the table", + "optional": 1, + "type": "p5.TableRow" + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + } + } + ], + "return": { + "description": "the row that was added", + "type": "p5.TableRow" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "removeRow", + "file": "src/io/p5.Table.js", + "line": 146, + "itemtype": "method", + "description": "Removes a row from the table object.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //remove the first row\n table.removeRow(0);\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "id", + "description": "ID number of the row to remove", + "type": "Integer" + } + ] + } + ], + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "getRow", + "file": "src/io/p5.Table.js", + "line": 192, + "itemtype": "method", + "description": "Returns a reference to the specified p5.TableRow. The reference\ncan then be used to get and set values of the selected row.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let row = table.getRow(1);\n //print it column by column\n //note: a row is an object, not an array\n for (let c = 0; c < table.getColumnCount(); c++) {\n print(row.getString(c));\n }\n\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "rowID", + "description": "ID number of the row to get", + "type": "Integer" + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + } + } + ], + "return": { + "description": "p5.TableRow object", + "type": "p5.TableRow" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "getRows", + "file": "src/io/p5.Table.js", + "line": 238, + "itemtype": "method", + "description": "Gets all rows from the table. Returns an array of p5.TableRows.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let rows = table.getRows();\n\n //warning: rows is an array of objects\n for (let r = 0; r < rows.length; r++) {\n rows[r].set('name', 'Unicorn');\n }\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + }, + { + "params": [], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + } + } + ], + "return": { + "description": "Array of p5.TableRows", + "type": "p5.TableRow[]" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "findRow", + "file": "src/io/p5.Table.js", + "line": 283, + "itemtype": "method", + "description": "Finds the first row in the Table that contains the value\nprovided, and returns a reference to that row. Even if\nmultiple rows are possible matches, only the first matching\nrow is returned. The column to search may be specified by\neither its ID or title.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //find the animal named zebra\n let row = table.findRow('Zebra', 'name');\n //find the corresponding species\n print(row.getString('species'));\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + } + } + ], + "return": { + "description": "", + "type": "p5.TableRow" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "findRows", + "file": "src/io/p5.Table.js", + "line": 349, + "itemtype": "method", + "description": "Finds the rows in the Table that contain the value\nprovided, and returns references to those rows. Returns an\nArray, so for must be used to iterate through all the rows,\nas shown in the example above. The column to search may be\nspecified by either its ID or title.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add another goat\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Scape Goat');\n newRow.setString('name', 'Goat');\n\n //find the rows containing animals named Goat\n let rows = table.findRows('Goat', 'name');\n print(rows.length + ' Goats found');\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "value", + "description": "The value to match", + "type": "String" + }, + { + "name": "column", + "description": "ID number or title of the\ncolumn to search", + "type": "Integer|String" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "matchRow", + "file": "src/io/p5.Table.js", + "line": 408, + "itemtype": "method", + "description": "Finds the first row in the Table that matches the regular\nexpression provided, and returns a reference to that row.\nEven if multiple rows are possible matches, only the first\nmatching row is returned. The column to search may be\nspecified by either its ID or title.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //Search using specified regex on a given column, return TableRow object\n let mammal = table.matchRow(new RegExp('ant'), 1);\n print(mammal.getString(1));\n //Output \"Panthera pardus\"\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "type": "String|Integer" + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + } + } + ], + "return": { + "description": "TableRow object", + "type": "p5.TableRow" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "matchRows", + "file": "src/io/p5.Table.js", + "line": 473, + "itemtype": "method", + "description": "Finds the rows in the Table that match the regular expression provided,\nand returns references to those rows. Returns an array, so for must be\nused to iterate through all the rows, as shown in the example. The\ncolumn to search may be specified by either its ID or title.", + "example": [ + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
", + "
\n\nlet table;\n\nfunction setup() {\n table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', 'Lion');\n newRow.setString('type', 'Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', 'Snake');\n newRow.setString('type', 'Reptile');\n\n newRow = table.addRow();\n newRow.setString('name', 'Mosquito');\n newRow.setString('type', 'Insect');\n\n newRow = table.addRow();\n newRow.setString('name', 'Lizard');\n newRow.setString('type', 'Reptile');\n\n let rows = table.matchRows('R.*', 'type');\n for (let i = 0; i < rows.length; i++) {\n print(rows[i].getString('name') + ': ' + rows[i].getString('type'));\n }\n}\n// Sketch prints:\n// Snake: Reptile\n// Lizard: Reptile\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + }, + { + "params": [ + { + "name": "regexp", + "description": "The regular expression to match", + "type": "String" + }, + { + "name": "column", + "description": "The column ID (number) or\ntitle (string)", + "optional": 1, + "type": "String|Integer" + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + } + } + ], + "return": { + "description": "An Array of TableRow objects", + "type": "p5.TableRow[]" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "getColumn", + "file": "src/io/p5.Table.js", + "line": 526, + "itemtype": "method", + "description": "Retrieves all values in the specified column, and returns them\nas an array. The column may be specified by either its ID or title.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //getColumn returns an array that can be printed directly\n print(table.getColumn('species'));\n //outputs [\"Capra hircus\", \"Panthera pardus\", \"Equus zebra\"]\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + }, + { + "params": [ + { + "name": "column", + "description": "String or Number of the column to return", + "type": "String|Number" + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + } + } + ], + "return": { + "description": "Array of column values", + "type": "Array" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "clearRows", + "file": "src/io/p5.Table.js", + "line": 572, + "itemtype": "method", + "description": "Removes all rows from a Table. While all rows are removed,\ncolumns and column titles are maintained.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.clearRows();\n print(table.getRowCount() + ' total rows in table');\n print(table.getColumnCount() + ' total columns in table');\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + }, + { + "params": [] + } + ], + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "addColumn", + "file": "src/io/p5.Table.js", + "line": 620, + "itemtype": "method", + "description": "Use addColumn() to add a new column to a Table object.\nTypically, you will want to specify a title, so the column\nmay be easily referenced later by name. (If no title is\nspecified, the new column's title will be null.)", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.addColumn('carnivore');\n table.set(0, 'carnivore', 'no');\n table.set(1, 'carnivore', 'yes');\n table.set(2, 'carnivore', 'no');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "title", + "description": "title of the given column", + "optional": 1, + "type": "String" + } + ] + } + ], + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "getColumnCount", + "file": "src/io/p5.Table.js", + "line": 656, + "itemtype": "method", + "description": "Returns the total number of columns in a Table.", + "example": [ + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n let numOfColumn = table.getColumnCount();\n text('There are ' + numOfColumn + ' columns in the table.', 100, 50);\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + } + } + ], + "return": { + "description": "Number of columns in this table", + "type": "Integer" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "getRowCount", + "file": "src/io/p5.Table.js", + "line": 691, + "itemtype": "method", + "description": "Returns the total number of rows in a Table.", + "example": [ + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
", + "
\n\n// given the cvs file \"blobs.csv\" in /assets directory\n//\n// ID, Name, Flavor, Shape, Color\n// Blob1, Blobby, Sweet, Blob, Pink\n// Blob2, Saddy, Savory, Blob, Blue\n\nlet table;\n\nfunction preload() {\n table = loadTable('assets/blobs.csv');\n}\n\nfunction setup() {\n createCanvas(200, 100);\n textAlign(CENTER);\n background(255);\n}\n\nfunction draw() {\n text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + }, + { + "params": [], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + } + } + ], + "return": { + "description": "Number of rows in this table", + "type": "Integer" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "removeTokens", + "file": "src/io/p5.Table.js", + "line": 731, + "itemtype": "method", + "description": "

Removes any of the specified characters (or \"tokens\").

\n

If no column is specified, then the values in all columns and\nrows are processed. A specific column may be referenced by\neither its ID or title.

\n", + "example": [ + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' $Lion ,');\n newRow.setString('type', ',,,Mammal');\n\n newRow = table.addRow();\n newRow.setString('name', '$Snake ');\n newRow.setString('type', ',,,Reptile');\n\n table.removeTokens(',$ ');\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "chars", + "description": "String listing characters to be removed", + "type": "String" + }, + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + } + ], + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "trim", + "file": "src/io/p5.Table.js", + "line": 799, + "itemtype": "method", + "description": "Trims leading and trailing whitespace, such as spaces and tabs,\nfrom String table values. If no column is specified, then the\nvalues in all columns and rows are trimmed. A specific column\nmay be referenced by either its ID or title.", + "example": [ + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
", + "
\nfunction setup() {\n let table = new p5.Table();\n\n table.addColumn('name');\n table.addColumn('type');\n\n let newRow = table.addRow();\n newRow.setString('name', ' Lion ,');\n newRow.setString('type', ' Mammal ');\n\n newRow = table.addRow();\n newRow.setString('name', ' Snake ');\n newRow.setString('type', ' Reptile ');\n\n table.trim();\n print(table.getArray());\n}\n\n// prints:\n// 0 \"Lion\" \"Mamal\"\n// 1 \"Snake\" \"Reptile\"\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "Column ID (number)\nor name (string)", + "optional": 1, + "type": "String|Integer" + } + ] + } + ], + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "removeColumn", + "file": "src/io/p5.Table.js", + "line": 865, + "itemtype": "method", + "description": "Use removeColumn() to remove an existing column from a Table\nobject. The column to be removed may be identified by either\nits title (a String) or its index value (an int).\nremoveColumn(0) would remove the first column, removeColumn(1)\nwould remove the second column, and so on.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.removeColumn('id');\n print(table.getColumnCount());\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + }, + { + "params": [ + { + "name": "column", + "description": "columnName (string) or ID (number)", + "type": "String|Integer" + } + ] + } + ], + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "set", + "file": "src/io/p5.Table.js", + "line": 933, + "itemtype": "method", + "description": "Stores a value in the Table's specified row and column.\nThe row is specified by its ID, while the column may be specified\nby either its ID or title.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.set(0, 'species', 'Canis Lupus');\n table.set(0, 'name', 'Wolf');\n\n //print the results\n for (let r = 0; r < table.getRowCount(); r++)\n for (let c = 0; c < table.getColumnCount(); c++)\n print(table.getString(r, c));\n\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String|Number" + } + ] + } + ], + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "setNum", + "file": "src/io/p5.Table.js", + "line": 977, + "itemtype": "method", + "description": "Stores a Float value in the Table's specified row and column.\nThe row is specified by its ID, while the column may be specified\nby either its ID or title.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n table.setNum(1, 'id', 1);\n\n print(table.getColumn(0));\n //[\"0\", 1, \"2\"]\n\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "Number" + } + ] + } + ], + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "setString", + "file": "src/io/p5.Table.js", + "line": 1020, + "itemtype": "method", + "description": "Stores a String value in the Table's specified row and column.\nThe row is specified by its ID, while the column may be specified\nby either its ID or title.", + "example": [ + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
", + "
\n// Given the CSV file \"mammals.csv\" in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n //add a row\n let newRow = table.addRow();\n newRow.setString('id', table.getRowCount() - 1);\n newRow.setString('species', 'Canis Lupus');\n newRow.setString('name', 'Wolf');\n\n print(table.getArray());\n\n describe('no image displayed');\n}\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "column ID (Number)\nor title (String)", + "type": "String|Integer" + }, + { + "name": "value", + "description": "value to assign", + "type": "String" + } + ] + } + ], + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "get", + "file": "src/io/p5.Table.js", + "line": 1063, + "itemtype": "method", + "description": "Retrieves a value from the Table's specified row and column.\nThe row is specified by its ID, while the column may be specified by\neither its ID or title.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.get(0, 1));\n //Capra hircus\n print(table.get(0, 'species'));\n //Capra hircus\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String|Number" + } + } + ], + "return": { + "description": "", + "type": "String|Number" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "getNum", + "file": "src/io/p5.Table.js", + "line": 1104, + "itemtype": "method", + "description": "Retrieves a Float value from the Table's specified row and column.\nThe row is specified by its ID, while the column may be specified by\neither its ID or title.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getNum(1, 0) + 100);\n //id 1 + 100 = 101\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "Number" + } + } + ], + "return": { + "description": "", + "type": "Number" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "getString", + "file": "src/io/p5.Table.js", + "line": 1153, + "itemtype": "method", + "description": "Retrieves a String value from the Table's specified row and column.\nThe row is specified by its ID, while the column may be specified by\neither its ID or title.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n print(table.getString(0, 0)); // 0\n print(table.getString(0, 1)); // Capra hircus\n print(table.getString(0, 2)); // Goat\n print(table.getString(1, 0)); // 1\n print(table.getString(1, 1)); // Panthera pardus\n print(table.getString(1, 2)); // Leopard\n print(table.getString(2, 0)); // 2\n print(table.getString(2, 1)); // Equus zebra\n print(table.getString(2, 2)); // Zebra\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + }, + { + "params": [ + { + "name": "row", + "description": "row ID", + "type": "Integer" + }, + { + "name": "column", + "description": "columnName (string) or\nID (number)", + "type": "String|Integer" + } + ], + "return": { + "description": "", + "type": "String" + } + } + ], + "return": { + "description": "", + "type": "String" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "getObject", + "file": "src/io/p5.Table.js", + "line": 1196, + "itemtype": "method", + "description": "Retrieves all table data and returns as an object. If a column name is\npassed in, each row object will be stored with that attribute as its\ntitle.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder:\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leopard\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n //my table is comma separated value \"csv\"\n //and has a header specifying the columns labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableObject = table.getObject();\n\n print(tableObject);\n //outputs an object\n\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + }, + { + "params": [ + { + "name": "headerColumn", + "description": "Name of the column which should be used to\ntitle each row object (optional)", + "optional": 1, + "type": "String" + } + ], + "return": { + "description": "", + "type": "Object" + } + } + ], + "return": { + "description": "", + "type": "Object" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + }, + { + "name": "getArray", + "file": "src/io/p5.Table.js", + "line": 1252, + "itemtype": "method", + "description": "Retrieves all table data and returns it as a multidimensional array.", + "example": [ + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
", + "
\n\n// Given the CSV file \"mammals.csv\"\n// in the project's \"assets\" folder\n//\n// id,species,name\n// 0,Capra hircus,Goat\n// 1,Panthera pardus,Leoperd\n// 2,Equus zebra,Zebra\n\nlet table;\n\nfunction preload() {\n // table is comma separated value \"CSV\"\n // and has specifiying header for column labels\n table = loadTable('assets/mammals.csv', 'csv', 'header');\n}\n\nfunction setup() {\n let tableArray = table.getArray();\n for (let i = 0; i < tableArray.length; i++) {\n print(tableArray[i]);\n }\n describe('no image displayed');\n}\n\n
" + ], + "overloads": [ + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + }, + { + "params": [], + "return": { + "description": "", + "type": "Array" + } + } + ], + "return": { + "description": "", + "type": "Array" + }, + "class": "p5.Table", + "static": false, + "module": "IO", + "submodule": "Table" + } + ], + "warnings": [], + "consts": { + "namedColors": [], + "WHITESPACE": [], + "colorPatterns": [], + "VERSION": [], + "P2D": [], + "WEBGL": [], + "WEBGL2": [], + "ARROW": [], + "CROSS": [], + "HAND": [], + "MOVE": [], + "TEXT": [], + "WAIT": [], + "HALF_PI": [], + "PI": [], + "QUARTER_PI": [], + "TAU": [], + "TWO_PI": [], + "DEGREES": [], + "RADIANS": [], + "CORNER": [], + "CORNERS": [], + "RADIUS": [], + "RIGHT": [], + "LEFT": [], + "CENTER": [], + "TOP": [], + "BOTTOM": [], + "BASELINE": [], + "POINTS": [ + "p5.beginShape" + ], + "LINES": [ + "p5.beginShape" + ], + "LINE_STRIP": [], + "LINE_LOOP": [], + "TRIANGLES": [ + "p5.beginShape" + ], + "TRIANGLE_FAN": [ + "p5.beginShape" + ], + "TRIANGLE_STRIP": [ + "p5.beginShape" + ], + "QUADS": [ + "p5.beginShape" + ], + "QUAD_STRIP": [ + "p5.beginShape" + ], + "TESS": [ + "p5.beginShape" + ], + "CLOSE": [ + "p5.endShape" + ], + "OPEN": [], + "CHORD": [], + "PIE": [], + "PROJECT": [], + "SQUARE": [], + "ROUND": [], + "BEVEL": [], + "MITER": [], + "RGB": [], + "HSB": [], + "HSL": [], + "AUTO": [], + "ALT": [], + "BACKSPACE": [], + "CONTROL": [], + "DELETE": [], + "DOWN_ARROW": [], + "ENTER": [], + "ESCAPE": [], + "LEFT_ARROW": [], + "OPTION": [], + "RETURN": [], + "RIGHT_ARROW": [], + "SHIFT": [], + "TAB": [], + "UP_ARROW": [], + "BLEND": [], + "REMOVE": [], + "ADD": [], + "DARKEST": [], + "LIGHTEST": [], + "DIFFERENCE": [], + "SUBTRACT": [], + "EXCLUSION": [], + "MULTIPLY": [], + "SCREEN": [], + "REPLACE": [], + "OVERLAY": [], + "HARD_LIGHT": [], + "SOFT_LIGHT": [], + "DODGE": [], + "BURN": [], + "THRESHOLD": [], + "GRAY": [], + "OPAQUE": [], + "INVERT": [], + "POSTERIZE": [], + "DILATE": [], + "ERODE": [], + "BLUR": [], + "NORMAL": [], + "ITALIC": [], + "BOLD": [], + "BOLDITALIC": [], + "CHAR": [], + "WORD": [], + "LINEAR": [], + "QUADRATIC": [], + "BEZIER": [], + "CURVE": [], + "STROKE": [], + "FILL": [], + "TEXTURE": [], + "IMMEDIATE": [], + "IMAGE": [], + "NEAREST": [], + "REPEAT": [], + "CLAMP": [], + "MIRROR": [], + "FLAT": [], + "SMOOTH": [], + "LANDSCAPE": [], + "PORTRAIT": [], + "GRID": [], + "AXES": [], + "LABEL": [], + "FALLBACK": [], + "CONTAIN": [], + "COVER": [], + "UNSIGNED_BYTE": [], + "UNSIGNED_INT": [], + "FLOAT": [], + "HALF_FLOAT": [], + "RGBA": [], + "initialize": [], + "availableTranslatorLanguages": [], + "currentTranslatorLanguage": [], + "setTranslatorLanguage": [], + "languages": [], + "styleEmpty": [], + "Filters": [] + } +} \ No newline at end of file diff --git a/docs/documented-method.js b/docs/documented-method.js deleted file mode 100644 index 32d38dd55c..0000000000 --- a/docs/documented-method.js +++ /dev/null @@ -1,60 +0,0 @@ -// https://github.com/umdjs/umd/blob/main/templates/returnExports.js -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define([], factory); - } else if (typeof module === 'object' && module.exports) { - module.exports = factory(); - } else { - root.DocumentedMethod = factory(); - } -}(this, function () { - function extend(target, src) { - Object.keys(src).forEach(function(prop) { - target[prop] = src[prop]; - }); - return target; - } - - function DocumentedMethod(classitem) { - extend(this, classitem); - - if (this.overloads) { - // Make each overload inherit properties from their parent - // classitem. - this.overloads = this.overloads.map(function(overload) { - return extend(Object.create(this), overload); - }, this); - - if (this.params) { - throw new Error('params for overloaded methods should be undefined'); - } - - this.params = this._getMergedParams(); - } - } - - DocumentedMethod.prototype = { - // Merge parameters across all overloaded versions of this item. - _getMergedParams: function() { - const paramNames = {}; - const params = []; - - this.overloads.forEach(function(overload) { - if (!overload.params) { - return; - } - overload.params.forEach(function(param) { - if (param.name in paramNames) { - return; - } - paramNames[param.name] = param; - params.push(param); - }); - }); - - return params; - } - }; - - return DocumentedMethod; -})); diff --git a/docs/parameterData.json b/docs/parameterData.json new file mode 100644 index 0000000000..dfa2e306ff --- /dev/null +++ b/docs/parameterData.json @@ -0,0 +1,4835 @@ +{ + "p5": { + "describe": { + "overloads": [ + [ + "String", + "FALLBACK|LABEL?" + ] + ] + }, + "describeElement": { + "overloads": [ + [ + "String", + "String", + "FALLBACK|LABEL?" + ] + ] + }, + "textOutput": { + "overloads": [ + [ + "FALLBACK|LABEL?" + ] + ] + }, + "gridOutput": { + "overloads": [ + [ + "FALLBACK|LABEL?" + ] + ] + }, + "p5": { + "overloads": [ + [ + "Object", + "String|HTMLElement" + ] + ] + }, + "color": { + "overloads": [ + [ + "Number", + "Number?" + ], + [ + "Number", + "Number", + "Number", + "Number?" + ], + [ + "String" + ], + [ + "Number[]" + ], + [ + "p5.Color" + ] + ] + }, + "red": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, + "green": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, + "blue": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, + "alpha": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, + "hue": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, + "saturation": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, + "brightness": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, + "lightness": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, + "lerpColor": { + "overloads": [ + [ + "p5.Color", + "p5.Color", + "Number" + ] + ] + }, + "paletteLerp": { + "overloads": [ + [ + null, + "Number" + ] + ] + }, + "beginClip": { + "overloads": [ + [ + "Object?" + ] + ] + }, + "endClip": { + "overloads": [ + [] + ] + }, + "clip": { + "overloads": [ + [ + "Function", + "Object?" + ] + ] + }, + "background": { + "overloads": [ + [ + "p5.Color" + ], + [ + "String", + "Number?" + ], + [ + "Number", + "Number?" + ], + [ + "Number", + "Number", + "Number", + "Number?" + ], + [ + "Number[]" + ], + [ + "p5.Image", + "Number?" + ] + ] + }, + "clear": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Number?" + ], + [] + ] + }, + "colorMode": { + "overloads": [ + [ + "RGB|HSB|HSL", + "Number?" + ], + [ + "RGB|HSB|HSL", + "Number", + "Number", + "Number", + "Number?" + ] + ] + }, + "fill": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number?" + ], + [ + "String" + ], + [ + "Number", + "Number?" + ], + [ + "Number[]" + ], + [ + "p5.Color" + ] + ] + }, + "noFill": { + "overloads": [ + [] + ] + }, + "noStroke": { + "overloads": [ + [] + ] + }, + "stroke": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number?" + ], + [ + "String" + ], + [ + "Number", + "Number?" + ], + [ + "Number[]" + ], + [ + "p5.Color" + ] + ] + }, + "erase": { + "overloads": [ + [ + "Number?", + "Number?" + ] + ] + }, + "noErase": { + "overloads": [ + [] + ] + }, + "blendMode": { + "overloads": [ + [ + "BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|REMOVE|SUBTRACT" + ] + ] + }, + "print": { + "overloads": [ + [ + "Any" + ], + [ + "String|Number|Array" + ] + ] + }, + "cursor": { + "overloads": [ + [ + "ARROW|CROSS|HAND|MOVE|TEXT|WAIT|String", + "Number?", + "Number?" + ] + ] + }, + "frameRate": { + "overloads": [ + [ + "Number" + ], + [] + ] + }, + "getTargetFrameRate": { + "overloads": [ + [] + ] + }, + "noCursor": { + "overloads": [ + [] + ] + }, + "windowResized": { + "overloads": [ + [ + "UIEvent?" + ] + ] + }, + "fullscreen": { + "overloads": [ + [ + "Boolean?" + ] + ] + }, + "pixelDensity": { + "overloads": [ + [ + "Number?" + ], + [] + ] + }, + "displayDensity": { + "overloads": [ + [] + ] + }, + "getURL": { + "overloads": [ + [] + ] + }, + "getURLPath": { + "overloads": [ + [] + ] + }, + "getURLParams": { + "overloads": [ + [] + ] + }, + "worldToScreen": { + "overloads": [ + [ + "p5.Vector" + ] + ] + }, + "sketchVerifier": { + "overloads": [ + [] + ] + }, + "setup": { + "overloads": [ + [] + ] + }, + "draw": { + "overloads": [ + [] + ] + }, + "calculateOffset": { + "overloads": [ + [] + ] + }, + "preload": { + "overloads": [ + [] + ] + }, + "createCanvas": { + "overloads": [ + [ + "Number?", + "Number?", + "P2D|WEBGL?", + "HTMLCanvasElement?" + ], + [ + "Number?", + "Number?", + "HTMLCanvasElement?" + ] + ] + }, + "resizeCanvas": { + "overloads": [ + [ + "Number", + "Number", + "Boolean?" + ] + ] + }, + "noCanvas": { + "overloads": [ + [] + ] + }, + "createGraphics": { + "overloads": [ + [ + "Number", + "Number", + "P2D|WEBGL?", + "HTMLCanvasElement?" + ], + [ + "Number", + "Number", + "HTMLCanvasElement?" + ] + ] + }, + "createFramebuffer": { + "overloads": [ + [ + "Object?" + ] + ] + }, + "clearDepth": { + "overloads": [ + [ + "Number?" + ] + ] + }, + "noLoop": { + "overloads": [ + [] + ] + }, + "loop": { + "overloads": [ + [] + ] + }, + "isLooping": { + "overloads": [ + [] + ] + }, + "redraw": { + "overloads": [ + [ + "Integer?" + ] + ] + }, + "applyMatrix": { + "overloads": [ + [ + "Array" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "resetMatrix": { + "overloads": [ + [] + ] + }, + "rotate": { + "overloads": [ + [ + "Number", + "p5.Vector|Number[]?" + ] + ] + }, + "rotateX": { + "overloads": [ + [ + "Number" + ] + ] + }, + "rotateY": { + "overloads": [ + [ + "Number" + ] + ] + }, + "rotateZ": { + "overloads": [ + [ + "Number" + ] + ] + }, + "scale": { + "overloads": [ + [ + "Number|p5.Vector|Number[]", + "Number?", + "Number?" + ], + [ + "p5.Vector|Number[]" + ] + ] + }, + "shearX": { + "overloads": [ + [ + "Number" + ] + ] + }, + "shearY": { + "overloads": [ + [ + "Number" + ] + ] + }, + "translate": { + "overloads": [ + [ + "Number", + "Number", + "Number?" + ], + [ + "p5.Vector" + ] + ] + }, + "push": { + "overloads": [ + [] + ] + }, + "pop": { + "overloads": [ + [] + ] + }, + "storeItem": { + "overloads": [ + [ + "String", + "String|Number|Boolean|Object|Array" + ] + ] + }, + "getItem": { + "overloads": [ + [ + "String" + ] + ] + }, + "clearStorage": { + "overloads": [ + [] + ] + }, + "removeItem": { + "overloads": [ + [ + "String" + ] + ] + }, + "createStringDict": { + "overloads": [ + [ + "String", + "String" + ], + [ + "Object" + ] + ] + }, + "createNumberDict": { + "overloads": [ + [ + "Number", + "Number" + ], + [ + "Object" + ] + ] + }, + "select": { + "overloads": [ + [ + "String", + "String|p5.Element|HTMLElement?" + ] + ] + }, + "selectAll": { + "overloads": [ + [ + "String", + "String|p5.Element|HTMLElement?" + ] + ] + }, + "createElement": { + "overloads": [ + [ + "String", + "String?" + ] + ] + }, + "removeElements": { + "overloads": [ + [] + ] + }, + "addElement": { + "overloads": [ + [] + ] + }, + "createDiv": { + "overloads": [ + [ + "String?" + ] + ] + }, + "createP": { + "overloads": [ + [ + "String?" + ] + ] + }, + "createSpan": { + "overloads": [ + [ + "String?" + ] + ] + }, + "createImg": { + "overloads": [ + [ + "String", + "String" + ], + [ + "String", + "String", + "String?", + "Function?" + ] + ] + }, + "createA": { + "overloads": [ + [ + "String", + "String", + "String?" + ] + ] + }, + "createSlider": { + "overloads": [ + [ + "Number", + "Number", + "Number?", + "Number?" + ] + ] + }, + "createButton": { + "overloads": [ + [ + "String", + "String?" + ] + ] + }, + "createCheckbox": { + "overloads": [ + [ + "String?", + "Boolean?" + ] + ] + }, + "createSelect": { + "overloads": [ + [ + "Boolean?" + ], + [ + "Object" + ] + ] + }, + "createRadio": { + "overloads": [ + [ + "Object?" + ], + [ + "String?" + ], + [] + ] + }, + "createColorPicker": { + "overloads": [ + [ + "String|p5.Color?" + ] + ] + }, + "createInput": { + "overloads": [ + [ + "String?", + "String?" + ], + [ + "String?" + ] + ] + }, + "createFileInput": { + "overloads": [ + [ + "Function", + "Boolean?" + ] + ] + }, + "setMoveThreshold": { + "overloads": [ + [ + "Number" + ] + ] + }, + "setShakeThreshold": { + "overloads": [ + [ + "Number" + ] + ] + }, + "deviceMoved": { + "overloads": [ + [] + ] + }, + "deviceTurned": { + "overloads": [ + [] + ] + }, + "deviceShaken": { + "overloads": [ + [] + ] + }, + "keyPressed": { + "overloads": [ + [ + "KeyboardEvent?" + ] + ] + }, + "keyReleased": { + "overloads": [ + [ + "KeyboardEvent?" + ] + ] + }, + "keyTyped": { + "overloads": [ + [ + "KeyboardEvent?" + ] + ] + }, + "keyIsDown": { + "overloads": [ + [ + "Number" + ] + ] + }, + "mouseMoved": { + "overloads": [ + [ + "MouseEvent?" + ] + ] + }, + "mouseDragged": { + "overloads": [ + [ + "MouseEvent?" + ] + ] + }, + "mousePressed": { + "overloads": [ + [ + "MouseEvent?" + ] + ] + }, + "mouseReleased": { + "overloads": [ + [ + "MouseEvent?" + ] + ] + }, + "mouseClicked": { + "overloads": [ + [ + "MouseEvent?" + ] + ] + }, + "doubleClicked": { + "overloads": [ + [ + "MouseEvent?" + ] + ] + }, + "mouseWheel": { + "overloads": [ + [ + "WheelEvent?" + ] + ] + }, + "requestPointerLock": { + "overloads": [ + [] + ] + }, + "exitPointerLock": { + "overloads": [ + [] + ] + }, + "createImage": { + "overloads": [ + [ + "Integer", + "Integer" + ] + ] + }, + "saveCanvas": { + "overloads": [ + [ + "p5.Framebuffer|p5.Element|HTMLCanvasElement", + "String?", + "String?" + ], + [ + "String?", + "String?" + ] + ] + }, + "saveFrames": { + "overloads": [ + [ + "String", + "String", + "Number", + "Number", + "function(Array)?" + ] + ] + }, + "loadImage": { + "overloads": [ + [ + "String|Request", + "function(p5.Image)?", + "function(Event)?" + ] + ] + }, + "saveGif": { + "overloads": [ + [ + "String", + "Number", + "Object?" + ] + ] + }, + "image": { + "overloads": [ + [ + "p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture", + "Number", + "Number", + "Number?", + "Number?" + ], + [ + "p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number?", + "Number?", + "CONTAIN|COVER?", + "LEFT|RIGHT|CENTER?", + "TOP|BOTTOM|CENTER?" + ] + ] + }, + "tint": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number?" + ], + [ + "String" + ], + [ + "Number", + "Number?" + ], + [ + "Number[]" + ], + [ + "p5.Color" + ] + ] + }, + "noTint": { + "overloads": [ + [] + ] + }, + "imageMode": { + "overloads": [ + [ + "CORNER|CORNERS|CENTER" + ] + ] + }, + "blend": { + "overloads": [ + [ + "p5.Image", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL" + ], + [ + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL" + ] + ] + }, + "copy": { + "overloads": [ + [ + "p5.Image|p5.Element", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer" + ], + [ + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer" + ] + ] + }, + "filter": { + "overloads": [ + [ + "THRESHOLD|GRAY|OPAQUE|INVERT|POSTERIZE|BLUR|ERODE|DILATE|BLUR", + "Number?", + "Boolean?" + ], + [ + "p5.Shader" + ] + ] + }, + "get": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number" + ], + [], + [ + "Number", + "Number" + ], + [ + "String|Integer" + ] + ] + }, + "loadPixels": { + "overloads": [ + [] + ] + }, + "set": { + "overloads": [ + [ + "Number", + "Number", + "Number|Number[]|Object" + ], + [ + "String|Integer", + "String|Number" + ] + ] + }, + "updatePixels": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Number?" + ], + [] + ] + }, + "loadJSON": { + "overloads": [ + [ + "String|Request", + "Function?", + "Function?" + ] + ] + }, + "loadStrings": { + "overloads": [ + [ + "String|Request", + "Function?", + "Function?" + ] + ] + }, + "loadTable": { + "overloads": [ + [ + "String|Request", + "String?", + "String?", + "Function?", + "Function?" + ] + ] + }, + "loadXML": { + "overloads": [ + [ + "String|Request", + "Function?", + "Function?" + ] + ] + }, + "loadBytes": { + "overloads": [ + [ + "String|Request", + "Function?", + "Function?" + ] + ] + }, + "httpGet": { + "overloads": [ + [ + "String|Request", + "String?", + "Function?", + "Function?" + ], + [ + "String|Request", + "Function", + "Function?" + ] + ] + }, + "httpPost": { + "overloads": [ + [ + "String|Request", + "Object|Boolean?", + "String?", + "Function?", + "Function?" + ], + [ + "String|Request", + "Object|Boolean", + "Function?", + "Function?" + ], + [ + "String|Request", + "Function?", + "Function?" + ] + ] + }, + "httpDo": { + "overloads": [ + [ + "String|Request", + "String?", + "String?", + "Object?", + "Function?", + "Function?" + ], + [ + "String|Request", + "Function?", + "Function?" + ] + ] + }, + "createWriter": { + "overloads": [ + [ + "String", + "String?" + ] + ] + }, + "write": { + "overloads": [ + [ + "String|Number|Array" + ] + ] + }, + "close": { + "overloads": [ + [] + ] + }, + "save": { + "overloads": [ + [ + "Object|String?", + "String?", + "Boolean|String?" + ] + ] + }, + "saveJSON": { + "overloads": [ + [ + "Array|Object", + "String", + "Boolean?" + ] + ] + }, + "saveStrings": { + "overloads": [ + [ + "String[]", + "String", + "String?", + "Boolean?" + ] + ] + }, + "saveTable": { + "overloads": [ + [ + "p5.Table", + "String", + "String?" + ] + ] + }, + "abs": { + "overloads": [ + [ + "Number" + ] + ] + }, + "ceil": { + "overloads": [ + [ + "Number" + ] + ] + }, + "constrain": { + "overloads": [ + [ + "Number", + "Number", + "Number" + ] + ] + }, + "dist": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "exp": { + "overloads": [ + [ + "Number" + ] + ] + }, + "floor": { + "overloads": [ + [ + "Number" + ] + ] + }, + "lerp": { + "overloads": [ + [ + "Number", + "Number", + "Number" + ] + ] + }, + "log": { + "overloads": [ + [ + "Number" + ] + ] + }, + "mag": { + "overloads": [ + [ + "Number", + "Number" + ] + ] + }, + "map": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Boolean?" + ] + ] + }, + "max": { + "overloads": [ + [ + "Number", + "Number" + ], + [ + "Number[]" + ] + ] + }, + "min": { + "overloads": [ + [ + "Number", + "Number" + ], + [ + "Number[]" + ] + ] + }, + "norm": { + "overloads": [ + [ + "Number", + "Number", + "Number" + ] + ] + }, + "pow": { + "overloads": [ + [ + "Number", + "Number" + ] + ] + }, + "round": { + "overloads": [ + [ + "Number", + "Number?" + ] + ] + }, + "sq": { + "overloads": [ + [ + "Number" + ] + ] + }, + "sqrt": { + "overloads": [ + [ + "Number" + ] + ] + }, + "fract": { + "overloads": [ + [ + "Number" + ] + ] + }, + "createVector": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?" + ] + ] + }, + "createMatrix": { + "overloads": [ + [] + ] + }, + "noise": { + "overloads": [ + [ + "Number", + "Number?", + "Number?" + ] + ] + }, + "noiseDetail": { + "overloads": [ + [ + "Number", + "Number" + ] + ] + }, + "noiseSeed": { + "overloads": [ + [ + "Number" + ] + ] + }, + "randomSeed": { + "overloads": [ + [ + "Number" + ] + ] + }, + "random": { + "overloads": [ + [ + "Number?", + "Number?" + ], + [ + "Array" + ] + ] + }, + "randomGaussian": { + "overloads": [ + [ + "Number?", + "Number?" + ] + ] + }, + "acos": { + "overloads": [ + [ + "Number" + ] + ] + }, + "asin": { + "overloads": [ + [ + "Number" + ] + ] + }, + "atan": { + "overloads": [ + [ + "Number" + ] + ] + }, + "atan2": { + "overloads": [ + [ + "Number", + "Number" + ] + ] + }, + "cos": { + "overloads": [ + [ + "Number" + ] + ] + }, + "sin": { + "overloads": [ + [ + "Number" + ] + ] + }, + "tan": { + "overloads": [ + [ + "Number" + ] + ] + }, + "degrees": { + "overloads": [ + [ + "Number" + ] + ] + }, + "radians": { + "overloads": [ + [ + "Number" + ] + ] + }, + "angleMode": { + "overloads": [ + [ + "RADIANS|DEGREES" + ], + [] + ] + }, + "arc": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "CHORD|PIE|OPEN?", + "Integer?" + ] + ] + }, + "ellipse": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number?" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Integer?" + ] + ] + }, + "circle": { + "overloads": [ + [ + "Number", + "Number", + "Number" + ] + ] + }, + "line": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "point": { + "overloads": [ + [ + "Number", + "Number", + "Number?" + ], + [ + "p5.Vector" + ] + ] + }, + "quad": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Integer?", + "Integer?" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Integer?", + "Integer?" + ] + ] + }, + "rect": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Integer?", + "Integer?" + ] + ] + }, + "square": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number?", + "Number?", + "Number?", + "Number?" + ] + ] + }, + "triangle": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "ellipseMode": { + "overloads": [ + [ + "CENTER|RADIUS|CORNER|CORNERS" + ] + ] + }, + "noSmooth": { + "overloads": [ + [] + ] + }, + "rectMode": { + "overloads": [ + [ + "CENTER|RADIUS|CORNER|CORNERS" + ] + ] + }, + "smooth": { + "overloads": [ + [] + ] + }, + "strokeCap": { + "overloads": [ + [ + "ROUND|SQUARE|PROJECT" + ] + ] + }, + "strokeJoin": { + "overloads": [ + [ + "MITER|BEVEL|ROUND" + ] + ] + }, + "strokeWeight": { + "overloads": [ + [ + "Number" + ] + ] + }, + "bezier": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "bezierDetail": { + "overloads": [ + [ + "Number" + ] + ] + }, + "bezierPoint": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "bezierTangent": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "curve": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "curvePoint": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "curveTangent": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "vertex": { + "overloads": [ + [ + "Number", + "Number" + ], + [ + "Number", + "Number", + "Number?" + ], + [ + "Number", + "Number", + "Number?", + "Number?", + "Number?" + ] + ] + }, + "beginContour": { + "overloads": [ + [] + ] + }, + "endContour": { + "overloads": [ + [ + "OPEN|CLOSE?" + ] + ] + }, + "beginShape": { + "overloads": [ + [ + "POINTS|LINES|TRIANGLES|TRIANGLE_FAN|TRIANGLE_STRIP|QUADS|QUAD_STRIP|PATH?" + ] + ] + }, + "bezierVertex": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "curveVertex": { + "overloads": [ + [ + "Number", + "Number" + ], + [ + "Number", + "Number", + "Number?" + ] + ] + }, + "endShape": { + "overloads": [ + [ + "CLOSE?", + "Integer?" + ] + ] + }, + "quadraticVertex": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ] + ] + }, + "normal": { + "overloads": [ + [ + "p5.Vector" + ], + [ + "Number", + "Number", + "Number" + ] + ] + }, + "vertexProperty": { + "overloads": [ + [ + "String", + "Number|Number[]" + ], + [ + "String", + "Number|Number[]", + "Number?" + ] + ] + }, + "append": { + "overloads": [ + [ + "Array", + "Any" + ] + ] + }, + "arrayCopy": { + "overloads": [ + [ + "Array", + "Integer", + "Array", + "Integer", + "Integer" + ], + [ + "Array", + "Array", + "Integer?" + ] + ] + }, + "concat": { + "overloads": [ + [ + "Array", + "Array" + ] + ] + }, + "reverse": { + "overloads": [ + [ + "Array" + ] + ] + }, + "shorten": { + "overloads": [ + [ + "Array" + ] + ] + }, + "shuffle": { + "overloads": [ + [ + "Array", + "Boolean?" + ] + ] + }, + "sort": { + "overloads": [ + [ + "Array", + "Integer?" + ] + ] + }, + "splice": { + "overloads": [ + [ + "Array", + "Any", + "Integer" + ] + ] + }, + "subset": { + "overloads": [ + [ + "Array", + "Integer", + "Integer?" + ] + ] + }, + "float": { + "overloads": [ + [ + "String" + ], + [ + "String[]" + ] + ] + }, + "int": { + "overloads": [ + [ + "String|Boolean|Number" + ], + [ + "Array" + ] + ] + }, + "str": { + "overloads": [ + [ + "String|Boolean|Number" + ] + ] + }, + "boolean": { + "overloads": [ + [ + "String|Boolean|Number" + ], + [ + "Array" + ] + ] + }, + "byte": { + "overloads": [ + [ + "String|Boolean|Number" + ], + [ + "Array" + ] + ] + }, + "char": { + "overloads": [ + [ + "String|Number" + ], + [ + "Array" + ] + ] + }, + "unchar": { + "overloads": [ + [ + "String" + ], + [ + "String[]" + ] + ] + }, + "hex": { + "overloads": [ + [ + "Number", + "Number?" + ], + [ + "Number[]", + "Number?" + ] + ] + }, + "unhex": { + "overloads": [ + [ + "String" + ], + [ + "String[]" + ] + ] + }, + "join": { + "overloads": [ + [ + "Array", + "String" + ] + ] + }, + "match": { + "overloads": [ + [ + "String", + "String" + ] + ] + }, + "matchAll": { + "overloads": [ + [ + "String", + "String" + ] + ] + }, + "nf": { + "overloads": [ + [ + "Number|String", + "Integer|String?", + "Integer|String?" + ], + [ + "Number[]", + "Integer|String?", + "Integer|String?" + ] + ] + }, + "nfc": { + "overloads": [ + [ + "Number|String", + "Integer|String?" + ], + [ + "Number[]", + "Integer|String?" + ] + ] + }, + "nfp": { + "overloads": [ + [ + "Number", + "Integer?", + "Integer?" + ], + [ + "Number[]", + "Integer?", + "Integer?" + ] + ] + }, + "nfs": { + "overloads": [ + [ + "Number", + "Integer?", + "Integer?" + ], + [ + "Array", + "Integer?", + "Integer?" + ] + ] + }, + "split": { + "overloads": [ + [ + "String", + "String" + ] + ] + }, + "splitTokens": { + "overloads": [ + [ + "String", + "String?" + ] + ] + }, + "trim": { + "overloads": [ + [ + "String" + ], + [ + "String[]" + ] + ] + }, + "day": { + "overloads": [ + [] + ] + }, + "hour": { + "overloads": [ + [] + ] + }, + "minute": { + "overloads": [ + [] + ] + }, + "millis": { + "overloads": [ + [] + ] + }, + "month": { + "overloads": [ + [] + ] + }, + "second": { + "overloads": [ + [] + ] + }, + "year": { + "overloads": [ + [] + ] + }, + "strokeMode": { + "overloads": [ + [ + "string" + ] + ] + }, + "buildGeometry": { + "overloads": [ + [ + "Function" + ] + ] + }, + "freeGeometry": { + "overloads": [ + [ + "p5.Geometry" + ] + ] + }, + "plane": { + "overloads": [ + [ + "Number?", + "Number?", + "Integer?", + "Integer?" + ] + ] + }, + "box": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Integer?", + "Integer?" + ] + ] + }, + "sphere": { + "overloads": [ + [ + "Number?", + "Integer?", + "Integer?" + ] + ] + }, + "cylinder": { + "overloads": [ + [ + "Number?", + "Number?", + "Integer?", + "Integer?", + "Boolean?", + "Boolean?" + ] + ] + }, + "cone": { + "overloads": [ + [ + "Number?", + "Number?", + "Integer?", + "Integer?", + "Boolean?" + ] + ] + }, + "ellipsoid": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Integer?", + "Integer?" + ] + ] + }, + "torus": { + "overloads": [ + [ + "Number?", + "Number?", + "Integer?", + "Integer?" + ] + ] + }, + "curveDetail": { + "overloads": [ + [ + "Number" + ] + ] + }, + "orbitControl": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Object?" + ] + ] + }, + "debugMode": { + "overloads": [ + [], + [ + "GRID|AXES" + ], + [ + "GRID|AXES", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?" + ], + [ + "GRID|AXES", + "Number?", + "Number?", + "Number?", + "Number?" + ], + [ + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?" + ] + ] + }, + "noDebugMode": { + "overloads": [ + [] + ] + }, + "ambientLight": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number?" + ], + [ + "Number", + "Number?" + ], + [ + "String" + ], + [ + "Number[]" + ], + [ + "p5.Color" + ] + ] + }, + "specularColor": { + "overloads": [ + [ + "Number", + "Number", + "Number" + ], + [ + "Number" + ], + [ + "String" + ], + [ + "Number[]" + ], + [ + "p5.Color" + ] + ] + }, + "directionalLight": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ], + [ + "Number", + "Number", + "Number", + "p5.Vector" + ], + [ + "p5.Color|Number[]|String", + "Number", + "Number", + "Number" + ], + [ + "p5.Color|Number[]|String", + "p5.Vector" + ] + ] + }, + "pointLight": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number" + ], + [ + "Number", + "Number", + "Number", + "p5.Vector" + ], + [ + "p5.Color|Number[]|String", + "Number", + "Number", + "Number" + ], + [ + "p5.Color|Number[]|String", + "p5.Vector" + ] + ] + }, + "imageLight": { + "overloads": [ + [ + "p5.image" + ] + ] + }, + "panorama": { + "overloads": [ + [ + "p5.Image" + ] + ] + }, + "lights": { + "overloads": [ + [] + ] + }, + "lightFalloff": { + "overloads": [ + [ + "Number", + "Number", + "Number" + ] + ] + }, + "spotLight": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number?", + "Number?" + ], + [ + "p5.Color|Number[]|String", + "p5.Vector", + "p5.Vector", + "Number?", + "Number?" + ], + [ + "Number", + "Number", + "Number", + "p5.Vector", + "p5.Vector", + "Number?", + "Number?" + ], + [ + "p5.Color|Number[]|String", + "Number", + "Number", + "Number", + "p5.Vector", + "Number?", + "Number?" + ], + [ + "p5.Color|Number[]|String", + "p5.Vector", + "Number", + "Number", + "Number", + "Number?", + "Number?" + ], + [ + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "p5.Vector", + "Number?", + "Number?" + ], + [ + "Number", + "Number", + "Number", + "p5.Vector", + "Number", + "Number", + "Number", + "Number?", + "Number?" + ], + [ + "p5.Color|Number[]|String", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number", + "Number?", + "Number?" + ] + ] + }, + "noLights": { + "overloads": [ + [] + ] + }, + "loadModel": { + "overloads": [ + [ + "String|Request", + "Boolean", + "function(p5.Geometry)?", + "function(Event)?", + "String?" + ], + [ + "String|Request", + "function(p5.Geometry)?", + "function(Event)?", + "String?" + ], + [ + "String|Request", + "Object?", + "function(p5.Geometry)?", + "function(Event)?", + "String?", + "Boolean?", + "Boolean?", + "Boolean?" + ] + ] + }, + "parseObj": { + "overloads": [ + [] + ] + }, + "parseSTL": { + "overloads": [ + [] + ] + }, + "isBinary": { + "overloads": [ + [] + ] + }, + "matchDataViewAt": { + "overloads": [ + [] + ] + }, + "parseBinarySTL": { + "overloads": [ + [] + ] + }, + "parseASCIISTL": { + "overloads": [ + [] + ] + }, + "model": { + "overloads": [ + [ + "p5.Geometry" + ] + ] + }, + "loadShader": { + "overloads": [ + [ + "String|Request", + "String|Request", + "Function?", + "Function?" + ] + ] + }, + "createShader": { + "overloads": [ + [ + "String", + "String", + "Object?" + ] + ] + }, + "loadFilterShader": { + "overloads": [ + [ + "String", + "Function?", + "Function?" + ] + ] + }, + "createFilterShader": { + "overloads": [ + [ + "String" + ] + ] + }, + "shader": { + "overloads": [ + [ + "p5.Shader" + ] + ] + }, + "strokeShader": { + "overloads": [ + [ + "p5.Shader" + ] + ] + }, + "imageShader": { + "overloads": [ + [ + "p5.Shader" + ] + ] + }, + "baseMaterialShader": { + "overloads": [ + [] + ] + }, + "baseNormalShader": { + "overloads": [ + [] + ] + }, + "baseColorShader": { + "overloads": [ + [] + ] + }, + "baseStrokeShader": { + "overloads": [ + [] + ] + }, + "resetShader": { + "overloads": [ + [] + ] + }, + "texture": { + "overloads": [ + [ + "p5.Image|p5.MediaElement|p5.Graphics|p5.Texture|p5.Framebuffer|p5.FramebufferTexture" + ] + ] + }, + "textureMode": { + "overloads": [ + [ + "IMAGE|NORMAL" + ] + ] + }, + "textureWrap": { + "overloads": [ + [ + "CLAMP|REPEAT|MIRROR", + "CLAMP|REPEAT|MIRROR?" + ] + ] + }, + "normalMaterial": { + "overloads": [ + [] + ] + }, + "ambientMaterial": { + "overloads": [ + [ + "Number", + "Number", + "Number" + ], + [ + "Number" + ], + [ + "p5.Color|Number[]|String" + ] + ] + }, + "emissiveMaterial": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number?" + ], + [ + "Number" + ], + [ + "p5.Color|Number[]|String" + ] + ] + }, + "specularMaterial": { + "overloads": [ + [ + "Number", + "Number?" + ], + [ + "Number", + "Number", + "Number", + "Number?" + ], + [ + "p5.Color|Number[]|String" + ] + ] + }, + "shininess": { + "overloads": [ + [ + "Number" + ] + ] + }, + "metalness": { + "overloads": [ + [ + "Number" + ] + ] + }, + "camera": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?" + ] + ] + }, + "perspective": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Number?" + ] + ] + }, + "linePerspective": { + "overloads": [ + [ + "Boolean" + ], + [] + ] + }, + "ortho": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?" + ] + ] + }, + "frustum": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?" + ] + ] + }, + "createCamera": { + "overloads": [ + [] + ] + }, + "setCamera": { + "overloads": [ + [ + "p5.Camera" + ] + ] + }, + "saveObj": { + "overloads": [ + [ + "String?" + ] + ] + }, + "saveStl": { + "overloads": [ + [ + "String?", + "Object?" + ] + ] + }, + "setAttributes": { + "overloads": [ + [ + "String", + "Boolean" + ], + [ + "Object" + ] + ] + }, + "update": { + "overloads": [ + [] + ] + }, + "bindTexture": { + "overloads": [ + [] + ] + }, + "unbindTexture": { + "overloads": [ + [] + ] + }, + "setInterpolation": { + "overloads": [ + [ + "String", + "String" + ] + ] + }, + "setWrapMode": { + "overloads": [ + [ + "String", + "String" + ] + ] + }, + "remove": { + "overloads": [ + [] + ] + }, + "loadFont": { + "overloads": [ + [ + null + ] + ] + } + }, + "p5.Element": { + "remove": { + "overloads": [ + [] + ] + }, + "child": { + "overloads": [ + [], + [ + "String|p5.Element?" + ] + ] + }, + "html": { + "overloads": [ + [], + [ + "String?", + "Boolean?" + ] + ] + }, + "addClass": { + "overloads": [ + [ + "String" + ] + ] + }, + "removeClass": { + "overloads": [ + [ + "String" + ] + ] + }, + "hasClass": { + "overloads": [ + [ + null + ] + ] + }, + "toggleClass": { + "overloads": [ + [ + null + ] + ] + }, + "center": { + "overloads": [ + [ + "String?" + ] + ] + }, + "position": { + "overloads": [ + [], + [ + "Number?", + "Number?", + "String?" + ] + ] + }, + "show": { + "overloads": [ + [] + ] + }, + "hide": { + "overloads": [ + [] + ] + }, + "size": { + "overloads": [ + [], + [ + "Number|AUTO?", + "Number|AUTO?" + ] + ] + }, + "style": { + "overloads": [ + [ + "String" + ], + [ + "String", + "String|p5.Color" + ] + ] + }, + "attribute": { + "overloads": [ + [], + [ + "String", + "String" + ] + ] + }, + "removeAttribute": { + "overloads": [ + [ + "String" + ] + ] + }, + "value": { + "overloads": [ + [], + [ + "String|Number" + ] + ] + }, + "changed": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "input": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "drop": { + "overloads": [ + [ + "Function", + "Function?" + ] + ] + }, + "draggable": { + "overloads": [ + [ + "p5.Element?" + ] + ] + }, + "volume": { + "overloads": [ + [ + "Number" + ] + ] + }, + "createMedia": { + "overloads": [ + [] + ] + }, + "createVideo": { + "overloads": [ + [ + "String|String[]", + "Function?" + ] + ] + }, + "createAudio": { + "overloads": [ + [ + "String|String[]?", + "Function?" + ] + ] + }, + "createCapture": { + "overloads": [ + [ + "AUDIO|VIDEO|Object?", + "Object?", + "Function?" + ] + ] + }, + "parent": { + "overloads": [ + [ + "String|p5.Element|Object" + ], + [] + ] + }, + "id": { + "overloads": [ + [ + "String" + ], + [] + ] + }, + "class": { + "overloads": [ + [ + "String" + ], + [] + ] + }, + "mousePressed": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "doubleClicked": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "mouseWheel": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "mouseReleased": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "mouseClicked": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "mouseMoved": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "mouseOver": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "mouseOut": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "touchStarted": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "touchMoved": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "touchEnded": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "dragOver": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + }, + "dragLeave": { + "overloads": [ + [ + "Function|Boolean" + ] + ] + } + }, + "p5.Color": { + "toString": { + "overloads": [ + [ + "String?" + ] + ] + }, + "setRed": { + "overloads": [ + [ + "Number" + ] + ] + }, + "setGreen": { + "overloads": [ + [ + "Number" + ] + ] + }, + "setBlue": { + "overloads": [ + [ + "Number" + ] + ] + }, + "setAlpha": { + "overloads": [ + [ + "Number" + ] + ] + } + }, + "p5.TypedDict": { + "size": { + "overloads": [ + [] + ] + }, + "hasKey": { + "overloads": [ + [ + "Number|String" + ] + ] + }, + "get": { + "overloads": [ + [ + "Number|String" + ] + ] + }, + "set": { + "overloads": [ + [ + "Number|String", + "Number|String" + ] + ] + }, + "create": { + "overloads": [ + [ + "Number|String", + "Number|String" + ], + [ + "Object" + ] + ] + }, + "clear": { + "overloads": [ + [] + ] + }, + "remove": { + "overloads": [ + [ + "Number|String" + ] + ] + }, + "print": { + "overloads": [ + [] + ] + }, + "saveTable": { + "overloads": [ + [] + ] + }, + "saveJSON": { + "overloads": [ + [] + ] + } + }, + "p5.NumberDict": { + "add": { + "overloads": [ + [ + "Number", + "Number" + ] + ] + }, + "sub": { + "overloads": [ + [ + "Number", + "Number" + ] + ] + }, + "mult": { + "overloads": [ + [ + "Number", + "Number" + ] + ] + }, + "div": { + "overloads": [ + [ + "Number", + "Number" + ] + ] + }, + "minValue": { + "overloads": [ + [] + ] + }, + "maxValue": { + "overloads": [ + [] + ] + }, + "minKey": { + "overloads": [ + [] + ] + }, + "maxKey": { + "overloads": [ + [] + ] + } + }, + "p5.Table": { + "addRow": { + "overloads": [ + [ + "p5.TableRow?" + ] + ] + }, + "removeRow": { + "overloads": [ + [ + "Integer" + ] + ] + }, + "getRow": { + "overloads": [ + [ + "Integer" + ] + ] + }, + "getRows": { + "overloads": [ + [] + ] + }, + "findRow": { + "overloads": [ + [ + "String", + "Integer|String" + ] + ] + }, + "findRows": { + "overloads": [ + [ + "String", + "Integer|String" + ] + ] + }, + "matchRow": { + "overloads": [ + [ + "String|RegExp", + "String|Integer" + ] + ] + }, + "matchRows": { + "overloads": [ + [ + "String", + "String|Integer?" + ] + ] + }, + "getColumn": { + "overloads": [ + [ + "String|Number" + ] + ] + }, + "clearRows": { + "overloads": [ + [] + ] + }, + "addColumn": { + "overloads": [ + [ + "String?" + ] + ] + }, + "getColumnCount": { + "overloads": [ + [] + ] + }, + "getRowCount": { + "overloads": [ + [] + ] + }, + "removeTokens": { + "overloads": [ + [ + "String", + "String|Integer?" + ] + ] + }, + "trim": { + "overloads": [ + [ + "String|Integer?" + ] + ] + }, + "removeColumn": { + "overloads": [ + [ + "String|Integer" + ] + ] + }, + "set": { + "overloads": [ + [ + "Integer", + "String|Integer", + "String|Number" + ] + ] + }, + "setNum": { + "overloads": [ + [ + "Integer", + "String|Integer", + "Number" + ] + ] + }, + "setString": { + "overloads": [ + [ + "Integer", + "String|Integer", + "String" + ] + ] + }, + "get": { + "overloads": [ + [ + "Integer", + "String|Integer" + ] + ] + }, + "getNum": { + "overloads": [ + [ + "Integer", + "String|Integer" + ] + ] + }, + "getString": { + "overloads": [ + [ + "Integer", + "String|Integer" + ] + ] + }, + "getObject": { + "overloads": [ + [ + "String?" + ] + ] + }, + "getArray": { + "overloads": [ + [] + ] + } + }, + "p5.Graphics": { + "reset": { + "overloads": [ + [] + ] + }, + "remove": { + "overloads": [ + [] + ] + }, + "createFramebuffer": { + "overloads": [ + [ + "Object?" + ] + ] + } + }, + "p5.Renderer": { + "resize": { + "overloads": [ + [] + ] + }, + "textBounds": { + "overloads": [ + [ + "string", + "number", + "number", + "number", + "number" + ] + ] + }, + "fontBounds": { + "overloads": [ + [ + "string", + "number", + "number", + "number", + "number" + ] + ] + }, + "textWidth": { + "overloads": [ + [ + "string" + ] + ] + }, + "fontWidth": { + "overloads": [ + [ + "string" + ] + ] + }, + "textAscent": { + "overloads": [ + [ + null + ] + ] + }, + "fontAscent": { + "overloads": [ + [] + ] + }, + "textDescent": { + "overloads": [ + [ + null + ] + ] + }, + "fontDescent": { + "overloads": [ + [] + ] + }, + "textFont": { + "overloads": [ + [ + "p5.Font|string", + "number", + "object" + ] + ] + }, + "textSize": { + "overloads": [ + [ + null + ] + ] + }, + "textProperty": { + "overloads": [ + [] + ] + }, + "textProperties": { + "overloads": [ + [] + ] + } + }, + "p5.MediaElement": { + "play": { + "overloads": [ + [] + ] + }, + "stop": { + "overloads": [ + [] + ] + }, + "pause": { + "overloads": [ + [] + ] + }, + "loop": { + "overloads": [ + [] + ] + }, + "noLoop": { + "overloads": [ + [] + ] + }, + "autoplay": { + "overloads": [ + [ + "Boolean?" + ] + ] + }, + "volume": { + "overloads": [ + [] + ] + }, + "speed": { + "overloads": [ + [], + [ + "Number" + ] + ] + }, + "time": { + "overloads": [ + [], + [ + "Number" + ] + ] + }, + "duration": { + "overloads": [ + [] + ] + }, + "onended": { + "overloads": [ + [ + "Function" + ] + ] + }, + "connect": { + "overloads": [ + [ + "AudioNode|Object" + ] + ] + }, + "disconnect": { + "overloads": [ + [] + ] + }, + "showControls": { + "overloads": [ + [] + ] + }, + "hideControls": { + "overloads": [ + [] + ] + }, + "addCue": { + "overloads": [ + [ + "Number", + "Function", + "Object?" + ] + ] + }, + "removeCue": { + "overloads": [ + [ + "Number" + ] + ] + }, + "clearCues": { + "overloads": [ + [] + ] + } + }, + "p5.Image": { + "pixelDensity": { + "overloads": [ + [ + "Number?" + ] + ] + }, + "loadPixels": { + "overloads": [ + [] + ] + }, + "updatePixels": { + "overloads": [ + [ + "Integer", + "Integer", + "Integer", + "Integer" + ] + ] + }, + "get": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number" + ], + [], + [ + "Number", + "Number" + ] + ] + }, + "set": { + "overloads": [ + [ + "Number", + "Number", + "Number|Number[]|Object" + ] + ] + }, + "resize": { + "overloads": [ + [ + "Number", + "Number" + ] + ] + }, + "copy": { + "overloads": [ + [ + "p5.Image|p5.Element", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer" + ], + [ + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer" + ] + ] + }, + "mask": { + "overloads": [ + [ + "p5.Image" + ] + ] + }, + "filter": { + "overloads": [ + [ + "THRESHOLD|GRAY|OPAQUE|INVERT|POSTERIZE|ERODE|DILATE|BLUR", + "Number?" + ] + ] + }, + "blend": { + "overloads": [ + [ + "p5.Image", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL" + ], + [ + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "Integer", + "BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL" + ] + ] + }, + "save": { + "overloads": [ + [ + "String", + "String?" + ] + ] + }, + "reset": { + "overloads": [ + [] + ] + }, + "getCurrentFrame": { + "overloads": [ + [] + ] + }, + "setFrame": { + "overloads": [ + [ + "Number" + ] + ] + }, + "numFrames": { + "overloads": [ + [] + ] + }, + "play": { + "overloads": [ + [] + ] + }, + "pause": { + "overloads": [ + [] + ] + }, + "delay": { + "overloads": [ + [ + "Number", + "Number?" + ] + ] + } + }, + "p5.XML": { + "getParent": { + "overloads": [ + [] + ] + }, + "getName": { + "overloads": [ + [] + ] + }, + "setName": { + "overloads": [ + [ + "String" + ] + ] + }, + "hasChildren": { + "overloads": [ + [] + ] + }, + "listChildren": { + "overloads": [ + [] + ] + }, + "getChildren": { + "overloads": [ + [ + "String?" + ] + ] + }, + "getChild": { + "overloads": [ + [ + "String|Integer" + ] + ] + }, + "addChild": { + "overloads": [ + [ + "p5.XML" + ] + ] + }, + "removeChild": { + "overloads": [ + [ + "String|Integer" + ] + ] + }, + "getAttributeCount": { + "overloads": [ + [] + ] + }, + "listAttributes": { + "overloads": [ + [] + ] + }, + "hasAttribute": { + "overloads": [ + [ + "String" + ] + ] + }, + "getNum": { + "overloads": [ + [ + "String", + "Number?" + ] + ] + }, + "getString": { + "overloads": [ + [ + "String", + "Number?" + ] + ] + }, + "setAttribute": { + "overloads": [ + [ + "String", + "Number|String|Boolean" + ] + ] + }, + "getContent": { + "overloads": [ + [ + "String?" + ] + ] + }, + "serialize": { + "overloads": [ + [] + ] + } + }, + "p5.Vector": { + "getValue": { + "overloads": [ + [ + "number" + ] + ] + }, + "setValue": { + "overloads": [ + [ + "number", + "number" + ] + ] + }, + "set": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?" + ], + [ + "p5.Vector|Number[]" + ] + ] + }, + "copy": { + "overloads": [ + [], + [ + "p5.Vector" + ] + ] + }, + "add": { + "overloads": [ + [ + "Number", + "Number?", + "Number?" + ], + [ + "p5.Vector|Number[]" + ], + [ + "p5.Vector", + "p5.Vector", + "p5.Vector?" + ] + ] + }, + "rem": { + "overloads": [ + [ + "Number", + "Number", + "Number" + ], + [ + "p5.Vector|Number[]" + ], + [ + "p5.Vector", + "p5.Vector" + ] + ] + }, + "sub": { + "overloads": [ + [ + "Number", + "Number?", + "Number?" + ], + [ + "p5.Vector|Number[]" + ], + [ + "p5.Vector", + "p5.Vector", + "p5.Vector?" + ] + ] + }, + "mult": { + "overloads": [ + [ + "Number" + ], + [ + "Number", + "Number", + "Number?" + ], + [ + "Number[]" + ], + [ + "p5.Vector" + ], + [], + [ + "p5.Vector", + "Number", + "p5.Vector?" + ], + [ + "p5.Vector", + "p5.Vector", + "p5.Vector?" + ], + [ + "p5.Vector", + "Number[]", + "p5.Vector?" + ] + ] + }, + "div": { + "overloads": [ + [ + "Number" + ], + [ + "Number", + "Number", + "Number?" + ], + [ + "Number[]" + ], + [ + "p5.Vector" + ], + [], + [ + "p5.Vector", + "Number", + "p5.Vector?" + ], + [ + "p5.Vector", + "p5.Vector", + "p5.Vector?" + ], + [ + "p5.Vector", + "Number[]", + "p5.Vector?" + ] + ] + }, + "mag": { + "overloads": [ + [], + [ + "p5.Vector" + ] + ] + }, + "magSq": { + "overloads": [ + [], + [ + "p5.Vector" + ] + ] + }, + "dot": { + "overloads": [ + [ + "Number", + "Number?", + "Number?" + ], + [ + "p5.Vector" + ], + [], + [ + "p5.Vector", + "p5.Vector" + ] + ] + }, + "cross": { + "overloads": [ + [ + "p5.Vector" + ], + [], + [ + "p5.Vector", + "p5.Vector" + ] + ] + }, + "dist": { + "overloads": [ + [ + "p5.Vector" + ], + [], + [ + "p5.Vector", + "p5.Vector" + ] + ] + }, + "normalize": { + "overloads": [ + [], + [ + "p5.Vector", + "p5.Vector?" + ] + ] + }, + "limit": { + "overloads": [ + [ + "Number" + ], + [], + [ + "p5.Vector", + "Number", + "p5.Vector?" + ] + ] + }, + "setMag": { + "overloads": [ + [ + "Number" + ], + [], + [ + "p5.Vector", + "Number", + "p5.Vector?" + ] + ] + }, + "heading": { + "overloads": [ + [], + [ + "p5.Vector" + ] + ] + }, + "setHeading": { + "overloads": [ + [ + "Number" + ] + ] + }, + "rotate": { + "overloads": [ + [ + "Number" + ], + [], + [ + "p5.Vector", + "Number", + "p5.Vector?" + ] + ] + }, + "angleBetween": { + "overloads": [ + [ + "p5.Vector" + ], + [], + [ + "p5.Vector", + "p5.Vector" + ] + ] + }, + "lerp": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number" + ], + [ + "p5.Vector", + "Number" + ], + [], + [ + "p5.Vector", + "p5.Vector", + "Number", + "p5.Vector?" + ] + ] + }, + "slerp": { + "overloads": [ + [ + "p5.Vector", + "Number" + ], + [], + [ + "p5.Vector", + "p5.Vector", + "Number", + "p5.Vector?" + ] + ] + }, + "reflect": { + "overloads": [ + [ + "p5.Vector" + ], + [], + [ + "p5.Vector", + "p5.Vector", + "p5.Vector?" + ] + ] + }, + "array": { + "overloads": [ + [], + [ + "p5.Vector" + ] + ] + }, + "equals": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?" + ], + [ + "p5.Vector|Array" + ], + [], + [ + "p5.Vector|Array", + "p5.Vector|Array" + ] + ] + }, + "fromAngle": { + "overloads": [ + [ + "Number", + "Number?" + ] + ] + }, + "fromAngles": { + "overloads": [ + [ + "Number", + "Number", + "Number?" + ] + ] + }, + "random2D": { + "overloads": [ + [] + ] + }, + "random3D": { + "overloads": [ + [] + ] + } + }, + "p5.Camera": { + "perspective": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Number?" + ] + ] + }, + "ortho": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?" + ] + ] + }, + "frustum": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?" + ] + ] + }, + "pan": { + "overloads": [ + [ + "Number" + ] + ] + }, + "tilt": { + "overloads": [ + [ + "Number" + ] + ] + }, + "lookAt": { + "overloads": [ + [ + "Number", + "Number", + "Number" + ] + ] + }, + "camera": { + "overloads": [ + [ + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?", + "Number?" + ] + ] + }, + "move": { + "overloads": [ + [ + "Number", + "Number", + "Number" + ] + ] + }, + "setPosition": { + "overloads": [ + [ + "Number", + "Number", + "Number" + ] + ] + }, + "set": { + "overloads": [ + [ + "p5.Camera" + ] + ] + }, + "slerp": { + "overloads": [ + [ + "p5.Camera", + "p5.Camera", + "Number" + ] + ] + } + }, + "p5.Framebuffer": { + "resize": { + "overloads": [ + [ + "Number", + "Number" + ] + ] + }, + "pixelDensity": { + "overloads": [ + [ + "Number?" + ] + ] + }, + "autoSized": { + "overloads": [ + [ + "Boolean?" + ] + ] + }, + "createCamera": { + "overloads": [ + [] + ] + }, + "remove": { + "overloads": [ + [] + ] + }, + "begin": { + "overloads": [ + [] + ] + }, + "end": { + "overloads": [ + [] + ] + }, + "draw": { + "overloads": [ + [ + "Function" + ] + ] + }, + "get": { + "overloads": [ + [ + "Number", + "Number", + "Number", + "Number" + ], + [], + [ + "Number", + "Number" + ] + ] + } + }, + "p5.Geometry": { + "calculateBoundingBox": { + "overloads": [ + [] + ] + }, + "clearColors": { + "overloads": [ + [] + ] + }, + "flipU": { + "overloads": [ + [] + ] + }, + "flipV": { + "overloads": [ + [] + ] + }, + "computeFaces": { + "overloads": [ + [] + ] + }, + "computeNormals": { + "overloads": [ + [ + "FLAT|SMOOTH?", + "Object?" + ] + ] + }, + "normalize": { + "overloads": [ + [] + ] + } + }, + "p5.Shader": { + "version": { + "overloads": [ + [] + ] + }, + "inspectHooks": { + "overloads": [ + [] + ] + }, + "modify": { + "overloads": [ + [ + "Object?" + ] + ] + }, + "copyToContext": { + "overloads": [ + [ + "p5|p5.Graphics" + ] + ] + }, + "setUniform": { + "overloads": [ + [ + "String", + "Boolean|Number|Number[]|p5.Image|p5.Graphics|p5.MediaElement|p5.Texture" + ] + ] + } + } +} \ No newline at end of file diff --git a/docs/parameterData.json.bak b/docs/parameterData.json.bak new file mode 100644 index 0000000000..6f10499199 --- /dev/null +++ b/docs/parameterData.json.bak @@ -0,0 +1,16154 @@ +{ + "p5": { + "describe": { + "name": "describe", + "params": [ + { + "name": "text", + "description": "

description of the canvas.

\n", + "type": "String" + }, + { + "name": "display", + "description": "

either LABEL or FALLBACK.

\n", + "type": "Constant", + "optional": true + } + ], + "class": "p5", + "module": "Environment" + }, + "describeElement": { + "name": "describeElement", + "params": [ + { + "name": "name", + "description": "

name of the element.

\n", + "type": "String" + }, + { + "name": "text", + "description": "

description of the element.

\n", + "type": "String" + }, + { + "name": "display", + "description": "

either LABEL or FALLBACK.

\n", + "type": "Constant", + "optional": true + } + ], + "class": "p5", + "module": "Environment" + }, + "textOutput": { + "name": "textOutput", + "params": [ + { + "name": "display", + "description": "

either FALLBACK or LABEL.

\n", + "type": "Constant", + "optional": true + } + ], + "class": "p5", + "module": "Environment" + }, + "gridOutput": { + "name": "gridOutput", + "params": [ + { + "name": "display", + "description": "

either FALLBACK or LABEL.

\n", + "type": "Constant", + "optional": true + } + ], + "class": "p5", + "module": "Environment" + }, + "alpha": { + "name": "alpha", + "params": [ + { + "name": "color", + "description": "

p5.Color object, array of\n color components, or CSS color string.

\n", + "type": "p5.Color|Number[]|String" + } + ], + "class": "p5", + "module": "Color" + }, + "blue": { + "name": "blue", + "params": [ + { + "name": "color", + "description": "

p5.Color object, array of\n color components, or CSS color string.

\n", + "type": "p5.Color|Number[]|String" + } + ], + "class": "p5", + "module": "Color" + }, + "brightness": { + "name": "brightness", + "params": [ + { + "name": "color", + "description": "

p5.Color object, array of\n color components, or CSS color string.

\n", + "type": "p5.Color|Number[]|String" + } + ], + "class": "p5", + "module": "Color" + }, + "color": { + "name": "color", + "class": "p5", + "module": "Color", + "overloads": [ + { + "params": [ + { + "name": "gray", + "description": "

number specifying value between white and black.

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "

alpha value relative to current color range\n (default is 0-255).

\n", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "

red or hue value relative to\n the current color range.

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green or saturation value\n relative to the current color range.

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue or brightness value\n relative to the current color range.

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "

a color string.

\n", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "values", + "description": "

an array containing the red, green, blue,\n and alpha components of the color.

\n", + "type": "Number[]" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "", + "type": "p5.Color" + } + ] + } + ] + }, + "green": { + "name": "green", + "params": [ + { + "name": "color", + "description": "

p5.Color object, array of\n color components, or CSS color string.

\n", + "type": "p5.Color|Number[]|String" + } + ], + "class": "p5", + "module": "Color" + }, + "hue": { + "name": "hue", + "params": [ + { + "name": "color", + "description": "

p5.Color object, array of\n color components, or CSS color string.

\n", + "type": "p5.Color|Number[]|String" + } + ], + "class": "p5", + "module": "Color" + }, + "lerpColor": { + "name": "lerpColor", + "params": [ + { + "name": "c1", + "description": "

interpolate from this color.

\n", + "type": "p5.Color" + }, + { + "name": "c2", + "description": "

interpolate to this color.

\n", + "type": "p5.Color" + }, + { + "name": "amt", + "description": "

number between 0 and 1.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Color" + }, + "lightness": { + "name": "lightness", + "params": [ + { + "name": "color", + "description": "

p5.Color object, array of\n color components, or CSS color string.

\n", + "type": "p5.Color|Number[]|String" + } + ], + "class": "p5", + "module": "Color" + }, + "red": { + "name": "red", + "params": [ + { + "name": "color", + "description": "

p5.Color object, array of\n color components, or CSS color string.

\n", + "type": "p5.Color|Number[]|String" + } + ], + "class": "p5", + "module": "Color" + }, + "saturation": { + "name": "saturation", + "params": [ + { + "name": "color", + "description": "

p5.Color object, array of\n color components, or CSS color string.

\n", + "type": "p5.Color|Number[]|String" + } + ], + "class": "p5", + "module": "Color" + }, + "beginClip": { + "name": "beginClip", + "params": [ + { + "name": "options", + "description": "

An object containing clip settings.

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5", + "module": "Color" + }, + "endClip": { + "name": "endClip", + "class": "p5", + "module": "Color" + }, + "clip": { + "name": "clip", + "params": [ + { + "name": "callback", + "description": "

A function that draws the mask shape.

\n", + "type": "Function" + }, + { + "name": "options", + "description": "

An object containing clip settings.

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5", + "module": "Color" + }, + "background": { + "name": "background", + "class": "p5", + "module": "Color", + "overloads": [ + { + "params": [ + { + "name": "color", + "description": "

any value created by the color() function

\n", + "type": "p5.Color" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "colorstring", + "description": "

color string, possible formats include: integer\n rgb() or rgba(), percentage rgb() or rgba(),\n 3-digit hex, 6-digit hex.

\n", + "type": "String" + }, + { + "name": "a", + "description": "

opacity of the background relative to current\n color range (default is 0-255).

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "gray", + "description": "

specifies a value between white and black.

\n", + "type": "Number" + }, + { + "name": "a", + "description": "", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v1", + "description": "

red value if color mode is RGB, or hue value if color mode is HSB.

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green value if color mode is RGB, or saturation value if color mode is HSB.

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue value if color mode is RGB, or brightness value if color mode is HSB.

\n", + "type": "Number" + }, + { + "name": "a", + "description": "", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "values", + "description": "

an array containing the red, green, blue\n and alpha components of the color.

\n", + "type": "Number[]" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "image", + "description": "

image created with loadImage()\n or createImage(),\n to set as background.\n (must be same size as the sketch window).

\n", + "type": "p5.Image" + }, + { + "name": "a", + "description": "", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + } + ] + }, + "clear": { + "name": "clear", + "params": [ + { + "name": "r", + "description": "

normalized red value.

\n", + "type": "Number", + "optional": true + }, + { + "name": "g", + "description": "

normalized green value.

\n", + "type": "Number", + "optional": true + }, + { + "name": "b", + "description": "

normalized blue value.

\n", + "type": "Number", + "optional": true + }, + { + "name": "a", + "description": "

normalized alpha value.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "Color" + }, + "colorMode": { + "name": "colorMode", + "class": "p5", + "module": "Color", + "overloads": [ + { + "params": [ + { + "name": "mode", + "description": "

either RGB, HSB or HSL, corresponding to\n Red/Green/Blue and Hue/Saturation/Brightness\n (or Lightness).

\n", + "type": "Constant" + }, + { + "name": "max", + "description": "

range for all values.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "mode", + "description": "", + "type": "Constant" + }, + { + "name": "max1", + "description": "

range for the red or hue depending on the\n current color mode.

\n", + "type": "Number" + }, + { + "name": "max2", + "description": "

range for the green or saturation depending\n on the current color mode.

\n", + "type": "Number" + }, + { + "name": "max3", + "description": "

range for the blue or brightness/lightness\n depending on the current color mode.

\n", + "type": "Number" + }, + { + "name": "maxA", + "description": "

range for the alpha.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + } + ] + }, + "fill": { + "name": "fill", + "class": "p5", + "module": "Color", + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "

red value if color mode is RGB or hue value if color mode is HSB.

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green value if color mode is RGB or saturation value if color mode is HSB.

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue value if color mode is RGB or brightness value if color mode is HSB.

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "value", + "description": "

a color string.

\n", + "type": "String" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "gray", + "description": "

a grayscale value.

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "values", + "description": "

an array containing the red, green, blue &\n and alpha components of the color.

\n", + "type": "Number[]" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "

the fill color.

\n", + "type": "p5.Color" + } + ], + "chainable": 1 + } + ] + }, + "noFill": { + "name": "noFill", + "class": "p5", + "module": "Color" + }, + "noStroke": { + "name": "noStroke", + "class": "p5", + "module": "Color" + }, + "stroke": { + "name": "stroke", + "class": "p5", + "module": "Color", + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "

red value if color mode is RGB or hue value if color mode is HSB.

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green value if color mode is RGB or saturation value if color mode is HSB.

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue value if color mode is RGB or brightness value if color mode is HSB.

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "value", + "description": "

a color string.

\n", + "type": "String" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "gray", + "description": "

a grayscale value.

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "values", + "description": "

an array containing the red, green, blue,\n and alpha components of the color.

\n", + "type": "Number[]" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "

the stroke color.

\n", + "type": "p5.Color" + } + ], + "chainable": 1 + } + ] + }, + "erase": { + "name": "erase", + "params": [ + { + "name": "strengthFill", + "description": "

a number (0-255) for the strength of erasing under a shape's interior.\n Defaults to 255, which is full strength.

\n", + "type": "Number", + "optional": true + }, + { + "name": "strengthStroke", + "description": "

a number (0-255) for the strength of erasing under a shape's edge.\n Defaults to 255, which is full strength.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "Color" + }, + "noErase": { + "name": "noErase", + "class": "p5", + "module": "Color" + }, + "arc": { + "name": "arc", + "params": [ + { + "name": "x", + "description": "

x-coordinate of the arc's ellipse.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the arc's ellipse.

\n", + "type": "Number" + }, + { + "name": "w", + "description": "

width of the arc's ellipse by default.

\n", + "type": "Number" + }, + { + "name": "h", + "description": "

height of the arc's ellipse by default.

\n", + "type": "Number" + }, + { + "name": "start", + "description": "

angle to start the arc, specified in radians.

\n", + "type": "Number" + }, + { + "name": "stop", + "description": "

angle to stop the arc, specified in radians.

\n", + "type": "Number" + }, + { + "name": "mode", + "description": "

optional parameter to determine the way of drawing\n the arc. either CHORD, PIE, or OPEN.

\n", + "type": "Constant", + "optional": true + }, + { + "name": "detail", + "description": "

optional parameter for WebGL mode only. This is to\n specify the number of vertices that makes up the\n perimeter of the arc. Default value is 25. Won't\n draw a stroke for a detail of more than 50.

\n", + "type": "Integer", + "optional": true + } + ], + "class": "p5", + "module": "Shape" + }, + "ellipse": { + "name": "ellipse", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x-coordinate of the center of the ellipse.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the center of the ellipse.

\n", + "type": "Number" + }, + { + "name": "w", + "description": "

width of the ellipse.

\n", + "type": "Number" + }, + { + "name": "h", + "description": "

height of the ellipse.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "w", + "description": "", + "type": "Number" + }, + { + "name": "h", + "description": "", + "type": "Number" + }, + { + "name": "detail", + "description": "

optional parameter for WebGL mode only. This is to\n specify the number of vertices that makes up the\n perimeter of the ellipse. Default value is 25. Won't\n draw a stroke for a detail of more than 50.

\n", + "type": "Integer", + "optional": true + } + ] + } + ] + }, + "circle": { + "name": "circle", + "params": [ + { + "name": "x", + "description": "

x-coordinate of the center of the circle.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the center of the circle.

\n", + "type": "Number" + }, + { + "name": "d", + "description": "

diameter of the circle.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Shape" + }, + "line": { + "name": "line", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "x1", + "description": "

the x-coordinate of the first point.

\n", + "type": "Number" + }, + { + "name": "y1", + "description": "

the y-coordinate of the first point.

\n", + "type": "Number" + }, + { + "name": "x2", + "description": "

the x-coordinate of the second point.

\n", + "type": "Number" + }, + { + "name": "y2", + "description": "

the y-coordinate of the second point.

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x1", + "description": "", + "type": "Number" + }, + { + "name": "y1", + "description": "", + "type": "Number" + }, + { + "name": "z1", + "description": "

the z-coordinate of the first point.

\n", + "type": "Number" + }, + { + "name": "x2", + "description": "", + "type": "Number" + }, + { + "name": "y2", + "description": "", + "type": "Number" + }, + { + "name": "z2", + "description": "

the z-coordinate of the second point.

\n", + "type": "Number" + } + ], + "chainable": 1 + } + ] + }, + "point": { + "name": "point", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

the x-coordinate.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

the y-coordinate.

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

the z-coordinate (for WebGL mode).

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "coordinateVector", + "description": "

the coordinate vector.

\n", + "type": "p5.Vector" + } + ], + "chainable": 1 + } + ] + }, + "quad": { + "name": "quad", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "x1", + "description": "

the x-coordinate of the first point.

\n", + "type": "Number" + }, + { + "name": "y1", + "description": "

the y-coordinate of the first point.

\n", + "type": "Number" + }, + { + "name": "x2", + "description": "

the x-coordinate of the second point.

\n", + "type": "Number" + }, + { + "name": "y2", + "description": "

the y-coordinate of the second point.

\n", + "type": "Number" + }, + { + "name": "x3", + "description": "

the x-coordinate of the third point.

\n", + "type": "Number" + }, + { + "name": "y3", + "description": "

the y-coordinate of the third point.

\n", + "type": "Number" + }, + { + "name": "x4", + "description": "

the x-coordinate of the fourth point.

\n", + "type": "Number" + }, + { + "name": "y4", + "description": "

the y-coordinate of the fourth point.

\n", + "type": "Number" + }, + { + "name": "detailX", + "description": "

number of segments in the x-direction.

\n", + "type": "Integer", + "optional": true + }, + { + "name": "detailY", + "description": "

number of segments in the y-direction.

\n", + "type": "Integer", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x1", + "description": "", + "type": "Number" + }, + { + "name": "y1", + "description": "", + "type": "Number" + }, + { + "name": "z1", + "description": "

the z-coordinate of the first point.

\n", + "type": "Number" + }, + { + "name": "x2", + "description": "", + "type": "Number" + }, + { + "name": "y2", + "description": "", + "type": "Number" + }, + { + "name": "z2", + "description": "

the z-coordinate of the second point.

\n", + "type": "Number" + }, + { + "name": "x3", + "description": "", + "type": "Number" + }, + { + "name": "y3", + "description": "", + "type": "Number" + }, + { + "name": "z3", + "description": "

the z-coordinate of the third point.

\n", + "type": "Number" + }, + { + "name": "x4", + "description": "", + "type": "Number" + }, + { + "name": "y4", + "description": "", + "type": "Number" + }, + { + "name": "z4", + "description": "

the z-coordinate of the fourth point.

\n", + "type": "Number" + }, + { + "name": "detailX", + "description": "", + "type": "Integer", + "optional": true + }, + { + "name": "detailY", + "description": "", + "type": "Integer", + "optional": true + } + ], + "chainable": 1 + } + ] + }, + "rect": { + "name": "rect", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x-coordinate of the rectangle.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the rectangle.

\n", + "type": "Number" + }, + { + "name": "w", + "description": "

width of the rectangle.

\n", + "type": "Number" + }, + { + "name": "h", + "description": "

height of the rectangle.

\n", + "type": "Number", + "optional": true + }, + { + "name": "tl", + "description": "

optional radius of top-left corner.

\n", + "type": "Number", + "optional": true + }, + { + "name": "tr", + "description": "

optional radius of top-right corner.

\n", + "type": "Number", + "optional": true + }, + { + "name": "br", + "description": "

optional radius of bottom-right corner.

\n", + "type": "Number", + "optional": true + }, + { + "name": "bl", + "description": "

optional radius of bottom-left corner.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "w", + "description": "", + "type": "Number" + }, + { + "name": "h", + "description": "", + "type": "Number" + }, + { + "name": "detailX", + "description": "

number of segments in the x-direction (for WebGL mode).

\n", + "type": "Integer", + "optional": true + }, + { + "name": "detailY", + "description": "

number of segments in the y-direction (for WebGL mode).

\n", + "type": "Integer", + "optional": true + } + ], + "chainable": 1 + } + ] + }, + "square": { + "name": "square", + "params": [ + { + "name": "x", + "description": "

x-coordinate of the square.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the square.

\n", + "type": "Number" + }, + { + "name": "s", + "description": "

side size of the square.

\n", + "type": "Number" + }, + { + "name": "tl", + "description": "

optional radius of top-left corner.

\n", + "type": "Number", + "optional": true + }, + { + "name": "tr", + "description": "

optional radius of top-right corner.

\n", + "type": "Number", + "optional": true + }, + { + "name": "br", + "description": "

optional radius of bottom-right corner.

\n", + "type": "Number", + "optional": true + }, + { + "name": "bl", + "description": "

optional radius of bottom-left corner.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "Shape" + }, + "triangle": { + "name": "triangle", + "params": [ + { + "name": "x1", + "description": "

x-coordinate of the first point.

\n", + "type": "Number" + }, + { + "name": "y1", + "description": "

y-coordinate of the first point.

\n", + "type": "Number" + }, + { + "name": "x2", + "description": "

x-coordinate of the second point.

\n", + "type": "Number" + }, + { + "name": "y2", + "description": "

y-coordinate of the second point.

\n", + "type": "Number" + }, + { + "name": "x3", + "description": "

x-coordinate of the third point.

\n", + "type": "Number" + }, + { + "name": "y3", + "description": "

y-coordinate of the third point.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Shape" + }, + "ellipseMode": { + "name": "ellipseMode", + "params": [ + { + "name": "mode", + "description": "

either CENTER, RADIUS, CORNER, or CORNERS

\n", + "type": "Constant" + } + ], + "class": "p5", + "module": "Shape" + }, + "noSmooth": { + "name": "noSmooth", + "class": "p5", + "module": "Shape" + }, + "rectMode": { + "name": "rectMode", + "params": [ + { + "name": "mode", + "description": "

either CORNER, CORNERS, CENTER, or RADIUS

\n", + "type": "Constant" + } + ], + "class": "p5", + "module": "Shape" + }, + "smooth": { + "name": "smooth", + "class": "p5", + "module": "Shape" + }, + "strokeCap": { + "name": "strokeCap", + "params": [ + { + "name": "cap", + "description": "

either ROUND, SQUARE, or PROJECT

\n", + "type": "Constant" + } + ], + "class": "p5", + "module": "Shape" + }, + "strokeJoin": { + "name": "strokeJoin", + "params": [ + { + "name": "join", + "description": "

either MITER, BEVEL, or ROUND

\n", + "type": "Constant" + } + ], + "class": "p5", + "module": "Shape" + }, + "strokeWeight": { + "name": "strokeWeight", + "params": [ + { + "name": "weight", + "description": "

the weight of the stroke (in pixels).

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Shape" + }, + "bezier": { + "name": "bezier", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "x1", + "description": "

x-coordinate for the first anchor point

\n", + "type": "Number" + }, + { + "name": "y1", + "description": "

y-coordinate for the first anchor point

\n", + "type": "Number" + }, + { + "name": "x2", + "description": "

x-coordinate for the first control point

\n", + "type": "Number" + }, + { + "name": "y2", + "description": "

y-coordinate for the first control point

\n", + "type": "Number" + }, + { + "name": "x3", + "description": "

x-coordinate for the second control point

\n", + "type": "Number" + }, + { + "name": "y3", + "description": "

y-coordinate for the second control point

\n", + "type": "Number" + }, + { + "name": "x4", + "description": "

x-coordinate for the second anchor point

\n", + "type": "Number" + }, + { + "name": "y4", + "description": "

y-coordinate for the second anchor point

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x1", + "description": "", + "type": "Number" + }, + { + "name": "y1", + "description": "", + "type": "Number" + }, + { + "name": "z1", + "description": "

z-coordinate for the first anchor point

\n", + "type": "Number" + }, + { + "name": "x2", + "description": "", + "type": "Number" + }, + { + "name": "y2", + "description": "", + "type": "Number" + }, + { + "name": "z2", + "description": "

z-coordinate for the first control point

\n", + "type": "Number" + }, + { + "name": "x3", + "description": "", + "type": "Number" + }, + { + "name": "y3", + "description": "", + "type": "Number" + }, + { + "name": "z3", + "description": "

z-coordinate for the second control point

\n", + "type": "Number" + }, + { + "name": "x4", + "description": "", + "type": "Number" + }, + { + "name": "y4", + "description": "", + "type": "Number" + }, + { + "name": "z4", + "description": "

z-coordinate for the second anchor point

\n", + "type": "Number" + } + ], + "chainable": 1 + } + ] + }, + "bezierDetail": { + "name": "bezierDetail", + "params": [ + { + "name": "detail", + "description": "

resolution of the curves

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Shape" + }, + "bezierPoint": { + "name": "bezierPoint", + "params": [ + { + "name": "a", + "description": "

coordinate of first point on the curve

\n", + "type": "Number" + }, + { + "name": "b", + "description": "

coordinate of first control point

\n", + "type": "Number" + }, + { + "name": "c", + "description": "

coordinate of second control point

\n", + "type": "Number" + }, + { + "name": "d", + "description": "

coordinate of second point on the curve

\n", + "type": "Number" + }, + { + "name": "t", + "description": "

value between 0 and 1

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Shape" + }, + "bezierTangent": { + "name": "bezierTangent", + "params": [ + { + "name": "a", + "description": "

coordinate of first point on the curve

\n", + "type": "Number" + }, + { + "name": "b", + "description": "

coordinate of first control point

\n", + "type": "Number" + }, + { + "name": "c", + "description": "

coordinate of second control point

\n", + "type": "Number" + }, + { + "name": "d", + "description": "

coordinate of second point on the curve

\n", + "type": "Number" + }, + { + "name": "t", + "description": "

value between 0 and 1

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Shape" + }, + "curve": { + "name": "curve", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "x1", + "description": "

x-coordinate for the beginning control point

\n", + "type": "Number" + }, + { + "name": "y1", + "description": "

y-coordinate for the beginning control point

\n", + "type": "Number" + }, + { + "name": "x2", + "description": "

x-coordinate for the first point

\n", + "type": "Number" + }, + { + "name": "y2", + "description": "

y-coordinate for the first point

\n", + "type": "Number" + }, + { + "name": "x3", + "description": "

x-coordinate for the second point

\n", + "type": "Number" + }, + { + "name": "y3", + "description": "

y-coordinate for the second point

\n", + "type": "Number" + }, + { + "name": "x4", + "description": "

x-coordinate for the ending control point

\n", + "type": "Number" + }, + { + "name": "y4", + "description": "

y-coordinate for the ending control point

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x1", + "description": "", + "type": "Number" + }, + { + "name": "y1", + "description": "", + "type": "Number" + }, + { + "name": "z1", + "description": "

z-coordinate for the beginning control point

\n", + "type": "Number" + }, + { + "name": "x2", + "description": "", + "type": "Number" + }, + { + "name": "y2", + "description": "", + "type": "Number" + }, + { + "name": "z2", + "description": "

z-coordinate for the first point

\n", + "type": "Number" + }, + { + "name": "x3", + "description": "", + "type": "Number" + }, + { + "name": "y3", + "description": "", + "type": "Number" + }, + { + "name": "z3", + "description": "

z-coordinate for the second point

\n", + "type": "Number" + }, + { + "name": "x4", + "description": "", + "type": "Number" + }, + { + "name": "y4", + "description": "", + "type": "Number" + }, + { + "name": "z4", + "description": "

z-coordinate for the ending control point

\n", + "type": "Number" + } + ], + "chainable": 1 + } + ] + }, + "curveDetail": { + "name": "curveDetail", + "params": [ + { + "name": "resolution", + "description": "

resolution of the curves

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Shape" + }, + "curveTightness": { + "name": "curveTightness", + "params": [ + { + "name": "amount", + "description": "

amount of deformation from the original vertices

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Shape" + }, + "curvePoint": { + "name": "curvePoint", + "params": [ + { + "name": "a", + "description": "

coordinate of first control point of the curve

\n", + "type": "Number" + }, + { + "name": "b", + "description": "

coordinate of first point

\n", + "type": "Number" + }, + { + "name": "c", + "description": "

coordinate of second point

\n", + "type": "Number" + }, + { + "name": "d", + "description": "

coordinate of second control point

\n", + "type": "Number" + }, + { + "name": "t", + "description": "

value between 0 and 1

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Shape" + }, + "curveTangent": { + "name": "curveTangent", + "params": [ + { + "name": "a", + "description": "

coordinate of first control point

\n", + "type": "Number" + }, + { + "name": "b", + "description": "

coordinate of first point on the curve

\n", + "type": "Number" + }, + { + "name": "c", + "description": "

coordinate of second point on the curve

\n", + "type": "Number" + }, + { + "name": "d", + "description": "

coordinate of second conrol point

\n", + "type": "Number" + }, + { + "name": "t", + "description": "

value between 0 and 1

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Shape" + }, + "beginContour": { + "name": "beginContour", + "class": "p5", + "module": "Shape" + }, + "beginShape": { + "name": "beginShape", + "params": [ + { + "name": "kind", + "description": "

either POINTS, LINES, TRIANGLES, TRIANGLE_FAN\n TRIANGLE_STRIP, QUADS, QUAD_STRIP or TESS

\n", + "type": "Constant", + "optional": true + } + ], + "class": "p5", + "module": "Shape" + }, + "bezierVertex": { + "name": "bezierVertex", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "x2", + "description": "

x-coordinate for the first control point

\n", + "type": "Number" + }, + { + "name": "y2", + "description": "

y-coordinate for the first control point

\n", + "type": "Number" + }, + { + "name": "x3", + "description": "

x-coordinate for the second control point

\n", + "type": "Number" + }, + { + "name": "y3", + "description": "

y-coordinate for the second control point

\n", + "type": "Number" + }, + { + "name": "x4", + "description": "

x-coordinate for the anchor point

\n", + "type": "Number" + }, + { + "name": "y4", + "description": "

y-coordinate for the anchor point

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x2", + "description": "", + "type": "Number" + }, + { + "name": "y2", + "description": "", + "type": "Number" + }, + { + "name": "z2", + "description": "

z-coordinate for the first control point (for WebGL mode)

\n", + "type": "Number" + }, + { + "name": "x3", + "description": "", + "type": "Number" + }, + { + "name": "y3", + "description": "", + "type": "Number" + }, + { + "name": "z3", + "description": "

z-coordinate for the second control point (for WebGL mode)

\n", + "type": "Number" + }, + { + "name": "x4", + "description": "", + "type": "Number" + }, + { + "name": "y4", + "description": "", + "type": "Number" + }, + { + "name": "z4", + "description": "

z-coordinate for the anchor point (for WebGL mode)

\n", + "type": "Number" + } + ], + "chainable": 1 + } + ] + }, + "curveVertex": { + "name": "curveVertex", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x-coordinate of the vertex

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the vertex

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "z", + "description": "

z-coordinate of the vertex (for WebGL mode)

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + } + ] + }, + "endContour": { + "name": "endContour", + "class": "p5", + "module": "Shape" + }, + "endShape": { + "name": "endShape", + "params": [ + { + "name": "mode", + "description": "

use CLOSE to close the shape

\n", + "type": "Constant", + "optional": true + }, + { + "name": "count", + "description": "

number of times you want to draw/instance the shape (for WebGL mode).

\n", + "type": "Integer", + "optional": true + } + ], + "class": "p5", + "module": "Shape" + }, + "quadraticVertex": { + "name": "quadraticVertex", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "cx", + "description": "

x-coordinate for the control point

\n", + "type": "Number" + }, + { + "name": "cy", + "description": "

y-coordinate for the control point

\n", + "type": "Number" + }, + { + "name": "x3", + "description": "

x-coordinate for the anchor point

\n", + "type": "Number" + }, + { + "name": "y3", + "description": "

y-coordinate for the anchor point

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "cx", + "description": "", + "type": "Number" + }, + { + "name": "cy", + "description": "", + "type": "Number" + }, + { + "name": "cz", + "description": "

z-coordinate for the control point (for WebGL mode)

\n", + "type": "Number" + }, + { + "name": "x3", + "description": "", + "type": "Number" + }, + { + "name": "y3", + "description": "", + "type": "Number" + }, + { + "name": "z3", + "description": "

z-coordinate for the anchor point (for WebGL mode)

\n", + "type": "Number" + } + ], + "chainable": 1 + } + ] + }, + "vertex": { + "name": "vertex", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x-coordinate of the vertex

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the vertex

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "z", + "description": "

z-coordinate of the vertex.\n Defaults to 0 if not specified.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "z", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "u", + "description": "

the vertex's texture u-coordinate

\n", + "type": "Number", + "optional": true + }, + { + "name": "v", + "description": "

the vertex's texture v-coordinate

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + } + ] + }, + "normal": { + "name": "normal", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "vector", + "description": "

A p5.Vector representing the vertex normal.

\n", + "type": "Vector" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x", + "description": "

The x component of the vertex normal.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

The y component of the vertex normal.

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

The z component of the vertex normal.

\n", + "type": "Number" + } + ], + "chainable": 1 + } + ] + }, + "VERSION": { + "name": "VERSION", + "class": "p5", + "module": "Constants" + }, + "P2D": { + "name": "P2D", + "class": "p5", + "module": "Constants" + }, + "WEBGL": { + "name": "WEBGL", + "class": "p5", + "module": "Constants" + }, + "WEBGL2": { + "name": "WEBGL2", + "class": "p5", + "module": "Constants" + }, + "ARROW": { + "name": "ARROW", + "class": "p5", + "module": "Constants" + }, + "CROSS": { + "name": "CROSS", + "class": "p5", + "module": "Constants" + }, + "HAND": { + "name": "HAND", + "class": "p5", + "module": "Constants" + }, + "MOVE": { + "name": "MOVE", + "class": "p5", + "module": "Constants" + }, + "TEXT": { + "name": "TEXT", + "class": "p5", + "module": "Constants" + }, + "WAIT": { + "name": "WAIT", + "class": "p5", + "module": "Constants" + }, + "HALF_PI": { + "name": "HALF_PI", + "class": "p5", + "module": "Constants" + }, + "PI": { + "name": "PI", + "class": "p5", + "module": "Constants" + }, + "QUARTER_PI": { + "name": "QUARTER_PI", + "class": "p5", + "module": "Constants" + }, + "TAU": { + "name": "TAU", + "class": "p5", + "module": "Constants" + }, + "TWO_PI": { + "name": "TWO_PI", + "class": "p5", + "module": "Constants" + }, + "DEGREES": { + "name": "DEGREES", + "class": "p5", + "module": "Constants" + }, + "RADIANS": { + "name": "RADIANS", + "class": "p5", + "module": "Constants" + }, + "CORNER": { + "name": "CORNER", + "class": "p5", + "module": "Constants" + }, + "CORNERS": { + "name": "CORNERS", + "class": "p5", + "module": "Constants" + }, + "RADIUS": { + "name": "RADIUS", + "class": "p5", + "module": "Constants" + }, + "RIGHT": { + "name": "RIGHT", + "class": "p5", + "module": "Constants" + }, + "LEFT": { + "name": "LEFT", + "class": "p5", + "module": "Constants" + }, + "CENTER": { + "name": "CENTER", + "class": "p5", + "module": "Constants" + }, + "TOP": { + "name": "TOP", + "class": "p5", + "module": "Constants" + }, + "BOTTOM": { + "name": "BOTTOM", + "class": "p5", + "module": "Constants" + }, + "BASELINE": { + "name": "BASELINE", + "class": "p5", + "module": "Constants" + }, + "POINTS": { + "name": "POINTS", + "class": "p5", + "module": "Constants" + }, + "LINES": { + "name": "LINES", + "class": "p5", + "module": "Constants" + }, + "LINE_STRIP": { + "name": "LINE_STRIP", + "class": "p5", + "module": "Constants" + }, + "LINE_LOOP": { + "name": "LINE_LOOP", + "class": "p5", + "module": "Constants" + }, + "TRIANGLES": { + "name": "TRIANGLES", + "class": "p5", + "module": "Constants" + }, + "TRIANGLE_FAN": { + "name": "TRIANGLE_FAN", + "class": "p5", + "module": "Constants" + }, + "TRIANGLE_STRIP": { + "name": "TRIANGLE_STRIP", + "class": "p5", + "module": "Constants" + }, + "QUADS": { + "name": "QUADS", + "class": "p5", + "module": "Constants" + }, + "QUAD_STRIP": { + "name": "QUAD_STRIP", + "class": "p5", + "module": "Constants" + }, + "TESS": { + "name": "TESS", + "class": "p5", + "module": "Constants" + }, + "CLOSE": { + "name": "CLOSE", + "class": "p5", + "module": "Constants" + }, + "OPEN": { + "name": "OPEN", + "class": "p5", + "module": "Constants" + }, + "CHORD": { + "name": "CHORD", + "class": "p5", + "module": "Constants" + }, + "PIE": { + "name": "PIE", + "class": "p5", + "module": "Constants" + }, + "PROJECT": { + "name": "PROJECT", + "class": "p5", + "module": "Constants" + }, + "SQUARE": { + "name": "SQUARE", + "class": "p5", + "module": "Constants" + }, + "ROUND": { + "name": "ROUND", + "class": "p5", + "module": "Constants" + }, + "BEVEL": { + "name": "BEVEL", + "class": "p5", + "module": "Constants" + }, + "MITER": { + "name": "MITER", + "class": "p5", + "module": "Constants" + }, + "RGB": { + "name": "RGB", + "class": "p5", + "module": "Constants" + }, + "HSB": { + "name": "HSB", + "class": "p5", + "module": "Constants" + }, + "HSL": { + "name": "HSL", + "class": "p5", + "module": "Constants" + }, + "AUTO": { + "name": "AUTO", + "class": "p5", + "module": "Constants" + }, + "ALT": { + "name": "ALT", + "class": "p5", + "module": "Constants" + }, + "BACKSPACE": { + "name": "BACKSPACE", + "class": "p5", + "module": "Constants" + }, + "CONTROL": { + "name": "CONTROL", + "class": "p5", + "module": "Constants" + }, + "DELETE": { + "name": "DELETE", + "class": "p5", + "module": "Constants" + }, + "DOWN_ARROW": { + "name": "DOWN_ARROW", + "class": "p5", + "module": "Constants" + }, + "ENTER": { + "name": "ENTER", + "class": "p5", + "module": "Constants" + }, + "ESCAPE": { + "name": "ESCAPE", + "class": "p5", + "module": "Constants" + }, + "LEFT_ARROW": { + "name": "LEFT_ARROW", + "class": "p5", + "module": "Constants" + }, + "OPTION": { + "name": "OPTION", + "class": "p5", + "module": "Constants" + }, + "RETURN": { + "name": "RETURN", + "class": "p5", + "module": "Constants" + }, + "RIGHT_ARROW": { + "name": "RIGHT_ARROW", + "class": "p5", + "module": "Constants" + }, + "SHIFT": { + "name": "SHIFT", + "class": "p5", + "module": "Constants" + }, + "TAB": { + "name": "TAB", + "class": "p5", + "module": "Constants" + }, + "UP_ARROW": { + "name": "UP_ARROW", + "class": "p5", + "module": "Constants" + }, + "BLEND": { + "name": "BLEND", + "class": "p5", + "module": "Constants" + }, + "REMOVE": { + "name": "REMOVE", + "class": "p5", + "module": "Constants" + }, + "ADD": { + "name": "ADD", + "class": "p5", + "module": "Constants" + }, + "DARKEST": { + "name": "DARKEST", + "class": "p5", + "module": "Constants" + }, + "LIGHTEST": { + "name": "LIGHTEST", + "class": "p5", + "module": "Constants" + }, + "DIFFERENCE": { + "name": "DIFFERENCE", + "class": "p5", + "module": "Constants" + }, + "SUBTRACT": { + "name": "SUBTRACT", + "class": "p5", + "module": "Constants" + }, + "EXCLUSION": { + "name": "EXCLUSION", + "class": "p5", + "module": "Constants" + }, + "MULTIPLY": { + "name": "MULTIPLY", + "class": "p5", + "module": "Constants" + }, + "SCREEN": { + "name": "SCREEN", + "class": "p5", + "module": "Constants" + }, + "REPLACE": { + "name": "REPLACE", + "class": "p5", + "module": "Constants" + }, + "OVERLAY": { + "name": "OVERLAY", + "class": "p5", + "module": "Constants" + }, + "HARD_LIGHT": { + "name": "HARD_LIGHT", + "class": "p5", + "module": "Constants" + }, + "SOFT_LIGHT": { + "name": "SOFT_LIGHT", + "class": "p5", + "module": "Constants" + }, + "DODGE": { + "name": "DODGE", + "class": "p5", + "module": "Constants" + }, + "BURN": { + "name": "BURN", + "class": "p5", + "module": "Constants" + }, + "THRESHOLD": { + "name": "THRESHOLD", + "class": "p5", + "module": "Constants" + }, + "GRAY": { + "name": "GRAY", + "class": "p5", + "module": "Constants" + }, + "OPAQUE": { + "name": "OPAQUE", + "class": "p5", + "module": "Constants" + }, + "INVERT": { + "name": "INVERT", + "class": "p5", + "module": "Constants" + }, + "POSTERIZE": { + "name": "POSTERIZE", + "class": "p5", + "module": "Constants" + }, + "DILATE": { + "name": "DILATE", + "class": "p5", + "module": "Constants" + }, + "ERODE": { + "name": "ERODE", + "class": "p5", + "module": "Constants" + }, + "BLUR": { + "name": "BLUR", + "class": "p5", + "module": "Constants" + }, + "NORMAL": { + "name": "NORMAL", + "class": "p5", + "module": "Constants" + }, + "ITALIC": { + "name": "ITALIC", + "class": "p5", + "module": "Constants" + }, + "BOLD": { + "name": "BOLD", + "class": "p5", + "module": "Constants" + }, + "BOLDITALIC": { + "name": "BOLDITALIC", + "class": "p5", + "module": "Constants" + }, + "CHAR": { + "name": "CHAR", + "class": "p5", + "module": "Constants" + }, + "WORD": { + "name": "WORD", + "class": "p5", + "module": "Constants" + }, + "LINEAR": { + "name": "LINEAR", + "class": "p5", + "module": "Constants" + }, + "QUADRATIC": { + "name": "QUADRATIC", + "class": "p5", + "module": "Constants" + }, + "BEZIER": { + "name": "BEZIER", + "class": "p5", + "module": "Constants" + }, + "CURVE": { + "name": "CURVE", + "class": "p5", + "module": "Constants" + }, + "STROKE": { + "name": "STROKE", + "class": "p5", + "module": "Constants" + }, + "FILL": { + "name": "FILL", + "class": "p5", + "module": "Constants" + }, + "TEXTURE": { + "name": "TEXTURE", + "class": "p5", + "module": "Constants" + }, + "IMMEDIATE": { + "name": "IMMEDIATE", + "class": "p5", + "module": "Constants" + }, + "IMAGE": { + "name": "IMAGE", + "class": "p5", + "module": "Constants" + }, + "NEAREST": { + "name": "NEAREST", + "class": "p5", + "module": "Constants" + }, + "REPEAT": { + "name": "REPEAT", + "class": "p5", + "module": "Constants" + }, + "CLAMP": { + "name": "CLAMP", + "class": "p5", + "module": "Constants" + }, + "MIRROR": { + "name": "MIRROR", + "class": "p5", + "module": "Constants" + }, + "FLAT": { + "name": "FLAT", + "class": "p5", + "module": "Constants" + }, + "SMOOTH": { + "name": "SMOOTH", + "class": "p5", + "module": "Constants" + }, + "LANDSCAPE": { + "name": "LANDSCAPE", + "class": "p5", + "module": "Constants" + }, + "PORTRAIT": { + "name": "PORTRAIT", + "class": "p5", + "module": "Constants" + }, + "GRID": { + "name": "GRID", + "class": "p5", + "module": "Constants" + }, + "AXES": { + "name": "AXES", + "class": "p5", + "module": "Constants" + }, + "LABEL": { + "name": "LABEL", + "class": "p5", + "module": "Constants" + }, + "FALLBACK": { + "name": "FALLBACK", + "class": "p5", + "module": "Constants" + }, + "CONTAIN": { + "name": "CONTAIN", + "class": "p5", + "module": "Constants" + }, + "COVER": { + "name": "COVER", + "class": "p5", + "module": "Constants" + }, + "UNSIGNED_BYTE": { + "name": "UNSIGNED_BYTE", + "class": "p5", + "module": "Constants" + }, + "UNSIGNED_INT": { + "name": "UNSIGNED_INT", + "class": "p5", + "module": "Constants" + }, + "FLOAT": { + "name": "FLOAT", + "class": "p5", + "module": "Constants" + }, + "HALF_FLOAT": { + "name": "HALF_FLOAT", + "class": "p5", + "module": "Constants" + }, + "RGBA": { + "name": "RGBA", + "class": "p5", + "module": "Constants" + }, + "print": { + "name": "print", + "params": [ + { + "name": "contents", + "description": "

content to print to the console.

\n", + "type": "Any" + } + ], + "class": "p5", + "module": "Environment" + }, + "frameCount": { + "name": "frameCount", + "class": "p5", + "module": "Environment" + }, + "deltaTime": { + "name": "deltaTime", + "class": "p5", + "module": "Environment" + }, + "focused": { + "name": "focused", + "class": "p5", + "module": "Environment" + }, + "cursor": { + "name": "cursor", + "params": [ + { + "name": "type", + "description": "

Built-in: either ARROW, CROSS, HAND, MOVE, TEXT, or WAIT.\n Native CSS properties: 'grab', 'progress', and so on.\n Path to cursor image.

\n", + "type": "String|Constant" + }, + { + "name": "x", + "description": "

horizontal active spot of the cursor.

\n", + "type": "Number", + "optional": true + }, + { + "name": "y", + "description": "

vertical active spot of the cursor.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "Environment" + }, + "frameRate": { + "name": "frameRate", + "class": "p5", + "module": "Environment", + "overloads": [ + { + "params": [ + { + "name": "fps", + "description": "

number of frames to draw per second.

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [] + } + ] + }, + "getTargetFrameRate": { + "name": "getTargetFrameRate", + "class": "p5", + "module": "Environment" + }, + "noCursor": { + "name": "noCursor", + "class": "p5", + "module": "Environment" + }, + "webglVersion": { + "name": "webglVersion", + "class": "p5", + "module": "Environment" + }, + "displayWidth": { + "name": "displayWidth", + "class": "p5", + "module": "Environment" + }, + "displayHeight": { + "name": "displayHeight", + "class": "p5", + "module": "Environment" + }, + "windowWidth": { + "name": "windowWidth", + "class": "p5", + "module": "Environment" + }, + "windowHeight": { + "name": "windowHeight", + "class": "p5", + "module": "Environment" + }, + "windowResized": { + "name": "windowResized", + "params": [ + { + "name": "event", + "description": "

optional resize Event.

\n", + "type": "UIEvent", + "optional": true + } + ], + "class": "p5", + "module": "Environment" + }, + "width": { + "name": "width", + "class": "p5", + "module": "Environment" + }, + "height": { + "name": "height", + "class": "p5", + "module": "Environment" + }, + "fullscreen": { + "name": "fullscreen", + "params": [ + { + "name": "val", + "description": "

whether the sketch should be in fullscreen mode.

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5", + "module": "Environment" + }, + "pixelDensity": { + "name": "pixelDensity", + "class": "p5", + "module": "Environment", + "overloads": [ + { + "params": [ + { + "name": "val", + "description": "

desired pixel density.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [] + } + ] + }, + "displayDensity": { + "name": "displayDensity", + "class": "p5", + "module": "Environment" + }, + "getURL": { + "name": "getURL", + "class": "p5", + "module": "Environment" + }, + "getURLPath": { + "name": "getURLPath", + "class": "p5", + "module": "Environment" + }, + "getURLParams": { + "name": "getURLParams", + "class": "p5", + "module": "Environment" + }, + "preload": { + "name": "preload", + "class": "p5", + "module": "Structure" + }, + "setup": { + "name": "setup", + "class": "p5", + "module": "Structure" + }, + "draw": { + "name": "draw", + "class": "p5", + "module": "Structure" + }, + "remove": { + "name": "remove", + "class": "p5", + "module": "Structure" + }, + "disableFriendlyErrors": { + "name": "disableFriendlyErrors", + "class": "p5", + "module": "Structure" + }, + "let": { + "name": "let", + "class": "p5", + "module": "Foundation" + }, + "const": { + "name": "const", + "class": "p5", + "module": "Foundation" + }, + "===": { + "name": "===", + "class": "p5", + "module": "Foundation" + }, + ">": { + "name": ">", + "class": "p5", + "module": "Foundation" + }, + ">=": { + "name": ">=", + "class": "p5", + "module": "Foundation" + }, + "<": { + "name": "<", + "class": "p5", + "module": "Foundation" + }, + "<=": { + "name": "<=", + "class": "p5", + "module": "Foundation" + }, + "if-else": { + "name": "if-else", + "class": "p5", + "module": "Foundation" + }, + "function": { + "name": "function", + "class": "p5", + "module": "Foundation" + }, + "return": { + "name": "return", + "class": "p5", + "module": "Foundation" + }, + "boolean": { + "name": "boolean", + "params": [ + { + "name": "n", + "description": "

value to parse

\n", + "type": "String|Boolean|Number|Array" + } + ], + "class": "p5", + "module": "Data" + }, + "string": { + "name": "string", + "class": "p5", + "module": "Foundation" + }, + "number": { + "name": "number", + "class": "p5", + "module": "Foundation" + }, + "object": { + "name": "object", + "class": "p5", + "module": "Foundation" + }, + "class": { + "name": "class", + "class": "p5", + "module": "Foundation" + }, + "for": { + "name": "for", + "class": "p5", + "module": "Foundation" + }, + "while": { + "name": "while", + "class": "p5", + "module": "Foundation" + }, + "createCanvas": { + "name": "createCanvas", + "class": "p5", + "module": "Rendering", + "overloads": [ + { + "params": [ + { + "name": "w", + "description": "

width of the canvas

\n", + "type": "Number" + }, + { + "name": "h", + "description": "

height of the canvas

\n", + "type": "Number" + }, + { + "name": "renderer", + "description": "

either P2D or WEBGL

\n", + "type": "Constant", + "optional": true + }, + { + "name": "canvas", + "description": "

existing html canvas element

\n", + "type": "HTMLCanvasElement", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "w", + "description": "", + "type": "Number" + }, + { + "name": "h", + "description": "", + "type": "Number" + }, + { + "name": "canvas", + "description": "", + "type": "HTMLCanvasElement", + "optional": true + } + ] + } + ] + }, + "resizeCanvas": { + "name": "resizeCanvas", + "params": [ + { + "name": "w", + "description": "

width of the canvas

\n", + "type": "Number" + }, + { + "name": "h", + "description": "

height of the canvas

\n", + "type": "Number" + }, + { + "name": "noRedraw", + "description": "

don't redraw the canvas immediately

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5", + "module": "Rendering" + }, + "noCanvas": { + "name": "noCanvas", + "class": "p5", + "module": "Rendering" + }, + "createGraphics": { + "name": "createGraphics", + "class": "p5", + "module": "Rendering", + "overloads": [ + { + "params": [ + { + "name": "w", + "description": "

width of the offscreen graphics buffer

\n", + "type": "Number" + }, + { + "name": "h", + "description": "

height of the offscreen graphics buffer

\n", + "type": "Number" + }, + { + "name": "renderer", + "description": "

either P2D or WEBGL\n undefined defaults to p2d

\n", + "type": "Constant", + "optional": true + }, + { + "name": "canvas", + "description": "

existing html canvas element

\n", + "type": "HTMLCanvasElement", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "w", + "description": "", + "type": "Number" + }, + { + "name": "h", + "description": "", + "type": "Number" + }, + { + "name": "canvas", + "description": "", + "type": "HTMLCanvasElement", + "optional": true + } + ] + } + ] + }, + "createFramebuffer": { + "name": "createFramebuffer", + "params": [ + { + "name": "options", + "description": "

An optional object with configuration

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5", + "module": "Rendering" + }, + "clearDepth": { + "name": "clearDepth", + "params": [ + { + "name": "depth", + "description": "

The value, between 0 and 1, to reset the depth to, where\n0 corresponds to a value as close as possible to the camera before getting\nclipped, and 1 corresponds to a value as far away from the camera as possible.\nThe default value is 1.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "Rendering" + }, + "blendMode": { + "name": "blendMode", + "params": [ + { + "name": "mode", + "description": "

blend mode to set for canvas.\n either BLEND, DARKEST, LIGHTEST, DIFFERENCE, MULTIPLY,\n EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,\n SOFT_LIGHT, DODGE, BURN, ADD, REMOVE or SUBTRACT

\n", + "type": "Constant" + } + ], + "class": "p5", + "module": "Rendering" + }, + "drawingContext": { + "name": "drawingContext", + "class": "p5", + "module": "Rendering" + }, + "noLoop": { + "name": "noLoop", + "class": "p5", + "module": "Structure" + }, + "loop": { + "name": "loop", + "class": "p5", + "module": "Structure" + }, + "isLooping": { + "name": "isLooping", + "class": "p5", + "module": "Structure" + }, + "push": { + "name": "push", + "class": "p5", + "module": "Structure" + }, + "pop": { + "name": "pop", + "class": "p5", + "module": "Structure" + }, + "redraw": { + "name": "redraw", + "params": [ + { + "name": "n", + "description": "

Redraw for n-times. The default value is 1.

\n", + "type": "Integer", + "optional": true + } + ], + "class": "p5", + "module": "Structure" + }, + "p5": { + "name": "p5", + "params": [ + { + "name": "sketch", + "description": "

a function containing a p5.js sketch

\n", + "type": "Object" + }, + { + "name": "node", + "description": "

ID or pointer to HTML DOM node to contain sketch in

\n", + "type": "String|Object" + } + ], + "class": "p5", + "module": "Structure" + }, + "applyMatrix": { + "name": "applyMatrix", + "class": "p5", + "module": "Transform", + "overloads": [ + { + "params": [ + { + "name": "arr", + "description": "

an array of numbers - should be 6 or 16 length (2×3 or 4×4 matrix values)

\n", + "type": "Array" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "a", + "description": "

numbers which define the 2×3 or 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "b", + "description": "

numbers which define the 2×3 or 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "c", + "description": "

numbers which define the 2×3 or 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "d", + "description": "

numbers which define the 2×3 or 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "e", + "description": "

numbers which define the 2×3 or 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "f", + "description": "

numbers which define the 2×3 or 4×4 matrix to be multiplied

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "a", + "description": "", + "type": "Number" + }, + { + "name": "b", + "description": "", + "type": "Number" + }, + { + "name": "c", + "description": "", + "type": "Number" + }, + { + "name": "d", + "description": "", + "type": "Number" + }, + { + "name": "e", + "description": "", + "type": "Number" + }, + { + "name": "f", + "description": "", + "type": "Number" + }, + { + "name": "g", + "description": "

numbers which define the 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "h", + "description": "

numbers which define the 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "i", + "description": "

numbers which define the 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "j", + "description": "

numbers which define the 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "k", + "description": "

numbers which define the 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "l", + "description": "

numbers which define the 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "m", + "description": "

numbers which define the 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "n", + "description": "

numbers which define the 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "o", + "description": "

numbers which define the 4×4 matrix to be multiplied

\n", + "type": "Number" + }, + { + "name": "p", + "description": "

numbers which define the 4×4 matrix to be multiplied

\n", + "type": "Number" + } + ], + "chainable": 1 + } + ] + }, + "resetMatrix": { + "name": "resetMatrix", + "class": "p5", + "module": "Transform" + }, + "rotate": { + "name": "rotate", + "params": [ + { + "name": "angle", + "description": "

the angle of rotation, specified in radians\n or degrees, depending on current angleMode

\n", + "type": "Number" + }, + { + "name": "axis", + "description": "

(in 3d) the axis to rotate around

\n", + "type": "p5.Vector|Number[]", + "optional": true + } + ], + "class": "p5", + "module": "Transform" + }, + "rotateX": { + "name": "rotateX", + "params": [ + { + "name": "angle", + "description": "

the angle of rotation, specified in radians\n or degrees, depending on current angleMode

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Transform" + }, + "rotateY": { + "name": "rotateY", + "params": [ + { + "name": "angle", + "description": "

the angle of rotation, specified in radians\n or degrees, depending on current angleMode

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Transform" + }, + "rotateZ": { + "name": "rotateZ", + "params": [ + { + "name": "angle", + "description": "

the angle of rotation, specified in radians\n or degrees, depending on current angleMode

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Transform" + }, + "scale": { + "name": "scale", + "class": "p5", + "module": "Transform", + "overloads": [ + { + "params": [ + { + "name": "s", + "description": "

percent to scale the object, or percentage to\n scale the object in the x-axis if multiple arguments\n are given

\n", + "type": "Number|p5.Vector|Number[]" + }, + { + "name": "y", + "description": "

percent to scale the object in the y-axis

\n", + "type": "Number", + "optional": true + }, + { + "name": "z", + "description": "

percent to scale the object in the z-axis (webgl only)

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "scales", + "description": "

per-axis percents to scale the object

\n", + "type": "p5.Vector|Number[]" + } + ], + "chainable": 1 + } + ] + }, + "shearX": { + "name": "shearX", + "params": [ + { + "name": "angle", + "description": "

angle of shear specified in radians or degrees,\n depending on current angleMode

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Transform" + }, + "shearY": { + "name": "shearY", + "params": [ + { + "name": "angle", + "description": "

angle of shear specified in radians or degrees,\n depending on current angleMode

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Transform" + }, + "translate": { + "name": "translate", + "class": "p5", + "module": "Transform", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

left/right translation

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

up/down translation

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

forward/backward translation (WEBGL only)

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "vector", + "description": "

the vector to translate by

\n", + "type": "p5.Vector" + } + ], + "chainable": 1 + } + ] + }, + "storeItem": { + "name": "storeItem", + "params": [ + { + "name": "key", + "description": "", + "type": "String" + }, + { + "name": "value", + "description": "", + "type": "String|Number|Object|Boolean|p5.Color|p5.Vector" + } + ], + "class": "p5", + "module": "Data" + }, + "getItem": { + "name": "getItem", + "params": [ + { + "name": "key", + "description": "

name that you wish to use to store in local storage

\n", + "type": "String" + } + ], + "class": "p5", + "module": "Data" + }, + "clearStorage": { + "name": "clearStorage", + "class": "p5", + "module": "Data" + }, + "removeItem": { + "name": "removeItem", + "params": [ + { + "name": "key", + "description": "", + "type": "String" + } + ], + "class": "p5", + "module": "Data" + }, + "createStringDict": { + "name": "createStringDict", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "key", + "description": "", + "type": "String" + }, + { + "name": "value", + "description": "", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "object", + "description": "

object

\n", + "type": "Object" + } + ] + } + ] + }, + "createNumberDict": { + "name": "createNumberDict", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "key", + "description": "", + "type": "Number" + }, + { + "name": "value", + "description": "", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "object", + "description": "

object

\n", + "type": "Object" + } + ] + } + ] + }, + "select": { + "name": "select", + "params": [ + { + "name": "selectors", + "description": "

CSS selector string of element to search for.

\n", + "type": "String" + }, + { + "name": "container", + "description": "

CSS selector string, p5.Element, or\n HTMLElement to search within.

\n", + "type": "String|p5.Element|HTMLElement", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "selectAll": { + "name": "selectAll", + "params": [ + { + "name": "selectors", + "description": "

CSS selector string of element to search for.

\n", + "type": "String" + }, + { + "name": "container", + "description": "

CSS selector string, p5.Element, or\n HTMLElement to search within.

\n", + "type": "String|p5.Element|HTMLElement", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "removeElements": { + "name": "removeElements", + "class": "p5", + "module": "DOM" + }, + "changed": { + "name": "changed", + "params": [ + { + "name": "fxn", + "description": "

function to call when the element changes.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5", + "module": "DOM" + }, + "input": { + "name": "input", + "params": [ + { + "name": "fxn", + "description": "

function to call when input is detected within\n the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5", + "module": "DOM" + }, + "createDiv": { + "name": "createDiv", + "params": [ + { + "name": "html", + "description": "

inner HTML for the new <div></div> element.

\n", + "type": "String", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createP": { + "name": "createP", + "params": [ + { + "name": "html", + "description": "

inner HTML for the new <p></p> element.

\n", + "type": "String", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createSpan": { + "name": "createSpan", + "params": [ + { + "name": "html", + "description": "

inner HTML for the new <span></span> element.

\n", + "type": "String", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createImg": { + "name": "createImg", + "class": "p5", + "module": "DOM", + "overloads": [ + { + "params": [ + { + "name": "src", + "description": "

relative path or URL for the image.

\n", + "type": "String" + }, + { + "name": "alt", + "description": "

alternate text for the image.

\n", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "src", + "description": "", + "type": "String" + }, + { + "name": "alt", + "description": "", + "type": "String" + }, + { + "name": "crossOrigin", + "description": "

crossOrigin property to use when fetching the image.

\n", + "type": "String", + "optional": true + }, + { + "name": "successCallback", + "description": "

function to call once the image loads. The new image will be passed\n to the function as a p5.Element object.

\n", + "type": "Function", + "optional": true + } + ] + } + ] + }, + "createA": { + "name": "createA", + "params": [ + { + "name": "href", + "description": "

URL of linked page.

\n", + "type": "String" + }, + { + "name": "html", + "description": "

inner HTML of link element to display.

\n", + "type": "String" + }, + { + "name": "target", + "description": "

target where the new link should open,\n either '_blank', '_self', '_parent', or '_top'.

\n", + "type": "String", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createSlider": { + "name": "createSlider", + "params": [ + { + "name": "min", + "description": "

minimum value of the slider.

\n", + "type": "Number" + }, + { + "name": "max", + "description": "

maximum value of the slider.

\n", + "type": "Number" + }, + { + "name": "value", + "description": "

default value of the slider.

\n", + "type": "Number", + "optional": true + }, + { + "name": "step", + "description": "

size for each step in the slider's range.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createButton": { + "name": "createButton", + "params": [ + { + "name": "label", + "description": "

label displayed on the button.

\n", + "type": "String" + }, + { + "name": "value", + "description": "

value of the button.

\n", + "type": "String", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createCheckbox": { + "name": "createCheckbox", + "params": [ + { + "name": "label", + "description": "

label displayed after the checkbox.

\n", + "type": "String", + "optional": true + }, + { + "name": "value", + "description": "

value of the checkbox. Checked is true and unchecked is false.

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createSelect": { + "name": "createSelect", + "class": "p5", + "module": "DOM", + "overloads": [ + { + "params": [ + { + "name": "multiple", + "description": "

support multiple selections.

\n", + "type": "Boolean", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "existing", + "description": "

select element to wrap, either as a p5.Element or\n a HTMLSelectElement.

\n", + "type": "Object" + } + ] + } + ] + }, + "createRadio": { + "name": "createRadio", + "class": "p5", + "module": "DOM", + "overloads": [ + { + "params": [ + { + "name": "containerElement", + "description": "

container HTML Element, either a <div></div>\nor <span></span>.

\n", + "type": "Object", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "name", + "description": "

name parameter assigned to each option's <input></input> element.

\n", + "type": "String", + "optional": true + } + ] + }, + { + "params": [] + } + ] + }, + "createColorPicker": { + "name": "createColorPicker", + "params": [ + { + "name": "value", + "description": "

default color as a CSS color string.

\n", + "type": "String|p5.Color", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createInput": { + "name": "createInput", + "class": "p5", + "module": "DOM", + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "

default value of the input box. Defaults to an empty string ''.

\n", + "type": "String", + "optional": true + }, + { + "name": "type", + "description": "

type of input. Defaults to 'text'.

\n", + "type": "String", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "", + "type": "String", + "optional": true + } + ] + } + ] + }, + "createFileInput": { + "name": "createFileInput", + "params": [ + { + "name": "callback", + "description": "

function to call once the file loads.

\n", + "type": "Function" + }, + { + "name": "multiple", + "description": "

allow multiple files to be selected.

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createVideo": { + "name": "createVideo", + "params": [ + { + "name": "src", + "description": "

path to a video file, or an array of paths for\n supporting different browsers.

\n", + "type": "String|String[]" + }, + { + "name": "callback", + "description": "

function to call once the video is ready to play.

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createAudio": { + "name": "createAudio", + "params": [ + { + "name": "src", + "description": "

path to an audio file, or an array of paths\n for supporting different browsers.

\n", + "type": "String|String[]", + "optional": true + }, + { + "name": "callback", + "description": "

function to call once the audio is ready to play.

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createCapture": { + "name": "createCapture", + "params": [ + { + "name": "type", + "description": "

type of capture, either AUDIO or VIDEO,\n or a constraints object. Both video and audio\n audio streams are captured by default.

\n", + "type": "String|Constant|Object", + "optional": true + }, + { + "name": "callback", + "description": "

function to call once the stream\n has loaded.

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "createElement": { + "name": "createElement", + "params": [ + { + "name": "tag", + "description": "

tag for the new element.

\n", + "type": "String" + }, + { + "name": "content", + "description": "

HTML content to insert into the element.

\n", + "type": "String", + "optional": true + } + ], + "class": "p5", + "module": "DOM" + }, + "deviceOrientation": { + "name": "deviceOrientation", + "class": "p5", + "module": "Events" + }, + "accelerationX": { + "name": "accelerationX", + "class": "p5", + "module": "Events" + }, + "accelerationY": { + "name": "accelerationY", + "class": "p5", + "module": "Events" + }, + "accelerationZ": { + "name": "accelerationZ", + "class": "p5", + "module": "Events" + }, + "pAccelerationX": { + "name": "pAccelerationX", + "class": "p5", + "module": "Events" + }, + "pAccelerationY": { + "name": "pAccelerationY", + "class": "p5", + "module": "Events" + }, + "pAccelerationZ": { + "name": "pAccelerationZ", + "class": "p5", + "module": "Events" + }, + "rotationX": { + "name": "rotationX", + "class": "p5", + "module": "Events" + }, + "rotationY": { + "name": "rotationY", + "class": "p5", + "module": "Events" + }, + "rotationZ": { + "name": "rotationZ", + "class": "p5", + "module": "Events" + }, + "pRotationX": { + "name": "pRotationX", + "class": "p5", + "module": "Events" + }, + "pRotationY": { + "name": "pRotationY", + "class": "p5", + "module": "Events" + }, + "pRotationZ": { + "name": "pRotationZ", + "class": "p5", + "module": "Events" + }, + "turnAxis": { + "name": "turnAxis", + "class": "p5", + "module": "Events" + }, + "setMoveThreshold": { + "name": "setMoveThreshold", + "params": [ + { + "name": "value", + "description": "

The threshold value

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Events" + }, + "setShakeThreshold": { + "name": "setShakeThreshold", + "params": [ + { + "name": "value", + "description": "

The threshold value

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Events" + }, + "deviceMoved": { + "name": "deviceMoved", + "class": "p5", + "module": "Events" + }, + "deviceTurned": { + "name": "deviceTurned", + "class": "p5", + "module": "Events" + }, + "deviceShaken": { + "name": "deviceShaken", + "class": "p5", + "module": "Events" + }, + "keyIsPressed": { + "name": "keyIsPressed", + "class": "p5", + "module": "Events" + }, + "key": { + "name": "key", + "class": "p5", + "module": "Events" + }, + "keyCode": { + "name": "keyCode", + "class": "p5", + "module": "Events" + }, + "keyPressed": { + "name": "keyPressed", + "params": [ + { + "name": "event", + "description": "

optional KeyboardEvent callback argument.

\n", + "type": "KeyboardEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "keyReleased": { + "name": "keyReleased", + "params": [ + { + "name": "event", + "description": "

optional KeyboardEvent callback argument.

\n", + "type": "KeyboardEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "keyTyped": { + "name": "keyTyped", + "params": [ + { + "name": "event", + "description": "

optional KeyboardEvent callback argument.

\n", + "type": "KeyboardEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "keyIsDown": { + "name": "keyIsDown", + "params": [ + { + "name": "code", + "description": "

The key to check for.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Events" + }, + "movedX": { + "name": "movedX", + "class": "p5", + "module": "Events" + }, + "movedY": { + "name": "movedY", + "class": "p5", + "module": "Events" + }, + "mouseX": { + "name": "mouseX", + "class": "p5", + "module": "Events" + }, + "mouseY": { + "name": "mouseY", + "class": "p5", + "module": "Events" + }, + "pmouseX": { + "name": "pmouseX", + "class": "p5", + "module": "Events" + }, + "pmouseY": { + "name": "pmouseY", + "class": "p5", + "module": "Events" + }, + "winMouseX": { + "name": "winMouseX", + "class": "p5", + "module": "Events" + }, + "winMouseY": { + "name": "winMouseY", + "class": "p5", + "module": "Events" + }, + "pwinMouseX": { + "name": "pwinMouseX", + "class": "p5", + "module": "Events" + }, + "pwinMouseY": { + "name": "pwinMouseY", + "class": "p5", + "module": "Events" + }, + "mouseButton": { + "name": "mouseButton", + "class": "p5", + "module": "Events" + }, + "mouseIsPressed": { + "name": "mouseIsPressed", + "class": "p5", + "module": "Events" + }, + "mouseMoved": { + "name": "mouseMoved", + "params": [ + { + "name": "event", + "description": "

optional MouseEvent callback argument.

\n", + "type": "MouseEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "mouseDragged": { + "name": "mouseDragged", + "params": [ + { + "name": "event", + "description": "

optional MouseEvent callback argument.

\n", + "type": "MouseEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "mousePressed": { + "name": "mousePressed", + "params": [ + { + "name": "event", + "description": "

optional MouseEvent callback argument.

\n", + "type": "MouseEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "mouseReleased": { + "name": "mouseReleased", + "params": [ + { + "name": "event", + "description": "

optional MouseEvent callback argument.

\n", + "type": "MouseEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "mouseClicked": { + "name": "mouseClicked", + "params": [ + { + "name": "event", + "description": "

optional MouseEvent callback argument.

\n", + "type": "MouseEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "doubleClicked": { + "name": "doubleClicked", + "params": [ + { + "name": "event", + "description": "

optional MouseEvent callback argument.

\n", + "type": "MouseEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "mouseWheel": { + "name": "mouseWheel", + "params": [ + { + "name": "event", + "description": "

optional WheelEvent callback argument.

\n", + "type": "WheelEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "requestPointerLock": { + "name": "requestPointerLock", + "class": "p5", + "module": "Events" + }, + "exitPointerLock": { + "name": "exitPointerLock", + "class": "p5", + "module": "Events" + }, + "touches": { + "name": "touches", + "class": "p5", + "module": "Events" + }, + "touchStarted": { + "name": "touchStarted", + "params": [ + { + "name": "event", + "description": "

optional TouchEvent callback argument.

\n", + "type": "TouchEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "touchMoved": { + "name": "touchMoved", + "params": [ + { + "name": "event", + "description": "

optional TouchEvent callback argument.

\n", + "type": "TouchEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "touchEnded": { + "name": "touchEnded", + "params": [ + { + "name": "event", + "description": "

optional TouchEvent callback argument.

\n", + "type": "TouchEvent", + "optional": true + } + ], + "class": "p5", + "module": "Events" + }, + "createImage": { + "name": "createImage", + "params": [ + { + "name": "width", + "description": "

width in pixels.

\n", + "type": "Integer" + }, + { + "name": "height", + "description": "

height in pixels.

\n", + "type": "Integer" + } + ], + "class": "p5", + "module": "Image" + }, + "saveCanvas": { + "name": "saveCanvas", + "class": "p5", + "module": "Image", + "overloads": [ + { + "params": [ + { + "name": "selectedCanvas", + "description": "

reference to a\n specific HTML5 canvas element.

\n", + "type": "p5.Framebuffer|p5.Element|HTMLCanvasElement" + }, + { + "name": "filename", + "description": "

file name. Defaults to 'untitled'.

\n", + "type": "String", + "optional": true + }, + { + "name": "extension", + "description": "

file extension, either 'jpg' or 'png'. Defaults to 'png'.

\n", + "type": "String", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "filename", + "description": "", + "type": "String", + "optional": true + }, + { + "name": "extension", + "description": "", + "type": "String", + "optional": true + } + ] + } + ] + }, + "saveFrames": { + "name": "saveFrames", + "params": [ + { + "name": "filename", + "description": "

prefix of file name.

\n", + "type": "String" + }, + { + "name": "extension", + "description": "

file extension, either 'jpg' or 'png'.

\n", + "type": "String" + }, + { + "name": "duration", + "description": "

duration in seconds to record. This parameter will be constrained to be less or equal to 15.

\n", + "type": "Number" + }, + { + "name": "framerate", + "description": "

number of frames to save per second. This parameter will be constrained to be less or equal to 22.

\n", + "type": "Number" + }, + { + "name": "callback", + "description": "

callback function that will be executed\n to handle the image data. This function\n should accept an array as argument. The\n array will contain the specified number of\n frames of objects. Each object has three\n properties: imageData, filename, and extension.

\n", + "type": "Function(Array)", + "optional": true + } + ], + "class": "p5", + "module": "Image" + }, + "loadImage": { + "name": "loadImage", + "params": [ + { + "name": "path", + "description": "

path of the image to be loaded or base64 encoded image.

\n", + "type": "String" + }, + { + "name": "successCallback", + "description": "

function called with\n p5.Image once it\n loads.

\n", + "type": "function(p5.Image)", + "optional": true + }, + { + "name": "failureCallback", + "description": "

function called with event\n error if the image fails to load.

\n", + "type": "Function(Event)", + "optional": true + } + ], + "class": "p5", + "module": "Image" + }, + "saveGif": { + "name": "saveGif", + "params": [ + { + "name": "filename", + "description": "

file name of gif.

\n", + "type": "String" + }, + { + "name": "duration", + "description": "

duration in seconds to capture from the sketch.

\n", + "type": "Number" + }, + { + "name": "options", + "description": "

an object that can contain five more properties:\n delay, a Number specifying how much time to wait before recording;\n units, a String that can be either 'seconds' or 'frames'. By default it's 'seconds’;\n silent, a Boolean that defines presence of progress notifications. By default it’s false;\n notificationDuration, a Number that defines how long in seconds the final notification\n will live. By default it's 0, meaning the notification will never be removed;\n notificationID, a String that specifies the id of the notification's DOM element. By default it’s 'progressBar’.

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5", + "module": "Image" + }, + "image": { + "name": "image", + "class": "p5", + "module": "Image", + "overloads": [ + { + "params": [ + { + "name": "img", + "description": "

image to display.

\n", + "type": "p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture" + }, + { + "name": "x", + "description": "

x-coordinate of the top-left corner of the image.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the top-left corner of the image.

\n", + "type": "Number" + }, + { + "name": "width", + "description": "

width to draw the image.

\n", + "type": "Number", + "optional": true + }, + { + "name": "height", + "description": "

height to draw the image.

\n", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "img", + "description": "", + "type": "p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture" + }, + { + "name": "dx", + "description": "

the x-coordinate of the destination\n rectangle in which to draw the source image

\n", + "type": "Number" + }, + { + "name": "dy", + "description": "

the y-coordinate of the destination\n rectangle in which to draw the source image

\n", + "type": "Number" + }, + { + "name": "dWidth", + "description": "

the width of the destination rectangle

\n", + "type": "Number" + }, + { + "name": "dHeight", + "description": "

the height of the destination rectangle

\n", + "type": "Number" + }, + { + "name": "sx", + "description": "

the x-coordinate of the subsection of the source\nimage to draw into the destination rectangle

\n", + "type": "Number" + }, + { + "name": "sy", + "description": "

the y-coordinate of the subsection of the source\nimage to draw into the destination rectangle

\n", + "type": "Number" + }, + { + "name": "sWidth", + "description": "

the width of the subsection of the\n source image to draw into the destination\n rectangle

\n", + "type": "Number", + "optional": true + }, + { + "name": "sHeight", + "description": "

the height of the subsection of the\n source image to draw into the destination rectangle

\n", + "type": "Number", + "optional": true + }, + { + "name": "fit", + "description": "

either CONTAIN or COVER

\n", + "type": "Constant", + "optional": true + }, + { + "name": "xAlign", + "description": "

either LEFT, RIGHT or CENTER default is CENTER

\n", + "type": "Constant", + "optional": true + }, + { + "name": "yAlign", + "description": "

either TOP, BOTTOM or CENTER default is CENTER

\n", + "type": "Constant", + "optional": true + } + ] + } + ] + }, + "tint": { + "name": "tint", + "class": "p5", + "module": "Image", + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "

red or hue value.

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green or saturation value.

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue or brightness.

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "

CSS color string.

\n", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "gray", + "description": "

grayscale value.

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "values", + "description": "

array containing the red, green, blue &\n alpha components of the color.

\n", + "type": "Number[]" + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "

the tint color

\n", + "type": "p5.Color" + } + ] + } + ] + }, + "noTint": { + "name": "noTint", + "class": "p5", + "module": "Image" + }, + "imageMode": { + "name": "imageMode", + "params": [ + { + "name": "mode", + "description": "

either CORNER, CORNERS, or CENTER.

\n", + "type": "Constant" + } + ], + "class": "p5", + "module": "Image" + }, + "pixels": { + "name": "pixels", + "class": "p5", + "module": "Image" + }, + "blend": { + "name": "blend", + "class": "p5", + "module": "Image", + "overloads": [ + { + "params": [ + { + "name": "srcImage", + "description": "

source image.

\n", + "type": "p5.Image" + }, + { + "name": "sx", + "description": "

x-coordinate of the source's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "sy", + "description": "

y-coordinate of the source's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "sw", + "description": "

source image width.

\n", + "type": "Integer" + }, + { + "name": "sh", + "description": "

source image height.

\n", + "type": "Integer" + }, + { + "name": "dx", + "description": "

x-coordinate of the destination's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "dy", + "description": "

y-coordinate of the destination's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "dw", + "description": "

destination image width.

\n", + "type": "Integer" + }, + { + "name": "dh", + "description": "

destination image height.

\n", + "type": "Integer" + }, + { + "name": "blendMode", + "description": "

the blend mode. either\n BLEND, DARKEST, LIGHTEST, DIFFERENCE,\n MULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,\n SOFT_LIGHT, DODGE, BURN, ADD or NORMAL.

\n", + "type": "Constant" + } + ] + }, + { + "params": [ + { + "name": "sx", + "description": "", + "type": "Integer" + }, + { + "name": "sy", + "description": "", + "type": "Integer" + }, + { + "name": "sw", + "description": "", + "type": "Integer" + }, + { + "name": "sh", + "description": "", + "type": "Integer" + }, + { + "name": "dx", + "description": "", + "type": "Integer" + }, + { + "name": "dy", + "description": "", + "type": "Integer" + }, + { + "name": "dw", + "description": "", + "type": "Integer" + }, + { + "name": "dh", + "description": "", + "type": "Integer" + }, + { + "name": "blendMode", + "description": "", + "type": "Constant" + } + ] + } + ] + }, + "copy": { + "name": "copy", + "class": "p5", + "module": "Image", + "overloads": [ + { + "params": [ + { + "name": "srcImage", + "description": "

source image.

\n", + "type": "p5.Image|p5.Element" + }, + { + "name": "sx", + "description": "

x-coordinate of the source's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "sy", + "description": "

y-coordinate of the source's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "sw", + "description": "

source image width.

\n", + "type": "Integer" + }, + { + "name": "sh", + "description": "

source image height.

\n", + "type": "Integer" + }, + { + "name": "dx", + "description": "

x-coordinate of the destination's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "dy", + "description": "

y-coordinate of the destination's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "dw", + "description": "

destination image width.

\n", + "type": "Integer" + }, + { + "name": "dh", + "description": "

destination image height.

\n", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "sx", + "description": "", + "type": "Integer" + }, + { + "name": "sy", + "description": "", + "type": "Integer" + }, + { + "name": "sw", + "description": "", + "type": "Integer" + }, + { + "name": "sh", + "description": "", + "type": "Integer" + }, + { + "name": "dx", + "description": "", + "type": "Integer" + }, + { + "name": "dy", + "description": "", + "type": "Integer" + }, + { + "name": "dw", + "description": "", + "type": "Integer" + }, + { + "name": "dh", + "description": "", + "type": "Integer" + } + ] + } + ] + }, + "filter": { + "name": "filter", + "class": "p5", + "module": "Image", + "overloads": [ + { + "params": [ + { + "name": "filterType", + "description": "

either THRESHOLD, GRAY, OPAQUE, INVERT,\n POSTERIZE, BLUR, ERODE, DILATE or BLUR.

\n", + "type": "Constant" + }, + { + "name": "filterParam", + "description": "

parameter unique to each filter.

\n", + "type": "Number", + "optional": true + }, + { + "name": "useWebGL", + "description": "

flag to control whether to use fast\n WebGL filters (GPU) or original image\n filters (CPU); defaults to true.

\n", + "type": "Boolean", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "filterType", + "description": "", + "type": "Constant" + }, + { + "name": "useWebGL", + "description": "", + "type": "Boolean", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "shaderFilter", + "description": "

shader that's been loaded, with the\n frag shader using a tex0 uniform.

\n", + "type": "p5.Shader" + } + ] + } + ] + }, + "get": { + "name": "get", + "class": "p5", + "module": "Image", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x-coordinate of the pixel.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the pixel.

\n", + "type": "Number" + }, + { + "name": "w", + "description": "

width of the subsection to be returned.

\n", + "type": "Number" + }, + { + "name": "h", + "description": "

height of the subsection to be returned.

\n", + "type": "Number" + } + ] + }, + { + "params": [] + }, + { + "params": [ + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + } + ] + } + ] + }, + "loadPixels": { + "name": "loadPixels", + "class": "p5", + "module": "Image" + }, + "set": { + "name": "set", + "params": [ + { + "name": "x", + "description": "

x-coordinate of the pixel.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the pixel.

\n", + "type": "Number" + }, + { + "name": "c", + "description": "

grayscale value | pixel array |\n p5.Color object | p5.Image to copy.

\n", + "type": "Number|Number[]|Object" + } + ], + "class": "p5", + "module": "Image" + }, + "updatePixels": { + "name": "updatePixels", + "params": [ + { + "name": "x", + "description": "

x-coordinate of the upper-left corner of region\n to update.

\n", + "type": "Number", + "optional": true + }, + { + "name": "y", + "description": "

y-coordinate of the upper-left corner of region\n to update.

\n", + "type": "Number", + "optional": true + }, + { + "name": "w", + "description": "

width of region to update.

\n", + "type": "Number", + "optional": true + }, + { + "name": "h", + "description": "

height of region to update.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "Image" + }, + "loadJSON": { + "name": "loadJSON", + "class": "p5", + "module": "IO", + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "

name of the file or url to load

\n", + "type": "String" + }, + { + "name": "jsonpOptions", + "description": "

options object for jsonp related settings

\n", + "type": "Object", + "optional": true + }, + { + "name": "datatype", + "description": "

\"json\" or \"jsonp\"

\n", + "type": "String", + "optional": true + }, + { + "name": "callback", + "description": "

function to be executed after\n loadJSON() completes, data is passed\n in as first argument

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

function to be executed if\n there is an error, response is passed\n in as first argument

\n", + "type": "Function", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "path", + "description": "", + "type": "String" + }, + { + "name": "datatype", + "description": "", + "type": "String" + }, + { + "name": "callback", + "description": "", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "", + "type": "Function", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "path", + "description": "", + "type": "String" + }, + { + "name": "callback", + "description": "", + "type": "Function" + }, + { + "name": "errorCallback", + "description": "", + "type": "Function", + "optional": true + } + ] + } + ] + }, + "loadStrings": { + "name": "loadStrings", + "params": [ + { + "name": "filename", + "description": "

name of the file or url to load

\n", + "type": "String" + }, + { + "name": "callback", + "description": "

function to be executed after loadStrings()\n completes, Array is passed in as first\n argument

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

function to be executed if\n there is an error, response is passed\n in as first argument

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5", + "module": "IO" + }, + "loadTable": { + "name": "loadTable", + "params": [ + { + "name": "filename", + "description": "

name of the file or URL to load

\n", + "type": "String" + }, + { + "name": "extension", + "description": "

parse the table by comma-separated values \"csv\", semicolon-separated\n values \"ssv\", or tab-separated values \"tsv\"

\n", + "type": "String", + "optional": true + }, + { + "name": "header", + "description": "

\"header\" to indicate table has header row

\n", + "type": "String", + "optional": true + }, + { + "name": "callback", + "description": "

function to be executed after\n loadTable() completes. On success, the\n Table object is passed in as the\n first argument.

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

function to be executed if\n there is an error, response is passed\n in as first argument

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5", + "module": "IO" + }, + "loadXML": { + "name": "loadXML", + "params": [ + { + "name": "filename", + "description": "

name of the file or URL to load

\n", + "type": "String" + }, + { + "name": "callback", + "description": "

function to be executed after loadXML()\n completes, XML object is passed in as\n first argument

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

function to be executed if\n there is an error, response is passed\n in as first argument

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5", + "module": "IO" + }, + "loadBytes": { + "name": "loadBytes", + "params": [ + { + "name": "file", + "description": "

name of the file or URL to load

\n", + "type": "String" + }, + { + "name": "callback", + "description": "

function to be executed after loadBytes()\n completes

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

function to be executed if there\n is an error

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5", + "module": "IO" + }, + "httpGet": { + "name": "httpGet", + "class": "p5", + "module": "IO", + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "

name of the file or url to load

\n", + "type": "String" + }, + { + "name": "datatype", + "description": "

\"json\", \"jsonp\", \"binary\", \"arrayBuffer\",\n \"xml\", or \"text\"

\n", + "type": "String", + "optional": true + }, + { + "name": "data", + "description": "

param data passed sent with request

\n", + "type": "Object|Boolean", + "optional": true + }, + { + "name": "callback", + "description": "

function to be executed after\n httpGet() completes, data is passed in\n as first argument

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

function to be executed if\n there is an error, response is passed\n in as first argument

\n", + "type": "Function", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "path", + "description": "", + "type": "String" + }, + { + "name": "data", + "description": "", + "type": "Object|Boolean" + }, + { + "name": "callback", + "description": "", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "", + "type": "Function", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "path", + "description": "", + "type": "String" + }, + { + "name": "callback", + "description": "", + "type": "Function" + }, + { + "name": "errorCallback", + "description": "", + "type": "Function", + "optional": true + } + ] + } + ] + }, + "httpPost": { + "name": "httpPost", + "class": "p5", + "module": "IO", + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "

name of the file or url to load

\n", + "type": "String" + }, + { + "name": "datatype", + "description": "

\"json\", \"jsonp\", \"xml\", or \"text\".\n If omitted, httpPost() will guess.

\n", + "type": "String", + "optional": true + }, + { + "name": "data", + "description": "

param data passed sent with request

\n", + "type": "Object|Boolean", + "optional": true + }, + { + "name": "callback", + "description": "

function to be executed after\n httpPost() completes, data is passed in\n as first argument

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

function to be executed if\n there is an error, response is passed\n in as first argument

\n", + "type": "Function", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "path", + "description": "", + "type": "String" + }, + { + "name": "data", + "description": "", + "type": "Object|Boolean" + }, + { + "name": "callback", + "description": "", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "", + "type": "Function", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "path", + "description": "", + "type": "String" + }, + { + "name": "callback", + "description": "", + "type": "Function" + }, + { + "name": "errorCallback", + "description": "", + "type": "Function", + "optional": true + } + ] + } + ] + }, + "httpDo": { + "name": "httpDo", + "class": "p5", + "module": "IO", + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "

name of the file or url to load

\n", + "type": "String" + }, + { + "name": "method", + "description": "

either \"GET\", \"POST\", or \"PUT\",\n defaults to \"GET\"

\n", + "type": "String", + "optional": true + }, + { + "name": "datatype", + "description": "

\"json\", \"jsonp\", \"xml\", or \"text\"

\n", + "type": "String", + "optional": true + }, + { + "name": "data", + "description": "

param data passed sent with request

\n", + "type": "Object", + "optional": true + }, + { + "name": "callback", + "description": "

function to be executed after\n httpGet() completes, data is passed in\n as first argument

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

function to be executed if\n there is an error, response is passed\n in as first argument

\n", + "type": "Function", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "path", + "description": "", + "type": "String" + }, + { + "name": "options", + "description": "

Request object options as documented in the\n \"fetch\" API\nreference

\n", + "type": "Object" + }, + { + "name": "callback", + "description": "", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "", + "type": "Function", + "optional": true + } + ] + } + ] + }, + "createWriter": { + "name": "createWriter", + "params": [ + { + "name": "name", + "description": "

name of the file to be created

\n", + "type": "String" + }, + { + "name": "extension", + "description": "", + "type": "String", + "optional": true + } + ], + "class": "p5", + "module": "IO" + }, + "save": { + "name": "save", + "params": [ + { + "name": "objectOrFilename", + "description": "

If filename is provided, will\n save canvas as an image with\n either png or jpg extension\n depending on the filename.\n If object is provided, will\n save depending on the object\n and filename (see examples\n above).

\n", + "type": "Object|String", + "optional": true + }, + { + "name": "filename", + "description": "

If an object is provided as the first\n parameter, then the second parameter\n indicates the filename,\n and should include an appropriate\n file extension (see examples above).

\n", + "type": "String", + "optional": true + }, + { + "name": "options", + "description": "

Additional options depend on\n filetype. For example, when saving JSON,\n true indicates that the\n output will be optimized for filesize,\n rather than readability.

\n", + "type": "Boolean|String", + "optional": true + } + ], + "class": "p5", + "module": "IO" + }, + "saveJSON": { + "name": "saveJSON", + "params": [ + { + "name": "json", + "description": "", + "type": "Array|Object" + }, + { + "name": "filename", + "description": "", + "type": "String" + }, + { + "name": "optimize", + "description": "

If true, removes line breaks\n and spaces from the output\n file to optimize filesize\n (but not readability).

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5", + "module": "IO" + }, + "saveStrings": { + "name": "saveStrings", + "params": [ + { + "name": "list", + "description": "

string array to be written

\n", + "type": "String[]" + }, + { + "name": "filename", + "description": "

filename for output

\n", + "type": "String" + }, + { + "name": "extension", + "description": "

the filename's extension

\n", + "type": "String", + "optional": true + }, + { + "name": "isCRLF", + "description": "

if true, change line-break to CRLF

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5", + "module": "IO" + }, + "saveTable": { + "name": "saveTable", + "params": [ + { + "name": "Table", + "description": "

the Table object to save to a file

\n", + "type": "p5.Table" + }, + { + "name": "filename", + "description": "

the filename to which the Table should be saved

\n", + "type": "String" + }, + { + "name": "options", + "description": "

can be one of \"tsv\", \"csv\", or \"html\"

\n", + "type": "String", + "optional": true + } + ], + "class": "p5", + "module": "IO" + }, + "abs": { + "name": "abs", + "params": [ + { + "name": "n", + "description": "

number to compute.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "ceil": { + "name": "ceil", + "params": [ + { + "name": "n", + "description": "

number to round up.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "constrain": { + "name": "constrain", + "params": [ + { + "name": "n", + "description": "

number to constrain.

\n", + "type": "Number" + }, + { + "name": "low", + "description": "

minimum limit.

\n", + "type": "Number" + }, + { + "name": "high", + "description": "

maximum limit.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "dist": { + "name": "dist", + "class": "p5", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "x1", + "description": "

x-coordinate of the first point.

\n", + "type": "Number" + }, + { + "name": "y1", + "description": "

y-coordinate of the first point.

\n", + "type": "Number" + }, + { + "name": "x2", + "description": "

x-coordinate of the second point.

\n", + "type": "Number" + }, + { + "name": "y2", + "description": "

y-coordinate of the second point.

\n", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "x1", + "description": "", + "type": "Number" + }, + { + "name": "y1", + "description": "", + "type": "Number" + }, + { + "name": "z1", + "description": "

z-coordinate of the first point.

\n", + "type": "Number" + }, + { + "name": "x2", + "description": "", + "type": "Number" + }, + { + "name": "y2", + "description": "", + "type": "Number" + }, + { + "name": "z2", + "description": "

z-coordinate of the second point.

\n", + "type": "Number" + } + ] + } + ] + }, + "exp": { + "name": "exp", + "params": [ + { + "name": "n", + "description": "

exponent to raise.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "floor": { + "name": "floor", + "params": [ + { + "name": "n", + "description": "

number to round down.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "lerp": { + "name": "lerp", + "params": [ + { + "name": "start", + "description": "

first value.

\n", + "type": "Number" + }, + { + "name": "stop", + "description": "

second value.

\n", + "type": "Number" + }, + { + "name": "amt", + "description": "

number.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "log": { + "name": "log", + "params": [ + { + "name": "n", + "description": "

number greater than 0.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "mag": { + "name": "mag", + "params": [ + { + "name": "x", + "description": "

first component.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

second component.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "map": { + "name": "map", + "params": [ + { + "name": "value", + "description": "

the incoming value to be converted.

\n", + "type": "Number" + }, + { + "name": "start1", + "description": "

lower bound of the value's current range.

\n", + "type": "Number" + }, + { + "name": "stop1", + "description": "

upper bound of the value's current range.

\n", + "type": "Number" + }, + { + "name": "start2", + "description": "

lower bound of the value's target range.

\n", + "type": "Number" + }, + { + "name": "stop2", + "description": "

upper bound of the value's target range.

\n", + "type": "Number" + }, + { + "name": "withinBounds", + "description": "

constrain the value to the newly mapped range.

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5", + "module": "Math" + }, + "max": { + "name": "max", + "class": "p5", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "n0", + "description": "

first number to compare.

\n", + "type": "Number" + }, + { + "name": "n1", + "description": "

second number to compare.

\n", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "nums", + "description": "

numbers to compare.

\n", + "type": "Number[]" + } + ] + } + ] + }, + "min": { + "name": "min", + "class": "p5", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "n0", + "description": "

first number to compare.

\n", + "type": "Number" + }, + { + "name": "n1", + "description": "

second number to compare.

\n", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "nums", + "description": "

numbers to compare.

\n", + "type": "Number[]" + } + ] + } + ] + }, + "norm": { + "name": "norm", + "params": [ + { + "name": "value", + "description": "

incoming value to be normalized.

\n", + "type": "Number" + }, + { + "name": "start", + "description": "

lower bound of the value's current range.

\n", + "type": "Number" + }, + { + "name": "stop", + "description": "

upper bound of the value's current range.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "pow": { + "name": "pow", + "params": [ + { + "name": "n", + "description": "

base of the exponential expression.

\n", + "type": "Number" + }, + { + "name": "e", + "description": "

power by which to raise the base.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "round": { + "name": "round", + "params": [ + { + "name": "n", + "description": "

number to round.

\n", + "type": "Number" + }, + { + "name": "decimals", + "description": "

number of decimal places to round to, default is 0.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "Math" + }, + "sq": { + "name": "sq", + "params": [ + { + "name": "n", + "description": "

number to square.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "sqrt": { + "name": "sqrt", + "params": [ + { + "name": "n", + "description": "

non-negative number to square root.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "fract": { + "name": "fract", + "params": [ + { + "name": "n", + "description": "

number whose fractional part will be found.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "createVector": { + "name": "createVector", + "params": [ + { + "name": "x", + "description": "

x component of the vector.

\n", + "type": "Number", + "optional": true + }, + { + "name": "y", + "description": "

y component of the vector.

\n", + "type": "Number", + "optional": true + }, + { + "name": "z", + "description": "

z component of the vector.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "Math" + }, + "noise": { + "name": "noise", + "params": [ + { + "name": "x", + "description": "

x-coordinate in noise space.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate in noise space.

\n", + "type": "Number", + "optional": true + }, + { + "name": "z", + "description": "

z-coordinate in noise space.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "Math" + }, + "noiseDetail": { + "name": "noiseDetail", + "params": [ + { + "name": "lod", + "description": "

number of octaves to be used by the noise.

\n", + "type": "Number" + }, + { + "name": "falloff", + "description": "

falloff factor for each octave.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "noiseSeed": { + "name": "noiseSeed", + "params": [ + { + "name": "seed", + "description": "

seed value.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "randomSeed": { + "name": "randomSeed", + "params": [ + { + "name": "seed", + "description": "

seed value.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "random": { + "name": "random", + "class": "p5", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "min", + "description": "

lower bound (inclusive).

\n", + "type": "Number", + "optional": true + }, + { + "name": "max", + "description": "

upper bound (exclusive).

\n", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "choices", + "description": "

array to choose from.

\n", + "type": "Array" + } + ] + } + ] + }, + "randomGaussian": { + "name": "randomGaussian", + "params": [ + { + "name": "mean", + "description": "

mean.

\n", + "type": "Number", + "optional": true + }, + { + "name": "sd", + "description": "

standard deviation.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "Math" + }, + "acos": { + "name": "acos", + "params": [ + { + "name": "value", + "description": "

value whose arc cosine is to be returned.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "asin": { + "name": "asin", + "params": [ + { + "name": "value", + "description": "

value whose arc sine is to be returned.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "atan": { + "name": "atan", + "params": [ + { + "name": "value", + "description": "

value whose arc tangent is to be returned.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "atan2": { + "name": "atan2", + "params": [ + { + "name": "y", + "description": "

y-coordinate of the point.

\n", + "type": "Number" + }, + { + "name": "x", + "description": "

x-coordinate of the point.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "cos": { + "name": "cos", + "params": [ + { + "name": "angle", + "description": "

the angle.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "sin": { + "name": "sin", + "params": [ + { + "name": "angle", + "description": "

the angle.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "tan": { + "name": "tan", + "params": [ + { + "name": "angle", + "description": "

the angle.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "degrees": { + "name": "degrees", + "params": [ + { + "name": "radians", + "description": "

radians value to convert to degrees.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "radians": { + "name": "radians", + "params": [ + { + "name": "degrees", + "description": "

degree value to convert to radians.

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "Math" + }, + "angleMode": { + "name": "angleMode", + "class": "p5", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "mode", + "description": "

either RADIANS or DEGREES.

\n", + "type": "Constant" + } + ] + }, + { + "params": [] + } + ] + }, + "textAlign": { + "name": "textAlign", + "class": "p5", + "module": "Typography", + "overloads": [ + { + "params": [ + { + "name": "horizAlign", + "description": "

horizontal alignment, either LEFT,\n CENTER, or RIGHT.

\n", + "type": "Constant" + }, + { + "name": "vertAlign", + "description": "

vertical alignment, either TOP,\n BOTTOM, CENTER, or BASELINE.

\n", + "type": "Constant", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [] + } + ] + }, + "textLeading": { + "name": "textLeading", + "class": "p5", + "module": "Typography", + "overloads": [ + { + "params": [ + { + "name": "leading", + "description": "

spacing between lines of text in units of pixels.

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [] + } + ] + }, + "textSize": { + "name": "textSize", + "class": "p5", + "module": "Typography", + "overloads": [ + { + "params": [ + { + "name": "size", + "description": "

size of the letters in units of pixels

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [] + } + ] + }, + "textStyle": { + "name": "textStyle", + "class": "p5", + "module": "Typography", + "overloads": [ + { + "params": [ + { + "name": "style", + "description": "

styling for text, either NORMAL,\n ITALIC, BOLD or BOLDITALIC

\n", + "type": "Constant" + } + ], + "chainable": 1 + }, + { + "params": [] + } + ] + }, + "textWidth": { + "name": "textWidth", + "params": [ + { + "name": "str", + "description": "

string of text to measure.

\n", + "type": "String" + } + ], + "class": "p5", + "module": "Typography" + }, + "textAscent": { + "name": "textAscent", + "class": "p5", + "module": "Typography" + }, + "textDescent": { + "name": "textDescent", + "class": "p5", + "module": "Typography" + }, + "textWrap": { + "name": "textWrap", + "params": [ + { + "name": "style", + "description": "

text wrapping style, either WORD or CHAR.

\n", + "type": "Constant" + } + ], + "class": "p5", + "module": "Typography" + }, + "loadFont": { + "name": "loadFont", + "params": [ + { + "name": "path", + "description": "

path of the font to be loaded.

\n", + "type": "String" + }, + { + "name": "successCallback", + "description": "

function called with the\n p5.Font object after it\n loads.

\n", + "type": "Function", + "optional": true + }, + { + "name": "failureCallback", + "description": "

function called with the error\n Event\n object if the font fails to load.

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5", + "module": "Typography" + }, + "text": { + "name": "text", + "params": [ + { + "name": "str", + "description": "

text to be displayed.

\n", + "type": "String|Object|Array|Number|Boolean" + }, + { + "name": "x", + "description": "

x-coordinate of the text box.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the text box.

\n", + "type": "Number" + }, + { + "name": "maxWidth", + "description": "

maximum width of the text box. See\n rectMode() for\n other options.

\n", + "type": "Number", + "optional": true + }, + { + "name": "maxHeight", + "description": "

maximum height of the text box. See\n rectMode() for\n other options.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "Typography" + }, + "textFont": { + "name": "textFont", + "class": "p5", + "module": "Typography", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "font", + "description": "

font as a p5.Font object or a string.

\n", + "type": "Object|String" + }, + { + "name": "size", + "description": "

font size in pixels.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + } + ] + }, + "append": { + "name": "append", + "params": [ + { + "name": "array", + "description": "

Array to append

\n", + "type": "Array" + }, + { + "name": "value", + "description": "

to be added to the Array

\n", + "type": "Any" + } + ], + "class": "p5", + "module": "Data" + }, + "arrayCopy": { + "name": "arrayCopy", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "src", + "description": "

the source Array

\n", + "type": "Array" + }, + { + "name": "srcPosition", + "description": "

starting position in the source Array

\n", + "type": "Integer" + }, + { + "name": "dst", + "description": "

the destination Array

\n", + "type": "Array" + }, + { + "name": "dstPosition", + "description": "

starting position in the destination Array

\n", + "type": "Integer" + }, + { + "name": "length", + "description": "

number of Array elements to be copied

\n", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "src", + "description": "", + "type": "Array" + }, + { + "name": "dst", + "description": "", + "type": "Array" + }, + { + "name": "length", + "description": "", + "type": "Integer", + "optional": true + } + ] + } + ] + }, + "concat": { + "name": "concat", + "params": [ + { + "name": "a", + "description": "

first Array to concatenate

\n", + "type": "Array" + }, + { + "name": "b", + "description": "

second Array to concatenate

\n", + "type": "Array" + } + ], + "class": "p5", + "module": "Data" + }, + "reverse": { + "name": "reverse", + "params": [ + { + "name": "list", + "description": "

Array to reverse

\n", + "type": "Array" + } + ], + "class": "p5", + "module": "Data" + }, + "shorten": { + "name": "shorten", + "params": [ + { + "name": "list", + "description": "

Array to shorten

\n", + "type": "Array" + } + ], + "class": "p5", + "module": "Data" + }, + "shuffle": { + "name": "shuffle", + "params": [ + { + "name": "array", + "description": "

Array to shuffle

\n", + "type": "Array" + }, + { + "name": "bool", + "description": "

modify passed array

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5", + "module": "Data" + }, + "sort": { + "name": "sort", + "params": [ + { + "name": "list", + "description": "

Array to sort

\n", + "type": "Array" + }, + { + "name": "count", + "description": "

number of elements to sort, starting from 0

\n", + "type": "Integer", + "optional": true + } + ], + "class": "p5", + "module": "Data" + }, + "splice": { + "name": "splice", + "params": [ + { + "name": "list", + "description": "

Array to splice into

\n", + "type": "Array" + }, + { + "name": "value", + "description": "

value to be spliced in

\n", + "type": "Any" + }, + { + "name": "position", + "description": "

in the array from which to insert data

\n", + "type": "Integer" + } + ], + "class": "p5", + "module": "Data" + }, + "subset": { + "name": "subset", + "params": [ + { + "name": "list", + "description": "

Array to extract from

\n", + "type": "Array" + }, + { + "name": "start", + "description": "

position to begin

\n", + "type": "Integer" + }, + { + "name": "count", + "description": "

number of values to extract

\n", + "type": "Integer", + "optional": true + } + ], + "class": "p5", + "module": "Data" + }, + "float": { + "name": "float", + "params": [ + { + "name": "str", + "description": "

float string to parse

\n", + "type": "String" + } + ], + "class": "p5", + "module": "Data" + }, + "int": { + "name": "int", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "

value to parse

\n", + "type": "String|Boolean|Number" + }, + { + "name": "radix", + "description": "

the radix to convert to (default: 10)

\n", + "type": "Integer", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "ns", + "description": "

values to parse

\n", + "type": "Array" + }, + { + "name": "radix", + "description": "", + "type": "Integer", + "optional": true + } + ] + } + ] + }, + "str": { + "name": "str", + "params": [ + { + "name": "n", + "description": "

value to parse

\n", + "type": "String|Boolean|Number|Array" + } + ], + "class": "p5", + "module": "Data" + }, + "byte": { + "name": "byte", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "

value to parse

\n", + "type": "String|Boolean|Number" + } + ] + }, + { + "params": [ + { + "name": "ns", + "description": "

values to parse

\n", + "type": "Array" + } + ] + } + ] + }, + "char": { + "name": "char", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "

value to parse

\n", + "type": "String|Number" + } + ] + }, + { + "params": [ + { + "name": "ns", + "description": "

values to parse

\n", + "type": "Array" + } + ] + } + ] + }, + "unchar": { + "name": "unchar", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "

value to parse

\n", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "ns", + "description": "

values to parse

\n", + "type": "Array" + } + ] + } + ] + }, + "hex": { + "name": "hex", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "

value to parse

\n", + "type": "Number" + }, + { + "name": "digits", + "description": "", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "ns", + "description": "

array of values to parse

\n", + "type": "Number[]" + }, + { + "name": "digits", + "description": "", + "type": "Number", + "optional": true + } + ] + } + ] + }, + "unhex": { + "name": "unhex", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "

value to parse

\n", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "ns", + "description": "

values to parse

\n", + "type": "Array" + } + ] + } + ] + }, + "join": { + "name": "join", + "params": [ + { + "name": "list", + "description": "

array of Strings to be joined

\n", + "type": "Array" + }, + { + "name": "separator", + "description": "

String to be placed between each item

\n", + "type": "String" + } + ], + "class": "p5", + "module": "Data" + }, + "match": { + "name": "match", + "params": [ + { + "name": "str", + "description": "

the String to be searched

\n", + "type": "String" + }, + { + "name": "regexp", + "description": "

the regexp to be used for matching

\n", + "type": "String" + } + ], + "class": "p5", + "module": "Data" + }, + "matchAll": { + "name": "matchAll", + "params": [ + { + "name": "str", + "description": "

the String to be searched

\n", + "type": "String" + }, + { + "name": "regexp", + "description": "

the regexp to be used for matching

\n", + "type": "String" + } + ], + "class": "p5", + "module": "Data" + }, + "nf": { + "name": "nf", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "num", + "description": "

the Number to format

\n", + "type": "Number|String" + }, + { + "name": "left", + "description": "

number of digits to the left of the\n decimal point

\n", + "type": "Integer|String", + "optional": true + }, + { + "name": "right", + "description": "

number of digits to the right of the\n decimal point

\n", + "type": "Integer|String", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "nums", + "description": "

the Numbers to format

\n", + "type": "Array" + }, + { + "name": "left", + "description": "", + "type": "Integer|String", + "optional": true + }, + { + "name": "right", + "description": "", + "type": "Integer|String", + "optional": true + } + ] + } + ] + }, + "nfc": { + "name": "nfc", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "num", + "description": "

the Number to format

\n", + "type": "Number|String" + }, + { + "name": "right", + "description": "

number of digits to the right of the\n decimal point

\n", + "type": "Integer|String", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "nums", + "description": "

the Numbers to format

\n", + "type": "Array" + }, + { + "name": "right", + "description": "", + "type": "Integer|String", + "optional": true + } + ] + } + ] + }, + "nfp": { + "name": "nfp", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "num", + "description": "

the Number to format

\n", + "type": "Number" + }, + { + "name": "left", + "description": "

number of digits to the left of the decimal\n point

\n", + "type": "Integer", + "optional": true + }, + { + "name": "right", + "description": "

number of digits to the right of the\n decimal point

\n", + "type": "Integer", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "nums", + "description": "

the Numbers to format

\n", + "type": "Number[]" + }, + { + "name": "left", + "description": "", + "type": "Integer", + "optional": true + }, + { + "name": "right", + "description": "", + "type": "Integer", + "optional": true + } + ] + } + ] + }, + "nfs": { + "name": "nfs", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "num", + "description": "

the Number to format

\n", + "type": "Number" + }, + { + "name": "left", + "description": "

number of digits to the left of the decimal\n point

\n", + "type": "Integer", + "optional": true + }, + { + "name": "right", + "description": "

number of digits to the right of the\n decimal point

\n", + "type": "Integer", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "nums", + "description": "

the Numbers to format

\n", + "type": "Array" + }, + { + "name": "left", + "description": "", + "type": "Integer", + "optional": true + }, + { + "name": "right", + "description": "", + "type": "Integer", + "optional": true + } + ] + } + ] + }, + "split": { + "name": "split", + "params": [ + { + "name": "value", + "description": "

the String to be split

\n", + "type": "String" + }, + { + "name": "delim", + "description": "

the String used to separate the data

\n", + "type": "String" + } + ], + "class": "p5", + "module": "Data" + }, + "splitTokens": { + "name": "splitTokens", + "params": [ + { + "name": "value", + "description": "

the String to be split

\n", + "type": "String" + }, + { + "name": "delim", + "description": "

list of individual Strings that will be used as\n separators

\n", + "type": "String", + "optional": true + } + ], + "class": "p5", + "module": "Data" + }, + "trim": { + "name": "trim", + "class": "p5", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "str", + "description": "

a String to be trimmed

\n", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "strs", + "description": "

an Array of Strings to be trimmed

\n", + "type": "Array" + } + ] + } + ] + }, + "day": { + "name": "day", + "class": "p5", + "module": "IO" + }, + "hour": { + "name": "hour", + "class": "p5", + "module": "IO" + }, + "minute": { + "name": "minute", + "class": "p5", + "module": "IO" + }, + "millis": { + "name": "millis", + "class": "p5", + "module": "IO" + }, + "month": { + "name": "month", + "class": "p5", + "module": "IO" + }, + "second": { + "name": "second", + "class": "p5", + "module": "IO" + }, + "year": { + "name": "year", + "class": "p5", + "module": "IO" + }, + "beginGeometry": { + "name": "beginGeometry", + "class": "p5", + "module": "Shape" + }, + "endGeometry": { + "name": "endGeometry", + "class": "p5", + "module": "Shape" + }, + "buildGeometry": { + "name": "buildGeometry", + "params": [ + { + "name": "callback", + "description": "

A function that draws shapes.

\n", + "type": "Function" + } + ], + "class": "p5", + "module": "Shape" + }, + "freeGeometry": { + "name": "freeGeometry", + "params": [ + { + "name": "geometry", + "description": "

The geometry whose resources should be freed

\n", + "type": "p5.Geometry" + } + ], + "class": "p5", + "module": "Shape" + }, + "plane": { + "name": "plane", + "params": [ + { + "name": "width", + "description": "

width of the plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "height", + "description": "

height of the plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "detailX", + "description": "

Optional number of triangle\n subdivisions in x-dimension

\n", + "type": "Integer", + "optional": true + }, + { + "name": "detailY", + "description": "

Optional number of triangle\n subdivisions in y-dimension

\n", + "type": "Integer", + "optional": true + } + ], + "class": "p5", + "module": "Shape" + }, + "box": { + "name": "box", + "params": [ + { + "name": "width", + "description": "

width of the box

\n", + "type": "Number", + "optional": true + }, + { + "name": "height", + "description": "

height of the box

\n", + "type": "Number", + "optional": true + }, + { + "name": "depth", + "description": "

depth of the box

\n", + "type": "Number", + "optional": true + }, + { + "name": "detailX", + "description": "

Optional number of triangle\n subdivisions in x-dimension

\n", + "type": "Integer", + "optional": true + }, + { + "name": "detailY", + "description": "

Optional number of triangle\n subdivisions in y-dimension

\n", + "type": "Integer", + "optional": true + } + ], + "class": "p5", + "module": "Shape" + }, + "sphere": { + "name": "sphere", + "params": [ + { + "name": "radius", + "description": "

radius of circle

\n", + "type": "Number", + "optional": true + }, + { + "name": "detailX", + "description": "

optional number of subdivisions in x-dimension

\n", + "type": "Integer", + "optional": true + }, + { + "name": "detailY", + "description": "

optional number of subdivisions in y-dimension

\n", + "type": "Integer", + "optional": true + } + ], + "class": "p5", + "module": "Shape" + }, + "cylinder": { + "name": "cylinder", + "params": [ + { + "name": "radius", + "description": "

radius of the surface

\n", + "type": "Number", + "optional": true + }, + { + "name": "height", + "description": "

height of the cylinder

\n", + "type": "Number", + "optional": true + }, + { + "name": "detailX", + "description": "

number of subdivisions in x-dimension;\n default is 24

\n", + "type": "Integer", + "optional": true + }, + { + "name": "detailY", + "description": "

number of subdivisions in y-dimension;\n default is 1

\n", + "type": "Integer", + "optional": true + }, + { + "name": "bottomCap", + "description": "

whether to draw the bottom of the cylinder

\n", + "type": "Boolean", + "optional": true + }, + { + "name": "topCap", + "description": "

whether to draw the top of the cylinder

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5", + "module": "Shape" + }, + "cone": { + "name": "cone", + "params": [ + { + "name": "radius", + "description": "

radius of the bottom surface

\n", + "type": "Number", + "optional": true + }, + { + "name": "height", + "description": "

height of the cone

\n", + "type": "Number", + "optional": true + }, + { + "name": "detailX", + "description": "

number of segments,\n the more segments the smoother geometry\n default is 24

\n", + "type": "Integer", + "optional": true + }, + { + "name": "detailY", + "description": "

number of segments,\n the more segments the smoother geometry\n default is 1

\n", + "type": "Integer", + "optional": true + }, + { + "name": "cap", + "description": "

whether to draw the base of the cone

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5", + "module": "Shape" + }, + "ellipsoid": { + "name": "ellipsoid", + "params": [ + { + "name": "radiusx", + "description": "

x-radius of ellipsoid

\n", + "type": "Number", + "optional": true + }, + { + "name": "radiusy", + "description": "

y-radius of ellipsoid

\n", + "type": "Number", + "optional": true + }, + { + "name": "radiusz", + "description": "

z-radius of ellipsoid

\n", + "type": "Number", + "optional": true + }, + { + "name": "detailX", + "description": "

number of segments,\n the more segments the smoother geometry\n default is 24. Avoid detail number above\n 150, it may crash the browser.

\n", + "type": "Integer", + "optional": true + }, + { + "name": "detailY", + "description": "

number of segments,\n the more segments the smoother geometry\n default is 16. Avoid detail number above\n 150, it may crash the browser.

\n", + "type": "Integer", + "optional": true + } + ], + "class": "p5", + "module": "Shape" + }, + "torus": { + "name": "torus", + "params": [ + { + "name": "radius", + "description": "

radius of the whole ring

\n", + "type": "Number", + "optional": true + }, + { + "name": "tubeRadius", + "description": "

radius of the tube

\n", + "type": "Number", + "optional": true + }, + { + "name": "detailX", + "description": "

number of segments in x-dimension,\n the more segments the smoother geometry\n default is 24

\n", + "type": "Integer", + "optional": true + }, + { + "name": "detailY", + "description": "

number of segments in y-dimension,\n the more segments the smoother geometry\n default is 16

\n", + "type": "Integer", + "optional": true + } + ], + "class": "p5", + "module": "Shape" + }, + "orbitControl": { + "name": "orbitControl", + "params": [ + { + "name": "sensitivityX", + "description": "

sensitivity to mouse movement along X axis

\n", + "type": "Number", + "optional": true + }, + { + "name": "sensitivityY", + "description": "

sensitivity to mouse movement along Y axis

\n", + "type": "Number", + "optional": true + }, + { + "name": "sensitivityZ", + "description": "

sensitivity to scroll movement along Z axis

\n", + "type": "Number", + "optional": true + }, + { + "name": "options", + "description": "

An optional object that can contain additional settings,\ndisableTouchActions - Boolean, default value is true.\nSetting this to true makes mobile interactions smoother by preventing\naccidental interactions with the page while orbiting. But if you're already\ndoing it via css or want the default touch actions, consider setting it to false.\nfreeRotation - Boolean, default value is false.\nBy default, horizontal movement of the mouse or touch pointer rotates the camera\naround the y-axis, and vertical movement rotates the camera around the x-axis.\nBut if setting this option to true, the camera always rotates in the direction\nthe pointer is moving. For zoom and move, the behavior is the same regardless of\ntrue/false.

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5", + "module": "3D" + }, + "debugMode": { + "name": "debugMode", + "class": "p5", + "module": "3D", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "mode", + "description": "

either GRID or AXES

\n", + "type": "Constant" + } + ] + }, + { + "params": [ + { + "name": "mode", + "description": "", + "type": "Constant" + }, + { + "name": "gridSize", + "description": "

size of one side of the grid

\n", + "type": "Number", + "optional": true + }, + { + "name": "gridDivisions", + "description": "

number of divisions in the grid

\n", + "type": "Number", + "optional": true + }, + { + "name": "xOff", + "description": "

X axis offset from origin (0,0,0)

\n", + "type": "Number", + "optional": true + }, + { + "name": "yOff", + "description": "

Y axis offset from origin (0,0,0)

\n", + "type": "Number", + "optional": true + }, + { + "name": "zOff", + "description": "

Z axis offset from origin (0,0,0)

\n", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "mode", + "description": "", + "type": "Constant" + }, + { + "name": "axesSize", + "description": "

size of axes icon

\n", + "type": "Number", + "optional": true + }, + { + "name": "xOff", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "yOff", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "zOff", + "description": "", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "gridSize", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "gridDivisions", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "gridXOff", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "gridYOff", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "gridZOff", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "axesSize", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "axesXOff", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "axesYOff", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "axesZOff", + "description": "", + "type": "Number", + "optional": true + } + ] + } + ] + }, + "noDebugMode": { + "name": "noDebugMode", + "class": "p5", + "module": "3D" + }, + "ambientLight": { + "name": "ambientLight", + "class": "p5", + "module": "3D", + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "

red or hue value relative to\n the current color range

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green or saturation value\n relative to the current color range

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue or brightness value\n relative to the current color range

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "

alpha value relative to current\n color range (default is 0-255)

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "gray", + "description": "

number specifying value between\n white and black

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "value", + "description": "

a color string

\n", + "type": "String" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "values", + "description": "

an array containing the red,green,blue &\n and alpha components of the color

\n", + "type": "Number[]" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "

color as a p5.Color

\n", + "type": "p5.Color" + } + ], + "chainable": 1 + } + ] + }, + "specularColor": { + "name": "specularColor", + "class": "p5", + "module": "3D", + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "

red or hue value relative to\n the current color range

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green or saturation value\n relative to the current color range

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue or brightness value\n relative to the current color range

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "gray", + "description": "

number specifying value between\n white and black

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "value", + "description": "

color as a CSS string

\n", + "type": "String" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "values", + "description": "

color as an array containing the\n red, green, and blue components

\n", + "type": "Number[]" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "

color as a p5.Color

\n", + "type": "p5.Color" + } + ], + "chainable": 1 + } + ] + }, + "directionalLight": { + "name": "directionalLight", + "class": "p5", + "module": "3D", + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "

red or hue value relative to the current\n color range

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green or saturation value relative to the\n current color range

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue or brightness value relative to the\n current color range

\n", + "type": "Number" + }, + { + "name": "x", + "description": "

x component of direction (inclusive range of -1 to 1)

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y component of direction (inclusive range of -1 to 1)

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

z component of direction (inclusive range of -1 to 1)

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v1", + "description": "", + "type": "Number" + }, + { + "name": "v2", + "description": "", + "type": "Number" + }, + { + "name": "v3", + "description": "", + "type": "Number" + }, + { + "name": "direction", + "description": "

direction of light as a\n p5.Vector

\n", + "type": "p5.Vector" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "

color as a p5.Color,\n as an array, or as a CSS string

\n", + "type": "p5.Color|Number[]|String" + }, + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "z", + "description": "", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "", + "type": "p5.Color|Number[]|String" + }, + { + "name": "direction", + "description": "", + "type": "p5.Vector" + } + ], + "chainable": 1 + } + ] + }, + "pointLight": { + "name": "pointLight", + "class": "p5", + "module": "3D", + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "

red or hue value relative to the current\n color range

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green or saturation value relative to the\n current color range

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue or brightness value relative to the\n current color range

\n", + "type": "Number" + }, + { + "name": "x", + "description": "

x component of position

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y component of position

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

z component of position

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v1", + "description": "", + "type": "Number" + }, + { + "name": "v2", + "description": "", + "type": "Number" + }, + { + "name": "v3", + "description": "", + "type": "Number" + }, + { + "name": "position", + "description": "

of light as a p5.Vector

\n", + "type": "p5.Vector" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "

color as a p5.Color,\n as an array, or as a CSS string

\n", + "type": "p5.Color|Number[]|String" + }, + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "z", + "description": "", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "", + "type": "p5.Color|Number[]|String" + }, + { + "name": "position", + "description": "", + "type": "p5.Vector" + } + ], + "chainable": 1 + } + ] + }, + "imageLight": { + "name": "imageLight", + "params": [ + { + "name": "img", + "description": "

image for the background

\n", + "type": "p5.image" + } + ], + "class": "p5", + "module": "3D" + }, + "lights": { + "name": "lights", + "class": "p5", + "module": "3D" + }, + "lightFalloff": { + "name": "lightFalloff", + "params": [ + { + "name": "constant", + "description": "

CONSTANT value for determining falloff

\n", + "type": "Number" + }, + { + "name": "linear", + "description": "

LINEAR value for determining falloff

\n", + "type": "Number" + }, + { + "name": "quadratic", + "description": "

QUADRATIC value for determining falloff

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "3D" + }, + "spotLight": { + "name": "spotLight", + "class": "p5", + "module": "3D", + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "

red or hue value relative to the current color range

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green or saturation value relative to the current color range

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue or brightness value relative to the current color range

\n", + "type": "Number" + }, + { + "name": "x", + "description": "

x component of position

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y component of position

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

z component of position

\n", + "type": "Number" + }, + { + "name": "rx", + "description": "

x component of light direction (inclusive range of -1 to 1)

\n", + "type": "Number" + }, + { + "name": "ry", + "description": "

y component of light direction (inclusive range of -1 to 1)

\n", + "type": "Number" + }, + { + "name": "rz", + "description": "

z component of light direction (inclusive range of -1 to 1)

\n", + "type": "Number" + }, + { + "name": "angle", + "description": "

angle of cone. Defaults to PI/3

\n", + "type": "Number", + "optional": true + }, + { + "name": "concentration", + "description": "

concentration of cone. Defaults to 100

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "

color as a p5.Color,\n as an array, or as a CSS string

\n", + "type": "p5.Color|Number[]|String" + }, + { + "name": "position", + "description": "

position of light as a p5.Vector

\n", + "type": "p5.Vector" + }, + { + "name": "direction", + "description": "

direction of light as a p5.Vector

\n", + "type": "p5.Vector" + }, + { + "name": "angle", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "concentration", + "description": "", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "", + "type": "Number" + }, + { + "name": "v2", + "description": "", + "type": "Number" + }, + { + "name": "v3", + "description": "", + "type": "Number" + }, + { + "name": "position", + "description": "", + "type": "p5.Vector" + }, + { + "name": "direction", + "description": "", + "type": "p5.Vector" + }, + { + "name": "angle", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "concentration", + "description": "", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "", + "type": "p5.Color|Number[]|String" + }, + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "z", + "description": "", + "type": "Number" + }, + { + "name": "direction", + "description": "", + "type": "p5.Vector" + }, + { + "name": "angle", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "concentration", + "description": "", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "", + "type": "p5.Color|Number[]|String" + }, + { + "name": "position", + "description": "", + "type": "p5.Vector" + }, + { + "name": "rx", + "description": "", + "type": "Number" + }, + { + "name": "ry", + "description": "", + "type": "Number" + }, + { + "name": "rz", + "description": "", + "type": "Number" + }, + { + "name": "angle", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "concentration", + "description": "", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "", + "type": "Number" + }, + { + "name": "v2", + "description": "", + "type": "Number" + }, + { + "name": "v3", + "description": "", + "type": "Number" + }, + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "z", + "description": "", + "type": "Number" + }, + { + "name": "direction", + "description": "", + "type": "p5.Vector" + }, + { + "name": "angle", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "concentration", + "description": "", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "", + "type": "Number" + }, + { + "name": "v2", + "description": "", + "type": "Number" + }, + { + "name": "v3", + "description": "", + "type": "Number" + }, + { + "name": "position", + "description": "", + "type": "p5.Vector" + }, + { + "name": "rx", + "description": "", + "type": "Number" + }, + { + "name": "ry", + "description": "", + "type": "Number" + }, + { + "name": "rz", + "description": "", + "type": "Number" + }, + { + "name": "angle", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "concentration", + "description": "", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "color", + "description": "", + "type": "p5.Color|Number[]|String" + }, + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "z", + "description": "", + "type": "Number" + }, + { + "name": "rx", + "description": "", + "type": "Number" + }, + { + "name": "ry", + "description": "", + "type": "Number" + }, + { + "name": "rz", + "description": "", + "type": "Number" + }, + { + "name": "angle", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "concentration", + "description": "", + "type": "Number", + "optional": true + } + ] + } + ] + }, + "noLights": { + "name": "noLights", + "class": "p5", + "module": "3D" + }, + "loadModel": { + "name": "loadModel", + "class": "p5", + "module": "Shape", + "overloads": [ + { + "params": [ + { + "name": "path", + "description": "

Path of the model to be loaded

\n", + "type": "String" + }, + { + "name": "normalize", + "description": "

If true, scale the model to a\n standardized size when loading

\n", + "type": "Boolean" + }, + { + "name": "successCallback", + "description": "

Function to be called\n once the model is loaded. Will be passed\n the 3D model object.

\n", + "type": "function(p5.Geometry)", + "optional": true + }, + { + "name": "failureCallback", + "description": "

called with event error if\n the model fails to load.

\n", + "type": "Function(Event)", + "optional": true + }, + { + "name": "fileType", + "description": "

The file extension of the model\n (.stl, .obj).

\n", + "type": "String", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "path", + "description": "", + "type": "String" + }, + { + "name": "successCallback", + "description": "", + "type": "function(p5.Geometry)", + "optional": true + }, + { + "name": "failureCallback", + "description": "", + "type": "Function(Event)", + "optional": true + }, + { + "name": "fileType", + "description": "", + "type": "String", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "path", + "description": "", + "type": "String" + }, + { + "name": "options", + "description": "", + "type": "Object", + "optional": true, + "props": [ + { + "name": "successCallback", + "description": "", + "type": "function(p5.Geometry)", + "optional": true + }, + { + "name": "failureCallback", + "description": "", + "type": "Function(Event)", + "optional": true + }, + { + "name": "fileType", + "description": "", + "type": "String", + "optional": true + }, + { + "name": "normalize", + "description": "", + "type": "Boolean", + "optional": true + }, + { + "name": "flipU", + "description": "", + "type": "Boolean", + "optional": true + }, + { + "name": "flipV", + "description": "", + "type": "Boolean", + "optional": true + } + ] + } + ] + } + ] + }, + "model": { + "name": "model", + "params": [ + { + "name": "model", + "description": "

Loaded 3d model to be rendered

\n", + "type": "p5.Geometry" + } + ], + "class": "p5", + "module": "Shape" + }, + "loadShader": { + "name": "loadShader", + "params": [ + { + "name": "vertFilename", + "description": "

path to file containing vertex shader\nsource code

\n", + "type": "String" + }, + { + "name": "fragFilename", + "description": "

path to file containing fragment shader\nsource code

\n", + "type": "String" + }, + { + "name": "callback", + "description": "

callback to be executed after loadShader\ncompletes. On success, the p5.Shader object is passed as the first argument.

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

callback to be executed when an error\noccurs inside loadShader. On error, the error is passed as the first\nargument.

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5", + "module": "3D" + }, + "createShader": { + "name": "createShader", + "params": [ + { + "name": "vertSrc", + "description": "

source code for the vertex shader

\n", + "type": "String" + }, + { + "name": "fragSrc", + "description": "

source code for the fragment shader

\n", + "type": "String" + } + ], + "class": "p5", + "module": "3D" + }, + "createFilterShader": { + "name": "createFilterShader", + "params": [ + { + "name": "fragSrc", + "description": "

source code for the fragment shader

\n", + "type": "String" + } + ], + "class": "p5", + "module": "3D" + }, + "shader": { + "name": "shader", + "params": [ + { + "name": "s", + "description": "

the p5.Shader object\nto use for rendering shapes.

\n", + "type": "p5.Shader" + } + ], + "class": "p5", + "module": "3D" + }, + "resetShader": { + "name": "resetShader", + "class": "p5", + "module": "3D" + }, + "texture": { + "name": "texture", + "params": [ + { + "name": "tex", + "description": "

image to use as texture

\n", + "type": "p5.Image|p5.MediaElement|p5.Graphics|p5.Texture|p5.Framebuffer|p5.FramebufferTexture" + } + ], + "class": "p5", + "module": "3D" + }, + "textureMode": { + "name": "textureMode", + "params": [ + { + "name": "mode", + "description": "

either IMAGE or NORMAL

\n", + "type": "Constant" + } + ], + "class": "p5", + "module": "3D" + }, + "textureWrap": { + "name": "textureWrap", + "params": [ + { + "name": "wrapX", + "description": "

either CLAMP, REPEAT, or MIRROR

\n", + "type": "Constant" + }, + { + "name": "wrapY", + "description": "

either CLAMP, REPEAT, or MIRROR

\n", + "type": "Constant", + "optional": true + } + ], + "class": "p5", + "module": "3D" + }, + "normalMaterial": { + "name": "normalMaterial", + "class": "p5", + "module": "3D" + }, + "ambientMaterial": { + "name": "ambientMaterial", + "class": "p5", + "module": "3D", + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "

red or hue value relative to the current\n color range

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green or saturation value relative to the\n current color range

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue or brightness value relative to the\n current color range

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "gray", + "description": "

number specifying value between\n white and black

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "

color as a p5.Color,\n as an array, or as a CSS string

\n", + "type": "p5.Color|Number[]|String" + } + ], + "chainable": 1 + } + ] + }, + "emissiveMaterial": { + "name": "emissiveMaterial", + "class": "p5", + "module": "3D", + "overloads": [ + { + "params": [ + { + "name": "v1", + "description": "

red or hue value relative to the current\n color range

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green or saturation value relative to the\n current color range

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue or brightness value relative to the\n current color range

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "

alpha value relative to current color\n range (default is 0-255)

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "gray", + "description": "

number specifying value between\n white and black

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "

color as a p5.Color,\n as an array, or as a CSS string

\n", + "type": "p5.Color|Number[]|String" + } + ], + "chainable": 1 + } + ] + }, + "specularMaterial": { + "name": "specularMaterial", + "class": "p5", + "module": "3D", + "overloads": [ + { + "params": [ + { + "name": "gray", + "description": "

number specifying value between white and black.

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "

alpha value relative to current color range\n (default is 0-255)

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v1", + "description": "

red or hue value relative to\n the current color range

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

green or saturation value\n relative to the current color range

\n", + "type": "Number" + }, + { + "name": "v3", + "description": "

blue or brightness value\n relative to the current color range

\n", + "type": "Number" + }, + { + "name": "alpha", + "description": "", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "color", + "description": "

color as a p5.Color,\n as an array, or as a CSS string

\n", + "type": "p5.Color|Number[]|String" + } + ], + "chainable": 1 + } + ] + }, + "shininess": { + "name": "shininess", + "params": [ + { + "name": "shine", + "description": "

degree of shininess

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "3D" + }, + "metalness": { + "name": "metalness", + "params": [ + { + "name": "metallic", + "description": "\n", + "type": "Number" + } + ], + "class": "p5", + "module": "3D" + }, + "camera": { + "name": "camera", + "params": [ + { + "name": "x", + "description": "

camera position value on x axis

\n", + "type": "Number", + "optional": true + }, + { + "name": "y", + "description": "

camera position value on y axis

\n", + "type": "Number", + "optional": true + }, + { + "name": "z", + "description": "

camera position value on z axis

\n", + "type": "Number", + "optional": true + }, + { + "name": "centerX", + "description": "

x coordinate representing center of the sketch

\n", + "type": "Number", + "optional": true + }, + { + "name": "centerY", + "description": "

y coordinate representing center of the sketch

\n", + "type": "Number", + "optional": true + }, + { + "name": "centerZ", + "description": "

z coordinate representing center of the sketch

\n", + "type": "Number", + "optional": true + }, + { + "name": "upX", + "description": "

x component of direction 'up' from camera

\n", + "type": "Number", + "optional": true + }, + { + "name": "upY", + "description": "

y component of direction 'up' from camera

\n", + "type": "Number", + "optional": true + }, + { + "name": "upZ", + "description": "

z component of direction 'up' from camera

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "3D" + }, + "perspective": { + "name": "perspective", + "params": [ + { + "name": "fovy", + "description": "

camera frustum vertical field of view,\n from bottom to top of view, in angleMode units

\n", + "type": "Number", + "optional": true + }, + { + "name": "aspect", + "description": "

camera frustum aspect ratio

\n", + "type": "Number", + "optional": true + }, + { + "name": "near", + "description": "

frustum near plane length

\n", + "type": "Number", + "optional": true + }, + { + "name": "far", + "description": "

frustum far plane length

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "3D" + }, + "linePerspective": { + "name": "linePerspective", + "class": "p5", + "module": "3D", + "overloads": [ + { + "params": [ + { + "name": "enable", + "description": "\n", + "type": "Boolean" + } + ] + }, + { + "params": [] + } + ] + }, + "ortho": { + "name": "ortho", + "params": [ + { + "name": "left", + "description": "

camera frustum left plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "right", + "description": "

camera frustum right plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "bottom", + "description": "

camera frustum bottom plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "top", + "description": "

camera frustum top plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "near", + "description": "

camera frustum near plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "far", + "description": "

camera frustum far plane

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "3D" + }, + "frustum": { + "name": "frustum", + "params": [ + { + "name": "left", + "description": "

camera frustum left plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "right", + "description": "

camera frustum right plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "bottom", + "description": "

camera frustum bottom plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "top", + "description": "

camera frustum top plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "near", + "description": "

camera frustum near plane

\n", + "type": "Number", + "optional": true + }, + { + "name": "far", + "description": "

camera frustum far plane

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "3D" + }, + "createCamera": { + "name": "createCamera", + "class": "p5", + "module": "3D" + }, + "setCamera": { + "name": "setCamera", + "params": [ + { + "name": "cam", + "description": "

p5.Camera object

\n", + "type": "p5.Camera" + } + ], + "class": "p5", + "module": "3D" + }, + "setAttributes": { + "name": "setAttributes", + "class": "p5", + "module": "Rendering", + "overloads": [ + { + "params": [ + { + "name": "key", + "description": "

Name of attribute

\n", + "type": "String" + }, + { + "name": "value", + "description": "

New value of named attribute

\n", + "type": "Boolean" + } + ] + }, + { + "params": [ + { + "name": "obj", + "description": "

object with key-value pairs

\n", + "type": "Object" + } + ] + } + ] + }, + "getAudioContext": { + "name": "getAudioContext", + "class": "p5", + "module": "p5.sound" + }, + "userStartAudio": { + "params": [ + { + "name": "elements", + "description": "

This argument can be an Element,\n Selector String, NodeList, p5.Element,\n jQuery Element, or an Array of any of those.

\n", + "type": "Element|Array", + "optional": true + }, + { + "name": "callback", + "description": "

Callback to invoke when the AudioContext\n has started

\n", + "type": "Function", + "optional": true + } + ], + "name": "userStartAudio", + "class": "p5", + "module": "p5.sound" + }, + "getOutputVolume": { + "name": "getOutputVolume", + "class": "p5", + "module": "p5.sound" + }, + "outputVolume": { + "name": "outputVolume", + "params": [ + { + "name": "volume", + "description": "

Volume (amplitude) between 0.0\n and 1.0 or modulating signal/oscillator

\n", + "type": "Number|Object" + }, + { + "name": "rampTime", + "description": "

Fade for t seconds

\n", + "type": "Number", + "optional": true + }, + { + "name": "timeFromNow", + "description": "

Schedule this event to happen at\n t seconds in the future

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5", + "module": "p5.sound" + }, + "soundOut": { + "name": "soundOut", + "class": "p5", + "module": "p5.sound" + }, + "sampleRate": { + "name": "sampleRate", + "class": "p5", + "module": "p5.sound" + }, + "freqToMidi": { + "name": "freqToMidi", + "params": [ + { + "name": "frequency", + "description": "

A freqeuncy, for example, the \"A\"\n above Middle C is 440Hz

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "p5.sound" + }, + "midiToFreq": { + "name": "midiToFreq", + "params": [ + { + "name": "midiNote", + "description": "

The number of a MIDI note

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "p5.sound" + }, + "soundFormats": { + "name": "soundFormats", + "params": [ + { + "name": "formats", + "description": "

i.e. 'mp3', 'wav', 'ogg'

\n", + "type": "String", + "optional": true, + "multiple": true + } + ], + "class": "p5", + "module": "p5.sound" + }, + "saveSound": { + "name": "saveSound", + "params": [ + { + "name": "soundFile", + "description": "

p5.SoundFile that you wish to save

\n", + "type": "p5.SoundFile" + }, + { + "name": "fileName", + "description": "

name of the resulting .wav file.

\n", + "type": "String" + } + ], + "class": "p5", + "module": "p5.sound" + }, + "loadSound": { + "name": "loadSound", + "params": [ + { + "name": "path", + "description": "

Path to the sound file, or an array with\n paths to soundfiles in multiple formats\n i.e. ['sound.ogg', 'sound.mp3'].\n Alternately, accepts an object: either\n from the HTML5 File API, or a p5.File.

\n", + "type": "String|Array" + }, + { + "name": "successCallback", + "description": "

Name of a function to call once file loads

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

Name of a function to call if there is\n an error loading the file.

\n", + "type": "Function", + "optional": true + }, + { + "name": "whileLoading", + "description": "

Name of a function to call while file is loading.\n This function will receive the percentage loaded\n so far, from 0.0 to 1.0.

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5", + "module": "p5.sound" + }, + "createConvolver": { + "name": "createConvolver", + "params": [ + { + "name": "path", + "description": "

path to a sound file

\n", + "type": "String" + }, + { + "name": "callback", + "description": "

function to call if loading is successful.\n The object will be passed in as the argument\n to the callback function.

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

function to call if loading is not successful.\n A custom error will be passed in as the argument\n to the callback function.

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5", + "module": "p5.sound" + }, + "setBPM": { + "name": "setBPM", + "params": [ + { + "name": "BPM", + "description": "

Beats Per Minute

\n", + "type": "Number" + }, + { + "name": "rampTime", + "description": "

Seconds from now

\n", + "type": "Number" + } + ], + "class": "p5", + "module": "p5.sound" + } + }, + "p5.Color": { + "toString": { + "name": "toString", + "params": [ + { + "name": "format", + "description": "

how the color string will be formatted.\nLeaving this empty formats the string as rgba(r, g, b, a).\n'#rgb' '#rgba' '#rrggbb' and '#rrggbbaa' format as hexadecimal color codes.\n'rgb' 'hsb' and 'hsl' return the color formatted in the specified color mode.\n'rgba' 'hsba' and 'hsla' are the same as above but with alpha channels.\n'rgb%' 'hsb%' 'hsl%' 'rgba%' 'hsba%' and 'hsla%' format as percentages.

\n", + "type": "String", + "optional": true + } + ], + "class": "p5.Color", + "module": "Color" + }, + "setRed": { + "name": "setRed", + "params": [ + { + "name": "red", + "description": "

the new red value.

\n", + "type": "Number" + } + ], + "class": "p5.Color", + "module": "Color" + }, + "setGreen": { + "name": "setGreen", + "params": [ + { + "name": "green", + "description": "

the new green value.

\n", + "type": "Number" + } + ], + "class": "p5.Color", + "module": "Color" + }, + "setBlue": { + "name": "setBlue", + "params": [ + { + "name": "blue", + "description": "

the new blue value.

\n", + "type": "Number" + } + ], + "class": "p5.Color", + "module": "Color" + }, + "setAlpha": { + "name": "setAlpha", + "params": [ + { + "name": "alpha", + "description": "

the new alpha value.

\n", + "type": "Number" + } + ], + "class": "p5.Color", + "module": "Color" + } + }, + "p5.Element": { + "elt": { + "name": "elt", + "class": "p5.Element", + "module": "DOM" + }, + "width": { + "name": "width", + "class": "p5.Element", + "module": "DOM" + }, + "height": { + "name": "height", + "class": "p5.Element", + "module": "DOM" + }, + "parent": { + "name": "parent", + "class": "p5.Element", + "module": "DOM", + "overloads": [ + { + "params": [ + { + "name": "parent", + "description": "

ID, p5.Element,\n or HTMLElement of desired parent element.

\n", + "type": "String|p5.Element|Object" + } + ], + "chainable": 1 + }, + { + "params": [] + } + ] + }, + "id": { + "name": "id", + "class": "p5.Element", + "module": "DOM", + "overloads": [ + { + "params": [ + { + "name": "id", + "description": "

ID of the element.

\n", + "type": "String" + } + ], + "chainable": 1 + }, + { + "params": [] + } + ] + }, + "class": { + "name": "class", + "class": "p5.Element", + "module": "DOM", + "overloads": [ + { + "params": [ + { + "name": "class", + "description": "

class to add.

\n", + "type": "String" + } + ], + "chainable": 1 + }, + { + "params": [] + } + ] + }, + "mousePressed": { + "name": "mousePressed", + "params": [ + { + "name": "fxn", + "description": "

function to call when the mouse is\n pressed over the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "doubleClicked": { + "name": "doubleClicked", + "params": [ + { + "name": "fxn", + "description": "

function to call when the mouse is\n double clicked over the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "mouseWheel": { + "name": "mouseWheel", + "params": [ + { + "name": "fxn", + "description": "

function to call when the mouse wheel is\n scrolled over the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "mouseReleased": { + "name": "mouseReleased", + "params": [ + { + "name": "fxn", + "description": "

function to call when the mouse is\n pressed over the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "mouseClicked": { + "name": "mouseClicked", + "params": [ + { + "name": "fxn", + "description": "

function to call when the mouse is\n pressed and released over the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "mouseMoved": { + "name": "mouseMoved", + "params": [ + { + "name": "fxn", + "description": "

function to call when the mouse\n moves over the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "mouseOver": { + "name": "mouseOver", + "params": [ + { + "name": "fxn", + "description": "

function to call when the mouse\n moves onto the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "mouseOut": { + "name": "mouseOut", + "params": [ + { + "name": "fxn", + "description": "

function to call when the mouse\n moves off the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "touchStarted": { + "name": "touchStarted", + "params": [ + { + "name": "fxn", + "description": "

function to call when the touch\n starts.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "touchMoved": { + "name": "touchMoved", + "params": [ + { + "name": "fxn", + "description": "

function to call when the touch\n moves over the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "touchEnded": { + "name": "touchEnded", + "params": [ + { + "name": "fxn", + "description": "

function to call when the touch\n ends.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "dragOver": { + "name": "dragOver", + "params": [ + { + "name": "fxn", + "description": "

function to call when the file is\n dragged over the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "dragLeave": { + "name": "dragLeave", + "params": [ + { + "name": "fxn", + "description": "

function to call when the file is\n dragged off the element.\n false disables the function.

\n", + "type": "Function|Boolean" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "addClass": { + "name": "addClass", + "params": [ + { + "name": "class", + "description": "

name of class to add

\n", + "type": "String" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "removeClass": { + "name": "removeClass", + "params": [ + { + "name": "class", + "description": "

name of class to remove

\n", + "type": "String" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "hasClass": { + "name": "hasClass", + "params": [ + { + "name": "c", + "description": "

class name of class to check

\n", + "type": "String" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "toggleClass": { + "name": "toggleClass", + "params": [ + { + "name": "c", + "description": "

class name to toggle

\n", + "type": "String" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "child": { + "name": "child", + "class": "p5.Element", + "module": "DOM", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "child", + "description": "

the ID, DOM node, or p5.Element\n to add to the current element

\n", + "type": "String|p5.Element", + "optional": true + } + ], + "chainable": 1 + } + ] + }, + "center": { + "name": "center", + "params": [ + { + "name": "align", + "description": "

passing 'vertical', 'horizontal' aligns element accordingly

\n", + "type": "String", + "optional": true + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "html": { + "name": "html", + "class": "p5.Element", + "module": "DOM", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "html", + "description": "

the HTML to be placed inside the element

\n", + "type": "String", + "optional": true + }, + { + "name": "append", + "description": "

whether to append HTML to existing

\n", + "type": "Boolean", + "optional": true + } + ], + "chainable": 1 + } + ] + }, + "position": { + "name": "position", + "class": "p5.Element", + "module": "DOM", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "x", + "description": "

x-position relative to upper left of window (optional)

\n", + "type": "Number", + "optional": true + }, + { + "name": "y", + "description": "

y-position relative to upper left of window (optional)

\n", + "type": "Number", + "optional": true + }, + { + "name": "positionType", + "description": "

it can be static, fixed, relative, sticky, initial or inherit (optional)

\n", + "type": "String", + "optional": true + } + ], + "chainable": 1 + } + ] + }, + "style": { + "name": "style", + "class": "p5.Element", + "module": "DOM", + "overloads": [ + { + "params": [ + { + "name": "property", + "description": "

style property to set.

\n", + "type": "String" + } + ] + }, + { + "params": [ + { + "name": "property", + "description": "", + "type": "String" + }, + { + "name": "value", + "description": "

value to assign to the property.

\n", + "type": "String|p5.Color" + } + ], + "chainable": 1 + } + ] + }, + "attribute": { + "name": "attribute", + "class": "p5.Element", + "module": "DOM", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "attr", + "description": "

attribute to set.

\n", + "type": "String" + }, + { + "name": "value", + "description": "

value to assign to the attribute.

\n", + "type": "String" + } + ], + "chainable": 1 + } + ] + }, + "removeAttribute": { + "name": "removeAttribute", + "params": [ + { + "name": "attr", + "description": "

attribute to remove.

\n", + "type": "String" + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "value": { + "name": "value", + "class": "p5.Element", + "module": "DOM", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "value", + "description": "", + "type": "String|Number" + } + ], + "chainable": 1 + } + ] + }, + "show": { + "name": "show", + "class": "p5.Element", + "module": "DOM" + }, + "hide": { + "name": "hide", + "class": "p5.Element", + "module": "DOM" + }, + "size": { + "name": "size", + "class": "p5.Element", + "module": "DOM", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "w", + "description": "

width of the element, either AUTO, or a number.

\n", + "type": "Number|Constant" + }, + { + "name": "h", + "description": "

height of the element, either AUTO, or a number.

\n", + "type": "Number|Constant", + "optional": true + } + ], + "chainable": 1 + } + ] + }, + "remove": { + "name": "remove", + "class": "p5.Element", + "module": "DOM" + }, + "drop": { + "name": "drop", + "params": [ + { + "name": "callback", + "description": "

called when a file loads. Called once for each file dropped.

\n", + "type": "Function" + }, + { + "name": "fxn", + "description": "

called once when any files are dropped.

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5.Element", + "module": "DOM" + }, + "draggable": { + "name": "draggable", + "params": [ + { + "name": "elmnt", + "description": "

pass another p5.Element

\n", + "type": "p5.Element", + "optional": true + } + ], + "class": "p5.Element", + "module": "DOM" + } + }, + "p5.Graphics": { + "reset": { + "name": "reset", + "class": "p5.Graphics", + "module": "Rendering" + }, + "remove": { + "name": "remove", + "class": "p5.Graphics", + "module": "Rendering" + }, + "createFramebuffer": { + "name": "createFramebuffer", + "class": "p5.Graphics", + "module": "Rendering" + } + }, + "JSON": { + "stringify": { + "name": "stringify", + "params": [ + { + "name": "object", + "description": "

:Javascript object that you would like to convert to JSON

\n", + "type": "Object" + } + ], + "class": "JSON", + "module": "Foundation" + } + }, + "console": { + "log": { + "name": "log", + "params": [ + { + "name": "message", + "description": "

:Message that you would like to print to the console

\n", + "type": "String|Expression|Object" + } + ], + "class": "console", + "module": "Foundation" + } + }, + "p5.TypedDict": { + "size": { + "name": "size", + "class": "p5.TypedDict", + "module": "Data" + }, + "hasKey": { + "name": "hasKey", + "params": [ + { + "name": "key", + "description": "

that you want to look up

\n", + "type": "Number|String" + } + ], + "class": "p5.TypedDict", + "module": "Data" + }, + "get": { + "name": "get", + "params": [ + { + "name": "the", + "description": "

key you want to access

\n", + "type": "Number|String" + } + ], + "class": "p5.TypedDict", + "module": "Data" + }, + "set": { + "name": "set", + "params": [ + { + "name": "key", + "description": "", + "type": "Number|String" + }, + { + "name": "value", + "description": "", + "type": "Number|String" + } + ], + "class": "p5.TypedDict", + "module": "Data" + }, + "create": { + "name": "create", + "class": "p5.TypedDict", + "module": "Data", + "overloads": [ + { + "params": [ + { + "name": "key", + "description": "", + "type": "Number|String" + }, + { + "name": "value", + "description": "", + "type": "Number|String" + } + ] + }, + { + "params": [ + { + "name": "obj", + "description": "

key/value pair

\n", + "type": "Object" + } + ] + } + ] + }, + "clear": { + "name": "clear", + "class": "p5.TypedDict", + "module": "Data" + }, + "remove": { + "name": "remove", + "params": [ + { + "name": "key", + "description": "

for the pair to remove

\n", + "type": "Number|String" + } + ], + "class": "p5.TypedDict", + "module": "Data" + }, + "print": { + "name": "print", + "class": "p5.TypedDict", + "module": "Data" + }, + "saveTable": { + "name": "saveTable", + "class": "p5.TypedDict", + "module": "Data" + }, + "saveJSON": { + "name": "saveJSON", + "class": "p5.TypedDict", + "module": "Data" + } + }, + "p5.NumberDict": { + "add": { + "name": "add", + "params": [ + { + "name": "Key", + "description": "

for the value you wish to add to

\n", + "type": "Number" + }, + { + "name": "Number", + "description": "

to add to the value

\n", + "type": "Number" + } + ], + "class": "p5.NumberDict", + "module": "Data" + }, + "sub": { + "name": "sub", + "params": [ + { + "name": "Key", + "description": "

for the value you wish to subtract from

\n", + "type": "Number" + }, + { + "name": "Number", + "description": "

to subtract from the value

\n", + "type": "Number" + } + ], + "class": "p5.NumberDict", + "module": "Data" + }, + "mult": { + "name": "mult", + "params": [ + { + "name": "Key", + "description": "

for value you wish to multiply

\n", + "type": "Number" + }, + { + "name": "Amount", + "description": "

to multiply the value by

\n", + "type": "Number" + } + ], + "class": "p5.NumberDict", + "module": "Data" + }, + "div": { + "name": "div", + "params": [ + { + "name": "Key", + "description": "

for value you wish to divide

\n", + "type": "Number" + }, + { + "name": "Amount", + "description": "

to divide the value by

\n", + "type": "Number" + } + ], + "class": "p5.NumberDict", + "module": "Data" + }, + "minValue": { + "name": "minValue", + "class": "p5.NumberDict", + "module": "Data" + }, + "maxValue": { + "name": "maxValue", + "class": "p5.NumberDict", + "module": "Data" + }, + "minKey": { + "name": "minKey", + "class": "p5.NumberDict", + "module": "Data" + }, + "maxKey": { + "name": "maxKey", + "class": "p5.NumberDict", + "module": "Data" + } + }, + "p5.MediaElement": { + "src": { + "name": "src", + "class": "p5.MediaElement", + "module": "DOM" + }, + "play": { + "name": "play", + "class": "p5.MediaElement", + "module": "DOM" + }, + "stop": { + "name": "stop", + "class": "p5.MediaElement", + "module": "DOM" + }, + "pause": { + "name": "pause", + "class": "p5.MediaElement", + "module": "DOM" + }, + "loop": { + "name": "loop", + "class": "p5.MediaElement", + "module": "DOM" + }, + "noLoop": { + "name": "noLoop", + "class": "p5.MediaElement", + "module": "DOM" + }, + "autoplay": { + "name": "autoplay", + "params": [ + { + "name": "shouldAutoplay", + "description": "

whether the element should autoplay.

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5.MediaElement", + "module": "DOM" + }, + "volume": { + "name": "volume", + "class": "p5.MediaElement", + "module": "DOM", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "val", + "description": "

volume between 0.0 and 1.0.

\n", + "type": "Number" + } + ], + "chainable": 1 + } + ] + }, + "speed": { + "name": "speed", + "class": "p5.MediaElement", + "module": "DOM", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "speed", + "description": "

speed multiplier for playback.

\n", + "type": "Number" + } + ], + "chainable": 1 + } + ] + }, + "time": { + "name": "time", + "class": "p5.MediaElement", + "module": "DOM", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "time", + "description": "

time to jump to (in seconds).

\n", + "type": "Number" + } + ], + "chainable": 1 + } + ] + }, + "duration": { + "name": "duration", + "class": "p5.MediaElement", + "module": "DOM" + }, + "onended": { + "name": "onended", + "params": [ + { + "name": "callback", + "description": "

function to call when playback ends.\n The p5.MediaElement is passed as\n the argument.

\n", + "type": "Function" + } + ], + "class": "p5.MediaElement", + "module": "DOM" + }, + "connect": { + "name": "connect", + "params": [ + { + "name": "audioNode", + "description": "

AudioNode from the Web Audio API,\nor an object from the p5.sound library

\n", + "type": "AudioNode|Object" + } + ], + "class": "p5.MediaElement", + "module": "DOM" + }, + "disconnect": { + "name": "disconnect", + "class": "p5.MediaElement", + "module": "DOM" + }, + "showControls": { + "name": "showControls", + "class": "p5.MediaElement", + "module": "DOM" + }, + "hideControls": { + "name": "hideControls", + "class": "p5.MediaElement", + "module": "DOM" + }, + "addCue": { + "name": "addCue", + "params": [ + { + "name": "time", + "description": "

cue time to run the callback function.

\n", + "type": "Number" + }, + { + "name": "callback", + "description": "

function to call at the cue time.

\n", + "type": "Function" + }, + { + "name": "value", + "description": "

object to pass as the argument to\n callback.

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5.MediaElement", + "module": "DOM" + }, + "removeCue": { + "name": "removeCue", + "params": [ + { + "name": "id", + "description": "

ID of the cue, created by media.addCue().

\n", + "type": "Number" + } + ], + "class": "p5.MediaElement", + "module": "DOM" + }, + "clearCues": { + "name": "clearCues", + "class": "p5.MediaElement", + "module": "DOM" + } + }, + "p5.File": { + "file": { + "name": "file", + "class": "p5.File", + "module": "DOM" + }, + "type": { + "name": "type", + "class": "p5.File", + "module": "DOM" + }, + "subtype": { + "name": "subtype", + "class": "p5.File", + "module": "DOM" + }, + "name": { + "name": "name", + "class": "p5.File", + "module": "DOM" + }, + "size": { + "name": "size", + "class": "p5.File", + "module": "DOM" + }, + "data": { + "name": "data", + "class": "p5.File", + "module": "DOM" + } + }, + "p5.Image": { + "width": { + "name": "width", + "class": "p5.Image", + "module": "Image" + }, + "height": { + "name": "height", + "class": "p5.Image", + "module": "Image" + }, + "pixels": { + "name": "pixels", + "class": "p5.Image", + "module": "Image" + }, + "pixelDensity": { + "name": "pixelDensity", + "params": [ + { + "name": "density", + "description": "

A scaling factor for the number of pixels per\nside

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Image", + "module": "Image" + }, + "loadPixels": { + "name": "loadPixels", + "class": "p5.Image", + "module": "Image" + }, + "updatePixels": { + "name": "updatePixels", + "class": "p5.Image", + "module": "Image", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x-coordinate of the upper-left corner\n of the subsection to update.

\n", + "type": "Integer" + }, + { + "name": "y", + "description": "

y-coordinate of the upper-left corner\n of the subsection to update.

\n", + "type": "Integer" + }, + { + "name": "w", + "description": "

width of the subsection to update.

\n", + "type": "Integer" + }, + { + "name": "h", + "description": "

height of the subsection to update.

\n", + "type": "Integer" + } + ] + }, + { + "params": [] + } + ] + }, + "get": { + "name": "get", + "class": "p5.Image", + "module": "Image", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x-coordinate of the pixel.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the pixel.

\n", + "type": "Number" + }, + { + "name": "w", + "description": "

width of the subsection to be returned.

\n", + "type": "Number" + }, + { + "name": "h", + "description": "

height of the subsection to be returned.

\n", + "type": "Number" + } + ] + }, + { + "params": [] + }, + { + "params": [ + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + } + ] + } + ] + }, + "set": { + "name": "set", + "params": [ + { + "name": "x", + "description": "

x-coordinate of the pixel.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the pixel.

\n", + "type": "Number" + }, + { + "name": "a", + "description": "

grayscale value | pixel array |\n p5.Color object |\n p5.Image to copy.

\n", + "type": "Number|Number[]|Object" + } + ], + "class": "p5.Image", + "module": "Image" + }, + "resize": { + "name": "resize", + "params": [ + { + "name": "width", + "description": "

resized image width.

\n", + "type": "Number" + }, + { + "name": "height", + "description": "

resized image height.

\n", + "type": "Number" + } + ], + "class": "p5.Image", + "module": "Image" + }, + "copy": { + "name": "copy", + "class": "p5.Image", + "module": "Image", + "overloads": [ + { + "params": [ + { + "name": "srcImage", + "description": "

source image.

\n", + "type": "p5.Image|p5.Element" + }, + { + "name": "sx", + "description": "

x-coordinate of the source's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "sy", + "description": "

y-coordinate of the source's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "sw", + "description": "

source image width.

\n", + "type": "Integer" + }, + { + "name": "sh", + "description": "

source image height.

\n", + "type": "Integer" + }, + { + "name": "dx", + "description": "

x-coordinate of the destination's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "dy", + "description": "

y-coordinate of the destination's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "dw", + "description": "

destination image width.

\n", + "type": "Integer" + }, + { + "name": "dh", + "description": "

destination image height.

\n", + "type": "Integer" + } + ] + }, + { + "params": [ + { + "name": "sx", + "description": "", + "type": "Integer" + }, + { + "name": "sy", + "description": "", + "type": "Integer" + }, + { + "name": "sw", + "description": "", + "type": "Integer" + }, + { + "name": "sh", + "description": "", + "type": "Integer" + }, + { + "name": "dx", + "description": "", + "type": "Integer" + }, + { + "name": "dy", + "description": "", + "type": "Integer" + }, + { + "name": "dw", + "description": "", + "type": "Integer" + }, + { + "name": "dh", + "description": "", + "type": "Integer" + } + ] + } + ] + }, + "mask": { + "name": "mask", + "params": [ + { + "name": "srcImage", + "description": "

source image.

\n", + "type": "p5.Image" + } + ], + "class": "p5.Image", + "module": "Image" + }, + "filter": { + "name": "filter", + "params": [ + { + "name": "filterType", + "description": "

either THRESHOLD, GRAY, OPAQUE, INVERT,\n POSTERIZE, ERODE, DILATE or BLUR.

\n", + "type": "Constant" + }, + { + "name": "filterParam", + "description": "

parameter unique to each filter.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Image", + "module": "Image" + }, + "blend": { + "name": "blend", + "class": "p5.Image", + "module": "Image", + "overloads": [ + { + "params": [ + { + "name": "srcImage", + "description": "

source image

\n", + "type": "p5.Image" + }, + { + "name": "sx", + "description": "

x-coordinate of the source's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "sy", + "description": "

y-coordinate of the source's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "sw", + "description": "

source image width.

\n", + "type": "Integer" + }, + { + "name": "sh", + "description": "

source image height.

\n", + "type": "Integer" + }, + { + "name": "dx", + "description": "

x-coordinate of the destination's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "dy", + "description": "

y-coordinate of the destination's upper-left corner.

\n", + "type": "Integer" + }, + { + "name": "dw", + "description": "

destination image width.

\n", + "type": "Integer" + }, + { + "name": "dh", + "description": "

destination image height.

\n", + "type": "Integer" + }, + { + "name": "blendMode", + "description": "

the blend mode. either\n BLEND, DARKEST, LIGHTEST, DIFFERENCE,\n MULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,\n SOFT_LIGHT, DODGE, BURN, ADD or NORMAL.

\n

Available blend modes are: normal | multiply | screen | overlay |\n darken | lighten | color-dodge | color-burn | hard-light |\n soft-light | difference | exclusion | hue | saturation |\n color | luminosity

\n

http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/

\n", + "type": "Constant" + } + ] + }, + { + "params": [ + { + "name": "sx", + "description": "", + "type": "Integer" + }, + { + "name": "sy", + "description": "", + "type": "Integer" + }, + { + "name": "sw", + "description": "", + "type": "Integer" + }, + { + "name": "sh", + "description": "", + "type": "Integer" + }, + { + "name": "dx", + "description": "", + "type": "Integer" + }, + { + "name": "dy", + "description": "", + "type": "Integer" + }, + { + "name": "dw", + "description": "", + "type": "Integer" + }, + { + "name": "dh", + "description": "", + "type": "Integer" + }, + { + "name": "blendMode", + "description": "", + "type": "Constant" + } + ] + } + ] + }, + "save": { + "name": "save", + "params": [ + { + "name": "filename", + "description": "

filename. Defaults to 'untitled'.

\n", + "type": "String" + }, + { + "name": "extension", + "description": "

file extension, either 'png' or 'jpg'.\n Defaults to 'png'.

\n", + "type": "String", + "optional": true + } + ], + "class": "p5.Image", + "module": "Image" + }, + "reset": { + "name": "reset", + "class": "p5.Image", + "module": "Image" + }, + "getCurrentFrame": { + "name": "getCurrentFrame", + "class": "p5.Image", + "module": "Image" + }, + "setFrame": { + "name": "setFrame", + "params": [ + { + "name": "index", + "description": "

index of the frame to display.

\n", + "type": "Number" + } + ], + "class": "p5.Image", + "module": "Image" + }, + "numFrames": { + "name": "numFrames", + "class": "p5.Image", + "module": "Image" + }, + "play": { + "name": "play", + "class": "p5.Image", + "module": "Image" + }, + "pause": { + "name": "pause", + "class": "p5.Image", + "module": "Image" + }, + "delay": { + "name": "delay", + "params": [ + { + "name": "d", + "description": "

delay in milliseconds between switching frames.

\n", + "type": "Number" + }, + { + "name": "index", + "description": "

index of the frame that will have its delay modified.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Image", + "module": "Image" + } + }, + "p5.PrintWriter": { + "write": { + "name": "write", + "params": [ + { + "name": "data", + "description": "

all data to be written by the PrintWriter

\n", + "type": "Array" + } + ], + "class": "p5.PrintWriter", + "module": "IO" + }, + "print": { + "name": "print", + "params": [ + { + "name": "data", + "description": "

all data to be printed by the PrintWriter

\n", + "type": "Array" + } + ], + "class": "p5.PrintWriter", + "module": "IO" + }, + "clear": { + "name": "clear", + "class": "p5.PrintWriter", + "module": "IO" + }, + "close": { + "name": "close", + "class": "p5.PrintWriter", + "module": "IO" + } + }, + "p5.Table": { + "columns": { + "name": "columns", + "class": "p5.Table", + "module": "IO" + }, + "rows": { + "name": "rows", + "class": "p5.Table", + "module": "IO" + }, + "addRow": { + "name": "addRow", + "params": [ + { + "name": "row", + "description": "

row to be added to the table

\n", + "type": "p5.TableRow", + "optional": true + } + ], + "class": "p5.Table", + "module": "IO" + }, + "removeRow": { + "name": "removeRow", + "params": [ + { + "name": "id", + "description": "

ID number of the row to remove

\n", + "type": "Integer" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "getRow": { + "name": "getRow", + "params": [ + { + "name": "rowID", + "description": "

ID number of the row to get

\n", + "type": "Integer" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "getRows": { + "name": "getRows", + "class": "p5.Table", + "module": "IO" + }, + "findRow": { + "name": "findRow", + "params": [ + { + "name": "value", + "description": "

The value to match

\n", + "type": "String" + }, + { + "name": "column", + "description": "

ID number or title of the\n column to search

\n", + "type": "Integer|String" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "findRows": { + "name": "findRows", + "params": [ + { + "name": "value", + "description": "

The value to match

\n", + "type": "String" + }, + { + "name": "column", + "description": "

ID number or title of the\n column to search

\n", + "type": "Integer|String" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "matchRow": { + "name": "matchRow", + "params": [ + { + "name": "regexp", + "description": "

The regular expression to match

\n", + "type": "String|RegExp" + }, + { + "name": "column", + "description": "

The column ID (number) or\n title (string)

\n", + "type": "String|Integer" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "matchRows": { + "name": "matchRows", + "params": [ + { + "name": "regexp", + "description": "

The regular expression to match

\n", + "type": "String" + }, + { + "name": "column", + "description": "

The column ID (number) or\n title (string)

\n", + "type": "String|Integer", + "optional": true + } + ], + "class": "p5.Table", + "module": "IO" + }, + "getColumn": { + "name": "getColumn", + "params": [ + { + "name": "column", + "description": "

String or Number of the column to return

\n", + "type": "String|Number" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "clearRows": { + "name": "clearRows", + "class": "p5.Table", + "module": "IO" + }, + "addColumn": { + "name": "addColumn", + "params": [ + { + "name": "title", + "description": "

title of the given column

\n", + "type": "String", + "optional": true + } + ], + "class": "p5.Table", + "module": "IO" + }, + "getColumnCount": { + "name": "getColumnCount", + "class": "p5.Table", + "module": "IO" + }, + "getRowCount": { + "name": "getRowCount", + "class": "p5.Table", + "module": "IO" + }, + "removeTokens": { + "name": "removeTokens", + "params": [ + { + "name": "chars", + "description": "

String listing characters to be removed

\n", + "type": "String" + }, + { + "name": "column", + "description": "

Column ID (number)\n or name (string)

\n", + "type": "String|Integer", + "optional": true + } + ], + "class": "p5.Table", + "module": "IO" + }, + "trim": { + "name": "trim", + "params": [ + { + "name": "column", + "description": "

Column ID (number)\n or name (string)

\n", + "type": "String|Integer", + "optional": true + } + ], + "class": "p5.Table", + "module": "IO" + }, + "removeColumn": { + "name": "removeColumn", + "params": [ + { + "name": "column", + "description": "

columnName (string) or ID (number)

\n", + "type": "String|Integer" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "set": { + "name": "set", + "params": [ + { + "name": "row", + "description": "

row ID

\n", + "type": "Integer" + }, + { + "name": "column", + "description": "

column ID (Number)\n or title (String)

\n", + "type": "String|Integer" + }, + { + "name": "value", + "description": "

value to assign

\n", + "type": "String|Number" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "setNum": { + "name": "setNum", + "params": [ + { + "name": "row", + "description": "

row ID

\n", + "type": "Integer" + }, + { + "name": "column", + "description": "

column ID (Number)\n or title (String)

\n", + "type": "String|Integer" + }, + { + "name": "value", + "description": "

value to assign

\n", + "type": "Number" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "setString": { + "name": "setString", + "params": [ + { + "name": "row", + "description": "

row ID

\n", + "type": "Integer" + }, + { + "name": "column", + "description": "

column ID (Number)\n or title (String)

\n", + "type": "String|Integer" + }, + { + "name": "value", + "description": "

value to assign

\n", + "type": "String" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "get": { + "name": "get", + "params": [ + { + "name": "row", + "description": "

row ID

\n", + "type": "Integer" + }, + { + "name": "column", + "description": "

columnName (string) or\n ID (number)

\n", + "type": "String|Integer" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "getNum": { + "name": "getNum", + "params": [ + { + "name": "row", + "description": "

row ID

\n", + "type": "Integer" + }, + { + "name": "column", + "description": "

columnName (string) or\n ID (number)

\n", + "type": "String|Integer" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "getString": { + "name": "getString", + "params": [ + { + "name": "row", + "description": "

row ID

\n", + "type": "Integer" + }, + { + "name": "column", + "description": "

columnName (string) or\n ID (number)

\n", + "type": "String|Integer" + } + ], + "class": "p5.Table", + "module": "IO" + }, + "getObject": { + "name": "getObject", + "params": [ + { + "name": "headerColumn", + "description": "

Name of the column which should be used to\n title each row object (optional)

\n", + "type": "String", + "optional": true + } + ], + "class": "p5.Table", + "module": "IO" + }, + "getArray": { + "name": "getArray", + "class": "p5.Table", + "module": "IO" + } + }, + "p5.TableRow": { + "set": { + "name": "set", + "params": [ + { + "name": "column", + "description": "

Column ID (Number)\n or Title (String)

\n", + "type": "String|Integer" + }, + { + "name": "value", + "description": "

The value to be stored

\n", + "type": "String|Number" + } + ], + "class": "p5.TableRow", + "module": "IO" + }, + "setNum": { + "name": "setNum", + "params": [ + { + "name": "column", + "description": "

Column ID (Number)\n or Title (String)

\n", + "type": "String|Integer" + }, + { + "name": "value", + "description": "

The value to be stored\n as a Float

\n", + "type": "Number|String" + } + ], + "class": "p5.TableRow", + "module": "IO" + }, + "setString": { + "name": "setString", + "params": [ + { + "name": "column", + "description": "

Column ID (Number)\n or Title (String)

\n", + "type": "String|Integer" + }, + { + "name": "value", + "description": "

The value to be stored\n as a String

\n", + "type": "String|Number|Boolean|Object" + } + ], + "class": "p5.TableRow", + "module": "IO" + }, + "get": { + "name": "get", + "params": [ + { + "name": "column", + "description": "

columnName (string) or\n ID (number)

\n", + "type": "String|Integer" + } + ], + "class": "p5.TableRow", + "module": "IO" + }, + "getNum": { + "name": "getNum", + "params": [ + { + "name": "column", + "description": "

columnName (string) or\n ID (number)

\n", + "type": "String|Integer" + } + ], + "class": "p5.TableRow", + "module": "IO" + }, + "getString": { + "name": "getString", + "params": [ + { + "name": "column", + "description": "

columnName (string) or\n ID (number)

\n", + "type": "String|Integer" + } + ], + "class": "p5.TableRow", + "module": "IO" + } + }, + "p5.XML": { + "getParent": { + "name": "getParent", + "class": "p5.XML", + "module": "IO" + }, + "getName": { + "name": "getName", + "class": "p5.XML", + "module": "IO" + }, + "setName": { + "name": "setName", + "params": [ + { + "name": "the", + "description": "

new name of the node

\n", + "type": "String" + } + ], + "class": "p5.XML", + "module": "IO" + }, + "hasChildren": { + "name": "hasChildren", + "class": "p5.XML", + "module": "IO" + }, + "listChildren": { + "name": "listChildren", + "class": "p5.XML", + "module": "IO" + }, + "getChildren": { + "name": "getChildren", + "params": [ + { + "name": "name", + "description": "

element name

\n", + "type": "String", + "optional": true + } + ], + "class": "p5.XML", + "module": "IO" + }, + "getChild": { + "name": "getChild", + "params": [ + { + "name": "name", + "description": "

element name or index

\n", + "type": "String|Integer" + } + ], + "class": "p5.XML", + "module": "IO" + }, + "addChild": { + "name": "addChild", + "params": [ + { + "name": "node", + "description": "

a p5.XML Object which will be the child to be added

\n", + "type": "p5.XML" + } + ], + "class": "p5.XML", + "module": "IO" + }, + "removeChild": { + "name": "removeChild", + "params": [ + { + "name": "name", + "description": "

element name or index

\n", + "type": "String|Integer" + } + ], + "class": "p5.XML", + "module": "IO" + }, + "getAttributeCount": { + "name": "getAttributeCount", + "class": "p5.XML", + "module": "IO" + }, + "listAttributes": { + "name": "listAttributes", + "class": "p5.XML", + "module": "IO" + }, + "hasAttribute": { + "name": "hasAttribute", + "params": [ + { + "name": "the", + "description": "

attribute to be checked

\n", + "type": "String" + } + ], + "class": "p5.XML", + "module": "IO" + }, + "getNum": { + "name": "getNum", + "params": [ + { + "name": "name", + "description": "

the non-null full name of the attribute

\n", + "type": "String" + }, + { + "name": "defaultValue", + "description": "

the default value of the attribute

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.XML", + "module": "IO" + }, + "getString": { + "name": "getString", + "params": [ + { + "name": "name", + "description": "

the non-null full name of the attribute

\n", + "type": "String" + }, + { + "name": "defaultValue", + "description": "

the default value of the attribute

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.XML", + "module": "IO" + }, + "setAttribute": { + "name": "setAttribute", + "params": [ + { + "name": "name", + "description": "

the full name of the attribute

\n", + "type": "String" + }, + { + "name": "value", + "description": "

the value of the attribute

\n", + "type": "Number|String|Boolean" + } + ], + "class": "p5.XML", + "module": "IO" + }, + "getContent": { + "name": "getContent", + "params": [ + { + "name": "defaultValue", + "description": "

value returned if no content is found

\n", + "type": "String", + "optional": true + } + ], + "class": "p5.XML", + "module": "IO" + }, + "setContent": { + "name": "setContent", + "params": [ + { + "name": "text", + "description": "

the new content

\n", + "type": "String" + } + ], + "class": "p5.XML", + "module": "IO" + }, + "serialize": { + "name": "serialize", + "class": "p5.XML", + "module": "IO" + } + }, + "p5.Vector": { + "x": { + "name": "x", + "class": "p5.Vector", + "module": "Math" + }, + "y": { + "name": "y", + "class": "p5.Vector", + "module": "Math" + }, + "z": { + "name": "z", + "class": "p5.Vector", + "module": "Math" + }, + "toString": { + "name": "toString", + "class": "p5.Vector", + "module": "Math" + }, + "set": { + "name": "set", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x component of the vector.

\n", + "type": "Number", + "optional": true + }, + { + "name": "y", + "description": "

y component of the vector.

\n", + "type": "Number", + "optional": true + }, + { + "name": "z", + "description": "

z component of the vector.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "value", + "description": "

vector to set.

\n", + "type": "p5.Vector|Number[]" + } + ], + "chainable": 1 + } + ] + }, + "copy": { + "name": "copy", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "v", + "description": "

the p5.Vector to create a copy of

\n", + "type": "p5.Vector" + } + ], + "static": 1 + } + ] + }, + "add": { + "name": "add", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x component of the vector to be added.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y component of the vector to be added.

\n", + "type": "Number", + "optional": true + }, + { + "name": "z", + "description": "

z component of the vector to be added.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "value", + "description": "

The vector to add

\n", + "type": "p5.Vector|Number[]" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v1", + "description": "

A p5.Vector to add

\n", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "

A p5.Vector to add

\n", + "type": "p5.Vector" + }, + { + "name": "target", + "description": "

vector to receive the result.

\n", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + } + ] + }, + "rem": { + "name": "rem", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x component of divisor vector.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y component of divisor vector.

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

z component of divisor vector.

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "value", + "description": "

divisor vector.

\n", + "type": "p5.Vector | Number[]" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v1", + "description": "

The dividend p5.Vector

\n", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "

The divisor p5.Vector

\n", + "type": "p5.Vector" + } + ], + "static": 1 + }, + { + "params": [ + { + "name": "v1", + "description": "", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "", + "type": "p5.Vector" + } + ], + "static": 1 + } + ] + }, + "sub": { + "name": "sub", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x component of the vector to subtract.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y component of the vector to subtract.

\n", + "type": "Number", + "optional": true + }, + { + "name": "z", + "description": "

z component of the vector to subtract.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "value", + "description": "

the vector to subtract

\n", + "type": "p5.Vector|Number[]" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v1", + "description": "

A p5.Vector to subtract from

\n", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "

A p5.Vector to subtract

\n", + "type": "p5.Vector" + }, + { + "name": "target", + "description": "

vector to receive the result.

\n", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + } + ] + }, + "mult": { + "name": "mult", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "

The number to multiply with the vector

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x", + "description": "

number to multiply with the x component of the vector.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

number to multiply with the y component of the vector.

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

number to multiply with the z component of the vector.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "arr", + "description": "

array to multiply with the components of the vector.

\n", + "type": "Number[]" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v", + "description": "

vector to multiply with the components of the original vector.

\n", + "type": "p5.Vector" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "z", + "description": "", + "type": "Number", + "optional": true + } + ], + "static": 1 + }, + { + "params": [ + { + "name": "v", + "description": "", + "type": "p5.Vector" + }, + { + "name": "n", + "description": "", + "type": "Number" + }, + { + "name": "target", + "description": "

vector to receive the result.

\n", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + }, + { + "params": [ + { + "name": "v0", + "description": "", + "type": "p5.Vector" + }, + { + "name": "v1", + "description": "", + "type": "p5.Vector" + }, + { + "name": "target", + "description": "", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + }, + { + "params": [ + { + "name": "v0", + "description": "", + "type": "p5.Vector" + }, + { + "name": "arr", + "description": "", + "type": "Number[]" + }, + { + "name": "target", + "description": "", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + } + ] + }, + "div": { + "name": "div", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "n", + "description": "

The number to divide the vector by

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x", + "description": "

number to divide with the x component of the vector.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

number to divide with the y component of the vector.

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

number to divide with the z component of the vector.

\n", + "type": "Number", + "optional": true + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "arr", + "description": "

array to divide the components of the vector by.

\n", + "type": "Number[]" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v", + "description": "

vector to divide the components of the original vector by.

\n", + "type": "p5.Vector" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + }, + { + "name": "z", + "description": "", + "type": "Number", + "optional": true + } + ], + "static": 1 + }, + { + "params": [ + { + "name": "v", + "description": "", + "type": "p5.Vector" + }, + { + "name": "n", + "description": "", + "type": "Number" + }, + { + "name": "target", + "description": "

The vector to receive the result

\n", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + }, + { + "params": [ + { + "name": "v0", + "description": "", + "type": "p5.Vector" + }, + { + "name": "v1", + "description": "", + "type": "p5.Vector" + }, + { + "name": "target", + "description": "", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + }, + { + "params": [ + { + "name": "v0", + "description": "", + "type": "p5.Vector" + }, + { + "name": "arr", + "description": "", + "type": "Number[]" + }, + { + "name": "target", + "description": "", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + } + ] + }, + "mag": { + "name": "mag", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "vecT", + "description": "

The vector to return the magnitude of

\n", + "type": "p5.Vector" + } + ], + "static": 1 + } + ] + }, + "magSq": { + "name": "magSq", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "vecT", + "description": "

the vector to return the squared magnitude of

\n", + "type": "p5.Vector" + } + ], + "static": 1 + } + ] + }, + "dot": { + "name": "dot", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x component of the vector.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y component of the vector.

\n", + "type": "Number", + "optional": true + }, + { + "name": "z", + "description": "

z component of the vector.

\n", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "v", + "description": "

p5.Vector to be dotted.

\n", + "type": "p5.Vector" + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "

first p5.Vector.

\n", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "

second p5.Vector.

\n", + "type": "p5.Vector" + } + ], + "static": 1 + } + ] + }, + "cross": { + "name": "cross", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "v", + "description": "

p5.Vector to be crossed.

\n", + "type": "p5.Vector" + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "

first p5.Vector.

\n", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "

second p5.Vector.

\n", + "type": "p5.Vector" + } + ], + "static": 1 + } + ] + }, + "dist": { + "name": "dist", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "v", + "description": "

x, y, and z coordinates of a p5.Vector.

\n", + "type": "p5.Vector" + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "

The first p5.Vector

\n", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "

The second p5.Vector

\n", + "type": "p5.Vector" + } + ], + "static": 1 + } + ] + }, + "normalize": { + "name": "normalize", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "v", + "description": "

The vector to normalize

\n", + "type": "p5.Vector" + }, + { + "name": "target", + "description": "

The vector to receive the result

\n", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + } + ] + }, + "limit": { + "name": "limit", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "max", + "description": "

maximum magnitude for the vector.

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v", + "description": "

the vector to limit

\n", + "type": "p5.Vector" + }, + { + "name": "max", + "description": "", + "type": "Number" + }, + { + "name": "target", + "description": "

the vector to receive the result (Optional)

\n", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + } + ] + }, + "setMag": { + "name": "setMag", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "len", + "description": "

new length for this vector.

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v", + "description": "

the vector to set the magnitude of

\n", + "type": "p5.Vector" + }, + { + "name": "len", + "description": "", + "type": "Number" + }, + { + "name": "target", + "description": "

the vector to receive the result (Optional)

\n", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + } + ] + }, + "heading": { + "name": "heading", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "v", + "description": "

the vector to find the angle of

\n", + "type": "p5.Vector" + } + ], + "static": 1 + } + ] + }, + "setHeading": { + "name": "setHeading", + "params": [ + { + "name": "angle", + "description": "

angle of rotation.

\n", + "type": "Number" + } + ], + "class": "p5.Vector", + "module": "Math" + }, + "rotate": { + "name": "rotate", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "angle", + "description": "

angle of rotation.

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v", + "description": "", + "type": "p5.Vector" + }, + { + "name": "angle", + "description": "", + "type": "Number" + }, + { + "name": "target", + "description": "

The vector to receive the result

\n", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + } + ] + }, + "angleBetween": { + "name": "angleBetween", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "value", + "description": "

x, y, and z components of a p5.Vector.

\n", + "type": "p5.Vector" + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "

the first vector.

\n", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "

the second vector.

\n", + "type": "p5.Vector" + } + ], + "static": 1 + } + ] + }, + "lerp": { + "name": "lerp", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x component.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y component.

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

z component.

\n", + "type": "Number" + }, + { + "name": "amt", + "description": "

amount of interpolation between 0.0 (old vector)\n and 1.0 (new vector). 0.5 is halfway between.

\n", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v", + "description": "

p5.Vector to lerp toward.

\n", + "type": "p5.Vector" + }, + { + "name": "amt", + "description": "", + "type": "Number" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "v1", + "description": "", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "", + "type": "p5.Vector" + }, + { + "name": "amt", + "description": "", + "type": "Number" + }, + { + "name": "target", + "description": "

The vector to receive the result

\n", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + } + ] + }, + "slerp": { + "name": "slerp", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "v", + "description": "

p5.Vector to slerp toward.

\n", + "type": "p5.Vector" + }, + { + "name": "amt", + "description": "

amount of interpolation between 0.0 (old vector)\n and 1.0 (new vector). 0.5 is halfway between.

\n", + "type": "Number" + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "

old vector.

\n", + "type": "p5.Vector" + }, + { + "name": "v2", + "description": "

new vector.

\n", + "type": "p5.Vector" + }, + { + "name": "amt", + "description": "", + "type": "Number" + }, + { + "name": "target", + "description": "

vector to receive the result.

\n", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + } + ] + }, + "reflect": { + "name": "reflect", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "surfaceNormal", + "description": "

p5.Vector\n to reflect about.

\n", + "type": "p5.Vector" + } + ], + "chainable": 1 + }, + { + "params": [ + { + "name": "incidentVector", + "description": "

vector to be reflected.

\n", + "type": "p5.Vector" + }, + { + "name": "surfaceNormal", + "description": "", + "type": "p5.Vector" + }, + { + "name": "target", + "description": "

vector to receive the result.

\n", + "type": "p5.Vector", + "optional": true + } + ], + "static": 1 + } + ] + }, + "array": { + "name": "array", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [] + }, + { + "params": [ + { + "name": "v", + "description": "

the vector to convert to an array

\n", + "type": "p5.Vector" + } + ], + "static": 1 + } + ] + }, + "equals": { + "name": "equals", + "class": "p5.Vector", + "module": "Math", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x component of the vector.

\n", + "type": "Number", + "optional": true + }, + { + "name": "y", + "description": "

y component of the vector.

\n", + "type": "Number", + "optional": true + }, + { + "name": "z", + "description": "

z component of the vector.

\n", + "type": "Number", + "optional": true + } + ] + }, + { + "params": [ + { + "name": "value", + "description": "

vector to compare.

\n", + "type": "p5.Vector|Array" + } + ] + }, + { + "params": [ + { + "name": "v1", + "description": "

the first vector to compare

\n", + "type": "p5.Vector|Array" + }, + { + "name": "v2", + "description": "

the second vector to compare

\n", + "type": "p5.Vector|Array" + } + ], + "static": 1 + } + ] + }, + "fromAngle": { + "name": "fromAngle", + "params": [ + { + "name": "angle", + "description": "

desired angle, in radians. Unaffected by angleMode().

\n", + "type": "Number" + }, + { + "name": "length", + "description": "

length of the new vector (defaults to 1).

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Vector", + "module": "Math" + }, + "fromAngles": { + "name": "fromAngles", + "params": [ + { + "name": "theta", + "description": "

polar angle in radians (zero is up).

\n", + "type": "Number" + }, + { + "name": "phi", + "description": "

azimuthal angle in radians\n (zero is out of the screen).

\n", + "type": "Number" + }, + { + "name": "length", + "description": "

length of the new vector (defaults to 1).

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Vector", + "module": "Math" + }, + "random2D": { + "name": "random2D", + "class": "p5.Vector", + "module": "Math" + }, + "random3D": { + "name": "random3D", + "class": "p5.Vector", + "module": "Math" + } + }, + "p5.Font": { + "font": { + "name": "font", + "class": "p5.Font", + "module": "Typography" + }, + "textBounds": { + "name": "textBounds", + "params": [ + { + "name": "str", + "description": "

string of text.

\n", + "type": "String" + }, + { + "name": "x", + "description": "

x-coordinate of the text.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the text.

\n", + "type": "Number" + }, + { + "name": "fontSize", + "description": "

font size. Defaults to the current\n textSize().

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Font", + "module": "Typography" + }, + "textToPoints": { + "name": "textToPoints", + "params": [ + { + "name": "str", + "description": "

string of text.

\n", + "type": "String" + }, + { + "name": "x", + "description": "

x-coordinate of the text.

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the text.

\n", + "type": "Number" + }, + { + "name": "fontSize", + "description": "

font size. Defaults to the current\n textSize().

\n", + "type": "Number", + "optional": true + }, + { + "name": "options", + "description": "

object with sampleFactor and simplifyThreshold\n properties.

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5.Font", + "module": "Typography" + } + }, + "p5.Camera": { + "eyeX": { + "name": "eyeX", + "class": "p5.Camera", + "module": "3D" + }, + "eyeY": { + "name": "eyeY", + "class": "p5.Camera", + "module": "3D" + }, + "eyeZ": { + "name": "eyeZ", + "class": "p5.Camera", + "module": "3D" + }, + "centerX": { + "name": "centerX", + "class": "p5.Camera", + "module": "3D" + }, + "centerY": { + "name": "centerY", + "class": "p5.Camera", + "module": "3D" + }, + "centerZ": { + "name": "centerZ", + "class": "p5.Camera", + "module": "3D" + }, + "upX": { + "name": "upX", + "class": "p5.Camera", + "module": "3D" + }, + "upY": { + "name": "upY", + "class": "p5.Camera", + "module": "3D" + }, + "upZ": { + "name": "upZ", + "class": "p5.Camera", + "module": "3D" + }, + "perspective": { + "name": "perspective", + "class": "p5.Camera", + "module": "3D" + }, + "ortho": { + "name": "ortho", + "class": "p5.Camera", + "module": "3D" + }, + "frustum": { + "name": "frustum", + "class": "p5.Camera", + "module": "3D" + }, + "pan": { + "name": "pan", + "params": [ + { + "name": "angle", + "description": "

amount to rotate camera in current\nangleMode units.\nGreater than 0 values rotate counterclockwise (to the left).

\n", + "type": "Number" + } + ], + "class": "p5.Camera", + "module": "3D" + }, + "tilt": { + "name": "tilt", + "params": [ + { + "name": "angle", + "description": "

amount to rotate camera in current\nangleMode units.\nGreater than 0 values rotate counterclockwise (to the left).

\n", + "type": "Number" + } + ], + "class": "p5.Camera", + "module": "3D" + }, + "lookAt": { + "name": "lookAt", + "params": [ + { + "name": "x", + "description": "

x position of a point in world space

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y position of a point in world space

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

z position of a point in world space

\n", + "type": "Number" + } + ], + "class": "p5.Camera", + "module": "3D" + }, + "camera": { + "name": "camera", + "class": "p5.Camera", + "module": "3D" + }, + "move": { + "name": "move", + "params": [ + { + "name": "x", + "description": "

amount to move along camera's left-right axis

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

amount to move along camera's up-down axis

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

amount to move along camera's forward-backward axis

\n", + "type": "Number" + } + ], + "class": "p5.Camera", + "module": "3D" + }, + "setPosition": { + "name": "setPosition", + "params": [ + { + "name": "x", + "description": "

x position of a point in world space

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y position of a point in world space

\n", + "type": "Number" + }, + { + "name": "z", + "description": "

z position of a point in world space

\n", + "type": "Number" + } + ], + "class": "p5.Camera", + "module": "3D" + }, + "set": { + "name": "set", + "params": [ + { + "name": "cam", + "description": "

source camera

\n", + "type": "p5.Camera" + } + ], + "class": "p5.Camera", + "module": "3D" + }, + "slerp": { + "name": "slerp", + "params": [ + { + "name": "cam0", + "description": "

first p5.Camera

\n", + "type": "p5.Camera" + }, + { + "name": "cam1", + "description": "

second p5.Camera

\n", + "type": "p5.Camera" + }, + { + "name": "amt", + "description": "

amount to use for interpolation during slerp

\n", + "type": "Number" + } + ], + "class": "p5.Camera", + "module": "3D" + } + }, + "p5.Framebuffer": { + "pixels": { + "name": "pixels", + "class": "p5.Framebuffer", + "module": "Rendering" + }, + "resize": { + "name": "resize", + "params": [ + { + "name": "width", + "description": "", + "type": "Number" + }, + { + "name": "height", + "description": "", + "type": "Number" + } + ], + "class": "p5.Framebuffer", + "module": "Rendering" + }, + "pixelDensity": { + "name": "pixelDensity", + "params": [ + { + "name": "density", + "description": "

A scaling factor for the number of pixels per\nside of the framebuffer

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Framebuffer", + "module": "Rendering" + }, + "autoSized": { + "name": "autoSized", + "params": [ + { + "name": "autoSized", + "description": "

Whether or not the framebuffer should resize\nalong with the canvas it's attached to

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5.Framebuffer", + "module": "Rendering" + }, + "createCamera": { + "name": "createCamera", + "class": "p5.Framebuffer", + "module": "Rendering" + }, + "remove": { + "name": "remove", + "class": "p5.Framebuffer", + "module": "Rendering" + }, + "begin": { + "name": "begin", + "class": "p5.Framebuffer", + "module": "Rendering" + }, + "end": { + "name": "end", + "class": "p5.Framebuffer", + "module": "Rendering" + }, + "draw": { + "name": "draw", + "params": [ + { + "name": "callback", + "description": "

A function to run that draws to the canvas. The\nfunction will immediately be run, but it will draw to the framebuffer\ninstead of the canvas.

\n", + "type": "Function" + } + ], + "class": "p5.Framebuffer", + "module": "Rendering" + }, + "get": { + "name": "get", + "class": "p5.Framebuffer", + "module": "Rendering", + "overloads": [ + { + "params": [ + { + "name": "x", + "description": "

x-coordinate of the pixel

\n", + "type": "Number" + }, + { + "name": "y", + "description": "

y-coordinate of the pixel

\n", + "type": "Number" + }, + { + "name": "w", + "description": "

width of the section to be returned

\n", + "type": "Number" + }, + { + "name": "h", + "description": "

height of the section to be returned

\n", + "type": "Number" + } + ] + }, + { + "params": [] + }, + { + "params": [ + { + "name": "x", + "description": "", + "type": "Number" + }, + { + "name": "y", + "description": "", + "type": "Number" + } + ] + } + ] + }, + "color": { + "name": "color", + "class": "p5.Framebuffer", + "module": "Rendering" + }, + "depth": { + "name": "depth", + "class": "p5.Framebuffer", + "module": "Rendering" + } + }, + "p5.Geometry": { + "clearColors": { + "name": "clearColors", + "class": "p5.Geometry", + "module": "Shape" + }, + "flipU": { + "name": "flipU", + "class": "p5.Geometry", + "module": "Shape" + }, + "flipV": { + "name": "flipV", + "class": "p5.Geometry", + "module": "Shape" + }, + "computeFaces": { + "name": "computeFaces", + "class": "p5.Geometry", + "module": "Shape" + }, + "computeNormals": { + "name": "computeNormals", + "params": [ + { + "name": "shadingType", + "description": "

shading type (FLAT for flat shading or SMOOTH for smooth shading) for buildGeometry() outputs. Defaults to FLAT.

\n", + "type": "String", + "optional": true + }, + { + "name": "options", + "description": "

An optional object with configuration.

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5.Geometry", + "module": "Shape" + }, + "averageNormals": { + "name": "averageNormals", + "class": "p5.Geometry", + "module": "Shape" + }, + "averagePoleNormals": { + "name": "averagePoleNormals", + "class": "p5.Geometry", + "module": "Shape" + }, + "normalize": { + "name": "normalize", + "class": "p5.Geometry", + "module": "Shape" + } + }, + "p5.Shader": { + "copyToContext": { + "name": "copyToContext", + "params": [ + { + "name": "context", + "description": "

The graphic or instance to copy this shader to.\nPass window if you need to copy to the main canvas.

\n", + "type": "p5|p5.Graphics" + } + ], + "class": "p5.Shader", + "module": "3D" + }, + "setUniform": { + "name": "setUniform", + "params": [ + { + "name": "uniformName", + "description": "

the name of the uniform.\nMust correspond to the name used in the vertex and fragment shaders

\n", + "type": "String" + }, + { + "name": "data", + "description": "

The value to assign to the uniform. This can be\na boolean (true/false), a number, an array of numbers, or\nan image (p5.Image, p5.Graphics, p5.MediaElement, p5.Texture)

\n", + "type": "Boolean|Number|Number[]|p5.Image|p5.Graphics|p5.MediaElement|p5.Texture" + } + ], + "class": "p5.Shader", + "module": "3D" + } + }, + "p5.SoundFile": { + "isLoaded": { + "name": "isLoaded", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "play": { + "name": "play", + "params": [ + { + "name": "startTime", + "description": "

(optional) schedule playback to start (in seconds from now).

\n", + "type": "Number", + "optional": true + }, + { + "name": "rate", + "description": "

(optional) playback rate

\n", + "type": "Number", + "optional": true + }, + { + "name": "amp", + "description": "

(optional) amplitude (volume)\n of playback

\n", + "type": "Number", + "optional": true + }, + { + "name": "cueStart", + "description": "

(optional) cue start time in seconds

\n", + "type": "Number", + "optional": true + }, + { + "name": "duration", + "description": "

(optional) duration of playback in seconds

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "playMode": { + "name": "playMode", + "params": [ + { + "name": "str", + "description": "

'restart' or 'sustain' or 'untilDone'

\n", + "type": "String" + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "pause": { + "name": "pause", + "params": [ + { + "name": "startTime", + "description": "

(optional) schedule event to occur\n seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "loop": { + "name": "loop", + "params": [ + { + "name": "startTime", + "description": "

(optional) schedule event to occur\n seconds from now

\n", + "type": "Number", + "optional": true + }, + { + "name": "rate", + "description": "

(optional) playback rate

\n", + "type": "Number", + "optional": true + }, + { + "name": "amp", + "description": "

(optional) playback volume

\n", + "type": "Number", + "optional": true + }, + { + "name": "cueLoopStart", + "description": "

(optional) startTime in seconds

\n", + "type": "Number", + "optional": true + }, + { + "name": "duration", + "description": "

(optional) loop duration in seconds

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "setLoop": { + "name": "setLoop", + "params": [ + { + "name": "Boolean", + "description": "

set looping to true or false

\n", + "type": "Boolean" + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "isLooping": { + "name": "isLooping", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "isPlaying": { + "name": "isPlaying", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "isPaused": { + "name": "isPaused", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "stop": { + "name": "stop", + "params": [ + { + "name": "startTime", + "description": "

(optional) schedule event to occur\n in seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "pan": { + "name": "pan", + "params": [ + { + "name": "panValue", + "description": "

Set the stereo panner

\n", + "type": "Number", + "optional": true + }, + { + "name": "timeFromNow", + "description": "

schedule this event to happen\n seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "getPan": { + "name": "getPan", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "rate": { + "name": "rate", + "params": [ + { + "name": "playbackRate", + "description": "

Set the playback rate. 1.0 is normal,\n .5 is half-speed, 2.0 is twice as fast.\n Values less than zero play backwards.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "setVolume": { + "name": "setVolume", + "params": [ + { + "name": "volume", + "description": "

Volume (amplitude) between 0.0\n and 1.0 or modulating signal/oscillator

\n", + "type": "Number|Object" + }, + { + "name": "rampTime", + "description": "

Fade for t seconds

\n", + "type": "Number", + "optional": true + }, + { + "name": "timeFromNow", + "description": "

Schedule this event to happen at\n t seconds in the future

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "duration": { + "name": "duration", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "currentTime": { + "name": "currentTime", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "jump": { + "name": "jump", + "params": [ + { + "name": "cueTime", + "description": "

cueTime of the soundFile in seconds.

\n", + "type": "Number" + }, + { + "name": "duration", + "description": "

duration in seconds.

\n", + "type": "Number" + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "channels": { + "name": "channels", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "sampleRate": { + "name": "sampleRate", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "frames": { + "name": "frames", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "getPeaks": { + "name": "getPeaks", + "params": [ + { + "name": "length", + "description": "

length is the size of the returned array.\n Larger length results in more precision.\n Defaults to 5*width of the browser window.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "reverseBuffer": { + "name": "reverseBuffer", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "onended": { + "name": "onended", + "params": [ + { + "name": "callback", + "description": "

function to call when the\n soundfile has ended.

\n", + "type": "Function" + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "connect": { + "name": "connect", + "params": [ + { + "name": "object", + "description": "

Audio object that accepts an input

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "disconnect": { + "name": "disconnect", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "setPath": { + "name": "setPath", + "params": [ + { + "name": "path", + "description": "

path to audio file

\n", + "type": "String" + }, + { + "name": "callback", + "description": "

Callback

\n", + "type": "Function" + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "setBuffer": { + "name": "setBuffer", + "params": [ + { + "name": "buf", + "description": "

Array of Float32 Array(s). 2 Float32 Arrays\n will create a stereo source. 1 will create\n a mono source.

\n", + "type": "Array" + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "addCue": { + "name": "addCue", + "params": [ + { + "name": "time", + "description": "

Time in seconds, relative to this media\n element's playback. For example, to trigger\n an event every time playback reaches two\n seconds, pass in the number 2. This will be\n passed as the first parameter to\n the callback function.

\n", + "type": "Number" + }, + { + "name": "callback", + "description": "

Name of a function that will be\n called at the given time. The callback will\n receive time and (optionally) param as its\n two parameters.

\n", + "type": "Function" + }, + { + "name": "value", + "description": "

An object to be passed as the\n second parameter to the\n callback function.

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "removeCue": { + "name": "removeCue", + "params": [ + { + "name": "id", + "description": "

ID of the cue, as returned by addCue

\n", + "type": "Number" + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "clearCues": { + "name": "clearCues", + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "save": { + "name": "save", + "params": [ + { + "name": "fileName", + "description": "

name of the resulting .wav file.

\n", + "type": "String", + "optional": true + } + ], + "class": "p5.SoundFile", + "module": "p5.sound" + }, + "getBlob": { + "name": "getBlob", + "class": "p5.SoundFile", + "module": "p5.sound" + } + }, + "p5.Amplitude": { + "setInput": { + "name": "setInput", + "params": [ + { + "name": "snd", + "description": "

set the sound source\n (optional, defaults to\n main output)

\n", + "type": "SoundObject|undefined", + "optional": true + }, + { + "name": "smoothing", + "description": "

a range between 0.0 and 1.0\n to smooth amplitude readings

\n", + "type": "Number|undefined", + "optional": true + } + ], + "class": "p5.Amplitude", + "module": "p5.sound" + }, + "getLevel": { + "name": "getLevel", + "params": [ + { + "name": "channel", + "description": "

Optionally return only channel 0 (left) or 1 (right)

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Amplitude", + "module": "p5.sound" + }, + "toggleNormalize": { + "name": "toggleNormalize", + "params": [ + { + "name": "boolean", + "description": "

set normalize to true (1) or false (0)

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5.Amplitude", + "module": "p5.sound" + }, + "smooth": { + "name": "smooth", + "params": [ + { + "name": "set", + "description": "

smoothing from 0.0 <= 1

\n", + "type": "Number" + } + ], + "class": "p5.Amplitude", + "module": "p5.sound" + } + }, + "p5.FFT": { + "setInput": { + "name": "setInput", + "params": [ + { + "name": "source", + "description": "

p5.sound object (or web audio API source node)

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5.FFT", + "module": "p5.sound" + }, + "waveform": { + "name": "waveform", + "params": [ + { + "name": "bins", + "description": "

Must be a power of two between\n 16 and 1024. Defaults to 1024.

\n", + "type": "Number", + "optional": true + }, + { + "name": "precision", + "description": "

If any value is provided, will return results\n in a Float32 Array which is more precise\n than a regular array.

\n", + "type": "String", + "optional": true + } + ], + "class": "p5.FFT", + "module": "p5.sound" + }, + "analyze": { + "name": "analyze", + "params": [ + { + "name": "bins", + "description": "

Must be a power of two between\n 16 and 1024. Defaults to 1024.

\n", + "type": "Number", + "optional": true + }, + { + "name": "scale", + "description": "

If \"dB,\" returns decibel\n float measurements between\n -140 and 0 (max).\n Otherwise returns integers from 0-255.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.FFT", + "module": "p5.sound" + }, + "getEnergy": { + "name": "getEnergy", + "params": [ + { + "name": "frequency1", + "description": "

Will return a value representing\n energy at this frequency. Alternately,\n the strings \"bass\", \"lowMid\" \"mid\",\n \"highMid\", and \"treble\" will return\n predefined frequency ranges.

\n", + "type": "Number|String" + }, + { + "name": "frequency2", + "description": "

If a second frequency is given,\n will return average amount of\n energy that exists between the\n two frequencies.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.FFT", + "module": "p5.sound" + }, + "getCentroid": { + "name": "getCentroid", + "class": "p5.FFT", + "module": "p5.sound" + }, + "smooth": { + "name": "smooth", + "params": [ + { + "name": "smoothing", + "description": "

0.0 < smoothing < 1.0.\n Defaults to 0.8.

\n", + "type": "Number" + } + ], + "class": "p5.FFT", + "module": "p5.sound" + }, + "linAverages": { + "name": "linAverages", + "params": [ + { + "name": "N", + "description": "

Number of returned frequency groups

\n", + "type": "Number" + } + ], + "class": "p5.FFT", + "module": "p5.sound" + }, + "logAverages": { + "name": "logAverages", + "params": [ + { + "name": "octaveBands", + "description": "

Array of Octave Bands objects for grouping

\n", + "type": "Array" + } + ], + "class": "p5.FFT", + "module": "p5.sound" + }, + "getOctaveBands": { + "name": "getOctaveBands", + "params": [ + { + "name": "N", + "description": "

Specifies the 1/N type of generated octave bands

\n", + "type": "Number" + }, + { + "name": "fCtr0", + "description": "

Minimum central frequency for the lowest band

\n", + "type": "Number" + } + ], + "class": "p5.FFT", + "module": "p5.sound" + } + }, + "p5.Oscillator": { + "start": { + "name": "start", + "params": [ + { + "name": "time", + "description": "

startTime in seconds from now.

\n", + "type": "Number", + "optional": true + }, + { + "name": "frequency", + "description": "

frequency in Hz.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "stop": { + "name": "stop", + "params": [ + { + "name": "secondsFromNow", + "description": "

Time, in seconds from now.

\n", + "type": "Number" + } + ], + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "amp": { + "name": "amp", + "params": [ + { + "name": "vol", + "description": "

between 0 and 1.0\n or a modulating signal/oscillator

\n", + "type": "Number|Object" + }, + { + "name": "rampTime", + "description": "

create a fade that lasts rampTime

\n", + "type": "Number", + "optional": true + }, + { + "name": "timeFromNow", + "description": "

schedule this event to happen\n seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "getAmp": { + "name": "getAmp", + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "freq": { + "name": "freq", + "params": [ + { + "name": "Frequency", + "description": "

Frequency in Hz\n or modulating signal/oscillator

\n", + "type": "Number|Object" + }, + { + "name": "rampTime", + "description": "

Ramp time (in seconds)

\n", + "type": "Number", + "optional": true + }, + { + "name": "timeFromNow", + "description": "

Schedule this event to happen\n at x seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "getFreq": { + "name": "getFreq", + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "setType": { + "name": "setType", + "params": [ + { + "name": "type", + "description": "

'sine', 'triangle', 'sawtooth' or 'square'.

\n", + "type": "String" + } + ], + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "getType": { + "name": "getType", + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "connect": { + "name": "connect", + "params": [ + { + "name": "unit", + "description": "

A p5.sound or Web Audio object

\n", + "type": "Object" + } + ], + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "disconnect": { + "name": "disconnect", + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "pan": { + "name": "pan", + "params": [ + { + "name": "panning", + "description": "

Number between -1 and 1

\n", + "type": "Number" + }, + { + "name": "timeFromNow", + "description": "

schedule this event to happen\n seconds from now

\n", + "type": "Number" + } + ], + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "getPan": { + "name": "getPan", + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "phase": { + "name": "phase", + "params": [ + { + "name": "phase", + "description": "

float between 0.0 and 1.0

\n", + "type": "Number" + } + ], + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "add": { + "name": "add", + "params": [ + { + "name": "number", + "description": "

Constant number to add

\n", + "type": "Number" + } + ], + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "mult": { + "name": "mult", + "params": [ + { + "name": "number", + "description": "

Constant number to multiply

\n", + "type": "Number" + } + ], + "class": "p5.Oscillator", + "module": "p5.sound" + }, + "scale": { + "name": "scale", + "params": [ + { + "name": "inMin", + "description": "

input range minumum

\n", + "type": "Number" + }, + { + "name": "inMax", + "description": "

input range maximum

\n", + "type": "Number" + }, + { + "name": "outMin", + "description": "

input range minumum

\n", + "type": "Number" + }, + { + "name": "outMax", + "description": "

input range maximum

\n", + "type": "Number" + } + ], + "class": "p5.Oscillator", + "module": "p5.sound" + } + }, + "p5.Envelope": { + "attackTime": { + "name": "attackTime", + "class": "p5.Envelope", + "module": "p5.sound" + }, + "attackLevel": { + "name": "attackLevel", + "class": "p5.Envelope", + "module": "p5.sound" + }, + "decayTime": { + "name": "decayTime", + "class": "p5.Envelope", + "module": "p5.sound" + }, + "decayLevel": { + "name": "decayLevel", + "class": "p5.Envelope", + "module": "p5.sound" + }, + "releaseTime": { + "name": "releaseTime", + "class": "p5.Envelope", + "module": "p5.sound" + }, + "releaseLevel": { + "name": "releaseLevel", + "class": "p5.Envelope", + "module": "p5.sound" + }, + "set": { + "name": "set", + "params": [ + { + "name": "attackTime", + "description": "

Time (in seconds) before level\n reaches attackLevel

\n", + "type": "Number" + }, + { + "name": "attackLevel", + "description": "

Typically an amplitude between\n 0.0 and 1.0

\n", + "type": "Number" + }, + { + "name": "decayTime", + "description": "

Time

\n", + "type": "Number" + }, + { + "name": "decayLevel", + "description": "

Amplitude (In a standard ADSR envelope,\n decayLevel = sustainLevel)

\n", + "type": "Number" + }, + { + "name": "releaseTime", + "description": "

Release Time (in seconds)

\n", + "type": "Number" + }, + { + "name": "releaseLevel", + "description": "

Amplitude

\n", + "type": "Number" + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + }, + "setADSR": { + "name": "setADSR", + "params": [ + { + "name": "attackTime", + "description": "

Time (in seconds before envelope\n reaches Attack Level

\n", + "type": "Number" + }, + { + "name": "decayTime", + "description": "

Time (in seconds) before envelope\n reaches Decay/Sustain Level

\n", + "type": "Number", + "optional": true + }, + { + "name": "susRatio", + "description": "

Ratio between attackLevel and releaseLevel, on a scale from 0 to 1,\n where 1.0 = attackLevel, 0.0 = releaseLevel.\n The susRatio determines the decayLevel and the level at which the\n sustain portion of the envelope will sustain.\n For example, if attackLevel is 0.4, releaseLevel is 0,\n and susAmt is 0.5, the decayLevel would be 0.2. If attackLevel is\n increased to 1.0 (using setRange),\n then decayLevel would increase proportionally, to become 0.5.

\n", + "type": "Number", + "optional": true + }, + { + "name": "releaseTime", + "description": "

Time in seconds from now (defaults to 0)

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + }, + "setRange": { + "name": "setRange", + "params": [ + { + "name": "aLevel", + "description": "

attack level (defaults to 1)

\n", + "type": "Number" + }, + { + "name": "rLevel", + "description": "

release level (defaults to 0)

\n", + "type": "Number" + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + }, + "setInput": { + "name": "setInput", + "params": [ + { + "name": "inputs", + "description": "

A p5.sound object or\n Web Audio Param.

\n", + "type": "Object", + "optional": true, + "multiple": true + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + }, + "setExp": { + "name": "setExp", + "params": [ + { + "name": "isExp", + "description": "

true is exponential, false is linear

\n", + "type": "Boolean" + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + }, + "play": { + "name": "play", + "params": [ + { + "name": "unit", + "description": "

A p5.sound object or\n Web Audio Param.

\n", + "type": "Object" + }, + { + "name": "startTime", + "description": "

time from now (in seconds) at which to play

\n", + "type": "Number", + "optional": true + }, + { + "name": "sustainTime", + "description": "

time to sustain before releasing the envelope

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + }, + "triggerAttack": { + "name": "triggerAttack", + "params": [ + { + "name": "unit", + "description": "

p5.sound Object or Web Audio Param

\n", + "type": "Object" + }, + { + "name": "secondsFromNow", + "description": "

time from now (in seconds)

\n", + "type": "Number" + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + }, + "triggerRelease": { + "name": "triggerRelease", + "params": [ + { + "name": "unit", + "description": "

p5.sound Object or Web Audio Param

\n", + "type": "Object" + }, + { + "name": "secondsFromNow", + "description": "

time to trigger the release

\n", + "type": "Number" + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + }, + "ramp": { + "name": "ramp", + "params": [ + { + "name": "unit", + "description": "

p5.sound Object or Web Audio Param

\n", + "type": "Object" + }, + { + "name": "secondsFromNow", + "description": "

When to trigger the ramp

\n", + "type": "Number" + }, + { + "name": "v", + "description": "

Target value

\n", + "type": "Number" + }, + { + "name": "v2", + "description": "

Second target value

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + }, + "add": { + "name": "add", + "params": [ + { + "name": "number", + "description": "

Constant number to add

\n", + "type": "Number" + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + }, + "mult": { + "name": "mult", + "params": [ + { + "name": "number", + "description": "

Constant number to multiply

\n", + "type": "Number" + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + }, + "scale": { + "name": "scale", + "params": [ + { + "name": "inMin", + "description": "

input range minumum

\n", + "type": "Number" + }, + { + "name": "inMax", + "description": "

input range maximum

\n", + "type": "Number" + }, + { + "name": "outMin", + "description": "

input range minumum

\n", + "type": "Number" + }, + { + "name": "outMax", + "description": "

input range maximum

\n", + "type": "Number" + } + ], + "class": "p5.Envelope", + "module": "p5.sound" + } + }, + "p5.Noise": { + "setType": { + "name": "setType", + "params": [ + { + "name": "type", + "description": "

'white', 'pink' or 'brown'

\n", + "type": "String", + "optional": true + } + ], + "class": "p5.Noise", + "module": "p5.sound" + } + }, + "p5.Pulse": { + "width": { + "name": "width", + "params": [ + { + "name": "width", + "description": "

Width between the pulses (0 to 1.0,\n defaults to 0)

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Pulse", + "module": "p5.sound" + } + }, + "p5.AudioIn": { + "input": { + "name": "input", + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "output": { + "name": "output", + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "stream": { + "name": "stream", + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "mediaStream": { + "name": "mediaStream", + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "currentSource": { + "name": "currentSource", + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "enabled": { + "name": "enabled", + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "amplitude": { + "name": "amplitude", + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "start": { + "name": "start", + "params": [ + { + "name": "successCallback", + "description": "

Name of a function to call on\n success.

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

Name of a function to call if\n there was an error. For example,\n some browsers do not support\n getUserMedia.

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "stop": { + "name": "stop", + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "connect": { + "name": "connect", + "params": [ + { + "name": "unit", + "description": "

An object that accepts audio input,\n such as an FFT

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "disconnect": { + "name": "disconnect", + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "getLevel": { + "name": "getLevel", + "params": [ + { + "name": "smoothing", + "description": "

Smoothing is 0.0 by default.\n Smooths values based on previous values.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "amp": { + "name": "amp", + "params": [ + { + "name": "vol", + "description": "

between 0 and 1.0

\n", + "type": "Number" + }, + { + "name": "time", + "description": "

ramp time (optional)

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "getSources": { + "name": "getSources", + "params": [ + { + "name": "successCallback", + "description": "

This callback function handles the sources when they\n have been enumerated. The callback function\n receives the deviceList array as its only argument

\n", + "type": "Function", + "optional": true + }, + { + "name": "errorCallback", + "description": "

This optional callback receives the error\n message as its argument.

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5.AudioIn", + "module": "p5.sound" + }, + "setSource": { + "name": "setSource", + "params": [ + { + "name": "num", + "description": "

position of input source in the array

\n", + "type": "Number" + } + ], + "class": "p5.AudioIn", + "module": "p5.sound" + } + }, + "p5.Effect": { + "amp": { + "name": "amp", + "params": [ + { + "name": "vol", + "description": "

amplitude between 0 and 1.0

\n", + "type": "Number", + "optional": true + }, + { + "name": "rampTime", + "description": "

create a fade that lasts until rampTime

\n", + "type": "Number", + "optional": true + }, + { + "name": "tFromNow", + "description": "

schedule this event to happen in tFromNow seconds

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Effect", + "module": "p5.sound" + }, + "chain": { + "name": "chain", + "params": [ + { + "name": "arguments", + "description": "

Chain together multiple sound objects

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5.Effect", + "module": "p5.sound" + }, + "drywet": { + "name": "drywet", + "params": [ + { + "name": "fade", + "description": "

The desired drywet value (0 - 1.0)

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Effect", + "module": "p5.sound" + }, + "connect": { + "name": "connect", + "params": [ + { + "name": "unit", + "description": "", + "type": "Object" + } + ], + "class": "p5.Effect", + "module": "p5.sound" + }, + "disconnect": { + "name": "disconnect", + "class": "p5.Effect", + "module": "p5.sound" + } + }, + "p5.Filter": { + "biquadFilter": { + "name": "biquadFilter", + "class": "p5.Filter", + "module": "p5.sound" + }, + "process": { + "name": "process", + "params": [ + { + "name": "Signal", + "description": "

An object that outputs audio

\n", + "type": "Object" + }, + { + "name": "freq", + "description": "

Frequency in Hz, from 10 to 22050

\n", + "type": "Number", + "optional": true + }, + { + "name": "res", + "description": "

Resonance/Width of the filter frequency\n from 0.001 to 1000

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Filter", + "module": "p5.sound" + }, + "set": { + "name": "set", + "params": [ + { + "name": "freq", + "description": "

Frequency in Hz, from 10 to 22050

\n", + "type": "Number", + "optional": true + }, + { + "name": "res", + "description": "

Resonance (Q) from 0.001 to 1000

\n", + "type": "Number", + "optional": true + }, + { + "name": "timeFromNow", + "description": "

schedule this event to happen\n seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Filter", + "module": "p5.sound" + }, + "freq": { + "name": "freq", + "params": [ + { + "name": "freq", + "description": "

Filter Frequency

\n", + "type": "Number" + }, + { + "name": "timeFromNow", + "description": "

schedule this event to happen\n seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Filter", + "module": "p5.sound" + }, + "res": { + "name": "res", + "params": [ + { + "name": "res", + "description": "

Resonance/Width of filter freq\n from 0.001 to 1000

\n", + "type": "Number" + }, + { + "name": "timeFromNow", + "description": "

schedule this event to happen\n seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Filter", + "module": "p5.sound" + }, + "gain": { + "name": "gain", + "params": [ + { + "name": "gain", + "description": "", + "type": "Number" + } + ], + "class": "p5.Filter", + "module": "p5.sound" + }, + "toggle": { + "name": "toggle", + "class": "p5.Filter", + "module": "p5.sound" + }, + "setType": { + "name": "setType", + "params": [ + { + "name": "t", + "description": "", + "type": "String" + } + ], + "class": "p5.Filter", + "module": "p5.sound" + } + }, + "p5.EQ": { + "bands": { + "name": "bands", + "class": "p5.EQ", + "module": "p5.sound" + }, + "process": { + "name": "process", + "params": [ + { + "name": "src", + "description": "

Audio source

\n", + "type": "Object" + } + ], + "class": "p5.EQ", + "module": "p5.sound" + } + }, + "p5.Panner3D": { + "panner": { + "name": "panner", + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "process": { + "name": "process", + "params": [ + { + "name": "src", + "description": "

Input source

\n", + "type": "Object" + } + ], + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "set": { + "name": "set", + "params": [ + { + "name": "xVal", + "description": "", + "type": "Number" + }, + { + "name": "yVal", + "description": "", + "type": "Number" + }, + { + "name": "zVal", + "description": "", + "type": "Number" + }, + { + "name": "time", + "description": "", + "type": "Number" + } + ], + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "positionX": { + "name": "positionX", + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "positionY": { + "name": "positionY", + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "positionZ": { + "name": "positionZ", + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "orient": { + "name": "orient", + "params": [ + { + "name": "xVal", + "description": "", + "type": "Number" + }, + { + "name": "yVal", + "description": "", + "type": "Number" + }, + { + "name": "zVal", + "description": "", + "type": "Number" + }, + { + "name": "time", + "description": "", + "type": "Number" + } + ], + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "orientX": { + "name": "orientX", + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "orientY": { + "name": "orientY", + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "orientZ": { + "name": "orientZ", + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "setFalloff": { + "name": "setFalloff", + "params": [ + { + "name": "maxDistance", + "description": "", + "type": "Number", + "optional": true + }, + { + "name": "rolloffFactor", + "description": "", + "type": "Number", + "optional": true + } + ], + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "maxDist": { + "name": "maxDist", + "params": [ + { + "name": "maxDistance", + "description": "", + "type": "Number" + } + ], + "class": "p5.Panner3D", + "module": "p5.sound" + }, + "rollof": { + "name": "rollof", + "params": [ + { + "name": "rolloffFactor", + "description": "", + "type": "Number" + } + ], + "class": "p5.Panner3D", + "module": "p5.sound" + } + }, + "p5.Delay": { + "leftDelay": { + "name": "leftDelay", + "class": "p5.Delay", + "module": "p5.sound" + }, + "rightDelay": { + "name": "rightDelay", + "class": "p5.Delay", + "module": "p5.sound" + }, + "process": { + "name": "process", + "params": [ + { + "name": "Signal", + "description": "

An object that outputs audio

\n", + "type": "Object" + }, + { + "name": "delayTime", + "description": "

Time (in seconds) of the delay/echo.\n Some browsers limit delayTime to\n 1 second.

\n", + "type": "Number", + "optional": true + }, + { + "name": "feedback", + "description": "

sends the delay back through itself\n in a loop that decreases in volume\n each time.

\n", + "type": "Number", + "optional": true + }, + { + "name": "lowPass", + "description": "

Cutoff frequency. Only frequencies\n below the lowPass will be part of the\n delay.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Delay", + "module": "p5.sound" + }, + "delayTime": { + "name": "delayTime", + "params": [ + { + "name": "delayTime", + "description": "

Time (in seconds) of the delay

\n", + "type": "Number" + } + ], + "class": "p5.Delay", + "module": "p5.sound" + }, + "feedback": { + "name": "feedback", + "params": [ + { + "name": "feedback", + "description": "

0.0 to 1.0, or an object such as an\n Oscillator that can be used to\n modulate this param

\n", + "type": "Number|Object" + } + ], + "class": "p5.Delay", + "module": "p5.sound" + }, + "filter": { + "name": "filter", + "params": [ + { + "name": "cutoffFreq", + "description": "

A lowpass filter will cut off any\n frequencies higher than the filter frequency.

\n", + "type": "Number|Object" + }, + { + "name": "res", + "description": "

Resonance of the filter frequency\n cutoff, or an object (i.e. a p5.Oscillator)\n that can be used to modulate this parameter.\n High numbers (i.e. 15) will produce a resonance,\n low numbers (i.e. .2) will produce a slope.

\n", + "type": "Number|Object" + } + ], + "class": "p5.Delay", + "module": "p5.sound" + }, + "setType": { + "name": "setType", + "params": [ + { + "name": "type", + "description": "

'pingPong' (1) or 'default' (0)

\n", + "type": "String|Number" + } + ], + "class": "p5.Delay", + "module": "p5.sound" + }, + "amp": { + "name": "amp", + "params": [ + { + "name": "volume", + "description": "

amplitude between 0 and 1.0

\n", + "type": "Number" + }, + { + "name": "rampTime", + "description": "

create a fade that lasts rampTime

\n", + "type": "Number", + "optional": true + }, + { + "name": "timeFromNow", + "description": "

schedule this event to happen\n seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Delay", + "module": "p5.sound" + }, + "connect": { + "name": "connect", + "params": [ + { + "name": "unit", + "description": "", + "type": "Object" + } + ], + "class": "p5.Delay", + "module": "p5.sound" + }, + "disconnect": { + "name": "disconnect", + "class": "p5.Delay", + "module": "p5.sound" + } + }, + "p5.Reverb": { + "process": { + "name": "process", + "params": [ + { + "name": "src", + "description": "

p5.sound / Web Audio object with a sound\n output.

\n", + "type": "Object" + }, + { + "name": "seconds", + "description": "

Duration of the reverb, in seconds.\n Min: 0, Max: 10. Defaults to 3.

\n", + "type": "Number", + "optional": true + }, + { + "name": "decayRate", + "description": "

Percentage of decay with each echo.\n Min: 0, Max: 100. Defaults to 2.

\n", + "type": "Number", + "optional": true + }, + { + "name": "reverse", + "description": "

Play the reverb backwards or forwards.

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5.Reverb", + "module": "p5.sound" + }, + "set": { + "name": "set", + "params": [ + { + "name": "seconds", + "description": "

Duration of the reverb, in seconds.\n Min: 0, Max: 10. Defaults to 3.

\n", + "type": "Number", + "optional": true + }, + { + "name": "decayRate", + "description": "

Percentage of decay with each echo.\n Min: 0, Max: 100. Defaults to 2.

\n", + "type": "Number", + "optional": true + }, + { + "name": "reverse", + "description": "

Play the reverb backwards or forwards.

\n", + "type": "Boolean", + "optional": true + } + ], + "class": "p5.Reverb", + "module": "p5.sound" + }, + "amp": { + "name": "amp", + "params": [ + { + "name": "volume", + "description": "

amplitude between 0 and 1.0

\n", + "type": "Number" + }, + { + "name": "rampTime", + "description": "

create a fade that lasts rampTime

\n", + "type": "Number", + "optional": true + }, + { + "name": "timeFromNow", + "description": "

schedule this event to happen\n seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Reverb", + "module": "p5.sound" + }, + "connect": { + "name": "connect", + "params": [ + { + "name": "unit", + "description": "", + "type": "Object" + } + ], + "class": "p5.Reverb", + "module": "p5.sound" + }, + "disconnect": { + "name": "disconnect", + "class": "p5.Reverb", + "module": "p5.sound" + } + }, + "p5.Convolver": { + "convolverNode": { + "name": "convolverNode", + "class": "p5.Convolver", + "module": "p5.sound" + }, + "impulses": { + "name": "impulses", + "class": "p5.Convolver", + "module": "p5.sound" + }, + "process": { + "name": "process", + "params": [ + { + "name": "src", + "description": "

p5.sound / Web Audio object with a sound\n output.

\n", + "type": "Object" + } + ], + "class": "p5.Convolver", + "module": "p5.sound" + }, + "addImpulse": { + "name": "addImpulse", + "params": [ + { + "name": "path", + "description": "

path to a sound file

\n", + "type": "String" + }, + { + "name": "callback", + "description": "

function (optional)

\n", + "type": "Function" + }, + { + "name": "errorCallback", + "description": "

function (optional)

\n", + "type": "Function" + } + ], + "class": "p5.Convolver", + "module": "p5.sound" + }, + "resetImpulse": { + "name": "resetImpulse", + "params": [ + { + "name": "path", + "description": "

path to a sound file

\n", + "type": "String" + }, + { + "name": "callback", + "description": "

function (optional)

\n", + "type": "Function" + }, + { + "name": "errorCallback", + "description": "

function (optional)

\n", + "type": "Function" + } + ], + "class": "p5.Convolver", + "module": "p5.sound" + }, + "toggleImpulse": { + "name": "toggleImpulse", + "params": [ + { + "name": "id", + "description": "

Identify the impulse by its original filename\n (String), or by its position in the\n .impulses Array (Number).

\n", + "type": "String|Number" + } + ], + "class": "p5.Convolver", + "module": "p5.sound" + } + }, + "p5.Phrase": { + "sequence": { + "name": "sequence", + "class": "p5.Phrase", + "module": "p5.sound" + } + }, + "p5.Part": { + "setBPM": { + "name": "setBPM", + "params": [ + { + "name": "BPM", + "description": "

Beats Per Minute

\n", + "type": "Number" + }, + { + "name": "rampTime", + "description": "

Seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Part", + "module": "p5.sound" + }, + "getBPM": { + "name": "getBPM", + "class": "p5.Part", + "module": "p5.sound" + }, + "start": { + "name": "start", + "params": [ + { + "name": "time", + "description": "

seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Part", + "module": "p5.sound" + }, + "loop": { + "name": "loop", + "params": [ + { + "name": "time", + "description": "

seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Part", + "module": "p5.sound" + }, + "noLoop": { + "name": "noLoop", + "class": "p5.Part", + "module": "p5.sound" + }, + "stop": { + "name": "stop", + "params": [ + { + "name": "time", + "description": "

seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Part", + "module": "p5.sound" + }, + "pause": { + "name": "pause", + "params": [ + { + "name": "time", + "description": "

seconds from now

\n", + "type": "Number" + } + ], + "class": "p5.Part", + "module": "p5.sound" + }, + "addPhrase": { + "name": "addPhrase", + "params": [ + { + "name": "phrase", + "description": "

reference to a p5.Phrase

\n", + "type": "p5.Phrase" + } + ], + "class": "p5.Part", + "module": "p5.sound" + }, + "removePhrase": { + "name": "removePhrase", + "params": [ + { + "name": "phraseName", + "description": "", + "type": "String" + } + ], + "class": "p5.Part", + "module": "p5.sound" + }, + "getPhrase": { + "name": "getPhrase", + "params": [ + { + "name": "phraseName", + "description": "", + "type": "String" + } + ], + "class": "p5.Part", + "module": "p5.sound" + }, + "replaceSequence": { + "name": "replaceSequence", + "params": [ + { + "name": "phraseName", + "description": "", + "type": "String" + }, + { + "name": "sequence", + "description": "

Array of values to pass into the callback\n at each step of the phrase.

\n", + "type": "Array" + } + ], + "class": "p5.Part", + "module": "p5.sound" + }, + "onStep": { + "name": "onStep", + "params": [ + { + "name": "callback", + "description": "

The name of the callback\n you want to fire\n on every beat/tatum.

\n", + "type": "Function" + } + ], + "class": "p5.Part", + "module": "p5.sound" + } + }, + "p5.Score": { + "start": { + "name": "start", + "class": "p5.Score", + "module": "p5.sound" + }, + "stop": { + "name": "stop", + "class": "p5.Score", + "module": "p5.sound" + }, + "pause": { + "name": "pause", + "class": "p5.Score", + "module": "p5.sound" + }, + "loop": { + "name": "loop", + "class": "p5.Score", + "module": "p5.sound" + }, + "noLoop": { + "name": "noLoop", + "class": "p5.Score", + "module": "p5.sound" + }, + "setBPM": { + "name": "setBPM", + "params": [ + { + "name": "BPM", + "description": "

Beats Per Minute

\n", + "type": "Number" + }, + { + "name": "rampTime", + "description": "

Seconds from now

\n", + "type": "Number" + } + ], + "class": "p5.Score", + "module": "p5.sound" + } + }, + "p5.SoundLoop": { + "bpm": { + "name": "bpm", + "class": "p5.SoundLoop", + "module": "p5.sound" + }, + "timeSignature": { + "name": "timeSignature", + "class": "p5.SoundLoop", + "module": "p5.sound" + }, + "interval": { + "name": "interval", + "class": "p5.SoundLoop", + "module": "p5.sound" + }, + "iterations": { + "name": "iterations", + "class": "p5.SoundLoop", + "module": "p5.sound" + }, + "musicalTimeMode": { + "name": "musicalTimeMode", + "class": "p5.SoundLoop", + "module": "p5.sound" + }, + "maxIterations": { + "name": "maxIterations", + "class": "p5.SoundLoop", + "module": "p5.sound" + }, + "start": { + "name": "start", + "params": [ + { + "name": "timeFromNow", + "description": "

schedule a starting time

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundLoop", + "module": "p5.sound" + }, + "stop": { + "name": "stop", + "params": [ + { + "name": "timeFromNow", + "description": "

schedule a stopping time

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundLoop", + "module": "p5.sound" + }, + "pause": { + "name": "pause", + "params": [ + { + "name": "timeFromNow", + "description": "

schedule a pausing time

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundLoop", + "module": "p5.sound" + }, + "syncedStart": { + "name": "syncedStart", + "params": [ + { + "name": "otherLoop", + "description": "

a p5.SoundLoop to sync with

\n", + "type": "Object" + }, + { + "name": "timeFromNow", + "description": "

Start the loops in sync after timeFromNow seconds

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.SoundLoop", + "module": "p5.sound" + } + }, + "p5.Compressor": { + "compressor": { + "name": "compressor", + "class": "p5.Compressor", + "module": "p5.sound" + }, + "process": { + "name": "process", + "params": [ + { + "name": "src", + "description": "

Sound source to be connected

\n", + "type": "Object" + }, + { + "name": "attack", + "description": "

The amount of time (in seconds) to reduce the gain by 10dB,\n default = .003, range 0 - 1

\n", + "type": "Number", + "optional": true + }, + { + "name": "knee", + "description": "

A decibel value representing the range above the\n threshold where the curve smoothly transitions to the \"ratio\" portion.\n default = 30, range 0 - 40

\n", + "type": "Number", + "optional": true + }, + { + "name": "ratio", + "description": "

The amount of dB change in input for a 1 dB change in output\n default = 12, range 1 - 20

\n", + "type": "Number", + "optional": true + }, + { + "name": "threshold", + "description": "

The decibel value above which the compression will start taking effect\n default = -24, range -100 - 0

\n", + "type": "Number", + "optional": true + }, + { + "name": "release", + "description": "

The amount of time (in seconds) to increase the gain by 10dB\n default = .25, range 0 - 1

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Compressor", + "module": "p5.sound" + }, + "set": { + "name": "set", + "params": [ + { + "name": "attack", + "description": "

The amount of time (in seconds) to reduce the gain by 10dB,\n default = .003, range 0 - 1

\n", + "type": "Number" + }, + { + "name": "knee", + "description": "

A decibel value representing the range above the\n threshold where the curve smoothly transitions to the \"ratio\" portion.\n default = 30, range 0 - 40

\n", + "type": "Number" + }, + { + "name": "ratio", + "description": "

The amount of dB change in input for a 1 dB change in output\n default = 12, range 1 - 20

\n", + "type": "Number" + }, + { + "name": "threshold", + "description": "

The decibel value above which the compression will start taking effect\n default = -24, range -100 - 0

\n", + "type": "Number" + }, + { + "name": "release", + "description": "

The amount of time (in seconds) to increase the gain by 10dB\n default = .25, range 0 - 1

\n", + "type": "Number" + } + ], + "class": "p5.Compressor", + "module": "p5.sound" + }, + "attack": { + "name": "attack", + "params": [ + { + "name": "attack", + "description": "

Attack is the amount of time (in seconds) to reduce the gain by 10dB,\n default = .003, range 0 - 1

\n", + "type": "Number", + "optional": true + }, + { + "name": "time", + "description": "

Assign time value to schedule the change in value

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Compressor", + "module": "p5.sound" + }, + "knee": { + "name": "knee", + "params": [ + { + "name": "knee", + "description": "

A decibel value representing the range above the\n threshold where the curve smoothly transitions to the \"ratio\" portion.\n default = 30, range 0 - 40

\n", + "type": "Number", + "optional": true + }, + { + "name": "time", + "description": "

Assign time value to schedule the change in value

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Compressor", + "module": "p5.sound" + }, + "ratio": { + "name": "ratio", + "params": [ + { + "name": "ratio", + "description": "

The amount of dB change in input for a 1 dB change in output\n default = 12, range 1 - 20

\n", + "type": "Number", + "optional": true + }, + { + "name": "time", + "description": "

Assign time value to schedule the change in value

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Compressor", + "module": "p5.sound" + }, + "threshold": { + "name": "threshold", + "params": [ + { + "name": "threshold", + "description": "

The decibel value above which the compression will start taking effect\n default = -24, range -100 - 0

\n", + "type": "Number" + }, + { + "name": "time", + "description": "

Assign time value to schedule the change in value

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Compressor", + "module": "p5.sound" + }, + "release": { + "name": "release", + "params": [ + { + "name": "release", + "description": "

The amount of time (in seconds) to increase the gain by 10dB\n default = .25, range 0 - 1

\n", + "type": "Number" + }, + { + "name": "time", + "description": "

Assign time value to schedule the change in value

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Compressor", + "module": "p5.sound" + }, + "reduction": { + "name": "reduction", + "class": "p5.Compressor", + "module": "p5.sound" + } + }, + "p5.PeakDetect": { + "isDetected": { + "name": "isDetected", + "class": "p5.PeakDetect", + "module": "p5.sound" + }, + "update": { + "name": "update", + "params": [ + { + "name": "fftObject", + "description": "

A p5.FFT object

\n", + "type": "p5.FFT" + } + ], + "class": "p5.PeakDetect", + "module": "p5.sound" + }, + "onPeak": { + "name": "onPeak", + "params": [ + { + "name": "callback", + "description": "

Name of a function that will\n be called when a peak is\n detected.

\n", + "type": "Function" + }, + { + "name": "val", + "description": "

Optional value to pass\n into the function when\n a peak is detected.

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5.PeakDetect", + "module": "p5.sound" + } + }, + "p5.SoundRecorder": { + "setInput": { + "name": "setInput", + "params": [ + { + "name": "unit", + "description": "

p5.sound object or a web audio unit\n that outputs sound

\n", + "type": "Object", + "optional": true + } + ], + "class": "p5.SoundRecorder", + "module": "p5.sound" + }, + "record": { + "name": "record", + "params": [ + { + "name": "soundFile", + "description": "

p5.SoundFile

\n", + "type": "p5.SoundFile" + }, + { + "name": "duration", + "description": "

Time (in seconds)

\n", + "type": "Number", + "optional": true + }, + { + "name": "callback", + "description": "

The name of a function that will be\n called once the recording completes

\n", + "type": "Function", + "optional": true + } + ], + "class": "p5.SoundRecorder", + "module": "p5.sound" + }, + "stop": { + "name": "stop", + "class": "p5.SoundRecorder", + "module": "p5.sound" + } + }, + "p5.Distortion": { + "WaveShaperNode": { + "name": "WaveShaperNode", + "class": "p5.Distortion", + "module": "p5.sound" + }, + "process": { + "name": "process", + "params": [ + { + "name": "amount", + "description": "

Unbounded distortion amount.\n Normal values range from 0-1.

\n", + "type": "Number", + "optional": true, + "optdefault": "0.25" + }, + { + "name": "oversample", + "description": "

'none', '2x', or '4x'.

\n", + "type": "String", + "optional": true, + "optdefault": "'none'" + } + ], + "class": "p5.Distortion", + "module": "p5.sound" + }, + "set": { + "name": "set", + "params": [ + { + "name": "amount", + "description": "

Unbounded distortion amount.\n Normal values range from 0-1.

\n", + "type": "Number", + "optional": true, + "optdefault": "0.25" + }, + { + "name": "oversample", + "description": "

'none', '2x', or '4x'.

\n", + "type": "String", + "optional": true, + "optdefault": "'none'" + } + ], + "class": "p5.Distortion", + "module": "p5.sound" + }, + "getAmount": { + "name": "getAmount", + "class": "p5.Distortion", + "module": "p5.sound" + }, + "getOversample": { + "name": "getOversample", + "class": "p5.Distortion", + "module": "p5.sound" + } + }, + "p5.Gain": { + "setInput": { + "name": "setInput", + "params": [ + { + "name": "src", + "description": "

p5.sound / Web Audio object with a sound\n output.

\n", + "type": "Object" + } + ], + "class": "p5.Gain", + "module": "p5.sound" + }, + "connect": { + "name": "connect", + "params": [ + { + "name": "unit", + "description": "", + "type": "Object" + } + ], + "class": "p5.Gain", + "module": "p5.sound" + }, + "disconnect": { + "name": "disconnect", + "class": "p5.Gain", + "module": "p5.sound" + }, + "amp": { + "name": "amp", + "params": [ + { + "name": "volume", + "description": "

amplitude between 0 and 1.0

\n", + "type": "Number" + }, + { + "name": "rampTime", + "description": "

create a fade that lasts rampTime

\n", + "type": "Number", + "optional": true + }, + { + "name": "timeFromNow", + "description": "

schedule this event to happen\n seconds from now

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.Gain", + "module": "p5.sound" + } + }, + "p5.AudioVoice": { + "connect": { + "name": "connect", + "params": [ + { + "name": "unit", + "description": "", + "type": "Object" + } + ], + "class": "p5.AudioVoice", + "module": "p5.sound" + }, + "disconnect": { + "name": "disconnect", + "class": "p5.AudioVoice", + "module": "p5.sound" + } + }, + "p5.MonoSynth": { + "attack": { + "name": "attack", + "class": "p5.MonoSynth", + "module": "p5.sound" + }, + "decay": { + "name": "decay", + "class": "p5.MonoSynth", + "module": "p5.sound" + }, + "sustain": { + "name": "sustain", + "class": "p5.MonoSynth", + "module": "p5.sound" + }, + "release": { + "name": "release", + "class": "p5.MonoSynth", + "module": "p5.sound" + }, + "play": { + "name": "play", + "params": [ + { + "name": "note", + "description": "

the note you want to play, specified as a\n frequency in Hertz (Number) or as a midi\n value in Note/Octave format (\"C4\", \"Eb3\"...etc\")\n See \n Tone. Defaults to 440 hz.

\n", + "type": "String | Number" + }, + { + "name": "velocity", + "description": "

velocity of the note to play (ranging from 0 to 1)

\n", + "type": "Number", + "optional": true + }, + { + "name": "secondsFromNow", + "description": "

time from now (in seconds) at which to play

\n", + "type": "Number", + "optional": true + }, + { + "name": "sustainTime", + "description": "

time to sustain before releasing the envelope. Defaults to 0.15 seconds.

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.MonoSynth", + "module": "p5.sound" + }, + "triggerAttack": { + "params": [ + { + "name": "note", + "description": "

the note you want to play, specified as a\n frequency in Hertz (Number) or as a midi\n value in Note/Octave format (\"C4\", \"Eb3\"...etc\")\n See \n Tone. Defaults to 440 hz

\n", + "type": "String | Number" + }, + { + "name": "velocity", + "description": "

velocity of the note to play (ranging from 0 to 1)

\n", + "type": "Number", + "optional": true + }, + { + "name": "secondsFromNow", + "description": "

time from now (in seconds) at which to play

\n", + "type": "Number", + "optional": true + } + ], + "name": "triggerAttack", + "class": "p5.MonoSynth", + "module": "p5.sound" + }, + "triggerRelease": { + "params": [ + { + "name": "secondsFromNow", + "description": "

time to trigger the release

\n", + "type": "Number" + } + ], + "name": "triggerRelease", + "class": "p5.MonoSynth", + "module": "p5.sound" + }, + "setADSR": { + "name": "setADSR", + "params": [ + { + "name": "attackTime", + "description": "

Time (in seconds before envelope\n reaches Attack Level

\n", + "type": "Number" + }, + { + "name": "decayTime", + "description": "

Time (in seconds) before envelope\n reaches Decay/Sustain Level

\n", + "type": "Number", + "optional": true + }, + { + "name": "susRatio", + "description": "

Ratio between attackLevel and releaseLevel, on a scale from 0 to 1,\n where 1.0 = attackLevel, 0.0 = releaseLevel.\n The susRatio determines the decayLevel and the level at which the\n sustain portion of the envelope will sustain.\n For example, if attackLevel is 0.4, releaseLevel is 0,\n and susAmt is 0.5, the decayLevel would be 0.2. If attackLevel is\n increased to 1.0 (using setRange),\n then decayLevel would increase proportionally, to become 0.5.

\n", + "type": "Number", + "optional": true + }, + { + "name": "releaseTime", + "description": "

Time in seconds from now (defaults to 0)

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.MonoSynth", + "module": "p5.sound" + }, + "amp": { + "name": "amp", + "params": [ + { + "name": "vol", + "description": "

desired volume

\n", + "type": "Number" + }, + { + "name": "rampTime", + "description": "

Time to reach new volume

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.MonoSynth", + "module": "p5.sound" + }, + "connect": { + "name": "connect", + "params": [ + { + "name": "unit", + "description": "

A p5.sound or Web Audio object

\n", + "type": "Object" + } + ], + "class": "p5.MonoSynth", + "module": "p5.sound" + }, + "disconnect": { + "name": "disconnect", + "class": "p5.MonoSynth", + "module": "p5.sound" + }, + "dispose": { + "name": "dispose", + "class": "p5.MonoSynth", + "module": "p5.sound" + } + }, + "p5.PolySynth": { + "notes": { + "name": "notes", + "class": "p5.PolySynth", + "module": "p5.sound" + }, + "polyvalue": { + "name": "polyvalue", + "class": "p5.PolySynth", + "module": "p5.sound" + }, + "AudioVoice": { + "name": "AudioVoice", + "class": "p5.PolySynth", + "module": "p5.sound" + }, + "play": { + "name": "play", + "params": [ + { + "name": "note", + "description": "

midi note to play (ranging from 0 to 127 - 60 being a middle C)

\n", + "type": "Number", + "optional": true + }, + { + "name": "velocity", + "description": "

velocity of the note to play (ranging from 0 to 1)

\n", + "type": "Number", + "optional": true + }, + { + "name": "secondsFromNow", + "description": "

time from now (in seconds) at which to play

\n", + "type": "Number", + "optional": true + }, + { + "name": "sustainTime", + "description": "

time to sustain before releasing the envelope

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.PolySynth", + "module": "p5.sound" + }, + "noteADSR": { + "name": "noteADSR", + "params": [ + { + "name": "note", + "description": "

Midi note on which ADSR should be set.

\n", + "type": "Number", + "optional": true + }, + { + "name": "attackTime", + "description": "

Time (in seconds before envelope\n reaches Attack Level

\n", + "type": "Number", + "optional": true + }, + { + "name": "decayTime", + "description": "

Time (in seconds) before envelope\n reaches Decay/Sustain Level

\n", + "type": "Number", + "optional": true + }, + { + "name": "susRatio", + "description": "

Ratio between attackLevel and releaseLevel, on a scale from 0 to 1,\n where 1.0 = attackLevel, 0.0 = releaseLevel.\n The susRatio determines the decayLevel and the level at which the\n sustain portion of the envelope will sustain.\n For example, if attackLevel is 0.4, releaseLevel is 0,\n and susAmt is 0.5, the decayLevel would be 0.2. If attackLevel is\n increased to 1.0 (using setRange),\n then decayLevel would increase proportionally, to become 0.5.

\n", + "type": "Number", + "optional": true + }, + { + "name": "releaseTime", + "description": "

Time in seconds from now (defaults to 0)

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.PolySynth", + "module": "p5.sound" + }, + "setADSR": { + "name": "setADSR", + "params": [ + { + "name": "attackTime", + "description": "

Time (in seconds before envelope\n reaches Attack Level

\n", + "type": "Number", + "optional": true + }, + { + "name": "decayTime", + "description": "

Time (in seconds) before envelope\n reaches Decay/Sustain Level

\n", + "type": "Number", + "optional": true + }, + { + "name": "susRatio", + "description": "

Ratio between attackLevel and releaseLevel, on a scale from 0 to 1,\n where 1.0 = attackLevel, 0.0 = releaseLevel.\n The susRatio determines the decayLevel and the level at which the\n sustain portion of the envelope will sustain.\n For example, if attackLevel is 0.4, releaseLevel is 0,\n and susAmt is 0.5, the decayLevel would be 0.2. If attackLevel is\n increased to 1.0 (using setRange),\n then decayLevel would increase proportionally, to become 0.5.

\n", + "type": "Number", + "optional": true + }, + { + "name": "releaseTime", + "description": "

Time in seconds from now (defaults to 0)

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.PolySynth", + "module": "p5.sound" + }, + "noteAttack": { + "name": "noteAttack", + "params": [ + { + "name": "note", + "description": "

midi note on which attack should be triggered.

\n", + "type": "Number", + "optional": true + }, + { + "name": "velocity", + "description": "

velocity of the note to play (ranging from 0 to 1)/

\n", + "type": "Number", + "optional": true + }, + { + "name": "secondsFromNow", + "description": "

time from now (in seconds)

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.PolySynth", + "module": "p5.sound" + }, + "noteRelease": { + "name": "noteRelease", + "params": [ + { + "name": "note", + "description": "

midi note on which attack should be triggered.\n If no value is provided, all notes will be released.

\n", + "type": "Number", + "optional": true + }, + { + "name": "secondsFromNow", + "description": "

time to trigger the release

\n", + "type": "Number", + "optional": true + } + ], + "class": "p5.PolySynth", + "module": "p5.sound" + }, + "connect": { + "name": "connect", + "params": [ + { + "name": "unit", + "description": "

A p5.sound or Web Audio object

\n", + "type": "Object" + } + ], + "class": "p5.PolySynth", + "module": "p5.sound" + }, + "disconnect": { + "name": "disconnect", + "class": "p5.PolySynth", + "module": "p5.sound" + }, + "dispose": { + "name": "dispose", + "class": "p5.PolySynth", + "module": "p5.sound" + } + } +} \ No newline at end of file diff --git a/docs/preprocessor.js b/docs/preprocessor.js deleted file mode 100644 index 7826816885..0000000000 --- a/docs/preprocessor.js +++ /dev/null @@ -1,314 +0,0 @@ -const marked = require('marked'); -const Entities = require('html-entities').AllHtmlEntities; - -const DocumentedMethod = require('./documented-method'); - -function smokeTestMethods(data) { - data.classitems.forEach(function(classitem) { - if (classitem.itemtype === 'method') { - new DocumentedMethod(classitem); - - if ( - classitem.access !== 'private' && - classitem.file.slice(0, 3) === 'src' && - classitem.name && - !classitem.example - ) { - console.log( - classitem.file + - ':' + - classitem.line + - ': ' + - classitem.itemtype + - ' ' + - classitem.class + - '.' + - classitem.name + - ' missing example' - ); - } - } - }); -} - -function mergeOverloadedMethods(data) { - let methodsByFullName = {}; - let paramsForOverloadedMethods = {}; - - let consts = (data.consts = {}); - - data.classitems = data.classitems.filter(function(classitem) { - if (classitem.access === 'private') { - return false; - } - - const itemClass = data.classes[classitem.class]; - if (!itemClass || itemClass.private) { - return false; - } - - let methodConsts = {}; - - let fullName, method; - - var assertEqual = function(a, b, msg) { - if (a !== b) { - throw new Error( - 'for ' + - fullName + - '() defined in ' + - classitem.file + - ':' + - classitem.line + - ', ' + - msg + - ' (' + - JSON.stringify(a) + - ' !== ' + - JSON.stringify(b) + - ')' - ); - } - }; - - var extractConsts = function(param) { - if (!param.type) { - console.log(param); - } - if (param.type.split('|').indexOf('Constant') >= 0) { - let match; - if (classitem.name === 'endShape' && param.name === 'mode') { - match = 'CLOSE'; - } else { - const constantRe = /either\s+(?:[A-Z0-9_]+\s*,?\s*(?:or)?\s*)+/g; - const execResult = constantRe.exec(param.description); - match = execResult && execResult[0]; - if (!match) { - throw new Error( - classitem.file + - ':' + - classitem.line + - ', Constant-typed parameter ' + - fullName + - '(...' + - param.name + - '...) is missing valid value enumeration. ' + - 'See inline_documentation.md#specify-parameters.' - ); - } - } - if (match) { - const reConst = /[A-Z0-9_]+/g; - let matchConst; - while ((matchConst = reConst.exec(match)) !== null) { - methodConsts[matchConst] = true; - } - } - } - }; - - var processOverloadedParams = function(params) { - let paramNames; - - if (!(fullName in paramsForOverloadedMethods)) { - paramsForOverloadedMethods[fullName] = {}; - } - - paramNames = paramsForOverloadedMethods[fullName]; - - params.forEach(function(param) { - const origParam = paramNames[param.name]; - - if (origParam) { - assertEqual( - origParam.type, - param.type, - 'types for param "' + - param.name + - '" must match ' + - 'across all overloads' - ); - assertEqual( - param.description, - '', - 'description for param "' + - param.name + - '" should ' + - 'only be defined in its first use; subsequent ' + - 'overloads should leave it empty' - ); - } else { - paramNames[param.name] = param; - extractConsts(param); - } - }); - - return params; - }; - - if (classitem.itemtype && classitem.itemtype === 'method') { - fullName = classitem.class + '.' + classitem.name; - if (fullName in methodsByFullName) { - // It's an overloaded version of a method that we've already - // indexed. We need to make sure that we don't list it multiple - // times in our index pages and such. - - method = methodsByFullName[fullName]; - - assertEqual( - method.file, - classitem.file, - 'all overloads must be defined in the same file' - ); - assertEqual( - method.module, - classitem.module, - 'all overloads must be defined in the same module' - ); - assertEqual( - method.submodule, - classitem.submodule, - 'all overloads must be defined in the same submodule' - ); - assertEqual( - classitem.description || '', - '', - 'additional overloads should have no description' - ); - - var makeOverload = function(method) { - const overload = { - line: method.line, - params: processOverloadedParams(method.params || []) - }; - // TODO: the doc renderer assumes (incorrectly) that - // these are the same for all overrides - if (method.static) overload.static = method.static; - if (method.chainable) overload.chainable = method.chainable; - if (method.return) overload.return = method.return; - return overload; - }; - - if (!method.overloads) { - method.overloads = [makeOverload(method)]; - delete method.params; - } - method.overloads.push(makeOverload(classitem)); - return false; - } else { - if (classitem.params) { - classitem.params.forEach(function(param) { - extractConsts(param); - }); - } - methodsByFullName[fullName] = classitem; - } - - Object.keys(methodConsts).forEach(constName => - (consts[constName] || (consts[constName] = [])).push(fullName) - ); - } - return true; - }); -} - -// build a copy of data.json for the FES, restructured for object lookup on -// classitems and removing all the parts not needed by the FES -function buildParamDocs(docs) { - let newClassItems = {}; - // the fields we need for the FES, discard everything else - let allowed = new Set(['name', 'class', 'module', 'params', 'overloads']); - for (let classitem of docs.classitems) { - if (classitem.name && classitem.class) { - for (let key in classitem) { - if (!allowed.has(key)) { - delete classitem[key]; - } - } - if (classitem.hasOwnProperty('overloads')) { - for (let overload of classitem.overloads) { - // remove line number and return type - if (overload.line) { - delete overload.line; - } - - if (overload.return) { - delete overload.return; - } - } - } - if (!newClassItems[classitem.class]) { - newClassItems[classitem.class] = {}; - } - - newClassItems[classitem.class][classitem.name] = classitem; - } - } - - let fs = require('fs'); - let path = require('path'); - let out = fs.createWriteStream( - path.join(process.cwd(), 'docs', 'parameterData.json'), - { - flags: 'w', - mode: '0644' - } - ); - out.write(JSON.stringify(newClassItems, null, 2)); - out.end(); -} - -function renderItemDescriptionsAsMarkdown(item) { - if (item.description) { - const entities = new Entities(); - item.description = entities.decode(marked.parse(item.description)); - } - if (item.params) { - item.params.forEach(renderItemDescriptionsAsMarkdown); - } -} - -function renderDescriptionsAsMarkdown(data) { - Object.keys(data.modules).forEach(function(moduleName) { - renderItemDescriptionsAsMarkdown(data.modules[moduleName]); - }); - Object.keys(data.classes).forEach(function(className) { - renderItemDescriptionsAsMarkdown(data.classes[className]); - }); - data.classitems.forEach(function(classitem) { - renderItemDescriptionsAsMarkdown(classitem); - }); -} - -module.exports = (data, options) => { - data.classitems - .filter( - ci => !ci.itemtype && (ci.params || ci.return) && ci.access !== 'private' - ) - .forEach(ci => { - console.error(ci.file + ':' + ci.line + ': unnamed public member'); - }); - - Object.keys(data.classes) - .filter(k => data.classes[k].access === 'private') - .forEach(k => delete data.classes[k]); - - renderDescriptionsAsMarkdown(data); - mergeOverloadedMethods(data); - smokeTestMethods(data); - buildParamDocs(JSON.parse(JSON.stringify(data))); -}; - -module.exports.mergeOverloadedMethods = mergeOverloadedMethods; -module.exports.renderDescriptionsAsMarkdown = renderDescriptionsAsMarkdown; - -module.exports.register = (Handlebars, options) => { - Handlebars.registerHelper('root', function(context, options) { - // if (this.language === 'en') { - // return ''; - // } else { - // return '/'+this.language; - // } - return window.location.pathname; - }); -}; diff --git a/docs/yuidoc-p5-theme/assets/Bold.ttf b/docs/yuidoc-p5-theme/assets/Bold.ttf deleted file mode 100644 index 50d81bdad5..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Bold.ttf and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/Damscray.mp3 b/docs/yuidoc-p5-theme/assets/Damscray.mp3 deleted file mode 100644 index b113049905..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Damscray.mp3 and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/Damscray.ogg b/docs/yuidoc-p5-theme/assets/Damscray.ogg deleted file mode 100644 index 77d530a2df..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Damscray.ogg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/Damscray_-_Dancing_Tiger_01.ogg b/docs/yuidoc-p5-theme/assets/Damscray_-_Dancing_Tiger_01.ogg deleted file mode 100644 index ddb329fc15..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Damscray_-_Dancing_Tiger_01.ogg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/Damscray_-_Dancing_Tiger_02.mp3 b/docs/yuidoc-p5-theme/assets/Damscray_-_Dancing_Tiger_02.mp3 deleted file mode 100644 index b113049905..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Damscray_-_Dancing_Tiger_02.mp3 and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/Damscray_01.mp3 b/docs/yuidoc-p5-theme/assets/Damscray_01.mp3 deleted file mode 100644 index 9653a67bb6..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Damscray_01.mp3 and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/Damscray_01.ogg b/docs/yuidoc-p5-theme/assets/Damscray_01.ogg deleted file mode 100644 index ddb329fc15..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Damscray_01.ogg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/Damscray_02.mp3 b/docs/yuidoc-p5-theme/assets/Damscray_02.mp3 deleted file mode 100644 index b113049905..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Damscray_02.mp3 and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/Damscray_02.ogg b/docs/yuidoc-p5-theme/assets/Damscray_02.ogg deleted file mode 100644 index 77d530a2df..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Damscray_02.ogg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/Damscray_DancingTiger.mp3 b/docs/yuidoc-p5-theme/assets/Damscray_DancingTiger.mp3 deleted file mode 100644 index b113049905..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Damscray_DancingTiger.mp3 and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/Italic.ttf b/docs/yuidoc-p5-theme/assets/Italic.ttf deleted file mode 100644 index e5a1a86e63..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Italic.ttf and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/Regular.otf b/docs/yuidoc-p5-theme/assets/Regular.otf deleted file mode 100644 index 38941ae72f..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/Regular.otf and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/all.css b/docs/yuidoc-p5-theme/assets/all.css deleted file mode 100644 index f73622fcba..0000000000 --- a/docs/yuidoc-p5-theme/assets/all.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! normalize.css v1.1.0 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}button,html,input,select,textarea{font-family:sans-serif}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ol,nav ul{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure,form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0} -;code[class*=language-],pre[class*=language-],textarea{color:#222;font-family:Inconsolata,Consolas,Monaco,Andale Mono,monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;font-size:1em!important}pre[class*=language-]{position:relative;padding:.5em 1em;margin:.5em 0 0 -.5em;border-left:.5em solid #afafaf;background-color:#fff;background-image:-webkit-linear-gradient(transparent 50%,rgba(69,142,209,.06) 0);background-image:-o-linear-gradient(transparent 50%,rgba(69,142,209,.06) 50%);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(50%,transparent),color-stop(50%,rgba(69,142,209,.06)));background-image:linear-gradient(transparent 50%,rgba(69,142,209,.06) 0);-webkit-background-size:2.9em 2.9em;background-size:2.9em 2.9em;-webkit-background-origin:content-box;background-origin:content-box;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{margin-bottom:1em}:not(pre)>code[class*=language-]{position:relative;padding:.2em;border-radius:.3em;color:#333;border:1px solid rgba(0,0,0,.1)}:not(pre)>code[class*=language-]:after,pre[class*=language-]:after{right:.75em;left:auto}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#a0a0a0}.token.punctuation{color:#666}.token.boolean,.token.constant,.token.function-name,.token.number,.token.property,.token.symbol,.token.tag{color:#dc3787}.token.attr-name,.token.builtin,.token.function,.token.selector,.token.string{color:#00a1d3}.token.entity,.token.operator,.token.url,.token.variable{color:#a67f59;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.class-name,.token.keyword{color:#704f21}.token.important,.token.regex{color:#e90}.language-css .token.string,.style .token.string{color:#a67f59;background:hsla(0,0%,100%,.5)}.token.important{font-weight:400}.token.entity{cursor:help}.namespace{opacity:.7}@media screen and (max-width:767px){pre[class*=language-]:after,pre[class*=language-]:before{bottom:14px;-webkit-box-shadow:none;box-shadow:none}}.token.cr:before,.token.lf:before,.token.tab:not(:empty):before{color:#e0d7d1}pre.line-numbers{padding-left:3.8em;counter-reset:linenumber}pre.line-numbers,pre.line-numbers>code{position:relative}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{pointer-events:none;display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right} -;button,html,input,select{color:#222}textarea{line-height:1.45em;padding:.5em 1em;border:none}body{font-size:1em;line-height:1.4}::-moz-selection{background:#b3d4fc;text-shadow:none}::selection{background:#b3d4fc;text-shadow:none}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}img{vertical-align:middle}img.med_left{width:300px;float:left}img.med_right{width:300px;float:right}img.small_left{width:200px;float:left}img.smaller_left{width:140px;float:left}img.small_right{width:200px;float:right}img.smaller_right{width:140px;float:right}img.small_center{width:200px;margin-left:250px}img.small{width:160px}img.med{width:400px}img.med_center{width:400px;margin-left:150px}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}.tagline{display:none}#home-page .home{pointer-events:none}#home-page .home a{pointer-events:all}#lockup>a{position:relative;display:block;width:200px;height:90px}#logo_image{position:absolute;top:0}#menu,#menu.top_menu{list-style:none;font-family:Montserrat,sans-serif;width:100%;margin:0 0 1em;padding:0;height:100%;font-size:1.3em}#menu.top_menu li{display:inline}#home-sketch{position:absolute;top:0;left:0;z-index:-2}@media screen and (max-width:780px){.sidebar-menu{clear:both;max-height:0;-webkit-transition:max-height .4s ease-out;-o-transition:max-height .4s ease-out;transition:max-height .4s ease-out;overflow:hidden}.sidebar-menu-nav-element{width:91vw}.sidebar-menu a{display:block;text-align:center;padding-bottom:.11em;border-bottom:.11em dashed transparent}.sidebar-menu-icon{top:2rem;cursor:pointer;float:right;padding:28px 20px;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;margin-bottom:5rem}.sidebar-menu-icon .sidebar-nav-icon{background:#ed225d;display:block;height:2px;position:relative;-webkit-transition:background .4s ease-out;-o-transition:background .4s ease-out;transition:background .4s ease-out;width:18px}.sidebar-menu-icon .sidebar-nav-icon:after,.sidebar-menu-icon .sidebar-nav-icon:before{background:#ed225d;content:"";display:block;height:100%;position:absolute;-webkit-transition:all .4s ease-out;-o-transition:all .4s ease-out;transition:all .4s ease-out;width:100%}.sidebar-menu-icon .sidebar-nav-icon:before{top:5px}.sidebar-menu-icon .sidebar-nav-icon:after{top:-5px}.sidebar-menu-btn{display:none}.sidebar-menu-btn:checked~.sidebar-menu{max-height:475px}.sidebar-menu-btn:checked~.sidebar-menu-icon .sidebar-nav-icon{background:transparent}.sidebar-menu-btn:checked~.sidebar-menu-icon .sidebar-nav-icon:before{-webkit-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg);top:0}.sidebar-menu-btn:checked~.sidebar-menu-icon .sidebar-nav-icon:after{-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg);top:0}}.sidebar-menu-btn{display:none}#footer-menu{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;list-style:none;font-family:Montserrat,sans-serif;font-size:1em;padding-top:1em;border-bottom:.11em dashed transparent}#footer-menu li{margin-right:1em}#footer-menu a{color:#ed225d}#home-sketch-frame{position:fixed;width:100%;height:100%;left:0;top:0;z-index:-2;overflow:hidden;pointer-events:all;border:0}#credits{position:fixed;bottom:0;left:0;z-index:2;padding:1em;font-size:.7em}#skip-to-content{position:absolute;left:0;top:40px;z-index:5;background-color:#ed225d;color:#fff;width:auto;height:50px;border:none;outline-style:none;text-align:center;font-size:25px;padding:5px;opacity:0}#skip-to-content:focus{opacity:1}.button_box{padding:.4em .6em;margin:.5em 0;font-family:Montserrat,sans-serif;display:inline-block}.button_box,.download_box{border:1px solid #ed225d;color:#333}.download_box{padding:.4em;margin:0 1.75em 0 0;width:18.65em;float:left;height:7.45em;position:relative}.button_box:hover,.download_box:hover{border:1px solid #ed225d;background:#ed225d;color:#fff}.download_box.half_box{width:10.83em;margin-right:1.75em;float:left}.download_box.half_box.last_box{margin-right:0}.download_box .download_name{font-size:1em;margin:0;padding-bottom:.3em;border-bottom:.09em dashed #ed225d;line-height:1.2;font-family:Montserrat,sans-serif;display:block}.download_box:hover .download_name{-webkit-text-stroke-width:0;border-bottom-color:#fff}.download_box p{font-size:.65em;margin:0;position:absolute;bottom:1em}.download_box svg{height:.65em;width:.65em;position:absolute;bottom:3.5em}.download_box:hover svg{fill:#fff}.download_box h4+p{display:block}#download-page .link_group{width:100%;margin-bottom:3em}.download_box{margin-top:1em}.support div.download_box{margin-top:1em;margin-bottom:1em}#download-page .support p{font-size:.8em;position:static;margin-top:.3em}#slideshow{margin:1em 0}#slideshow p{font-size:.8em;color:#ababab;line-height:1.2em;margin-top:.5em}.extra{color:#fff;position:absolute;bottom:.65em;right:.9em;font-weight:700;-ms-transform:rotate(-12deg);-webkit-transform:rotate(-12deg);-o-transform:rotate(-12deg);transform:rotate(-12deg);font-size:.8em}#get-started-page .edit_space{position:relative;-webkit-box-ordinal-group:4;-webkit-order:3;-ms-flex-order:3;order:3;margin-bottom:4.8125em}#get-started-page .edit_space .copy_button{color:#2d7bb6;border-color:rgba(45,123,182,.25);float:right;margin:1.5em 0 1.5em .5em;background:hsla(0,0%,100%,.7);position:absolute;z-index:2;left:31.33em;top:-1.5em}@media (max-width:780px){#get-started-page .edit_space .copy_button{left:6.44em}}@media (max-width:600px){#get-started-page .edit_space .copy_button{left:5.91em}}#examples-page .column{margin-bottom:2em}#reference-page main h1{float:left}.reference-group h2{font-size:1.5em}.reference-group h3{font-size:1em;font-family:Montserrat,sans-serif;margin-top:.5em}div.reference-group{display:inline-block}div.reference-subgroups{margin:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap}div.reference-subgroup{width:11em;margin-bottom:1em}#reference-page .params table p{display:inline;font-size:inherit}#reference-page .param-optional{color:#afafaf}#item{width:100%}#item h2{margin:.777em 0 0;font-size:1.444em;font-weight:inherit;font-family:Inconsolata,consolas,monospace;color:#00a1d3}#item h3{font-size:1.33em;margin:1em 0 0}#item ul{margin-top:.5em}#item li{margin-bottom:1em}#reference-page #item ul{list-style:initial;margin-left:1.5em}#reference-page #item .example_container li,#reference-page #item .params li{margin-bottom:1em}#reference-page #item .example_container ul,#reference-page #item .params ul,#reference-page #item ul[aria-labelledby=reference-fields],#reference-page #item ul[aria-labelledby=reference-methods]{list-style:none;margin-left:0}#reference-page #item li{margin-bottom:.5em}.description{clear:both;display:block;width:100%}.syntax pre{width:100%}.item-wrapper,.list-wrapper{float:left;outline:none}.paramname{display:inline-block;min-width:25%;margin-right:1%;font-size:1.2em}.paramtype p{display:inline;font-size:1em}.paramtype{font-size:1.2em;width:73%;vertical-align:top}#library-page .group-name,.paramtype{display:inline-block}#library-page .group-name:hover{color:#ed225d}.example div{position:relative}.example-content .example_code{position:relative;left:1em;padding-top:0;margin-top:1rem;border:none;width:30.5em;max-width:100%}.example-content .example_code.norender{left:0;margin-left:0}.example-content .edit_space{position:absolute;top:0;left:0;margin-top:-.5em;width:100%;pointer-events:none}.example-content .edit_space *{pointer-events:auto}.example-content .edit_space ul{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;position:relative;pointer-events:none}.example-content .edit_space ul li button{font-family:Montserrat,sans-serif;font-size:1em;color:#ccc;border:1px solid hsla(0,0%,78.4%,.15);background:transparent;outline:none;margin-top:.25em}.example-content .edit_space ul li button:hover,.example_container.editing ul li button{color:#2d7bb6;border-color:rgba(45,123,182,.25)}.example-content .edit_space .edit_area{position:absolute;top:.5em;left:120px;width:30.5em;display:none;font-family:monospace;padding:1.5em .5em .5em;font-size:15pt}.display_button{margin-bottom:2em;font-family:Montserrat,sans-serif;font-size:1em;color:#2d7bb6;border:1px solid rgba(45,123,182,.25);background:transparent;outline:none}.example-content .example_container{width:36em;max-width:100%;border-top:.09em dashed #333;padding-top:.5em;margin-top:2em;min-height:120px;height:-webkit-calc(110% + 20px);height:calc(110% + 20px);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.example-content .example_container:first-of-type{margin-top:1em}@media (max-width:600px){.example-content .example_code{margin-top:.2rem;left:.5rem}.example-content .example_container{width:100%;min-height:220px;height:-webkit-calc(110% + 120px);height:calc(110% + 120px);display:block}.example-content .edit_space .edit_area{top:-webkit-calc(120px + 1em);top:calc(120px + 1em);left:0;width:100%;padding:.5em}.example_container button{top:124px}.description{margin-top:3rem}.edit_button{left:0}.reset_button{left:2.58em}.copy_button{left:5.91em}}form{pointer-events:all}#search_button{background:url(../img/search.png) 100% no-repeat}#search input[type=search],#search input[type=text]{border:1px solid hsla(0,0%,78.4%,.5);font-family:Montserrat,sans-serif;font-size:2.25em;width:9.75em}#search .twitter-typeahead .tt-hint,#search ::-webkit-input-placeholder{color:#ccc}:-moz-placeholder,:-ms-input-placeholder,::-moz-placeholder{color:#ccc}#search input[type=text]:focus{color:#2d7bb6;outline-color:#2d7bb6;outline-width:1px;outline-style:solid}#search .twitter-typeahead .tt-dropdown-menu{background-color:#fff}#search .twitter-typeahead .tt-suggestion.tt-cursor{color:#333;background-color:#eee}#search .twitter-typeahead .tt-suggestion p{margin:0}#search .twitter-typeahead .tt-suggestion p .small{font-size:12px;color:#666}#search{float:right}#search .twitter-typeahead .tt-dropdown-menu{border:1px solid rgba(0,0,0,.2);padding:.5em;max-height:200px;overflow-y:auto;font-size:1em;line-height:1.4em}#search .twitter-typeahead .tt-suggestion{padding:3px 20px;line-height:24px;cursor:pointer}#search .twitter-typeahead .empty-message{padding:8px 20px 1px;font-size:14px;line-height:24px}#search_button{float:right}a.code.core{color:#333}a.code.addon{color:#704f21}#contribute-item{font-size:.75em;text-align:left;display:inline-block;width:320px;height:250px;float:left;border:1px solid #ed225d;margin:0 25px 25px 0;position:relative}.contribute-item-container{position:absolute;z-index:20;margin:0;padding:10px}.container{height:100px;position:relative;background:#fff;margin-top:1.5em}#infoi,#navi{width:100%;height:100%;position:absolute;top:0;left:0}#infoi{z-index:10}h3.contribute-title{font-size:1.33em;margin:0 0 27px;padding-bottom:.3em;border-bottom:.09em dashed #ed225d}.label{position:relative}.label .nounderline img{margin:.5em 0 0}.label h3{color:#fff;position:absolute;top:0;margin:1em}.label:hover h3{color:#ed225d}h3{font-size:1.33em;margin:1em 0 0}.bullet-list{padding:0 0 0 40px;list-style:disc}#libraries-page .label h3{background-color:#000;padding:0 5px}#learn-page .label .nounderline img{height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}#learn-page .info{display:inline-block}#exampleDisplay,#exampleEditor,#exampleFrame{width:36em;border:none}#exampleDisplay{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-flow:column;-ms-flex-flow:column;flex-flow:column}#popupExampleFrame{position:fixed;top:0;left:0;right:0;bottom:0;z-index:1000;border:none}#exampleDisplay button{color:#2d7bb6;border-color:rgba(45,123,182,.25);float:right;margin:.5em 0 0 .5em;background:hsla(0,0%,100%,.7);position:absolute;left:0;z-index:2}#exampleDisplay .edit_button{left:25.42em;top:-2.5em}#exampleDisplay .reset_button{left:28em;top:-2.5em}#exampleDisplay .copy_button{left:31.33em;top:-2.5em}#exampleDisplay button:hover{background:#fff}#exampleDisplay .edit_space{position:relative;-webkit-box-ordinal-group:4;-webkit-order:3;-ms-flex-order:3;order:3}#exampleDisplay #exampleFrame{height:22em;-webkit-box-ordinal-group:3;-webkit-order:2;-ms-flex-order:2;order:2}#exampleDisplay #exampleEditor{height:500em;width:710px;overflow:hidden;margin-top:.5em;color:#222;font-family:Inconsolata,consolas,monospace;font-size:1em;background-color:#fff;line-height:1em;-webkit-box-ordinal-group:5;-webkit-order:4;-ms-flex-order:4;order:4}#exampleDisplay #exampleEditor .ace_gutter-cell{background-image:none;padding-left:10px;overflow:hidden;background-color:#afafaf}#exampleDisplay #exampleEditor .ace_gutter-cell.ace_info{background-color:#d7e5f5}#exampleDisplay #exampleEditor .ace_gutter-cell.ace_warning{background-color:gold;color:#fff}#exampleDisplay #exampleEditor .ace_gutter-cell.ace_error{background-color:tomato;color:#fff}#exampleDisplay #exampleEditor .ace_numeric,#exampleDisplay #exampleEditor .ace_tag{color:#dc3787}#exampleDisplay #exampleEditor .ace_attribute-name,#exampleDisplay #exampleEditor .ace_class,#exampleDisplay #exampleEditor .ace_type{color:#704f21}#exampleDisplay #exampleEditor .ace_function,#exampleDisplay #exampleEditor .ace_keyword,#exampleDisplay #exampleEditor .ace_support{color:#00a1d3}#exampleDisplay #exampleEditor .ace_comment{color:#a0a0a0}#exampleDisplay #exampleEditor .ace_string{color:#a67f59}#exampleDisplay #exampleEditor .ace_operator{color:#333}#exampleDisplay #exampleEditor .ace_regexp{color:#e90}#exampleDisplay #exampleEditor .ace-gutter,#exampleDisplay #exampleEditor .ace-gutter-layer{color:#333}#exampleDisplay #exampleEditor .ace_folding-enabled{width:10px!important;color:#333}.attribution{background-color:#eee;font-size:15px;padding:10px;margin:30px 0}#featuring{margin-bottom:1em}#showcase-page .showcase-intro h1{font:italic 900 14.5vw Montserrat,sans-serif;color:#ed225d;text-align:left;text-transform:uppercase}#showcase-page .showcase-intro p{font:400 1.4rem Montserrat,sans-serif;line-height:1.5em}#showcase-page .project-page h2,#showcase-page .showcase-featured h2{font:italic 900 2rem Montserrat,sans-serif;color:#ed225d;letter-spacing:.05rem}#showcase-page ul.left-column,#showcase-page ul.links,#showcase-page ul.project-tags,#showcase-page ul.right-column{list-style:none}#showcase-page img[alt]{font-size:.9rem}#showcase-page .showcase-featured{margin-top:15%}#showcase-page .showcase-featured h3.title{font:italic 900 1rem Montserrat,sans-serif}#showcase-page .showcase-featured p.credit{font:500 1rem Montserrat,sans-serif}#showcase-page .showcase-featured p.description{font-size:1em;margin-bottom:.5rem}#showcase-page .nominate{margin-top:1.5em;display:inline-block}#showcase-page .nominate a,#showcase-page .nominate a:visited{padding:.4em .5em;position:relative;top:0;left:0;border:2px solid #ed225d;-webkit-box-shadow:4px 4px 0 #ed225d;box-shadow:4px 4px 0 #ed225d;font:1.5rem Montserrat,sans-serif;color:#ed225d;letter-spacing:.02rem;-webkit-transition:all .3s;-o-transition:all .3s;transition:all .3s}@media (max-width:500px){#showcase-page .nominate a,#showcase-page .nominate a:visited{padding:.4em .3em;font:1.3rem Montserrat,sans-serif}}#showcase-page .nominate a:hover{top:4px;left:4px;-webkit-box-shadow:none;box-shadow:none}#showcase-page .showcase-featured a,#showcase-page .showcase-featured a:visited{font-size:1.2rem;color:#ed225d;letter-spacing:.02rem;line-height:1.5}#showcase-page .showcase-featured a:after{content:" →"}#showcase-page .showcase-featured a.tag:after{content:""}#showcase-page .showcase-featured .no-arrow-link:after{content:" "}#showcase-page .showcase-featured .no-arrow-link:hover{text-decoration:none;padding:none;border:none}.project-info{margin-top:1em}ul.project-tags a{line-height:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;font-size:.5em;margin:0}h3.title{margin-top:3em}#showcase-page ul.project-tags li{margin:5px;display:inline-block}h2.featuring{margin-top:0}#showcase-page a.tag{display:inline-block;padding:6px 14px;background-color:#ffe8e8;border-radius:27px;font:.7rem Montserrat,sans-serif;color:#333}#showcase-page ul.project-tags li{margin:0}#showcase-page{margin-top:3em}#showcase-page .project-page h2{line-height:1.4}@media (min-width:720px){#showcase-page .showcase-intro h1{font:italic 900 6.35vw Montserrat,sans-serif}#showcase-page .showcase-intro p{line-height:1.75em;font-size:1em}#showcase-page .project-metadata{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}#showcase-page .project-resources{margin-left:3rem}#showcase-page .project-a{width:90%;float:right;display:inline-block;clear:both}#showcase-page .half-image{width:48%}}#showcase-page .project-metadata{margin-top:3%}#showcase-page .project-metadata section h3{color:#ed225d;font:700 italic 1rem Montserrat,sans-serif}#showcase-page .project-resources ul.links{font:500 .7rem Montserrat,sans-serif;letter-spacing:.01rem;line-height:1.5;margin:.5rem 0}#showcase-page .project-credit{font:italic 700 1.25rem Montserrat,sans-serif}#showcase-page .project-credit p{margin:.5rem 0}#showcase-page .creator-from,#showcase-page .note{font-size:.7rem}#showcase-page .qa-group{margin-bottom:2em}#showcase-page .project-q{margin-left:0;display:inline-block;clear:both;font:900 1.2rem Montserrat,sans-serif;line-height:1.5}#showcase-page code{font-size:1.1rem}#teach-page .case-list a:hover{border-bottom:none}#teach-page .heading{width:100%;font:400 1.4rem Montserrat,sans-serif;color:#000;line-height:1.2em;padding-bottom:.4em;border-bottom:4px dotted #ed225d}#teach-page h3.title{margin-top:3em}#teach-page section.workshopS{overflow:auto}#teach-page .workshop-content{padding:.6em}#teach-page ul.workshops{padding-top:.4em;width:41%;float:left}#teach-page .btn,#teach-page li.workshop .active,li.workshop p:hover{margin-bottom:.8em;padding-bottom:.4em;font:400 .9rem Times,sans-serif;line-height:1.2em;border-bottom:.1em dashed #ffe8e8}#teach-page li.workshop .active,li.workshop p:hover{color:#ed225d}#teach-page .upcoming-banners{width:59%;float:right;padding-top:.8em;padding-left:1em;border:none}#teach-page .banner2,.banner3,.time2,.time3{display:none}#teach-page .banner1:hover,.banner2:hover,.banner3:hover{border:none}#teach-page .banner-img{float:left;-webkit-box-shadow:0 0 5px 2px rgba(0,0,0,.1);box-shadow:0 0 5px 2px rgba(0,0,0,.1);border-radius:5px;border:none}#teach-page .banner-img:hover{-webkit-box-shadow:0 0 5px 2px #e088a1;box-shadow:0 0 5px 2px #e088a1;border-radius:5px;border:none}#teach-page .upcoming-time p{float:right;margin-bottom:.8em;padding-bottom:.4em;font:400 .8rem Times,sans-serif;line-height:.01em}#teach-page .search-filter{display:inline}#teach-page .search-filter label{display:inline-block;font:italic 900 1rem Montserrat,sans-serif;padding:6px 12px;text-align:left;white-space:nowrap;color:#ed225d;margin-bottom:.6em;margin-top:1.2em;border:1px solid #ed225d;cursor:pointer}#teach-page .search-filter label:hover{color:#fff;background-color:#ed225d}#teach-page .search-filter input[type=checkbox]{display:absolute;position:absolute;opacity:0}#teach-page ul.filters p.filter-title{font:400 .83rem Montserrat,sans-serif;color:#ed225d;height:50px;padding-top:20px;background:none;background-color:none;-webkit-box-shadow:none;box-shadow:none;display:inline-block;border:none;clear:both}#teach-page ul.filters li{display:inline;list-style:none;width:100%}#teach-page ul.filters li label{display:inline-block;border-radius:25px;font:200 .7rem Montserrat,sans-serif;color:#000;white-space:nowrap;margin:3px 0;-webkit-transition:.2s;-o-transition:.2s;transition:.2s;background:#fafafa;padding:6px 12px;cursor:pointer}#teach-page ul.filters li label:before{display:inline-block;padding:2px}#teach-page ul.filters li label:hover{color:#ed225d;background:#ffe8e8}#teach-page ul.filters li input[type=checkbox]:checked+label{color:#fff;background:#ed225d}#teach-page ul.filters li input[type=checkbox]{display:absolute;position:absolute;opacity:0}#teach-page ul.filters li.clear{display:block;clear:both}#teach-page .filter-panel{background-color:#fff;max-height:0;overflow:hidden;-webkit-transition:max-height .2s ease-out;-o-transition:max-height .2s ease-out;transition:max-height .2s ease-out;margin-bottom:.8em;padding:0 0 .4em}#teach-page .filter-panel p{margin:0;color:#333;font-size:.83em;height:50px;padding-top:20px;-webkit-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out}#teach-page .teach-intro p{font:400 1.2rem Times,sans-serif;line-height:1.5em}#teach-page .modal-title{margin-left:1em;margin-right:1em;font:400 1rem Montserrat,sans-serif;color:#ed225d;line-height:1.2em}#teach-page ul.cases li.clear{display:block;clear:both;margin-top:1em;margin-bottom:1.2em}#teach-page img{margin-bottom:1.4em}#teach-page img[alt]{font:.6rem Montserrat,sans-serif;color:#bababa}#teach-page .close{position:relative;color:#ffc7c7;float:right;font-size:40px;font-weight:700;margin-right:.4em;margin-top:.4em;cursor:pointer}#teach-page .close:hover,.close:focus{color:#ed225d;text-decoration:none;cursor:pointer}#teach-page .case label{margin:2px;padding:5px 8px;display:inline-block;border-radius:25px;font:.7rem Montserrat,sans-serif;color:#aaa;white-space:nowrap;color:#fff;background:#ed225d}#teach-page .modal-body::-webkit-scrollbar{width:5px;height:5px;border-radius:10px}#teach-page .modal-body::-webkit-scrollbar-track{background:#f1f1f1}#teach-page .modal-body::-webkit-scrollbar-thumb{background:#ffe8e8}#teach-page .case{margin-left:2em;margin-right:2em}#teach-page .case span{color:#ed225d;font:900 1.4rem Montserrat,sans-serif}#teach-page .case p.lead-name{font:900 Italic 1.2rem Montserrat,sans-serif;color:#ed225d;line-height:1.4em;border-bottom:1.4em}#teach-page .case .speech{position:relative;font:200 Italic .8rem Montserrat,sans-serif;color:#000;background:#ffe8e8;padding:.5em 1.2em;border-radius:.4em;border-bottom:none;margin-bottom:2em;margin-top:1em}#teach-page .case .speech:after{content:"";position:absolute;top:0;left:8%;width:0;height:0;border:10px solid transparent;border-bottom-color:#ffe8e8;border-top:0;margin-left:-10px;margin-top:-10px}#teach-page .case p.subtitle{font:400 1rem Montserrat,sans-serif;color:#ed225d}#teach-page .case p,#teach-page .case p.subtitle{line-height:1.4em;border-bottom:.1em dashed rgba(237,34,93,.15)}#teach-page .case p{font:400 1rem Times,sans-serif;color:#000}#teach-page .modal-footer,#teach-page .modal-header{margin-bottom:.8em}#teach-page .modal-body:-webkit-scrollbar{display:none}#teach-page .modal{display:none;position:fixed;z-index:100;width:100%;height:100%;top:0;left:0;right:0;overflow:auto;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background-color:rgba(255,232,232,.5)}#teach-page .modal-content{position:fixed;background:#fff;top:2%;left:2%;right:2%;bottom:2%;margin:auto;border:1.2px solid #ffe8e8;max-width:740px;-webkit-box-shadow:10px 100px 30px -17px rgba(237,34,93,.5);box-shadow:10px 100px 30px -17px rgba(237,34,93,.5);-webkit-box-shadow:10px 100px 20px -17px rgba(237,34,93,.5);box-shadow:10px 100px 20px -17px rgba(237,34,93,.5);-webkit-box-shadow:10px 20px 10px -17px rgba(237,34,93,.5);box-shadow:10px 20px 10px -17px rgba(237,34,93,.5)}#teach-page .modal-body{margin:auto;height:85%;width:95%;overflow-y:auto}#teach-page .results-wrapper{width:100%;outline:none;background:#fff}#teach-page .results-wrapper ul li.case-list a.myBtn{overflow:hidden;text-overflow:ellipsis}#teach-page .case-list{margin-bottom:.8em;padding-bottom:.4em;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;font:400 1rem Times,sans-serif;line-height:1.2em;border-bottom:.1em dashed #ffe8e8}#teach-page .labels{width:40%}#teach-page .tags.selected{display:inline-block;margin:2px;padding:5px 8px;border-radius:25px;font:200 .7rem Montserrat,sans-serif;color:#fff;white-space:nowrap;background:#ed225d}#teach-page .caseBtn{padding-top:.2em;padding-bottom:.2em;width:60%;height:-webkit-max-content;height:-moz-max-content;height:max-content;float:left}#teach-page .case-list label{display:inline-block;margin:2px;padding:5px 8px;border-radius:25px;font:200 .7rem Montserrat,sans-serif;color:#000;white-space:nowrap;background:#fafafa}*,:after,:before{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}html{font-size:1.25em}body{margin:0;background-color:#fff;font-family:Times;font-weight:400;line-height:1.45;color:#333}p{font-size:1.2em;margin:.5em 0}.freeze{overflow:hidden}#menu li a:focus:active,#menu li a:focus:hover,#menu li a:link,#menu li a:visited{color:#ed225d;background:transparent;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}a:link,a:visited{color:#2d7bb6;text-decoration:none}#reference a:hover,a:active,a:hover{color:#ed225d;text-decoration:none;padding-bottom:.11em;border-bottom:.11em dashed #ed225d;-webkit-transition:border-bottom 30ms linear;-o-transition:border-bottom 30ms linear;transition:border-bottom 30ms linear}a.nounderline:hover{border:none}a.here{color:#ed225d;text-decoration:none;padding-bottom:.1em;border-bottom:transparent;border-bottom-color:#ed225d}.highlight{background-color:rgba(237,34,93,.15)}.container>div:first-of-type{margin-top:2em}h1,h2,h3,h4,h5{margin:1.414em 0 .5em;font-weight:inherit;line-height:1.2;font-family:Montserrat,sans-serif}h1{font-size:2.25em;margin:0}h2{font-size:1.5em;margin:1em 0 0}.code{font-family:Inconsolata,consolas,monospace}#backlink{margin:1.2em .444em 0 0;font-family:Montserrat,sans-serif;float:right}#backlink a{color:#afafaf}#backlink a:hover{color:#ed225d;border-bottom:none}#promo,#promo:visited{width:100%;background:#98fb98;margin:0;text-align:center;padding:.4em 0;background:#74ffb7;background:-webkit-radial-gradient(circle,#74ffb7 0,#8afff2 100%);background:-o-radial-gradient(circle,#74ffb7 0,#8afff2 100%);background:radial-gradient(circle,#74ffb7 0,#8afff2 100%);font-family:Montserrat,sans-serif;color:#ed225d!important}#promo:hover{background:#ed225d;color:#fff!important}#promo-link{margin:0!important;padding:0}#family a:link,#family a:visited{margin:.4em}#family a:active,#family a:hover{margin:.4em;border:none}#family{position:absolute;z-index:9999;top:0;left:0;width:100%;overflow:none;margin:0;border-bottom:1px solid rgba(51,51,51,.5);-webkit-box-shadow:0 0 10px #333;box-shadow:0 0 10px #333;background-color:hsla(0,0%,100%,.85)}#processing-sites{margin:.375em 0}#processing-sites li{list-style:none;display:inline-block;margin:0}#processing-sites li:first-child{margin-left:2em}#processing-sites li:last-child{float:right;margin-right:2em}.code-snippet{margin:0 0 0 1em;width:90%;clear:both}.column-span{margin:0 0 0 1em;padding:0;float:left;max-width:100%}.method_description p{margin-top:0}main{padding:0}.spacer{clear:both}ul{margin:0;padding:0;list-style:none}ol{font-size:1.2em}li{margin:0;padding:0}ul.list_view{margin:.5em 0 0 1em;padding:0;list-style:disc;list-style-position:outside;font-size:1.2em}ol ul.list_view{font-size:1em}ul.inside{margin:0 0 0 2em;padding:0;list-style:disc;list-style-position:inside}.image-row img{width:48%;height:14.3%}.image-row img+img{float:right;margin-right:0;margin-bottom:.25em}img,main div img{margin:.5em .5em 0 0;width:100%}p+img{margin-top:0}.video{width:100%}#lockup{position:absolute;top:-5.75em;left:1.25em;height:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#lockup object{margin:0;padding:0;border:none}#lockup a:focus{outline:0}.logo{padding:0;border:none;margin:0 0 .4em;height:4.5em;width:9.75em}#lockup p{color:#ed225d;font-size:.7em;font-family:Montserrat,sans-serif;margin:.5em 0 0 8.5em}#lockup a:link{border:transparent;height:4.5em;width:9.75em}.caption{margin-bottom:2.5em}.caption p,.caption span{text-align:right;font-size:.7em;font-family:Montserrat,sans-serif;padding-top:.25em}footer{clear:both;border-top:.1em dashed #ed225d;margin:2em 0}.videoWrapper{position:relative;padding-bottom:56.25%;height:0;margin-top:.5em}.videoWrapper iframe{position:absolute;top:0;left:0;width:100%;height:100%}.ir{background-color:transparent;border:0;overflow:hidden;*text-indent:-9999px}.ir:before{content:"";display:block;width:0;height:150%}.hidden{display:none!important;visibility:hidden}.sr-only{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only.focusable:active,.sr-only.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.clearfix:after,.clearfix:before{content:" ";display:table}.clearfix:after{clear:both}.clearfix{*zoom:1}#notMobile-message{display:none;-webkit-box-ordinal-group:2;-webkit-order:1;-ms-flex-order:1;order:1}#asterisk-design-element,#isMobile-displayButton,.separator-design-element{display:none}.pointerevents #asterisk-design-element,.pointerevents .separator-design-element{pointer-events:none;display:inline-block}@media (min-width:780px){.container{width:49em!important;margin:11.5em auto}main{width:36em;padding:0!important}footer{width:48em}}@media (min-width:780px){.container{margin:11.5em auto;width:100%;padding:.8em 0 0;height:auto;min-height:100%}#home-page{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:row;-ms-flex-wrap:row;flex-wrap:row}main{padding:0 1em 0 0}#family,.small,footer,small{font-size:.7em}footer{clear:both}#i18n-btn{position:absolute;top:4em;right:1em}#i18n-btn a,#menu{font-family:Montserrat,sans-serif}#menu{list-style:none;margin:0 .75em 0 -1.85em;width:7.3em;height:100%;border-top:transparent;border-bottom:transparent;padding:0;font-size:.83em;z-index:100;position:relative;top:0}#menu li{float:none;margin:0 0 1em;text-align:right}#menu li:nth-child(11){margin-top:3em;padding-top:.5em}.left-column{width:48%;float:left;margin-bottom:40px}.right-column{width:48%;float:right;margin-right:0;margin-bottom:.25em}.narrow-left-column{width:32%;float:left}.wide-right-column{width:64%;float:right;margin-right:0;margin-bottom:.25em}.book{font-size:.7em}.column_0,.column_1,.column_2{float:left;width:11.333em}.column_0,.column_1{margin-right:1em}#collection-list-nav{width:100%;clear:both;border-bottom:1px dashed rgba(0,0,0,.2)}#collection-list-categories{font-family:Montserrat,sans-serif;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;margin:1em 0 1.5em}#collection-list-categories ul{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}#collection-list{margin:0}.group-name.first{margin-top:0!important}.column.group-name{margin-bottom:1em}#library-page .group-name{margin:2em 0 .5em}#library-page .column{margin-top:1em}#list{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-bottom:1em}}@media (max-width:780px){.tagline{display:none!important}#family{display:none}#i18n-btn{position:absolute;top:.5em;right:.7em;z-index:10}#search{float:left;margin-bottom:1em}#search,#search input[type=text]{width:100%}#search input[type=search],#search input[type=text]{font-size:1.5em}#lockup{position:absolute;top:2em;left:1em}.column-span{margin:0;padding:0 1em;float:left}#menu,#menu.top_menu{margin:6em 0 .5em;font-size:1.3em}#menu li{display:inline-block}#menu li:last-child a:after{content:""}#menu li a:after{content:","}#contribute-item:first-child{margin-left:0}.download_box{width:96%}.download_box.half_box{width:46%;margin-right:4%;float:left}#etc_list{font-size:1.2em;margin-top:1em}#asterisk-design-element,.separator-design-element{display:none!important;pointer-events:none}pre[class*=language-]{padding:.5em;width:100%}code{word-wrap:break-word;word-break:break-all}#credits{position:relative!important;z-index:2;margin-top:-7em;padding:0 2em 3em 1em;font-size:.5em;float:right;width:100%;text-align:right}#credits,#home-sketch-frame{display:none}#exampleDisplay,#exampleDisplay #exampleEditor,#exampleFrame{width:100%}#exampleDisplay .edit_button{left:-.58em}#exampleDisplay .reset_button{left:3em}#exampleDisplay .copy_button{left:6.44em}#exampleEditor{margin-top:3em}.small,footer,small{font-size:.5em}.paramtype{width:96%}}@media (max-width:400px){#i18n{margin-top:.75em!important}body{margin-top:-.75em!important}}iframe{border:none;width:100%}.iframe-container{overflow:hidden;position:relative}.iframe-container iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.cnv_div{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}.cnv_div>*{width:100px;height:auto}@media (max-width:780px){#teach-page ul.workshops{width:100%}#teach-page .upcoming-banners{width:100%;float:left;padding-top:0;padding-left:0;border:none}#teach-page .case-list{display:block}#teach-page .caseBtn,#teach-page .labels{min-width:100%}}#i18n-buttons{margin:0;background:#fff}#i18n-buttons li{list-style:none;display:inline-block;margin-left:.5em}#i18n-btn a{border:none;outline:none;font-size:.7em;color:#696969;z-index:5}#i18n-btn a:hover{color:#ed225d}#i18n-btn a.disabled{color:#ed225d;cursor:default}#asterisk-design-element{position:fixed;z-index:-1;opacity:.6;pointer-events:none}.separator-design-element{width:.33em;height:.33em;margin:0 .09em .18em;display:inline-block;overflow:hidden;text-indent:-100%;background:transparent url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCI+PHBhdGggZD0iTTE2LjkgMTAuM2w4LjUtMi42IDEuNyA1LjItOC41IDIuOSA1LjMgNy41LTQuNCAzLjItNS42LTcuMy01LjQgNy4xTDQuMiAyM2w1LjMtNy4yLTguNi0zLjIgMS43LTUuMiA4LjYgMi44VjEuNEgxN3Y4Ljl6IiBmaWxsPSIjZWQyMjVkIiBzdHJva2U9IiNlZDIyNWQiLz48L3N2Zz4=");-webkit-background-size:.33em .33em;background-size:.33em}#home-page #asterisk-design-element{bottom:-8%;right:20%;height:12em;width:12em;opacity:1}#examples-page #asterisk-design-element,#learn-page #asterisk-design-element,#other-content-types-page #asterisk-design-element{bottom:-14%;left:-20%;height:25em;width:25em;-webkit-transform:rotate(-1deg);-ms-transform:rotate(-1deg);-o-transform:rotate(-1deg);transform:rotate(-1deg)}#books-page #asterisk-design-element,#libraries-page #asterisk-design-element{bottom:-19%;right:-16%;height:28em;width:28em;-webkit-transform:rotate(2deg);-ms-transform:rotate(2deg);-o-transform:rotate(2deg);transform:rotate(2deg)}#community-page #asterisk-design-element,#get-started-page #asterisk-design-element{top:10%;right:-20%;height:30em;width:30em;-webkit-transform:rotate(2deg);-ms-transform:rotate(2deg);-o-transform:rotate(2deg);transform:rotate(2deg)}#download-page #asterisk-design-element,#reference-page #asterisk-design-element{top:7%;left:1%;height:10em;width:10em;-webkit-transform:rotate(-21deg);-ms-transform:rotate(-21deg);-o-transform:rotate(-21deg);transform:rotate(-21deg)}@media(max-width:1352px){#download-page #asterisk-design-element,#reference-page #asterisk-design-element{display:none!important}}.email-octopus-email-address{color:#ed225d;text-decoration:none;padding-bottom:.11em;outline:none;border:none;border-bottom:.11em dashed #ed225d;-webkit-transition:border-bottom 30ms linear;-o-transition:border-bottom 30ms linear;transition:border-bottom 30ms linear;width:13em}.email-octopus-form-row-hp{position:absolute;left:-5000px}.email-octopus-form-row button{border:1px solid #ed225d;color:#ed225d;padding:.4em .6em;margin:1em 0 0;font-family:Montserrat,sans-serif;display:block}.email-octopus-form-row button:hover{background-color:#ed225d;color:#fff}.email-octopus-email-address::-webkit-input-placeholder{color:#ababab}.email-octopus-email-address:-moz-placeholder,.email-octopus-email-address::-moz-placeholder{color:#ababab}.email-octopus-email-address:-ms-input-placeholder{color:#ababab}@media (min-width:720px){.email-octopus-email-address{width:16em}.email-octopus-form-row button{margin:0 0 0 .5em;display:inline}} -/*# sourceMappingURL=maps/all.css.map */ \ No newline at end of file diff --git a/docs/yuidoc-p5-theme/assets/arnott-wallace-eye-loop-forever.gif b/docs/yuidoc-p5-theme/assets/arnott-wallace-eye-loop-forever.gif deleted file mode 100644 index 992757bbaf..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/arnott-wallace-eye-loop-forever.gif and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/arnott-wallace-wink-loop-once.gif b/docs/yuidoc-p5-theme/assets/arnott-wallace-wink-loop-once.gif deleted file mode 100644 index 94f2015ff3..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/arnott-wallace-wink-loop-once.gif and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/beat.mp3 b/docs/yuidoc-p5-theme/assets/beat.mp3 deleted file mode 100644 index 3e5802604c..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/beat.mp3 and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/beat.ogg b/docs/yuidoc-p5-theme/assets/beat.ogg deleted file mode 100644 index c13f86a41f..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/beat.ogg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/beatbox.mp3 b/docs/yuidoc-p5-theme/assets/beatbox.mp3 deleted file mode 100644 index 23fb92eb71..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/beatbox.mp3 and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/beatbox.ogg b/docs/yuidoc-p5-theme/assets/beatbox.ogg deleted file mode 100644 index b2593a6340..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/beatbox.ogg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/blobs.csv b/docs/yuidoc-p5-theme/assets/blobs.csv deleted file mode 100644 index bda44d2203..0000000000 --- a/docs/yuidoc-p5-theme/assets/blobs.csv +++ /dev/null @@ -1,3 +0,0 @@ -ID,Name,Flavor,Shape,Color -Blob1,Blobby,Sweet,Blob,Pink -Blob2,Saddy,Savory,Blob,Blue \ No newline at end of file diff --git a/docs/yuidoc-p5-theme/assets/bricks.jpg b/docs/yuidoc-p5-theme/assets/bricks.jpg deleted file mode 100644 index 231161d752..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/bricks.jpg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/bricks_third.jpg b/docs/yuidoc-p5-theme/assets/bricks_third.jpg deleted file mode 100644 index 2fdb075996..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/bricks_third.jpg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/bx-spring.mp3 b/docs/yuidoc-p5-theme/assets/bx-spring.mp3 deleted file mode 100644 index 4a955ab7fa..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/bx-spring.mp3 and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/bx-spring.ogg b/docs/yuidoc-p5-theme/assets/bx-spring.ogg deleted file mode 100644 index b01abee927..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/bx-spring.ogg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/concrete-tunnel.mp3 b/docs/yuidoc-p5-theme/assets/concrete-tunnel.mp3 deleted file mode 100644 index 1bfbd4f8f8..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/concrete-tunnel.mp3 and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/concrete-tunnel.ogg b/docs/yuidoc-p5-theme/assets/concrete-tunnel.ogg deleted file mode 100644 index be5f66b72c..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/concrete-tunnel.ogg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/doorbell.mp3 b/docs/yuidoc-p5-theme/assets/doorbell.mp3 deleted file mode 100644 index 44b6367916..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/doorbell.mp3 and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/doorbell.ogg b/docs/yuidoc-p5-theme/assets/doorbell.ogg deleted file mode 100644 index e816288c97..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/doorbell.ogg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/drawImage.png b/docs/yuidoc-p5-theme/assets/drawImage.png deleted file mode 100644 index 1a7aa5d1d8..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/drawImage.png and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/drum.mp3 b/docs/yuidoc-p5-theme/assets/drum.mp3 deleted file mode 100644 index 9cd8727617..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/drum.mp3 and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/drum.ogg b/docs/yuidoc-p5-theme/assets/drum.ogg deleted file mode 100644 index 5061a1b319..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/drum.ogg and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/favicon.ico b/docs/yuidoc-p5-theme/assets/favicon.ico deleted file mode 100644 index 33ad9806c8..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/favicon.ico and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/fingers.mov b/docs/yuidoc-p5-theme/assets/fingers.mov deleted file mode 100644 index a9cbbbe3ea..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/fingers.mov and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/fonts/inconsolata.eot b/docs/yuidoc-p5-theme/assets/fonts/inconsolata.eot deleted file mode 100644 index 4b076884d0..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/fonts/inconsolata.eot and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/fonts/inconsolata.otf b/docs/yuidoc-p5-theme/assets/fonts/inconsolata.otf deleted file mode 100644 index e7e1fa0cd7..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/fonts/inconsolata.otf and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/fonts/inconsolata.svg b/docs/yuidoc-p5-theme/assets/fonts/inconsolata.svg deleted file mode 100644 index 2feb4e38c3..0000000000 --- a/docs/yuidoc-p5-theme/assets/fonts/inconsolata.svg +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/yuidoc-p5-theme/assets/fonts/inconsolata.ttf b/docs/yuidoc-p5-theme/assets/fonts/inconsolata.ttf deleted file mode 100644 index 2e8649b73d..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/fonts/inconsolata.ttf and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/fonts/inconsolata.woff b/docs/yuidoc-p5-theme/assets/fonts/inconsolata.woff deleted file mode 100644 index 41990c1dde..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/fonts/inconsolata.woff and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/gradient.png b/docs/yuidoc-p5-theme/assets/gradient.png deleted file mode 100644 index 5245af69cd..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/gradient.png and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/img/p5js.svg b/docs/yuidoc-p5-theme/assets/img/p5js.svg deleted file mode 100755 index eaf5b87618..0000000000 --- a/docs/yuidoc-p5-theme/assets/img/p5js.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/yuidoc-p5-theme/assets/inconsolata.otf b/docs/yuidoc-p5-theme/assets/inconsolata.otf deleted file mode 100644 index e7e1fa0cd7..0000000000 Binary files a/docs/yuidoc-p5-theme/assets/inconsolata.otf and /dev/null differ diff --git a/docs/yuidoc-p5-theme/assets/js/reference.js b/docs/yuidoc-p5-theme/assets/js/reference.js deleted file mode 100644 index 455d4425d9..0000000000 --- a/docs/yuidoc-p5-theme/assets/js/reference.js +++ /dev/null @@ -1,4949 +0,0 @@ -(function () { -// https://github.com/umdjs/umd/blob/master/templates/returnExports.js -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define('documented-method',[], factory); - } else if (typeof module === 'object' && module.exports) { - module.exports = factory(); - } else { - root.DocumentedMethod = factory(); - } -}(this, function () { - function extend(target, src) { - Object.keys(src).forEach(function(prop) { - target[prop] = src[prop]; - }); - return target; - } - - function DocumentedMethod(classitem) { - extend(this, classitem); - - if (this.overloads) { - // Make each overload inherit properties from their parent - // classitem. - this.overloads = this.overloads.map(function(overload) { - return extend(Object.create(this), overload); - }, this); - - if (this.params) { - throw new Error('params for overloaded methods should be undefined'); - } - - this.params = this._getMergedParams(); - } - } - - DocumentedMethod.prototype = { - // Merge parameters across all overloaded versions of this item. - _getMergedParams: function() { - var paramNames = {}; - var params = []; - - this.overloads.forEach(function(overload) { - if (!overload.params) { - return; - } - overload.params.forEach(function(param) { - if (param.name in paramNames) { - return; - } - paramNames[param.name] = param; - params.push(param); - }); - }); - - return params; - } - }; - - return DocumentedMethod; -})); - -/** - * @license RequireJS text 2.0.10 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. - * Available via the MIT or new BSD license. - * see: http://github.com/requirejs/text for details - */ -/*jslint regexp: true */ -/*global require, XMLHttpRequest, ActiveXObject, - define, window, process, Packages, - java, location, Components, FileUtils */ - -define('text',['module'], function (module) { - 'use strict'; - - var text, fs, Cc, Ci, xpcIsWindows, - progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], - xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, - bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im, - hasLocation = typeof location !== 'undefined' && location.href, - defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''), - defaultHostName = hasLocation && location.hostname, - defaultPort = hasLocation && (location.port || undefined), - buildMap = {}, - masterConfig = (module.config && module.config()) || {}; - - text = { - version: '2.0.10', - - strip: function (content) { - //Strips declarations so that external SVG and XML - //documents can be added to a document without worry. Also, if the string - //is an HTML document, only the part inside the body tag is returned. - if (content) { - content = content.replace(xmlRegExp, ""); - var matches = content.match(bodyRegExp); - if (matches) { - content = matches[1]; - } - } else { - content = ""; - } - return content; - }, - - jsEscape: function (content) { - return content.replace(/(['\\])/g, '\\$1') - .replace(/[\f]/g, "\\f") - .replace(/[\b]/g, "\\b") - .replace(/[\n]/g, "\\n") - .replace(/[\t]/g, "\\t") - .replace(/[\r]/g, "\\r") - .replace(/[\u2028]/g, "\\u2028") - .replace(/[\u2029]/g, "\\u2029"); - }, - - createXhr: masterConfig.createXhr || function () { - //Would love to dump the ActiveX crap in here. Need IE 6 to die first. - var xhr, i, progId; - if (typeof XMLHttpRequest !== "undefined") { - return new XMLHttpRequest(); - } else if (typeof ActiveXObject !== "undefined") { - for (i = 0; i < 3; i += 1) { - progId = progIds[i]; - try { - xhr = new ActiveXObject(progId); - } catch (e) {} - - if (xhr) { - progIds = [progId]; // so faster next time - break; - } - } - } - - return xhr; - }, - - /** - * Parses a resource name into its component parts. Resource names - * look like: module/name.ext!strip, where the !strip part is - * optional. - * @param {String} name the resource name - * @returns {Object} with properties "moduleName", "ext" and "strip" - * where strip is a boolean. - */ - parseName: function (name) { - var modName, ext, temp, - strip = false, - index = name.indexOf("."), - isRelative = name.indexOf('./') === 0 || - name.indexOf('../') === 0; - - if (index !== -1 && (!isRelative || index > 1)) { - modName = name.substring(0, index); - ext = name.substring(index + 1, name.length); - } else { - modName = name; - } - - temp = ext || modName; - index = temp.indexOf("!"); - if (index !== -1) { - //Pull off the strip arg. - strip = temp.substring(index + 1) === "strip"; - temp = temp.substring(0, index); - if (ext) { - ext = temp; - } else { - modName = temp; - } - } - - return { - moduleName: modName, - ext: ext, - strip: strip - }; - }, - - xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/, - - /** - * Is an URL on another domain. Only works for browser use, returns - * false in non-browser environments. Only used to know if an - * optimized .js version of a text resource should be loaded - * instead. - * @param {String} url - * @returns Boolean - */ - useXhr: function (url, protocol, hostname, port) { - var uProtocol, uHostName, uPort, - match = text.xdRegExp.exec(url); - if (!match) { - return true; - } - uProtocol = match[2]; - uHostName = match[3]; - - uHostName = uHostName.split(':'); - uPort = uHostName[1]; - uHostName = uHostName[0]; - - return (!uProtocol || uProtocol === protocol) && - (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && - ((!uPort && !uHostName) || uPort === port); - }, - - finishLoad: function (name, strip, content, onLoad) { - content = strip ? text.strip(content) : content; - if (masterConfig.isBuild) { - buildMap[name] = content; - } - onLoad(content); - }, - - load: function (name, req, onLoad, config) { - //Name has format: some.module.filext!strip - //The strip part is optional. - //if strip is present, then that means only get the string contents - //inside a body tag in an HTML string. For XML/SVG content it means - //removing the declarations so the content can be inserted - //into the current doc without problems. - - // Do not bother with the work if a build and text will - // not be inlined. - if (config.isBuild && !config.inlineText) { - onLoad(); - return; - } - - masterConfig.isBuild = config.isBuild; - - var parsed = text.parseName(name), - nonStripName = parsed.moduleName + - (parsed.ext ? '.' + parsed.ext : ''), - url = req.toUrl(nonStripName), - useXhr = (masterConfig.useXhr) || - text.useXhr; - - // Do not load if it is an empty: url - if (url.indexOf('empty:') === 0) { - onLoad(); - return; - } - - //Load the text. Use XHR if possible and in a browser. - if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) { - text.get(url, function (content) { - text.finishLoad(name, parsed.strip, content, onLoad); - }, function (err) { - if (onLoad.error) { - onLoad.error(err); - } - }); - } else { - //Need to fetch the resource across domains. Assume - //the resource has been optimized into a JS module. Fetch - //by the module name + extension, but do not include the - //!strip part to avoid file system issues. - req([nonStripName], function (content) { - text.finishLoad(parsed.moduleName + '.' + parsed.ext, - parsed.strip, content, onLoad); - }); - } - }, - - write: function (pluginName, moduleName, write, config) { - if (buildMap.hasOwnProperty(moduleName)) { - var content = text.jsEscape(buildMap[moduleName]); - write.asModule(pluginName + "!" + moduleName, - "define(function () { return '" + - content + - "';});\n"); - } - }, - - writeFile: function (pluginName, moduleName, req, write, config) { - var parsed = text.parseName(moduleName), - extPart = parsed.ext ? '.' + parsed.ext : '', - nonStripName = parsed.moduleName + extPart, - //Use a '.js' file name so that it indicates it is a - //script that can be loaded across domains. - fileName = req.toUrl(parsed.moduleName + extPart) + '.js'; - - //Leverage own load() method to load plugin value, but only - //write out values that do not have the strip argument, - //to avoid any potential issues with ! in file names. - text.load(nonStripName, req, function (value) { - //Use own write() method to construct full module value. - //But need to create shell that translates writeFile's - //write() to the right interface. - var textWrite = function (contents) { - return write(fileName, contents); - }; - textWrite.asModule = function (moduleName, contents) { - return write.asModule(moduleName, fileName, contents); - }; - - text.write(pluginName, nonStripName, textWrite, config); - }, config); - } - }; - - if (masterConfig.env === 'node' || (!masterConfig.env && - typeof process !== "undefined" && - process.versions && - !!process.versions.node && - !process.versions['node-webkit'])) { - //Using special require.nodeRequire, something added by r.js. - fs = require.nodeRequire('fs'); - - text.get = function (url, callback, errback) { - try { - var file = fs.readFileSync(url, 'utf8'); - //Remove BOM (Byte Mark Order) from utf8 files if it is there. - if (file.indexOf('\uFEFF') === 0) { - file = file.substring(1); - } - callback(file); - } catch (e) { - errback(e); - } - }; - } else if (masterConfig.env === 'xhr' || (!masterConfig.env && - text.createXhr())) { - text.get = function (url, callback, errback, headers) { - var xhr = text.createXhr(), header; - xhr.open('GET', url, true); - - //Allow plugins direct access to xhr headers - if (headers) { - for (header in headers) { - if (headers.hasOwnProperty(header)) { - xhr.setRequestHeader(header.toLowerCase(), headers[header]); - } - } - } - - //Allow overrides specified in config - if (masterConfig.onXhr) { - masterConfig.onXhr(xhr, url); - } - - xhr.onreadystatechange = function (evt) { - var status, err; - //Do not explicitly handle errors, those should be - //visible via console output in the browser. - if (xhr.readyState === 4) { - status = xhr.status; - if (status > 399 && status < 600) { - //An http 4xx or 5xx error. Signal an error. - err = new Error(url + ' HTTP status: ' + status); - err.xhr = xhr; - errback(err); - } else { - callback(xhr.responseText); - } - - if (masterConfig.onXhrComplete) { - masterConfig.onXhrComplete(xhr, url); - } - } - }; - xhr.send(null); - }; - } else if (masterConfig.env === 'rhino' || (!masterConfig.env && - typeof Packages !== 'undefined' && typeof java !== 'undefined')) { - //Why Java, why is this so awkward? - text.get = function (url, callback) { - var stringBuffer, line, - encoding = "utf-8", - file = new java.io.File(url), - lineSeparator = java.lang.System.getProperty("line.separator"), - input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), - content = ''; - try { - stringBuffer = new java.lang.StringBuffer(); - line = input.readLine(); - - // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 - // http://www.unicode.org/faq/utf_bom.html - - // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 - if (line && line.length() && line.charAt(0) === 0xfeff) { - // Eat the BOM, since we've already found the encoding on this file, - // and we plan to concatenating this buffer with others; the BOM should - // only appear at the top of a file. - line = line.substring(1); - } - - if (line !== null) { - stringBuffer.append(line); - } - - while ((line = input.readLine()) !== null) { - stringBuffer.append(lineSeparator); - stringBuffer.append(line); - } - //Make sure we return a JavaScript string and not a Java string. - content = String(stringBuffer.toString()); //String - } finally { - input.close(); - } - callback(content); - }; - } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env && - typeof Components !== 'undefined' && Components.classes && - Components.interfaces)) { - //Avert your gaze! - Cc = Components.classes, - Ci = Components.interfaces; - Components.utils['import']('resource://gre/modules/FileUtils.jsm'); - xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc); - - text.get = function (url, callback) { - var inStream, convertStream, fileObj, - readData = {}; - - if (xpcIsWindows) { - url = url.replace(/\//g, '\\'); - } - - fileObj = new FileUtils.File(url); - - //XPCOM, you so crazy - try { - inStream = Cc['@mozilla.org/network/file-input-stream;1'] - .createInstance(Ci.nsIFileInputStream); - inStream.init(fileObj, 1, 0, false); - - convertStream = Cc['@mozilla.org/intl/converter-input-stream;1'] - .createInstance(Ci.nsIConverterInputStream); - convertStream.init(inStream, "utf-8", inStream.available(), - Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); - - convertStream.readString(inStream.available(), readData); - convertStream.close(); - inStream.close(); - callback(readData.value); - } catch (e) { - throw new Error((fileObj && fileObj.path || '') + ': ' + e); - } - }; - } - return text; -}); - - -define('text!tpl/search.html',[],function () { return '

search

\n
\n \n \n
\n\n';}); - - -define('text!tpl/search_suggestion.html',[],function () { return '

\n\n <%=name%>\n\n \n <% if (final) { %>\n constant\n <% } else if (itemtype) { %>\n <%=itemtype%> \n <% } %>\n\n <% if (className) { %>\n in <%=className%>\n <% } %>\n\n <% if (typeof is_constructor !== \'undefined\' && is_constructor) { %>\n constructor\n <% } %>\n \n\n

';}); - -/*! - * typeahead.js 0.10.2 - * https://github.com/twitter/typeahead.js - * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT - */ -define('typeahead',[], function() { - -//(function($) { - - - var _ = { - isMsie: function() { - return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; - }, - isBlankString: function(str) { - return !str || /^\s*$/.test(str); - }, - escapeRegExChars: function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - }, - isString: function(obj) { - return typeof obj === "string"; - }, - isNumber: function(obj) { - return typeof obj === "number"; - }, - isArray: $.isArray, - isFunction: $.isFunction, - isObject: $.isPlainObject, - isUndefined: function(obj) { - return typeof obj === "undefined"; - }, - bind: $.proxy, - each: function(collection, cb) { - $.each(collection, reverseArgs); - function reverseArgs(index, value) { - return cb(value, index); - } - }, - map: $.map, - filter: $.grep, - every: function(obj, test) { - var result = true; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (!(result = test.call(null, val, key, obj))) { - return false; - } - }); - return !!result; - }, - some: function(obj, test) { - var result = false; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (result = test.call(null, val, key, obj)) { - return false; - } - }); - return !!result; - }, - mixin: $.extend, - getUniqueId: function() { - var counter = 0; - return function() { - return counter++; - }; - }(), - templatify: function templatify(obj) { - return $.isFunction(obj) ? obj : template; - function template() { - return String(obj); - } - }, - defer: function(fn) { - setTimeout(fn, 0); - }, - debounce: function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments, later, callNow; - later = function() { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - } - }; - callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) { - result = func.apply(context, args); - } - return result; - }; - }, - throttle: function(func, wait) { - var context, args, timeout, result, previous, later; - previous = 0; - later = function() { - previous = new Date(); - timeout = null; - result = func.apply(context, args); - }; - return function() { - var now = new Date(), remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - } else if (!timeout) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - noop: function() {} - }; - var VERSION = "0.10.2"; - var tokenizers = function(root) { - return { - nonword: nonword, - whitespace: whitespace, - obj: { - nonword: getObjTokenizer(nonword), - whitespace: getObjTokenizer(whitespace) - } - }; - function whitespace(s) { - return s.split(/\s+/); - } - function nonword(s) { - return s.split(/\W+/); - } - function getObjTokenizer(tokenizer) { - return function setKey(key) { - return function tokenize(o) { - return tokenizer(o[key]); - }; - }; - } - }(); - var LruCache = function() { - function LruCache(maxSize) { - this.maxSize = maxSize || 100; - this.size = 0; - this.hash = {}; - this.list = new List(); - } - _.mixin(LruCache.prototype, { - set: function set(key, val) { - var tailItem = this.list.tail, node; - if (this.size >= this.maxSize) { - this.list.remove(tailItem); - delete this.hash[tailItem.key]; - } - if (node = this.hash[key]) { - node.val = val; - this.list.moveToFront(node); - } else { - node = new Node(key, val); - this.list.add(node); - this.hash[key] = node; - this.size++; - } - }, - get: function get(key) { - var node = this.hash[key]; - if (node) { - this.list.moveToFront(node); - return node.val; - } - } - }); - function List() { - this.head = this.tail = null; - } - _.mixin(List.prototype, { - add: function add(node) { - if (this.head) { - node.next = this.head; - this.head.prev = node; - } - this.head = node; - this.tail = this.tail || node; - }, - remove: function remove(node) { - node.prev ? node.prev.next = node.next : this.head = node.next; - node.next ? node.next.prev = node.prev : this.tail = node.prev; - }, - moveToFront: function(node) { - this.remove(node); - this.add(node); - } - }); - function Node(key, val) { - this.key = key; - this.val = val; - this.prev = this.next = null; - } - return LruCache; - }(); - var PersistentStorage = function() { - var ls, methods; - try { - ls = window.localStorage; - ls.setItem("~~~", "!"); - ls.removeItem("~~~"); - } catch (err) { - ls = null; - } - function PersistentStorage(namespace) { - this.prefix = [ "__", namespace, "__" ].join(""); - this.ttlKey = "__ttl__"; - this.keyMatcher = new RegExp("^" + this.prefix); - } - if (ls && window.JSON) { - methods = { - _prefix: function(key) { - return this.prefix + key; - }, - _ttlKey: function(key) { - return this._prefix(key) + this.ttlKey; - }, - get: function(key) { - if (this.isExpired(key)) { - this.remove(key); - } - return decode(ls.getItem(this._prefix(key))); - }, - set: function(key, val, ttl) { - if (_.isNumber(ttl)) { - ls.setItem(this._ttlKey(key), encode(now() + ttl)); - } else { - ls.removeItem(this._ttlKey(key)); - } - return ls.setItem(this._prefix(key), encode(val)); - }, - remove: function(key) { - ls.removeItem(this._ttlKey(key)); - ls.removeItem(this._prefix(key)); - return this; - }, - clear: function() { - var i, key, keys = [], len = ls.length; - for (i = 0; i < len; i++) { - if ((key = ls.key(i)).match(this.keyMatcher)) { - keys.push(key.replace(this.keyMatcher, "")); - } - } - for (i = keys.length; i--; ) { - this.remove(keys[i]); - } - return this; - }, - isExpired: function(key) { - var ttl = decode(ls.getItem(this._ttlKey(key))); - return _.isNumber(ttl) && now() > ttl ? true : false; - } - }; - } else { - methods = { - get: _.noop, - set: _.noop, - remove: _.noop, - clear: _.noop, - isExpired: _.noop - }; - } - _.mixin(PersistentStorage.prototype, methods); - return PersistentStorage; - function now() { - return new Date().getTime(); - } - function encode(val) { - return JSON.stringify(_.isUndefined(val) ? null : val); - } - function decode(val) { - return JSON.parse(val); - } - }(); - var Transport = function() { - var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, requestCache = new LruCache(10); - function Transport(o) { - o = o || {}; - this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax; - this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get; - } - Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { - maxPendingRequests = num; - }; - Transport.resetCache = function clearCache() { - requestCache = new LruCache(10); - }; - _.mixin(Transport.prototype, { - _get: function(url, o, cb) { - var that = this, jqXhr; - if (jqXhr = pendingRequests[url]) { - jqXhr.done(done).fail(fail); - } else if (pendingRequestsCount < maxPendingRequests) { - pendingRequestsCount++; - pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always); - } else { - this.onDeckRequestArgs = [].slice.call(arguments, 0); - } - function done(resp) { - cb && cb(null, resp); - requestCache.set(url, resp); - } - function fail() { - cb && cb(true); - } - function always() { - pendingRequestsCount--; - delete pendingRequests[url]; - if (that.onDeckRequestArgs) { - that._get.apply(that, that.onDeckRequestArgs); - that.onDeckRequestArgs = null; - } - } - }, - get: function(url, o, cb) { - var resp; - if (_.isFunction(o)) { - cb = o; - o = {}; - } - if (resp = requestCache.get(url)) { - _.defer(function() { - cb && cb(null, resp); - }); - } else { - this._get(url, o, cb); - } - return !!resp; - } - }); - return Transport; - function callbackToDeferred(fn) { - return function customSendWrapper(url, o) { - var deferred = $.Deferred(); - fn(url, o, onSuccess, onError); - return deferred; - function onSuccess(resp) { - _.defer(function() { - deferred.resolve(resp); - }); - } - function onError(err) { - _.defer(function() { - deferred.reject(err); - }); - } - }; - } - }(); - var SearchIndex = function() { - function SearchIndex(o) { - o = o || {}; - if (!o.datumTokenizer || !o.queryTokenizer) { - $.error("datumTokenizer and queryTokenizer are both required"); - } - this.datumTokenizer = o.datumTokenizer; - this.queryTokenizer = o.queryTokenizer; - this.reset(); - } - _.mixin(SearchIndex.prototype, { - bootstrap: function bootstrap(o) { - this.datums = o.datums; - this.trie = o.trie; - }, - add: function(data) { - var that = this; - data = _.isArray(data) ? data : [ data ]; - _.each(data, function(datum) { - var id, tokens; - id = that.datums.push(datum) - 1; - tokens = normalizeTokens(that.datumTokenizer(datum)); - _.each(tokens, function(token) { - var node, chars, ch; - node = that.trie; - chars = token.split(""); - while (ch = chars.shift()) { - node = node.children[ch] || (node.children[ch] = newNode()); - node.ids.push(id); - } - }); - }); - }, - get: function get(query) { - var that = this, tokens, matches; - tokens = normalizeTokens(this.queryTokenizer(query)); - _.each(tokens, function(token) { - var node, chars, ch, ids; - if (matches && matches.length === 0) { - return false; - } - node = that.trie; - chars = token.split(""); - while (node && (ch = chars.shift())) { - node = node.children[ch]; - } - if (node && chars.length === 0) { - ids = node.ids.slice(0); - matches = matches ? getIntersection(matches, ids) : ids; - } else { - matches = []; - return false; - } - }); - return matches ? _.map(unique(matches), function(id) { - return that.datums[id]; - }) : []; - }, - reset: function reset() { - this.datums = []; - this.trie = newNode(); - }, - serialize: function serialize() { - return { - datums: this.datums, - trie: this.trie - }; - } - }); - return SearchIndex; - function normalizeTokens(tokens) { - tokens = _.filter(tokens, function(token) { - return !!token; - }); - tokens = _.map(tokens, function(token) { - return token.toLowerCase(); - }); - return tokens; - } - function newNode() { - return { - ids: [], - children: {} - }; - } - function unique(array) { - var seen = {}, uniques = []; - for (var i = 0; i < array.length; i++) { - if (!seen[array[i]]) { - seen[array[i]] = true; - uniques.push(array[i]); - } - } - return uniques; - } - function getIntersection(arrayA, arrayB) { - var ai = 0, bi = 0, intersection = []; - arrayA = arrayA.sort(compare); - arrayB = arrayB.sort(compare); - while (ai < arrayA.length && bi < arrayB.length) { - if (arrayA[ai] < arrayB[bi]) { - ai++; - } else if (arrayA[ai] > arrayB[bi]) { - bi++; - } else { - intersection.push(arrayA[ai]); - ai++; - bi++; - } - } - return intersection; - function compare(a, b) { - return a - b; - } - } - }(); - var oParser = function() { - return { - local: getLocal, - prefetch: getPrefetch, - remote: getRemote - }; - function getLocal(o) { - return o.local || null; - } - function getPrefetch(o) { - var prefetch, defaults; - defaults = { - url: null, - thumbprint: "", - ttl: 24 * 60 * 60 * 1e3, - filter: null, - ajax: {} - }; - if (prefetch = o.prefetch || null) { - prefetch = _.isString(prefetch) ? { - url: prefetch - } : prefetch; - prefetch = _.mixin(defaults, prefetch); - prefetch.thumbprint = VERSION + prefetch.thumbprint; - prefetch.ajax.type = prefetch.ajax.type || "GET"; - prefetch.ajax.dataType = prefetch.ajax.dataType || "json"; - !prefetch.url && $.error("prefetch requires url to be set"); - } - return prefetch; - } - function getRemote(o) { - var remote, defaults; - defaults = { - url: null, - wildcard: "%QUERY", - replace: null, - rateLimitBy: "debounce", - rateLimitWait: 300, - send: null, - filter: null, - ajax: {} - }; - if (remote = o.remote || null) { - remote = _.isString(remote) ? { - url: remote - } : remote; - remote = _.mixin(defaults, remote); - remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait); - remote.ajax.type = remote.ajax.type || "GET"; - remote.ajax.dataType = remote.ajax.dataType || "json"; - delete remote.rateLimitBy; - delete remote.rateLimitWait; - !remote.url && $.error("remote requires url to be set"); - } - return remote; - function byDebounce(wait) { - return function(fn) { - return _.debounce(fn, wait); - }; - } - function byThrottle(wait) { - return function(fn) { - return _.throttle(fn, wait); - }; - } - } - }(); - (function(root) { - var old, keys; - old = root.Bloodhound; - keys = { - data: "data", - protocol: "protocol", - thumbprint: "thumbprint" - }; - root.Bloodhound = Bloodhound; - function Bloodhound(o) { - if (!o || !o.local && !o.prefetch && !o.remote) { - $.error("one of local, prefetch, or remote is required"); - } - this.limit = o.limit || 5; - this.sorter = getSorter(o.sorter); - this.dupDetector = o.dupDetector || ignoreDuplicates; - this.local = oParser.local(o); - this.prefetch = oParser.prefetch(o); - this.remote = oParser.remote(o); - this.cacheKey = this.prefetch ? this.prefetch.cacheKey || this.prefetch.url : null; - this.index = new SearchIndex({ - datumTokenizer: o.datumTokenizer, - queryTokenizer: o.queryTokenizer - }); - this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null; - } - Bloodhound.noConflict = function noConflict() { - root.Bloodhound = old; - return Bloodhound; - }; - Bloodhound.tokenizers = tokenizers; - _.mixin(Bloodhound.prototype, { - _loadPrefetch: function loadPrefetch(o) { - var that = this, serialized, deferred; - if (serialized = this._readFromStorage(o.thumbprint)) { - this.index.bootstrap(serialized); - deferred = $.Deferred().resolve(); - } else { - deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse); - } - return deferred; - function handlePrefetchResponse(resp) { - that.clear(); - that.add(o.filter ? o.filter(resp) : resp); - that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl); - } - }, - _getFromRemote: function getFromRemote(query, cb) { - var that = this, url, uriEncodedQuery; - query = query || ""; - uriEncodedQuery = encodeURIComponent(query); - url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery); - return this.transport.get(url, this.remote.ajax, handleRemoteResponse); - function handleRemoteResponse(err, resp) { - err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp); - } - }, - _saveToStorage: function saveToStorage(data, thumbprint, ttl) { - if (this.storage) { - this.storage.set(keys.data, data, ttl); - this.storage.set(keys.protocol, location.protocol, ttl); - this.storage.set(keys.thumbprint, thumbprint, ttl); - } - }, - _readFromStorage: function readFromStorage(thumbprint) { - var stored = {}, isExpired; - if (this.storage) { - stored.data = this.storage.get(keys.data); - stored.protocol = this.storage.get(keys.protocol); - stored.thumbprint = this.storage.get(keys.thumbprint); - } - isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol; - return stored.data && !isExpired ? stored.data : null; - }, - _initialize: function initialize() { - var that = this, local = this.local, deferred; - deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve(); - local && deferred.done(addLocalToIndex); - this.transport = this.remote ? new Transport(this.remote) : null; - return this.initPromise = deferred.promise(); - function addLocalToIndex() { - that.add(_.isFunction(local) ? local() : local); - } - }, - initialize: function initialize(force) { - return !this.initPromise || force ? this._initialize() : this.initPromise; - }, - add: function add(data) { - this.index.add(data); - }, - get: function get(query, cb) { - var that = this, matches = [], cacheHit = false; - matches = this.index.get(query); - matches = this.sorter(matches).slice(0, this.limit); - if (matches.length < this.limit && this.transport) { - cacheHit = this._getFromRemote(query, returnRemoteMatches); - } - if (!cacheHit) { - (matches.length > 0 || !this.transport) && cb && cb(matches); - } - function returnRemoteMatches(remoteMatches) { - var matchesWithBackfill = matches.slice(0); - _.each(remoteMatches, function(remoteMatch) { - var isDuplicate; - isDuplicate = _.some(matchesWithBackfill, function(match) { - return that.dupDetector(remoteMatch, match); - }); - !isDuplicate && matchesWithBackfill.push(remoteMatch); - return matchesWithBackfill.length < that.limit; - }); - cb && cb(that.sorter(matchesWithBackfill)); - } - }, - clear: function clear() { - this.index.reset(); - }, - clearPrefetchCache: function clearPrefetchCache() { - this.storage && this.storage.clear(); - }, - clearRemoteCache: function clearRemoteCache() { - this.transport && Transport.resetCache(); - }, - ttAdapter: function ttAdapter() { - return _.bind(this.get, this); - } - }); - return Bloodhound; - function getSorter(sortFn) { - return _.isFunction(sortFn) ? sort : noSort; - function sort(array) { - return array.sort(sortFn); - } - function noSort(array) { - return array; - } - } - function ignoreDuplicates() { - return false; - } - })(this); - var html = { - wrapper: '', - dropdown: '', - dataset: '
', - suggestions: '', - suggestion: '
' - }; - var css = { - wrapper: { - position: "relative", - display: "inline-block" - }, - hint: { - position: "absolute", - top: "0", - left: "0", - borderColor: "transparent", - boxShadow: "none" - }, - input: { - position: "relative", - verticalAlign: "top", - backgroundColor: "transparent" - }, - inputWithNoHint: { - position: "relative", - verticalAlign: "top" - }, - dropdown: { - position: "absolute", - top: "100%", - left: "0", - zIndex: "100", - display: "none" - }, - suggestions: { - display: "block" - }, - suggestion: { - whiteSpace: "nowrap", - cursor: "pointer" - }, - suggestionChild: { - whiteSpace: "normal" - }, - ltr: { - left: "0", - right: "auto" - }, - rtl: { - left: "auto", - right: " 0" - } - }; - if (_.isMsie()) { - _.mixin(css.input, { - backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" - }); - } - if (_.isMsie() && _.isMsie() <= 7) { - _.mixin(css.input, { - marginTop: "-1px" - }); - } - var EventBus = function() { - var namespace = "typeahead:"; - function EventBus(o) { - if (!o || !o.el) { - $.error("EventBus initialized without el"); - } - this.$el = $(o.el); - } - _.mixin(EventBus.prototype, { - trigger: function(type) { - var args = [].slice.call(arguments, 1); - this.$el.trigger(namespace + type, args); - } - }); - return EventBus; - }(); - var EventEmitter = function() { - var splitter = /\s+/, nextTick = getNextTick(); - return { - onSync: onSync, - onAsync: onAsync, - off: off, - trigger: trigger - }; - function on(method, types, cb, context) { - var type; - if (!cb) { - return this; - } - types = types.split(splitter); - cb = context ? bindContext(cb, context) : cb; - this._callbacks = this._callbacks || {}; - while (type = types.shift()) { - this._callbacks[type] = this._callbacks[type] || { - sync: [], - async: [] - }; - this._callbacks[type][method].push(cb); - } - return this; - } - function onAsync(types, cb, context) { - return on.call(this, "async", types, cb, context); - } - function onSync(types, cb, context) { - return on.call(this, "sync", types, cb, context); - } - function off(types) { - var type; - if (!this._callbacks) { - return this; - } - types = types.split(splitter); - while (type = types.shift()) { - delete this._callbacks[type]; - } - return this; - } - function trigger(types) { - var type, callbacks, args, syncFlush, asyncFlush; - if (!this._callbacks) { - return this; - } - types = types.split(splitter); - args = [].slice.call(arguments, 1); - while ((type = types.shift()) && (callbacks = this._callbacks[type])) { - syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); - asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); - syncFlush() && nextTick(asyncFlush); - } - return this; - } - function getFlush(callbacks, context, args) { - return flush; - function flush() { - var cancelled; - for (var i = 0; !cancelled && i < callbacks.length; i += 1) { - cancelled = callbacks[i].apply(context, args) === false; - } - return !cancelled; - } - } - function getNextTick() { - var nextTickFn; - if (window.setImmediate) { - nextTickFn = function nextTickSetImmediate(fn) { - setImmediate(function() { - fn(); - }); - }; - } else { - nextTickFn = function nextTickSetTimeout(fn) { - setTimeout(function() { - fn(); - }, 0); - }; - } - return nextTickFn; - } - function bindContext(fn, context) { - return fn.bind ? fn.bind(context) : function() { - fn.apply(context, [].slice.call(arguments, 0)); - }; - } - }(); - var highlight = function(doc) { - var defaults = { - node: null, - pattern: null, - tagName: "strong", - className: null, - wordsOnly: false, - caseSensitive: false - }; - return function hightlight(o) { - var regex; - o = _.mixin({}, defaults, o); - if (!o.node || !o.pattern) { - return; - } - o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; - regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); - traverse(o.node, hightlightTextNode); - function hightlightTextNode(textNode) { - var match, patternNode; - if (match = regex.exec(textNode.data)) { - wrapperNode = doc.createElement(o.tagName); - o.className && (wrapperNode.className = o.className); - patternNode = textNode.splitText(match.index); - patternNode.splitText(match[0].length); - wrapperNode.appendChild(patternNode.cloneNode(true)); - textNode.parentNode.replaceChild(wrapperNode, patternNode); - } - return !!match; - } - function traverse(el, hightlightTextNode) { - var childNode, TEXT_NODE_TYPE = 3; - for (var i = 0; i < el.childNodes.length; i++) { - childNode = el.childNodes[i]; - if (childNode.nodeType === TEXT_NODE_TYPE) { - i += hightlightTextNode(childNode) ? 1 : 0; - } else { - traverse(childNode, hightlightTextNode); - } - } - } - }; - function getRegex(patterns, caseSensitive, wordsOnly) { - var escapedPatterns = [], regexStr; - for (var i = 0; i < patterns.length; i++) { - escapedPatterns.push(_.escapeRegExChars(patterns[i])); - } - regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; - return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); - } - }(window.document); - var Input = function() { - var specialKeyCodeMap; - specialKeyCodeMap = { - 9: "tab", - 27: "esc", - 37: "left", - 39: "right", - 13: "enter", - 38: "up", - 40: "down" - }; - function Input(o) { - var that = this, onBlur, onFocus, onKeydown, onInput; - o = o || {}; - if (!o.input) { - $.error("input is missing"); - } - onBlur = _.bind(this._onBlur, this); - onFocus = _.bind(this._onFocus, this); - onKeydown = _.bind(this._onKeydown, this); - onInput = _.bind(this._onInput, this); - this.$hint = $(o.hint); - this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); - if (this.$hint.length === 0) { - this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; - } - if (!_.isMsie()) { - this.$input.on("input.tt", onInput); - } else { - this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { - if (specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - _.defer(_.bind(that._onInput, that, $e)); - }); - } - this.query = this.$input.val(); - this.$overflowHelper = buildOverflowHelper(this.$input); - } - Input.normalizeQuery = function(str) { - return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - }; - _.mixin(Input.prototype, EventEmitter, { - _onBlur: function onBlur() { - this.resetInputValue(); - this.trigger("blurred"); - }, - _onFocus: function onFocus() { - this.trigger("focused"); - }, - _onKeydown: function onKeydown($e) { - var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; - this._managePreventDefault(keyName, $e); - if (keyName && this._shouldTrigger(keyName, $e)) { - this.trigger(keyName + "Keyed", $e); - } - }, - _onInput: function onInput() { - this._checkInputValue(); - }, - _managePreventDefault: function managePreventDefault(keyName, $e) { - var preventDefault, hintValue, inputValue; - switch (keyName) { - case "tab": - hintValue = this.getHint(); - inputValue = this.getInputValue(); - preventDefault = hintValue && hintValue !== inputValue && !withModifier($e); - break; - - case "up": - case "down": - preventDefault = !withModifier($e); - break; - - default: - preventDefault = false; - } - preventDefault && $e.preventDefault(); - }, - _shouldTrigger: function shouldTrigger(keyName, $e) { - var trigger; - switch (keyName) { - case "tab": - trigger = !withModifier($e); - break; - - default: - trigger = true; - } - return trigger; - }, - _checkInputValue: function checkInputValue() { - var inputValue, areEquivalent, hasDifferentWhitespace; - inputValue = this.getInputValue(); - areEquivalent = areQueriesEquivalent(inputValue, this.query); - hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false; - if (!areEquivalent) { - this.trigger("queryChanged", this.query = inputValue); - } else if (hasDifferentWhitespace) { - this.trigger("whitespaceChanged", this.query); - } - }, - focus: function focus() { - this.$input.focus(); - }, - blur: function blur() { - this.$input.blur(); - }, - getQuery: function getQuery() { - return this.query; - }, - setQuery: function setQuery(query) { - this.query = query; - }, - getInputValue: function getInputValue() { - return this.$input.val(); - }, - setInputValue: function setInputValue(value, silent) { - this.$input.val(value); - silent ? this.clearHint() : this._checkInputValue(); - }, - resetInputValue: function resetInputValue() { - this.setInputValue(this.query, true); - }, - getHint: function getHint() { - return this.$hint.val(); - }, - setHint: function setHint(value) { - this.$hint.val(value); - }, - clearHint: function clearHint() { - this.setHint(""); - }, - clearHintIfInvalid: function clearHintIfInvalid() { - var val, hint, valIsPrefixOfHint, isValid; - val = this.getInputValue(); - hint = this.getHint(); - valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; - isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); - !isValid && this.clearHint(); - }, - getLanguageDirection: function getLanguageDirection() { - return (this.$input.css("direction") || "ltr").toLowerCase(); - }, - hasOverflow: function hasOverflow() { - var constraint = this.$input.width() - 2; - this.$overflowHelper.text(this.getInputValue()); - return this.$overflowHelper.width() >= constraint; - }, - isCursorAtEnd: function() { - var valueLength, selectionStart, range; - valueLength = this.$input.val().length; - selectionStart = this.$input[0].selectionStart; - if (_.isNumber(selectionStart)) { - return selectionStart === valueLength; - } else if (document.selection) { - range = document.selection.createRange(); - range.moveStart("character", -valueLength); - return valueLength === range.text.length; - } - return true; - }, - destroy: function destroy() { - this.$hint.off(".tt"); - this.$input.off(".tt"); - this.$hint = this.$input = this.$overflowHelper = null; - } - }); - return Input; - function buildOverflowHelper($input) { - return $('').css({ - position: "absolute", - visibility: "hidden", - whiteSpace: "pre", - fontFamily: $input.css("font-family"), - fontSize: $input.css("font-size"), - fontStyle: $input.css("font-style"), - fontVariant: $input.css("font-variant"), - fontWeight: $input.css("font-weight"), - wordSpacing: $input.css("word-spacing"), - letterSpacing: $input.css("letter-spacing"), - textIndent: $input.css("text-indent"), - textRendering: $input.css("text-rendering"), - textTransform: $input.css("text-transform") - }).insertAfter($input); - } - function areQueriesEquivalent(a, b) { - return Input.normalizeQuery(a) === Input.normalizeQuery(b); - } - function withModifier($e) { - return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; - } - }(); - var Dataset = function() { - var datasetKey = "ttDataset", valueKey = "ttValue", datumKey = "ttDatum"; - function Dataset(o) { - o = o || {}; - o.templates = o.templates || {}; - if (!o.source) { - $.error("missing source"); - } - if (o.name && !isValidName(o.name)) { - $.error("invalid dataset name: " + o.name); - } - this.query = null; - this.highlight = !!o.highlight; - this.name = o.name || _.getUniqueId(); - this.source = o.source; - this.displayFn = getDisplayFn(o.display || o.displayKey); - this.templates = getTemplates(o.templates, this.displayFn); - this.$el = $(html.dataset.replace("%CLASS%", this.name)); - } - Dataset.extractDatasetName = function extractDatasetName(el) { - return $(el).data(datasetKey); - }; - Dataset.extractValue = function extractDatum(el) { - return $(el).data(valueKey); - }; - Dataset.extractDatum = function extractDatum(el) { - return $(el).data(datumKey); - }; - _.mixin(Dataset.prototype, EventEmitter, { - _render: function render(query, suggestions) { - if (!this.$el) { - return; - } - var that = this, hasSuggestions; - this.$el.empty(); - hasSuggestions = suggestions && suggestions.length; - if (!hasSuggestions && this.templates.empty) { - this.$el.html(getEmptyHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); - } else if (hasSuggestions) { - this.$el.html(getSuggestionsHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); - } - this.trigger("rendered"); - function getEmptyHtml() { - return that.templates.empty({ - query: query, - isEmpty: true - }); - } - function getSuggestionsHtml() { - var $suggestions, nodes; - $suggestions = $(html.suggestions).css(css.suggestions); - nodes = _.map(suggestions, getSuggestionNode); - $suggestions.append.apply($suggestions, nodes); - that.highlight && highlight({ - node: $suggestions[0], - pattern: query - }); - return $suggestions; - function getSuggestionNode(suggestion) { - var $el; - $el = $(html.suggestion).append(that.templates.suggestion(suggestion)).data(datasetKey, that.name).data(valueKey, that.displayFn(suggestion)).data(datumKey, suggestion); - $el.children().each(function() { - $(this).css(css.suggestionChild); - }); - return $el; - } - } - function getHeaderHtml() { - return that.templates.header({ - query: query, - isEmpty: !hasSuggestions - }); - } - function getFooterHtml() { - return that.templates.footer({ - query: query, - isEmpty: !hasSuggestions - }); - } - }, - getRoot: function getRoot() { - return this.$el; - }, - update: function update(query) { - var that = this; - this.query = query; - this.canceled = false; - this.source(query, render); - function render(suggestions) { - if (!that.canceled && query === that.query) { - that._render(query, suggestions); - } - } - }, - cancel: function cancel() { - this.canceled = true; - }, - clear: function clear() { - this.cancel(); - this.$el.empty(); - this.trigger("rendered"); - }, - isEmpty: function isEmpty() { - return this.$el.is(":empty"); - }, - destroy: function destroy() { - this.$el = null; - } - }); - return Dataset; - function getDisplayFn(display) { - display = display || "value"; - return _.isFunction(display) ? display : displayFn; - function displayFn(obj) { - return obj[display]; - } - } - function getTemplates(templates, displayFn) { - return { - empty: templates.empty && _.templatify(templates.empty), - header: templates.header && _.templatify(templates.header), - footer: templates.footer && _.templatify(templates.footer), - suggestion: templates.suggestion || suggestionTemplate - }; - function suggestionTemplate(context) { - return "

" + displayFn(context) + "

"; - } - } - function isValidName(str) { - return /^[_a-zA-Z0-9-]+$/.test(str); - } - }(); - var Dropdown = function() { - function Dropdown(o) { - var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave; - o = o || {}; - if (!o.menu) { - $.error("menu is required"); - } - this.isOpen = false; - this.isEmpty = true; - this.datasets = _.map(o.datasets, initializeDataset); - onSuggestionClick = _.bind(this._onSuggestionClick, this); - onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this); - onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this); - this.$menu = $(o.menu).on("click.tt", ".tt-suggestion", onSuggestionClick).on("mouseenter.tt", ".tt-suggestion", onSuggestionMouseEnter).on("mouseleave.tt", ".tt-suggestion", onSuggestionMouseLeave); - _.each(this.datasets, function(dataset) { - that.$menu.append(dataset.getRoot()); - dataset.onSync("rendered", that._onRendered, that); - }); - } - _.mixin(Dropdown.prototype, EventEmitter, { - _onSuggestionClick: function onSuggestionClick($e) { - this.trigger("suggestionClicked", $($e.currentTarget)); - }, - _onSuggestionMouseEnter: function onSuggestionMouseEnter($e) { - this._removeCursor(); - this._setCursor($($e.currentTarget), true); - }, - _onSuggestionMouseLeave: function onSuggestionMouseLeave() { - this._removeCursor(); - }, - _onRendered: function onRendered() { - this.isEmpty = _.every(this.datasets, isDatasetEmpty); - this.isEmpty ? this._hide() : this.isOpen && this._show(); - this.trigger("datasetRendered"); - function isDatasetEmpty(dataset) { - return dataset.isEmpty(); - } - }, - _hide: function() { - this.$menu.hide(); - }, - _show: function() { - this.$menu.css("display", "block"); - }, - _getSuggestions: function getSuggestions() { - return this.$menu.find(".tt-suggestion"); - }, - _getCursor: function getCursor() { - return this.$menu.find(".tt-cursor").first(); - }, - _setCursor: function setCursor($el, silent) { - $el.first().addClass("tt-cursor"); - !silent && this.trigger("cursorMoved"); - }, - _removeCursor: function removeCursor() { - this._getCursor().removeClass("tt-cursor"); - }, - _moveCursor: function moveCursor(increment) { - var $suggestions, $oldCursor, newCursorIndex, $newCursor; - if (!this.isOpen) { - return; - } - $oldCursor = this._getCursor(); - $suggestions = this._getSuggestions(); - this._removeCursor(); - newCursorIndex = $suggestions.index($oldCursor) + increment; - newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1; - if (newCursorIndex === -1) { - this.trigger("cursorRemoved"); - return; - } else if (newCursorIndex < -1) { - newCursorIndex = $suggestions.length - 1; - } - this._setCursor($newCursor = $suggestions.eq(newCursorIndex)); - this._ensureVisible($newCursor); - }, - _ensureVisible: function ensureVisible($el) { - var elTop, elBottom, menuScrollTop, menuHeight; - elTop = $el.position().top; - elBottom = elTop + $el.outerHeight(true); - menuScrollTop = this.$menu.scrollTop(); - menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10); - if (elTop < 0) { - this.$menu.scrollTop(menuScrollTop + elTop); - } else if (menuHeight < elBottom) { - this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); - } - }, - close: function close() { - if (this.isOpen) { - this.isOpen = false; - this._removeCursor(); - this._hide(); - this.trigger("closed"); - } - }, - open: function open() { - if (!this.isOpen) { - this.isOpen = true; - !this.isEmpty && this._show(); - this.trigger("opened"); - } - }, - setLanguageDirection: function setLanguageDirection(dir) { - this.$menu.css(dir === "ltr" ? css.ltr : css.rtl); - }, - moveCursorUp: function moveCursorUp() { - this._moveCursor(-1); - }, - moveCursorDown: function moveCursorDown() { - this._moveCursor(+1); - }, - getDatumForSuggestion: function getDatumForSuggestion($el) { - var datum = null; - if ($el.length) { - datum = { - raw: Dataset.extractDatum($el), - value: Dataset.extractValue($el), - datasetName: Dataset.extractDatasetName($el) - }; - } - return datum; - }, - getDatumForCursor: function getDatumForCursor() { - return this.getDatumForSuggestion(this._getCursor().first()); - }, - getDatumForTopSuggestion: function getDatumForTopSuggestion() { - return this.getDatumForSuggestion(this._getSuggestions().first()); - }, - update: function update(query) { - _.each(this.datasets, updateDataset); - function updateDataset(dataset) { - dataset.update(query); - } - }, - empty: function empty() { - _.each(this.datasets, clearDataset); - this.isEmpty = true; - function clearDataset(dataset) { - dataset.clear(); - } - }, - isVisible: function isVisible() { - return this.isOpen && !this.isEmpty; - }, - destroy: function destroy() { - this.$menu.off(".tt"); - this.$menu = null; - _.each(this.datasets, destroyDataset); - function destroyDataset(dataset) { - dataset.destroy(); - } - } - }); - return Dropdown; - function initializeDataset(oDataset) { - return new Dataset(oDataset); - } - }(); - var Typeahead = function() { - var attrsKey = "ttAttrs"; - function Typeahead(o) { - var $menu, $input, $hint; - o = o || {}; - if (!o.input) { - $.error("missing input"); - } - this.isActivated = false; - this.autoselect = !!o.autoselect; - this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; - this.$node = buildDomStructure(o.input, o.withHint); - $menu = this.$node.find(".tt-dropdown-menu"); - $input = this.$node.find(".tt-input"); - $hint = this.$node.find(".tt-hint"); - $input.on("blur.tt", function($e) { - var active, isActive, hasActive; - active = document.activeElement; - isActive = $menu.is(active); - hasActive = $menu.has(active).length > 0; - if (_.isMsie() && (isActive || hasActive)) { - $e.preventDefault(); - $e.stopImmediatePropagation(); - _.defer(function() { - $input.focus(); - }); - } - }); - $menu.on("mousedown.tt", function($e) { - $e.preventDefault(); - }); - this.eventBus = o.eventBus || new EventBus({ - el: $input - }); - this.dropdown = new Dropdown({ - menu: $menu, - datasets: o.datasets - }).onSync("suggestionClicked", this._onSuggestionClicked, this).onSync("cursorMoved", this._onCursorMoved, this).onSync("cursorRemoved", this._onCursorRemoved, this).onSync("opened", this._onOpened, this).onSync("closed", this._onClosed, this).onAsync("datasetRendered", this._onDatasetRendered, this); - this.input = new Input({ - input: $input, - hint: $hint - }).onSync("focused", this._onFocused, this).onSync("blurred", this._onBlurred, this).onSync("enterKeyed", this._onEnterKeyed, this).onSync("tabKeyed", this._onTabKeyed, this).onSync("escKeyed", this._onEscKeyed, this).onSync("upKeyed", this._onUpKeyed, this).onSync("downKeyed", this._onDownKeyed, this).onSync("leftKeyed", this._onLeftKeyed, this).onSync("rightKeyed", this._onRightKeyed, this).onSync("queryChanged", this._onQueryChanged, this).onSync("whitespaceChanged", this._onWhitespaceChanged, this); - this._setLanguageDirection(); - } - _.mixin(Typeahead.prototype, { - _onSuggestionClicked: function onSuggestionClicked(type, $el) { - var datum; - if (datum = this.dropdown.getDatumForSuggestion($el)) { - this._select(datum); - } - }, - _onCursorMoved: function onCursorMoved() { - var datum = this.dropdown.getDatumForCursor(); - this.input.setInputValue(datum.value, true); - this.eventBus.trigger("cursorchanged", datum.raw, datum.datasetName); - }, - _onCursorRemoved: function onCursorRemoved() { - this.input.resetInputValue(); - this._updateHint(); - }, - _onDatasetRendered: function onDatasetRendered() { - this._updateHint(); - }, - _onOpened: function onOpened() { - this._updateHint(); - this.eventBus.trigger("opened"); - }, - _onClosed: function onClosed() { - this.input.clearHint(); - this.eventBus.trigger("closed"); - }, - _onFocused: function onFocused() { - this.isActivated = true; - this.dropdown.open(); - }, - _onBlurred: function onBlurred() { - this.isActivated = false; - this.dropdown.empty(); - this.dropdown.close(); - this.setVal("", true); //LM - }, - _onEnterKeyed: function onEnterKeyed(type, $e) { - var cursorDatum, topSuggestionDatum; - cursorDatum = this.dropdown.getDatumForCursor(); - topSuggestionDatum = this.dropdown.getDatumForTopSuggestion(); - if (cursorDatum) { - this._select(cursorDatum); - $e.preventDefault(); - } else if (this.autoselect && topSuggestionDatum) { - this._select(topSuggestionDatum); - $e.preventDefault(); - } - }, - _onTabKeyed: function onTabKeyed(type, $e) { - var datum; - if (datum = this.dropdown.getDatumForCursor()) { - this._select(datum); - $e.preventDefault(); - } else { - this._autocomplete(true); - } - }, - _onEscKeyed: function onEscKeyed() { - this.dropdown.close(); - this.input.resetInputValue(); - }, - _onUpKeyed: function onUpKeyed() { - var query = this.input.getQuery(); - this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorUp(); - this.dropdown.open(); - }, - _onDownKeyed: function onDownKeyed() { - var query = this.input.getQuery(); - this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorDown(); - this.dropdown.open(); - }, - _onLeftKeyed: function onLeftKeyed() { - this.dir === "rtl" && this._autocomplete(); - }, - _onRightKeyed: function onRightKeyed() { - this.dir === "ltr" && this._autocomplete(); - }, - _onQueryChanged: function onQueryChanged(e, query) { - this.input.clearHintIfInvalid(); - query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.empty(); - this.dropdown.open(); - this._setLanguageDirection(); - }, - _onWhitespaceChanged: function onWhitespaceChanged() { - this._updateHint(); - this.dropdown.open(); - }, - _setLanguageDirection: function setLanguageDirection() { - var dir; - if (this.dir !== (dir = this.input.getLanguageDirection())) { - this.dir = dir; - this.$node.css("direction", dir); - this.dropdown.setLanguageDirection(dir); - } - }, - _updateHint: function updateHint() { - var datum, val, query, escapedQuery, frontMatchRegEx, match; - datum = this.dropdown.getDatumForTopSuggestion(); - if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) { - val = this.input.getInputValue(); - query = Input.normalizeQuery(val); - escapedQuery = _.escapeRegExChars(query); - frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); - match = frontMatchRegEx.exec(datum.value); - match ? this.input.setHint(val + match[1]) : this.input.clearHint(); - } else { - this.input.clearHint(); - } - }, - _autocomplete: function autocomplete(laxCursor) { - var hint, query, isCursorAtEnd, datum; - hint = this.input.getHint(); - query = this.input.getQuery(); - isCursorAtEnd = laxCursor || this.input.isCursorAtEnd(); - if (hint && query !== hint && isCursorAtEnd) { - datum = this.dropdown.getDatumForTopSuggestion(); - datum && this.input.setInputValue(datum.value); - this.eventBus.trigger("autocompleted", datum.raw, datum.datasetName); - } - }, - _select: function select(datum) { - this.input.setQuery(datum.value); - this.input.setInputValue(datum.value, true); - this._setLanguageDirection(); - this.eventBus.trigger("selected", datum.raw, datum.datasetName); - this.dropdown.close(); - _.defer(_.bind(this.dropdown.empty, this.dropdown)); - }, - open: function open() { - this.dropdown.open(); - }, - close: function close() { - this.dropdown.close(); - }, - setVal: function setVal(val) { - if (this.isActivated) { - this.input.setInputValue(val); - } else { - this.input.setQuery(val); - this.input.setInputValue(val, true); - } - this._setLanguageDirection(); - }, - getVal: function getVal() { - return this.input.getQuery(); - }, - destroy: function destroy() { - this.input.destroy(); - this.dropdown.destroy(); - destroyDomStructure(this.$node); - this.$node = null; - } - }); - return Typeahead; - function buildDomStructure(input, withHint) { - var $input, $wrapper, $dropdown, $hint; - $input = $(input); - $wrapper = $(html.wrapper).css(css.wrapper); - $dropdown = $(html.dropdown).css(css.dropdown); - $hint = $input.clone().css(css.hint).css(getBackgroundStyles($input)); - $hint.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder").prop("disabled", true).attr({ - autocomplete: "off", - spellcheck: "false" - }); - $input.data(attrsKey, { - dir: $input.attr("dir"), - autocomplete: $input.attr("autocomplete"), - spellcheck: $input.attr("spellcheck"), - style: $input.attr("style") - }); - $input.addClass("tt-input").attr({ - autocomplete: "off", - spellcheck: false - }).css(withHint ? css.input : css.inputWithNoHint); - try { - !$input.attr("dir") && $input.attr("dir", "auto"); - } catch (e) {} - return $input.wrap($wrapper).parent().prepend(withHint ? $hint : null).append($dropdown); - } - function getBackgroundStyles($el) { - return { - backgroundAttachment: $el.css("background-attachment"), - backgroundClip: $el.css("background-clip"), - backgroundColor: $el.css("background-color"), - backgroundImage: $el.css("background-image"), - backgroundOrigin: $el.css("background-origin"), - backgroundPosition: $el.css("background-position"), - backgroundRepeat: $el.css("background-repeat"), - backgroundSize: $el.css("background-size") - }; - } - function destroyDomStructure($node) { - var $input = $node.find(".tt-input"); - _.each($input.data(attrsKey), function(val, key) { - _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); - }); - $input.detach().removeData(attrsKey).removeClass("tt-input").insertAfter($node); - $node.remove(); - } - }(); - (function() { - var old, typeaheadKey, methods; - old = $.fn.typeahead; - typeaheadKey = "ttTypeahead"; - methods = { - initialize: function initialize(o, datasets) { - datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); - o = o || {}; - return this.each(attach); - function attach() { - var $input = $(this), eventBus, typeahead; - _.each(datasets, function(d) { - d.highlight = !!o.highlight; - }); - typeahead = new Typeahead({ - input: $input, - eventBus: eventBus = new EventBus({ - el: $input - }), - withHint: _.isUndefined(o.hint) ? true : !!o.hint, - minLength: o.minLength, - autoselect: o.autoselect, - datasets: datasets - }); - $input.data(typeaheadKey, typeahead); - } - }, - open: function open() { - return this.each(openTypeahead); - function openTypeahead() { - var $input = $(this), typeahead; - if (typeahead = $input.data(typeaheadKey)) { - typeahead.open(); - } - } - }, - close: function close() { - return this.each(closeTypeahead); - function closeTypeahead() { - var $input = $(this), typeahead; - if (typeahead = $input.data(typeaheadKey)) { - typeahead.close(); - } - } - }, - val: function val(newVal) { - return !arguments.length ? getVal(this.first()) : this.each(setVal); - function setVal() { - var $input = $(this), typeahead; - if (typeahead = $input.data(typeaheadKey)) { - typeahead.setVal(newVal); - } - } - function getVal($input) { - var typeahead, query; - if (typeahead = $input.data(typeaheadKey)) { - query = typeahead.getVal(); - } - return query; - } - }, - destroy: function destroy() { - return this.each(unattach); - function unattach() { - var $input = $(this), typeahead; - if (typeahead = $input.data(typeaheadKey)) { - typeahead.destroy(); - $input.removeData(typeaheadKey); - } - } - } - }; - $.fn.typeahead = function(method) { - if (methods[method]) { - return methods[method].apply(this, [].slice.call(arguments, 1)); - } else { - return methods.initialize.apply(this, arguments); - } - }; - $.fn.typeahead.noConflict = function noConflict() { - $.fn.typeahead = old; - return this; - }; - })(); - - - -//})(window.jQuery); - - -}); -define('searchView',[ - 'App', - // Templates - 'text!tpl/search.html', - 'text!tpl/search_suggestion.html', - // Tools - 'typeahead' -], function(App, searchTpl, suggestionTpl) { - - var searchView = Backbone.View.extend({ - el: '#search', - /** - * Init. - */ - init: function() { - var tpl = _.template(searchTpl); - var className = 'form-control input-lg'; - var placeholder = 'Search reference'; - this.searchHtml = tpl({ - 'placeholder': placeholder, - 'className': className - }); - this.items = App.classes.concat(App.allItems); - - return this; - }, - /** - * Render input field with Typehead activated. - */ - render: function() { - // Append the view to the dom - this.$el.append(this.searchHtml); - - // Render Typeahead - var $searchInput = this.$el.find('input[type=text]'); - this.typeaheadRender($searchInput); - this.typeaheadEvents($searchInput); - - return this; - }, - /** - * Apply Twitter Typeahead to the search input field. - * @param {jquery} $input - */ - typeaheadRender: function($input) { - var self = this; - $input.typeahead(null, { - 'displayKey': 'name', - 'minLength': 2, - //'highlight': true, - 'source': self.substringMatcher(this.items), - 'templates': { - 'empty': '

Unable to find any item that match the current query

', - 'suggestion': _.template(suggestionTpl) - } - }); - }, - /** - * Setup typeahead custom events (item selected). - */ - typeaheadEvents: function($input) { - var self = this; - $input.on('typeahead:selected', function(e, item, datasetName) { - var selectedItem = self.items[item.idx]; - select(selectedItem); - }); - $input.on('keydown', function(e) { - if (e.which === 13) { // enter - var txt = $input.val(); - var f = _.find(self.items, function(it) { return it.name == txt; }); - if (f) { - select(f); - } - } else if (e.which === 27) { - $input.blur(); - } - }); - - function select(selectedItem) { - var hash = App.router.getHash(selectedItem);// - App.router.navigate(hash, {'trigger': true}); - $('#item').focus(); - } - }, - /** - * substringMatcher function for Typehead (search for strings in an array). - * @param {array} array - * @returns {Function} - */ - substringMatcher: function(array) { - return function findMatches(query, callback) { - var matches = [], substrRegex, arrayLength = array.length; - - // regex used to determine if a string contains the substring `query` - substrRegex = new RegExp(query, 'i'); - - // iterate through the pool of strings and for any string that - // contains the substring `query`, add it to the `matches` array - for (var i=0; i < arrayLength; i++) { - var item = array[i]; - if (substrRegex.test(item.name)) { - // typeahead expects suggestions to be a js object - matches.push({ - 'itemtype': item.itemtype, - 'name': item.name, - 'className': item.class, - 'is_constructor': !!item.is_constructor, - 'final': item.final, - 'idx': i - }); - } - } - - callback(matches); - }; - } - - }); - - return searchView; - -}); - - -define('text!tpl/list.html',[],function () { return '<% _.each(groups, function(group){ %>\n
\n

<%=group.name%>

\n
\n <% _.each(group.subgroups, function(subgroup, ind) { %>\n
\n <% if (subgroup.name !== \'0\') { %>\n

<%=subgroup.name%>

\n <% } %>\n \n
\n <% }); %>\n
\n
\n<% }); %>\n';}); - -define('listView',[ - 'App', - // Templates - 'text!tpl/list.html' -], function (App, listTpl) { - var striptags = function(html) { - var div = document.createElement('div'); - div.innerHTML = html; - return div.textContent; - }; - - var listView = Backbone.View.extend({ - el: '#list', - events: {}, - /** - * Init. - */ - init: function () { - this.listTpl = _.template(listTpl); - - return this; - }, - /** - * Render the list. - */ - render: function (items, listCollection) { - if (items && listCollection) { - var self = this; - - // Render items and group them by module - // module === group - this.groups = {}; - _.each(items, function (item, i) { - - if (!item.private && item.file.indexOf('addons') === -1) { //addons don't get displayed on main page - - var group = item.module || '_'; - var subgroup = item.submodule || '_'; - if (group === subgroup) { - subgroup = '0'; - } - var hash = App.router.getHash(item); - - // fixes broken links for #/p5/> and #/p5/>= - item.hash = item.hash.replace('>', '>'); - - // Create a group list - if (!self.groups[group]) { - self.groups[group] = { - name: group.replace('_', ' '), - subgroups: {} - }; - } - - // Create a subgroup list - if (!self.groups[group].subgroups[subgroup]) { - self.groups[group].subgroups[subgroup] = { - name: subgroup.replace('_', ' '), - items: [] - }; - } - - // hide the un-interesting constants - if (group === 'Constants' && !item.example) - return; - - if (item.class === 'p5') { - - self.groups[group].subgroups[subgroup].items.push(item); - - } else { - - var found = _.find(self.groups[group].subgroups[subgroup].items, - function(i){ return i.name == item.class; }); - - if (!found) { - - // FIX TO INVISIBLE OBJECTS: DH (see also router.js) - var ind = hash.lastIndexOf('/'); - hash = item.hash.substring(0, ind).replace('p5/','p5.'); - self.groups[group].subgroups[subgroup].items.push({ - name: item.class, - hash: hash - }); - } - - } - } - }); - - // Put the
  • items html into the list
      - var listHtml = self.listTpl({ - 'striptags': striptags, - 'title': self.capitalizeFirst(listCollection), - 'groups': self.groups, - 'listCollection': listCollection - }); - - // Render the view - this.$el.html(listHtml); - } - - var renderEvent = new Event('reference-rendered'); - window.dispatchEvent(renderEvent); - - return this; - }, - /** - * Show a list of items. - * @param {array} items Array of item objects. - * @returns {object} This view. - */ - show: function (listGroup) { - if (App[listGroup]) { - this.render(App[listGroup], listGroup); - } - App.pageView.hideContentViews(); - - this.$el.show(); - - return this; - }, - /** - * Helper method to capitalize the first letter of a string - * @param {string} str - * @returns {string} Returns the string. - */ - capitalizeFirst: function (str) { - return str.substr(0, 1).toUpperCase() + str.substr(1); - } - - - - }); - - return listView; - -}); - - -define('text!tpl/item.html',[],function () { return '

      <%=item.name%><% if (item.isMethod) { %>()<% } %>

      \n\n<% if (item.example) { %>\n
      \n

      Examples

      \n\n
      \n <% _.each(item.example, function(example, i){ %>\n <%= example %>\n <% }); %>\n
      \n
      \n<% } %>\n\n
      \n \n

      Description

      \n\n <% if (item.deprecated) { %>\n

      \n Deprecated: <%=item.name%><% if (item.isMethod) { %>()<% } %> is deprecated and will be removed in a future version of p5. <% if (item.deprecationMessage) { %><%=item.deprecationMessage%><% } %>\n

      \n <% } %>\n \n\n <%= item.description %>\n\n <% if (item.extends) { %>\n

      Extends <%=item.extends%>

      \n <% } %>\n\n <% if (item.module === \'p5.sound\') { %>\n

      This function requires you include the p5.sound library. Add the following into the head of your index.html file:\n

      <script src="path/to/p5.sound.js"></script>
      \n

      \n <% } %>\n\n <% if (item.constRefs) { %>\n

      Used by:\n <%\n var refs = item.constRefs;\n for (var i = 0; i < refs.length; i ++) {\n var ref = refs[i];\n var name = ref;\n if (name.substr(0, 3) === \'p5.\') {\n name = name.substr(3);\n }\n if (i !== 0) {\n if (i == refs.length - 1) {\n %> and <%\n } else {\n %>, <%\n }\n }\n %><%= name %>()<%\n }\n %>\n

      \n <% } %>\n
      \n\n<% if (isConstructor || !isClass) { %>\n\n
      \n

      Syntax

      \n

      \n <% syntaxes.forEach(function(syntax) { %>\n

      <%= syntax %>
      \n <% }) %>\n

      \n
      \n\n\n<% if (item.params) { %>\n
      \n

      Parameters

      \n
        \n <% for (var i=0; i\n <% var p = item.params[i] %>\n
      • \n
        <%=p.name%>
        \n <% if (p.type) { %>\n
        \n <% var type = p.type.replace(/(p5\\.[A-Z][A-Za-z]*)/, \'$1\'); %>\n <%=type%>: <%=p.description%>\n <% if (p.optional) { %> (Optional)<% } %>\n
        \n <% } %>\n
      • \n <% } %>\n
      \n
      \n<% } %>\n\n<% if (item.return && item.return.type) { %>\n
      \n

      Returns

      \n

      <%=item.return.type%>: <%= item.return.description %>

      \n
      \n<% } %>\n\n<% } %>\n';}); - - -define('text!tpl/class.html',[],function () { return '\n<% if (typeof constructor !== \'undefined\') { %>\n
      \n <%=constructor%>\n
      \n<% } %>\n\n<% let fields = _.filter(things, function(item) { return item.itemtype === \'property\' && item.access !== \'private\' }); %>\n<% if (fields.length > 0) { %>\n

      Fields

      \n \n<% } %>\n\n<% let methods = _.filter(things, function(item) { return item.itemtype === \'method\' && item.access !== \'private\' }); %>\n<% if (methods.length > 0) { %>\n

      Methods

      \n \n<% } %>\n';}); - - -define('text!tpl/itemEnd.html',[],function () { return '\n

      \n\n
      \n<% if (item.file && item.line) { %>\nNotice any errors or typos? Please let us know. Please feel free to edit <%= item.file %> and issue a pull request!\n<% } %>\n
      \n\ncreative commons logo\n

      \n';}); - -// 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. - * - *

      - * For a fairly comprehensive set of languages see the - * README - * 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. - *

      - * Usage:

        - *
      1. include this source file in an html page via - * {@code } - *
      2. define style rules. See the example page for examples. - *
      3. mark the {@code
        } and {@code } tags in your source with
        - *    {@code class=prettyprint.}
        - *    You can also use the (html deprecated) {@code } 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 = [JAVA_KEYWORDS,
        -      "as,base,by,checked,decimal,delegate,descending,dynamic,event," +
        -      "fixed,foreach,from,group,implicit,in,internal,into,is,let," +
        -      "lock,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 iff white-space in text nodes should
        -   *     be treated as significant.
        -   */
        -  function numberLines(node, opt_startLineNum, isPreformatted) {
        -    var nocode = /(?:^|\s)nocode(?:\s|$)/;
        -    var lineBreak = /\r\n?|\n/;
        -  
        -    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('ol');
        -    ol.className = 'linenums';
        -    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.
        -      li.className = 'L' + ((i + offset) % 10);
        -      if (!li.firstChild) {
        -        li.appendChild(document.createTextNode('\xA0'));
        -      }
        -      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;
        -            }
        -            if (lineNums) { numberLines(cs, lineNums, preformatted); }
        -
        -            // 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; 
        -    });
        -  }
        -})();
        -
        -define("prettify", function(){});
        -
        -define('itemView',[
        -  'App',
        -  // Templates
        -  'text!tpl/item.html',
        -  'text!tpl/class.html',
        -  'text!tpl/itemEnd.html',
        -  // Tools
        -  'prettify'
        -], function(App, itemTpl, classTpl, endTpl) {
        -  'use strict';
        -
        -  var appVersion = App.project.version || 'master';
        -
        -  var itemView = Backbone.View.extend({
        -    el: '#item',
        -    init: function() {
        -      this.$html = $('html');
        -      this.$body = $('body');
        -      this.$scrollBody = $('html, body'); // hack for Chrome/Firefox scroll
        -
        -      this.tpl = _.template(itemTpl);
        -      this.classTpl = _.template(classTpl);
        -      this.endTpl = _.template(endTpl);
        -
        -      return this;
        -    },
        -    getSyntax: function(isMethod, cleanItem) {
        -      var isConstructor = cleanItem.is_constructor;
        -      var syntax = '';
        -      if (isConstructor) {
        -        syntax += 'new ';
        -      } else if (cleanItem.static && cleanItem.class) {
        -        syntax += cleanItem.class + '.';
        -      }
        -      syntax += cleanItem.name;
        -
        -      if (isMethod || isConstructor) {
        -        syntax += '(';
        -        if (cleanItem.params) {
        -          for (var i = 0; i < cleanItem.params.length; i++) {
        -            var p = cleanItem.params[i];
        -            if (p.optional) {
        -              syntax += '[';
        -            }
        -            syntax += p.name;
        -            if (p.optdefault) {
        -              syntax += '=' + p.optdefault;
        -            }
        -            if (p.optional) {
        -              syntax += ']';
        -            }
        -            if (i !== cleanItem.params.length - 1) {
        -              syntax += ', ';
        -            }
        -          }
        -        }
        -        syntax += ')';
        -      }
        -
        -      return syntax;
        -    },
        -    // Return a list of valid syntaxes across all overloaded versions of
        -    // this item.
        -    //
        -    // For reference, we ultimately want to replicate something like this:
        -    //
        -    // https://processing.org/reference/color_.html
        -    getSyntaxes: function(isMethod, cleanItem) {
        -      var overloads = cleanItem.overloads || [cleanItem];
        -      return overloads.map(this.getSyntax.bind(this, isMethod));
        -    },
        -    render: function(item) {
        -      if (item) {
        -        var itemHtml = '';
        -        var cleanItem = this.clean(item);
        -        var isClass = item.hasOwnProperty('itemtype') ? 0 : 1;
        -        var collectionName = isClass
        -            ? 'Constructor'
        -            : this.capitalizeFirst(cleanItem.itemtype),
        -          isConstructor = cleanItem.is_constructor;
        -        cleanItem.isMethod = collectionName === 'Method';
        -
        -        var syntaxes = this.getSyntaxes(cleanItem.isMethod, cleanItem);
        -
        -        // Set the item header (title)
        -
        -        // Set item contents
        -        if (isClass) {
        -          var constructor = this.tpl({
        -            item: cleanItem,
        -            isClass: true,
        -            isConstructor: isConstructor,
        -            syntaxes: syntaxes
        -          });
        -          cleanItem.constructor = constructor;
        -
        -          var contents = _.find(App.classes, function(c) {
        -            return c.name === cleanItem.name;
        -          });
        -          cleanItem.things = contents.items;
        -
        -          itemHtml = this.classTpl(cleanItem);
        -        } else {
        -          cleanItem.constRefs =
        -            item.module === 'Constants' && App.data.consts[item.name];
        -
        -          itemHtml = this.tpl({
        -            item: cleanItem,
        -            isClass: false,
        -            isConstructor: false,
        -            syntaxes: syntaxes
        -          });
        -        }
        -
        -        itemHtml += this.endTpl({ item: cleanItem, appVersion: appVersion });
        -
        -        // Insert the view in the dom
        -        this.$el.html(itemHtml);
        -
        -        renderCode(cleanItem.name);
        -
        -        // Set the document title based on the item name.
        -        // If it is a method, add parentheses to the name
        -        if (item.itemtype === 'method') {
        -          App.pageView.appendToDocumentTitle(item.name + '()');
        -        } else {
        -          App.pageView.appendToDocumentTitle(item.name);
        -        }
        -
        -        // Hook up alt-text for examples
        -        setTimeout(function() {
        -          var alts = $('.example-content')[0];
        -          if (alts) {
        -            alts = $(alts)
        -              .data('alt')
        -              .split('\n');
        -
        -            var canvases = $('.cnv_div');
        -            for (var j = 0; j < alts.length; j++) {
        -              if (j < canvases.length) {
        -                $(canvases[j]).append(
        -                  '<span class="sr-only">' + alts[j] + '</span>'
        -                );
        -              }
        -            }
        -          }
        -        }, 1000);
        -        Prism.highlightAll();
        -      }
        -
        -      var renderEvent = new Event('reference-rendered');
        -      window.dispatchEvent(renderEvent);
        -
        -      return this;
        -    },
        -    /**
        -     * Clean item properties: url encode properties containing paths.
        -     * @param {object} item The item object.
        -     * @returns {object} Returns the same item object with urlencoded paths.
        -     */
        -    clean: function(item) {
        -      var cleanItem = item;
        -
        -      if (cleanItem.hasOwnProperty('file')) {
        -        cleanItem.urlencodedfile = encodeURIComponent(item.file);
        -      }
        -      return cleanItem;
        -    },
        -    /**
        -     * Show a single item.
        -     * @param {object} item Item object.
        -     * @returns {object} This view.
        -     */
        -    show: function(item) {
        -      if (item) {
        -        this.render(item);
        -      }
        -
        -      App.pageView.hideContentViews();
        -
        -      this.$el.show();
        -
        -      this.scrollTop();
        -      $('#item').focus();
        -      return this;
        -    },
        -    /**
        -     * Show a message if no item is found.
        -     * @returns {object} This view.
        -     */
        -    nothingFound: function() {
        -      this.$el.html(
        -        '<p><br><br>Ouch. I am unable to find any item that match the current query.</p>'
        -      );
        -      App.pageView.hideContentViews();
        -      this.$el.show();
        -
        -      return this;
        -    },
        -    /**
        -     * Scroll to the top of the window with an animation.
        -     */
        -    scrollTop: function() {
        -      // Hack for Chrome/Firefox scroll animation
        -      // Chrome scrolls 'body', Firefox scrolls 'html'
        -      var scroll = this.$body.scrollTop() > 0 || this.$html.scrollTop() > 0;
        -      if (scroll) {
        -        this.$scrollBody.animate({ scrollTop: 0 }, 600);
        -      }
        -    },
        -    /**
        -     * Helper method to capitalize the first letter of a string
        -     * @param {string} str
        -     * @returns {string} Returns the string.
        -     */
        -    capitalizeFirst: function(str) {
        -      return str.substr(0, 1).toUpperCase() + str.substr(1);
        -    }
        -  });
        -
        -  return itemView;
        -});
        -
        -
        -define('text!tpl/menu.html',[],function () { return '<div>\n  <br>\n  <span id="reference-description1">Can\'t find what you\'re looking for? You may want to check out</span>\n  <a href="#/libraries/p5.sound">p5.sound</a>.<br><a href=\'https://p5js.org/offline-reference/p5-reference.zip\' target=_blank><span id="reference-description3">You can also download an offline version of the reference.</span></a>\n</div>\n\n<div id=\'collection-list-categories\'>\n<h2 class="sr-only" id="categories">Categories</h2>\n<% var i=0; %>\n<% var max=Math.floor(groups.length/4); %>\n<% var rem=groups.length%4; %>\n\n<% _.each(groups, function(group){ %>\n  <% var m = rem > 0 ? 1 : 0 %>\n  <% if (i === 0) { %>\n    <ul aria-labelledby="categories">\n    <% } %>\n    <li><a href="#group-<%=group%>"><%=group%></a></li>\n    <% if (i === (max+m-1)) { %>\n    </ul>\n  \t<% rem-- %>\n  \t<% i=0 %>\n  <% } else { %>\n  \t<% i++ %>\n  <% } %>\n<% }); %>\n</div>\n';});
        -
        -define('menuView',[
        -  'App',
        -  'text!tpl/menu.html'
        -], function(App, menuTpl) {
        -
        -  var menuView = Backbone.View.extend({
        -    el: '#collection-list-nav',
        -    /**
        -     * Init.
        -     * @returns {object} This view.
        -     */
        -    init: function() {
        -      this.menuTpl = _.template(menuTpl);
        -      return this;
        -    },
        -    /**
        -     * Render.
        -     * @returns {object} This view.
        -     */
        -    render: function() {
        -
        -      var groups = [];
        -      _.each(App.modules, function (item, i) {
        -        if (!item.is_submodule) {
        -          if (!item.file || item.file.indexOf('addons') === -1) { //addons don't get displayed on main page
        -            groups.push(item.name);
        -          }
        -        }
        -        //}
        -      });
        -
        -      // Sort groups by name A-Z
        -      groups.sort();
        -
        -      var menuHtml = this.menuTpl({
        -        'groups': groups
        -      });
        -
        -      // Render the view
        -      this.$el.html(menuHtml);
        -    },
        -
        -    hide: function() {
        -      this.$el.hide();
        -    },
        -
        -    show: function() {
        -      this.$el.show();
        -    },
        -
        -    /**
        -     * Update the menu.
        -     * @param {string} el The name of the current route.
        -     */
        -    update: function(menuItem) {
        -      //console.log(menuItem);
        -      // this.$menuItems.removeClass('active');
        -      // this.$menuItems.find('a[href=#'+menuItem+']').parent().addClass('active');
        -
        -    }
        -  });
        -
        -  return menuView;
        -
        -});
        -
        -
        -define('text!tpl/library.html',[],function () { return '<h3><%= module.name %> library</h3>\n\n<p><%= module.description %></p>\n\n<div id="library-page" class="reference-group clearfix">  \n\n<% var t = 0; col = 0; %>\n\n<% _.each(groups, function(group){ %>\n  <% if (t == 0) { %> \n    <div class="column_<%=col%>">\n  <% } %>\n  <% if (group.name !== module.name && group.name !== \'p5\') { %>\n    <% if (group.hash) { %> <a href="<%=group.hash%>" <% if (group.module !== module.name) { %>class="core"<% } %>><% } %>  \n    <h4 class="group-name <% if (t == 0) { %> first<%}%>"><%=group.name%></h4>\n    <% if (group.hash) { %> </a><br> <% } %>\n  <% } %>\n  <% _.each(group.items.filter(function(item) {return item.access !== \'private\'}), function(item) { %>\n    <a href="<%=item.hash%>" <% if (item.module !== module.name) { %>class="core"<% } %>><%=item.name%><% if (item.itemtype === \'method\') { %>()<%}%></a><br>\n    <% t++; %>\n  <% }); %>\n  <% if (t >= Math.floor(totalItems/4)) { col++; t = 0; %>\n    </div>\n  <% } %>\n<% }); %>\n</div>\n';});
        -
        -define(
        -  'libraryView',[
        -    'App',
        -    // Templates
        -    'text!tpl/library.html'
        -  ],
        -  function(App, libraryTpl) {
        -    var libraryView = Backbone.View.extend({
        -      el: '#list',
        -      events: {},
        -      /**
        -       * Init.
        -       */
        -      init: function() {
        -        this.libraryTpl = _.template(libraryTpl);
        -
        -        return this;
        -      },
        -      /**
        -       * Render the list.
        -       */
        -      render: function(m, listCollection) {
        -        if (m && listCollection) {
        -          var self = this;
        -
        -          // Render items and group them by module
        -          // module === group
        -          this.groups = {};
        -          _.each(m.items, function(item, i) {
        -            var module = item.module || '_';
        -            var group;
        -            // Override default group with a selected category
        -            // TODO: Overwriting with the first category might not be the best choice
        -            // We might also want to have links for categories
        -            if (item.category && item.category[0]) {
        -              group = item.category[0];
        -              // Populate item.hash
        -              App.router.getHash(item);
        -
        -              // Create a group list without link hash
        -              if (!self.groups[group]) {
        -                self.groups[group] = {
        -                  name: group.replace('_', '&nbsp;'),
        -                  module: module,
        -                  hash: undefined,
        -                  items: []
        -                };
        -              }
        -            } else {
        -              group = item.class || '_';
        -              var hash = App.router.getHash(item);
        -
        -              var ind = hash.lastIndexOf('/');
        -              hash = hash.substring(0, ind);
        -
        -              // Create a group list
        -              if (!self.groups[group]) {
        -                self.groups[group] = {
        -                  name: group.replace('_', '&nbsp;'),
        -                  module: module,
        -                  hash: hash,
        -                  items: []
        -                };
        -              }
        -            }
        -
        -            self.groups[group].items.push(item);
        -          });
        -
        -          // Sort groups by name A-Z
        -          self.groups = _.sortBy(self.groups, this.sortByName);
        -
        -          // Put the <li> items html into the list <ul>
        -          var libraryHtml = self.libraryTpl({
        -            title: self.capitalizeFirst(listCollection),
        -            module: m.module,
        -            totalItems: m.items.length,
        -            groups: self.groups
        -          });
        -
        -          // Render the view
        -          this.$el.html(libraryHtml);
        -        }
        -
        -        return this;
        -      },
        -      /**
        -       * Show a list of items.
        -       * @param {array} items Array of item objects.
        -       * @returns {object} This view.
        -       */
        -      show: function(listGroup) {
        -        if (App[listGroup]) {
        -          this.render(App[listGroup], listGroup);
        -        }
        -        App.pageView.hideContentViews();
        -
        -        this.$el.show();
        -
        -        return this;
        -      },
        -      /**
        -       * Helper method to capitalize the first letter of a string
        -       * @param {string} str
        -       * @returns {string} Returns the string.
        -       */
        -      capitalizeFirst: function(str) {
        -        return str.substr(0, 1).toUpperCase() + str.substr(1);
        -      },
        -      /**
        -       * Sort function (for the Array.prototype.sort() native method): from A to Z.
        -       * @param {string} a
        -       * @param {string} b
        -       * @returns {Array} Returns an array with elements sorted from A to Z.
        -       */
        -      sortAZ: function(a, b) {
        -        return a.innerHTML.toLowerCase() > b.innerHTML.toLowerCase() ? 1 : -1;
        -      },
        -
        -      sortByName: function(a, b) {
        -        if (a.name === 'p5') return -1;
        -        else return 0;
        -      }
        -    });
        -
        -    return libraryView;
        -  }
        -);
        -
        -define('pageView',[
        -  'App',
        -
        -  // Views
        -  'searchView',
        -  'listView',
        -  'itemView',
        -  'menuView',
        -  'libraryView'
        -], function(App, searchView, listView, itemView, menuView, libraryView) {
        -
        -  // Store the original title parts so we can substitue different endings.
        -  var _originalDocumentTitle = window.document.title;
        -
        -  var pageView = Backbone.View.extend({
        -    el: 'body',
        -    /**
        -     * Init.
        -     */
        -    init: function() {
        -      App.$container = $('#container');
        -      App.contentViews = [];
        -
        -      return this;
        -    },
        -    /**
        -     * Render.
        -     */
        -    render: function() {
        -
        -      // Menu view
        -      if (!App.menuView) {
        -        App.menuView = new menuView();
        -        App.menuView.init().render();
        -      }
        -
        -      // Item view
        -      if (!App.itemView) {
        -        App.itemView = new itemView();
        -        App.itemView.init().render();
        -        // Add the item view to the views array
        -        App.contentViews.push(App.itemView);
        -      }
        -
        -      // List view
        -      if (!App.listView) {
        -        App.listView = new listView();
        -        App.listView.init().render();
        -        // Add the list view to the views array
        -        App.contentViews.push(App.listView);
        -      }
        -
        -      // Library view
        -      if (!App.libraryView) {
        -        App.libraryView = new libraryView();
        -        App.libraryView.init().render();
        -        // Add the list view to the views array
        -        App.contentViews.push(App.libraryView);
        -      }
        -
        -      // Search
        -      if (!App.searchView) {
        -        App.searchView = new searchView();
        -        App.searchView.init().render();
        -      }
        -      return this;
        -    },
        -    /**
        -     * Hide item and list views.
        -     * @returns {object} This view.
        -     */
        -    hideContentViews: function() {
        -      _.each(App.contentViews, function(view, i) {
        -        view.$el.hide();
        -      });
        -
        -      return this;
        -    },
        -    /**
        -     * Append the supplied name to the first part of original document title.
        -     * If no name is supplied, the title will reset to the original one.
        -     */
        -    appendToDocumentTitle: function(name){
        -      if(name){
        -        let firstTitlePart = _originalDocumentTitle.split(" | ")[0];
        -        window.document.title = [firstTitlePart, name].join(" | ");
        -      } else {
        -        window.document.title = _originalDocumentTitle;
        -      }
        -    }    
        -  });
        -
        -  return pageView;
        -
        -});
        -
        -define('router',[
        -  'App'
        -], function(App) {
        -
        -  'use strict'; //
        -
        -  var Router = Backbone.Router.extend({
        -
        -    routes: {
        -      '': 'list',
        -      'p5': 'list',
        -      'p5/': 'list',
        -      'classes': 'list',
        -      'search': 'search',
        -      'libraries/:lib': 'library',
        -      ':searchClass(/:searchItem)': 'get'
        -    },
        -    /**
        -     * Whether the json API data was loaded.
        -     */
        -    _initialized: false,
        -    /**
        -     * Initialize the app: load json API data and create searchable arrays.
        -     */
        -    init: function(callback) {
        -      var self = this;
        -      require(['pageView'], function(pageView) {
        -
        -        // If already initialized, move away from here!
        -        if (self._initialized) {
        -          if (callback)
        -            callback();
        -          return;
        -        }
        -
        -        // Update initialization state: must be done now to avoid recursive mess
        -        self._initialized = true;
        -
        -        // Render views
        -        if (!App.pageView) {
        -          App.pageView = new pageView();
        -          App.pageView.init().render();
        -        }
        -
        -        // If a callback is set (a route has already been called), run it
        -        // otherwise, show the default list
        -        if (callback)
        -          callback();
        -        else
        -          self.list();
        -      });
        -    },
        -    /**
        -     * Start route. Simply check if initialized.
        -     */
        -    start: function() {
        -      this.init();
        -    },
        -    /**
        -     * Show item details by searching a class or a class item (method, property or event).
        -     * @param {string} searchClass The class name (mandatory).
        -     * @param {string} searchItem The class item name: can be a method, property or event name.
        -     */
        -    get: function(searchClass, searchItem) {
        -
        -      // if looking for a library page, redirect
        -      if (searchClass === 'p5.sound' && !searchItem) {
        -        window.location.hash = '/libraries/'+searchClass;
        -        return;
        -      }
        -
        -      var self = this;
        -      this.init(function() {
        -        var item = self.getItem(searchClass, searchItem);
        -
        -        App.menuView.hide();
        -
        -        if (item) {
        -          App.itemView.show(item);
        -        } else {
        -          //App.itemView.nothingFound();
        -
        -          self.list();
        -        }
        -
        -        styleCodeLinks();
        -      });
        -    },
        -    /**
        -     * Returns one item object by searching a class or a class item (method, property or event).
        -     * @param {string} searchClass The class name (mandatory).
        -     * @param {string} searchItem The class item name: can be a method, property or event name.
        -     * @returns {object} The item found or undefined if nothing was found.
        -     */
        -    getItem: function(searchClass, searchItem) {
        -      var classes = App.classes,
        -              items = App.allItems,
        -              classesCount = classes.length,
        -              itemsCount = items.length,
        -              className = searchClass ? searchClass.toLowerCase() : undefined,
        -              itemName = searchItem ? searchItem : undefined,
        -              found;
        -
        -      // Only search for a class, if itemName is undefined
        -      if (className && !itemName) {
        -        for (var i = 0; i < classesCount; i++) {
        -          if (classes[i].name.toLowerCase() === className) {
        -            found = classes[i];
        -            _.each(found.items, function(i, idx) {
        -              i.hash = App.router.getHash(i);
        -            });
        -            break;
        -          }
        -        }
        -        // Search for a class item
        -      } else if (className && itemName) {
        -        // Search case sensitively
        -        for (var i = 0; i < itemsCount; i++) {
        -          if (items[i].class.toLowerCase() === className &&
        -            items[i].name === itemName) {
        -            found = items[i];
        -            break;
        -          }
        -        }
        -
        -        // If no match was found, fallback to search case insensitively
        -        if(!found){
        -          for (var i = 0; i < itemsCount; i++) {
        -            if(items[i].class.toLowerCase() === className &&
        -              items[i].name.toLowerCase() === itemName.toLowerCase()){
        -              found = items[i];
        -              break;
        -            }
        -          }
        -        }
        -      }
        -
        -      return found;
        -    },
        -    /**
        -     * List items.
        -     * @param {string} collection The name of the collection to list.
        -     */
        -    list: function(collection) {
        -
        -      collection = 'allItems';
        -
        -      // Make sure collection is valid
        -      if (App.collections.indexOf(collection) < 0) {
        -        return;
        -      }
        -
        -      this.init(function() {
        -        App.menuView.show(collection);
        -        App.menuView.update(collection);
        -        App.listView.show(collection);
        -        styleCodeLinks();
        -      });
        -    },
        -    /**
        -     * Display information for a library.
        -     * @param {string} collection The name of the collection to list.
        -     */
        -    library: function(collection) {
        -      this.init(function() {
        -        App.menuView.hide();
        -        App.libraryView.show(collection.substring(3)); //remove p5.
        -        styleCodeLinks();
        -      });
        -    },
        -    /**
        -     * Close all content views.
        -     */
        -    search: function() {
        -      this.init(function() {
        -        App.menuView.hide();
        -        App.pageView.hideContentViews();
        -      });
        -    },
        -
        -    /**
        -     * Create an hash/url for the item.
        -     * @param {Object} item A class, method, property or event object.
        -     * @returns {String} The hash string, including the '#'.
        -     */
        -     getHash: function(item) {
        -
        -       if (!item.hash) {
        -
        -         // FIX TO INVISIBLE OBJECTS: DH (see also listView.js)
        -
        -         if (item.class) {
        -           var clsFunc = '#/' + item.class + '.' + item.name;
        -           var idx = clsFunc.lastIndexOf('.');
        -           item.hash = clsFunc.substring(0,idx) + '/' + clsFunc.substring(idx+1);
        -         } else {
        -          item.hash = '#/' + item.name;
        -         }
        -       }
        -
        -       return item.hash;
        -    }
        -  });
        -
        -  
        -  function styleCodeLinks() {
        -    var links = document.getElementsByTagName("a");
        -    for (var iLink = 0; iLink < links.length; iLink++) {
        -      var link = links[iLink];
        -      if (link.hash.startsWith('#/p5')) {
        -        link.classList.add('code');
        -      }
        -    }
        -  }
        -
        -
        -  // Get the router
        -  App.router = new Router();
        -
        -  // Start history
        -  Backbone.history.start();
        -
        -  return App.router;
        -
        -});
        -
        -/**
        - * Define global App.
        - */
        -var App = window.App || {};
        -define('App', [],function() {
        -  return App;
        -});
        -
        -/**
        - * Load json API data and start the router.
        - * @param {module} App
        - * @param {module} router
        - */
        -require([
        -  'App',
        -  './documented-method'], function(App, DocumentedMethod) {
        -
        -  // Set collections
        -  App.collections = ['allItems', 'classes', 'events', 'methods', 'properties', 'p5.sound'];
        -
        -  // Get json API data
        -  $.getJSON('data.min.json', function(data) {
        -    App.data = data;
        -    App.classes = [];
        -    App.methods = [];
        -    App.properties = [];
        -    App.events = [];
        -    App.allItems = [];
        -    App.sound = { items: [] };
        -    App.dom = { items: [] };
        -    App.modules = [];
        -    App.project = data.project;
        -
        -
        -    var modules = data.modules;
        -
        -    // Get class items (methods, properties, events)
        -    _.each(modules, function(m, idx, array) {
        -      App.modules.push(m);
        -      if (m.name == "p5.sound") {
        -        App.sound.module = m;
        -      }
        -    });
        -
        -
        -    var items = data.classitems;
        -    var classes = data.classes;
        -
        -    // Get classes
        -    _.each(classes, function(c, idx, array) {
        -      if (!c.private) {
        -        App.classes.push(c);
        -      }
        -    });
        -
        -
        -    // Get class items (methods, properties, events)
        -    _.each(items, function(el, idx, array) {
        -      if (el.itemtype) {
        -        if (el.itemtype === "method") {
        -          el = new DocumentedMethod(el);
        -          App.methods.push(el);
        -          App.allItems.push(el);
        -        } else if (el.itemtype === "property") {
        -          App.properties.push(el);
        -          App.allItems.push(el);
        -        } else if (el.itemtype === "event") {
        -          App.events.push(el);
        -          App.allItems.push(el);
        -        }
        -
        -        // libraries
        -        if (el.module === "p5.sound") {
        -          App.sound.items.push(el);
        -        }
        -      }
        -    });
        -
        -    _.each(App.classes, function(c, idx) {
        -      c.items = _.filter(App.allItems, function(it){ return it.class === c.name; });
        -    });
        -
        -    require(['router']);
        -  });
        -});
        -
        -define("main", function(){});
        -
        -}());
        -//# sourceMappingURL=reference.js.map
        diff --git a/docs/yuidoc-p5-theme/assets/js/reference.js.map b/docs/yuidoc-p5-theme/assets/js/reference.js.map
        deleted file mode 100644
        index abfb0b3794..0000000000
        --- a/docs/yuidoc-p5-theme/assets/js/reference.js.map
        +++ /dev/null
        @@ -1,54 +0,0 @@
        -{
        -  "version": 3,
        -  "sources": [
        -    "../../../config-wrap-start-default.js",
        -    "documented-method.js",
        -    "vendor/require/text.js",
        -    "tpl/search.html!text",
        -    "tpl/search_suggestion.html!text",
        -    "vendor/typeahead-amd/typeahead.bundle.js",
        -    "views/searchView.js",
        -    "tpl/list.html!text",
        -    "views/listView.js",
        -    "tpl/item.html!text",
        -    "tpl/class.html!text",
        -    "tpl/itemEnd.html!text",
        -    "vendor/prettify/prettify.js",
        -    "views/itemView.js",
        -    "tpl/menu.html!text",
        -    "views/menuView.js",
        -    "tpl/library.html!text",
        -    "views/libraryView.js",
        -    "views/pageView.js",
        -    "router.js",
        -    "main.js",
        -    "../../../config-wrap-end-default.js"
        -  ],
        -  "names": [],
        -  "mappings": "AAAA;AACA,ACDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,AC7DA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,ACnYA;AACA;AACA;AACA,ACHA;AACA;AACA;AACA,ACHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,AC9rDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,AC3HA;AACA;AACA;AACA,ACHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,AC3IA;AACA;AACA;AACA,ACHA;AACA;AACA;AACA,ACHA;AACA;AACA;AACA,ACHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,AC1nDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,AC/NA;AACA;AACA;AACA,ACHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,AClEA;AACA;AACA;AACA,ACHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,ACjIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,AChGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,AClOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,ACxFA",
        -  "file": "reference.js",
        -  "sourcesContent": [
        -    "(function () {\n",
        -    "// https://github.com/umdjs/umd/blob/master/templates/returnExports.js\n(function (root, factory) {\n  if (typeof define === 'function' && define.amd) {\n    define('documented-method',[], factory);\n  } else if (typeof module === 'object' && module.exports) {\n    module.exports = factory();\n  } else {\n    root.DocumentedMethod = factory();\n  }\n}(this, function () {\n  function extend(target, src) {\n    Object.keys(src).forEach(function(prop) {\n      target[prop] = src[prop];\n    });\n    return target;\n  }\n\n  function DocumentedMethod(classitem) {\n    extend(this, classitem);\n\n    if (this.overloads) {\n      // Make each overload inherit properties from their parent\n      // classitem.\n      this.overloads = this.overloads.map(function(overload) {\n        return extend(Object.create(this), overload);\n      }, this);\n\n      if (this.params) {\n        throw new Error('params for overloaded methods should be undefined');\n      }\n\n      this.params = this._getMergedParams();\n    }\n  }\n\n  DocumentedMethod.prototype = {\n    // Merge parameters across all overloaded versions of this item.\n    _getMergedParams: function() {\n      var paramNames = {};\n      var params = [];\n\n      this.overloads.forEach(function(overload) {\n        if (!overload.params) {\n          return;\n        }\n        overload.params.forEach(function(param) {\n          if (param.name in paramNames) {\n            return;\n          }\n          paramNames[param.name] = param;\n          params.push(param);\n        });\n      });\n\n      return params;\n    }\n  };\n\n  return DocumentedMethod;\n}));\n\n",
        -    "/**\n * @license RequireJS text 2.0.10 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.\n * Available via the MIT or new BSD license.\n * see: http://github.com/requirejs/text for details\n */\n/*jslint regexp: true */\n/*global require, XMLHttpRequest, ActiveXObject,\n  define, window, process, Packages,\n  java, location, Components, FileUtils */\n\ndefine('text',['module'], function (module) {\n    'use strict';\n\n    var text, fs, Cc, Ci, xpcIsWindows,\n        progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],\n        xmlRegExp = /^\\s*<\\?xml(\\s)+version=[\\'\\\"](\\d)*.(\\d)*[\\'\\\"](\\s)*\\?>/im,\n        bodyRegExp = /<body[^>]*>\\s*([\\s\\S]+)\\s*<\\/body>/im,\n        hasLocation = typeof location !== 'undefined' && location.href,\n        defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\\:/, ''),\n        defaultHostName = hasLocation && location.hostname,\n        defaultPort = hasLocation && (location.port || undefined),\n        buildMap = {},\n        masterConfig = (module.config && module.config()) || {};\n\n    text = {\n        version: '2.0.10',\n\n        strip: function (content) {\n            //Strips <?xml ...?> declarations so that external SVG and XML\n            //documents can be added to a document without worry. Also, if the string\n            //is an HTML document, only the part inside the body tag is returned.\n            if (content) {\n                content = content.replace(xmlRegExp, \"\");\n                var matches = content.match(bodyRegExp);\n                if (matches) {\n                    content = matches[1];\n                }\n            } else {\n                content = \"\";\n            }\n            return content;\n        },\n\n        jsEscape: function (content) {\n            return content.replace(/(['\\\\])/g, '\\\\$1')\n                .replace(/[\\f]/g, \"\\\\f\")\n                .replace(/[\\b]/g, \"\\\\b\")\n                .replace(/[\\n]/g, \"\\\\n\")\n                .replace(/[\\t]/g, \"\\\\t\")\n                .replace(/[\\r]/g, \"\\\\r\")\n                .replace(/[\\u2028]/g, \"\\\\u2028\")\n                .replace(/[\\u2029]/g, \"\\\\u2029\");\n        },\n\n        createXhr: masterConfig.createXhr || function () {\n            //Would love to dump the ActiveX crap in here. Need IE 6 to die first.\n            var xhr, i, progId;\n            if (typeof XMLHttpRequest !== \"undefined\") {\n                return new XMLHttpRequest();\n            } else if (typeof ActiveXObject !== \"undefined\") {\n                for (i = 0; i < 3; i += 1) {\n                    progId = progIds[i];\n                    try {\n                        xhr = new ActiveXObject(progId);\n                    } catch (e) {}\n\n                    if (xhr) {\n                        progIds = [progId];  // so faster next time\n                        break;\n                    }\n                }\n            }\n\n            return xhr;\n        },\n\n        /**\n         * Parses a resource name into its component parts. Resource names\n         * look like: module/name.ext!strip, where the !strip part is\n         * optional.\n         * @param {String} name the resource name\n         * @returns {Object} with properties \"moduleName\", \"ext\" and \"strip\"\n         * where strip is a boolean.\n         */\n        parseName: function (name) {\n            var modName, ext, temp,\n                strip = false,\n                index = name.indexOf(\".\"),\n                isRelative = name.indexOf('./') === 0 ||\n                             name.indexOf('../') === 0;\n\n            if (index !== -1 && (!isRelative || index > 1)) {\n                modName = name.substring(0, index);\n                ext = name.substring(index + 1, name.length);\n            } else {\n                modName = name;\n            }\n\n            temp = ext || modName;\n            index = temp.indexOf(\"!\");\n            if (index !== -1) {\n                //Pull off the strip arg.\n                strip = temp.substring(index + 1) === \"strip\";\n                temp = temp.substring(0, index);\n                if (ext) {\n                    ext = temp;\n                } else {\n                    modName = temp;\n                }\n            }\n\n            return {\n                moduleName: modName,\n                ext: ext,\n                strip: strip\n            };\n        },\n\n        xdRegExp: /^((\\w+)\\:)?\\/\\/([^\\/\\\\]+)/,\n\n        /**\n         * Is an URL on another domain. Only works for browser use, returns\n         * false in non-browser environments. Only used to know if an\n         * optimized .js version of a text resource should be loaded\n         * instead.\n         * @param {String} url\n         * @returns Boolean\n         */\n        useXhr: function (url, protocol, hostname, port) {\n            var uProtocol, uHostName, uPort,\n                match = text.xdRegExp.exec(url);\n            if (!match) {\n                return true;\n            }\n            uProtocol = match[2];\n            uHostName = match[3];\n\n            uHostName = uHostName.split(':');\n            uPort = uHostName[1];\n            uHostName = uHostName[0];\n\n            return (!uProtocol || uProtocol === protocol) &&\n                   (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&\n                   ((!uPort && !uHostName) || uPort === port);\n        },\n\n        finishLoad: function (name, strip, content, onLoad) {\n            content = strip ? text.strip(content) : content;\n            if (masterConfig.isBuild) {\n                buildMap[name] = content;\n            }\n            onLoad(content);\n        },\n\n        load: function (name, req, onLoad, config) {\n            //Name has format: some.module.filext!strip\n            //The strip part is optional.\n            //if strip is present, then that means only get the string contents\n            //inside a body tag in an HTML string. For XML/SVG content it means\n            //removing the <?xml ...?> declarations so the content can be inserted\n            //into the current doc without problems.\n\n            // Do not bother with the work if a build and text will\n            // not be inlined.\n            if (config.isBuild && !config.inlineText) {\n                onLoad();\n                return;\n            }\n\n            masterConfig.isBuild = config.isBuild;\n\n            var parsed = text.parseName(name),\n                nonStripName = parsed.moduleName +\n                    (parsed.ext ? '.' + parsed.ext : ''),\n                url = req.toUrl(nonStripName),\n                useXhr = (masterConfig.useXhr) ||\n                         text.useXhr;\n\n            // Do not load if it is an empty: url\n            if (url.indexOf('empty:') === 0) {\n                onLoad();\n                return;\n            }\n\n            //Load the text. Use XHR if possible and in a browser.\n            if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) {\n                text.get(url, function (content) {\n                    text.finishLoad(name, parsed.strip, content, onLoad);\n                }, function (err) {\n                    if (onLoad.error) {\n                        onLoad.error(err);\n                    }\n                });\n            } else {\n                //Need to fetch the resource across domains. Assume\n                //the resource has been optimized into a JS module. Fetch\n                //by the module name + extension, but do not include the\n                //!strip part to avoid file system issues.\n                req([nonStripName], function (content) {\n                    text.finishLoad(parsed.moduleName + '.' + parsed.ext,\n                                    parsed.strip, content, onLoad);\n                });\n            }\n        },\n\n        write: function (pluginName, moduleName, write, config) {\n            if (buildMap.hasOwnProperty(moduleName)) {\n                var content = text.jsEscape(buildMap[moduleName]);\n                write.asModule(pluginName + \"!\" + moduleName,\n                               \"define(function () { return '\" +\n                                   content +\n                               \"';});\\n\");\n            }\n        },\n\n        writeFile: function (pluginName, moduleName, req, write, config) {\n            var parsed = text.parseName(moduleName),\n                extPart = parsed.ext ? '.' + parsed.ext : '',\n                nonStripName = parsed.moduleName + extPart,\n                //Use a '.js' file name so that it indicates it is a\n                //script that can be loaded across domains.\n                fileName = req.toUrl(parsed.moduleName + extPart) + '.js';\n\n            //Leverage own load() method to load plugin value, but only\n            //write out values that do not have the strip argument,\n            //to avoid any potential issues with ! in file names.\n            text.load(nonStripName, req, function (value) {\n                //Use own write() method to construct full module value.\n                //But need to create shell that translates writeFile's\n                //write() to the right interface.\n                var textWrite = function (contents) {\n                    return write(fileName, contents);\n                };\n                textWrite.asModule = function (moduleName, contents) {\n                    return write.asModule(moduleName, fileName, contents);\n                };\n\n                text.write(pluginName, nonStripName, textWrite, config);\n            }, config);\n        }\n    };\n\n    if (masterConfig.env === 'node' || (!masterConfig.env &&\n            typeof process !== \"undefined\" &&\n            process.versions &&\n            !!process.versions.node &&\n            !process.versions['node-webkit'])) {\n        //Using special require.nodeRequire, something added by r.js.\n        fs = require.nodeRequire('fs');\n\n        text.get = function (url, callback, errback) {\n            try {\n                var file = fs.readFileSync(url, 'utf8');\n                //Remove BOM (Byte Mark Order) from utf8 files if it is there.\n                if (file.indexOf('\\uFEFF') === 0) {\n                    file = file.substring(1);\n                }\n                callback(file);\n            } catch (e) {\n                errback(e);\n            }\n        };\n    } else if (masterConfig.env === 'xhr' || (!masterConfig.env &&\n            text.createXhr())) {\n        text.get = function (url, callback, errback, headers) {\n            var xhr = text.createXhr(), header;\n            xhr.open('GET', url, true);\n\n            //Allow plugins direct access to xhr headers\n            if (headers) {\n                for (header in headers) {\n                    if (headers.hasOwnProperty(header)) {\n                        xhr.setRequestHeader(header.toLowerCase(), headers[header]);\n                    }\n                }\n            }\n\n            //Allow overrides specified in config\n            if (masterConfig.onXhr) {\n                masterConfig.onXhr(xhr, url);\n            }\n\n            xhr.onreadystatechange = function (evt) {\n                var status, err;\n                //Do not explicitly handle errors, those should be\n                //visible via console output in the browser.\n                if (xhr.readyState === 4) {\n                    status = xhr.status;\n                    if (status > 399 && status < 600) {\n                        //An http 4xx or 5xx error. Signal an error.\n                        err = new Error(url + ' HTTP status: ' + status);\n                        err.xhr = xhr;\n                        errback(err);\n                    } else {\n                        callback(xhr.responseText);\n                    }\n\n                    if (masterConfig.onXhrComplete) {\n                        masterConfig.onXhrComplete(xhr, url);\n                    }\n                }\n            };\n            xhr.send(null);\n        };\n    } else if (masterConfig.env === 'rhino' || (!masterConfig.env &&\n            typeof Packages !== 'undefined' && typeof java !== 'undefined')) {\n        //Why Java, why is this so awkward?\n        text.get = function (url, callback) {\n            var stringBuffer, line,\n                encoding = \"utf-8\",\n                file = new java.io.File(url),\n                lineSeparator = java.lang.System.getProperty(\"line.separator\"),\n                input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)),\n                content = '';\n            try {\n                stringBuffer = new java.lang.StringBuffer();\n                line = input.readLine();\n\n                // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324\n                // http://www.unicode.org/faq/utf_bom.html\n\n                // Note that when we use utf-8, the BOM should appear as \"EF BB BF\", but it doesn't due to this bug in the JDK:\n                // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058\n                if (line && line.length() && line.charAt(0) === 0xfeff) {\n                    // Eat the BOM, since we've already found the encoding on this file,\n                    // and we plan to concatenating this buffer with others; the BOM should\n                    // only appear at the top of a file.\n                    line = line.substring(1);\n                }\n\n                if (line !== null) {\n                    stringBuffer.append(line);\n                }\n\n                while ((line = input.readLine()) !== null) {\n                    stringBuffer.append(lineSeparator);\n                    stringBuffer.append(line);\n                }\n                //Make sure we return a JavaScript string and not a Java string.\n                content = String(stringBuffer.toString()); //String\n            } finally {\n                input.close();\n            }\n            callback(content);\n        };\n    } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env &&\n            typeof Components !== 'undefined' && Components.classes &&\n            Components.interfaces)) {\n        //Avert your gaze!\n        Cc = Components.classes,\n        Ci = Components.interfaces;\n        Components.utils['import']('resource://gre/modules/FileUtils.jsm');\n        xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc);\n\n        text.get = function (url, callback) {\n            var inStream, convertStream, fileObj,\n                readData = {};\n\n            if (xpcIsWindows) {\n                url = url.replace(/\\//g, '\\\\');\n            }\n\n            fileObj = new FileUtils.File(url);\n\n            //XPCOM, you so crazy\n            try {\n                inStream = Cc['@mozilla.org/network/file-input-stream;1']\n                           .createInstance(Ci.nsIFileInputStream);\n                inStream.init(fileObj, 1, 0, false);\n\n                convertStream = Cc['@mozilla.org/intl/converter-input-stream;1']\n                                .createInstance(Ci.nsIConverterInputStream);\n                convertStream.init(inStream, \"utf-8\", inStream.available(),\n                Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);\n\n                convertStream.readString(inStream.available(), readData);\n                convertStream.close();\n                inStream.close();\n                callback(readData.value);\n            } catch (e) {\n                throw new Error((fileObj && fileObj.path || '') + ': ' + e);\n            }\n        };\n    }\n    return text;\n});\n\n",
        -    "\ndefine('text!tpl/search.html',[],function () { return '<h2 class=\"sr-only\">search</h2>\\n<form>\\n  <input id=\"search_reference_field\" type=\"text\" class=\"<%=className%>\" value=\"\" placeholder=\"<%=placeholder%>\" aria-label=\"search reference\">\\n  <label class=\"sr-only\" for=\"search_reference_field\">Search reference</label>\\n</form>\\n\\n';});\n\n",
        -    "\ndefine('text!tpl/search_suggestion.html',[],function () { return '<p id=\"index-<%=idx%>\" class=\"search-suggestion\">\\n\\n  <strong><%=name%></strong>\\n\\n  <span class=\"small\">\\n    <% if (final) { %>\\n    constant\\n    <% } else if (itemtype) { %>\\n    <%=itemtype%> \\n    <% } %>\\n\\n    <% if (className) { %>\\n    in <strong><%=className%></strong>\\n    <% } %>\\n\\n    <% if (typeof is_constructor !== \\'undefined\\' && is_constructor) { %>\\n    <strong><span class=\"glyphicon glyphicon-star\"></span> constructor</strong>\\n    <% } %>\\n  </span>\\n\\n</p>';});\n\n",
        -    "/*!\n * typeahead.js 0.10.2\n * https://github.com/twitter/typeahead.js\n * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT\n */\ndefine('typeahead',[], function() {\n\n//(function($) {\n\n\n    var _ = {\n        isMsie: function() {\n            return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\\d+(.\\d+)?)/i)[2] : false;\n        },\n        isBlankString: function(str) {\n            return !str || /^\\s*$/.test(str);\n        },\n        escapeRegExChars: function(str) {\n            return str.replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|]/g, \"\\\\$&\");\n        },\n        isString: function(obj) {\n            return typeof obj === \"string\";\n        },\n        isNumber: function(obj) {\n            return typeof obj === \"number\";\n        },\n        isArray: $.isArray,\n        isFunction: $.isFunction,\n        isObject: $.isPlainObject,\n        isUndefined: function(obj) {\n            return typeof obj === \"undefined\";\n        },\n        bind: $.proxy,\n        each: function(collection, cb) {\n            $.each(collection, reverseArgs);\n            function reverseArgs(index, value) {\n                return cb(value, index);\n            }\n        },\n        map: $.map,\n        filter: $.grep,\n        every: function(obj, test) {\n            var result = true;\n            if (!obj) {\n                return result;\n            }\n            $.each(obj, function(key, val) {\n                if (!(result = test.call(null, val, key, obj))) {\n                    return false;\n                }\n            });\n            return !!result;\n        },\n        some: function(obj, test) {\n            var result = false;\n            if (!obj) {\n                return result;\n            }\n            $.each(obj, function(key, val) {\n                if (result = test.call(null, val, key, obj)) {\n                    return false;\n                }\n            });\n            return !!result;\n        },\n        mixin: $.extend,\n        getUniqueId: function() {\n            var counter = 0;\n            return function() {\n                return counter++;\n            };\n        }(),\n        templatify: function templatify(obj) {\n            return $.isFunction(obj) ? obj : template;\n            function template() {\n                return String(obj);\n            }\n        },\n        defer: function(fn) {\n            setTimeout(fn, 0);\n        },\n        debounce: function(func, wait, immediate) {\n            var timeout, result;\n            return function() {\n                var context = this, args = arguments, later, callNow;\n                later = function() {\n                    timeout = null;\n                    if (!immediate) {\n                        result = func.apply(context, args);\n                    }\n                };\n                callNow = immediate && !timeout;\n                clearTimeout(timeout);\n                timeout = setTimeout(later, wait);\n                if (callNow) {\n                    result = func.apply(context, args);\n                }\n                return result;\n            };\n        },\n        throttle: function(func, wait) {\n            var context, args, timeout, result, previous, later;\n            previous = 0;\n            later = function() {\n                previous = new Date();\n                timeout = null;\n                result = func.apply(context, args);\n            };\n            return function() {\n                var now = new Date(), remaining = wait - (now - previous);\n                context = this;\n                args = arguments;\n                if (remaining <= 0) {\n                    clearTimeout(timeout);\n                    timeout = null;\n                    previous = now;\n                    result = func.apply(context, args);\n                } else if (!timeout) {\n                    timeout = setTimeout(later, remaining);\n                }\n                return result;\n            };\n        },\n        noop: function() {}\n    };\n    var VERSION = \"0.10.2\";\n    var tokenizers = function(root) {\n        return {\n            nonword: nonword,\n            whitespace: whitespace,\n            obj: {\n                nonword: getObjTokenizer(nonword),\n                whitespace: getObjTokenizer(whitespace)\n            }\n        };\n        function whitespace(s) {\n            return s.split(/\\s+/);\n        }\n        function nonword(s) {\n            return s.split(/\\W+/);\n        }\n        function getObjTokenizer(tokenizer) {\n            return function setKey(key) {\n                return function tokenize(o) {\n                    return tokenizer(o[key]);\n                };\n            };\n        }\n    }();\n    var LruCache = function() {\n        function LruCache(maxSize) {\n            this.maxSize = maxSize || 100;\n            this.size = 0;\n            this.hash = {};\n            this.list = new List();\n        }\n        _.mixin(LruCache.prototype, {\n            set: function set(key, val) {\n                var tailItem = this.list.tail, node;\n                if (this.size >= this.maxSize) {\n                    this.list.remove(tailItem);\n                    delete this.hash[tailItem.key];\n                }\n                if (node = this.hash[key]) {\n                    node.val = val;\n                    this.list.moveToFront(node);\n                } else {\n                    node = new Node(key, val);\n                    this.list.add(node);\n                    this.hash[key] = node;\n                    this.size++;\n                }\n            },\n            get: function get(key) {\n                var node = this.hash[key];\n                if (node) {\n                    this.list.moveToFront(node);\n                    return node.val;\n                }\n            }\n        });\n        function List() {\n            this.head = this.tail = null;\n        }\n        _.mixin(List.prototype, {\n            add: function add(node) {\n                if (this.head) {\n                    node.next = this.head;\n                    this.head.prev = node;\n                }\n                this.head = node;\n                this.tail = this.tail || node;\n            },\n            remove: function remove(node) {\n                node.prev ? node.prev.next = node.next : this.head = node.next;\n                node.next ? node.next.prev = node.prev : this.tail = node.prev;\n            },\n            moveToFront: function(node) {\n                this.remove(node);\n                this.add(node);\n            }\n        });\n        function Node(key, val) {\n            this.key = key;\n            this.val = val;\n            this.prev = this.next = null;\n        }\n        return LruCache;\n    }();\n    var PersistentStorage = function() {\n        var ls, methods;\n        try {\n            ls = window.localStorage;\n            ls.setItem(\"~~~\", \"!\");\n            ls.removeItem(\"~~~\");\n        } catch (err) {\n            ls = null;\n        }\n        function PersistentStorage(namespace) {\n            this.prefix = [ \"__\", namespace, \"__\" ].join(\"\");\n            this.ttlKey = \"__ttl__\";\n            this.keyMatcher = new RegExp(\"^\" + this.prefix);\n        }\n        if (ls && window.JSON) {\n            methods = {\n                _prefix: function(key) {\n                    return this.prefix + key;\n                },\n                _ttlKey: function(key) {\n                    return this._prefix(key) + this.ttlKey;\n                },\n                get: function(key) {\n                    if (this.isExpired(key)) {\n                        this.remove(key);\n                    }\n                    return decode(ls.getItem(this._prefix(key)));\n                },\n                set: function(key, val, ttl) {\n                    if (_.isNumber(ttl)) {\n                        ls.setItem(this._ttlKey(key), encode(now() + ttl));\n                    } else {\n                        ls.removeItem(this._ttlKey(key));\n                    }\n                    return ls.setItem(this._prefix(key), encode(val));\n                },\n                remove: function(key) {\n                    ls.removeItem(this._ttlKey(key));\n                    ls.removeItem(this._prefix(key));\n                    return this;\n                },\n                clear: function() {\n                    var i, key, keys = [], len = ls.length;\n                    for (i = 0; i < len; i++) {\n                        if ((key = ls.key(i)).match(this.keyMatcher)) {\n                            keys.push(key.replace(this.keyMatcher, \"\"));\n                        }\n                    }\n                    for (i = keys.length; i--; ) {\n                        this.remove(keys[i]);\n                    }\n                    return this;\n                },\n                isExpired: function(key) {\n                    var ttl = decode(ls.getItem(this._ttlKey(key)));\n                    return _.isNumber(ttl) && now() > ttl ? true : false;\n                }\n            };\n        } else {\n            methods = {\n                get: _.noop,\n                set: _.noop,\n                remove: _.noop,\n                clear: _.noop,\n                isExpired: _.noop\n            };\n        }\n        _.mixin(PersistentStorage.prototype, methods);\n        return PersistentStorage;\n        function now() {\n            return new Date().getTime();\n        }\n        function encode(val) {\n            return JSON.stringify(_.isUndefined(val) ? null : val);\n        }\n        function decode(val) {\n            return JSON.parse(val);\n        }\n    }();\n    var Transport = function() {\n        var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, requestCache = new LruCache(10);\n        function Transport(o) {\n            o = o || {};\n            this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax;\n            this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get;\n        }\n        Transport.setMaxPendingRequests = function setMaxPendingRequests(num) {\n            maxPendingRequests = num;\n        };\n        Transport.resetCache = function clearCache() {\n            requestCache = new LruCache(10);\n        };\n        _.mixin(Transport.prototype, {\n            _get: function(url, o, cb) {\n                var that = this, jqXhr;\n                if (jqXhr = pendingRequests[url]) {\n                    jqXhr.done(done).fail(fail);\n                } else if (pendingRequestsCount < maxPendingRequests) {\n                    pendingRequestsCount++;\n                    pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always);\n                } else {\n                    this.onDeckRequestArgs = [].slice.call(arguments, 0);\n                }\n                function done(resp) {\n                    cb && cb(null, resp);\n                    requestCache.set(url, resp);\n                }\n                function fail() {\n                    cb && cb(true);\n                }\n                function always() {\n                    pendingRequestsCount--;\n                    delete pendingRequests[url];\n                    if (that.onDeckRequestArgs) {\n                        that._get.apply(that, that.onDeckRequestArgs);\n                        that.onDeckRequestArgs = null;\n                    }\n                }\n            },\n            get: function(url, o, cb) {\n                var resp;\n                if (_.isFunction(o)) {\n                    cb = o;\n                    o = {};\n                }\n                if (resp = requestCache.get(url)) {\n                    _.defer(function() {\n                        cb && cb(null, resp);\n                    });\n                } else {\n                    this._get(url, o, cb);\n                }\n                return !!resp;\n            }\n        });\n        return Transport;\n        function callbackToDeferred(fn) {\n            return function customSendWrapper(url, o) {\n                var deferred = $.Deferred();\n                fn(url, o, onSuccess, onError);\n                return deferred;\n                function onSuccess(resp) {\n                    _.defer(function() {\n                        deferred.resolve(resp);\n                    });\n                }\n                function onError(err) {\n                    _.defer(function() {\n                        deferred.reject(err);\n                    });\n                }\n            };\n        }\n    }();\n    var SearchIndex = function() {\n        function SearchIndex(o) {\n            o = o || {};\n            if (!o.datumTokenizer || !o.queryTokenizer) {\n                $.error(\"datumTokenizer and queryTokenizer are both required\");\n            }\n            this.datumTokenizer = o.datumTokenizer;\n            this.queryTokenizer = o.queryTokenizer;\n            this.reset();\n        }\n        _.mixin(SearchIndex.prototype, {\n            bootstrap: function bootstrap(o) {\n                this.datums = o.datums;\n                this.trie = o.trie;\n            },\n            add: function(data) {\n                var that = this;\n                data = _.isArray(data) ? data : [ data ];\n                _.each(data, function(datum) {\n                    var id, tokens;\n                    id = that.datums.push(datum) - 1;\n                    tokens = normalizeTokens(that.datumTokenizer(datum));\n                    _.each(tokens, function(token) {\n                        var node, chars, ch;\n                        node = that.trie;\n                        chars = token.split(\"\");\n                        while (ch = chars.shift()) {\n                            node = node.children[ch] || (node.children[ch] = newNode());\n                            node.ids.push(id);\n                        }\n                    });\n                });\n            },\n            get: function get(query) {\n                var that = this, tokens, matches;\n                tokens = normalizeTokens(this.queryTokenizer(query));\n                _.each(tokens, function(token) {\n                    var node, chars, ch, ids;\n                    if (matches && matches.length === 0) {\n                        return false;\n                    }\n                    node = that.trie;\n                    chars = token.split(\"\");\n                    while (node && (ch = chars.shift())) {\n                        node = node.children[ch];\n                    }\n                    if (node && chars.length === 0) {\n                        ids = node.ids.slice(0);\n                        matches = matches ? getIntersection(matches, ids) : ids;\n                    } else {\n                        matches = [];\n                        return false;\n                    }\n                });\n                return matches ? _.map(unique(matches), function(id) {\n                    return that.datums[id];\n                }) : [];\n            },\n            reset: function reset() {\n                this.datums = [];\n                this.trie = newNode();\n            },\n            serialize: function serialize() {\n                return {\n                    datums: this.datums,\n                    trie: this.trie\n                };\n            }\n        });\n        return SearchIndex;\n        function normalizeTokens(tokens) {\n            tokens = _.filter(tokens, function(token) {\n                return !!token;\n            });\n            tokens = _.map(tokens, function(token) {\n                return token.toLowerCase();\n            });\n            return tokens;\n        }\n        function newNode() {\n            return {\n                ids: [],\n                children: {}\n            };\n        }\n        function unique(array) {\n            var seen = {}, uniques = [];\n            for (var i = 0; i < array.length; i++) {\n                if (!seen[array[i]]) {\n                    seen[array[i]] = true;\n                    uniques.push(array[i]);\n                }\n            }\n            return uniques;\n        }\n        function getIntersection(arrayA, arrayB) {\n            var ai = 0, bi = 0, intersection = [];\n            arrayA = arrayA.sort(compare);\n            arrayB = arrayB.sort(compare);\n            while (ai < arrayA.length && bi < arrayB.length) {\n                if (arrayA[ai] < arrayB[bi]) {\n                    ai++;\n                } else if (arrayA[ai] > arrayB[bi]) {\n                    bi++;\n                } else {\n                    intersection.push(arrayA[ai]);\n                    ai++;\n                    bi++;\n                }\n            }\n            return intersection;\n            function compare(a, b) {\n                return a - b;\n            }\n        }\n    }();\n    var oParser = function() {\n        return {\n            local: getLocal,\n            prefetch: getPrefetch,\n            remote: getRemote\n        };\n        function getLocal(o) {\n            return o.local || null;\n        }\n        function getPrefetch(o) {\n            var prefetch, defaults;\n            defaults = {\n                url: null,\n                thumbprint: \"\",\n                ttl: 24 * 60 * 60 * 1e3,\n                filter: null,\n                ajax: {}\n            };\n            if (prefetch = o.prefetch || null) {\n                prefetch = _.isString(prefetch) ? {\n                    url: prefetch\n                } : prefetch;\n                prefetch = _.mixin(defaults, prefetch);\n                prefetch.thumbprint = VERSION + prefetch.thumbprint;\n                prefetch.ajax.type = prefetch.ajax.type || \"GET\";\n                prefetch.ajax.dataType = prefetch.ajax.dataType || \"json\";\n                !prefetch.url && $.error(\"prefetch requires url to be set\");\n            }\n            return prefetch;\n        }\n        function getRemote(o) {\n            var remote, defaults;\n            defaults = {\n                url: null,\n                wildcard: \"%QUERY\",\n                replace: null,\n                rateLimitBy: \"debounce\",\n                rateLimitWait: 300,\n                send: null,\n                filter: null,\n                ajax: {}\n            };\n            if (remote = o.remote || null) {\n                remote = _.isString(remote) ? {\n                    url: remote\n                } : remote;\n                remote = _.mixin(defaults, remote);\n                remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait);\n                remote.ajax.type = remote.ajax.type || \"GET\";\n                remote.ajax.dataType = remote.ajax.dataType || \"json\";\n                delete remote.rateLimitBy;\n                delete remote.rateLimitWait;\n                !remote.url && $.error(\"remote requires url to be set\");\n            }\n            return remote;\n            function byDebounce(wait) {\n                return function(fn) {\n                    return _.debounce(fn, wait);\n                };\n            }\n            function byThrottle(wait) {\n                return function(fn) {\n                    return _.throttle(fn, wait);\n                };\n            }\n        }\n    }();\n    (function(root) {\n        var old, keys;\n        old = root.Bloodhound;\n        keys = {\n            data: \"data\",\n            protocol: \"protocol\",\n            thumbprint: \"thumbprint\"\n        };\n        root.Bloodhound = Bloodhound;\n        function Bloodhound(o) {\n            if (!o || !o.local && !o.prefetch && !o.remote) {\n                $.error(\"one of local, prefetch, or remote is required\");\n            }\n            this.limit = o.limit || 5;\n            this.sorter = getSorter(o.sorter);\n            this.dupDetector = o.dupDetector || ignoreDuplicates;\n            this.local = oParser.local(o);\n            this.prefetch = oParser.prefetch(o);\n            this.remote = oParser.remote(o);\n            this.cacheKey = this.prefetch ? this.prefetch.cacheKey || this.prefetch.url : null;\n            this.index = new SearchIndex({\n                datumTokenizer: o.datumTokenizer,\n                queryTokenizer: o.queryTokenizer\n            });\n            this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null;\n        }\n        Bloodhound.noConflict = function noConflict() {\n            root.Bloodhound = old;\n            return Bloodhound;\n        };\n        Bloodhound.tokenizers = tokenizers;\n        _.mixin(Bloodhound.prototype, {\n            _loadPrefetch: function loadPrefetch(o) {\n                var that = this, serialized, deferred;\n                if (serialized = this._readFromStorage(o.thumbprint)) {\n                    this.index.bootstrap(serialized);\n                    deferred = $.Deferred().resolve();\n                } else {\n                    deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse);\n                }\n                return deferred;\n                function handlePrefetchResponse(resp) {\n                    that.clear();\n                    that.add(o.filter ? o.filter(resp) : resp);\n                    that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl);\n                }\n            },\n            _getFromRemote: function getFromRemote(query, cb) {\n                var that = this, url, uriEncodedQuery;\n                query = query || \"\";\n                uriEncodedQuery = encodeURIComponent(query);\n                url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery);\n                return this.transport.get(url, this.remote.ajax, handleRemoteResponse);\n                function handleRemoteResponse(err, resp) {\n                    err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp);\n                }\n            },\n            _saveToStorage: function saveToStorage(data, thumbprint, ttl) {\n                if (this.storage) {\n                    this.storage.set(keys.data, data, ttl);\n                    this.storage.set(keys.protocol, location.protocol, ttl);\n                    this.storage.set(keys.thumbprint, thumbprint, ttl);\n                }\n            },\n            _readFromStorage: function readFromStorage(thumbprint) {\n                var stored = {}, isExpired;\n                if (this.storage) {\n                    stored.data = this.storage.get(keys.data);\n                    stored.protocol = this.storage.get(keys.protocol);\n                    stored.thumbprint = this.storage.get(keys.thumbprint);\n                }\n                isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol;\n                return stored.data && !isExpired ? stored.data : null;\n            },\n            _initialize: function initialize() {\n                var that = this, local = this.local, deferred;\n                deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve();\n                local && deferred.done(addLocalToIndex);\n                this.transport = this.remote ? new Transport(this.remote) : null;\n                return this.initPromise = deferred.promise();\n                function addLocalToIndex() {\n                    that.add(_.isFunction(local) ? local() : local);\n                }\n            },\n            initialize: function initialize(force) {\n                return !this.initPromise || force ? this._initialize() : this.initPromise;\n            },\n            add: function add(data) {\n                this.index.add(data);\n            },\n            get: function get(query, cb) {\n                var that = this, matches = [], cacheHit = false;\n                matches = this.index.get(query);\n                matches = this.sorter(matches).slice(0, this.limit);\n                if (matches.length < this.limit && this.transport) {\n                    cacheHit = this._getFromRemote(query, returnRemoteMatches);\n                }\n                if (!cacheHit) {\n                    (matches.length > 0 || !this.transport) && cb && cb(matches);\n                }\n                function returnRemoteMatches(remoteMatches) {\n                    var matchesWithBackfill = matches.slice(0);\n                    _.each(remoteMatches, function(remoteMatch) {\n                        var isDuplicate;\n                        isDuplicate = _.some(matchesWithBackfill, function(match) {\n                            return that.dupDetector(remoteMatch, match);\n                        });\n                        !isDuplicate && matchesWithBackfill.push(remoteMatch);\n                        return matchesWithBackfill.length < that.limit;\n                    });\n                    cb && cb(that.sorter(matchesWithBackfill));\n                }\n            },\n            clear: function clear() {\n                this.index.reset();\n            },\n            clearPrefetchCache: function clearPrefetchCache() {\n                this.storage && this.storage.clear();\n            },\n            clearRemoteCache: function clearRemoteCache() {\n                this.transport && Transport.resetCache();\n            },\n            ttAdapter: function ttAdapter() {\n                return _.bind(this.get, this);\n            }\n        });\n        return Bloodhound;\n        function getSorter(sortFn) {\n            return _.isFunction(sortFn) ? sort : noSort;\n            function sort(array) {\n                return array.sort(sortFn);\n            }\n            function noSort(array) {\n                return array;\n            }\n        }\n        function ignoreDuplicates() {\n            return false;\n        }\n    })(this);\n    var html = {\n        wrapper: '<span class=\"twitter-typeahead\"></span>',\n        dropdown: '<span class=\"tt-dropdown-menu\"></span>',\n        dataset: '<div class=\"tt-dataset-%CLASS%\"></div>',\n        suggestions: '<span class=\"tt-suggestions\"></span>',\n        suggestion: '<div class=\"tt-suggestion\"></div>'\n    };\n    var css = {\n        wrapper: {\n            position: \"relative\",\n            display: \"inline-block\"\n        },\n        hint: {\n            position: \"absolute\",\n            top: \"0\",\n            left: \"0\",\n            borderColor: \"transparent\",\n            boxShadow: \"none\"\n        },\n        input: {\n            position: \"relative\",\n            verticalAlign: \"top\",\n            backgroundColor: \"transparent\"\n        },\n        inputWithNoHint: {\n            position: \"relative\",\n            verticalAlign: \"top\"\n        },\n        dropdown: {\n            position: \"absolute\",\n            top: \"100%\",\n            left: \"0\",\n            zIndex: \"100\",\n            display: \"none\"\n        },\n        suggestions: {\n            display: \"block\"\n        },\n        suggestion: {\n            whiteSpace: \"nowrap\",\n            cursor: \"pointer\"\n        },\n        suggestionChild: {\n            whiteSpace: \"normal\"\n        },\n        ltr: {\n            left: \"0\",\n            right: \"auto\"\n        },\n        rtl: {\n            left: \"auto\",\n            right: \" 0\"\n        }\n    };\n    if (_.isMsie()) {\n        _.mixin(css.input, {\n            backgroundImage: \"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)\"\n        });\n    }\n    if (_.isMsie() && _.isMsie() <= 7) {\n        _.mixin(css.input, {\n            marginTop: \"-1px\"\n        });\n    }\n    var EventBus = function() {\n        var namespace = \"typeahead:\";\n        function EventBus(o) {\n            if (!o || !o.el) {\n                $.error(\"EventBus initialized without el\");\n            }\n            this.$el = $(o.el);\n        }\n        _.mixin(EventBus.prototype, {\n            trigger: function(type) {\n                var args = [].slice.call(arguments, 1);\n                this.$el.trigger(namespace + type, args);\n            }\n        });\n        return EventBus;\n    }();\n    var EventEmitter = function() {\n        var splitter = /\\s+/, nextTick = getNextTick();\n        return {\n            onSync: onSync,\n            onAsync: onAsync,\n            off: off,\n            trigger: trigger\n        };\n        function on(method, types, cb, context) {\n            var type;\n            if (!cb) {\n                return this;\n            }\n            types = types.split(splitter);\n            cb = context ? bindContext(cb, context) : cb;\n            this._callbacks = this._callbacks || {};\n            while (type = types.shift()) {\n                this._callbacks[type] = this._callbacks[type] || {\n                    sync: [],\n                    async: []\n                };\n                this._callbacks[type][method].push(cb);\n            }\n            return this;\n        }\n        function onAsync(types, cb, context) {\n            return on.call(this, \"async\", types, cb, context);\n        }\n        function onSync(types, cb, context) {\n            return on.call(this, \"sync\", types, cb, context);\n        }\n        function off(types) {\n            var type;\n            if (!this._callbacks) {\n                return this;\n            }\n            types = types.split(splitter);\n            while (type = types.shift()) {\n                delete this._callbacks[type];\n            }\n            return this;\n        }\n        function trigger(types) {\n            var type, callbacks, args, syncFlush, asyncFlush;\n            if (!this._callbacks) {\n                return this;\n            }\n            types = types.split(splitter);\n            args = [].slice.call(arguments, 1);\n            while ((type = types.shift()) && (callbacks = this._callbacks[type])) {\n                syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args));\n                asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args));\n                syncFlush() && nextTick(asyncFlush);\n            }\n            return this;\n        }\n        function getFlush(callbacks, context, args) {\n            return flush;\n            function flush() {\n                var cancelled;\n                for (var i = 0; !cancelled && i < callbacks.length; i += 1) {\n                    cancelled = callbacks[i].apply(context, args) === false;\n                }\n                return !cancelled;\n            }\n        }\n        function getNextTick() {\n            var nextTickFn;\n            if (window.setImmediate) {\n                nextTickFn = function nextTickSetImmediate(fn) {\n                    setImmediate(function() {\n                        fn();\n                    });\n                };\n            } else {\n                nextTickFn = function nextTickSetTimeout(fn) {\n                    setTimeout(function() {\n                        fn();\n                    }, 0);\n                };\n            }\n            return nextTickFn;\n        }\n        function bindContext(fn, context) {\n            return fn.bind ? fn.bind(context) : function() {\n                fn.apply(context, [].slice.call(arguments, 0));\n            };\n        }\n    }();\n    var highlight = function(doc) {\n        var defaults = {\n            node: null,\n            pattern: null,\n            tagName: \"strong\",\n            className: null,\n            wordsOnly: false,\n            caseSensitive: false\n        };\n        return function hightlight(o) {\n            var regex;\n            o = _.mixin({}, defaults, o);\n            if (!o.node || !o.pattern) {\n                return;\n            }\n            o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ];\n            regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly);\n            traverse(o.node, hightlightTextNode);\n            function hightlightTextNode(textNode) {\n                var match, patternNode;\n                if (match = regex.exec(textNode.data)) {\n                    wrapperNode = doc.createElement(o.tagName);\n                    o.className && (wrapperNode.className = o.className);\n                    patternNode = textNode.splitText(match.index);\n                    patternNode.splitText(match[0].length);\n                    wrapperNode.appendChild(patternNode.cloneNode(true));\n                    textNode.parentNode.replaceChild(wrapperNode, patternNode);\n                }\n                return !!match;\n            }\n            function traverse(el, hightlightTextNode) {\n                var childNode, TEXT_NODE_TYPE = 3;\n                for (var i = 0; i < el.childNodes.length; i++) {\n                    childNode = el.childNodes[i];\n                    if (childNode.nodeType === TEXT_NODE_TYPE) {\n                        i += hightlightTextNode(childNode) ? 1 : 0;\n                    } else {\n                        traverse(childNode, hightlightTextNode);\n                    }\n                }\n            }\n        };\n        function getRegex(patterns, caseSensitive, wordsOnly) {\n            var escapedPatterns = [], regexStr;\n            for (var i = 0; i < patterns.length; i++) {\n                escapedPatterns.push(_.escapeRegExChars(patterns[i]));\n            }\n            regexStr = wordsOnly ? \"\\\\b(\" + escapedPatterns.join(\"|\") + \")\\\\b\" : \"(\" + escapedPatterns.join(\"|\") + \")\";\n            return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, \"i\");\n        }\n    }(window.document);\n    var Input = function() {\n        var specialKeyCodeMap;\n        specialKeyCodeMap = {\n            9: \"tab\",\n            27: \"esc\",\n            37: \"left\",\n            39: \"right\",\n            13: \"enter\",\n            38: \"up\",\n            40: \"down\"\n        };\n        function Input(o) {\n            var that = this, onBlur, onFocus, onKeydown, onInput;\n            o = o || {};\n            if (!o.input) {\n                $.error(\"input is missing\");\n            }\n            onBlur = _.bind(this._onBlur, this);\n            onFocus = _.bind(this._onFocus, this);\n            onKeydown = _.bind(this._onKeydown, this);\n            onInput = _.bind(this._onInput, this);\n            this.$hint = $(o.hint);\n            this.$input = $(o.input).on(\"blur.tt\", onBlur).on(\"focus.tt\", onFocus).on(\"keydown.tt\", onKeydown);\n            if (this.$hint.length === 0) {\n                this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop;\n            }\n            if (!_.isMsie()) {\n                this.$input.on(\"input.tt\", onInput);\n            } else {\n                this.$input.on(\"keydown.tt keypress.tt cut.tt paste.tt\", function($e) {\n                    if (specialKeyCodeMap[$e.which || $e.keyCode]) {\n                        return;\n                    }\n                    _.defer(_.bind(that._onInput, that, $e));\n                });\n            }\n            this.query = this.$input.val();\n            this.$overflowHelper = buildOverflowHelper(this.$input);\n        }\n        Input.normalizeQuery = function(str) {\n            return (str || \"\").replace(/^\\s*/g, \"\").replace(/\\s{2,}/g, \" \");\n        };\n        _.mixin(Input.prototype, EventEmitter, {\n            _onBlur: function onBlur() {\n                this.resetInputValue();\n                this.trigger(\"blurred\");\n            },\n            _onFocus: function onFocus() {\n                this.trigger(\"focused\");\n            },\n            _onKeydown: function onKeydown($e) {\n                var keyName = specialKeyCodeMap[$e.which || $e.keyCode];\n                this._managePreventDefault(keyName, $e);\n                if (keyName && this._shouldTrigger(keyName, $e)) {\n                    this.trigger(keyName + \"Keyed\", $e);\n                }\n            },\n            _onInput: function onInput() {\n                this._checkInputValue();\n            },\n            _managePreventDefault: function managePreventDefault(keyName, $e) {\n                var preventDefault, hintValue, inputValue;\n                switch (keyName) {\n                  case \"tab\":\n                    hintValue = this.getHint();\n                    inputValue = this.getInputValue();\n                    preventDefault = hintValue && hintValue !== inputValue && !withModifier($e);\n                    break;\n\n                  case \"up\":\n                  case \"down\":\n                    preventDefault = !withModifier($e);\n                    break;\n\n                  default:\n                    preventDefault = false;\n                }\n                preventDefault && $e.preventDefault();\n            },\n            _shouldTrigger: function shouldTrigger(keyName, $e) {\n                var trigger;\n                switch (keyName) {\n                  case \"tab\":\n                    trigger = !withModifier($e);\n                    break;\n\n                  default:\n                    trigger = true;\n                }\n                return trigger;\n            },\n            _checkInputValue: function checkInputValue() {\n                var inputValue, areEquivalent, hasDifferentWhitespace;\n                inputValue = this.getInputValue();\n                areEquivalent = areQueriesEquivalent(inputValue, this.query);\n                hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false;\n                if (!areEquivalent) {\n                    this.trigger(\"queryChanged\", this.query = inputValue);\n                } else if (hasDifferentWhitespace) {\n                    this.trigger(\"whitespaceChanged\", this.query);\n                }\n            },\n            focus: function focus() {\n                this.$input.focus();\n            },\n            blur: function blur() {\n                this.$input.blur();\n            },\n            getQuery: function getQuery() {\n                return this.query;\n            },\n            setQuery: function setQuery(query) {\n                this.query = query;\n            },\n            getInputValue: function getInputValue() {\n                return this.$input.val();\n            },\n            setInputValue: function setInputValue(value, silent) {\n                this.$input.val(value);\n                silent ? this.clearHint() : this._checkInputValue();\n            },\n            resetInputValue: function resetInputValue() {\n                this.setInputValue(this.query, true);\n            },\n            getHint: function getHint() {\n                return this.$hint.val();\n            },\n            setHint: function setHint(value) {\n                this.$hint.val(value);\n            },\n            clearHint: function clearHint() {\n                this.setHint(\"\");\n            },\n            clearHintIfInvalid: function clearHintIfInvalid() {\n                var val, hint, valIsPrefixOfHint, isValid;\n                val = this.getInputValue();\n                hint = this.getHint();\n                valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0;\n                isValid = val !== \"\" && valIsPrefixOfHint && !this.hasOverflow();\n                !isValid && this.clearHint();\n            },\n            getLanguageDirection: function getLanguageDirection() {\n                return (this.$input.css(\"direction\") || \"ltr\").toLowerCase();\n            },\n            hasOverflow: function hasOverflow() {\n                var constraint = this.$input.width() - 2;\n                this.$overflowHelper.text(this.getInputValue());\n                return this.$overflowHelper.width() >= constraint;\n            },\n            isCursorAtEnd: function() {\n                var valueLength, selectionStart, range;\n                valueLength = this.$input.val().length;\n                selectionStart = this.$input[0].selectionStart;\n                if (_.isNumber(selectionStart)) {\n                    return selectionStart === valueLength;\n                } else if (document.selection) {\n                    range = document.selection.createRange();\n                    range.moveStart(\"character\", -valueLength);\n                    return valueLength === range.text.length;\n                }\n                return true;\n            },\n            destroy: function destroy() {\n                this.$hint.off(\".tt\");\n                this.$input.off(\".tt\");\n                this.$hint = this.$input = this.$overflowHelper = null;\n            }\n        });\n        return Input;\n        function buildOverflowHelper($input) {\n            return $('<pre aria-hidden=\"true\"></pre>').css({\n                position: \"absolute\",\n                visibility: \"hidden\",\n                whiteSpace: \"pre\",\n                fontFamily: $input.css(\"font-family\"),\n                fontSize: $input.css(\"font-size\"),\n                fontStyle: $input.css(\"font-style\"),\n                fontVariant: $input.css(\"font-variant\"),\n                fontWeight: $input.css(\"font-weight\"),\n                wordSpacing: $input.css(\"word-spacing\"),\n                letterSpacing: $input.css(\"letter-spacing\"),\n                textIndent: $input.css(\"text-indent\"),\n                textRendering: $input.css(\"text-rendering\"),\n                textTransform: $input.css(\"text-transform\")\n            }).insertAfter($input);\n        }\n        function areQueriesEquivalent(a, b) {\n            return Input.normalizeQuery(a) === Input.normalizeQuery(b);\n        }\n        function withModifier($e) {\n            return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey;\n        }\n    }();\n    var Dataset = function() {\n        var datasetKey = \"ttDataset\", valueKey = \"ttValue\", datumKey = \"ttDatum\";\n        function Dataset(o) {\n            o = o || {};\n            o.templates = o.templates || {};\n            if (!o.source) {\n                $.error(\"missing source\");\n            }\n            if (o.name && !isValidName(o.name)) {\n                $.error(\"invalid dataset name: \" + o.name);\n            }\n            this.query = null;\n            this.highlight = !!o.highlight;\n            this.name = o.name || _.getUniqueId();\n            this.source = o.source;\n            this.displayFn = getDisplayFn(o.display || o.displayKey);\n            this.templates = getTemplates(o.templates, this.displayFn);\n            this.$el = $(html.dataset.replace(\"%CLASS%\", this.name));\n        }\n        Dataset.extractDatasetName = function extractDatasetName(el) {\n            return $(el).data(datasetKey);\n        };\n        Dataset.extractValue = function extractDatum(el) {\n            return $(el).data(valueKey);\n        };\n        Dataset.extractDatum = function extractDatum(el) {\n            return $(el).data(datumKey);\n        };\n        _.mixin(Dataset.prototype, EventEmitter, {\n            _render: function render(query, suggestions) {\n                if (!this.$el) {\n                    return;\n                }\n                var that = this, hasSuggestions;\n                this.$el.empty();\n                hasSuggestions = suggestions && suggestions.length;\n                if (!hasSuggestions && this.templates.empty) {\n                    this.$el.html(getEmptyHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null);\n                } else if (hasSuggestions) {\n                    this.$el.html(getSuggestionsHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null);\n                }\n                this.trigger(\"rendered\");\n                function getEmptyHtml() {\n                    return that.templates.empty({\n                        query: query,\n                        isEmpty: true\n                    });\n                }\n                function getSuggestionsHtml() {\n                    var $suggestions, nodes;\n                    $suggestions = $(html.suggestions).css(css.suggestions);\n                    nodes = _.map(suggestions, getSuggestionNode);\n                    $suggestions.append.apply($suggestions, nodes);\n                    that.highlight && highlight({\n                        node: $suggestions[0],\n                        pattern: query\n                    });\n                    return $suggestions;\n                    function getSuggestionNode(suggestion) {\n                        var $el;\n                        $el = $(html.suggestion).append(that.templates.suggestion(suggestion)).data(datasetKey, that.name).data(valueKey, that.displayFn(suggestion)).data(datumKey, suggestion);\n                        $el.children().each(function() {\n                            $(this).css(css.suggestionChild);\n                        });\n                        return $el;\n                    }\n                }\n                function getHeaderHtml() {\n                    return that.templates.header({\n                        query: query,\n                        isEmpty: !hasSuggestions\n                    });\n                }\n                function getFooterHtml() {\n                    return that.templates.footer({\n                        query: query,\n                        isEmpty: !hasSuggestions\n                    });\n                }\n            },\n            getRoot: function getRoot() {\n                return this.$el;\n            },\n            update: function update(query) {\n                var that = this;\n                this.query = query;\n                this.canceled = false;\n                this.source(query, render);\n                function render(suggestions) {\n                    if (!that.canceled && query === that.query) {\n                        that._render(query, suggestions);\n                    }\n                }\n            },\n            cancel: function cancel() {\n                this.canceled = true;\n            },\n            clear: function clear() {\n                this.cancel();\n                this.$el.empty();\n                this.trigger(\"rendered\");\n            },\n            isEmpty: function isEmpty() {\n                return this.$el.is(\":empty\");\n            },\n            destroy: function destroy() {\n                this.$el = null;\n            }\n        });\n        return Dataset;\n        function getDisplayFn(display) {\n            display = display || \"value\";\n            return _.isFunction(display) ? display : displayFn;\n            function displayFn(obj) {\n                return obj[display];\n            }\n        }\n        function getTemplates(templates, displayFn) {\n            return {\n                empty: templates.empty && _.templatify(templates.empty),\n                header: templates.header && _.templatify(templates.header),\n                footer: templates.footer && _.templatify(templates.footer),\n                suggestion: templates.suggestion || suggestionTemplate\n            };\n            function suggestionTemplate(context) {\n                return \"<p>\" + displayFn(context) + \"</p>\";\n            }\n        }\n        function isValidName(str) {\n            return /^[_a-zA-Z0-9-]+$/.test(str);\n        }\n    }();\n    var Dropdown = function() {\n        function Dropdown(o) {\n            var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave;\n            o = o || {};\n            if (!o.menu) {\n                $.error(\"menu is required\");\n            }\n            this.isOpen = false;\n            this.isEmpty = true;\n            this.datasets = _.map(o.datasets, initializeDataset);\n            onSuggestionClick = _.bind(this._onSuggestionClick, this);\n            onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this);\n            onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this);\n            this.$menu = $(o.menu).on(\"click.tt\", \".tt-suggestion\", onSuggestionClick).on(\"mouseenter.tt\", \".tt-suggestion\", onSuggestionMouseEnter).on(\"mouseleave.tt\", \".tt-suggestion\", onSuggestionMouseLeave);\n            _.each(this.datasets, function(dataset) {\n                that.$menu.append(dataset.getRoot());\n                dataset.onSync(\"rendered\", that._onRendered, that);\n            });\n        }\n        _.mixin(Dropdown.prototype, EventEmitter, {\n            _onSuggestionClick: function onSuggestionClick($e) {\n                this.trigger(\"suggestionClicked\", $($e.currentTarget));\n            },\n            _onSuggestionMouseEnter: function onSuggestionMouseEnter($e) {\n                this._removeCursor();\n                this._setCursor($($e.currentTarget), true);\n            },\n            _onSuggestionMouseLeave: function onSuggestionMouseLeave() {\n                this._removeCursor();\n            },\n            _onRendered: function onRendered() {\n                this.isEmpty = _.every(this.datasets, isDatasetEmpty);\n                this.isEmpty ? this._hide() : this.isOpen && this._show();\n                this.trigger(\"datasetRendered\");\n                function isDatasetEmpty(dataset) {\n                    return dataset.isEmpty();\n                }\n            },\n            _hide: function() {\n                this.$menu.hide();\n            },\n            _show: function() {\n                this.$menu.css(\"display\", \"block\");\n            },\n            _getSuggestions: function getSuggestions() {\n                return this.$menu.find(\".tt-suggestion\");\n            },\n            _getCursor: function getCursor() {\n                return this.$menu.find(\".tt-cursor\").first();\n            },\n            _setCursor: function setCursor($el, silent) {\n                $el.first().addClass(\"tt-cursor\");\n                !silent && this.trigger(\"cursorMoved\");\n            },\n            _removeCursor: function removeCursor() {\n                this._getCursor().removeClass(\"tt-cursor\");\n            },\n            _moveCursor: function moveCursor(increment) {\n                var $suggestions, $oldCursor, newCursorIndex, $newCursor;\n                if (!this.isOpen) {\n                    return;\n                }\n                $oldCursor = this._getCursor();\n                $suggestions = this._getSuggestions();\n                this._removeCursor();\n                newCursorIndex = $suggestions.index($oldCursor) + increment;\n                newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1;\n                if (newCursorIndex === -1) {\n                    this.trigger(\"cursorRemoved\");\n                    return;\n                } else if (newCursorIndex < -1) {\n                    newCursorIndex = $suggestions.length - 1;\n                }\n                this._setCursor($newCursor = $suggestions.eq(newCursorIndex));\n                this._ensureVisible($newCursor);\n            },\n            _ensureVisible: function ensureVisible($el) {\n                var elTop, elBottom, menuScrollTop, menuHeight;\n                elTop = $el.position().top;\n                elBottom = elTop + $el.outerHeight(true);\n                menuScrollTop = this.$menu.scrollTop();\n                menuHeight = this.$menu.height() + parseInt(this.$menu.css(\"paddingTop\"), 10) + parseInt(this.$menu.css(\"paddingBottom\"), 10);\n                if (elTop < 0) {\n                    this.$menu.scrollTop(menuScrollTop + elTop);\n                } else if (menuHeight < elBottom) {\n                    this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight));\n                }\n            },\n            close: function close() {\n                if (this.isOpen) {\n                    this.isOpen = false;\n                    this._removeCursor();\n                    this._hide();\n                    this.trigger(\"closed\");\n                }\n            },\n            open: function open() {\n                if (!this.isOpen) {\n                    this.isOpen = true;\n                    !this.isEmpty && this._show();\n                    this.trigger(\"opened\");\n                }\n            },\n            setLanguageDirection: function setLanguageDirection(dir) {\n                this.$menu.css(dir === \"ltr\" ? css.ltr : css.rtl);\n            },\n            moveCursorUp: function moveCursorUp() {\n                this._moveCursor(-1);\n            },\n            moveCursorDown: function moveCursorDown() {\n                this._moveCursor(+1);\n            },\n            getDatumForSuggestion: function getDatumForSuggestion($el) {\n                var datum = null;\n                if ($el.length) {\n                    datum = {\n                        raw: Dataset.extractDatum($el),\n                        value: Dataset.extractValue($el),\n                        datasetName: Dataset.extractDatasetName($el)\n                    };\n                }\n                return datum;\n            },\n            getDatumForCursor: function getDatumForCursor() {\n                return this.getDatumForSuggestion(this._getCursor().first());\n            },\n            getDatumForTopSuggestion: function getDatumForTopSuggestion() {\n                return this.getDatumForSuggestion(this._getSuggestions().first());\n            },\n            update: function update(query) {\n                _.each(this.datasets, updateDataset);\n                function updateDataset(dataset) {\n                    dataset.update(query);\n                }\n            },\n            empty: function empty() {\n                _.each(this.datasets, clearDataset);\n                this.isEmpty = true;\n                function clearDataset(dataset) {\n                    dataset.clear();\n                }\n            },\n            isVisible: function isVisible() {\n                return this.isOpen && !this.isEmpty;\n            },\n            destroy: function destroy() {\n                this.$menu.off(\".tt\");\n                this.$menu = null;\n                _.each(this.datasets, destroyDataset);\n                function destroyDataset(dataset) {\n                    dataset.destroy();\n                }\n            }\n        });\n        return Dropdown;\n        function initializeDataset(oDataset) {\n            return new Dataset(oDataset);\n        }\n    }();\n    var Typeahead = function() {\n        var attrsKey = \"ttAttrs\";\n        function Typeahead(o) {\n            var $menu, $input, $hint;\n            o = o || {};\n            if (!o.input) {\n                $.error(\"missing input\");\n            }\n            this.isActivated = false;\n            this.autoselect = !!o.autoselect;\n            this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;\n            this.$node = buildDomStructure(o.input, o.withHint);\n            $menu = this.$node.find(\".tt-dropdown-menu\");\n            $input = this.$node.find(\".tt-input\");\n            $hint = this.$node.find(\".tt-hint\");\n            $input.on(\"blur.tt\", function($e) {\n                var active, isActive, hasActive;\n                active = document.activeElement;\n                isActive = $menu.is(active);\n                hasActive = $menu.has(active).length > 0;\n                if (_.isMsie() && (isActive || hasActive)) {\n                    $e.preventDefault();\n                    $e.stopImmediatePropagation();\n                    _.defer(function() {\n                        $input.focus();\n                    });\n                }\n            });\n            $menu.on(\"mousedown.tt\", function($e) {\n                $e.preventDefault();\n            });\n            this.eventBus = o.eventBus || new EventBus({\n                el: $input\n            });\n            this.dropdown = new Dropdown({\n                menu: $menu,\n                datasets: o.datasets\n            }).onSync(\"suggestionClicked\", this._onSuggestionClicked, this).onSync(\"cursorMoved\", this._onCursorMoved, this).onSync(\"cursorRemoved\", this._onCursorRemoved, this).onSync(\"opened\", this._onOpened, this).onSync(\"closed\", this._onClosed, this).onAsync(\"datasetRendered\", this._onDatasetRendered, this);\n            this.input = new Input({\n                input: $input,\n                hint: $hint\n            }).onSync(\"focused\", this._onFocused, this).onSync(\"blurred\", this._onBlurred, this).onSync(\"enterKeyed\", this._onEnterKeyed, this).onSync(\"tabKeyed\", this._onTabKeyed, this).onSync(\"escKeyed\", this._onEscKeyed, this).onSync(\"upKeyed\", this._onUpKeyed, this).onSync(\"downKeyed\", this._onDownKeyed, this).onSync(\"leftKeyed\", this._onLeftKeyed, this).onSync(\"rightKeyed\", this._onRightKeyed, this).onSync(\"queryChanged\", this._onQueryChanged, this).onSync(\"whitespaceChanged\", this._onWhitespaceChanged, this);\n            this._setLanguageDirection();\n        }\n        _.mixin(Typeahead.prototype, {\n            _onSuggestionClicked: function onSuggestionClicked(type, $el) {\n                var datum;\n                if (datum = this.dropdown.getDatumForSuggestion($el)) {\n                    this._select(datum);\n                }\n            },\n            _onCursorMoved: function onCursorMoved() {\n                var datum = this.dropdown.getDatumForCursor();\n                this.input.setInputValue(datum.value, true);\n                this.eventBus.trigger(\"cursorchanged\", datum.raw, datum.datasetName);\n            },\n            _onCursorRemoved: function onCursorRemoved() {\n                this.input.resetInputValue();\n                this._updateHint();\n            },\n            _onDatasetRendered: function onDatasetRendered() {\n                this._updateHint();\n            },\n            _onOpened: function onOpened() {\n                this._updateHint();\n                this.eventBus.trigger(\"opened\");\n            },\n            _onClosed: function onClosed() {\n                this.input.clearHint();\n                this.eventBus.trigger(\"closed\");\n            },\n            _onFocused: function onFocused() {\n                this.isActivated = true;\n                this.dropdown.open();\n            },\n            _onBlurred: function onBlurred() {\n                this.isActivated = false;\n                this.dropdown.empty();\n                this.dropdown.close();\n                this.setVal(\"\", true); //LM\n            },\n            _onEnterKeyed: function onEnterKeyed(type, $e) {\n                var cursorDatum, topSuggestionDatum;\n                cursorDatum = this.dropdown.getDatumForCursor();\n                topSuggestionDatum = this.dropdown.getDatumForTopSuggestion();\n                if (cursorDatum) {\n                    this._select(cursorDatum);\n                    $e.preventDefault();\n                } else if (this.autoselect && topSuggestionDatum) {\n                    this._select(topSuggestionDatum);\n                    $e.preventDefault();\n                }\n            },\n            _onTabKeyed: function onTabKeyed(type, $e) {\n                var datum;\n                if (datum = this.dropdown.getDatumForCursor()) {\n                    this._select(datum);\n                    $e.preventDefault();\n                } else {\n                    this._autocomplete(true);\n                }\n            },\n            _onEscKeyed: function onEscKeyed() {\n                this.dropdown.close();\n                this.input.resetInputValue();\n            },\n            _onUpKeyed: function onUpKeyed() {\n                var query = this.input.getQuery();\n                this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorUp();\n                this.dropdown.open();\n            },\n            _onDownKeyed: function onDownKeyed() {\n                var query = this.input.getQuery();\n                this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorDown();\n                this.dropdown.open();\n            },\n            _onLeftKeyed: function onLeftKeyed() {\n                this.dir === \"rtl\" && this._autocomplete();\n            },\n            _onRightKeyed: function onRightKeyed() {\n                this.dir === \"ltr\" && this._autocomplete();\n            },\n            _onQueryChanged: function onQueryChanged(e, query) {\n                this.input.clearHintIfInvalid();\n                query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.empty();\n                this.dropdown.open();\n                this._setLanguageDirection();\n            },\n            _onWhitespaceChanged: function onWhitespaceChanged() {\n                this._updateHint();\n                this.dropdown.open();\n            },\n            _setLanguageDirection: function setLanguageDirection() {\n                var dir;\n                if (this.dir !== (dir = this.input.getLanguageDirection())) {\n                    this.dir = dir;\n                    this.$node.css(\"direction\", dir);\n                    this.dropdown.setLanguageDirection(dir);\n                }\n            },\n            _updateHint: function updateHint() {\n                var datum, val, query, escapedQuery, frontMatchRegEx, match;\n                datum = this.dropdown.getDatumForTopSuggestion();\n                if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) {\n                    val = this.input.getInputValue();\n                    query = Input.normalizeQuery(val);\n                    escapedQuery = _.escapeRegExChars(query);\n                    frontMatchRegEx = new RegExp(\"^(?:\" + escapedQuery + \")(.+$)\", \"i\");\n                    match = frontMatchRegEx.exec(datum.value);\n                    match ? this.input.setHint(val + match[1]) : this.input.clearHint();\n                } else {\n                    this.input.clearHint();\n                }\n            },\n            _autocomplete: function autocomplete(laxCursor) {\n                var hint, query, isCursorAtEnd, datum;\n                hint = this.input.getHint();\n                query = this.input.getQuery();\n                isCursorAtEnd = laxCursor || this.input.isCursorAtEnd();\n                if (hint && query !== hint && isCursorAtEnd) {\n                    datum = this.dropdown.getDatumForTopSuggestion();\n                    datum && this.input.setInputValue(datum.value);\n                    this.eventBus.trigger(\"autocompleted\", datum.raw, datum.datasetName);\n                }\n            },\n            _select: function select(datum) {\n                this.input.setQuery(datum.value);\n                this.input.setInputValue(datum.value, true);\n                this._setLanguageDirection();\n                this.eventBus.trigger(\"selected\", datum.raw, datum.datasetName);\n                this.dropdown.close();\n                _.defer(_.bind(this.dropdown.empty, this.dropdown));\n            },\n            open: function open() {\n                this.dropdown.open();\n            },\n            close: function close() {\n                this.dropdown.close();\n            },\n            setVal: function setVal(val) {\n                if (this.isActivated) {\n                    this.input.setInputValue(val);\n                } else {\n                    this.input.setQuery(val);\n                    this.input.setInputValue(val, true);\n                }\n                this._setLanguageDirection();\n            },\n            getVal: function getVal() {\n                return this.input.getQuery();\n            },\n            destroy: function destroy() {\n                this.input.destroy();\n                this.dropdown.destroy();\n                destroyDomStructure(this.$node);\n                this.$node = null;\n            }\n        });\n        return Typeahead;\n        function buildDomStructure(input, withHint) {\n            var $input, $wrapper, $dropdown, $hint;\n            $input = $(input);\n            $wrapper = $(html.wrapper).css(css.wrapper);\n            $dropdown = $(html.dropdown).css(css.dropdown);\n            $hint = $input.clone().css(css.hint).css(getBackgroundStyles($input));\n            $hint.val(\"\").removeData().addClass(\"tt-hint\").removeAttr(\"id name placeholder\").prop(\"disabled\", true).attr({\n                autocomplete: \"off\",\n                spellcheck: \"false\"\n            });\n            $input.data(attrsKey, {\n                dir: $input.attr(\"dir\"),\n                autocomplete: $input.attr(\"autocomplete\"),\n                spellcheck: $input.attr(\"spellcheck\"),\n                style: $input.attr(\"style\")\n            });\n            $input.addClass(\"tt-input\").attr({\n                autocomplete: \"off\",\n                spellcheck: false\n            }).css(withHint ? css.input : css.inputWithNoHint);\n            try {\n                !$input.attr(\"dir\") && $input.attr(\"dir\", \"auto\");\n            } catch (e) {}\n            return $input.wrap($wrapper).parent().prepend(withHint ? $hint : null).append($dropdown);\n        }\n        function getBackgroundStyles($el) {\n            return {\n                backgroundAttachment: $el.css(\"background-attachment\"),\n                backgroundClip: $el.css(\"background-clip\"),\n                backgroundColor: $el.css(\"background-color\"),\n                backgroundImage: $el.css(\"background-image\"),\n                backgroundOrigin: $el.css(\"background-origin\"),\n                backgroundPosition: $el.css(\"background-position\"),\n                backgroundRepeat: $el.css(\"background-repeat\"),\n                backgroundSize: $el.css(\"background-size\")\n            };\n        }\n        function destroyDomStructure($node) {\n            var $input = $node.find(\".tt-input\");\n            _.each($input.data(attrsKey), function(val, key) {\n                _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);\n            });\n            $input.detach().removeData(attrsKey).removeClass(\"tt-input\").insertAfter($node);\n            $node.remove();\n        }\n    }();\n    (function() {\n        var old, typeaheadKey, methods;\n        old = $.fn.typeahead;\n        typeaheadKey = \"ttTypeahead\";\n        methods = {\n            initialize: function initialize(o, datasets) {\n                datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1);\n                o = o || {};\n                return this.each(attach);\n                function attach() {\n                    var $input = $(this), eventBus, typeahead;\n                    _.each(datasets, function(d) {\n                        d.highlight = !!o.highlight;\n                    });\n                    typeahead = new Typeahead({\n                        input: $input,\n                        eventBus: eventBus = new EventBus({\n                            el: $input\n                        }),\n                        withHint: _.isUndefined(o.hint) ? true : !!o.hint,\n                        minLength: o.minLength,\n                        autoselect: o.autoselect,\n                        datasets: datasets\n                    });\n                    $input.data(typeaheadKey, typeahead);\n                }\n            },\n            open: function open() {\n                return this.each(openTypeahead);\n                function openTypeahead() {\n                    var $input = $(this), typeahead;\n                    if (typeahead = $input.data(typeaheadKey)) {\n                        typeahead.open();\n                    }\n                }\n            },\n            close: function close() {\n                return this.each(closeTypeahead);\n                function closeTypeahead() {\n                    var $input = $(this), typeahead;\n                    if (typeahead = $input.data(typeaheadKey)) {\n                        typeahead.close();\n                    }\n                }\n            },\n            val: function val(newVal) {\n                return !arguments.length ? getVal(this.first()) : this.each(setVal);\n                function setVal() {\n                    var $input = $(this), typeahead;\n                    if (typeahead = $input.data(typeaheadKey)) {\n                        typeahead.setVal(newVal);\n                    }\n                }\n                function getVal($input) {\n                    var typeahead, query;\n                    if (typeahead = $input.data(typeaheadKey)) {\n                        query = typeahead.getVal();\n                    }\n                    return query;\n                }\n            },\n            destroy: function destroy() {\n                return this.each(unattach);\n                function unattach() {\n                    var $input = $(this), typeahead;\n                    if (typeahead = $input.data(typeaheadKey)) {\n                        typeahead.destroy();\n                        $input.removeData(typeaheadKey);\n                    }\n                }\n            }\n        };\n        $.fn.typeahead = function(method) {\n            if (methods[method]) {\n                return methods[method].apply(this, [].slice.call(arguments, 1));\n            } else {\n                return methods.initialize.apply(this, arguments);\n            }\n        };\n        $.fn.typeahead.noConflict = function noConflict() {\n            $.fn.typeahead = old;\n            return this;\n        };\n    })();\n    \n    \n    \n//})(window.jQuery);\n\n\n});\n",
        -    "define('searchView',[\n  'App',\n  // Templates\n  'text!tpl/search.html',\n  'text!tpl/search_suggestion.html',\n  // Tools\n  'typeahead'\n], function(App, searchTpl, suggestionTpl) {\n\n  var searchView = Backbone.View.extend({\n    el: '#search',\n    /**\n     * Init.\n     */\n    init: function() {\n      var tpl = _.template(searchTpl);\n      var className = 'form-control input-lg';\n      var placeholder = 'Search reference';\n      this.searchHtml = tpl({\n        'placeholder': placeholder,\n        'className': className\n      });\n      this.items = App.classes.concat(App.allItems);\n\n      return this;\n    },\n    /**\n     * Render input field with Typehead activated.\n     */\n    render: function() {\n      // Append the view to the dom\n      this.$el.append(this.searchHtml);\n\n      // Render Typeahead\n      var $searchInput = this.$el.find('input[type=text]');\n      this.typeaheadRender($searchInput);\n      this.typeaheadEvents($searchInput);\n\n      return this;\n    },\n    /**\n     * Apply Twitter Typeahead to the search input field.\n     * @param {jquery} $input\n     */\n    typeaheadRender: function($input) {\n      var self = this;\n      $input.typeahead(null, {\n        'displayKey': 'name',\n        'minLength': 2,\n        //'highlight': true,\n        'source': self.substringMatcher(this.items),\n        'templates': {\n          'empty': '<p class=\"empty-message\">Unable to find any item that match the current query</p>',\n          'suggestion': _.template(suggestionTpl)\n        }\n      });\n    },\n    /**\n     * Setup typeahead custom events (item selected).\n     */\n    typeaheadEvents: function($input) {\n      var self = this;\n      $input.on('typeahead:selected', function(e, item, datasetName) {\n        var selectedItem = self.items[item.idx];\n        select(selectedItem);\n      });\n      $input.on('keydown', function(e) {\n        if (e.which === 13) { // enter\n          var txt = $input.val();\n          var f = _.find(self.items, function(it) { return it.name == txt; });\n          if (f) {\n            select(f);\n          }\n        } else if (e.which === 27) {\n          $input.blur();\n        }\n      });\n\n      function select(selectedItem) {\n        var hash = App.router.getHash(selectedItem);//\n        App.router.navigate(hash, {'trigger': true});\n        $('#item').focus();\n      }\n    },\n    /**\n     * substringMatcher function for Typehead (search for strings in an array).\n     * @param {array} array\n     * @returns {Function}\n     */\n    substringMatcher: function(array) {\n      return function findMatches(query, callback) {\n        var matches = [], substrRegex, arrayLength = array.length;\n\n        // regex used to determine if a string contains the substring `query`\n        substrRegex = new RegExp(query, 'i');\n\n        // iterate through the pool of strings and for any string that\n        // contains the substring `query`, add it to the `matches` array\n        for (var i=0; i < arrayLength; i++) {\n          var item = array[i];\n          if (substrRegex.test(item.name)) {\n            // typeahead expects suggestions to be a js object\n            matches.push({\n              'itemtype': item.itemtype,\n              'name': item.name,\n              'className': item.class,\n              'is_constructor': !!item.is_constructor,\n              'final': item.final,\n              'idx': i\n            });\n          }\n        }\n\n        callback(matches);\n      };\n    }\n\n  });\n\n  return searchView;\n\n});\n\n",
        -    "\ndefine('text!tpl/list.html',[],function () { return '<% _.each(groups, function(group){ %>\\n  <div class=\"reference-group clearfix main-ref-page\">  \\n    <h2 class=\"group-name\" id=\"group-<%=group.name%>\" tab-index=\"-1\"><%=group.name%></h2>\\n    <div class=\"reference-subgroups clearfix main-ref-page\">  \\n    <% _.each(group.subgroups, function(subgroup, ind) { %>\\n      <div class=\"reference-subgroup\">\\n        <% if (subgroup.name !== \\'0\\') { %>\\n          <h3 id=\"<%=group.name%><%=ind%>\" class=\"subgroup-name subgroup-<%=subgroup.name%>\"><%=subgroup.name%></h3>\\n        <% } %>\\n        <ul aria-labelledby=\"<%=group.name%> <%=ind%>\">\\n        <% _.each(subgroup.items, function(item) { %>\\n        <li><a href=\"<%=item.hash%>\"><%=item.name%><% if (item.itemtype === \\'method\\') { %>()<%}%></a></li>\\n        <% }); %>\\n        </ul>\\n      </div>\\n    <% }); %>\\n    </div>\\n  </div>\\n<% }); %>\\n';});\n\n",
        -    "define('listView',[\n  'App',\n  // Templates\n  'text!tpl/list.html'\n], function (App, listTpl) {\n  var striptags = function(html) {\n    var div = document.createElement('div');\n    div.innerHTML = html;\n    return div.textContent;\n  };\n\n  var listView = Backbone.View.extend({\n    el: '#list',\n    events: {},\n    /**\n     * Init.\n     */\n    init: function () {\n      this.listTpl = _.template(listTpl);\n\n      return this;\n    },\n    /**\n     * Render the list.\n     */\n    render: function (items, listCollection) {\n      if (items && listCollection) {\n        var self = this;\n\n        // Render items and group them by module\n        // module === group\n        this.groups = {};\n        _.each(items, function (item, i) {\n\n          if (!item.private && item.file.indexOf('addons') === -1) { //addons don't get displayed on main page\n\n            var group = item.module || '_';\n            var subgroup = item.submodule || '_';\n            if (group === subgroup) {\n              subgroup = '0';\n            }\n            var hash = App.router.getHash(item);\n\n            // fixes broken links for #/p5/> and #/p5/>=\n            item.hash = item.hash.replace('>', '&gt;');\n\n            // Create a group list\n            if (!self.groups[group]) {\n              self.groups[group] = {\n                name: group.replace('_', '&nbsp;'),\n                subgroups: {}\n              };\n            }\n\n            // Create a subgroup list\n            if (!self.groups[group].subgroups[subgroup]) {\n              self.groups[group].subgroups[subgroup] = {\n                name: subgroup.replace('_', '&nbsp;'),\n                items: []\n              };\n            }\n\n            // hide the un-interesting constants\n            if (group === 'Constants' && !item.example)\n              return;\n\n            if (item.class === 'p5') {\n\n              self.groups[group].subgroups[subgroup].items.push(item);\n\n            } else {\n\n              var found = _.find(self.groups[group].subgroups[subgroup].items,\n                function(i){ return i.name == item.class; });\n\n              if (!found) {\n\n                // FIX TO INVISIBLE OBJECTS: DH (see also router.js)\n                var ind = hash.lastIndexOf('/');\n                hash = item.hash.substring(0, ind).replace('p5/','p5.');\n                self.groups[group].subgroups[subgroup].items.push({\n                  name: item.class,\n                  hash: hash\n                });\n              }\n\n            }\n          }\n        });\n\n        // Put the <li> items html into the list <ul>\n        var listHtml = self.listTpl({\n          'striptags': striptags,\n          'title': self.capitalizeFirst(listCollection),\n          'groups': self.groups,\n          'listCollection': listCollection\n        });\n\n        // Render the view\n        this.$el.html(listHtml);\n      }\n\n      var renderEvent = new Event('reference-rendered');\n      window.dispatchEvent(renderEvent);\n\n      return this;\n    },\n    /**\n     * Show a list of items.\n     * @param {array} items Array of item objects.\n     * @returns {object} This view.\n     */\n    show: function (listGroup) {\n      if (App[listGroup]) {\n        this.render(App[listGroup], listGroup);\n      }\n      App.pageView.hideContentViews();\n\n      this.$el.show();\n\n      return this;\n    },\n    /**\n     * Helper method to capitalize the first letter of a string\n     * @param {string} str\n     * @returns {string} Returns the string.\n     */\n    capitalizeFirst: function (str) {\n      return str.substr(0, 1).toUpperCase() + str.substr(1);\n    }\n\n\n\n  });\n\n  return listView;\n\n});\n\n",
        -    "\ndefine('text!tpl/item.html',[],function () { return '<h2><%=item.name%><% if (item.isMethod) { %>()<% } %></h2>\\n\\n<% if (item.example) { %>\\n<div class=\"example\">\\n  <h3 id=\"reference-example\">Examples</h3>\\n\\n  <div class=\"example-content\" data-alt=\"<%= item.alt %>\">\\n    <% _.each(item.example, function(example, i){ %>\\n      <%= example %>\\n    <% }); %>\\n  </div>\\n</div>\\n<% } %>\\n\\n<div class=\"description\">\\n    \\n  <h3 id=\"reference-description\">Description</h3>\\n\\n  <% if (item.deprecated) { %>\\n    <p>\\n      Deprecated: <%=item.name%><% if (item.isMethod) { %>()<% } %> is deprecated and will be removed in a future version of p5. <% if (item.deprecationMessage) { %><%=item.deprecationMessage%><% } %>\\n    </p>\\n  <% } %>\\n      \\n\\n  <span class=\\'description-text\\'><%= item.description %></span>\\n\\n  <% if (item.extends) { %>\\n    <p><span id=\"reference-extends\">Extends</span> <a href=\"/reference/#/<%=item.extends%>\" title=\"<%=item.extends%> reference\"><%=item.extends%></a></p>\\n  <% } %>\\n\\n  <% if (item.module === \\'p5.sound\\') { %>\\n    <p>This function requires you include the p5.sound library.  Add the following into the head of your index.html file:\\n      <pre><code class=\"language-javascript\">&lt;script src=\"path/to/p5.sound.js\"&gt;&lt;/script&gt;</code></pre>\\n    </p>\\n  <% } %>\\n\\n  <% if (item.constRefs) { %>\\n    <p>Used by:\\n  <%\\n      var refs = item.constRefs;\\n      for (var i = 0; i < refs.length; i ++) {\\n        var ref = refs[i];\\n        var name = ref;\\n        if (name.substr(0, 3) === \\'p5.\\') {\\n          name = name.substr(3);\\n        }\\n  if (i !== 0) {\\n          if (i == refs.length - 1) {\\n            %> and <%\\n          } else {\\n            %>, <%\\n          }\\n        }\\n        %><a href=\"./#/<%= ref.replace(\\'.\\', \\'/\\') %>\"><%= name %>()</a><%\\n      }\\n  %>\\n    </p>\\n  <% } %>\\n</div>\\n\\n<% if (isConstructor || !isClass) { %>\\n\\n<div>\\n  <h3 id=\"reference-syntax\">Syntax</h3>\\n  <p>\\n    <% syntaxes.forEach(function(syntax) { %>\\n    <pre><code class=\"language-javascript\"><%= syntax %></code></pre>\\n    <% }) %>\\n  </p>\\n</div>\\n\\n\\n<% if (item.params) { %>\\n  <div class=\"params\">\\n    <h3 id=\"reference-parameters\">Parameters</h3>\\n    <ul aria-labelledby=\\'reference-parameters\\'>\\n    <% for (var i=0; i<item.params.length; i++) { %>\\n      <% var p = item.params[i] %>\\n      <li>\\n        <div class=\\'paramname\\'><%=p.name%></div>\\n        <% if (p.type) { %>\\n          <div class=\\'paramtype\\'>\\n          <% var type = p.type.replace(/(p5\\\\.[A-Z][A-Za-z]*)/, \\'<a href=\"#/$1\">$1</a>\\'); %>\\n          <span class=\"param-type label label-info\"><%=type%></span>: <%=p.description%>\\n          <% if (p.optional) { %> (Optional)<% } %>\\n          </div>\\n        <% } %>\\n      </li>\\n    <% } %>\\n    </ul>\\n  </div>\\n<% } %>\\n\\n<% if (item.return && item.return.type) { %>\\n  <div>\\n    <h3 id=\"reference-returns\">Returns</h3>\\n    <p class=\\'returns\\'><span class=\"param-type label label-info\"><%=item.return.type%></span>: <%= item.return.description %></p>\\n  </div>\\n<% } %>\\n\\n<% } %>\\n';});\n\n",
        -    "\ndefine('text!tpl/class.html',[],function () { return '\\n<% if (typeof constructor !== \\'undefined\\') { %>\\n<div class=\"constructor\">\\n  <%=constructor%>\\n</div>\\n<% } %>\\n\\n<% let fields = _.filter(things, function(item) { return item.itemtype === \\'property\\' && item.access !== \\'private\\' }); %>\\n<% if (fields.length > 0) { %>\\n  <h3 id=\\'reference-fields\\'>Fields</h3>\\n  <ul aria-labelledby=\\'reference-fields\\'>\\n  <% _.each(fields, function(item) { %>\\n    <li>\\n      <div class=\\'paramname\\'><a href=\"<%=item.hash%>\" <% if (item.module !== module) { %>class=\"addon\"<% } %>><%=item.name%></a></div>\\n      <div class=\\'paramtype\\'><%= item.description %></div>\\n    </li>\\n  <% }); %>\\n  </ul>\\n<% } %>\\n\\n<% let methods = _.filter(things, function(item) { return item.itemtype === \\'method\\' && item.access !== \\'private\\' }); %>\\n<% if (methods.length > 0) { %>\\n  <h3 id=\\'reference-methods\\'>Methods</h3>\\n  <ul aria-labelledby=\\'reference-methods\\'>\\n    <% _.each(methods, function(item) { %>\\n      <li>\\n        <div class=\\'paramname\\'><a href=\"<%=item.hash%>\" <% if (item.module !== module) { %>class=\"addon\"<% } %>><%=item.name%><% if (item.itemtype === \\'method\\') { %>()<%}%></a></div>\\n        <div class=\\'paramtype\\'><%= item.description %></div>\\n      </li>\\n    <% }); %>\\n  </ul>\\n<% } %>\\n';});\n\n",
        -    "\ndefine('text!tpl/itemEnd.html',[],function () { return '\\n<br><br>\\n\\n<div>\\n<% if (item.file && item.line) { %>\\n<span id=\"reference-error1\">Notice any errors or typos?</span> <a href=\"https://github.com/processing/p5.js/issues\"><span id=\"reference-contribute2\">Please let us know.</span></a> <span id=\"reference-error3\">Please feel free to edit</span> <a href=\"https://github.com/processing/p5.js/blob/<%= appVersion %>/<%= item.file %>#L<%= item.line %>\" target=\"_blank\" ><%= item.file %></a> <span id=\"reference-error5\">and issue a pull request!</span>\\n<% } %>\\n</div>\\n\\n<a style=\"border-bottom:none !important;\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\" target=_blank><img src=\"https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png\" style=\"width:88px\" alt=\"creative commons logo\"/></a>\\n<br><br>\\n';});\n\n",
        -    "// Copyright (C) 2006 Google Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n\n/**\n * @fileoverview\n * some functions for browser-side pretty printing of code contained in html.\n *\n * <p>\n * For a fairly comprehensive set of languages see the\n * <a href=\"http://google-code-prettify.googlecode.com/svn/trunk/README.html#langs\">README</a>\n * file that came with this source.  At a minimum, the lexer should work on a\n * number of languages including C and friends, Java, Python, Bash, SQL, HTML,\n * XML, CSS, Javascript, and Makefiles.  It works passably on Ruby, PHP and Awk\n * and a subset of Perl, but, because of commenting conventions, doesn't work on\n * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class.\n * <p>\n * Usage: <ol>\n * <li> include this source file in an html page via\n *   {@code <script src=\"/path/to/prettify.js\"></script>}\n * <li> define style rules.  See the example page for examples.\n * <li> mark the {@code <pre>} and {@code <code>} tags in your source with\n *    {@code class=prettyprint.}\n *    You can also use the (html deprecated) {@code <xmp>} tag, but the pretty\n *    printer needs to do more substantial DOM manipulations to support that, so\n *    some css styles may not be preserved.\n * </ol>\n * That's it.  I wanted to keep the API as simple as possible, so there's no\n * need to specify which language the code is in, but if you wish, you can add\n * another class to the {@code <pre>} or {@code <code>} element to specify the\n * language, as in {@code <pre class=\"prettyprint lang-java\">}.  Any class that\n * starts with \"lang-\" followed by a file extension, specifies the file type.\n * See the \"lang-*.js\" files in this directory for code that implements\n * per-language file handlers.\n * <p>\n * Change log:<br>\n * cbeust, 2006/08/22\n * <blockquote>\n *   Java annotations (start with \"@\") are now captured as literals (\"lit\")\n * </blockquote>\n * @requires console\n */\n\n// JSLint declarations\n/*global console, document, navigator, setTimeout, window, define */\n\n/** @define {boolean} */\nvar IN_GLOBAL_SCOPE = true;\n\n/**\n * Split {@code prettyPrint} into multiple timeouts so as not to interfere with\n * UI events.\n * If set to {@code false}, {@code prettyPrint()} is synchronous.\n */\nwindow['PR_SHOULD_USE_CONTINUATION'] = true;\n\n/**\n * Pretty print a chunk of code.\n * @param {string} sourceCodeHtml The HTML to pretty print.\n * @param {string} opt_langExtension The language name to use.\n *     Typically, a filename extension like 'cpp' or 'java'.\n * @param {number|boolean} opt_numberLines True to number lines,\n *     or the 1-indexed number of the first line in sourceCodeHtml.\n * @return {string} code as html, but prettier\n */\nvar prettyPrintOne;\n/**\n * Find all the {@code <pre>} and {@code <code>} tags in the DOM with\n * {@code class=prettyprint} and prettify them.\n *\n * @param {Function} opt_whenDone called when prettifying is done.\n * @param {HTMLElement|HTMLDocument} opt_root an element or document\n *   containing all the elements to pretty print.\n *   Defaults to {@code document.body}.\n */\nvar prettyPrint;\n\n\n(function () {\n  var win = window;\n  // Keyword lists for various languages.\n  // We use things that coerce to strings to make them compact when minified\n  // and to defeat aggressive optimizers that fold large string constants.\n  var FLOW_CONTROL_KEYWORDS = [\"break,continue,do,else,for,if,return,while\"];\n  var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,\"auto,case,char,const,default,\" + \n      \"double,enum,extern,float,goto,inline,int,long,register,short,signed,\" +\n      \"sizeof,static,struct,switch,typedef,union,unsigned,void,volatile\"];\n  var COMMON_KEYWORDS = [C_KEYWORDS,\"catch,class,delete,false,import,\" +\n      \"new,operator,private,protected,public,this,throw,true,try,typeof\"];\n  var CPP_KEYWORDS = [COMMON_KEYWORDS,\"alignof,align_union,asm,axiom,bool,\" +\n      \"concept,concept_map,const_cast,constexpr,decltype,delegate,\" +\n      \"dynamic_cast,explicit,export,friend,generic,late_check,\" +\n      \"mutable,namespace,nullptr,property,reinterpret_cast,static_assert,\" +\n      \"static_cast,template,typeid,typename,using,virtual,where\"];\n  var JAVA_KEYWORDS = [COMMON_KEYWORDS,\n      \"abstract,assert,boolean,byte,extends,final,finally,implements,import,\" +\n      \"instanceof,interface,null,native,package,strictfp,super,synchronized,\" +\n      \"throws,transient\"];\n  var CSHARP_KEYWORDS = [JAVA_KEYWORDS,\n      \"as,base,by,checked,decimal,delegate,descending,dynamic,event,\" +\n      \"fixed,foreach,from,group,implicit,in,internal,into,is,let,\" +\n      \"lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,\" +\n      \"sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,\" +\n      \"var,virtual,where\"];\n  var COFFEE_KEYWORDS = \"all,and,by,catch,class,else,extends,false,finally,\" +\n      \"for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,\" +\n      \"throw,true,try,unless,until,when,while,yes\";\n  var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS,\n      \"debugger,eval,export,function,get,null,set,undefined,var,with,\" +\n      \"Infinity,NaN\"];\n  var PERL_KEYWORDS = \"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,\" +\n      \"goto,if,import,last,local,my,next,no,our,print,package,redo,require,\" +\n      \"sub,undef,unless,until,use,wantarray,while,BEGIN,END\";\n  var PYTHON_KEYWORDS = [FLOW_CONTROL_KEYWORDS, \"and,as,assert,class,def,del,\" +\n      \"elif,except,exec,finally,from,global,import,in,is,lambda,\" +\n      \"nonlocal,not,or,pass,print,raise,try,with,yield,\" +\n      \"False,True,None\"];\n  var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, \"alias,and,begin,case,class,\" +\n      \"def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,\" +\n      \"rescue,retry,self,super,then,true,undef,unless,until,when,yield,\" +\n      \"BEGIN,END\"];\n   var RUST_KEYWORDS = [FLOW_CONTROL_KEYWORDS, \"as,assert,const,copy,drop,\" +\n      \"enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,\" +\n      \"pub,pure,ref,self,static,struct,true,trait,type,unsafe,use\"];\n  var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, \"case,done,elif,esac,eval,fi,\" +\n      \"function,in,local,set,then,until\"];\n  var ALL_KEYWORDS = [\n      CPP_KEYWORDS, CSHARP_KEYWORDS, JSCRIPT_KEYWORDS, PERL_KEYWORDS,\n      PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS];\n  var C_TYPES = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\\d*)\\b/;\n\n  // token style names.  correspond to css classes\n  /**\n   * token style for a string literal\n   * @const\n   */\n  var PR_STRING = 'str';\n  /**\n   * token style for a keyword\n   * @const\n   */\n  var PR_KEYWORD = 'kwd';\n  /**\n   * token style for a comment\n   * @const\n   */\n  var PR_COMMENT = 'com';\n  /**\n   * token style for a type\n   * @const\n   */\n  var PR_TYPE = 'typ';\n  /**\n   * token style for a literal value.  e.g. 1, null, true.\n   * @const\n   */\n  var PR_LITERAL = 'lit';\n  /**\n   * token style for a punctuation string.\n   * @const\n   */\n  var PR_PUNCTUATION = 'pun';\n  /**\n   * token style for plain text.\n   * @const\n   */\n  var PR_PLAIN = 'pln';\n\n  /**\n   * token style for an sgml tag.\n   * @const\n   */\n  var PR_TAG = 'tag';\n  /**\n   * token style for a markup declaration such as a DOCTYPE.\n   * @const\n   */\n  var PR_DECLARATION = 'dec';\n  /**\n   * token style for embedded source.\n   * @const\n   */\n  var PR_SOURCE = 'src';\n  /**\n   * token style for an sgml attribute name.\n   * @const\n   */\n  var PR_ATTRIB_NAME = 'atn';\n  /**\n   * token style for an sgml attribute value.\n   * @const\n   */\n  var PR_ATTRIB_VALUE = 'atv';\n\n  /**\n   * A class that indicates a section of markup that is not code, e.g. to allow\n   * embedding of line numbers within code listings.\n   * @const\n   */\n  var PR_NOCODE = 'nocode';\n\n  \n  \n  /**\n   * A set of tokens that can precede a regular expression literal in\n   * javascript\n   * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html\n   * has the full list, but I've removed ones that might be problematic when\n   * seen in languages that don't support regular expression literals.\n   *\n   * <p>Specifically, I've removed any keywords that can't precede a regexp\n   * literal in a syntactically legal javascript program, and I've removed the\n   * \"in\" keyword since it's not a keyword in many languages, and might be used\n   * as a count of inches.\n   *\n   * <p>The link above does not accurately describe EcmaScript rules since\n   * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works\n   * very well in practice.\n   *\n   * @private\n   * @const\n   */\n  var REGEXP_PRECEDER_PATTERN = '(?:^^\\\\.?|[+-]|[!=]=?=?|\\\\#|%=?|&&?=?|\\\\(|\\\\*=?|[+\\\\-]=|->|\\\\/=?|::?|<<?=?|>>?>?=?|,|;|\\\\?|@|\\\\[|~|{|\\\\^\\\\^?=?|\\\\|\\\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\\\s*';\n  \n  // CAVEAT: this does not properly handle the case where a regular\n  // expression immediately follows another since a regular expression may\n  // have flags for case-sensitivity and the like.  Having regexp tokens\n  // adjacent is not valid in any language I'm aware of, so I'm punting.\n  // TODO: maybe style special characters inside a regexp as punctuation.\n\n  /**\n   * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally\n   * matches the union of the sets of strings matched by the input RegExp.\n   * Since it matches globally, if the input strings have a start-of-input\n   * anchor (/^.../), it is ignored for the purposes of unioning.\n   * @param {Array.<RegExp>} regexs non multiline, non-global regexs.\n   * @return {RegExp} a global regex.\n   */\n  function combinePrefixPatterns(regexs) {\n    var capturedGroupIndex = 0;\n  \n    var needToFoldCase = false;\n    var ignoreCase = false;\n    for (var i = 0, n = regexs.length; i < n; ++i) {\n      var regex = regexs[i];\n      if (regex.ignoreCase) {\n        ignoreCase = true;\n      } else if (/[a-z]/i.test(regex.source.replace(\n                     /\\\\u[0-9a-f]{4}|\\\\x[0-9a-f]{2}|\\\\[^ux]/gi, ''))) {\n        needToFoldCase = true;\n        ignoreCase = false;\n        break;\n      }\n    }\n  \n    var escapeCharToCodeUnit = {\n      'b': 8,\n      't': 9,\n      'n': 0xa,\n      'v': 0xb,\n      'f': 0xc,\n      'r': 0xd\n    };\n  \n    function decodeEscape(charsetPart) {\n      var cc0 = charsetPart.charCodeAt(0);\n      if (cc0 !== 92 /* \\\\ */) {\n        return cc0;\n      }\n      var c1 = charsetPart.charAt(1);\n      cc0 = escapeCharToCodeUnit[c1];\n      if (cc0) {\n        return cc0;\n      } else if ('0' <= c1 && c1 <= '7') {\n        return parseInt(charsetPart.substring(1), 8);\n      } else if (c1 === 'u' || c1 === 'x') {\n        return parseInt(charsetPart.substring(2), 16);\n      } else {\n        return charsetPart.charCodeAt(1);\n      }\n    }\n  \n    function encodeEscape(charCode) {\n      if (charCode < 0x20) {\n        return (charCode < 0x10 ? '\\\\x0' : '\\\\x') + charCode.toString(16);\n      }\n      var ch = String.fromCharCode(charCode);\n      return (ch === '\\\\' || ch === '-' || ch === ']' || ch === '^')\n          ? \"\\\\\" + ch : ch;\n    }\n  \n    function caseFoldCharset(charSet) {\n      var charsetParts = charSet.substring(1, charSet.length - 1).match(\n          new RegExp(\n              '\\\\\\\\u[0-9A-Fa-f]{4}'\n              + '|\\\\\\\\x[0-9A-Fa-f]{2}'\n              + '|\\\\\\\\[0-3][0-7]{0,2}'\n              + '|\\\\\\\\[0-7]{1,2}'\n              + '|\\\\\\\\[\\\\s\\\\S]'\n              + '|-'\n              + '|[^-\\\\\\\\]',\n              'g'));\n      var ranges = [];\n      var inverse = charsetParts[0] === '^';\n  \n      var out = ['['];\n      if (inverse) { out.push('^'); }\n  \n      for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {\n        var p = charsetParts[i];\n        if (/\\\\[bdsw]/i.test(p)) {  // Don't muck with named groups.\n          out.push(p);\n        } else {\n          var start = decodeEscape(p);\n          var end;\n          if (i + 2 < n && '-' === charsetParts[i + 1]) {\n            end = decodeEscape(charsetParts[i + 2]);\n            i += 2;\n          } else {\n            end = start;\n          }\n          ranges.push([start, end]);\n          // If the range might intersect letters, then expand it.\n          // This case handling is too simplistic.\n          // It does not deal with non-latin case folding.\n          // It works for latin source code identifiers though.\n          if (!(end < 65 || start > 122)) {\n            if (!(end < 65 || start > 90)) {\n              ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);\n            }\n            if (!(end < 97 || start > 122)) {\n              ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);\n            }\n          }\n        }\n      }\n  \n      // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]\n      // -> [[1, 12], [14, 14], [16, 17]]\n      ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });\n      var consolidatedRanges = [];\n      var lastRange = [];\n      for (var i = 0; i < ranges.length; ++i) {\n        var range = ranges[i];\n        if (range[0] <= lastRange[1] + 1) {\n          lastRange[1] = Math.max(lastRange[1], range[1]);\n        } else {\n          consolidatedRanges.push(lastRange = range);\n        }\n      }\n  \n      for (var i = 0; i < consolidatedRanges.length; ++i) {\n        var range = consolidatedRanges[i];\n        out.push(encodeEscape(range[0]));\n        if (range[1] > range[0]) {\n          if (range[1] + 1 > range[0]) { out.push('-'); }\n          out.push(encodeEscape(range[1]));\n        }\n      }\n      out.push(']');\n      return out.join('');\n    }\n  \n    function allowAnywhereFoldCaseAndRenumberGroups(regex) {\n      // Split into character sets, escape sequences, punctuation strings\n      // like ('(', '(?:', ')', '^'), and runs of characters that do not\n      // include any of the above.\n      var parts = regex.source.match(\n          new RegExp(\n              '(?:'\n              + '\\\\[(?:[^\\\\x5C\\\\x5D]|\\\\\\\\[\\\\s\\\\S])*\\\\]'  // a character set\n              + '|\\\\\\\\u[A-Fa-f0-9]{4}'  // a unicode escape\n              + '|\\\\\\\\x[A-Fa-f0-9]{2}'  // a hex escape\n              + '|\\\\\\\\[0-9]+'  // a back-reference or octal escape\n              + '|\\\\\\\\[^ux0-9]'  // other escape sequence\n              + '|\\\\(\\\\?[:!=]'  // start of a non-capturing group\n              + '|[\\\\(\\\\)\\\\^]'  // start/end of a group, or line start\n              + '|[^\\\\x5B\\\\x5C\\\\(\\\\)\\\\^]+'  // run of other characters\n              + ')',\n              'g'));\n      var n = parts.length;\n  \n      // Maps captured group numbers to the number they will occupy in\n      // the output or to -1 if that has not been determined, or to\n      // undefined if they need not be capturing in the output.\n      var capturedGroups = [];\n  \n      // Walk over and identify back references to build the capturedGroups\n      // mapping.\n      for (var i = 0, groupIndex = 0; i < n; ++i) {\n        var p = parts[i];\n        if (p === '(') {\n          // groups are 1-indexed, so max group index is count of '('\n          ++groupIndex;\n        } else if ('\\\\' === p.charAt(0)) {\n          var decimalValue = +p.substring(1);\n          if (decimalValue) {\n            if (decimalValue <= groupIndex) {\n              capturedGroups[decimalValue] = -1;\n            } else {\n              // Replace with an unambiguous escape sequence so that\n              // an octal escape sequence does not turn into a backreference\n              // to a capturing group from an earlier regex.\n              parts[i] = encodeEscape(decimalValue);\n            }\n          }\n        }\n      }\n  \n      // Renumber groups and reduce capturing groups to non-capturing groups\n      // where possible.\n      for (var i = 1; i < capturedGroups.length; ++i) {\n        if (-1 === capturedGroups[i]) {\n          capturedGroups[i] = ++capturedGroupIndex;\n        }\n      }\n      for (var i = 0, groupIndex = 0; i < n; ++i) {\n        var p = parts[i];\n        if (p === '(') {\n          ++groupIndex;\n          if (!capturedGroups[groupIndex]) {\n            parts[i] = '(?:';\n          }\n        } else if ('\\\\' === p.charAt(0)) {\n          var decimalValue = +p.substring(1);\n          if (decimalValue && decimalValue <= groupIndex) {\n            parts[i] = '\\\\' + capturedGroups[decimalValue];\n          }\n        }\n      }\n  \n      // Remove any prefix anchors so that the output will match anywhere.\n      // ^^ really does mean an anchored match though.\n      for (var i = 0; i < n; ++i) {\n        if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }\n      }\n  \n      // Expand letters to groups to handle mixing of case-sensitive and\n      // case-insensitive patterns if necessary.\n      if (regex.ignoreCase && needToFoldCase) {\n        for (var i = 0; i < n; ++i) {\n          var p = parts[i];\n          var ch0 = p.charAt(0);\n          if (p.length >= 2 && ch0 === '[') {\n            parts[i] = caseFoldCharset(p);\n          } else if (ch0 !== '\\\\') {\n            // TODO: handle letters in numeric escapes.\n            parts[i] = p.replace(\n                /[a-zA-Z]/g,\n                function (ch) {\n                  var cc = ch.charCodeAt(0);\n                  return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';\n                });\n          }\n        }\n      }\n  \n      return parts.join('');\n    }\n  \n    var rewritten = [];\n    for (var i = 0, n = regexs.length; i < n; ++i) {\n      var regex = regexs[i];\n      if (regex.global || regex.multiline) { throw new Error('' + regex); }\n      rewritten.push(\n          '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');\n    }\n  \n    return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');\n  }\n\n  /**\n   * Split markup into a string of source code and an array mapping ranges in\n   * that string to the text nodes in which they appear.\n   *\n   * <p>\n   * The HTML DOM structure:</p>\n   * <pre>\n   * (Element   \"p\"\n   *   (Element \"b\"\n   *     (Text  \"print \"))       ; #1\n   *   (Text    \"'Hello '\")      ; #2\n   *   (Element \"br\")            ; #3\n   *   (Text    \"  + 'World';\")) ; #4\n   * </pre>\n   * <p>\n   * corresponds to the HTML\n   * {@code <p><b>print </b>'Hello '<br>  + 'World';</p>}.</p>\n   *\n   * <p>\n   * It will produce the output:</p>\n   * <pre>\n   * {\n   *   sourceCode: \"print 'Hello '\\n  + 'World';\",\n   *   //                     1          2\n   *   //           012345678901234 5678901234567\n   *   spans: [0, #1, 6, #2, 14, #3, 15, #4]\n   * }\n   * </pre>\n   * <p>\n   * where #1 is a reference to the {@code \"print \"} text node above, and so\n   * on for the other text nodes.\n   * </p>\n   *\n   * <p>\n   * The {@code} spans array is an array of pairs.  Even elements are the start\n   * indices of substrings, and odd elements are the text nodes (or BR elements)\n   * that contain the text for those substrings.\n   * Substrings continue until the next index or the end of the source.\n   * </p>\n   *\n   * @param {Node} node an HTML DOM subtree containing source-code.\n   * @param {boolean} isPreformatted true if white-space in text nodes should\n   *    be considered significant.\n   * @return {Object} source code and the text nodes in which they occur.\n   */\n  function extractSourceSpans(node, isPreformatted) {\n    var nocode = /(?:^|\\s)nocode(?:\\s|$)/;\n  \n    var chunks = [];\n    var length = 0;\n    var spans = [];\n    var k = 0;\n  \n    function walk(node) {\n      var type = node.nodeType;\n      if (type == 1) {  // Element\n        if (nocode.test(node.className)) { return; }\n        for (var child = node.firstChild; child; child = child.nextSibling) {\n          walk(child);\n        }\n        var nodeName = node.nodeName.toLowerCase();\n        if ('br' === nodeName || 'li' === nodeName) {\n          chunks[k] = '\\n';\n          spans[k << 1] = length++;\n          spans[(k++ << 1) | 1] = node;\n        }\n      } else if (type == 3 || type == 4) {  // Text\n        var text = node.nodeValue;\n        if (text.length) {\n          if (!isPreformatted) {\n            text = text.replace(/[ \\t\\r\\n]+/g, ' ');\n          } else {\n            text = text.replace(/\\r\\n?/g, '\\n');  // Normalize newlines.\n          }\n          // TODO: handle tabs here?\n          chunks[k] = text;\n          spans[k << 1] = length;\n          length += text.length;\n          spans[(k++ << 1) | 1] = node;\n        }\n      }\n    }\n  \n    walk(node);\n  \n    return {\n      sourceCode: chunks.join('').replace(/\\n$/, ''),\n      spans: spans\n    };\n  }\n\n  /**\n   * Apply the given language handler to sourceCode and add the resulting\n   * decorations to out.\n   * @param {number} basePos the index of sourceCode within the chunk of source\n   *    whose decorations are already present on out.\n   */\n  function appendDecorations(basePos, sourceCode, langHandler, out) {\n    if (!sourceCode) { return; }\n    var job = {\n      sourceCode: sourceCode,\n      basePos: basePos\n    };\n    langHandler(job);\n    out.push.apply(out, job.decorations);\n  }\n\n  var notWs = /\\S/;\n\n  /**\n   * Given an element, if it contains only one child element and any text nodes\n   * it contains contain only space characters, return the sole child element.\n   * Otherwise returns undefined.\n   * <p>\n   * This is meant to return the CODE element in {@code <pre><code ...>} when\n   * there is a single child element that contains all the non-space textual\n   * content, but not to return anything where there are multiple child elements\n   * as in {@code <pre><code>...</code><code>...</code></pre>} or when there\n   * is textual content.\n   */\n  function childContentWrapper(element) {\n    var wrapper = undefined;\n    for (var c = element.firstChild; c; c = c.nextSibling) {\n      var type = c.nodeType;\n      wrapper = (type === 1)  // Element Node\n          ? (wrapper ? element : c)\n          : (type === 3)  // Text Node\n          ? (notWs.test(c.nodeValue) ? element : wrapper)\n          : wrapper;\n    }\n    return wrapper === element ? undefined : wrapper;\n  }\n\n  /** Given triples of [style, pattern, context] returns a lexing function,\n    * The lexing function interprets the patterns to find token boundaries and\n    * returns a decoration list of the form\n    * [index_0, style_0, index_1, style_1, ..., index_n, style_n]\n    * where index_n is an index into the sourceCode, and style_n is a style\n    * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to\n    * all characters in sourceCode[index_n-1:index_n].\n    *\n    * The stylePatterns is a list whose elements have the form\n    * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].\n    *\n    * Style is a style constant like PR_PLAIN, or can be a string of the\n    * form 'lang-FOO', where FOO is a language extension describing the\n    * language of the portion of the token in $1 after pattern executes.\n    * E.g., if style is 'lang-lisp', and group 1 contains the text\n    * '(hello (world))', then that portion of the token will be passed to the\n    * registered lisp handler for formatting.\n    * The text before and after group 1 will be restyled using this decorator\n    * so decorators should take care that this doesn't result in infinite\n    * recursion.  For example, the HTML lexer rule for SCRIPT elements looks\n    * something like ['lang-js', /<[s]cript>(.+?)<\\/script>/].  This may match\n    * '<script>foo()<\\/script>', which would cause the current decorator to\n    * be called with '<script>' which would not match the same rule since\n    * group 1 must not be empty, so it would be instead styled as PR_TAG by\n    * the generic tag rule.  The handler registered for the 'js' extension would\n    * then be called with 'foo()', and finally, the current decorator would\n    * be called with '<\\/script>' which would not match the original rule and\n    * so the generic tag rule would identify it as a tag.\n    *\n    * Pattern must only match prefixes, and if it matches a prefix, then that\n    * match is considered a token with the same style.\n    *\n    * Context is applied to the last non-whitespace, non-comment token\n    * recognized.\n    *\n    * Shortcut is an optional string of characters, any of which, if the first\n    * character, gurantee that this pattern and only this pattern matches.\n    *\n    * @param {Array} shortcutStylePatterns patterns that always start with\n    *   a known character.  Must have a shortcut string.\n    * @param {Array} fallthroughStylePatterns patterns that will be tried in\n    *   order if the shortcut ones fail.  May have shortcuts.\n    *\n    * @return {function (Object)} a\n    *   function that takes source code and returns a list of decorations.\n    */\n  function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {\n    var shortcuts = {};\n    var tokenizer;\n    (function () {\n      var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);\n      var allRegexs = [];\n      var regexKeys = {};\n      for (var i = 0, n = allPatterns.length; i < n; ++i) {\n        var patternParts = allPatterns[i];\n        var shortcutChars = patternParts[3];\n        if (shortcutChars) {\n          for (var c = shortcutChars.length; --c >= 0;) {\n            shortcuts[shortcutChars.charAt(c)] = patternParts;\n          }\n        }\n        var regex = patternParts[1];\n        var k = '' + regex;\n        if (!regexKeys.hasOwnProperty(k)) {\n          allRegexs.push(regex);\n          regexKeys[k] = null;\n        }\n      }\n      allRegexs.push(/[\\0-\\uffff]/);\n      tokenizer = combinePrefixPatterns(allRegexs);\n    })();\n\n    var nPatterns = fallthroughStylePatterns.length;\n\n    /**\n     * Lexes job.sourceCode and produces an output array job.decorations of\n     * style classes preceded by the position at which they start in\n     * job.sourceCode in order.\n     *\n     * @param {Object} job an object like <pre>{\n     *    sourceCode: {string} sourceText plain text,\n     *    basePos: {int} position of job.sourceCode in the larger chunk of\n     *        sourceCode.\n     * }</pre>\n     */\n    var decorate = function (job) {\n      var sourceCode = job.sourceCode, basePos = job.basePos;\n      /** Even entries are positions in source in ascending order.  Odd enties\n        * are style markers (e.g., PR_COMMENT) that run from that position until\n        * the end.\n        * @type {Array.<number|string>}\n        */\n      var decorations = [basePos, PR_PLAIN];\n      var pos = 0;  // index into sourceCode\n      var tokens = sourceCode.match(tokenizer) || [];\n      var styleCache = {};\n\n      for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {\n        var token = tokens[ti];\n        var style = styleCache[token];\n        var match = void 0;\n\n        var isEmbedded;\n        if (typeof style === 'string') {\n          isEmbedded = false;\n        } else {\n          var patternParts = shortcuts[token.charAt(0)];\n          if (patternParts) {\n            match = token.match(patternParts[1]);\n            style = patternParts[0];\n          } else {\n            for (var i = 0; i < nPatterns; ++i) {\n              patternParts = fallthroughStylePatterns[i];\n              match = token.match(patternParts[1]);\n              if (match) {\n                style = patternParts[0];\n                break;\n              }\n            }\n\n            if (!match) {  // make sure that we make progress\n              style = PR_PLAIN;\n            }\n          }\n\n          isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);\n          if (isEmbedded && !(match && typeof match[1] === 'string')) {\n            isEmbedded = false;\n            style = PR_SOURCE;\n          }\n\n          if (!isEmbedded) { styleCache[token] = style; }\n        }\n\n        var tokenStart = pos;\n        pos += token.length;\n\n        if (!isEmbedded) {\n          decorations.push(basePos + tokenStart, style);\n        } else {  // Treat group 1 as an embedded block of source code.\n          var embeddedSource = match[1];\n          var embeddedSourceStart = token.indexOf(embeddedSource);\n          var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;\n          if (match[2]) {\n            // If embeddedSource can be blank, then it would match at the\n            // beginning which would cause us to infinitely recurse on the\n            // entire token, so we catch the right context in match[2].\n            embeddedSourceEnd = token.length - match[2].length;\n            embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;\n          }\n          var lang = style.substring(5);\n          // Decorate the left of the embedded source\n          appendDecorations(\n              basePos + tokenStart,\n              token.substring(0, embeddedSourceStart),\n              decorate, decorations);\n          // Decorate the embedded source\n          appendDecorations(\n              basePos + tokenStart + embeddedSourceStart,\n              embeddedSource,\n              langHandlerForExtension(lang, embeddedSource),\n              decorations);\n          // Decorate the right of the embedded section\n          appendDecorations(\n              basePos + tokenStart + embeddedSourceEnd,\n              token.substring(embeddedSourceEnd),\n              decorate, decorations);\n        }\n      }\n      job.decorations = decorations;\n    };\n    return decorate;\n  }\n\n  /** returns a function that produces a list of decorations from source text.\n    *\n    * This code treats \", ', and ` as string delimiters, and \\ as a string\n    * escape.  It does not recognize perl's qq() style strings.\n    * It has no special handling for double delimiter escapes as in basic, or\n    * the tripled delimiters used in python, but should work on those regardless\n    * although in those cases a single string literal may be broken up into\n    * multiple adjacent string literals.\n    *\n    * It recognizes C, C++, and shell style comments.\n    *\n    * @param {Object} options a set of optional parameters.\n    * @return {function (Object)} a function that examines the source code\n    *     in the input job and builds the decoration list.\n    */\n  function sourceDecorator(options) {\n    var shortcutStylePatterns = [], fallthroughStylePatterns = [];\n    if (options['tripleQuotedStrings']) {\n      // '''multi-line-string''', 'single-line-string', and double-quoted\n      shortcutStylePatterns.push(\n          [PR_STRING,  /^(?:\\'\\'\\'(?:[^\\'\\\\]|\\\\[\\s\\S]|\\'{1,2}(?=[^\\']))*(?:\\'\\'\\'|$)|\\\"\\\"\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S]|\\\"{1,2}(?=[^\\\"]))*(?:\\\"\\\"\\\"|$)|\\'(?:[^\\\\\\']|\\\\[\\s\\S])*(?:\\'|$)|\\\"(?:[^\\\\\\\"]|\\\\[\\s\\S])*(?:\\\"|$))/,\n           null, '\\'\"']);\n    } else if (options['multiLineStrings']) {\n      // 'multi-line-string', \"multi-line-string\"\n      shortcutStylePatterns.push(\n          [PR_STRING,  /^(?:\\'(?:[^\\\\\\']|\\\\[\\s\\S])*(?:\\'|$)|\\\"(?:[^\\\\\\\"]|\\\\[\\s\\S])*(?:\\\"|$)|\\`(?:[^\\\\\\`]|\\\\[\\s\\S])*(?:\\`|$))/,\n           null, '\\'\"`']);\n    } else {\n      // 'single-line-string', \"single-line-string\"\n      shortcutStylePatterns.push(\n          [PR_STRING,\n           /^(?:\\'(?:[^\\\\\\'\\r\\n]|\\\\.)*(?:\\'|$)|\\\"(?:[^\\\\\\\"\\r\\n]|\\\\.)*(?:\\\"|$))/,\n           null, '\"\\'']);\n    }\n    if (options['verbatimStrings']) {\n      // verbatim-string-literal production from the C# grammar.  See issue 93.\n      fallthroughStylePatterns.push(\n          [PR_STRING, /^@\\\"(?:[^\\\"]|\\\"\\\")*(?:\\\"|$)/, null]);\n    }\n    var hc = options['hashComments'];\n    if (hc) {\n      if (options['cStyleComments']) {\n        if (hc > 1) {  // multiline hash comments\n          shortcutStylePatterns.push(\n              [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']);\n        } else {\n          // Stop C preprocessor declarations at an unclosed open comment\n          shortcutStylePatterns.push(\n              [PR_COMMENT, /^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\\b|[^\\r\\n]*)/,\n               null, '#']);\n        }\n        // #include <stdio.h>\n        fallthroughStylePatterns.push(\n            [PR_STRING,\n             /^<(?:(?:(?:\\.\\.\\/)*|\\/?)(?:[\\w-]+(?:\\/[\\w-]+)+)?[\\w-]+\\.h(?:h|pp|\\+\\+)?|[a-z]\\w*)>/,\n             null]);\n      } else {\n        shortcutStylePatterns.push([PR_COMMENT, /^#[^\\r\\n]*/, null, '#']);\n      }\n    }\n    if (options['cStyleComments']) {\n      fallthroughStylePatterns.push([PR_COMMENT, /^\\/\\/[^\\r\\n]*/, null]);\n      fallthroughStylePatterns.push(\n          [PR_COMMENT, /^\\/\\*[\\s\\S]*?(?:\\*\\/|$)/, null]);\n    }\n    var regexLiterals = options['regexLiterals'];\n    if (regexLiterals) {\n      /**\n       * @const\n       */\n      var regexExcls = regexLiterals > 1\n        ? ''  // Multiline regex literals\n        : '\\n\\r';\n      /**\n       * @const\n       */\n      var regexAny = regexExcls ? '.' : '[\\\\S\\\\s]';\n      /**\n       * @const\n       */\n      var REGEX_LITERAL = (\n          // A regular expression literal starts with a slash that is\n          // not followed by * or / so that it is not confused with\n          // comments.\n          '/(?=[^/*' + regexExcls + '])'\n          // and then contains any number of raw characters,\n          + '(?:[^/\\\\x5B\\\\x5C' + regexExcls + ']'\n          // escape sequences (\\x5C),\n          +    '|\\\\x5C' + regexAny\n          // or non-nesting character sets (\\x5B\\x5D);\n          +    '|\\\\x5B(?:[^\\\\x5C\\\\x5D' + regexExcls + ']'\n          +             '|\\\\x5C' + regexAny + ')*(?:\\\\x5D|$))+'\n          // finally closed by a /.\n          + '/');\n      fallthroughStylePatterns.push(\n          ['lang-regex',\n           RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')\n           ]);\n    }\n\n    var types = options['types'];\n    if (types) {\n      fallthroughStylePatterns.push([PR_TYPE, types]);\n    }\n\n    var keywords = (\"\" + options['keywords']).replace(/^ | $/g, '');\n    if (keywords.length) {\n      fallthroughStylePatterns.push(\n          [PR_KEYWORD,\n           new RegExp('^(?:' + keywords.replace(/[\\s,]+/g, '|') + ')\\\\b'),\n           null]);\n    }\n\n    shortcutStylePatterns.push([PR_PLAIN,       /^\\s+/, null, ' \\r\\n\\t\\xA0']);\n\n    var punctuation =\n      // The Bash man page says\n\n      // A word is a sequence of characters considered as a single\n      // unit by GRUB. Words are separated by metacharacters,\n      // which are the following plus space, tab, and newline: { }\n      // | & $ ; < >\n      // ...\n      \n      // A word beginning with # causes that word and all remaining\n      // characters on that line to be ignored.\n\n      // which means that only a '#' after /(?:^|[{}|&$;<>\\s])/ starts a\n      // comment but empirically\n      // $ echo {#}\n      // {#}\n      // $ echo \\$#\n      // $#\n      // $ echo }#\n      // }#\n\n      // so /(?:^|[|&;<>\\s])/ is more appropriate.\n\n      // http://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC3\n      // suggests that this definition is compatible with a\n      // default mode that tries to use a single token definition\n      // to recognize both bash/python style comments and C\n      // preprocessor directives.\n\n      // This definition of punctuation does not include # in the list of\n      // follow-on exclusions, so # will not be broken before if preceeded\n      // by a punctuation character.  We could try to exclude # after\n      // [|&;<>] but that doesn't seem to cause many major problems.\n      // If that does turn out to be a problem, we should change the below\n      // when hc is truthy to include # in the run of punctuation characters\n      // only when not followint [|&;<>].\n      '^.[^\\\\s\\\\w.$@\\'\"`/\\\\\\\\]*';\n    if (options['regexLiterals']) {\n      punctuation += '(?!\\s*\\/)';\n    }\n\n    fallthroughStylePatterns.push(\n        // TODO(mikesamuel): recognize non-latin letters and numerals in idents\n        [PR_LITERAL,     /^@[a-z_$][a-z_$@0-9]*/i, null],\n        [PR_TYPE,        /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\\w+_t\\b)/, null],\n        [PR_PLAIN,       /^[a-z_$][a-z_$@0-9]*/i, null],\n        [PR_LITERAL,\n         new RegExp(\n             '^(?:'\n             // A hex number\n             + '0x[a-f0-9]+'\n             // or an octal or decimal number,\n             + '|(?:\\\\d(?:_\\\\d+)*\\\\d*(?:\\\\.\\\\d*)?|\\\\.\\\\d\\\\+)'\n             // possibly in scientific notation\n             + '(?:e[+\\\\-]?\\\\d+)?'\n             + ')'\n             // with an optional modifier like UL for unsigned long\n             + '[a-z]*', 'i'),\n         null, '0123456789'],\n        // Don't treat escaped quotes in bash as starting strings.\n        // See issue 144.\n        [PR_PLAIN,       /^\\\\[\\s\\S]?/, null],\n        [PR_PUNCTUATION, new RegExp(punctuation), null]);\n\n    return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);\n  }\n\n  var decorateSource = sourceDecorator({\n        'keywords': ALL_KEYWORDS,\n        'hashComments': true,\n        'cStyleComments': true,\n        'multiLineStrings': true,\n        'regexLiterals': true\n      });\n\n  /**\n   * Given a DOM subtree, wraps it in a list, and puts each line into its own\n   * list item.\n   *\n   * @param {Node} node modified in place.  Its content is pulled into an\n   *     HTMLOListElement, and each line is moved into a separate list item.\n   *     This requires cloning elements, so the input might not have unique\n   *     IDs after numbering.\n   * @param {boolean} isPreformatted true iff white-space in text nodes should\n   *     be treated as significant.\n   */\n  function numberLines(node, opt_startLineNum, isPreformatted) {\n    var nocode = /(?:^|\\s)nocode(?:\\s|$)/;\n    var lineBreak = /\\r\\n?|\\n/;\n  \n    var document = node.ownerDocument;\n  \n    var li = document.createElement('li');\n    while (node.firstChild) {\n      li.appendChild(node.firstChild);\n    }\n    // An array of lines.  We split below, so this is initialized to one\n    // un-split line.\n    var listItems = [li];\n  \n    function walk(node) {\n      var type = node.nodeType;\n      if (type == 1 && !nocode.test(node.className)) {  // Element\n        if ('br' === node.nodeName) {\n          breakAfter(node);\n          // Discard the <BR> since it is now flush against a </LI>.\n          if (node.parentNode) {\n            node.parentNode.removeChild(node);\n          }\n        } else {\n          for (var child = node.firstChild; child; child = child.nextSibling) {\n            walk(child);\n          }\n        }\n      } else if ((type == 3 || type == 4) && isPreformatted) {  // Text\n        var text = node.nodeValue;\n        var match = text.match(lineBreak);\n        if (match) {\n          var firstLine = text.substring(0, match.index);\n          node.nodeValue = firstLine;\n          var tail = text.substring(match.index + match[0].length);\n          if (tail) {\n            var parent = node.parentNode;\n            parent.insertBefore(\n              document.createTextNode(tail), node.nextSibling);\n          }\n          breakAfter(node);\n          if (!firstLine) {\n            // Don't leave blank text nodes in the DOM.\n            node.parentNode.removeChild(node);\n          }\n        }\n      }\n    }\n  \n    // Split a line after the given node.\n    function breakAfter(lineEndNode) {\n      // If there's nothing to the right, then we can skip ending the line\n      // here, and move root-wards since splitting just before an end-tag\n      // would require us to create a bunch of empty copies.\n      while (!lineEndNode.nextSibling) {\n        lineEndNode = lineEndNode.parentNode;\n        if (!lineEndNode) { return; }\n      }\n  \n      function breakLeftOf(limit, copy) {\n        // Clone shallowly if this node needs to be on both sides of the break.\n        var rightSide = copy ? limit.cloneNode(false) : limit;\n        var parent = limit.parentNode;\n        if (parent) {\n          // We clone the parent chain.\n          // This helps us resurrect important styling elements that cross lines.\n          // E.g. in <i>Foo<br>Bar</i>\n          // should be rewritten to <li><i>Foo</i></li><li><i>Bar</i></li>.\n          var parentClone = breakLeftOf(parent, 1);\n          // Move the clone and everything to the right of the original\n          // onto the cloned parent.\n          var next = limit.nextSibling;\n          parentClone.appendChild(rightSide);\n          for (var sibling = next; sibling; sibling = next) {\n            next = sibling.nextSibling;\n            parentClone.appendChild(sibling);\n          }\n        }\n        return rightSide;\n      }\n  \n      var copiedListItem = breakLeftOf(lineEndNode.nextSibling, 0);\n  \n      // Walk the parent chain until we reach an unattached LI.\n      for (var parent;\n           // Check nodeType since IE invents document fragments.\n           (parent = copiedListItem.parentNode) && parent.nodeType === 1;) {\n        copiedListItem = parent;\n      }\n      // Put it on the list of lines for later processing.\n      listItems.push(copiedListItem);\n    }\n  \n    // Split lines while there are lines left to split.\n    for (var i = 0;  // Number of lines that have been split so far.\n         i < listItems.length;  // length updated by breakAfter calls.\n         ++i) {\n      walk(listItems[i]);\n    }\n  \n    // Make sure numeric indices show correctly.\n    if (opt_startLineNum === (opt_startLineNum|0)) {\n      listItems[0].setAttribute('value', opt_startLineNum);\n    }\n  \n    var ol = document.createElement('ol');\n    ol.className = 'linenums';\n    var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0;\n    for (var i = 0, n = listItems.length; i < n; ++i) {\n      li = listItems[i];\n      // Stick a class on the LIs so that stylesheets can\n      // color odd/even rows, or any other row pattern that\n      // is co-prime with 10.\n      li.className = 'L' + ((i + offset) % 10);\n      if (!li.firstChild) {\n        li.appendChild(document.createTextNode('\\xA0'));\n      }\n      ol.appendChild(li);\n    }\n  \n    node.appendChild(ol);\n  }\n  /**\n   * Breaks {@code job.sourceCode} around style boundaries in\n   * {@code job.decorations} and modifies {@code job.sourceNode} in place.\n   * @param {Object} job like <pre>{\n   *    sourceCode: {string} source as plain text,\n   *    sourceNode: {HTMLElement} the element containing the source,\n   *    spans: {Array.<number|Node>} alternating span start indices into source\n   *       and the text node or element (e.g. {@code <BR>}) corresponding to that\n   *       span.\n   *    decorations: {Array.<number|string} an array of style classes preceded\n   *       by the position at which they start in job.sourceCode in order\n   * }</pre>\n   * @private\n   */\n  function recombineTagsAndDecorations(job) {\n    var isIE8OrEarlier = /\\bMSIE\\s(\\d+)/.exec(navigator.userAgent);\n    isIE8OrEarlier = isIE8OrEarlier && +isIE8OrEarlier[1] <= 8;\n    var newlineRe = /\\n/g;\n  \n    var source = job.sourceCode;\n    var sourceLength = source.length;\n    // Index into source after the last code-unit recombined.\n    var sourceIndex = 0;\n  \n    var spans = job.spans;\n    var nSpans = spans.length;\n    // Index into spans after the last span which ends at or before sourceIndex.\n    var spanIndex = 0;\n  \n    var decorations = job.decorations;\n    var nDecorations = decorations.length;\n    // Index into decorations after the last decoration which ends at or before\n    // sourceIndex.\n    var decorationIndex = 0;\n  \n    // Remove all zero-length decorations.\n    decorations[nDecorations] = sourceLength;\n    var decPos, i;\n    for (i = decPos = 0; i < nDecorations;) {\n      if (decorations[i] !== decorations[i + 2]) {\n        decorations[decPos++] = decorations[i++];\n        decorations[decPos++] = decorations[i++];\n      } else {\n        i += 2;\n      }\n    }\n    nDecorations = decPos;\n  \n    // Simplify decorations.\n    for (i = decPos = 0; i < nDecorations;) {\n      var startPos = decorations[i];\n      // Conflate all adjacent decorations that use the same style.\n      var startDec = decorations[i + 1];\n      var end = i + 2;\n      while (end + 2 <= nDecorations && decorations[end + 1] === startDec) {\n        end += 2;\n      }\n      decorations[decPos++] = startPos;\n      decorations[decPos++] = startDec;\n      i = end;\n    }\n  \n    nDecorations = decorations.length = decPos;\n  \n    var sourceNode = job.sourceNode;\n    var oldDisplay;\n    if (sourceNode) {\n      oldDisplay = sourceNode.style.display;\n      sourceNode.style.display = 'none';\n    }\n    try {\n      var decoration = null;\n      while (spanIndex < nSpans) {\n        var spanStart = spans[spanIndex];\n        var spanEnd = spans[spanIndex + 2] || sourceLength;\n  \n        var decEnd = decorations[decorationIndex + 2] || sourceLength;\n  \n        var end = Math.min(spanEnd, decEnd);\n  \n        var textNode = spans[spanIndex + 1];\n        var styledText;\n        if (textNode.nodeType !== 1  // Don't muck with <BR>s or <LI>s\n            // Don't introduce spans around empty text nodes.\n            && (styledText = source.substring(sourceIndex, end))) {\n          // This may seem bizarre, and it is.  Emitting LF on IE causes the\n          // code to display with spaces instead of line breaks.\n          // Emitting Windows standard issue linebreaks (CRLF) causes a blank\n          // space to appear at the beginning of every line but the first.\n          // Emitting an old Mac OS 9 line separator makes everything spiffy.\n          if (isIE8OrEarlier) {\n            styledText = styledText.replace(newlineRe, '\\r');\n          }\n          textNode.nodeValue = styledText;\n          var document = textNode.ownerDocument;\n          var span = document.createElement('span');\n          span.className = decorations[decorationIndex + 1];\n          var parentNode = textNode.parentNode;\n          parentNode.replaceChild(span, textNode);\n          span.appendChild(textNode);\n          if (sourceIndex < spanEnd) {  // Split off a text node.\n            spans[spanIndex + 1] = textNode\n                // TODO: Possibly optimize by using '' if there's no flicker.\n                = document.createTextNode(source.substring(end, spanEnd));\n            parentNode.insertBefore(textNode, span.nextSibling);\n          }\n        }\n  \n        sourceIndex = end;\n  \n        if (sourceIndex >= spanEnd) {\n          spanIndex += 2;\n        }\n        if (sourceIndex >= decEnd) {\n          decorationIndex += 2;\n        }\n      }\n    } finally {\n      if (sourceNode) {\n        sourceNode.style.display = oldDisplay;\n      }\n    }\n  }\n\n  /** Maps language-specific file extensions to handlers. */\n  var langHandlerRegistry = {};\n  /** Register a language handler for the given file extensions.\n    * @param {function (Object)} handler a function from source code to a list\n    *      of decorations.  Takes a single argument job which describes the\n    *      state of the computation.   The single parameter has the form\n    *      {@code {\n    *        sourceCode: {string} as plain text.\n    *        decorations: {Array.<number|string>} an array of style classes\n    *                     preceded by the position at which they start in\n    *                     job.sourceCode in order.\n    *                     The language handler should assigned this field.\n    *        basePos: {int} the position of source in the larger source chunk.\n    *                 All positions in the output decorations array are relative\n    *                 to the larger source chunk.\n    *      } }\n    * @param {Array.<string>} fileExtensions\n    */\n  function registerLangHandler(handler, fileExtensions) {\n    for (var i = fileExtensions.length; --i >= 0;) {\n      var ext = fileExtensions[i];\n      if (!langHandlerRegistry.hasOwnProperty(ext)) {\n        langHandlerRegistry[ext] = handler;\n      } else if (win['console']) {\n        console['warn']('cannot override language handler %s', ext);\n      }\n    }\n  }\n  function langHandlerForExtension(extension, source) {\n    if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {\n      // Treat it as markup if the first non whitespace character is a < and\n      // the last non-whitespace character is a >.\n      extension = /^\\s*</.test(source)\n          ? 'default-markup'\n          : 'default-code';\n    }\n    return langHandlerRegistry[extension];\n  }\n  registerLangHandler(decorateSource, ['default-code']);\n  registerLangHandler(\n      createSimpleLexer(\n          [],\n          [\n           [PR_PLAIN,       /^[^<?]+/],\n           [PR_DECLARATION, /^<!\\w[^>]*(?:>|$)/],\n           [PR_COMMENT,     /^<\\!--[\\s\\S]*?(?:-\\->|$)/],\n           // Unescaped content in an unknown language\n           ['lang-',        /^<\\?([\\s\\S]+?)(?:\\?>|$)/],\n           ['lang-',        /^<%([\\s\\S]+?)(?:%>|$)/],\n           [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],\n           ['lang-',        /^<xmp\\b[^>]*>([\\s\\S]+?)<\\/xmp\\b[^>]*>/i],\n           // Unescaped content in javascript.  (Or possibly vbscript).\n           ['lang-js',      /^<script\\b[^>]*>([\\s\\S]*?)(<\\/script\\b[^>]*>)/i],\n           // Contains unescaped stylesheet content\n           ['lang-css',     /^<style\\b[^>]*>([\\s\\S]*?)(<\\/style\\b[^>]*>)/i],\n           ['lang-in.tag',  /^(<\\/?[a-z][^<>]*>)/i]\n          ]),\n      ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);\n  registerLangHandler(\n      createSimpleLexer(\n          [\n           [PR_PLAIN,        /^[\\s]+/, null, ' \\t\\r\\n'],\n           [PR_ATTRIB_VALUE, /^(?:\\\"[^\\\"]*\\\"?|\\'[^\\']*\\'?)/, null, '\\\"\\'']\n           ],\n          [\n           [PR_TAG,          /^^<\\/?[a-z](?:[\\w.:-]*\\w)?|\\/?>$/i],\n           [PR_ATTRIB_NAME,  /^(?!style[\\s=]|on)[a-z](?:[\\w:-]*\\w)?/i],\n           ['lang-uq.val',   /^=\\s*([^>\\'\\\"\\s]*(?:[^>\\'\\\"\\s\\/]|\\/(?=\\s)))/],\n           [PR_PUNCTUATION,  /^[=<>\\/]+/],\n           ['lang-js',       /^on\\w+\\s*=\\s*\\\"([^\\\"]+)\\\"/i],\n           ['lang-js',       /^on\\w+\\s*=\\s*\\'([^\\']+)\\'/i],\n           ['lang-js',       /^on\\w+\\s*=\\s*([^\\\"\\'>\\s]+)/i],\n           ['lang-css',      /^style\\s*=\\s*\\\"([^\\\"]+)\\\"/i],\n           ['lang-css',      /^style\\s*=\\s*\\'([^\\']+)\\'/i],\n           ['lang-css',      /^style\\s*=\\s*([^\\\"\\'>\\s]+)/i]\n           ]),\n      ['in.tag']);\n  registerLangHandler(\n      createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\\s\\S]+/]]), ['uq.val']);\n  registerLangHandler(sourceDecorator({\n          'keywords': CPP_KEYWORDS,\n          'hashComments': true,\n          'cStyleComments': true,\n          'types': C_TYPES\n        }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);\n  registerLangHandler(sourceDecorator({\n          'keywords': 'null,true,false'\n        }), ['json']);\n  registerLangHandler(sourceDecorator({\n          'keywords': CSHARP_KEYWORDS,\n          'hashComments': true,\n          'cStyleComments': true,\n          'verbatimStrings': true,\n          'types': C_TYPES\n        }), ['cs']);\n  registerLangHandler(sourceDecorator({\n          'keywords': JAVA_KEYWORDS,\n          'cStyleComments': true\n        }), ['java']);\n  registerLangHandler(sourceDecorator({\n          'keywords': SH_KEYWORDS,\n          'hashComments': true,\n          'multiLineStrings': true\n        }), ['bash', 'bsh', 'csh', 'sh']);\n  registerLangHandler(sourceDecorator({\n          'keywords': PYTHON_KEYWORDS,\n          'hashComments': true,\n          'multiLineStrings': true,\n          'tripleQuotedStrings': true\n        }), ['cv', 'py', 'python']);\n  registerLangHandler(sourceDecorator({\n          'keywords': PERL_KEYWORDS,\n          'hashComments': true,\n          'multiLineStrings': true,\n          'regexLiterals': 2  // multiline regex literals\n        }), ['perl', 'pl', 'pm']);\n  registerLangHandler(sourceDecorator({\n          'keywords': RUBY_KEYWORDS,\n          'hashComments': true,\n          'multiLineStrings': true,\n          'regexLiterals': true\n        }), ['rb', 'ruby']);\n  registerLangHandler(sourceDecorator({\n          'keywords': JSCRIPT_KEYWORDS,\n          'cStyleComments': true,\n          'regexLiterals': true\n        }), ['javascript', 'js']);\n  registerLangHandler(sourceDecorator({\n          'keywords': COFFEE_KEYWORDS,\n          'hashComments': 3,  // ### style block comments\n          'cStyleComments': true,\n          'multilineStrings': true,\n          'tripleQuotedStrings': true,\n          'regexLiterals': true\n        }), ['coffee']);\n  registerLangHandler(sourceDecorator({\n          'keywords': RUST_KEYWORDS,\n          'cStyleComments': true,\n          'multilineStrings': true\n        }), ['rc', 'rs', 'rust']);\n  registerLangHandler(\n      createSimpleLexer([], [[PR_STRING, /^[\\s\\S]+/]]), ['regex']);\n\n  function applyDecorator(job) {\n    var opt_langExtension = job.langExtension;\n\n    try {\n      // Extract tags, and convert the source code to plain text.\n      var sourceAndSpans = extractSourceSpans(job.sourceNode, job.pre);\n      /** Plain text. @type {string} */\n      var source = sourceAndSpans.sourceCode;\n      job.sourceCode = source;\n      job.spans = sourceAndSpans.spans;\n      job.basePos = 0;\n\n      // Apply the appropriate language handler\n      langHandlerForExtension(opt_langExtension, source)(job);\n\n      // Integrate the decorations and tags back into the source code,\n      // modifying the sourceNode in place.\n      recombineTagsAndDecorations(job);\n    } catch (e) {\n      if (win['console']) {\n        console['log'](e && e['stack'] || e);\n      }\n    }\n  }\n\n  /**\n   * Pretty print a chunk of code.\n   * @param sourceCodeHtml {string} The HTML to pretty print.\n   * @param opt_langExtension {string} The language name to use.\n   *     Typically, a filename extension like 'cpp' or 'java'.\n   * @param opt_numberLines {number|boolean} True to number lines,\n   *     or the 1-indexed number of the first line in sourceCodeHtml.\n   */\n  function $prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) {\n    var container = document.createElement('div');\n    // This could cause images to load and onload listeners to fire.\n    // E.g. <img onerror=\"alert(1337)\" src=\"nosuchimage.png\">.\n    // We assume that the inner HTML is from a trusted source.\n    // The pre-tag is required for IE8 which strips newlines from innerHTML\n    // when it is injected into a <pre> tag.\n    // http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie\n    // http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript\n    container.innerHTML = '<pre>' + sourceCodeHtml + '</pre>';\n    container = container.firstChild;\n    if (opt_numberLines) {\n      numberLines(container, opt_numberLines, true);\n    }\n\n    var job = {\n      langExtension: opt_langExtension,\n      numberLines: opt_numberLines,\n      sourceNode: container,\n      pre: 1\n    };\n    applyDecorator(job);\n    return container.innerHTML;\n  }\n\n   /**\n    * Find all the {@code <pre>} and {@code <code>} tags in the DOM with\n    * {@code class=prettyprint} and prettify them.\n    *\n    * @param {Function} opt_whenDone called when prettifying is done.\n    * @param {HTMLElement|HTMLDocument} opt_root an element or document\n    *   containing all the elements to pretty print.\n    *   Defaults to {@code document.body}.\n    */\n  function $prettyPrint(opt_whenDone, opt_root) {\n    var root = opt_root || document.body;\n    var doc = root.ownerDocument || document;\n    function byTagName(tn) { return root.getElementsByTagName(tn); }\n    // fetch a list of nodes to rewrite\n    var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];\n    var elements = [];\n    for (var i = 0; i < codeSegments.length; ++i) {\n      for (var j = 0, n = codeSegments[i].length; j < n; ++j) {\n        elements.push(codeSegments[i][j]);\n      }\n    }\n    codeSegments = null;\n\n    var clock = Date;\n    if (!clock['now']) {\n      clock = { 'now': function () { return +(new Date); } };\n    }\n\n    // The loop is broken into a series of continuations to make sure that we\n    // don't make the browser unresponsive when rewriting a large page.\n    var k = 0;\n    var prettyPrintingJob;\n\n    var langExtensionRe = /\\blang(?:uage)?-([\\w.]+)(?!\\S)/;\n    var prettyPrintRe = /\\bprettyprint\\b/;\n    var prettyPrintedRe = /\\bprettyprinted\\b/;\n    var preformattedTagNameRe = /pre|xmp/i;\n    var codeRe = /^code$/i;\n    var preCodeXmpRe = /^(?:pre|code|xmp)$/i;\n    var EMPTY = {};\n\n    function doWork() {\n      var endTime = (win['PR_SHOULD_USE_CONTINUATION'] ?\n                     clock['now']() + 250 /* ms */ :\n                     Infinity);\n      for (; k < elements.length && clock['now']() < endTime; k++) {\n        var cs = elements[k];\n\n        // Look for a preceding comment like\n        // <?prettify lang=\"...\" linenums=\"...\"?>\n        var attrs = EMPTY;\n        {\n          for (var preceder = cs; (preceder = preceder.previousSibling);) {\n            var nt = preceder.nodeType;\n            // <?foo?> is parsed by HTML 5 to a comment node (8)\n            // like <!--?foo?-->, but in XML is a processing instruction\n            var value = (nt === 7 || nt === 8) && preceder.nodeValue;\n            if (value\n                ? !/^\\??prettify\\b/.test(value)\n                : (nt !== 3 || /\\S/.test(preceder.nodeValue))) {\n              // Skip over white-space text nodes but not others.\n              break;\n            }\n            if (value) {\n              attrs = {};\n              value.replace(\n                  /\\b(\\w+)=([\\w:.%+-]+)/g,\n                function (_, name, value) { attrs[name] = value; });\n              break;\n            }\n          }\n        }\n\n        var className = cs.className;\n        if ((attrs !== EMPTY || prettyPrintRe.test(className))\n            // Don't redo this if we've already done it.\n            // This allows recalling pretty print to just prettyprint elements\n            // that have been added to the page since last call.\n            && !prettyPrintedRe.test(className)) {\n\n          // make sure this is not nested in an already prettified element\n          var nested = false;\n          for (var p = cs.parentNode; p; p = p.parentNode) {\n            var tn = p.tagName;\n            if (preCodeXmpRe.test(tn)\n                && p.className && prettyPrintRe.test(p.className)) {\n              nested = true;\n              break;\n            }\n          }\n          if (!nested) {\n            // Mark done.  If we fail to prettyprint for whatever reason,\n            // we shouldn't try again.\n            cs.className += ' prettyprinted';\n\n            // If the classes includes a language extensions, use it.\n            // Language extensions can be specified like\n            //     <pre class=\"prettyprint lang-cpp\">\n            // the language extension \"cpp\" is used to find a language handler\n            // as passed to PR.registerLangHandler.\n            // HTML5 recommends that a language be specified using \"language-\"\n            // as the prefix instead.  Google Code Prettify supports both.\n            // http://dev.w3.org/html5/spec-author-view/the-code-element.html\n            var langExtension = attrs['lang'];\n            if (!langExtension) {\n              langExtension = className.match(langExtensionRe);\n              // Support <pre class=\"prettyprint\"><code class=\"language-c\">\n              var wrapper;\n              if (!langExtension && (wrapper = childContentWrapper(cs))\n                  && codeRe.test(wrapper.tagName)) {\n                langExtension = wrapper.className.match(langExtensionRe);\n              }\n\n              if (langExtension) { langExtension = langExtension[1]; }\n            }\n\n            var preformatted;\n            if (preformattedTagNameRe.test(cs.tagName)) {\n              preformatted = 1;\n            } else {\n              var currentStyle = cs['currentStyle'];\n              var defaultView = doc.defaultView;\n              var whitespace = (\n                  currentStyle\n                  ? currentStyle['whiteSpace']\n                  : (defaultView\n                     && defaultView.getComputedStyle)\n                  ? defaultView.getComputedStyle(cs, null)\n                  .getPropertyValue('white-space')\n                  : 0);\n              preformatted = whitespace\n                  && 'pre' === whitespace.substring(0, 3);\n            }\n\n            // Look for a class like linenums or linenums:<n> where <n> is the\n            // 1-indexed number of the first line.\n            var lineNums = attrs['linenums'];\n            if (!(lineNums = lineNums === 'true' || +lineNums)) {\n              lineNums = className.match(/\\blinenums\\b(?::(\\d+))?/);\n              lineNums =\n                lineNums\n                ? lineNums[1] && lineNums[1].length\n                  ? +lineNums[1] : true\n                : false;\n            }\n            if (lineNums) { numberLines(cs, lineNums, preformatted); }\n\n            // do the pretty printing\n            prettyPrintingJob = {\n              langExtension: langExtension,\n              sourceNode: cs,\n              numberLines: lineNums,\n              pre: preformatted\n            };\n            applyDecorator(prettyPrintingJob);\n          }\n        }\n      }\n      if (k < elements.length) {\n        // finish up in a continuation\n        setTimeout(doWork, 250);\n      } else if ('function' === typeof opt_whenDone) {\n        opt_whenDone();\n      }\n    }\n\n    doWork();\n  }\n\n  /**\n   * Contains functions for creating and registering new language handlers.\n   * @type {Object}\n   */\n  var PR = win['PR'] = {\n        'createSimpleLexer': createSimpleLexer,\n        'registerLangHandler': registerLangHandler,\n        'sourceDecorator': sourceDecorator,\n        'PR_ATTRIB_NAME': PR_ATTRIB_NAME,\n        'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,\n        'PR_COMMENT': PR_COMMENT,\n        'PR_DECLARATION': PR_DECLARATION,\n        'PR_KEYWORD': PR_KEYWORD,\n        'PR_LITERAL': PR_LITERAL,\n        'PR_NOCODE': PR_NOCODE,\n        'PR_PLAIN': PR_PLAIN,\n        'PR_PUNCTUATION': PR_PUNCTUATION,\n        'PR_SOURCE': PR_SOURCE,\n        'PR_STRING': PR_STRING,\n        'PR_TAG': PR_TAG,\n        'PR_TYPE': PR_TYPE,\n        'prettyPrintOne':\n           IN_GLOBAL_SCOPE\n             ? (win['prettyPrintOne'] = $prettyPrintOne)\n             : (prettyPrintOne = $prettyPrintOne),\n        'prettyPrint': prettyPrint =\n           IN_GLOBAL_SCOPE\n             ? (win['prettyPrint'] = $prettyPrint)\n             : (prettyPrint = $prettyPrint)\n      };\n\n  // Make PR available via the Asynchronous Module Definition (AMD) API.\n  // Per https://github.com/amdjs/amdjs-api/wiki/AMD:\n  // The Asynchronous Module Definition (AMD) API specifies a\n  // mechanism for defining modules such that the module and its\n  // dependencies can be asynchronously loaded.\n  // ...\n  // To allow a clear indicator that a global define function (as\n  // needed for script src browser loading) conforms to the AMD API,\n  // any global define function SHOULD have a property called \"amd\"\n  // whose value is an object. This helps avoid conflict with any\n  // other existing JavaScript code that could have defined a define()\n  // function that does not conform to the AMD API.\n  if (typeof define === \"function\" && define['amd']) {\n    define(\"google-code-prettify\", [], function () {\n      return PR; \n    });\n  }\n})();\n\ndefine(\"prettify\", function(){});\n\n",
        -    "define('itemView',[\n  'App',\n  // Templates\n  'text!tpl/item.html',\n  'text!tpl/class.html',\n  'text!tpl/itemEnd.html',\n  // Tools\n  'prettify'\n], function(App, itemTpl, classTpl, endTpl) {\n  'use strict';\n\n  var appVersion = App.project.version || 'master';\n\n  var itemView = Backbone.View.extend({\n    el: '#item',\n    init: function() {\n      this.$html = $('html');\n      this.$body = $('body');\n      this.$scrollBody = $('html, body'); // hack for Chrome/Firefox scroll\n\n      this.tpl = _.template(itemTpl);\n      this.classTpl = _.template(classTpl);\n      this.endTpl = _.template(endTpl);\n\n      return this;\n    },\n    getSyntax: function(isMethod, cleanItem) {\n      var isConstructor = cleanItem.is_constructor;\n      var syntax = '';\n      if (isConstructor) {\n        syntax += 'new ';\n      } else if (cleanItem.static && cleanItem.class) {\n        syntax += cleanItem.class + '.';\n      }\n      syntax += cleanItem.name;\n\n      if (isMethod || isConstructor) {\n        syntax += '(';\n        if (cleanItem.params) {\n          for (var i = 0; i < cleanItem.params.length; i++) {\n            var p = cleanItem.params[i];\n            if (p.optional) {\n              syntax += '[';\n            }\n            syntax += p.name;\n            if (p.optdefault) {\n              syntax += '=' + p.optdefault;\n            }\n            if (p.optional) {\n              syntax += ']';\n            }\n            if (i !== cleanItem.params.length - 1) {\n              syntax += ', ';\n            }\n          }\n        }\n        syntax += ')';\n      }\n\n      return syntax;\n    },\n    // Return a list of valid syntaxes across all overloaded versions of\n    // this item.\n    //\n    // For reference, we ultimately want to replicate something like this:\n    //\n    // https://processing.org/reference/color_.html\n    getSyntaxes: function(isMethod, cleanItem) {\n      var overloads = cleanItem.overloads || [cleanItem];\n      return overloads.map(this.getSyntax.bind(this, isMethod));\n    },\n    render: function(item) {\n      if (item) {\n        var itemHtml = '';\n        var cleanItem = this.clean(item);\n        var isClass = item.hasOwnProperty('itemtype') ? 0 : 1;\n        var collectionName = isClass\n            ? 'Constructor'\n            : this.capitalizeFirst(cleanItem.itemtype),\n          isConstructor = cleanItem.is_constructor;\n        cleanItem.isMethod = collectionName === 'Method';\n\n        var syntaxes = this.getSyntaxes(cleanItem.isMethod, cleanItem);\n\n        // Set the item header (title)\n\n        // Set item contents\n        if (isClass) {\n          var constructor = this.tpl({\n            item: cleanItem,\n            isClass: true,\n            isConstructor: isConstructor,\n            syntaxes: syntaxes\n          });\n          cleanItem.constructor = constructor;\n\n          var contents = _.find(App.classes, function(c) {\n            return c.name === cleanItem.name;\n          });\n          cleanItem.things = contents.items;\n\n          itemHtml = this.classTpl(cleanItem);\n        } else {\n          cleanItem.constRefs =\n            item.module === 'Constants' && App.data.consts[item.name];\n\n          itemHtml = this.tpl({\n            item: cleanItem,\n            isClass: false,\n            isConstructor: false,\n            syntaxes: syntaxes\n          });\n        }\n\n        itemHtml += this.endTpl({ item: cleanItem, appVersion: appVersion });\n\n        // Insert the view in the dom\n        this.$el.html(itemHtml);\n\n        renderCode(cleanItem.name);\n\n        // Set the document title based on the item name.\n        // If it is a method, add parentheses to the name\n        if (item.itemtype === 'method') {\n          App.pageView.appendToDocumentTitle(item.name + '()');\n        } else {\n          App.pageView.appendToDocumentTitle(item.name);\n        }\n\n        // Hook up alt-text for examples\n        setTimeout(function() {\n          var alts = $('.example-content')[0];\n          if (alts) {\n            alts = $(alts)\n              .data('alt')\n              .split('\\n');\n\n            var canvases = $('.cnv_div');\n            for (var j = 0; j < alts.length; j++) {\n              if (j < canvases.length) {\n                $(canvases[j]).append(\n                  '<span class=\"sr-only\">' + alts[j] + '</span>'\n                );\n              }\n            }\n          }\n        }, 1000);\n        Prism.highlightAll();\n      }\n\n      var renderEvent = new Event('reference-rendered');\n      window.dispatchEvent(renderEvent);\n\n      return this;\n    },\n    /**\n     * Clean item properties: url encode properties containing paths.\n     * @param {object} item The item object.\n     * @returns {object} Returns the same item object with urlencoded paths.\n     */\n    clean: function(item) {\n      var cleanItem = item;\n\n      if (cleanItem.hasOwnProperty('file')) {\n        cleanItem.urlencodedfile = encodeURIComponent(item.file);\n      }\n      return cleanItem;\n    },\n    /**\n     * Show a single item.\n     * @param {object} item Item object.\n     * @returns {object} This view.\n     */\n    show: function(item) {\n      if (item) {\n        this.render(item);\n      }\n\n      App.pageView.hideContentViews();\n\n      this.$el.show();\n\n      this.scrollTop();\n      $('#item').focus();\n      return this;\n    },\n    /**\n     * Show a message if no item is found.\n     * @returns {object} This view.\n     */\n    nothingFound: function() {\n      this.$el.html(\n        '<p><br><br>Ouch. I am unable to find any item that match the current query.</p>'\n      );\n      App.pageView.hideContentViews();\n      this.$el.show();\n\n      return this;\n    },\n    /**\n     * Scroll to the top of the window with an animation.\n     */\n    scrollTop: function() {\n      // Hack for Chrome/Firefox scroll animation\n      // Chrome scrolls 'body', Firefox scrolls 'html'\n      var scroll = this.$body.scrollTop() > 0 || this.$html.scrollTop() > 0;\n      if (scroll) {\n        this.$scrollBody.animate({ scrollTop: 0 }, 600);\n      }\n    },\n    /**\n     * Helper method to capitalize the first letter of a string\n     * @param {string} str\n     * @returns {string} Returns the string.\n     */\n    capitalizeFirst: function(str) {\n      return str.substr(0, 1).toUpperCase() + str.substr(1);\n    }\n  });\n\n  return itemView;\n});\n\n",
        -    "\ndefine('text!tpl/menu.html',[],function () { return '<div>\\n  <br>\\n  <span id=\"reference-description1\">Can\\'t find what you\\'re looking for? You may want to check out</span>\\n  <a href=\"#/libraries/p5.sound\">p5.sound</a>.<br><a href=\\'https://p5js.org/offline-reference/p5-reference.zip\\' target=_blank><span id=\"reference-description3\">You can also download an offline version of the reference.</span></a>\\n</div>\\n\\n<div id=\\'collection-list-categories\\'>\\n<h2 class=\"sr-only\" id=\"categories\">Categories</h2>\\n<% var i=0; %>\\n<% var max=Math.floor(groups.length/4); %>\\n<% var rem=groups.length%4; %>\\n\\n<% _.each(groups, function(group){ %>\\n  <% var m = rem > 0 ? 1 : 0 %>\\n  <% if (i === 0) { %>\\n    <ul aria-labelledby=\"categories\">\\n    <% } %>\\n    <li><a href=\"#group-<%=group%>\"><%=group%></a></li>\\n    <% if (i === (max+m-1)) { %>\\n    </ul>\\n  \\t<% rem-- %>\\n  \\t<% i=0 %>\\n  <% } else { %>\\n  \\t<% i++ %>\\n  <% } %>\\n<% }); %>\\n</div>\\n';});\n\n",
        -    "define('menuView',[\n  'App',\n  'text!tpl/menu.html'\n], function(App, menuTpl) {\n\n  var menuView = Backbone.View.extend({\n    el: '#collection-list-nav',\n    /**\n     * Init.\n     * @returns {object} This view.\n     */\n    init: function() {\n      this.menuTpl = _.template(menuTpl);\n      return this;\n    },\n    /**\n     * Render.\n     * @returns {object} This view.\n     */\n    render: function() {\n\n      var groups = [];\n      _.each(App.modules, function (item, i) {\n        if (!item.is_submodule) {\n          if (!item.file || item.file.indexOf('addons') === -1) { //addons don't get displayed on main page\n            groups.push(item.name);\n          }\n        }\n        //}\n      });\n\n      // Sort groups by name A-Z\n      groups.sort();\n\n      var menuHtml = this.menuTpl({\n        'groups': groups\n      });\n\n      // Render the view\n      this.$el.html(menuHtml);\n    },\n\n    hide: function() {\n      this.$el.hide();\n    },\n\n    show: function() {\n      this.$el.show();\n    },\n\n    /**\n     * Update the menu.\n     * @param {string} el The name of the current route.\n     */\n    update: function(menuItem) {\n      //console.log(menuItem);\n      // this.$menuItems.removeClass('active');\n      // this.$menuItems.find('a[href=#'+menuItem+']').parent().addClass('active');\n\n    }\n  });\n\n  return menuView;\n\n});\n\n",
        -    "\ndefine('text!tpl/library.html',[],function () { return '<h3><%= module.name %> library</h3>\\n\\n<p><%= module.description %></p>\\n\\n<div id=\"library-page\" class=\"reference-group clearfix\">  \\n\\n<% var t = 0; col = 0; %>\\n\\n<% _.each(groups, function(group){ %>\\n  <% if (t == 0) { %> \\n    <div class=\"column_<%=col%>\">\\n  <% } %>\\n  <% if (group.name !== module.name && group.name !== \\'p5\\') { %>\\n    <% if (group.hash) { %> <a href=\"<%=group.hash%>\" <% if (group.module !== module.name) { %>class=\"core\"<% } %>><% } %>  \\n    <h4 class=\"group-name <% if (t == 0) { %> first<%}%>\"><%=group.name%></h4>\\n    <% if (group.hash) { %> </a><br> <% } %>\\n  <% } %>\\n  <% _.each(group.items.filter(function(item) {return item.access !== \\'private\\'}), function(item) { %>\\n    <a href=\"<%=item.hash%>\" <% if (item.module !== module.name) { %>class=\"core\"<% } %>><%=item.name%><% if (item.itemtype === \\'method\\') { %>()<%}%></a><br>\\n    <% t++; %>\\n  <% }); %>\\n  <% if (t >= Math.floor(totalItems/4)) { col++; t = 0; %>\\n    </div>\\n  <% } %>\\n<% }); %>\\n</div>\\n';});\n\n",
        -    "define(\n  'libraryView',[\n    'App',\n    // Templates\n    'text!tpl/library.html'\n  ],\n  function(App, libraryTpl) {\n    var libraryView = Backbone.View.extend({\n      el: '#list',\n      events: {},\n      /**\n       * Init.\n       */\n      init: function() {\n        this.libraryTpl = _.template(libraryTpl);\n\n        return this;\n      },\n      /**\n       * Render the list.\n       */\n      render: function(m, listCollection) {\n        if (m && listCollection) {\n          var self = this;\n\n          // Render items and group them by module\n          // module === group\n          this.groups = {};\n          _.each(m.items, function(item, i) {\n            var module = item.module || '_';\n            var group;\n            // Override default group with a selected category\n            // TODO: Overwriting with the first category might not be the best choice\n            // We might also want to have links for categories\n            if (item.category && item.category[0]) {\n              group = item.category[0];\n              // Populate item.hash\n              App.router.getHash(item);\n\n              // Create a group list without link hash\n              if (!self.groups[group]) {\n                self.groups[group] = {\n                  name: group.replace('_', '&nbsp;'),\n                  module: module,\n                  hash: undefined,\n                  items: []\n                };\n              }\n            } else {\n              group = item.class || '_';\n              var hash = App.router.getHash(item);\n\n              var ind = hash.lastIndexOf('/');\n              hash = hash.substring(0, ind);\n\n              // Create a group list\n              if (!self.groups[group]) {\n                self.groups[group] = {\n                  name: group.replace('_', '&nbsp;'),\n                  module: module,\n                  hash: hash,\n                  items: []\n                };\n              }\n            }\n\n            self.groups[group].items.push(item);\n          });\n\n          // Sort groups by name A-Z\n          self.groups = _.sortBy(self.groups, this.sortByName);\n\n          // Put the <li> items html into the list <ul>\n          var libraryHtml = self.libraryTpl({\n            title: self.capitalizeFirst(listCollection),\n            module: m.module,\n            totalItems: m.items.length,\n            groups: self.groups\n          });\n\n          // Render the view\n          this.$el.html(libraryHtml);\n        }\n\n        return this;\n      },\n      /**\n       * Show a list of items.\n       * @param {array} items Array of item objects.\n       * @returns {object} This view.\n       */\n      show: function(listGroup) {\n        if (App[listGroup]) {\n          this.render(App[listGroup], listGroup);\n        }\n        App.pageView.hideContentViews();\n\n        this.$el.show();\n\n        return this;\n      },\n      /**\n       * Helper method to capitalize the first letter of a string\n       * @param {string} str\n       * @returns {string} Returns the string.\n       */\n      capitalizeFirst: function(str) {\n        return str.substr(0, 1).toUpperCase() + str.substr(1);\n      },\n      /**\n       * Sort function (for the Array.prototype.sort() native method): from A to Z.\n       * @param {string} a\n       * @param {string} b\n       * @returns {Array} Returns an array with elements sorted from A to Z.\n       */\n      sortAZ: function(a, b) {\n        return a.innerHTML.toLowerCase() > b.innerHTML.toLowerCase() ? 1 : -1;\n      },\n\n      sortByName: function(a, b) {\n        if (a.name === 'p5') return -1;\n        else return 0;\n      }\n    });\n\n    return libraryView;\n  }\n);\n\n",
        -    "define('pageView',[\n  'App',\n\n  // Views\n  'searchView',\n  'listView',\n  'itemView',\n  'menuView',\n  'libraryView'\n], function(App, searchView, listView, itemView, menuView, libraryView) {\n\n  // Store the original title parts so we can substitue different endings.\n  var _originalDocumentTitle = window.document.title;\n\n  var pageView = Backbone.View.extend({\n    el: 'body',\n    /**\n     * Init.\n     */\n    init: function() {\n      App.$container = $('#container');\n      App.contentViews = [];\n\n      return this;\n    },\n    /**\n     * Render.\n     */\n    render: function() {\n\n      // Menu view\n      if (!App.menuView) {\n        App.menuView = new menuView();\n        App.menuView.init().render();\n      }\n\n      // Item view\n      if (!App.itemView) {\n        App.itemView = new itemView();\n        App.itemView.init().render();\n        // Add the item view to the views array\n        App.contentViews.push(App.itemView);\n      }\n\n      // List view\n      if (!App.listView) {\n        App.listView = new listView();\n        App.listView.init().render();\n        // Add the list view to the views array\n        App.contentViews.push(App.listView);\n      }\n\n      // Library view\n      if (!App.libraryView) {\n        App.libraryView = new libraryView();\n        App.libraryView.init().render();\n        // Add the list view to the views array\n        App.contentViews.push(App.libraryView);\n      }\n\n      // Search\n      if (!App.searchView) {\n        App.searchView = new searchView();\n        App.searchView.init().render();\n      }\n      return this;\n    },\n    /**\n     * Hide item and list views.\n     * @returns {object} This view.\n     */\n    hideContentViews: function() {\n      _.each(App.contentViews, function(view, i) {\n        view.$el.hide();\n      });\n\n      return this;\n    },\n    /**\n     * Append the supplied name to the first part of original document title.\n     * If no name is supplied, the title will reset to the original one.\n     */\n    appendToDocumentTitle: function(name){\n      if(name){\n        let firstTitlePart = _originalDocumentTitle.split(\" | \")[0];\n        window.document.title = [firstTitlePart, name].join(\" | \");\n      } else {\n        window.document.title = _originalDocumentTitle;\n      }\n    }    \n  });\n\n  return pageView;\n\n});\n\n",
        -    "define('router',[\n  'App'\n], function(App) {\n\n  'use strict'; //\n\n  var Router = Backbone.Router.extend({\n\n    routes: {\n      '': 'list',\n      'p5': 'list',\n      'p5/': 'list',\n      'classes': 'list',\n      'search': 'search',\n      'libraries/:lib': 'library',\n      ':searchClass(/:searchItem)': 'get'\n    },\n    /**\n     * Whether the json API data was loaded.\n     */\n    _initialized: false,\n    /**\n     * Initialize the app: load json API data and create searchable arrays.\n     */\n    init: function(callback) {\n      var self = this;\n      require(['pageView'], function(pageView) {\n\n        // If already initialized, move away from here!\n        if (self._initialized) {\n          if (callback)\n            callback();\n          return;\n        }\n\n        // Update initialization state: must be done now to avoid recursive mess\n        self._initialized = true;\n\n        // Render views\n        if (!App.pageView) {\n          App.pageView = new pageView();\n          App.pageView.init().render();\n        }\n\n        // If a callback is set (a route has already been called), run it\n        // otherwise, show the default list\n        if (callback)\n          callback();\n        else\n          self.list();\n      });\n    },\n    /**\n     * Start route. Simply check if initialized.\n     */\n    start: function() {\n      this.init();\n    },\n    /**\n     * Show item details by searching a class or a class item (method, property or event).\n     * @param {string} searchClass The class name (mandatory).\n     * @param {string} searchItem The class item name: can be a method, property or event name.\n     */\n    get: function(searchClass, searchItem) {\n\n      // if looking for a library page, redirect\n      if (searchClass === 'p5.sound' && !searchItem) {\n        window.location.hash = '/libraries/'+searchClass;\n        return;\n      }\n\n      var self = this;\n      this.init(function() {\n        var item = self.getItem(searchClass, searchItem);\n\n        App.menuView.hide();\n\n        if (item) {\n          App.itemView.show(item);\n        } else {\n          //App.itemView.nothingFound();\n\n          self.list();\n        }\n\n        styleCodeLinks();\n      });\n    },\n    /**\n     * Returns one item object by searching a class or a class item (method, property or event).\n     * @param {string} searchClass The class name (mandatory).\n     * @param {string} searchItem The class item name: can be a method, property or event name.\n     * @returns {object} The item found or undefined if nothing was found.\n     */\n    getItem: function(searchClass, searchItem) {\n      var classes = App.classes,\n              items = App.allItems,\n              classesCount = classes.length,\n              itemsCount = items.length,\n              className = searchClass ? searchClass.toLowerCase() : undefined,\n              itemName = searchItem ? searchItem : undefined,\n              found;\n\n      // Only search for a class, if itemName is undefined\n      if (className && !itemName) {\n        for (var i = 0; i < classesCount; i++) {\n          if (classes[i].name.toLowerCase() === className) {\n            found = classes[i];\n            _.each(found.items, function(i, idx) {\n              i.hash = App.router.getHash(i);\n            });\n            break;\n          }\n        }\n        // Search for a class item\n      } else if (className && itemName) {\n        // Search case sensitively\n        for (var i = 0; i < itemsCount; i++) {\n          if (items[i].class.toLowerCase() === className &&\n            items[i].name === itemName) {\n            found = items[i];\n            break;\n          }\n        }\n\n        // If no match was found, fallback to search case insensitively\n        if(!found){\n          for (var i = 0; i < itemsCount; i++) {\n            if(items[i].class.toLowerCase() === className &&\n              items[i].name.toLowerCase() === itemName.toLowerCase()){\n              found = items[i];\n              break;\n            }\n          }\n        }\n      }\n\n      return found;\n    },\n    /**\n     * List items.\n     * @param {string} collection The name of the collection to list.\n     */\n    list: function(collection) {\n\n      collection = 'allItems';\n\n      // Make sure collection is valid\n      if (App.collections.indexOf(collection) < 0) {\n        return;\n      }\n\n      this.init(function() {\n        App.menuView.show(collection);\n        App.menuView.update(collection);\n        App.listView.show(collection);\n        styleCodeLinks();\n      });\n    },\n    /**\n     * Display information for a library.\n     * @param {string} collection The name of the collection to list.\n     */\n    library: function(collection) {\n      this.init(function() {\n        App.menuView.hide();\n        App.libraryView.show(collection.substring(3)); //remove p5.\n        styleCodeLinks();\n      });\n    },\n    /**\n     * Close all content views.\n     */\n    search: function() {\n      this.init(function() {\n        App.menuView.hide();\n        App.pageView.hideContentViews();\n      });\n    },\n\n    /**\n     * Create an hash/url for the item.\n     * @param {Object} item A class, method, property or event object.\n     * @returns {String} The hash string, including the '#'.\n     */\n     getHash: function(item) {\n\n       if (!item.hash) {\n\n         // FIX TO INVISIBLE OBJECTS: DH (see also listView.js)\n\n         if (item.class) {\n           var clsFunc = '#/' + item.class + '.' + item.name;\n           var idx = clsFunc.lastIndexOf('.');\n           item.hash = clsFunc.substring(0,idx) + '/' + clsFunc.substring(idx+1);\n         } else {\n          item.hash = '#/' + item.name;\n         }\n       }\n\n       return item.hash;\n    }\n  });\n\n  \n  function styleCodeLinks() {\n    var links = document.getElementsByTagName(\"a\");\n    for (var iLink = 0; iLink < links.length; iLink++) {\n      var link = links[iLink];\n      if (link.hash.startsWith('#/p5')) {\n        link.classList.add('code');\n      }\n    }\n  }\n\n\n  // Get the router\n  App.router = new Router();\n\n  // Start history\n  Backbone.history.start();\n\n  return App.router;\n\n});\n\n",
        -    "/**\n * Define global App.\n */\nvar App = window.App || {};\ndefine('App', [],function() {\n  return App;\n});\n\n/**\n * Load json API data and start the router.\n * @param {module} App\n * @param {module} router\n */\nrequire([\n  'App',\n  './documented-method'], function(App, DocumentedMethod) {\n\n  // Set collections\n  App.collections = ['allItems', 'classes', 'events', 'methods', 'properties', 'p5.sound'];\n\n  // Get json API data\n  $.getJSON('data.min.json', function(data) {\n    App.data = data;\n    App.classes = [];\n    App.methods = [];\n    App.properties = [];\n    App.events = [];\n    App.allItems = [];\n    App.sound = { items: [] };\n    App.dom = { items: [] };\n    App.modules = [];\n    App.project = data.project;\n\n\n    var modules = data.modules;\n\n    // Get class items (methods, properties, events)\n    _.each(modules, function(m, idx, array) {\n      App.modules.push(m);\n      if (m.name == \"p5.sound\") {\n        App.sound.module = m;\n      }\n    });\n\n\n    var items = data.classitems;\n    var classes = data.classes;\n\n    // Get classes\n    _.each(classes, function(c, idx, array) {\n      if (!c.private) {\n        App.classes.push(c);\n      }\n    });\n\n\n    // Get class items (methods, properties, events)\n    _.each(items, function(el, idx, array) {\n      if (el.itemtype) {\n        if (el.itemtype === \"method\") {\n          el = new DocumentedMethod(el);\n          App.methods.push(el);\n          App.allItems.push(el);\n        } else if (el.itemtype === \"property\") {\n          App.properties.push(el);\n          App.allItems.push(el);\n        } else if (el.itemtype === \"event\") {\n          App.events.push(el);\n          App.allItems.push(el);\n        }\n\n        // libraries\n        if (el.module === \"p5.sound\") {\n          App.sound.items.push(el);\n        }\n      }\n    });\n\n    _.each(App.classes, function(c, idx) {\n      c.items = _.filter(App.allItems, function(it){ return it.class === c.name; });\n    });\n\n    require(['router']);\n  });\n});\n\ndefine(\"main\", function(){});\n\n",
        -    "}());"
        -  ]
        -}
        \ No newline at end of file
        diff --git a/docs/yuidoc-p5-theme/assets/js/render.js b/docs/yuidoc-p5-theme/assets/js/render.js
        deleted file mode 100644
        index aaac07bbb6..0000000000
        --- a/docs/yuidoc-p5-theme/assets/js/render.js
        +++ /dev/null
        @@ -1,299 +0,0 @@
        -var renderCode = function(exampleName) {
        -
        -  var _p5 = p5;
        -  var instances = [];
        -  var selector = 'example';
        -  var examples = document.getElementsByClassName(selector);
        -  if (examples.length > 0) {
        -
        -    var sketches = examples[0].getElementsByTagName('code');
        -    var sketches_array = Array.prototype.slice.call(sketches);
        -    var i = 0;
        -    sketches_array.forEach(function(s) {
        -      var rc = (s.parentNode.className.indexOf('norender') === -1);
        -      setupCode(s, rc, i);
        -      runCode(s, rc, i);
        -      i++;
        -    });
        -  }
        -
        -  function enableTab(el) {
        -    el.onkeydown = function(e) {
        -      if (e.keyCode === 9) { // tab was pressed
        -        // get caret position/selection
        -        var val = this.value,
        -            start = this.selectionStart,
        -            end = this.selectionEnd;
        -        // set textarea value to: text before caret + tab + text after caret
        -        this.value = val.substring(0, start) + '  ' + val.substring(end);
        -        // put caret at right position again
        -        this.selectionStart = this.selectionEnd = start + 2;
        -        // prevent the focus lose
        -        return false;
        -
        -      }
        -    };
        -  }
        -
        -  function setupCode(sketch, rc, i) {
        -
        -    var isRef = sketch.parentNode.tagName !== 'PRE';
        -    var sketchNode =  isRef ? sketch : sketch.parentNode;
        -    var sketchContainer = sketchNode.parentNode;
        -
        -    if (isRef) {
        -      $(sketchContainer).prepend('<h4 id="example'+i+'" class="sr-only">'+exampleName+' example '+i+'</h4>');
        -      var pre = document.createElement('pre');
        -      pre.className = 'ref';
        -      pre.appendChild(sketchNode);
        -      sketchContainer.appendChild(pre);
        -      sketchContainer.className = 'example_container';
        -      sketch.className = 'language-javascript';
        -      if (!rc) {
        -        pre.className += ' norender';
        -      }
        -    }
        -
        -
        -    // remove start and end lines
        -    var runnable = sketch.textContent.replace(/^\s+|\s+$/g, '');
        -    var rows = sketch.textContent.split('\n').length;
        -
        -    // var h = Math.max(sketch.offsetHeight, 100) + 25;
        -
        -    // store original sketch
        -    var orig_sketch = document.createElement('div');
        -    orig_sketch.innerHTML = sketch.innerHTML;
        -
        -    // create canvas
        -    if (rc) {
        -      var cnv = document.createElement('div');
        -      cnv.className = 'cnv_div';
        -      if (isRef) {
        -        sketchContainer.appendChild(cnv);
        -      } else {
        -        sketchContainer.parentNode.insertBefore(cnv, sketchContainer);
        -      }
        -
        -      // create edit space
        -      let edit_space = document.createElement('div');
        -      edit_space.className = 'edit_space';
        -      sketchContainer.appendChild(edit_space);
        -      $(edit_space).append('<h5 class="sr-only" id="buttons"'+i+' aria-labelledby="buttons'+i+' example'+i+'">buttons</h5>');
        -
        -      var edit_area = document.createElement('textarea');
        -      edit_area.value = runnable;
        -      edit_area.rows = rows;
        -      edit_area.cols = 62;
        -      edit_area.style.position = 'absolute'
        -      edit_area.style.top = '4px';
        -      edit_area.style.left = '13px';
        -      edit_space.appendChild(edit_area);
        -      edit_area.style.display = 'none';
        -      enableTab(edit_area);
        -
        -      //add buttons
        -      let button_space = document.createElement('ul');
        -      edit_space.appendChild(button_space);
        -      let edit_button = document.createElement('button');
        -      edit_button.value = 'edit';
        -      edit_button.innerHTML = 'edit';
        -      edit_button.id = 'edit'+i;
        -      edit_button.setAttribute('aria-labelledby', edit_button.id+' example'+i);
        -      edit_button.className = 'edit_button';
        -      edit_button.onclick = function(e) {
        -        if (edit_button.innerHTML === 'edit') { // edit
        -          setMode(sketch, 'edit');
        -        } else { // run
        -          setMode(sketch, 'run');
        -        }
        -      };
        -      let edit_li = button_space.appendChild(document.createElement('li'));
        -      edit_li.appendChild(edit_button);
        -
        -      let reset_button = document.createElement('button');
        -      reset_button.value = 'reset';
        -      reset_button.innerHTML = 'reset';
        -      reset_button.id = 'reset'+i;
        -      reset_button.setAttribute('aria-labelledby', reset_button.id+' example'+i);
        -      reset_button.className = 'reset_button';
        -      reset_button.onclick = function() {
        -        edit_area.value = orig_sketch.textContent;
        -        setMode(sketch, 'run');
        -      };
        -      let reset_li = button_space.appendChild(document.createElement('li'));
        -      reset_li.appendChild(reset_button);
        -
        -      let copy_button = document.createElement('button');
        -      copy_button.value = 'copy';
        -      copy_button.innerHTML = 'copy';
        -      copy_button.id = 'copy'+i;
        -      copy_button.setAttribute('aria-labelledby', copy_button.id+' example'+i);
        -      copy_button.className = 'copy_button';
        -      copy_button.onclick = function() {
        -        setMode(sketch, 'edit');
        -        edit_area.select();
        -        document.execCommand('copy');
        -      };
        -      let copy_li = button_space.appendChild(document.createElement('li'));
        -      copy_li.appendChild(copy_button);
        -
        -
        -      function setMode(sketch, m) {
        -        if (m === 'edit') {
        -          $('.example_container').each(function(ind, con) {
        -            if (ind !== i) {
        -              $(con).css('opacity', 0.25);
        -            } else {
        -              $(con).addClass('editing');
        -            }
        -          });
        -          edit_button.innerHTML = 'run';
        -          edit_area.style.display = 'block';
        -          edit_area.focus();
        -        } else {
        -          edit_button.innerHTML = 'edit';
        -          edit_area.style.display = 'none';
        -          sketch.textContent = edit_area.value;
        -          $('.example_container').each(function (ind, con) {
        -            $(con).css('opacity', 1.0);
        -            $(con).removeClass('editing');
        -            $this = $(this);
        -            var pre = $this.find('pre')[0];
        -            if (pre) {
        -              $this.height(Math.max($(pre).height(), 100) + 20);
        -            }
        -          });
        -          runCode(sketch, true, i);
        -        }
        -      }
        -    }
        -  }
        -
        -  function runCode(sketch, rc, i) {
        -
        -    if (instances[i]) {
        -      instances[i].remove();
        -    }
        -
        -    var sketchNode = sketch.parentNode;
        -    var isRef = sketchNode.className.indexOf('ref') !== -1;
        -    var sketchContainer = sketchNode.parentNode;
        -    var parent = sketchContainer.parentNode;
        -
        -    var runnable = sketch.textContent.replace(/^\s+|\s+$/g, '');
        -    var cnv;
        -
        -    if (rc) {
        -      if (isRef) {
        -        cnv = sketchContainer.getElementsByClassName('cnv_div')[0];
        -      } else {
        -        cnv = parent.parentNode.getElementsByClassName('cnv_div')[0];
        -      }
        -      cnv.innerHTML = '';
        -
        -      var s = function( p ) {
        -        var fxns = ['setup', 'draw', 'preload', 'mousePressed', 'mouseReleased',
        -          'mouseMoved', 'mouseDragged', 'mouseClicked','doubleClicked','mouseWheel',
        -          'touchStarted', 'touchMoved', 'touchEnded',
        -          'keyPressed', 'keyReleased', 'keyTyped'];
        -        var _found = [];
        -        // p.preload is an empty function created by the p5.sound library in order to use the p5.js preload system
        -        // to load AudioWorklet modules before a sketch runs, even if that sketch doesn't have its own preload function.
        -        // However, this causes an error in the eval code below because the _found array will always contain "preload",
        -        // even if the sketch in question doesn't have a preload function. To get around this, we delete p.preload before
        -        // eval-ing the sketch and add it back afterwards if the sketch doesn't contain its own preload function.
        -        // For more info, see: https://github.com/processing/p5.js-sound/blob/master/src/audioWorklet/index.js#L22
        -        if (p.preload) {
        -          delete p.preload;
        -        }
        -        with (p) {
        -          // Builds a function to detect declared functions via
        -          // them being hoisted past the return statement. Does
        -          // not execute runnable. Two returns with different
        -          // conditions guarantee a return but suppress unreachable
        -          // code warnings.
        -          eval([
        -            '(function() {',
        -              fxns.map(function (_name) {
        -                return [
        -                  'try {',
        -                  '  eval(' + _name + ');',
        -                  '  _found.push(\'' + _name + '\');',
        -                  '} catch(e) {',
        -                  '  if(!(e instanceof ReferenceError)) {',
        -                  '    throw e;',
        -                  '  }',
        -                  '}'
        -                ].join('');
        -              }).join(''),
        -              'if(_found.length) return;',
        -              'if(!_found.length) return;',
        -              runnable,
        -            '})();'
        -          ].join('\n'));
        -        }
        -        // If we haven't found any functions we'll assume it's
        -        // just a setup body with an empty preload.
        -        if (!_found.length) {
        -          p.preload = function() {};
        -          p.setup = function() {
        -            p.createCanvas(100, 100);
        -            p.background(200);
        -            with (p) {
        -              eval(runnable);
        -            }
        -          }
        -        } else {
        -          // Actually runs the code to get functions into scope.
        -          with (p) {
        -            eval(runnable);
        -          }
        -          _found.forEach(function(name) {
        -            p[name] = eval(name);
        -          });
        -          // Ensure p.preload exists even if the sketch doesn't have a preload function.
        -          p.preload = p.preload || function() {};
        -          p.setup = p.setup || function() {
        -            p.createCanvas(100, 100);
        -            p.background(200);
        -          };
        -        }
        -      };
        -    }
        -
        -    //if (typeof prettyPrint !== 'undefined') prettyPrint();
        -    if (typeof Prism !== 'undefined'){
        -      Prism.highlightAll()
        -    };
        -
        -    // when a hash is changed, remove all the sounds,
        -    // even tho the p5 sketch has been disposed.
        -    function registerHashChange() {
        -      window.onhashchange = function(e) {
        -        for (var i = 0; i < instances.length; i++) {
        -          instances[i].remove();
        -        }
        -      }
        -    }
        -
        -    $( document ).ready(function() {
        -
        -      registerHashChange();
        -
        -      setTimeout(function() {
        -        var myp5 = new _p5(s, cnv);
        -        $( ".example-content" ).find('div').each(function() {
        -          $this = $( this );
        -          var pre = $this.find('pre')[0];
        -          if (pre) {
        -            $this.height( Math.max($(pre).height()*1.1, 100) + 20 );
        -          }
        -        });
        -        instances[i] = myp5;
        -      }, 100);
        -    });
        -
        -  }
        -
        -};
        diff --git a/docs/yuidoc-p5-theme/assets/js/require.min.js b/docs/yuidoc-p5-theme/assets/js/require.min.js
        deleted file mode 100644
        index e599a6abe0..0000000000
        --- a/docs/yuidoc-p5-theme/assets/js/require.min.js
        +++ /dev/null
        @@ -1,36 +0,0 @@
        -/*
        - RequireJS 2.1.11 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
        - Available via the MIT or new BSD license.
        - see: http://github.com/jrburke/requirejs for details
        -*/
        -var requirejs,require,define;
        -(function(ca){function G(b){return"[object Function]"===M.call(b)}function H(b){return"[object Array]"===M.call(b)}function v(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function U(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function s(b,c){return ga.call(b,c)}function j(b,c){return s(b,c)&&b[c]}function B(b,c){for(var d in b)if(s(b,d)&&c(b[d],d))break}function V(b,c,d,g){c&&B(c,function(c,h){if(d||!s(b,h))g&&"object"===typeof c&&c&&!H(c)&&!G(c)&&!(c instanceof
        -RegExp)?(b[h]||(b[h]={}),V(b[h],c,d,g)):b[h]=c});return b}function t(b,c){return function(){return c.apply(b,arguments)}}function da(b){throw b;}function ea(b){if(!b)return b;var c=ca;v(b.split("."),function(b){c=c[b]});return c}function C(b,c,d,g){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=g;d&&(c.originalError=d);return c}function ha(b){function c(a,e,b){var f,n,c,d,g,h,i,I=e&&e.split("/");n=I;var m=l.map,k=m&&m["*"];if(a&&"."===a.charAt(0))if(e){n=
        -I.slice(0,I.length-1);a=a.split("/");e=a.length-1;l.nodeIdCompat&&R.test(a[e])&&(a[e]=a[e].replace(R,""));n=a=n.concat(a);d=n.length;for(e=0;e<d;e++)if(c=n[e],"."===c)n.splice(e,1),e-=1;else if(".."===c)if(1===e&&(".."===n[2]||".."===n[0]))break;else 0<e&&(n.splice(e-1,2),e-=2);a=a.join("/")}else 0===a.indexOf("./")&&(a=a.substring(2));if(b&&m&&(I||k)){n=a.split("/");e=n.length;a:for(;0<e;e-=1){d=n.slice(0,e).join("/");if(I)for(c=I.length;0<c;c-=1)if(b=j(m,I.slice(0,c).join("/")))if(b=j(b,d)){f=b;
        -g=e;break a}!h&&(k&&j(k,d))&&(h=j(k,d),i=e)}!f&&h&&(f=h,g=i);f&&(n.splice(0,g,f),a=n.join("/"))}return(f=j(l.pkgs,a))?f:a}function d(a){z&&v(document.getElementsByTagName("script"),function(e){if(e.getAttribute("data-requiremodule")===a&&e.getAttribute("data-requirecontext")===i.contextName)return e.parentNode.removeChild(e),!0})}function g(a){var e=j(l.paths,a);if(e&&H(e)&&1<e.length)return e.shift(),i.require.undef(a),i.require([a]),!0}function u(a){var e,b=a?a.indexOf("!"):-1;-1<b&&(e=a.substring(0,
        -b),a=a.substring(b+1,a.length));return[e,a]}function m(a,e,b,f){var n,d,g=null,h=e?e.name:null,l=a,m=!0,k="";a||(m=!1,a="_@r"+(M+=1));a=u(a);g=a[0];a=a[1];g&&(g=c(g,h,f),d=j(p,g));a&&(g?k=d&&d.normalize?d.normalize(a,function(a){return c(a,h,f)}):c(a,h,f):(k=c(a,h,f),a=u(k),g=a[0],k=a[1],b=!0,n=i.nameToUrl(k)));b=g&&!d&&!b?"_unnormalized"+(Q+=1):"";return{prefix:g,name:k,parentMap:e,unnormalized:!!b,url:n,originalName:l,isDefine:m,id:(g?g+"!"+k:k)+b}}function q(a){var e=a.id,b=j(k,e);b||(b=k[e]=new i.Module(a));
        -return b}function r(a,e,b){var f=a.id,n=j(k,f);if(s(p,f)&&(!n||n.defineEmitComplete))"defined"===e&&b(p[f]);else if(n=q(a),n.error&&"error"===e)b(n.error);else n.on(e,b)}function w(a,e){var b=a.requireModules,f=!1;if(e)e(a);else if(v(b,function(e){if(e=j(k,e))e.error=a,e.events.error&&(f=!0,e.emit("error",a))}),!f)h.onError(a)}function x(){S.length&&(ia.apply(A,[A.length,0].concat(S)),S=[])}function y(a){delete k[a];delete W[a]}function F(a,e,b){var f=a.map.id;a.error?a.emit("error",a.error):(e[f]=
        -!0,v(a.depMaps,function(f,c){var d=f.id,g=j(k,d);g&&(!a.depMatched[c]&&!b[d])&&(j(e,d)?(a.defineDep(c,p[d]),a.check()):F(g,e,b))}),b[f]=!0)}function D(){var a,e,b=(a=1E3*l.waitSeconds)&&i.startTime+a<(new Date).getTime(),f=[],c=[],h=!1,k=!0;if(!X){X=!0;B(W,function(a){var i=a.map,m=i.id;if(a.enabled&&(i.isDefine||c.push(a),!a.error))if(!a.inited&&b)g(m)?h=e=!0:(f.push(m),d(m));else if(!a.inited&&(a.fetched&&i.isDefine)&&(h=!0,!i.prefix))return k=!1});if(b&&f.length)return a=C("timeout","Load timeout for modules: "+
        -f,null,f),a.contextName=i.contextName,w(a);k&&v(c,function(a){F(a,{},{})});if((!b||e)&&h)if((z||fa)&&!Y)Y=setTimeout(function(){Y=0;D()},50);X=!1}}function E(a){s(p,a[0])||q(m(a[0],null,!0)).init(a[1],a[2])}function K(a){var a=a.currentTarget||a.srcElement,e=i.onScriptLoad;a.detachEvent&&!Z?a.detachEvent("onreadystatechange",e):a.removeEventListener("load",e,!1);e=i.onScriptError;(!a.detachEvent||Z)&&a.removeEventListener("error",e,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function L(){var a;
        -for(x();A.length;){a=A.shift();if(null===a[0])return w(C("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));E(a)}}var X,$,i,N,Y,l={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},k={},W={},aa={},A=[],p={},T={},ba={},M=1,Q=1;N={require:function(a){return a.require?a.require:a.require=i.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?p[a.map.id]=a.exports:a.exports=p[a.map.id]={}},module:function(a){return a.module?
        -a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){return j(l.config,a.map.id)||{}},exports:a.exports||(a.exports={})}}};$=function(a){this.events=j(aa,a.id)||{};this.map=a;this.shim=j(l.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};$.prototype={init:function(a,e,b,f){f=f||{};if(!this.inited){this.factory=e;if(b)this.on("error",b);else this.events.error&&(b=t(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=
        -b;this.inited=!0;this.ignore=f.ignore;f.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,e){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=e)},fetch:function(){if(!this.fetched){this.fetched=!0;i.startTime=(new Date).getTime();var a=this.map;if(this.shim)i.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],t(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=
        -this.map.url;T[a]||(T[a]=!0,i.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,e,b=this.map.id;e=this.depExports;var f=this.exports,c=this.factory;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(G(c)){if(this.events.error&&this.map.isDefine||h.onError!==da)try{f=i.execCb(b,c,e,f)}catch(d){a=d}else f=i.execCb(b,c,e,f);this.map.isDefine&&void 0===f&&((e=this.module)?f=e.exports:this.usingExports&&
        -(f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=c;this.exports=f;if(this.map.isDefine&&!this.ignore&&(p[b]=f,h.onResourceLoad))h.onResourceLoad(i,this.map,this.depMaps);y(b);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=
        -this.map,b=a.id,d=m(a.prefix);this.depMaps.push(d);r(d,"defined",t(this,function(f){var d,g;g=j(ba,this.map.id);var J=this.map.name,u=this.map.parentMap?this.map.parentMap.name:null,p=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(J=f.normalize(J,function(a){return c(a,u,!0)})||""),f=m(a.prefix+"!"+J,this.map.parentMap),r(f,"defined",t(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),g=j(k,f.id)){this.depMaps.push(f);
        -if(this.events.error)g.on("error",t(this,function(a){this.emit("error",a)}));g.enable()}}else g?(this.map.url=i.nameToUrl(g),this.load()):(d=t(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),d.error=t(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];B(k,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),d.fromText=t(this,function(f,c){var g=a.name,J=m(g),k=O;c&&(f=c);k&&(O=!1);q(J);s(l.config,b)&&(l.config[g]=l.config[b]);try{h.exec(f)}catch(j){return w(C("fromtexteval",
        -"fromText eval for "+b+" failed: "+j,j,[b]))}k&&(O=!0);this.depMaps.push(J);i.completeLoad(g);p([g],d)}),f.load(a.name,p,d,l))}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){W[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,t(this,function(a,b){var c,f;if("string"===typeof a){a=m(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=j(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;r(a,"defined",t(this,function(a){this.defineDep(b,
        -a);this.check()}));this.errback&&r(a,"error",t(this,this.errback))}c=a.id;f=k[c];!s(N,c)&&(f&&!f.enabled)&&i.enable(a,this)}));B(this.pluginMaps,t(this,function(a){var b=j(k,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:l,contextName:b,registry:k,defined:p,urlFetched:T,defQueue:A,Module:$,makeModuleMap:m,
        -nextTick:h.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=l.shim,c={paths:!0,bundles:!0,config:!0,map:!0};B(a,function(a,b){c[b]?(l[b]||(l[b]={}),V(l[b],a,!0,!0)):l[b]=a});a.bundles&&B(a.bundles,function(a,b){v(a,function(a){a!==b&&(ba[a]=b)})});a.shim&&(B(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);b[c]=a}),l.shim=b);a.packages&&v(a.packages,function(a){var b,
        -a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(l.paths[b]=a.location);l.pkgs[b]=a.name+"/"+(a.main||"main").replace(ja,"").replace(R,"")});B(k,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=m(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ca,arguments));return b||a.exports&&ea(a.exports)}},makeRequire:function(a,e){function g(f,c,d){var j,l;e.enableBuildCallback&&(c&&G(c))&&(c.__requireJsBuild=
        -!0);if("string"===typeof f){if(G(c))return w(C("requireargs","Invalid require call"),d);if(a&&s(N,f))return N[f](k[a.id]);if(h.get)return h.get(i,f,a,g);j=m(f,a,!1,!0);j=j.id;return!s(p,j)?w(C("notloaded",'Module name "'+j+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):p[j]}L();i.nextTick(function(){L();l=q(m(null,a));l.skipMap=e.skipMap;l.init(f,c,d,{enabled:!0});D()});return g}e=e||{};V(g,{isBrowser:z,toUrl:function(b){var e,d=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==
        -d&&(!("."===g||".."===g)||1<d))e=b.substring(d,b.length),b=b.substring(0,d);return i.nameToUrl(c(b,a&&a.id,!0),e,!0)},defined:function(b){return s(p,m(b,a,!1,!0).id)},specified:function(b){b=m(b,a,!1,!0).id;return s(p,b)||s(k,b)}});a||(g.undef=function(b){x();var c=m(b,a,!0),e=j(k,b);d(b);delete p[b];delete T[c.url];delete aa[b];U(A,function(a,c){a[0]===b&&A.splice(c,1)});e&&(e.events.defined&&(aa[b]=e.events),y(b))});return g},enable:function(a){j(k,a.id)&&q(a).enable()},completeLoad:function(a){var b,
        -c,f=j(l.shim,a)||{},d=f.exports;for(x();A.length;){c=A.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);E(c)}c=j(k,a);if(!b&&!s(p,a)&&c&&!c.inited){if(l.enforceDefine&&(!d||!ea(d)))return g(a)?void 0:w(C("nodefine","No define call for "+a,null,[a]));E([a,f.deps||[],f.exportsFn])}D()},nameToUrl:function(a,b,c){var f,d,g;(f=j(l.pkgs,a))&&(a=f);if(f=j(ba,a))return i.nameToUrl(f,b,c);if(h.jsExtRegExp.test(a))f=a+(b||"");else{f=l.paths;a=a.split("/");for(d=a.length;0<d;d-=1)if(g=a.slice(0,
        -d).join("/"),g=j(f,g)){H(g)&&(g=g[0]);a.splice(0,d,g);break}f=a.join("/");f+=b||(/^data\:|\?/.test(f)||c?"":".js");f=("/"===f.charAt(0)||f.match(/^[\w\+\.\-]+:/)?"":l.baseUrl)+f}return l.urlArgs?f+((-1===f.indexOf("?")?"?":"&")+l.urlArgs):f},load:function(a,b){h.load(i,a,b)},execCb:function(a,b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||ka.test((a.currentTarget||a.srcElement).readyState))P=null,a=K(a),i.completeLoad(a.id)},onScriptError:function(a){var b=K(a);if(!g(b.id))return w(C("scripterror",
        -"Script error for: "+b.id,a,[b.id]))}};i.require=i.makeRequire();return i}var h,x,y,D,K,E,P,L,q,Q,la=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ma=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,R=/\.js$/,ja=/^\.\//;x=Object.prototype;var M=x.toString,ga=x.hasOwnProperty,ia=Array.prototype.splice,z=!!("undefined"!==typeof window&&"undefined"!==typeof navigator&&window.document),fa=!z&&"undefined"!==typeof importScripts,ka=z&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,
        -Z="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),F={},r={},S=[],O=!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(G(requirejs))return;r=requirejs;requirejs=void 0}"undefined"!==typeof require&&!G(require)&&(r=require,require=void 0);h=requirejs=function(b,c,d,g){var u,m="_";!H(b)&&"string"!==typeof b&&(u=b,H(c)?(b=c,c=d,d=g):b=[]);u&&u.context&&(m=u.context);(g=j(F,m))||(g=F[m]=h.s.newContext(m));u&&g.configure(u);return g.require(b,c,d)};h.config=function(b){return h(b)};
        -h.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,4)}:function(b){b()};require||(require=h);h.version="2.1.11";h.jsExtRegExp=/^\/|:|\?|\.js$/;h.isBrowser=z;x=h.s={contexts:F,newContext:ha};h({});v(["toUrl","undef","defined","specified"],function(b){h[b]=function(){var c=F._;return c.require[b].apply(c,arguments)}});if(z&&(y=x.head=document.getElementsByTagName("head")[0],D=document.getElementsByTagName("base")[0]))y=x.head=D.parentNode;h.onError=da;h.createNode=function(b){var c=
        -b.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");c.type=b.scriptType||"text/javascript";c.charset="utf-8";c.async=!0;return c};h.load=function(b,c,d){var g=b&&b.config||{};if(z)return g=h.createNode(g,c,d),g.setAttribute("data-requirecontext",b.contextName),g.setAttribute("data-requiremodule",c),g.attachEvent&&!(g.attachEvent.toString&&0>g.attachEvent.toString().indexOf("[native code"))&&!Z?(O=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):
        -(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1)),g.src=d,L=g,D?y.insertBefore(g,D):y.appendChild(g),L=null,g;if(fa)try{importScripts(d),b.completeLoad(c)}catch(j){b.onError(C("importscripts","importScripts failed for "+c+" at "+d,j,[c]))}};z&&!r.skipDataMain&&U(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(K=b.getAttribute("data-main"))return q=K,r.baseUrl||(E=q.split("/"),q=E.pop(),Q=E.length?E.join("/")+"/":"./",r.baseUrl=
        -Q),q=q.replace(R,""),h.jsExtRegExp.test(q)&&(q=K),r.deps=r.deps?r.deps.concat(q):[q],!0});define=function(b,c,d){var g,h;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(la,"").replace(ma,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(O){if(!(g=L))P&&"interactive"===P.readyState||U(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),g=P;g&&(b||
        -(b=g.getAttribute("data-requiremodule")),h=F[g.getAttribute("data-requirecontext")])}(h?h.defQueue:S).push([b,c,d])};define.amd={jQuery:!0};h.exec=function(b){return eval(b)};h(r)}})(this);
        diff --git a/docs/yuidoc-p5-theme/assets/js/vendor/ace-nc/ace.js b/docs/yuidoc-p5-theme/assets/js/vendor/ace-nc/ace.js
        deleted file mode 100644
        index b2ed67b299..0000000000
        --- a/docs/yuidoc-p5-theme/assets/js/vendor/ace-nc/ace.js
        +++ /dev/null
        @@ -1,11 +0,0 @@
        -(function(){function s(r){var i=function(e,t){return n("",e,t)},s=e;r&&(e[r]||(e[r]={}),s=e[r]);if(!s.define||!s.define.packaged)t.original=s.define,s.define=t,s.define.packaged=!0;if(!s.require||!s.require.packaged)n.original=s.require,s.require=i,s.require.packaged=!0}var ACE_NAMESPACE = "ace",e=function(){return this}();if(!ACE_NAMESPACE&&typeof requirejs!="undefined")return;var t=function(e,n,r){if(typeof e!="string"){t.original?t.original.apply(window,arguments):(console.error("dropping module because define wasn't a string."),console.trace());return}arguments.length==2&&(r=n),t.modules||(t.modules={},t.payloads={}),t.payloads[e]=r,t.modules[e]=null},n=function(e,t,r){if(Object.prototype.toString.call(t)==="[object Array]"){var s=[];for(var o=0,u=t.length;o<u;++o){var a=i(e,t[o]);if(!a&&n.original)return n.original.apply(window,arguments);s.push(a)}r&&r.apply(null,s)}else{if(typeof t=="string"){var f=i(e,t);return!f&&n.original?n.original.apply(window,arguments):(r&&r(),f)}if(n.original)return n.original.apply(window,arguments)}},r=function(e,t){if(t.indexOf("!")!==-1){var n=t.split("!");return r(e,n[0])+"!"+r(e,n[1])}if(t.charAt(0)=="."){var i=e.split("/").slice(0,-1).join("/");t=i+"/"+t;while(t.indexOf(".")!==-1&&s!=t){var s=t;t=t.replace(/\/\.\//,"/").replace(/[^\/]+\/\.\.\//,"")}}return t},i=function(e,i){i=r(e,i);var s=t.modules[i];if(!s){s=t.payloads[i];if(typeof s=="function"){var o={},u={id:i,uri:"",exports:o,packaged:!0},a=function(e,t){return n(i,e,t)},f=s(a,o,u);o=f||u.exports,t.modules[i]=o,delete t.payloads[i]}s=t.modules[i]=o||s}return s};s(ACE_NAMESPACE)})(),ace.define("ace/lib/regexp",["require","exports","module"],function(e,t,n){"use strict";function o(e){return(e.global?"g":"")+(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.extended?"x":"")+(e.sticky?"y":"")}function u(e,t,n){if(Array.prototype.indexOf)return e.indexOf(t,n);for(var r=n||0;r<e.length;r++)if(e[r]===t)return r;return-1}var r={exec:RegExp.prototype.exec,test:RegExp.prototype.test,match:String.prototype.match,replace:String.prototype.replace,split:String.prototype.split},i=r.exec.call(/()??/,"")[1]===undefined,s=function(){var e=/^/g;return r.test.call(e,""),!e.lastIndex}();if(s&&i)return;RegExp.prototype.exec=function(e){var t=r.exec.apply(this,arguments),n,a;if(typeof e=="string"&&t){!i&&t.length>1&&u(t,"")>-1&&(a=RegExp(this.source,r.replace.call(o(this),"g","")),r.replace.call(e.slice(t.index),a,function(){for(var e=1;e<arguments.length-2;e++)arguments[e]===undefined&&(t[e]=undefined)}));if(this._xregexp&&this._xregexp.captureNames)for(var f=1;f<t.length;f++)n=this._xregexp.captureNames[f-1],n&&(t[n]=t[f]);!s&&this.global&&!t[0].length&&this.lastIndex>t.index&&this.lastIndex--}return t},s||(RegExp.prototype.test=function(e){var t=r.exec.call(this,e);return t&&this.global&&!t[0].length&&this.lastIndex>t.index&&this.lastIndex--,!!t})}),ace.define("ace/lib/es5-shim",["require","exports","module"],function(e,t,n){function r(){}function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentinel"in e}catch(t){}}function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-1)*Math.floor(Math.abs(e))),e}function B(e){var t=typeof e;return e===null||t==="undefined"||t==="boolean"||t==="number"||t==="string"}function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="function"){t=n.call(e);if(B(t))return t}r=e.toString;if(typeof r=="function"){t=r.call(e);if(B(t))return t}throw new TypeError}Function.prototype.bind||(Function.prototype.bind=function(t){var n=this;if(typeof n!="function")throw new TypeError("Function.prototype.bind called on incompatible "+n);var i=u.call(arguments,1),s=function(){if(this instanceof s){var e=n.apply(this,i.concat(u.call(arguments)));return Object(e)===e?e:this}return n.apply(t,i.concat(u.call(arguments)))};return n.prototype&&(r.prototype=n.prototype,s.prototype=new r,r.prototype=null),s});var i=Function.prototype.call,s=Array.prototype,o=Object.prototype,u=s.slice,a=i.bind(o.toString),f=i.bind(o.hasOwnProperty),l,c,h,p,d;if(d=f(o,"__defineGetter__"))l=i.bind(o.__defineGetter__),c=i.bind(o.__defineSetter__),h=i.bind(o.__lookupGetter__),p=i.bind(o.__lookupSetter__);if([1,2].splice(0).length!=2)if(!function(){function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}var t=[],n;t.splice.apply(t,e(20)),t.splice.apply(t,e(26)),n=t.length,t.splice(5,0,"XXX"),n+1==t.length;if(n+1==t.length)return!0}())Array.prototype.splice=function(e,t){var n=this.length;e>0?e>n&&(e=n):e==void 0?e=0:e<0&&(e=Math.max(n+e,0)),e+t<n||(t=n-e);var r=this.slice(e,e+t),i=u.call(arguments,2),s=i.length;if(e===n)s&&this.push.apply(this,i);else{var o=Math.min(t,n-e),a=e+o,f=a+s-o,l=n-a,c=n-o;if(f<a)for(var h=0;h<l;++h)this[f+h]=this[a+h];else if(f>a)for(h=l;h--;)this[f+h]=this[a+h];if(s&&e===c)this.length=c,this.push.apply(this,i);else{this.length=c+s;for(h=0;h<s;++h)this[e+h]=i[h]}}return r};else{var v=Array.prototype.splice;Array.prototype.splice=function(e,t){return arguments.length?v.apply(this,[e===void 0?0:e,t===void 0?this.length-e:t].concat(u.call(arguments,2))):[]}}Array.isArray||(Array.isArray=function(t){return a(t)=="[object Array]"});var m=Object("a"),g=m[0]!="a"||!(0 in m);Array.prototype.forEach||(Array.prototype.forEach=function(t){var n=F(this),r=g&&a(this)=="[object String]"?this.split(""):n,i=arguments[1],s=-1,o=r.length>>>0;if(a(t)!="[object Function]")throw new TypeError;while(++s<o)s in r&&t.call(i,r[s],s,n)}),Array.prototype.map||(Array.prototype.map=function(t){var n=F(this),r=g&&a(this)=="[object String]"?this.split(""):n,i=r.length>>>0,s=Array(i),o=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var u=0;u<i;u++)u in r&&(s[u]=t.call(o,r[u],u,n));return s}),Array.prototype.filter||(Array.prototype.filter=function(t){var n=F(this),r=g&&a(this)=="[object String]"?this.split(""):n,i=r.length>>>0,s=[],o,u=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var f=0;f<i;f++)f in r&&(o=r[f],t.call(u,o,f,n)&&s.push(o));return s}),Array.prototype.every||(Array.prototype.every=function(t){var n=F(this),r=g&&a(this)=="[object String]"?this.split(""):n,i=r.length>>>0,s=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var o=0;o<i;o++)if(o in r&&!t.call(s,r[o],o,n))return!1;return!0}),Array.prototype.some||(Array.prototype.some=function(t){var n=F(this),r=g&&a(this)=="[object String]"?this.split(""):n,i=r.length>>>0,s=arguments[1];if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");for(var o=0;o<i;o++)if(o in r&&t.call(s,r[o],o,n))return!0;return!1}),Array.prototype.reduce||(Array.prototype.reduce=function(t){var n=F(this),r=g&&a(this)=="[object String]"?this.split(""):n,i=r.length>>>0;if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");if(!i&&arguments.length==1)throw new TypeError("reduce of empty array with no initial value");var s=0,o;if(arguments.length>=2)o=arguments[1];else do{if(s in r){o=r[s++];break}if(++s>=i)throw new TypeError("reduce of empty array with no initial value")}while(!0);for(;s<i;s++)s in r&&(o=t.call(void 0,o,r[s],s,n));return o}),Array.prototype.reduceRight||(Array.prototype.reduceRight=function(t){var n=F(this),r=g&&a(this)=="[object String]"?this.split(""):n,i=r.length>>>0;if(a(t)!="[object Function]")throw new TypeError(t+" is not a function");if(!i&&arguments.length==1)throw new TypeError("reduceRight of empty array with no initial value");var s,o=i-1;if(arguments.length>=2)s=arguments[1];else do{if(o in r){s=r[o--];break}if(--o<0)throw new TypeError("reduceRight of empty array with no initial value")}while(!0);do o in this&&(s=t.call(void 0,s,r[o],o,n));while(o--);return s});if(!Array.prototype.indexOf||[0,1].indexOf(1,2)!=-1)Array.prototype.indexOf=function(t){var n=g&&a(this)=="[object String]"?this.split(""):F(this),r=n.length>>>0;if(!r)return-1;var i=0;arguments.length>1&&(i=H(arguments[1])),i=i>=0?i:Math.max(0,r+i);for(;i<r;i++)if(i in n&&n[i]===t)return i;return-1};if(!Array.prototype.lastIndexOf||[0,1].lastIndexOf(0,-3)!=-1)Array.prototype.lastIndexOf=function(t){var n=g&&a(this)=="[object String]"?this.split(""):F(this),r=n.length>>>0;if(!r)return-1;var i=r-1;arguments.length>1&&(i=Math.min(i,H(arguments[1]))),i=i>=0?i:r-Math.abs(i);for(;i>=0;i--)if(i in n&&t===n[i])return i;return-1};Object.getPrototypeOf||(Object.getPrototypeOf=function(t){return t.__proto__||(t.constructor?t.constructor.prototype:o)});if(!Object.getOwnPropertyDescriptor){var y="Object.getOwnPropertyDescriptor called on a non-object: ";Object.getOwnPropertyDescriptor=function(t,n){if(typeof t!="object"&&typeof t!="function"||t===null)throw new TypeError(y+t);if(!f(t,n))return;var r,i,s;r={enumerable:!0,configurable:!0};if(d){var u=t.__proto__;t.__proto__=o;var i=h(t,n),s=p(t,n);t.__proto__=u;if(i||s)return i&&(r.get=i),s&&(r.set=s),r}return r.value=t[n],r}}Object.getOwnPropertyNames||(Object.getOwnPropertyNames=function(t){return Object.keys(t)});if(!Object.create){var b;Object.prototype.__proto__===null?b=function(){return{__proto__:null}}:b=function(){var e={};for(var t in e)e[t]=null;return e.constructor=e.hasOwnProperty=e.propertyIsEnumerable=e.isPrototypeOf=e.toLocaleString=e.toString=e.valueOf=e.__proto__=null,e},Object.create=function(t,n){var r;if(t===null)r=b();else{if(typeof t!="object")throw new TypeError("typeof prototype["+typeof t+"] != 'object'");var i=function(){};i.prototype=t,r=new i,r.__proto__=t}return n!==void 0&&Object.defineProperties(r,n),r}}if(Object.defineProperty){var E=w({}),S=typeof document=="undefined"||w(document.createElement("div"));if(!E||!S)var x=Object.defineProperty}if(!Object.defineProperty||x){var T="Property description must be an object: ",N="Object.defineProperty called on non-object: ",C="getters & setters can not be defined on this javascript engine";Object.defineProperty=function(t,n,r){if(typeof t!="object"&&typeof t!="function"||t===null)throw new TypeError(N+t);if(typeof r!="object"&&typeof r!="function"||r===null)throw new TypeError(T+r);if(x)try{return x.call(Object,t,n,r)}catch(i){}if(f(r,"value"))if(d&&(h(t,n)||p(t,n))){var s=t.__proto__;t.__proto__=o,delete t[n],t[n]=r.value,t.__proto__=s}else t[n]=r.value;else{if(!d)throw new TypeError(C);f(r,"get")&&l(t,n,r.get),f(r,"set")&&c(t,n,r.set)}return t}}Object.defineProperties||(Object.defineProperties=function(t,n){for(var r in n)f(n,r)&&Object.defineProperty(t,r,n[r]);return t}),Object.seal||(Object.seal=function(t){return t}),Object.freeze||(Object.freeze=function(t){return t});try{Object.freeze(function(){})}catch(k){Object.freeze=function(t){return function(n){return typeof n=="function"?n:t(n)}}(Object.freeze)}Object.preventExtensions||(Object.preventExtensions=function(t){return t}),Object.isSealed||(Object.isSealed=function(t){return!1}),Object.isFrozen||(Object.isFrozen=function(t){return!1}),Object.isExtensible||(Object.isExtensible=function(t){if(Object(t)===t)throw new TypeError;var n="";while(f(t,n))n+="?";t[n]=!0;var r=f(t,n);return delete t[n],r});if(!Object.keys){var L=!0,A=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],O=A.length;for(var M in{toString:null})L=!1;Object.keys=function I(e){if(typeof e!="object"&&typeof e!="function"||e===null)throw new TypeError("Object.keys called on a non-object");var I=[];for(var t in e)f(e,t)&&I.push(t);if(L)for(var n=0,r=O;n<r;n++){var i=A[n];f(e,i)&&I.push(i)}return I}}Date.now||(Date.now=function(){return(new Date).getTime()});var _="	\n\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\ufeff";if(!String.prototype.trim||_.trim()){_="["+_+"]";var D=new RegExp("^"+_+_+"*"),P=new RegExp(_+_+"*$");String.prototype.trim=function(){return String(this).replace(D,"").replace(P,"")}}var F=function(e){if(e==null)throw new TypeError("can't convert "+e+" to object");return Object(e)}}),ace.define("ace/lib/fixoldbrowsers",["require","exports","module","ace/lib/regexp","ace/lib/es5-shim"],function(e,t,n){"use strict";e("./regexp"),e("./es5-shim")}),ace.define("ace/lib/dom",["require","exports","module"],function(e,t,n){"use strict";if(typeof document=="undefined")return;var r="http://www.w3.org/1999/xhtml";t.getDocumentHead=function(e){return e||(e=document),e.head||e.getElementsByTagName("head")[0]||e.documentElement},t.createElement=function(e,t){return document.createElementNS?document.createElementNS(t||r,e):document.createElement(e)},t.hasCssClass=function(e,t){var n=e.className.split(/\s+/g);return n.indexOf(t)!==-1},t.addCssClass=function(e,n){t.hasCssClass(e,n)||(e.className+=" "+n)},t.removeCssClass=function(e,t){var n=e.className.split(/\s+/g);for(;;){var r=n.indexOf(t);if(r==-1)break;n.splice(r,1)}e.className=n.join(" ")},t.toggleCssClass=function(e,t){var n=e.className.split(/\s+/g),r=!0;for(;;){var i=n.indexOf(t);if(i==-1)break;r=!1,n.splice(i,1)}return r&&n.push(t),e.className=n.join(" "),r},t.setCssClass=function(e,n,r){r?t.addCssClass(e,n):t.removeCssClass(e,n)},t.hasCssString=function(e,t){var n=0,r;t=t||document;if(t.createStyleSheet&&(r=t.styleSheets)){while(n<r.length)if(r[n++].owningElement.id===e)return!0}else if(r=t.getElementsByTagName("style"))while(n<r.length)if(r[n++].id===e)return!0;return!1},t.importCssString=function(n,i,s){s=s||document;if(i&&t.hasCssString(i,s))return null;var o;s.createStyleSheet?(o=s.createStyleSheet(),o.cssText=n,i&&(o.owningElement.id=i)):(o=s.createElementNS?s.createElementNS(r,"style"):s.createElement("style"),o.appendChild(s.createTextNode(n)),i&&(o.id=i),t.getDocumentHead(s).appendChild(o))},t.importCssStylsheet=function(e,n){if(n.createStyleSheet)n.createStyleSheet(e);else{var r=t.createElement("link");r.rel="stylesheet",r.href=e,t.getDocumentHead(n).appendChild(r)}},t.getInnerWidth=function(e){return parseInt(t.computedStyle(e,"paddingLeft"),10)+parseInt(t.computedStyle(e,"paddingRight"),10)+e.clientWidth},t.getInnerHeight=function(e){return parseInt(t.computedStyle(e,"paddingTop"),10)+parseInt(t.computedStyle(e,"paddingBottom"),10)+e.clientHeight},window.pageYOffset!==undefined?(t.getPageScrollTop=function(){return window.pageYOffset},t.getPageScrollLeft=function(){return window.pageXOffset}):(t.getPageScrollTop=function(){return document.body.scrollTop},t.getPageScrollLeft=function(){return document.body.scrollLeft}),window.getComputedStyle?t.computedStyle=function(e,t){return t?(window.getComputedStyle(e,"")||{})[t]||"":window.getComputedStyle(e,"")||{}}:t.computedStyle=function(e,t){return t?e.currentStyle[t]:e.currentStyle},t.scrollbarWidth=function(e){var n=t.createElement("ace_inner");n.style.width="100%",n.style.minWidth="0px",n.style.height="200px",n.style.display="block";var r=t.createElement("ace_outer"),i=r.style;i.position="absolute",i.left="-10000px",i.overflow="hidden",i.width="200px",i.minWidth="0px",i.height="150px",i.display="block",r.appendChild(n);var s=e.documentElement;s.appendChild(r);var o=n.offsetWidth;i.overflow="scroll";var u=n.offsetWidth;return o==u&&(u=r.clientWidth),s.removeChild(r),o-u},t.setInnerHtml=function(e,t){var n=e.cloneNode(!1);return n.innerHTML=t,e.parentNode.replaceChild(n,e),n},"textContent"in document.documentElement?(t.setInnerText=function(e,t){e.textContent=t},t.getInnerText=function(e){return e.textContent}):(t.setInnerText=function(e,t){e.innerText=t},t.getInnerText=function(e){return e.innerText}),t.getParentWindow=function(e){return e.defaultView||e.parentWindow}}),ace.define("ace/lib/oop",["require","exports","module"],function(e,t,n){"use strict";t.inherits=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})},t.mixin=function(e,t){for(var n in t)e[n]=t[n];return e},t.implement=function(e,n){t.mixin(e,n)}}),ace.define("ace/lib/keys",["require","exports","module","ace/lib/oop"],function(e,t,n){"use strict";var r=e("./oop"),i=function(){var e={MODIFIER_KEYS:{16:"Shift",17:"Ctrl",18:"Alt",224:"Meta"},KEY_MODS:{ctrl:1,alt:2,option:2,shift:4,"super":8,meta:8,command:8,cmd:8},FUNCTION_KEYS:{8:"Backspace",9:"Tab",13:"Return",19:"Pause",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"Print",45:"Insert",46:"Delete",96:"Numpad0",97:"Numpad1",98:"Numpad2",99:"Numpad3",100:"Numpad4",101:"Numpad5",102:"Numpad6",103:"Numpad7",104:"Numpad8",105:"Numpad9","-13":"NumpadEnter",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"Numlock",145:"Scrolllock"},PRINTABLE_KEYS:{32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",61:"=",65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z",107:"+",109:"-",110:".",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"}},t,n;for(n in e.FUNCTION_KEYS)t=e.FUNCTION_KEYS[n].toLowerCase(),e[t]=parseInt(n,10);for(n in e.PRINTABLE_KEYS)t=e.PRINTABLE_KEYS[n].toLowerCase(),e[t]=parseInt(n,10);return r.mixin(e,e.MODIFIER_KEYS),r.mixin(e,e.PRINTABLE_KEYS),r.mixin(e,e.FUNCTION_KEYS),e.enter=e["return"],e.escape=e.esc,e.del=e["delete"],e[173]="-",function(){var t=["cmd","ctrl","alt","shift"];for(var n=Math.pow(2,t.length);n--;)e.KEY_MODS[n]=t.filter(function(t){return n&e.KEY_MODS[t]}).join("-")+"-"}(),e}();r.mixin(t,i),t.keyCodeToString=function(e){var t=i[e];return typeof t!="string"&&(t=String.fromCharCode(e)),t.toLowerCase()}}),ace.define("ace/lib/useragent",["require","exports","module"],function(e,t,n){"use strict";t.OS={LINUX:"LINUX",MAC:"MAC",WINDOWS:"WINDOWS"},t.getOS=function(){return t.isMac?t.OS.MAC:t.isLinux?t.OS.LINUX:t.OS.WINDOWS};if(typeof navigator!="object")return;var r=(navigator.platform.match(/mac|win|linux/i)||["other"])[0].toLowerCase(),i=navigator.userAgent;t.isWin=r=="win",t.isMac=r=="mac",t.isLinux=r=="linux",t.isIE=navigator.appName=="Microsoft Internet Explorer"||navigator.appName.indexOf("MSAppHost")>=0?parseFloat((i.match(/(?:MSIE |Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/)||[])[1]):parseFloat((i.match(/(?:Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/)||[])[1]),t.isOldIE=t.isIE&&t.isIE<9,t.isGecko=t.isMozilla=(window.Controllers||window.controllers)&&window.navigator.product==="Gecko",t.isOldGecko=t.isGecko&&parseInt((i.match(/rv\:(\d+)/)||[])[1],10)<4,t.isOpera=window.opera&&Object.prototype.toString.call(window.opera)=="[object Opera]",t.isWebKit=parseFloat(i.split("WebKit/")[1])||undefined,t.isChrome=parseFloat(i.split(" Chrome/")[1])||undefined,t.isAIR=i.indexOf("AdobeAIR")>=0,t.isIPad=i.indexOf("iPad")>=0,t.isTouchPad=i.indexOf("TouchPad")>=0,t.isChromeOS=i.indexOf(" CrOS ")>=0}),ace.define("ace/lib/event",["require","exports","module","ace/lib/keys","ace/lib/useragent"],function(e,t,n){"use strict";function o(e,t,n){var o=s(t);if(!i.isMac&&u){if(u[91]||u[92])o|=8;if(u.altGr){if((3&o)==3)return;u.altGr=0}if(n===18||n===17){var f=t.location||t.keyLocation;if(n===17&&f===1)a=t.timeStamp;else if(n===18&&o===3&&f===2){var l=-a;a=t.timeStamp,l+=a,l<3&&(u.altGr=!0)}}}if(n in r.MODIFIER_KEYS){switch(r.MODIFIER_KEYS[n]){case"Alt":o=2;break;case"Shift":o=4;break;case"Ctrl":o=1;break;default:o=8}n=-1}o&8&&(n===91||n===93)&&(n=-1);if(!o&&n===13)if(t.location||t.keyLocation===3){e(t,o,-n);if(t.defaultPrevented)return}if(i.isChromeOS&&o&8){e(t,o,n);if(t.defaultPrevented)return;o&=-9}return!!o||n in r.FUNCTION_KEYS||n in r.PRINTABLE_KEYS?e(t,o,n):!1}var r=e("./keys"),i=e("./useragent");t.addListener=function(e,t,n){if(e.addEventListener)return e.addEventListener(t,n,!1);if(e.attachEvent){var r=function(){n.call(e,window.event)};n._wrapper=r,e.attachEvent("on"+t,r)}},t.removeListener=function(e,t,n){if(e.removeEventListener)return e.removeEventListener(t,n,!1);e.detachEvent&&e.detachEvent("on"+t,n._wrapper||n)},t.stopEvent=function(e){return t.stopPropagation(e),t.preventDefault(e),!1},t.stopPropagation=function(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0},t.preventDefault=function(e){e.preventDefault?e.preventDefault():e.returnValue=!1},t.getButton=function(e){return e.type=="dblclick"?0:e.type=="contextmenu"||i.isMac&&e.ctrlKey&&!e.altKey&&!e.shiftKey?2:e.preventDefault?e.button:{1:0,2:2,4:1}[e.button]},t.capture=function(e,n,r){function i(e){n&&n(e),r&&r(e),t.removeListener(document,"mousemove",n,!0),t.removeListener(document,"mouseup",i,!0),t.removeListener(document,"dragstart",i,!0)}return t.addListener(document,"mousemove",n,!0),t.addListener(document,"mouseup",i,!0),t.addListener(document,"dragstart",i,!0),i},t.addMouseWheelListener=function(e,n){"onmousewheel"in e?t.addListener(e,"mousewheel",function(e){var t=8;e.wheelDeltaX!==undefined?(e.wheelX=-e.wheelDeltaX/t,e.wheelY=-e.wheelDeltaY/t):(e.wheelX=0,e.wheelY=-e.wheelDelta/t),n(e)}):"onwheel"in e?t.addListener(e,"wheel",function(e){var t=.35;switch(e.deltaMode){case e.DOM_DELTA_PIXEL:e.wheelX=e.deltaX*t||0,e.wheelY=e.deltaY*t||0;break;case e.DOM_DELTA_LINE:case e.DOM_DELTA_PAGE:e.wheelX=(e.deltaX||0)*5,e.wheelY=(e.deltaY||0)*5}n(e)}):t.addListener(e,"DOMMouseScroll",function(e){e.axis&&e.axis==e.HORIZONTAL_AXIS?(e.wheelX=(e.detail||0)*5,e.wheelY=0):(e.wheelX=0,e.wheelY=(e.detail||0)*5),n(e)})},t.addMultiMouseDownListener=function(e,n,r,s){var o=0,u,a,f,l={2:"dblclick",3:"tripleclick",4:"quadclick"};t.addListener(e,"mousedown",function(e){t.getButton(e)!==0?o=0:e.detail>1?(o++,o>4&&(o=1)):o=1;if(i.isIE){var c=Math.abs(e.clientX-u)>5||Math.abs(e.clientY-a)>5;if(!f||c)o=1;f&&clearTimeout(f),f=setTimeout(function(){f=null},n[o-1]||600),o==1&&(u=e.clientX,a=e.clientY)}r[s]("mousedown",e);if(o>4)o=0;else if(o>1)return r[s](l[o],e)}),i.isOldIE&&t.addListener(e,"dblclick",function(e){o=2,f&&clearTimeout(f),f=setTimeout(function(){f=null},n[o-1]||600),r[s]("mousedown",e),r[s](l[o],e)})};var s=!i.isMac||!i.isOpera||"KeyboardEvent"in window?function(e){return 0|(e.ctrlKey?1:0)|(e.altKey?2:0)|(e.shiftKey?4:0)|(e.metaKey?8:0)}:function(e){return 0|(e.metaKey?1:0)|(e.altKey?2:0)|(e.shiftKey?4:0)|(e.ctrlKey?8:0)};t.getModifierString=function(e){return r.KEY_MODS[s(e)]};var u=null,a=0;t.addCommandKeyListener=function(e,n){var r=t.addListener;if(i.isOldGecko||i.isOpera&&!("KeyboardEvent"in window)){var s=null;r(e,"keydown",function(e){s=e.keyCode}),r(e,"keypress",function(e){return o(n,e,s)})}else{var a=null;r(e,"keydown",function(e){u[e.keyCode]=!0;var t=o(n,e,e.keyCode);return a=e.defaultPrevented,t}),r(e,"keypress",function(e){a&&(e.ctrlKey||e.altKey||e.shiftKey||e.metaKey)&&(t.stopEvent(e),a=null)}),r(e,"keyup",function(e){u[e.keyCode]=null}),u||(u=Object.create(null),r(window,"focus",function(e){u=Object.create(null)}))}};if(window.postMessage&&!i.isOldIE){var f=1;t.nextTick=function(e,n){n=n||window;var r="zero-timeout-message-"+f;t.addListener(n,"message",function i(s){s.data==r&&(t.stopPropagation(s),t.removeListener(n,"message",i),e())}),n.postMessage(r,"*")}}t.nextFrame=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||window.oRequestAnimationFrame,t.nextFrame?t.nextFrame=t.nextFrame.bind(window):t.nextFrame=function(e){setTimeout(e,17)}}),ace.define("ace/lib/lang",["require","exports","module"],function(e,t,n){"use strict";t.last=function(e){return e[e.length-1]},t.stringReverse=function(e){return e.split("").reverse().join("")},t.stringRepeat=function(e,t){var n="";while(t>0){t&1&&(n+=e);if(t>>=1)e+=e}return n};var r=/^\s\s*/,i=/\s\s*$/;t.stringTrimLeft=function(e){return e.replace(r,"")},t.stringTrimRight=function(e){return e.replace(i,"")},t.copyObject=function(e){var t={};for(var n in e)t[n]=e[n];return t},t.copyArray=function(e){var t=[];for(var n=0,r=e.length;n<r;n++)e[n]&&typeof e[n]=="object"?t[n]=this.copyObject(e[n]):t[n]=e[n];return t},t.deepCopy=function(e){if(typeof e!="object"||!e)return e;var n=e.constructor;if(n===RegExp)return e;var r=n();for(var i in e)typeof e[i]=="object"?r[i]=t.deepCopy(e[i]):r[i]=e[i];return r},t.arrayToMap=function(e){var t={};for(var n=0;n<e.length;n++)t[e[n]]=1;return t},t.createMap=function(e){var t=Object.create(null);for(var n in e)t[n]=e[n];return t},t.arrayRemove=function(e,t){for(var n=0;n<=e.length;n++)t===e[n]&&e.splice(n,1)},t.escapeRegExp=function(e){return e.replace(/([.*+?^${}()|[\]\/\\])/g,"\\$1")},t.escapeHTML=function(e){return e.replace(/&/g,"&#38;").replace(/"/g,"&#34;").replace(/'/g,"&#39;").replace(/</g,"&#60;")},t.getMatchOffsets=function(e,t){var n=[];return e.replace(t,function(e){n.push({offset:arguments[arguments.length-2],length:e.length})}),n},t.deferredCall=function(e){var t=null,n=function(){t=null,e()},r=function(e){return r.cancel(),t=setTimeout(n,e||0),r};return r.schedule=r,r.call=function(){return this.cancel(),e(),r},r.cancel=function(){return clearTimeout(t),t=null,r},r.isPending=function(){return t},r},t.delayedCall=function(e,t){var n=null,r=function(){n=null,e()},i=function(e){n==null&&(n=setTimeout(r,e||t))};return i.delay=function(e){n&&clearTimeout(n),n=setTimeout(r,e||t)},i.schedule=i,i.call=function(){this.cancel(),e()},i.cancel=function(){n&&clearTimeout(n),n=null},i.isPending=function(){return n},i}}),ace.define("ace/keyboard/textinput",["require","exports","module","ace/lib/event","ace/lib/useragent","ace/lib/dom","ace/lib/lang"],function(e,t,n){"use strict";var r=e("../lib/event"),i=e("../lib/useragent"),s=e("../lib/dom"),o=e("../lib/lang"),u=i.isChrome<18,a=i.isIE,f=function(e,t){function b(e){if(h)return;if(k)t=0,r=e?0:n.value.length-1;else var t=e?2:1,r=2;try{n.setSelectionRange(t,r)}catch(i){}}function w(){if(h)return;n.value=f,i.isWebKit&&y.schedule()}function q(){setTimeout(function(){p&&(n.style.cssText=p,p=""),t.renderer.$keepTextAreaAtCursor==null&&(t.renderer.$keepTextAreaAtCursor=!0,t.renderer.$moveTextAreaToCursor())},0)}var n=s.createElement("textarea");n.className="ace_text-input",i.isTouchPad&&n.setAttribute("x-palm-disable-auto-cap",!0),n.wrap="off",n.autocorrect="off",n.autocapitalize="off",n.spellcheck=!1,n.style.opacity="0",e.insertBefore(n,e.firstChild);var f="",l=!1,c=!1,h=!1,p="",d=!0;try{var v=document.activeElement===n}catch(m){}r.addListener(n,"blur",function(){t.onBlur(),v=!1}),r.addListener(n,"focus",function(){v=!0,t.onFocus(),b()}),this.focus=function(){n.focus()},this.blur=function(){n.blur()},this.isFocused=function(){return v};var g=o.delayedCall(function(){v&&b(d)}),y=o.delayedCall(function(){h||(n.value=f,v&&b())});i.isWebKit||t.addEventListener("changeSelection",function(){t.selection.isEmpty()!=d&&(d=!d,g.schedule())}),w(),v&&t.onFocus();var E=function(e){return e.selectionStart===0&&e.selectionEnd===e.value.length};!n.setSelectionRange&&n.createTextRange&&(n.setSelectionRange=function(e,t){var n=this.createTextRange();n.collapse(!0),n.moveStart("character",e),n.moveEnd("character",t),n.select()},E=function(e){try{var t=e.ownerDocument.selection.createRange()}catch(n){}return!t||t.parentElement()!=e?!1:t.text==e.value});if(i.isOldIE){var S=!1,x=function(e){if(S)return;var t=n.value;if(h||!t||t==f)return;if(e&&t==f[0])return T.schedule();A(t),S=!0,w(),S=!1},T=o.delayedCall(x);r.addListener(n,"propertychange",x);var N={13:1,27:1};r.addListener(n,"keyup",function(e){h&&(!n.value||N[e.keyCode])&&setTimeout(F,0);if((n.value.charCodeAt(0)||0)<129)return T.call();h?j():B()}),r.addListener(n,"keydown",function(e){T.schedule(50)})}var C=function(e){l?l=!1:E(n)?(t.selectAll(),b()):k&&b(t.selection.isEmpty())},k=null;this.setInputHandler=function(e){k=e},this.getInputHandler=function(){return k};var L=!1,A=function(e){k&&(e=k(e),k=null),c?(b(),e&&t.onPaste(e),c=!1):e==f.charAt(0)?L?t.execCommand("del",{source:"ace"}):t.execCommand("backspace",{source:"ace"}):(e.substring(0,2)==f?e=e.substr(2):e.charAt(0)==f.charAt(0)?e=e.substr(1):e.charAt(e.length-1)==f.charAt(0)&&(e=e.slice(0,-1)),e.charAt(e.length-1)==f.charAt(0)&&(e=e.slice(0,-1)),e&&t.onTextInput(e)),L&&(L=!1)},O=function(e){if(h)return;var t=n.value;A(t),w()},M=function(e,t){var n=e.clipboardData||window.clipboardData;if(!n||u)return;var r=a?"Text":"text/plain";return t?n.setData(r,t)!==!1:n.getData(r)},_=function(e,i){var s=t.getCopyText();if(!s)return r.preventDefault(e);M(e,s)?(i?t.onCut():t.onCopy(),r.preventDefault(e)):(l=!0,n.value=s,n.select(),setTimeout(function(){l=!1,w(),b(),i?t.onCut():t.onCopy()}))},D=function(e){_(e,!0)},P=function(e){_(e,!1)},H=function(e){var s=M(e);typeof s=="string"?(s&&t.onPaste(s),i.isIE&&setTimeout(b),r.preventDefault(e)):(n.value="",c=!0)};r.addCommandKeyListener(n,t.onCommandKey.bind(t)),r.addListener(n,"select",C),r.addListener(n,"input",O),r.addListener(n,"cut",D),r.addListener(n,"copy",P),r.addListener(n,"paste",H),(!("oncut"in n)||!("oncopy"in n)||!("onpaste"in n))&&r.addListener(e,"keydown",function(e){if(i.isMac&&!e.metaKey||!e.ctrlKey)return;switch(e.keyCode){case 67:P(e);break;case 86:H(e);break;case 88:D(e)}});var B=function(e){if(h||!t.onCompositionStart)return;h={},t.onCompositionStart(),setTimeout(j,0),t.on("mousedown",F),t.selection.isEmpty()||(t.insert(""),t.session.markUndoGroup(),t.selection.clearSelection()),t.session.markUndoGroup()},j=function(){if(!h||!t.onCompositionUpdate)return;var e=n.value.replace(/\x01/g,"");if(h.lastValue===e)return;t.onCompositionUpdate(e),h.lastValue&&t.undo(),h.lastValue=e;if(h.lastValue){var r=t.selection.getRange();t.insert(h.lastValue),t.session.markUndoGroup(),h.range=t.selection.getRange(),t.selection.setRange(r),t.selection.clearSelection()}},F=function(e){if(!t.onCompositionEnd)return;var r=h;h=!1;var i=setTimeout(function(){i=null;var e=n.value.replace(/\x01/g,"");if(h)return;e==r.lastValue?w():!r.lastValue&&e&&(w(),A(e))});k=function(n){return i&&clearTimeout(i),n=n.replace(/\x01/g,""),n==r.lastValue?"":(r.lastValue&&i&&t.undo(),n)},t.onCompositionEnd(),t.removeListener("mousedown",F),e.type=="compositionend"&&r.range&&t.selection.setRange(r.range)},I=o.delayedCall(j,50);r.addListener(n,"compositionstart",B),i.isGecko?r.addListener(n,"text",function(){I.schedule()}):(r.addListener(n,"keyup",function(){I.schedule()}),r.addListener(n,"keydown",function(){I.schedule()})),r.addListener(n,"compositionend",F),this.getElement=function(){return n},this.setReadOnly=function(e){n.readOnly=e},this.onContextMenu=function(e){L=!0,b(t.selection.isEmpty()),t._emit("nativecontextmenu",{target:t,domEvent:e}),this.moveToMouse(e,!0)},this.moveToMouse=function(e,o){p||(p=n.style.cssText),n.style.cssText=(o?"z-index:100000;":"")+(i.isIE?"opacity:0.1;":"");var u=t.container.getBoundingClientRect(),a=s.computedStyle(t.container),f=u.top+(parseInt(a.borderTopWidth)||0),l=u.left+(parseInt(u.borderLeftWidth)||0),c=u.bottom-f-n.clientHeight,h=function(e){n.style.left=e.clientX-l-2+"px",n.style.top=Math.min(e.clientY-f-2,c)+"px"};h(e);if(e.type!="mousedown")return;t.renderer.$keepTextAreaAtCursor&&(t.renderer.$keepTextAreaAtCursor=null),i.isWin&&r.capture(t.container,h,q)},this.onContextMenuClose=q;if(!i.isGecko||i.isMac){var R=function(e){t.textInput.onContextMenu(e),q()};r.addListener(t.renderer.scroller,"contextmenu",R),r.addListener(n,"contextmenu",R)}};t.TextInput=f}),ace.define("ace/mouse/default_handlers",["require","exports","module","ace/lib/dom","ace/lib/event","ace/lib/useragent"],function(e,t,n){"use strict";function u(e){e.$clickSelection=null;var t=e.editor;t.setDefaultHandler("mousedown",this.onMouseDown.bind(e)),t.setDefaultHandler("dblclick",this.onDoubleClick.bind(e)),t.setDefaultHandler("tripleclick",this.onTripleClick.bind(e)),t.setDefaultHandler("quadclick",this.onQuadClick.bind(e)),t.setDefaultHandler("mousewheel",this.onMouseWheel.bind(e));var n=["select","startSelect","selectEnd","selectAllEnd","selectByWordsEnd","selectByLinesEnd","dragWait","dragWaitEnd","focusWait"];n.forEach(function(t){e[t]=this[t]},this),e.selectByLines=this.extendSelectionBy.bind(e,"getLineRange"),e.selectByWords=this.extendSelectionBy.bind(e,"getWordRange")}function a(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}function f(e,t){if(e.start.row==e.end.row)var n=2*t.column-e.start.column-e.end.column;else if(e.start.row==e.end.row-1&&!e.start.column&&!e.end.column)var n=t.column-4;else var n=2*t.row-e.start.row-e.end.row;return n<0?{cursor:e.start,anchor:e.end}:{cursor:e.end,anchor:e.start}}var r=e("../lib/dom"),i=e("../lib/event"),s=e("../lib/useragent"),o=0;(function(){this.onMouseDown=function(e){var t=e.inSelection(),n=e.getDocumentPosition();this.mousedownEvent=e;var r=this.editor,i=e.getButton();if(i!==0){var s=r.getSelectionRange(),o=s.isEmpty();o&&r.selection.moveToPosition(n),r.textInput.onContextMenu(e.domEvent);return}if(t&&!r.isFocused()){r.focus();if(this.$focusTimout&&!this.$clickSelection&&!r.inMultiSelectMode){this.mousedownEvent.time=Date.now(),this.setState("focusWait"),this.captureMouse(e);return}}return this.captureMouse(e),!t||this.$clickSelection||e.getShiftKey()||r.inMultiSelectMode?this.startSelect(n):t&&(this.mousedownEvent.time=Date.now(),this.startSelect(n)),e.preventDefault()},this.startSelect=function(e){e=e||this.editor.renderer.screenToTextCoordinates(this.x,this.y);var t=this.editor,n=this.mousedownEvent.getShiftKey();setTimeout(function(){n?t.selection.selectToPosition(e):this.$clickSelection||t.selection.moveToPosition(e),this.select()}.bind(this),0),t.renderer.scroller.setCapture&&t.renderer.scroller.setCapture(),t.setStyle("ace_selecting"),this.setState("select")},this.select=function(){var e,t=this.editor,n=t.renderer.screenToTextCoordinates(this.x,this.y);if(this.$clickSelection){var r=this.$clickSelection.comparePoint(n);if(r==-1)e=this.$clickSelection.end;else if(r==1)e=this.$clickSelection.start;else{var i=f(this.$clickSelection,n);n=i.cursor,e=i.anchor}t.selection.setSelectionAnchor(e.row,e.column)}t.selection.selectToPosition(n),t.renderer.scrollCursorIntoView()},this.extendSelectionBy=function(e){var t,n=this.editor,r=n.renderer.screenToTextCoordinates(this.x,this.y),i=n.selection[e](r.row,r.column);if(this.$clickSelection){var s=this.$clickSelection.comparePoint(i.start),o=this.$clickSelection.comparePoint(i.end);if(s==-1&&o<=0){t=this.$clickSelection.end;if(i.end.row!=r.row||i.end.column!=r.column)r=i.start}else if(o==1&&s>=0){t=this.$clickSelection.start;if(i.start.row!=r.row||i.start.column!=r.column)r=i.end}else if(s==-1&&o==1)r=i.end,t=i.start;else{var u=f(this.$clickSelection,r);r=u.cursor,t=u.anchor}n.selection.setSelectionAnchor(t.row,t.column)}n.selection.selectToPosition(r),n.renderer.scrollCursorIntoView()},this.selectEnd=this.selectAllEnd=this.selectByWordsEnd=this.selectByLinesEnd=function(){this.$clickSelection=null,this.editor.unsetStyle("ace_selecting"),this.editor.renderer.scroller.releaseCapture&&this.editor.renderer.scroller.releaseCapture()},this.focusWait=function(){var e=a(this.mousedownEvent.x,this.mousedownEvent.y,this.x,this.y),t=Date.now();(e>o||t-this.mousedownEvent.time>this.$focusTimout)&&this.startSelect(this.mousedownEvent.getDocumentPosition())},this.onDoubleClick=function(e){var t=e.getDocumentPosition(),n=this.editor,r=n.session,i=r.getBracketRange(t);i?(i.isEmpty()&&(i.start.column--,i.end.column++),this.setState("select")):(i=n.selection.getWordRange(t.row,t.column),this.setState("selectByWords")),this.$clickSelection=i},this.onTripleClick=function(e){var t=e.getDocumentPosition(),n=this.editor;this.setState("selectByLines");var r=n.getSelectionRange();r.isMultiLine()&&r.contains(t.row,t.column)?(this.$clickSelection=n.selection.getLineRange(r.start.row),this.$clickSelection.end=n.selection.getLineRange(r.end.row).end):this.$clickSelection=n.selection.getLineRange(t.row)},this.onQuadClick=function(e){var t=this.editor;t.selectAll(),this.$clickSelection=t.getSelectionRange(),this.setState("selectAll")},this.onMouseWheel=function(e){if(e.getAccelKey())return;e.getShiftKey()&&e.wheelY&&!e.wheelX&&(e.wheelX=e.wheelY,e.wheelY=0);var t=e.domEvent.timeStamp,n=t-(this.$lastScrollTime||0),r=this.editor,i=r.renderer.isScrollableBy(e.wheelX*e.speed,e.wheelY*e.speed);if(i||n<200)return this.$lastScrollTime=t,r.renderer.scrollBy(e.wheelX*e.speed,e.wheelY*e.speed),e.stop()}}).call(u.prototype),t.DefaultHandlers=u}),ace.define("ace/tooltip",["require","exports","module","ace/lib/oop","ace/lib/dom"],function(e,t,n){"use strict";function s(e){this.isOpen=!1,this.$element=null,this.$parentNode=e}var r=e("./lib/oop"),i=e("./lib/dom");(function(){this.$init=function(){return this.$element=i.createElement("div"),this.$element.className="ace_tooltip",this.$element.style.display="none",this.$parentNode.appendChild(this.$element),this.$element},this.getElement=function(){return this.$element||this.$init()},this.setText=function(e){i.setInnerText(this.getElement(),e)},this.setHtml=function(e){this.getElement().innerHTML=e},this.setPosition=function(e,t){this.getElement().style.left=e+"px",this.getElement().style.top=t+"px"},this.setClassName=function(e){i.addCssClass(this.getElement(),e)},this.show=function(e,t,n){e!=null&&this.setText(e),t!=null&&n!=null&&this.setPosition(t,n),this.isOpen||(this.getElement().style.display="block",this.isOpen=!0)},this.hide=function(){this.isOpen&&(this.getElement().style.display="none",this.isOpen=!1)},this.getHeight=function(){return this.getElement().offsetHeight},this.getWidth=function(){return this.getElement().offsetWidth}}).call(s.prototype),t.Tooltip=s}),ace.define("ace/mouse/default_gutter_handler",["require","exports","module","ace/lib/dom","ace/lib/oop","ace/lib/event","ace/tooltip"],function(e,t,n){"use strict";function u(e){function l(){var r=u.getDocumentPosition().row,s=n.$annotations[r];if(!s)return c();var o=t.session.getLength();if(r==o){var a=t.renderer.pixelToScreenCoordinates(0,u.y).row,l=u.$pos;if(a>t.session.documentToScreenRow(l.row,l.column))return c()}if(f==s)return;f=s.text.join("<br/>"),i.setHtml(f),i.show(),t.on("mousewheel",c);if(e.$tooltipFollowsMouse)h(u);else{var p=n.$cells[t.session.documentToScreenRow(r,0)].element,d=p.getBoundingClientRect(),v=i.getElement().style;v.left=d.right+"px",v.top=d.bottom+"px"}}function c(){o&&(o=clearTimeout(o)),f&&(i.hide(),f=null,t.removeEventListener("mousewheel",c))}function h(e){i.setPosition(e.x,e.y)}var t=e.editor,n=t.renderer.$gutterLayer,i=new a(t.container);e.editor.setDefaultHandler("guttermousedown",function(r){if(!t.isFocused()||r.getButton()!=0)return;var i=n.getRegion(r);if(i=="foldWidgets")return;var s=r.getDocumentPosition().row,o=t.session.selection;if(r.getShiftKey())o.selectTo(s,0);else{if(r.domEvent.detail==2)return t.selectAll(),r.preventDefault();e.$clickSelection=t.selection.getLineRange(s)}return e.setState("selectByLines"),e.captureMouse(r),r.preventDefault()});var o,u,f;e.editor.setDefaultHandler("guttermousemove",function(t){var n=t.domEvent.target||t.domEvent.srcElement;if(r.hasCssClass(n,"ace_fold-widget"))return c();f&&e.$tooltipFollowsMouse&&h(t),u=t;if(o)return;o=setTimeout(function(){o=null,u&&!e.isMousePressed?l():c()},50)}),s.addListener(t.renderer.$gutter,"mouseout",function(e){u=null;if(!f||o)return;o=setTimeout(function(){o=null,c()},50)}),t.on("changeSession",c)}function a(e){o.call(this,e)}var r=e("../lib/dom"),i=e("../lib/oop"),s=e("../lib/event"),o=e("../tooltip").Tooltip;i.inherits(a,o),function(){this.setPosition=function(e,t){var n=window.innerWidth||document.documentElement.clientWidth,r=window.innerHeight||document.documentElement.clientHeight,i=this.getWidth(),s=this.getHeight();e+=15,t+=15,e+i>n&&(e-=e+i-n),t+s>r&&(t-=20+s),o.prototype.setPosition.call(this,e,t)}}.call(a.prototype),t.GutterHandler=u}),ace.define("ace/mouse/mouse_event",["require","exports","module","ace/lib/event","ace/lib/useragent"],function(e,t,n){"use strict";var r=e("../lib/event"),i=e("../lib/useragent"),s=t.MouseEvent=function(e,t){this.domEvent=e,this.editor=t,this.x=this.clientX=e.clientX,this.y=this.clientY=e.clientY,this.$pos=null,this.$inSelection=null,this.propagationStopped=!1,this.defaultPrevented=!1};(function(){this.stopPropagation=function(){r.stopPropagation(this.domEvent),this.propagationStopped=!0},this.preventDefault=function(){r.preventDefault(this.domEvent),this.defaultPrevented=!0},this.stop=function(){this.stopPropagation(),this.preventDefault()},this.getDocumentPosition=function(){return this.$pos?this.$pos:(this.$pos=this.editor.renderer.screenToTextCoordinates(this.clientX,this.clientY),this.$pos)},this.inSelection=function(){if(this.$inSelection!==null)return this.$inSelection;var e=this.editor,t=e.getSelectionRange();if(t.isEmpty())this.$inSelection=!1;else{var n=this.getDocumentPosition();this.$inSelection=t.contains(n.row,n.column)}return this.$inSelection},this.getButton=function(){return r.getButton(this.domEvent)},this.getShiftKey=function(){return this.domEvent.shiftKey},this.getAccelKey=i.isMac?function(){return this.domEvent.metaKey}:function(){return this.domEvent.ctrlKey}}).call(s.prototype)}),ace.define("ace/mouse/dragdrop_handler",["require","exports","module","ace/lib/dom","ace/lib/event","ace/lib/useragent"],function(e,t,n){"use strict";function f(e){function T(e,n){var r=Date.now(),i=!n||e.row!=n.row,s=!n||e.column!=n.column;if(!S||i||s)t.$blockScrolling+=1,t.moveCursorToPosition(e),t.$blockScrolling-=1,S=r,x={x:p,y:d};else{var o=l(x.x,x.y,p,d);o>a?S=null:r-S>=u&&(t.renderer.scrollCursorIntoView(),S=null)}}function N(e,n){var r=Date.now(),i=t.renderer.layerConfig.lineHeight,s=t.renderer.layerConfig.characterWidth,u=t.renderer.scroller.getBoundingClientRect(),a={x:{left:p-u.left,right:u.right-p},y:{top:d-u.top,bottom:u.bottom-d}},f=Math.min(a.x.left,a.x.right),l=Math.min(a.y.top,a.y.bottom),c={row:e.row,column:e.column};f/s<=2&&(c.column+=a.x.left<a.x.right?-3:2),l/i<=1&&(c.row+=a.y.top<a.y.bottom?-1:1);var h=e.row!=c.row,v=e.column!=c.column,m=!n||e.row!=n.row;h||v&&!m?E?r-E>=o&&t.renderer.scrollCursorIntoView(c):E=r:E=null}function C(){var e=g;g=t.renderer.screenToTextCoordinates(p,d),T(g,e),N(g,e)}function k(){m=t.selection.toOrientedRange(),h=t.session.addMarker(m,"ace_selection",t.getSelectionStyle()),t.clearSelection(),t.isFocused()&&t.renderer.$cursorLayer.setBlinking(!1),clearInterval(v),v=setInterval(C,20),y=0,i.addListener(document,"mousemove",O)}function L(){clearInterval(v),t.session.removeMarker(h),h=null,t.$blockScrolling+=1,t.selection.fromOrientedRange(m),t.$blockScrolling-=1,t.isFocused()&&!w&&t.renderer.$cursorLayer.setBlinking(!t.getReadOnly()),m=null,y=0,E=null,S=null,i.removeListener(document,"mousemove",O)}function O(){A==null&&(A=setTimeout(function(){A!=null&&h&&L()},20))}function M(e){var t=e.types;return!t||Array.prototype.some.call(t,function(e){return e=="text/plain"||e=="Text"})}function _(e){var t=["copy","copymove","all","uninitialized"],n=["move","copymove","linkmove","all","uninitialized"],r=s.isMac?e.altKey:e.ctrlKey,i="uninitialized";try{i=e.dataTransfer.effectAllowed.toLowerCase()}catch(e){}var o="none";return r&&t.indexOf(i)>=0?o="copy":n.indexOf(i)>=0?o="move":t.indexOf(i)>=0&&(o="copy"),o}var t=e.editor,n=r.createElement("img");n.src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",s.isOpera&&(n.style.cssText="width:1px;height:1px;position:fixed;top:0;left:0;z-index:2147483647;opacity:0;");var f=["dragWait","dragWaitEnd","startDrag","dragReadyEnd","onMouseDrag"];f.forEach(function(t){e[t]=this[t]},this),t.addEventListener("mousedown",this.onMouseDown.bind(e));var c=t.container,h,p,d,v,m,g,y=0,b,w,E,S,x;this.onDragStart=function(e){if(this.cancelDrag||!c.draggable){var r=this;return setTimeout(function(){r.startSelect(),r.captureMouse(e)},0),e.preventDefault()}m=t.getSelectionRange();var i=e.dataTransfer;i.effectAllowed=t.getReadOnly()?"copy":"copyMove",s.isOpera&&(t.container.appendChild(n),n._top=n.offsetTop),i.setDragImage&&i.setDragImage(n,0,0),s.isOpera&&t.container.removeChild(n),i.clearData(),i.setData("Text",t.session.getTextRange()),w=!0,this.setState("drag")},this.onDragEnd=function(e){c.draggable=!1,w=!1,this.setState(null);if(!t.getReadOnly()){var n=e.dataTransfer.dropEffect;!b&&n=="move"&&t.session.remove(t.getSelectionRange()),t.renderer.$cursorLayer.setBlinking(!0)}this.editor.unsetStyle("ace_dragging")},this.onDragEnter=function(e){if(t.getReadOnly()||!M(e.dataTransfer))return;return h||k(),y++,e.dataTransfer.dropEffect=b=_(e),i.preventDefault(e)},this.onDragOver=function(e){if(t.getReadOnly()||!M(e.dataTransfer))return;return h||(k(),y++),A!==null&&(A=null),p=e.clientX,d=e.clientY,e.dataTransfer.dropEffect=b=_(e),i.preventDefault(e)},this.onDragLeave=function(e){y--;if(y<=0&&h)return L(),b=null,i.preventDefault(e)},this.onDrop=function(e){if(!h)return;var n=e.dataTransfer;if(w)switch(b){case"move":m.contains(g.row,g.column)?m={start:g,end:g}:m=t.moveText(m,g);break;case"copy":m=t.moveText(m,g,!0)}else{var r=n.getData("Text");m={start:g,end:t.session.insert(g,r)},t.focus(),b=null}return L(),i.preventDefault(e)},i.addListener(c,"dragstart",this.onDragStart.bind(e)),i.addListener(c,"dragend",this.onDragEnd.bind(e)),i.addListener(c,"dragenter",this.onDragEnter.bind(e)),i.addListener(c,"dragover",this.onDragOver.bind(e)),i.addListener(c,"dragleave",this.onDragLeave.bind(e)),i.addListener(c,"drop",this.onDrop.bind(e));var A=null}function l(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}var r=e("../lib/dom"),i=e("../lib/event"),s=e("../lib/useragent"),o=200,u=200,a=5;(function(){this.dragWait=function(){var e=Date.now()-this.mousedownEvent.time;e>this.editor.getDragDelay()&&this.startDrag()},this.dragWaitEnd=function(){var e=this.editor.container;e.draggable=!1,this.startSelect(this.mousedownEvent.getDocumentPosition()),this.selectEnd()},this.dragReadyEnd=function(e){this.editor.renderer.$cursorLayer.setBlinking(!this.editor.getReadOnly()),this.editor.unsetStyle("ace_dragging"),this.dragWaitEnd()},this.startDrag=function(){this.cancelDrag=!1;var e=this.editor.container;e.draggable=!0,this.editor.renderer.$cursorLayer.setBlinking(!1),this.editor.setStyle("ace_dragging"),this.setState("dragReady")},this.onMouseDrag=function(e){var t=this.editor.container;if(s.isIE&&this.state=="dragReady"){var n=l(this.mousedownEvent.x,this.mousedownEvent.y,this.x,this.y);n>3&&t.dragDrop()}if(this.state==="dragWait"){var n=l(this.mousedownEvent.x,this.mousedownEvent.y,this.x,this.y);n>0&&(t.draggable=!1,this.startSelect(this.mousedownEvent.getDocumentPosition()))}},this.onMouseDown=function(e){if(!this.$dragEnabled)return;this.mousedownEvent=e;var t=this.editor,n=e.inSelection(),r=e.getButton(),i=e.domEvent.detail||1;if(i===1&&r===0&&n){if(e.editor.inMultiSelectMode&&(e.getAccelKey()||e.getShiftKey()))return;this.mousedownEvent.time=Date.now();var o=e.domEvent.target||e.domEvent.srcElement;"unselectable"in o&&(o.unselectable="on");if(t.getDragDelay()){if(s.isWebKit){this.cancelDrag=!0;var u=t.container;u.draggable=!0}this.setState("dragWait")}else this.startDrag();this.captureMouse(e,this.onMouseDrag.bind(this)),e.defaultPrevented=!0}}}).call(f.prototype),t.DragdropHandler=f}),ace.define("ace/lib/net",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";var r=e("./dom");t.get=function(e,t){var n=new XMLHttpRequest;n.open("GET",e,!0),n.onreadystatechange=function(){n.readyState===4&&t(n.responseText)},n.send(null)},t.loadScript=function(e,t){var n=r.getDocumentHead(),i=document.createElement("script");i.src=e,n.appendChild(i),i.onload=i.onreadystatechange=function(e,n){if(n||!i.readyState||i.readyState=="loaded"||i.readyState=="complete")i=i.onload=i.onreadystatechange=null,n||t()}},t.qualifyURL=function(e){var t=document.createElement("a");return t.href=e,t.href}}),ace.define("ace/lib/event_emitter",["require","exports","module"],function(e,t,n){"use strict";var r={},i=function(){this.propagationStopped=!0},s=function(){this.defaultPrevented=!0};r._emit=r._dispatchEvent=function(e,t){this._eventRegistry||(this._eventRegistry={}),this._defaultHandlers||(this._defaultHandlers={});var n=this._eventRegistry[e]||[],r=this._defaultHandlers[e];if(!n.length&&!r)return;if(typeof t!="object"||!t)t={};t.type||(t.type=e),t.stopPropagation||(t.stopPropagation=i),t.preventDefault||(t.preventDefault=s),n=n.slice();for(var o=0;o<n.length;o++){n[o](t,this);if(t.propagationStopped)break}if(r&&!t.defaultPrevented)return r(t,this)},r._signal=function(e,t){var n=(this._eventRegistry||{})[e];if(!n)return;n=n.slice();for(var r=0;r<n.length;r++)n[r](t,this)},r.once=function(e,t){var n=this;t&&this.addEventListener(e,function r(){n.removeEventListener(e,r),t.apply(null,arguments)})},r.setDefaultHandler=function(e,t){var n=this._defaultHandlers;n||(n=this._defaultHandlers={_disabled_:{}});if(n[e]){var r=n[e],i=n._disabled_[e];i||(n._disabled_[e]=i=[]),i.push(r);var s=i.indexOf(t);s!=-1&&i.splice(s,1)}n[e]=t},r.removeDefaultHandler=function(e,t){var n=this._defaultHandlers;if(!n)return;var r=n._disabled_[e];if(n[e]==t){var i=n[e];r&&this.setDefaultHandler(e,r.pop())}else if(r){var s=r.indexOf(t);s!=-1&&r.splice(s,1)}},r.on=r.addEventListener=function(e,t,n){this._eventRegistry=this._eventRegistry||{};var r=this._eventRegistry[e];return r||(r=this._eventRegistry[e]=[]),r.indexOf(t)==-1&&r[n?"unshift":"push"](t),t},r.off=r.removeListener=r.removeEventListener=function(e,t){this._eventRegistry=this._eventRegistry||{};var n=this._eventRegistry[e];if(!n)return;var r=n.indexOf(t);r!==-1&&n.splice(r,1)},r.removeAllListeners=function(e){this._eventRegistry&&(this._eventRegistry[e]=[])},t.EventEmitter=r}),ace.define("ace/config",["require","exports","module","ace/lib/lang","ace/lib/oop","ace/lib/net","ace/lib/event_emitter"],function(e,t,n){"no use strict";function f(r){a.packaged=r||e.packaged||n.packaged||u.define&&define.packaged;if(!u.document)return"";var i={},s="",o=document.currentScript||document._currentScript,f=o&&o.ownerDocument||document,c=f.getElementsByTagName("script");for(var h=0;h<c.length;h++){var p=c[h],d=p.src||p.getAttribute("src");if(!d)continue;var v=p.attributes;for(var m=0,g=v.length;m<g;m++){var y=v[m];y.name.indexOf("data-ace-")===0&&(i[l(y.name.replace(/^data-ace-/,""))]=y.value)}var b=d.match(/^(.*)\/ace(\-\w+)?\.js(\?|$)/);b&&(s=b[1])}s&&(i.base=i.base||s,i.packaged=!0),i.basePath=i.base,i.workerPath=i.workerPath||i.base,i.modePath=i.modePath||i.base,i.themePath=i.themePath||i.base,delete i.base;for(var w in i)typeof i[w]!="undefined"&&t.set(w,i[w])}function l(e){return e.replace(/-(.)/g,function(e,t){return t.toUpperCase()})}var r=e("./lib/lang"),i=e("./lib/oop"),s=e("./lib/net"),o=e("./lib/event_emitter").EventEmitter,u=function(){return this}(),a={packaged:!1,workerPath:null,modePath:null,themePath:null,basePath:"",suffix:".js",$moduleUrls:{}};t.get=function(e){if(!a.hasOwnProperty(e))throw new Error("Unknown config key: "+e);return a[e]},t.set=function(e,t){if(!a.hasOwnProperty(e))throw new Error("Unknown config key: "+e);a[e]=t},t.all=function(){return r.copyObject(a)},i.implement(t,o),t.moduleUrl=function(e,t){if(a.$moduleUrls[e])return a.$moduleUrls[e];var n=e.split("/");t=t||n[n.length-2]||"";var r=t=="snippets"?"/":"-",i=n[n.length-1];if(r=="-"){var s=new RegExp("^"+t+"[\\-_]|[\\-_]"+t+"$","g");i=i.replace(s,"")}(!i||i==t)&&n.length>1&&(i=n[n.length-2]);var o=a[t+"Path"];return o==null?o=a.basePath:r=="/"&&(t=r=""),o&&o.slice(-1)!="/"&&(o+="/"),o+t+r+i+this.get("suffix")},t.setModuleUrl=function(e,t){return a.$moduleUrls[e]=t},t.$loading={},t.loadModule=function(n,r){var i,o;Array.isArray(n)&&(o=n[0],n=n[1]);try{i=e(n)}catch(u){}if(i&&!t.$loading[n])return r&&r(i);t.$loading[n]||(t.$loading[n]=[]),t.$loading[n].push(r);if(t.$loading[n].length>1)return;var a=function(){e([n],function(e){t._emit("load.module",{name:n,module:e});var r=t.$loading[n];t.$loading[n]=null,r.forEach(function(t){t&&t(e)})})};if(!t.get("packaged"))return a();s.loadScript(t.moduleUrl(n,o),a)},t.init=f;var c={setOptions:function(e){Object.keys(e).forEach(function(t){this.setOption(t,e[t])},this)},getOptions:function(e){var t={};return e?Array.isArray(e)||(t=e,e=Object.keys(t)):e=Object.keys(this.$options),e.forEach(function(e){t[e]=this.getOption(e)},this),t},setOption:function(e,t){if(this["$"+e]===t)return;var n=this.$options[e];if(!n)return typeof console!="undefined"&&console.warn&&console.warn('misspelled option "'+e+'"'),undefined;if(n.forwardTo)return this[n.forwardTo]&&this[n.forwardTo].setOption(e,t);n.handlesSet||(this["$"+e]=t),n&&n.set&&n.set.call(this,t)},getOption:function(e){var t=this.$options[e];return t?t.forwardTo?this[t.forwardTo]&&this[t.forwardTo].getOption(e):t&&t.get?t.get.call(this):this["$"+e]:(typeof console!="undefined"&&console.warn&&console.warn('misspelled option "'+e+'"'),undefined)}},h={};t.defineOptions=function(e,t,n){return e.$options||(h[t]=e.$options={}),Object.keys(n).forEach(function(t){var r=n[t];typeof r=="string"&&(r={forwardTo:r}),r.name||(r.name=t),e.$options[r.name]=r,"initialValue"in r&&(e["$"+r.name]=r.initialValue)}),i.implement(e,c),this},t.resetOptions=function(e){Object.keys(e.$options).forEach(function(t){var n=e.$options[t];"value"in n&&e.setOption(t,n.value)})},t.setDefaultValue=function(e,n,r){var i=h[e]||(h[e]={});i[n]&&(i.forwardTo?t.setDefaultValue(i.forwardTo,n,r):i[n].value=r)},t.setDefaultValues=function(e,n){Object.keys(n).forEach(function(r){t.setDefaultValue(e,r,n[r])})}}),ace.define("ace/mouse/mouse_handler",["require","exports","module","ace/lib/event","ace/lib/useragent","ace/mouse/default_handlers","ace/mouse/default_gutter_handler","ace/mouse/mouse_event","ace/mouse/dragdrop_handler","ace/config"],function(e,t,n){"use strict";var r=e("../lib/event"),i=e("../lib/useragent"),s=e("./default_handlers").DefaultHandlers,o=e("./default_gutter_handler").GutterHandler,u=e("./mouse_event").MouseEvent,a=e("./dragdrop_handler").DragdropHandler,f=e("../config"),l=function(e){var t=this;this.editor=e,new s(this),new o(this),new a(this);var n=function(t){!e.isFocused()&&e.textInput&&e.textInput.moveToMouse(t),e.focus()},u=e.renderer.getMouseEventTarget();r.addListener(u,"click",this.onMouseEvent.bind(this,"click")),r.addListener(u,"mousemove",this.onMouseMove.bind(this,"mousemove")),r.addMultiMouseDownListener(u,[400,300,250],this,"onMouseEvent"),e.renderer.scrollBarV&&(r.addMultiMouseDownListener(e.renderer.scrollBarV.inner,[400,300,250],this,"onMouseEvent"),r.addMultiMouseDownListener(e.renderer.scrollBarH.inner,[400,300,250],this,"onMouseEvent"),i.isIE&&(r.addListener(e.renderer.scrollBarV.element,"mousedown",n),r.addListener(e.renderer.scrollBarH.element,"mousemove",n))),r.addMouseWheelListener(e.container,this.onMouseWheel.bind(this,"mousewheel"));var f=e.renderer.$gutter;r.addListener(f,"mousedown",this.onMouseEvent.bind(this,"guttermousedown")),r.addListener(f,"click",this.onMouseEvent.bind(this,"gutterclick")),r.addListener(f,"dblclick",this.onMouseEvent.bind(this,"gutterdblclick")),r.addListener(f,"mousemove",this.onMouseEvent.bind(this,"guttermousemove")),r.addListener(u,"mousedown",n),r.addListener(f,"mousedown",function(t){return e.focus(),r.preventDefault(t)}),e.on("mousemove",function(n){if(t.state||t.$dragDelay||!t.$dragEnabled)return;var r=e.renderer.screenToTextCoordinates(n.x,n.y),i=e.session.selection.getRange(),s=e.renderer;!i.isEmpty()&&i.insideStart(r.row,r.column)?s.setCursorStyle("default"):s.setCursorStyle("")})};(function(){this.onMouseEvent=function(e,t){this.editor._emit(e,new u(t,this.editor))},this.onMouseMove=function(e,t){var n=this.editor._eventRegistry&&this.editor._eventRegistry.mousemove;if(!n||!n.length)return;this.editor._emit(e,new u(t,this.editor))},this.onMouseWheel=function(e,t){var n=new u(t,this.editor);n.speed=this.$scrollSpeed*2,n.wheelX=t.wheelX,n.wheelY=t.wheelY,this.editor._emit(e,n)},this.setState=function(e){this.state=e},this.captureMouse=function(e,t){this.x=e.x,this.y=e.y,this.isMousePressed=!0;var n=this.editor.renderer;n.$keepTextAreaAtCursor&&(n.$keepTextAreaAtCursor=null);var s=this,o=function(e){if(!e)return;if(i.isWebKit&&!e.which&&s.releaseMouse)return s.releaseMouse();s.x=e.clientX,s.y=e.clientY,t&&t(e),s.mouseEvent=new u(e,s.editor),s.$mouseMoved=!0},a=function(e){clearInterval(l),f(),s[s.state+"End"]&&s[s.state+"End"](e),s.state="",n.$keepTextAreaAtCursor==null&&(n.$keepTextAreaAtCursor=!0,n.$moveTextAreaToCursor()),s.isMousePressed=!1,s.$onCaptureMouseMove=s.releaseMouse=null,e&&s.onMouseEvent("mouseup",e)},f=function(){s[s.state]&&s[s.state](),s.$mouseMoved=!1};if(i.isOldIE&&e.domEvent.type=="dblclick")return setTimeout(function(){a(e)});s.$onCaptureMouseMove=o,s.releaseMouse=r.capture(this.editor.container,o,a);var l=setInterval(f,20)},this.releaseMouse=null,this.cancelContextMenu=function(){var e=function(t){if(t&&t.domEvent&&t.domEvent.type!="contextmenu")return;this.editor.off("nativecontextmenu",e),t&&t.domEvent&&r.stopEvent(t.domEvent)}.bind(this);setTimeout(e,10),this.editor.on("nativecontextmenu",e)}}).call(l.prototype),f.defineOptions(l.prototype,"mouseHandler",{scrollSpeed:{initialValue:2},dragDelay:{initialValue:i.isMac?150:0},dragEnabled:{initialValue:!0},focusTimout:{initialValue:0},tooltipFollowsMouse:{initialValue:!0}}),t.MouseHandler=l}),ace.define("ace/mouse/fold_handler",["require","exports","module"],function(e,t,n){"use strict";function r(e){e.on("click",function(t){var n=t.getDocumentPosition(),r=e.session,i=r.getFoldAt(n.row,n.column,1);i&&(t.getAccelKey()?r.removeFold(i):r.expandFold(i),t.stop())}),e.on("gutterclick",function(t){var n=e.renderer.$gutterLayer.getRegion(t);if(n=="foldWidgets"){var r=t.getDocumentPosition().row,i=e.session;i.foldWidgets&&i.foldWidgets[r]&&e.session.onFoldWidgetClick(r,t),e.isFocused()||e.focus(),t.stop()}}),e.on("gutterdblclick",function(t){var n=e.renderer.$gutterLayer.getRegion(t);if(n=="foldWidgets"){var r=t.getDocumentPosition().row,i=e.session,s=i.getParentFoldRangeData(r,!0),o=s.range||s.firstRange;if(o){r=o.start.row;var u=i.getFoldAt(r,i.getLine(r).length,1);u?i.removeFold(u):(i.addFold("...",o),e.renderer.scrollCursorIntoView({row:o.start.row,column:0}))}t.stop()}})}t.FoldHandler=r}),ace.define("ace/keyboard/keybinding",["require","exports","module","ace/lib/keys","ace/lib/event"],function(e,t,n){"use strict";var r=e("../lib/keys"),i=e("../lib/event"),s=function(e){this.$editor=e,this.$data={editor:e},this.$handlers=[],this.setDefaultHandler(e.commands)};(function(){this.setDefaultHandler=function(e){this.removeKeyboardHandler(this.$defaultHandler),this.$defaultHandler=e,this.addKeyboardHandler(e,0)},this.setKeyboardHandler=function(e){var t=this.$handlers;if(t[t.length-1]==e)return;while(t[t.length-1]&&t[t.length-1]!=this.$defaultHandler)this.removeKeyboardHandler(t[t.length-1]);this.addKeyboardHandler(e,1)},this.addKeyboardHandler=function(e,t){if(!e)return;typeof e=="function"&&!e.handleKeyboard&&(e.handleKeyboard=e);var n=this.$handlers.indexOf(e);n!=-1&&this.$handlers.splice(n,1),t==undefined?this.$handlers.push(e):this.$handlers.splice(t,0,e),n==-1&&e.attach&&e.attach(this.$editor)},this.removeKeyboardHandler=function(e){var t=this.$handlers.indexOf(e);return t==-1?!1:(this.$handlers.splice(t,1),e.detach&&e.detach(this.$editor),!0)},this.getKeyboardHandler=function(){return this.$handlers[this.$handlers.length-1]},this.$callKeyboardHandlers=function(e,t,n,r){var s,o=!1,u=this.$editor.commands;for(var a=this.$handlers.length;a--;){s=this.$handlers[a].handleKeyboard(this.$data,e,t,n,r);if(!s||!s.command)continue;s.command=="null"?o=!0:o=u.exec(s.command,this.$editor,s.args,r),o&&r&&e!=-1&&s.passEvent!=1&&s.command.passEvent!=1&&i.stopEvent(r);if(o)break}return o},this.onCommandKey=function(e,t,n){var i=r.keyCodeToString(n);this.$callKeyboardHandlers(t,i,n,e)},this.onTextInput=function(e){var t=this.$callKeyboardHandlers(-1,e);t||this.$editor.commands.exec("insertstring",this.$editor,e)}}).call(s.prototype),t.KeyBinding=s}),ace.define("ace/range",["require","exports","module"],function(e,t,n){"use strict";var r=function(e,t){return e.row-t.row||e.column-t.column},i=function(e,t,n,r){this.start={row:e,column:t},this.end={row:n,column:r}};(function(){this.isEqual=function(e){return this.start.row===e.start.row&&this.end.row===e.end.row&&this.start.column===e.start.column&&this.end.column===e.end.column},this.toString=function(){return"Range: ["+this.start.row+"/"+this.start.column+"] -> ["+this.end.row+"/"+this.end.column+"]"},this.contains=function(e,t){return this.compare(e,t)==0},this.compareRange=function(e){var t,n=e.end,r=e.start;return t=this.compare(n.row,n.column),t==1?(t=this.compare(r.row,r.column),t==1?2:t==0?1:0):t==-1?-2:(t=this.compare(r.row,r.column),t==-1?-1:t==1?42:0)},this.comparePoint=function(e){return this.compare(e.row,e.column)},this.containsRange=function(e){return this.comparePoint(e.start)==0&&this.comparePoint(e.end)==0},this.intersects=function(e){var t=this.compareRange(e);return t==-1||t==0||t==1},this.isEnd=function(e,t){return this.end.row==e&&this.end.column==t},this.isStart=function(e,t){return this.start.row==e&&this.start.column==t},this.setStart=function(e,t){typeof e=="object"?(this.start.column=e.column,this.start.row=e.row):(this.start.row=e,this.start.column=t)},this.setEnd=function(e,t){typeof e=="object"?(this.end.column=e.column,this.end.row=e.row):(this.end.row=e,this.end.column=t)},this.inside=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)||this.isStart(e,t)?!1:!0:!1},this.insideStart=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)?!1:!0:!1},this.insideEnd=function(e,t){return this.compare(e,t)==0?this.isStart(e,t)?!1:!0:!1},this.compare=function(e,t){return!this.isMultiLine()&&e===this.start.row?t<this.start.column?-1:t>this.end.column?1:0:e<this.start.row?-1:e>this.end.row?1:this.start.row===e?t>=this.start.column?0:-1:this.end.row===e?t<=this.end.column?0:1:0},this.compareStart=function(e,t){return this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.compareEnd=function(e,t){return this.end.row==e&&this.end.column==t?1:this.compare(e,t)},this.compareInside=function(e,t){return this.end.row==e&&this.end.column==t?1:this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.clipRows=function(e,t){if(this.end.row>t)var n={row:t+1,column:0};else if(this.end.row<e)var n={row:e,column:0};if(this.start.row>t)var r={row:t+1,column:0};else if(this.start.row<e)var r={row:e,column:0};return i.fromPoints(r||this.start,n||this.end)},this.extend=function(e,t){var n=this.compare(e,t);if(n==0)return this;if(n==-1)var r={row:e,column:t};else var s={row:e,column:t};return i.fromPoints(r||this.start,s||this.end)},this.isEmpty=function(){return this.start.row===this.end.row&&this.start.column===this.end.column},this.isMultiLine=function(){return this.start.row!==this.end.row},this.clone=function(){return i.fromPoints(this.start,this.end)},this.collapseRows=function(){return this.end.column==0?new i(this.start.row,0,Math.max(this.start.row,this.end.row-1),0):new i(this.start.row,0,this.end.row,0)},this.toScreenRange=function(e){var t=e.documentToScreenPosition(this.start),n=e.documentToScreenPosition(this.end);return new i(t.row,t.column,n.row,n.column)},this.moveBy=function(e,t){this.start.row+=e,this.start.column+=t,this.end.row+=e,this.end.column+=t}}).call(i.prototype),i.fromPoints=function(e,t){return new i(e.row,e.column,t.row,t.column)},i.comparePoints=r,i.comparePoints=function(e,t){return e.row-t.row||e.column-t.column},t.Range=i}),ace.define("ace/selection",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/lib/event_emitter","ace/range"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/lang"),s=e("./lib/event_emitter").EventEmitter,o=e("./range").Range,u=function(e){this.session=e,this.doc=e.getDocument(),this.clearSelection(),this.lead=this.selectionLead=this.doc.createAnchor(0,0),this.anchor=this.selectionAnchor=this.doc.createAnchor(0,0);var t=this;this.lead.on("change",function(e){t._emit("changeCursor"),t.$isEmpty||t._emit("changeSelection"),!t.$keepDesiredColumnOnChange&&e.old.column!=e.value.column&&(t.$desiredColumn=null)}),this.selectionAnchor.on("change",function(){t.$isEmpty||t._emit("changeSelection")})};(function(){r.implement(this,s),this.isEmpty=function(){return this.$isEmpty||this.anchor.row==this.lead.row&&this.anchor.column==this.lead.column},this.isMultiLine=function(){return this.isEmpty()?!1:this.getRange().isMultiLine()},this.getCursor=function(){return this.lead.getPosition()},this.setSelectionAnchor=function(e,t){this.anchor.setPosition(e,t),this.$isEmpty&&(this.$isEmpty=!1,this._emit("changeSelection"))},this.getSelectionAnchor=function(){return this.$isEmpty?this.getSelectionLead():this.anchor.getPosition()},this.getSelectionLead=function(){return this.lead.getPosition()},this.shiftSelection=function(e){if(this.$isEmpty){this.moveCursorTo(this.lead.row,this.lead.column+e);return}var t=this.getSelectionAnchor(),n=this.getSelectionLead(),r=this.isBackwards();(!r||t.column!==0)&&this.setSelectionAnchor(t.row,t.column+e),(r||n.column!==0)&&this.$moveSelection(function(){this.moveCursorTo(n.row,n.column+e)})},this.isBackwards=function(){var e=this.anchor,t=this.lead;return e.row>t.row||e.row==t.row&&e.column>t.column},this.getRange=function(){var e=this.anchor,t=this.lead;return this.isEmpty()?o.fromPoints(t,t):this.isBackwards()?o.fromPoints(t,e):o.fromPoints(e,t)},this.clearSelection=function(){this.$isEmpty||(this.$isEmpty=!0,this._emit("changeSelection"))},this.selectAll=function(){var e=this.doc.getLength()-1;this.setSelectionAnchor(0,0),this.moveCursorTo(e,this.doc.getLine(e).length)},this.setRange=this.setSelectionRange=function(e,t){t?(this.setSelectionAnchor(e.end.row,e.end.column),this.selectTo(e.start.row,e.start.column)):(this.setSelectionAnchor(e.start.row,e.start.column),this.selectTo(e.end.row,e.end.column)),this.getRange().isEmpty()&&(this.$isEmpty=!0),this.$desiredColumn=null},this.$moveSelection=function(e){var t=this.lead;this.$isEmpty&&this.setSelectionAnchor(t.row,t.column),e.call(this)},this.selectTo=function(e,t){this.$moveSelection(function(){this.moveCursorTo(e,t)})},this.selectToPosition=function(e){this.$moveSelection(function(){this.moveCursorToPosition(e)})},this.moveTo=function(e,t){this.clearSelection(),this.moveCursorTo(e,t)},this.moveToPosition=function(e){this.clearSelection(),this.moveCursorToPosition(e)},this.selectUp=function(){this.$moveSelection(this.moveCursorUp)},this.selectDown=function(){this.$moveSelection(this.moveCursorDown)},this.selectRight=function(){this.$moveSelection(this.moveCursorRight)},this.selectLeft=function(){this.$moveSelection(this.moveCursorLeft)},this.selectLineStart=function(){this.$moveSelection(this.moveCursorLineStart)},this.selectLineEnd=function(){this.$moveSelection(this.moveCursorLineEnd)},this.selectFileEnd=function(){this.$moveSelection(this.moveCursorFileEnd)},this.selectFileStart=function(){this.$moveSelection(this.moveCursorFileStart)},this.selectWordRight=function(){this.$moveSelection(this.moveCursorWordRight)},this.selectWordLeft=function(){this.$moveSelection(this.moveCursorWordLeft)},this.getWordRange=function(e,t){if(typeof t=="undefined"){var n=e||this.lead;e=n.row,t=n.column}return this.session.getWordRange(e,t)},this.selectWord=function(){this.setSelectionRange(this.getWordRange())},this.selectAWord=function(){var e=this.getCursor(),t=this.session.getAWordRange(e.row,e.column);this.setSelectionRange(t)},this.getLineRange=function(e,t){var n=typeof e=="number"?e:this.lead.row,r,i=this.session.getFoldLine(n);return i?(n=i.start.row,r=i.end.row):r=n,t===!0?new o(n,0,r,this.session.getLine(r).length):new o(n,0,r+1,0)},this.selectLine=function(){this.setSelectionRange(this.getLineRange())},this.moveCursorUp=function(){this.moveCursorBy(-1,0)},this.moveCursorDown=function(){this.moveCursorBy(1,0)},this.moveCursorLeft=function(){var e=this.lead.getPosition(),t;if(t=this.session.getFoldAt(e.row,e.column,-1))this.moveCursorTo(t.start.row,t.start.column);else if(e.column==0)e.row>0&&this.moveCursorTo(e.row-1,this.doc.getLine(e.row-1).length);else{var n=this.session.getTabSize();this.session.isTabStop(e)&&this.doc.getLine(e.row).slice(e.column-n,e.column).split(" ").length-1==n?this.moveCursorBy(0,-n):this.moveCursorBy(0,-1)}},this.moveCursorRight=function(){var e=this.lead.getPosition(),t;if(t=this.session.getFoldAt(e.row,e.column,1))this.moveCursorTo(t.end.row,t.end.column);else if(this.lead.column==this.doc.getLine(this.lead.row).length)this.lead.row<this.doc.getLength()-1&&this.moveCursorTo(this.lead.row+1,0);else{var n=this.session.getTabSize(),e=this.lead;this.session.isTabStop(e)&&this.doc.getLine(e.row).slice(e.column,e.column+n).split(" ").length-1==n?this.moveCursorBy(0,n):this.moveCursorBy(0,1)}},this.moveCursorLineStart=function(){var e=this.lead.row,t=this.lead.column,n=this.session.documentToScreenRow(e,t),r=this.session.screenToDocumentPosition(n,0),i=this.session.getDisplayLine(e,null,r.row,r.column),s=i.match(/^\s*/);s[0].length!=t&&!this.session.$useEmacsStyleLineStart&&(r.column+=s[0].length),this.moveCursorToPosition(r)},this.moveCursorLineEnd=function(){var e=this.lead,t=this.session.getDocumentLastRowColumnPosition(e.row,e.column);if(this.lead.column==t.column){var n=this.session.getLine(t.row);if(t.column==n.length){var r=n.search(/\s+$/);r>0&&(t.column=r)}}this.moveCursorTo(t.row,t.column)},this.moveCursorFileEnd=function(){var e=this.doc.getLength()-1,t=this.doc.getLine(e).length;this.moveCursorTo(e,t)},this.moveCursorFileStart=function(){this.moveCursorTo(0,0)},this.moveCursorLongWordRight=function(){var e=this.lead.row,t=this.lead.column,n=this.doc.getLine(e),r=n.substring(t),i;this.session.nonTokenRe.lastIndex=0,this.session.tokenRe.lastIndex=0;var s=this.session.getFoldAt(e,t,1);if(s){this.moveCursorTo(s.end.row,s.end.column);return}if(i=this.session.nonTokenRe.exec(r))t+=this.session.nonTokenRe.lastIndex,this.session.nonTokenRe.lastIndex=0,r=n.substring(t);if(t>=n.length){this.moveCursorTo(e,n.length),this.moveCursorRight(),e<this.doc.getLength()-1&&this.moveCursorWordRight();return}if(i=this.session.tokenRe.exec(r))t+=this.session.tokenRe.lastIndex,this.session.tokenRe.lastIndex=0;this.moveCursorTo(e,t)},this.moveCursorLongWordLeft=function(){var e=this.lead.row,t=this.lead.column,n;if(n=this.session.getFoldAt(e,t,-1)){this.moveCursorTo(n.start.row,n.start.column);return}var r=this.session.getFoldStringAt(e,t,-1);r==null&&(r=this.doc.getLine(e).substring(0,t));var s=i.stringReverse(r),o;this.session.nonTokenRe.lastIndex=0,this.session.tokenRe.lastIndex=0;if(o=this.session.nonTokenRe.exec(s))t-=this.session.nonTokenRe.lastIndex,s=s.slice(this.session.nonTokenRe.lastIndex),this.session.nonTokenRe.lastIndex=0;if(t<=0){this.moveCursorTo(e,0),this.moveCursorLeft(),e>0&&this.moveCursorWordLeft();return}if(o=this.session.tokenRe.exec(s))t-=this.session.tokenRe.lastIndex,this.session.tokenRe.lastIndex=0;this.moveCursorTo(e,t)},this.$shortWordEndIndex=function(e){var t,n=0,r,i=/\s/,s=this.session.tokenRe;s.lastIndex=0;if(t=this.session.tokenRe.exec(e))n=this.session.tokenRe.lastIndex;else{while((r=e[n])&&i.test(r))n++;if(n<1){s.lastIndex=0;while((r=e[n])&&!s.test(r)){s.lastIndex=0,n++;if(i.test(r)){if(n>2){n--;break}while((r=e[n])&&i.test(r))n++;if(n>2)break}}}}return s.lastIndex=0,n},this.moveCursorShortWordRight=function(){var e=this.lead.row,t=this.lead.column,n=this.doc.getLine(e),r=n.substring(t),i=this.session.getFoldAt(e,t,1);if(i)return this.moveCursorTo(i.end.row,i.end.column);if(t==n.length){var s=this.doc.getLength();do e++,r=this.doc.getLine(e);while(e<s&&/^\s*$/.test(r));/^\s+/.test(r)||(r=""),t=0}var o=this.$shortWordEndIndex(r);this.moveCursorTo(e,t+o)},this.moveCursorShortWordLeft=function(){var e=this.lead.row,t=this.lead.column,n;if(n=this.session.getFoldAt(e,t,-1))return this.moveCursorTo(n.start.row,n.start.column);var r=this.session.getLine(e).substring(0,t);if(t==0){do e--,r=this.doc.getLine(e);while(e>0&&/^\s*$/.test(r));t=r.length,/\s+$/.test(r)||(r="")}var s=i.stringReverse(r),o=this.$shortWordEndIndex(s);return this.moveCursorTo(e,t-o)},this.moveCursorWordRight=function(){this.session.$selectLongWords?this.moveCursorLongWordRight():this.moveCursorShortWordRight()},this.moveCursorWordLeft=function(){this.session.$selectLongWords?this.moveCursorLongWordLeft():this.moveCursorShortWordLeft()},this.moveCursorBy=function(e,t){var n=this.session.documentToScreenPosition(this.lead.row,this.lead.column);t===0&&(this.$desiredColumn?n.column=this.$desiredColumn:this.$desiredColumn=n.column);var r=this.session.screenToDocumentPosition(n.row+e,n.column);e!==0&&t===0&&r.row===this.lead.row&&r.column===this.lead.column&&this.session.lineWidgets&&this.session.lineWidgets[r.row]&&r.row++,this.moveCursorTo(r.row,r.column+t,t===0)},this.moveCursorToPosition=function(e){this.moveCursorTo(e.row,e.column)},this.moveCursorTo=function(e,t,n){var r=this.session.getFoldAt(e,t,1);r&&(e=r.start.row,t=r.start.column),this.$keepDesiredColumnOnChange=!0,this.lead.setPosition(e,t),this.$keepDesiredColumnOnChange=!1,n||(this.$desiredColumn=null)},this.moveCursorToScreen=function(e,t,n){var r=this.session.screenToDocumentPosition(e,t);this.moveCursorTo(r.row,r.column,n)},this.detach=function(){this.lead.detach(),this.anchor.detach(),this.session=this.doc=null},this.fromOrientedRange=function(e){this.setSelectionRange(e,e.cursor==e.start),this.$desiredColumn=e.desiredColumn||this.$desiredColumn},this.toOrientedRange=function(e){var t=this.getRange();return e?(e.start.column=t.start.column,e.start.row=t.start.row,e.end.column=t.end.column,e.end.row=t.end.row):e=t,e.cursor=this.isBackwards()?e.start:e.end,e.desiredColumn=this.$desiredColumn,e},this.getRangeOfMovements=function(e){var t=this.getCursor();try{e.call(null,this);var n=this.getCursor();return o.fromPoints(t,n)}catch(r){return o.fromPoints(t,t)}finally{this.moveCursorToPosition(t)}},this.toJSON=function(){if(this.rangeCount)var e=this.ranges.map(function(e){var t=e.clone();return t.isBackwards=e.cursor==e.start,t});else{var e=this.getRange();e.isBackwards=this.isBackwards()}return e},this.fromJSON=function(e){if(e.start==undefined){if(this.rangeList){this.toSingleRange(e[0]);for(var t=e.length;t--;){var n=o.fromPoints(e[t].start,e[t].end);e.isBackwards&&(n.cursor=n.start),this.addRange(n,!0)}return}e=e[0]}this.rangeList&&this.toSingleRange(e),this.setSelectionRange(e,e.isBackwards)},this.isEqual=function(e){if((e.length||this.rangeCount)&&e.length!=this.rangeCount)return!1;if(!e.length||!this.ranges)return this.getRange().isEqual(e);for(var t=this.ranges.length;t--;)if(!this.ranges[t].isEqual(e[t]))return!1;return!0}}).call(u.prototype),t.Selection=u}),ace.define("ace/tokenizer",["require","exports","module"],function(e,t,n){"use strict";var r=1e3,i=function(e){this.states=e,this.regExps={},this.matchMappings={};for(var t in this.states){var n=this.states[t],r=[],i=0,s=this.matchMappings[t]={defaultToken:"text"},o="g",u=[];for(var a=0;a<n.length;a++){var f=n[a];f.defaultToken&&(s.defaultToken=f.defaultToken),f.caseInsensitive&&(o="gi");if(f.regex==null)continue;f.regex instanceof RegExp&&(f.regex=f.regex.toString().slice(1,-1));var l=f.regex,c=(new RegExp("(?:("+l+")|(.))")).exec("a").length-2;if(Array.isArray(f.token))if(f.token.length==1||c==1)f.token=f.token[0];else{if(c-1!=f.token.length)throw new Error("number of classes and regexp groups in '"+f.token+"'\n'"+f.regex+"' doesn't match\n"+(c-1)+"!="+f.token.length);f.tokenArray=f.token,f.token=null,f.onMatch=this.$arrayTokens}else typeof f.token=="function"&&!f.onMatch&&(c>1?f.onMatch=this.$applyToken:f.onMatch=f.token);c>1&&(/\\\d/.test(f.regex)?l=f.regex.replace(/\\([0-9]+)/g,function(e,t){return"\\"+(parseInt(t,10)+i+1)}):(c=1,l=this.removeCapturingGroups(f.regex)),!f.splitRegex&&typeof f.token!="string"&&u.push(f)),s[i]=a,i+=c,r.push(l),f.onMatch||(f.onMatch=null)}r.length||(s[0]=0,r.push("$")),u.forEach(function(e){e.splitRegex=this.createSplitterRegexp(e.regex,o)},this),this.regExps[t]=new RegExp("("+r.join(")|(")+")|($)",o)}};(function(){this.$setMaxTokenCount=function(e){r=e|0},this.$applyToken=function(e){var t=this.splitRegex.exec(e).slice(1),n=this.token.apply(this,t);if(typeof n=="string")return[{type:n,value:e}];var r=[];for(var i=0,s=n.length;i<s;i++)t[i]&&(r[r.length]={type:n[i],value:t[i]});return r},this.$arrayTokens=function(e){if(!e)return[];var t=this.splitRegex.exec(e);if(!t)return"text";var n=[],r=this.tokenArray;for(var i=0,s=r.length;i<s;i++)t[i+1]&&(n[n.length]={type:r[i],value:t[i+1]});return n},this.removeCapturingGroups=function(e){var t=e.replace(/\[(?:\\.|[^\]])*?\]|\\.|\(\?[:=!]|(\()/g,function(e,t){return t?"(?:":e});return t},this.createSplitterRegexp=function(e,t){if(e.indexOf("(?=")!=-1){var n=0,r=!1,i={};e.replace(/(\\.)|(\((?:\?[=!])?)|(\))|([\[\]])/g,function(e,t,s,o,u,a){return r?r=u!="]":u?r=!0:o?(n==i.stack&&(i.end=a+1,i.stack=-1),n--):s&&(n++,s.length!=1&&(i.stack=n,i.start=a)),e}),i.end!=null&&/^\)*$/.test(e.substr(i.end))&&(e=e.substring(0,i.start)+e.substr(i.end))}return new RegExp(e,(t||"").replace("g",""))},this.getLineTokens=function(e,t){if(t&&typeof t!="string"){var n=t.slice(0);t=n[0]}else var n=[];var i=t||"start",s=this.states[i];s||(i="start",s=this.states[i]);var o=this.matchMappings[i],u=this.regExps[i];u.lastIndex=0;var a,f=[],l=0,c={type:null,value:""};while(a=u.exec(e)){var h=o.defaultToken,p=null,d=a[0],v=u.lastIndex;if(v-d.length>l){var m=e.substring(l,v-d.length);c.type==h?c.value+=m:(c.type&&f.push(c),c={type:h,value:m})}for(var g=0;g<a.length-2;g++){if(a[g+1]===undefined)continue;p=s[o[g]],p.onMatch?h=p.onMatch(d,i,n):h=p.token,p.next&&(typeof p.next=="string"?i=p.next:i=p.next(i,n),s=this.states[i],s||(window.console&&console.error&&console.error(i,"doesn't exist"),i="start",s=this.states[i]),o=this.matchMappings[i],l=v,u=this.regExps[i],u.lastIndex=v);break}if(d)if(typeof h=="string")!!p&&p.merge===!1||c.type!==h?(c.type&&f.push(c),c={type:h,value:d}):c.value+=d;else if(h){c.type&&f.push(c),c={type:null,value:""};for(var g=0;g<h.length;g++)f.push(h[g])}if(l==e.length)break;l=v;if(f.length>r){while(l<e.length)c.type&&f.push(c),c={value:e.substring(l,l+=2e3),type:"overflow"};i="start",n=[];break}}return c.type&&f.push(c),n.length>1&&n[0]!==i&&n.unshift(i),{tokens:f,state:n.length?n:i}}}).call(i.prototype),t.Tokenizer=i}),ace.define("ace/mode/text_highlight_rules",["require","exports","module","ace/lib/lang"],function(e,t,n){"use strict";var r=e("../lib/lang"),i=function(){this.$rules={start:[{token:"empty_line",regex:"^$"},{defaultToken:"text"}]}};(function(){this.addRules=function(e,t){if(!t){for(var n in e)this.$rules[n]=e[n];return}for(var n in e){var r=e[n];for(var i=0;i<r.length;i++){var s=r[i];s.next&&(typeof s.next!="string"?s.nextState&&s.nextState.indexOf(t)!==0&&(s.nextState=t+s.nextState):s.next.indexOf(t)!==0&&(s.next=t+s.next))}this.$rules[t+n]=r}},this.getRules=function(){return this.$rules},this.embedRules=function(e,t,n,i,s){var o=typeof e=="function"?(new e).getRules():e;if(i)for(var u=0;u<i.length;u++)i[u]=t+i[u];else{i=[];for(var a in o)i.push(t+a)}this.addRules(o,t);if(n){var f=Array.prototype[s?"push":"unshift"];for(var u=0;u<i.length;u++)f.apply(this.$rules[i[u]],r.deepCopy(n))}this.$embeds||(this.$embeds=[]),this.$embeds.push(t)},this.getEmbeds=function(){return this.$embeds};var e=function(e,t){return(e!="start"||t.length)&&t.unshift(this.nextState,e),this.nextState},t=function(e,t){return t.shift(),t.shift()||"start"};this.normalizeRules=function(){function i(s){var o=r[s];o.processed=!0;for(var u=0;u<o.length;u++){var a=o[u];!a.regex&&a.start&&(a.regex=a.start,a.next||(a.next=[]),a.next.push({defaultToken:a.token},{token:a.token+".end",regex:a.end||a.start,next:"pop"}),a.token=a.token+".start",a.push=!0);var f=a.next||a.push;if(f&&Array.isArray(f)){var l=a.stateName;l||(l=a.token,typeof l!="string"&&(l=l[0]||""),r[l]&&(l+=n++)),r[l]=f,a.next=l,i(l)}else f=="pop"&&(a.next=t);a.push&&(a.nextState=a.next||a.push,a.next=e,delete a.push);if(a.rules)for(var c in a.rules)r[c]?r[c].push&&r[c].push.apply(r[c],a.rules[c]):r[c]=a.rules[c];if(a.include||typeof a=="string")var h=a.include||a,p=r[h];else Array.isArray(a)&&(p=a);if(p){var d=[u,1].concat(p);a.noEscape&&(d=d.filter(function(e){return!e.next})),o.splice.apply(o,d),u--,p=null}a.keywordMap&&(a.token=this.createKeywordMapper(a.keywordMap,a.defaultToken||"text",a.caseInsensitive),delete a.defaultToken)}}var n=0,r=this.$rules;Object.keys(r).forEach(i,this)},this.createKeywordMapper=function(e,t,n,r){var i=Object.create(null);return Object.keys(e).forEach(function(t){var s=e[t];n&&(s=s.toLowerCase());var o=s.split(r||"|");for(var u=o.length;u--;)i[o[u]]=t}),Object.getPrototypeOf(i)&&(i.__proto__=null),this.$keywordList=Object.keys(i),e=null,n?function(e){return i[e.toLowerCase()]||t}:function(e){return i[e]||t}},this.getKeywords=function(){return this.$keywords}}).call(i.prototype),t.TextHighlightRules=i}),ace.define("ace/mode/behaviour",["require","exports","module"],function(e,t,n){"use strict";var r=function(){this.$behaviours={}};(function(){this.add=function(e,t,n){switch(undefined){case this.$behaviours:this.$behaviours={};case this.$behaviours[e]:this.$behaviours[e]={}}this.$behaviours[e][t]=n},this.addBehaviours=function(e){for(var t in e)for(var n in e[t])this.add(t,n,e[t][n])},this.remove=function(e){this.$behaviours&&this.$behaviours[e]&&delete this.$behaviours[e]},this.inherit=function(e,t){if(typeof e=="function")var n=(new e).getBehaviours(t);else var n=e.getBehaviours(t);this.addBehaviours(n)},this.getBehaviours=function(e){if(!e)return this.$behaviours;var t={};for(var n=0;n<e.length;n++)this.$behaviours[e[n]]&&(t[e[n]]=this.$behaviours[e[n]]);return t}}).call(r.prototype),t.Behaviour=r}),ace.define("ace/unicode",["require","exports","module"],function(e,t,n){"use strict";function r(e){var n=/\w{4}/g;for(var r in e)t.packages[r]=e[r].replace(n,"\\u$&")}t.packages={},r({L:"0041-005A0061-007A00AA00B500BA00C0-00D600D8-00F600F8-02C102C6-02D102E0-02E402EC02EE0370-037403760377037A-037D03860388-038A038C038E-03A103A3-03F503F7-0481048A-05250531-055605590561-058705D0-05EA05F0-05F20621-064A066E066F0671-06D306D506E506E606EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA07F407F507FA0800-0815081A082408280904-0939093D09500958-0961097109720979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A59-0A5C0A5E0A72-0A740A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD0AD00AE00AE10B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D0B5C0B5D0B5F-0B610B710B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BD00C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D0C580C590C600C610C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBD0CDE0CE00CE10D05-0D0C0D0E-0D100D12-0D280D2A-0D390D3D0D600D610D7A-0D7F0D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60E01-0E300E320E330E40-0E460E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB00EB20EB30EBD0EC0-0EC40EC60EDC0EDD0F000F40-0F470F49-0F6C0F88-0F8B1000-102A103F1050-1055105A-105D106110651066106E-10701075-1081108E10A0-10C510D0-10FA10FC1100-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A1380-138F13A0-13F41401-166C166F-167F1681-169A16A0-16EA1700-170C170E-17111720-17311740-17511760-176C176E-17701780-17B317D717DC1820-18771880-18A818AA18B0-18F51900-191C1950-196D1970-19741980-19AB19C1-19C71A00-1A161A20-1A541AA71B05-1B331B45-1B4B1B83-1BA01BAE1BAF1C00-1C231C4D-1C4F1C5A-1C7D1CE9-1CEC1CEE-1CF11D00-1DBF1E00-1F151F18-1F1D1F20-1F451F48-1F4D1F50-1F571F591F5B1F5D1F5F-1F7D1F80-1FB41FB6-1FBC1FBE1FC2-1FC41FC6-1FCC1FD0-1FD31FD6-1FDB1FE0-1FEC1FF2-1FF41FF6-1FFC2071207F2090-209421022107210A-211321152119-211D212421262128212A-212D212F-2139213C-213F2145-2149214E218321842C00-2C2E2C30-2C5E2C60-2CE42CEB-2CEE2D00-2D252D30-2D652D6F2D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE2E2F300530063031-3035303B303C3041-3096309D-309F30A1-30FA30FC-30FF3105-312D3131-318E31A0-31B731F0-31FF3400-4DB54E00-9FCBA000-A48CA4D0-A4FDA500-A60CA610-A61FA62AA62BA640-A65FA662-A66EA67F-A697A6A0-A6E5A717-A71FA722-A788A78BA78CA7FB-A801A803-A805A807-A80AA80C-A822A840-A873A882-A8B3A8F2-A8F7A8FBA90A-A925A930-A946A960-A97CA984-A9B2A9CFAA00-AA28AA40-AA42AA44-AA4BAA60-AA76AA7AAA80-AAAFAAB1AAB5AAB6AAB9-AABDAAC0AAC2AADB-AADDABC0-ABE2AC00-D7A3D7B0-D7C6D7CB-D7FBF900-FA2DFA30-FA6DFA70-FAD9FB00-FB06FB13-FB17FB1DFB1F-FB28FB2A-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBB1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFBFE70-FE74FE76-FEFCFF21-FF3AFF41-FF5AFF66-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC",Ll:"0061-007A00AA00B500BA00DF-00F600F8-00FF01010103010501070109010B010D010F01110113011501170119011B011D011F01210123012501270129012B012D012F01310133013501370138013A013C013E014001420144014601480149014B014D014F01510153015501570159015B015D015F01610163016501670169016B016D016F0171017301750177017A017C017E-0180018301850188018C018D019201950199-019B019E01A101A301A501A801AA01AB01AD01B001B401B601B901BA01BD-01BF01C601C901CC01CE01D001D201D401D601D801DA01DC01DD01DF01E101E301E501E701E901EB01ED01EF01F001F301F501F901FB01FD01FF02010203020502070209020B020D020F02110213021502170219021B021D021F02210223022502270229022B022D022F02310233-0239023C023F0240024202470249024B024D024F-02930295-02AF037103730377037B-037D039003AC-03CE03D003D103D5-03D703D903DB03DD03DF03E103E303E503E703E903EB03ED03EF-03F303F503F803FB03FC0430-045F04610463046504670469046B046D046F04710473047504770479047B047D047F0481048B048D048F04910493049504970499049B049D049F04A104A304A504A704A904AB04AD04AF04B104B304B504B704B904BB04BD04BF04C204C404C604C804CA04CC04CE04CF04D104D304D504D704D904DB04DD04DF04E104E304E504E704E904EB04ED04EF04F104F304F504F704F904FB04FD04FF05010503050505070509050B050D050F05110513051505170519051B051D051F0521052305250561-05871D00-1D2B1D62-1D771D79-1D9A1E011E031E051E071E091E0B1E0D1E0F1E111E131E151E171E191E1B1E1D1E1F1E211E231E251E271E291E2B1E2D1E2F1E311E331E351E371E391E3B1E3D1E3F1E411E431E451E471E491E4B1E4D1E4F1E511E531E551E571E591E5B1E5D1E5F1E611E631E651E671E691E6B1E6D1E6F1E711E731E751E771E791E7B1E7D1E7F1E811E831E851E871E891E8B1E8D1E8F1E911E931E95-1E9D1E9F1EA11EA31EA51EA71EA91EAB1EAD1EAF1EB11EB31EB51EB71EB91EBB1EBD1EBF1EC11EC31EC51EC71EC91ECB1ECD1ECF1ED11ED31ED51ED71ED91EDB1EDD1EDF1EE11EE31EE51EE71EE91EEB1EED1EEF1EF11EF31EF51EF71EF91EFB1EFD1EFF-1F071F10-1F151F20-1F271F30-1F371F40-1F451F50-1F571F60-1F671F70-1F7D1F80-1F871F90-1F971FA0-1FA71FB0-1FB41FB61FB71FBE1FC2-1FC41FC61FC71FD0-1FD31FD61FD71FE0-1FE71FF2-1FF41FF61FF7210A210E210F2113212F21342139213C213D2146-2149214E21842C30-2C5E2C612C652C662C682C6A2C6C2C712C732C742C76-2C7C2C812C832C852C872C892C8B2C8D2C8F2C912C932C952C972C992C9B2C9D2C9F2CA12CA32CA52CA72CA92CAB2CAD2CAF2CB12CB32CB52CB72CB92CBB2CBD2CBF2CC12CC32CC52CC72CC92CCB2CCD2CCF2CD12CD32CD52CD72CD92CDB2CDD2CDF2CE12CE32CE42CEC2CEE2D00-2D25A641A643A645A647A649A64BA64DA64FA651A653A655A657A659A65BA65DA65FA663A665A667A669A66BA66DA681A683A685A687A689A68BA68DA68FA691A693A695A697A723A725A727A729A72BA72DA72F-A731A733A735A737A739A73BA73DA73FA741A743A745A747A749A74BA74DA74FA751A753A755A757A759A75BA75DA75FA761A763A765A767A769A76BA76DA76FA771-A778A77AA77CA77FA781A783A785A787A78CFB00-FB06FB13-FB17FF41-FF5A",Lu:"0041-005A00C0-00D600D8-00DE01000102010401060108010A010C010E01100112011401160118011A011C011E01200122012401260128012A012C012E01300132013401360139013B013D013F0141014301450147014A014C014E01500152015401560158015A015C015E01600162016401660168016A016C016E017001720174017601780179017B017D018101820184018601870189-018B018E-0191019301940196-0198019C019D019F01A001A201A401A601A701A901AC01AE01AF01B1-01B301B501B701B801BC01C401C701CA01CD01CF01D101D301D501D701D901DB01DE01E001E201E401E601E801EA01EC01EE01F101F401F6-01F801FA01FC01FE02000202020402060208020A020C020E02100212021402160218021A021C021E02200222022402260228022A022C022E02300232023A023B023D023E02410243-02460248024A024C024E03700372037603860388-038A038C038E038F0391-03A103A3-03AB03CF03D2-03D403D803DA03DC03DE03E003E203E403E603E803EA03EC03EE03F403F703F903FA03FD-042F04600462046404660468046A046C046E04700472047404760478047A047C047E0480048A048C048E04900492049404960498049A049C049E04A004A204A404A604A804AA04AC04AE04B004B204B404B604B804BA04BC04BE04C004C104C304C504C704C904CB04CD04D004D204D404D604D804DA04DC04DE04E004E204E404E604E804EA04EC04EE04F004F204F404F604F804FA04FC04FE05000502050405060508050A050C050E05100512051405160518051A051C051E0520052205240531-055610A0-10C51E001E021E041E061E081E0A1E0C1E0E1E101E121E141E161E181E1A1E1C1E1E1E201E221E241E261E281E2A1E2C1E2E1E301E321E341E361E381E3A1E3C1E3E1E401E421E441E461E481E4A1E4C1E4E1E501E521E541E561E581E5A1E5C1E5E1E601E621E641E661E681E6A1E6C1E6E1E701E721E741E761E781E7A1E7C1E7E1E801E821E841E861E881E8A1E8C1E8E1E901E921E941E9E1EA01EA21EA41EA61EA81EAA1EAC1EAE1EB01EB21EB41EB61EB81EBA1EBC1EBE1EC01EC21EC41EC61EC81ECA1ECC1ECE1ED01ED21ED41ED61ED81EDA1EDC1EDE1EE01EE21EE41EE61EE81EEA1EEC1EEE1EF01EF21EF41EF61EF81EFA1EFC1EFE1F08-1F0F1F18-1F1D1F28-1F2F1F38-1F3F1F48-1F4D1F591F5B1F5D1F5F1F68-1F6F1FB8-1FBB1FC8-1FCB1FD8-1FDB1FE8-1FEC1FF8-1FFB21022107210B-210D2110-211221152119-211D212421262128212A-212D2130-2133213E213F214521832C00-2C2E2C602C62-2C642C672C692C6B2C6D-2C702C722C752C7E-2C802C822C842C862C882C8A2C8C2C8E2C902C922C942C962C982C9A2C9C2C9E2CA02CA22CA42CA62CA82CAA2CAC2CAE2CB02CB22CB42CB62CB82CBA2CBC2CBE2CC02CC22CC42CC62CC82CCA2CCC2CCE2CD02CD22CD42CD62CD82CDA2CDC2CDE2CE02CE22CEB2CEDA640A642A644A646A648A64AA64CA64EA650A652A654A656A658A65AA65CA65EA662A664A666A668A66AA66CA680A682A684A686A688A68AA68CA68EA690A692A694A696A722A724A726A728A72AA72CA72EA732A734A736A738A73AA73CA73EA740A742A744A746A748A74AA74CA74EA750A752A754A756A758A75AA75CA75EA760A762A764A766A768A76AA76CA76EA779A77BA77DA77EA780A782A784A786A78BFF21-FF3A",Lt:"01C501C801CB01F21F88-1F8F1F98-1F9F1FA8-1FAF1FBC1FCC1FFC",Lm:"02B0-02C102C6-02D102E0-02E402EC02EE0374037A0559064006E506E607F407F507FA081A0824082809710E460EC610FC17D718431AA71C78-1C7D1D2C-1D611D781D9B-1DBF2071207F2090-20942C7D2D6F2E2F30053031-3035303B309D309E30FC-30FEA015A4F8-A4FDA60CA67FA717-A71FA770A788A9CFAA70AADDFF70FF9EFF9F",Lo:"01BB01C0-01C3029405D0-05EA05F0-05F20621-063F0641-064A066E066F0671-06D306D506EE06EF06FA-06FC06FF07100712-072F074D-07A507B107CA-07EA0800-08150904-0939093D09500958-096109720979-097F0985-098C098F09900993-09A809AA-09B009B209B6-09B909BD09CE09DC09DD09DF-09E109F009F10A05-0A0A0A0F0A100A13-0A280A2A-0A300A320A330A350A360A380A390A59-0A5C0A5E0A72-0A740A85-0A8D0A8F-0A910A93-0AA80AAA-0AB00AB20AB30AB5-0AB90ABD0AD00AE00AE10B05-0B0C0B0F0B100B13-0B280B2A-0B300B320B330B35-0B390B3D0B5C0B5D0B5F-0B610B710B830B85-0B8A0B8E-0B900B92-0B950B990B9A0B9C0B9E0B9F0BA30BA40BA8-0BAA0BAE-0BB90BD00C05-0C0C0C0E-0C100C12-0C280C2A-0C330C35-0C390C3D0C580C590C600C610C85-0C8C0C8E-0C900C92-0CA80CAA-0CB30CB5-0CB90CBD0CDE0CE00CE10D05-0D0C0D0E-0D100D12-0D280D2A-0D390D3D0D600D610D7A-0D7F0D85-0D960D9A-0DB10DB3-0DBB0DBD0DC0-0DC60E01-0E300E320E330E40-0E450E810E820E840E870E880E8A0E8D0E94-0E970E99-0E9F0EA1-0EA30EA50EA70EAA0EAB0EAD-0EB00EB20EB30EBD0EC0-0EC40EDC0EDD0F000F40-0F470F49-0F6C0F88-0F8B1000-102A103F1050-1055105A-105D106110651066106E-10701075-1081108E10D0-10FA1100-1248124A-124D1250-12561258125A-125D1260-1288128A-128D1290-12B012B2-12B512B8-12BE12C012C2-12C512C8-12D612D8-13101312-13151318-135A1380-138F13A0-13F41401-166C166F-167F1681-169A16A0-16EA1700-170C170E-17111720-17311740-17511760-176C176E-17701780-17B317DC1820-18421844-18771880-18A818AA18B0-18F51900-191C1950-196D1970-19741980-19AB19C1-19C71A00-1A161A20-1A541B05-1B331B45-1B4B1B83-1BA01BAE1BAF1C00-1C231C4D-1C4F1C5A-1C771CE9-1CEC1CEE-1CF12135-21382D30-2D652D80-2D962DA0-2DA62DA8-2DAE2DB0-2DB62DB8-2DBE2DC0-2DC62DC8-2DCE2DD0-2DD62DD8-2DDE3006303C3041-3096309F30A1-30FA30FF3105-312D3131-318E31A0-31B731F0-31FF3400-4DB54E00-9FCBA000-A014A016-A48CA4D0-A4F7A500-A60BA610-A61FA62AA62BA66EA6A0-A6E5A7FB-A801A803-A805A807-A80AA80C-A822A840-A873A882-A8B3A8F2-A8F7A8FBA90A-A925A930-A946A960-A97CA984-A9B2AA00-AA28AA40-AA42AA44-AA4BAA60-AA6FAA71-AA76AA7AAA80-AAAFAAB1AAB5AAB6AAB9-AABDAAC0AAC2AADBAADCABC0-ABE2AC00-D7A3D7B0-D7C6D7CB-D7FBF900-FA2DFA30-FA6DFA70-FAD9FB1DFB1F-FB28FB2A-FB36FB38-FB3CFB3EFB40FB41FB43FB44FB46-FBB1FBD3-FD3DFD50-FD8FFD92-FDC7FDF0-FDFBFE70-FE74FE76-FEFCFF66-FF6FFF71-FF9DFFA0-FFBEFFC2-FFC7FFCA-FFCFFFD2-FFD7FFDA-FFDC",M:"0300-036F0483-04890591-05BD05BF05C105C205C405C505C70610-061A064B-065E067006D6-06DC06DE-06E406E706E806EA-06ED07110730-074A07A6-07B007EB-07F30816-0819081B-08230825-08270829-082D0900-0903093C093E-094E0951-0955096209630981-098309BC09BE-09C409C709C809CB-09CD09D709E209E30A01-0A030A3C0A3E-0A420A470A480A4B-0A4D0A510A700A710A750A81-0A830ABC0ABE-0AC50AC7-0AC90ACB-0ACD0AE20AE30B01-0B030B3C0B3E-0B440B470B480B4B-0B4D0B560B570B620B630B820BBE-0BC20BC6-0BC80BCA-0BCD0BD70C01-0C030C3E-0C440C46-0C480C4A-0C4D0C550C560C620C630C820C830CBC0CBE-0CC40CC6-0CC80CCA-0CCD0CD50CD60CE20CE30D020D030D3E-0D440D46-0D480D4A-0D4D0D570D620D630D820D830DCA0DCF-0DD40DD60DD8-0DDF0DF20DF30E310E34-0E3A0E47-0E4E0EB10EB4-0EB90EBB0EBC0EC8-0ECD0F180F190F350F370F390F3E0F3F0F71-0F840F860F870F90-0F970F99-0FBC0FC6102B-103E1056-1059105E-10601062-10641067-106D1071-10741082-108D108F109A-109D135F1712-17141732-1734175217531772177317B6-17D317DD180B-180D18A91920-192B1930-193B19B0-19C019C819C91A17-1A1B1A55-1A5E1A60-1A7C1A7F1B00-1B041B34-1B441B6B-1B731B80-1B821BA1-1BAA1C24-1C371CD0-1CD21CD4-1CE81CED1CF21DC0-1DE61DFD-1DFF20D0-20F02CEF-2CF12DE0-2DFF302A-302F3099309AA66F-A672A67CA67DA6F0A6F1A802A806A80BA823-A827A880A881A8B4-A8C4A8E0-A8F1A926-A92DA947-A953A980-A983A9B3-A9C0AA29-AA36AA43AA4CAA4DAA7BAAB0AAB2-AAB4AAB7AAB8AABEAABFAAC1ABE3-ABEAABECABEDFB1EFE00-FE0FFE20-FE26",Mn:"0300-036F0483-04870591-05BD05BF05C105C205C405C505C70610-061A064B-065E067006D6-06DC06DF-06E406E706E806EA-06ED07110730-074A07A6-07B007EB-07F30816-0819081B-08230825-08270829-082D0900-0902093C0941-0948094D0951-095509620963098109BC09C1-09C409CD09E209E30A010A020A3C0A410A420A470A480A4B-0A4D0A510A700A710A750A810A820ABC0AC1-0AC50AC70AC80ACD0AE20AE30B010B3C0B3F0B41-0B440B4D0B560B620B630B820BC00BCD0C3E-0C400C46-0C480C4A-0C4D0C550C560C620C630CBC0CBF0CC60CCC0CCD0CE20CE30D41-0D440D4D0D620D630DCA0DD2-0DD40DD60E310E34-0E3A0E47-0E4E0EB10EB4-0EB90EBB0EBC0EC8-0ECD0F180F190F350F370F390F71-0F7E0F80-0F840F860F870F90-0F970F99-0FBC0FC6102D-10301032-10371039103A103D103E10581059105E-10601071-1074108210851086108D109D135F1712-17141732-1734175217531772177317B7-17BD17C617C9-17D317DD180B-180D18A91920-19221927192819321939-193B1A171A181A561A58-1A5E1A601A621A65-1A6C1A73-1A7C1A7F1B00-1B031B341B36-1B3A1B3C1B421B6B-1B731B801B811BA2-1BA51BA81BA91C2C-1C331C361C371CD0-1CD21CD4-1CE01CE2-1CE81CED1DC0-1DE61DFD-1DFF20D0-20DC20E120E5-20F02CEF-2CF12DE0-2DFF302A-302F3099309AA66FA67CA67DA6F0A6F1A802A806A80BA825A826A8C4A8E0-A8F1A926-A92DA947-A951A980-A982A9B3A9B6-A9B9A9BCAA29-AA2EAA31AA32AA35AA36AA43AA4CAAB0AAB2-AAB4AAB7AAB8AABEAABFAAC1ABE5ABE8ABEDFB1EFE00-FE0FFE20-FE26",Mc:"0903093E-09400949-094C094E0982098309BE-09C009C709C809CB09CC09D70A030A3E-0A400A830ABE-0AC00AC90ACB0ACC0B020B030B3E0B400B470B480B4B0B4C0B570BBE0BBF0BC10BC20BC6-0BC80BCA-0BCC0BD70C01-0C030C41-0C440C820C830CBE0CC0-0CC40CC70CC80CCA0CCB0CD50CD60D020D030D3E-0D400D46-0D480D4A-0D4C0D570D820D830DCF-0DD10DD8-0DDF0DF20DF30F3E0F3F0F7F102B102C10311038103B103C105610571062-10641067-106D108310841087-108C108F109A-109C17B617BE-17C517C717C81923-19261929-192B193019311933-193819B0-19C019C819C91A19-1A1B1A551A571A611A631A641A6D-1A721B041B351B3B1B3D-1B411B431B441B821BA11BA61BA71BAA1C24-1C2B1C341C351CE11CF2A823A824A827A880A881A8B4-A8C3A952A953A983A9B4A9B5A9BAA9BBA9BD-A9C0AA2FAA30AA33AA34AA4DAA7BABE3ABE4ABE6ABE7ABE9ABEAABEC",Me:"0488048906DE20DD-20E020E2-20E4A670-A672",N:"0030-003900B200B300B900BC-00BE0660-066906F0-06F907C0-07C90966-096F09E6-09EF09F4-09F90A66-0A6F0AE6-0AEF0B66-0B6F0BE6-0BF20C66-0C6F0C78-0C7E0CE6-0CEF0D66-0D750E50-0E590ED0-0ED90F20-0F331040-10491090-10991369-137C16EE-16F017E0-17E917F0-17F91810-18191946-194F19D0-19DA1A80-1A891A90-1A991B50-1B591BB0-1BB91C40-1C491C50-1C5920702074-20792080-20892150-21822185-21892460-249B24EA-24FF2776-27932CFD30073021-30293038-303A3192-31953220-32293251-325F3280-328932B1-32BFA620-A629A6E6-A6EFA830-A835A8D0-A8D9A900-A909A9D0-A9D9AA50-AA59ABF0-ABF9FF10-FF19",Nd:"0030-00390660-066906F0-06F907C0-07C90966-096F09E6-09EF0A66-0A6F0AE6-0AEF0B66-0B6F0BE6-0BEF0C66-0C6F0CE6-0CEF0D66-0D6F0E50-0E590ED0-0ED90F20-0F291040-10491090-109917E0-17E91810-18191946-194F19D0-19DA1A80-1A891A90-1A991B50-1B591BB0-1BB91C40-1C491C50-1C59A620-A629A8D0-A8D9A900-A909A9D0-A9D9AA50-AA59ABF0-ABF9FF10-FF19",Nl:"16EE-16F02160-21822185-218830073021-30293038-303AA6E6-A6EF",No:"00B200B300B900BC-00BE09F4-09F90BF0-0BF20C78-0C7E0D70-0D750F2A-0F331369-137C17F0-17F920702074-20792080-20892150-215F21892460-249B24EA-24FF2776-27932CFD3192-31953220-32293251-325F3280-328932B1-32BFA830-A835",P:"0021-00230025-002A002C-002F003A003B003F0040005B-005D005F007B007D00A100AB00B700BB00BF037E0387055A-055F0589058A05BE05C005C305C605F305F40609060A060C060D061B061E061F066A-066D06D40700-070D07F7-07F90830-083E0964096509700DF40E4F0E5A0E5B0F04-0F120F3A-0F3D0F850FD0-0FD4104A-104F10FB1361-13681400166D166E169B169C16EB-16ED1735173617D4-17D617D8-17DA1800-180A1944194519DE19DF1A1E1A1F1AA0-1AA61AA8-1AAD1B5A-1B601C3B-1C3F1C7E1C7F1CD32010-20272030-20432045-20512053-205E207D207E208D208E2329232A2768-277527C527C627E6-27EF2983-299829D8-29DB29FC29FD2CF9-2CFC2CFE2CFF2E00-2E2E2E302E313001-30033008-30113014-301F3030303D30A030FBA4FEA4FFA60D-A60FA673A67EA6F2-A6F7A874-A877A8CEA8CFA8F8-A8FAA92EA92FA95FA9C1-A9CDA9DEA9DFAA5C-AA5FAADEAADFABEBFD3EFD3FFE10-FE19FE30-FE52FE54-FE61FE63FE68FE6AFE6BFF01-FF03FF05-FF0AFF0C-FF0FFF1AFF1BFF1FFF20FF3B-FF3DFF3FFF5BFF5DFF5F-FF65",Pd:"002D058A05BE140018062010-20152E172E1A301C303030A0FE31FE32FE58FE63FF0D",Ps:"0028005B007B0F3A0F3C169B201A201E2045207D208D23292768276A276C276E27702772277427C527E627E827EA27EC27EE2983298529872989298B298D298F299129932995299729D829DA29FC2E222E242E262E283008300A300C300E3010301430163018301A301DFD3EFE17FE35FE37FE39FE3BFE3DFE3FFE41FE43FE47FE59FE5BFE5DFF08FF3BFF5BFF5FFF62",Pe:"0029005D007D0F3B0F3D169C2046207E208E232A2769276B276D276F27712773277527C627E727E927EB27ED27EF298429862988298A298C298E2990299229942996299829D929DB29FD2E232E252E272E293009300B300D300F3011301530173019301B301E301FFD3FFE18FE36FE38FE3AFE3CFE3EFE40FE42FE44FE48FE5AFE5CFE5EFF09FF3DFF5DFF60FF63",Pi:"00AB2018201B201C201F20392E022E042E092E0C2E1C2E20",Pf:"00BB2019201D203A2E032E052E0A2E0D2E1D2E21",Pc:"005F203F20402054FE33FE34FE4D-FE4FFF3F",Po:"0021-00230025-0027002A002C002E002F003A003B003F0040005C00A100B700BF037E0387055A-055F058905C005C305C605F305F40609060A060C060D061B061E061F066A-066D06D40700-070D07F7-07F90830-083E0964096509700DF40E4F0E5A0E5B0F04-0F120F850FD0-0FD4104A-104F10FB1361-1368166D166E16EB-16ED1735173617D4-17D617D8-17DA1800-18051807-180A1944194519DE19DF1A1E1A1F1AA0-1AA61AA8-1AAD1B5A-1B601C3B-1C3F1C7E1C7F1CD3201620172020-20272030-2038203B-203E2041-20432047-205120532055-205E2CF9-2CFC2CFE2CFF2E002E012E06-2E082E0B2E0E-2E162E182E192E1B2E1E2E1F2E2A-2E2E2E302E313001-3003303D30FBA4FEA4FFA60D-A60FA673A67EA6F2-A6F7A874-A877A8CEA8CFA8F8-A8FAA92EA92FA95FA9C1-A9CDA9DEA9DFAA5C-AA5FAADEAADFABEBFE10-FE16FE19FE30FE45FE46FE49-FE4CFE50-FE52FE54-FE57FE5F-FE61FE68FE6AFE6BFF01-FF03FF05-FF07FF0AFF0CFF0EFF0FFF1AFF1BFF1FFF20FF3CFF61FF64FF65",S:"0024002B003C-003E005E0060007C007E00A2-00A900AC00AE-00B100B400B600B800D700F702C2-02C502D2-02DF02E5-02EB02ED02EF-02FF03750384038503F604820606-0608060B060E060F06E906FD06FE07F609F209F309FA09FB0AF10B700BF3-0BFA0C7F0CF10CF20D790E3F0F01-0F030F13-0F170F1A-0F1F0F340F360F380FBE-0FC50FC7-0FCC0FCE0FCF0FD5-0FD8109E109F13601390-139917DB194019E0-19FF1B61-1B6A1B74-1B7C1FBD1FBF-1FC11FCD-1FCF1FDD-1FDF1FED-1FEF1FFD1FFE20442052207A-207C208A-208C20A0-20B8210021012103-21062108210921142116-2118211E-2123212521272129212E213A213B2140-2144214A-214D214F2190-2328232B-23E82400-24262440-244A249C-24E92500-26CD26CF-26E126E326E8-26FF2701-27042706-2709270C-27272729-274B274D274F-27522756-275E2761-276727942798-27AF27B1-27BE27C0-27C427C7-27CA27CC27D0-27E527F0-29822999-29D729DC-29FB29FE-2B4C2B50-2B592CE5-2CEA2E80-2E992E9B-2EF32F00-2FD52FF0-2FFB300430123013302030363037303E303F309B309C319031913196-319F31C0-31E33200-321E322A-32503260-327F328A-32B032C0-32FE3300-33FF4DC0-4DFFA490-A4C6A700-A716A720A721A789A78AA828-A82BA836-A839AA77-AA79FB29FDFCFDFDFE62FE64-FE66FE69FF04FF0BFF1C-FF1EFF3EFF40FF5CFF5EFFE0-FFE6FFE8-FFEEFFFCFFFD",Sm:"002B003C-003E007C007E00AC00B100D700F703F60606-060820442052207A-207C208A-208C2140-2144214B2190-2194219A219B21A021A321A621AE21CE21CF21D221D421F4-22FF2308-230B23202321237C239B-23B323DC-23E125B725C125F8-25FF266F27C0-27C427C7-27CA27CC27D0-27E527F0-27FF2900-29822999-29D729DC-29FB29FE-2AFF2B30-2B442B47-2B4CFB29FE62FE64-FE66FF0BFF1C-FF1EFF5CFF5EFFE2FFE9-FFEC",Sc:"002400A2-00A5060B09F209F309FB0AF10BF90E3F17DB20A0-20B8A838FDFCFE69FF04FFE0FFE1FFE5FFE6",Sk:"005E006000A800AF00B400B802C2-02C502D2-02DF02E5-02EB02ED02EF-02FF0375038403851FBD1FBF-1FC11FCD-1FCF1FDD-1FDF1FED-1FEF1FFD1FFE309B309CA700-A716A720A721A789A78AFF3EFF40FFE3",So:"00A600A700A900AE00B000B60482060E060F06E906FD06FE07F609FA0B700BF3-0BF80BFA0C7F0CF10CF20D790F01-0F030F13-0F170F1A-0F1F0F340F360F380FBE-0FC50FC7-0FCC0FCE0FCF0FD5-0FD8109E109F13601390-1399194019E0-19FF1B61-1B6A1B74-1B7C210021012103-21062108210921142116-2118211E-2123212521272129212E213A213B214A214C214D214F2195-2199219C-219F21A121A221A421A521A7-21AD21AF-21CD21D021D121D321D5-21F32300-2307230C-231F2322-2328232B-237B237D-239A23B4-23DB23E2-23E82400-24262440-244A249C-24E92500-25B625B8-25C025C2-25F72600-266E2670-26CD26CF-26E126E326E8-26FF2701-27042706-2709270C-27272729-274B274D274F-27522756-275E2761-276727942798-27AF27B1-27BE2800-28FF2B00-2B2F2B452B462B50-2B592CE5-2CEA2E80-2E992E9B-2EF32F00-2FD52FF0-2FFB300430123013302030363037303E303F319031913196-319F31C0-31E33200-321E322A-32503260-327F328A-32B032C0-32FE3300-33FF4DC0-4DFFA490-A4C6A828-A82BA836A837A839AA77-AA79FDFDFFE4FFE8FFEDFFEEFFFCFFFD",Z:"002000A01680180E2000-200A20282029202F205F3000",Zs:"002000A01680180E2000-200A202F205F3000",Zl:"2028",Zp:"2029",C:"0000-001F007F-009F00AD03780379037F-0383038B038D03A20526-05300557055805600588058B-059005C8-05CF05EB-05EF05F5-0605061C061D0620065F06DD070E070F074B074C07B2-07BF07FB-07FF082E082F083F-08FF093A093B094F095609570973-097809800984098D098E0991099209A909B109B3-09B509BA09BB09C509C609C909CA09CF-09D609D8-09DB09DE09E409E509FC-0A000A040A0B-0A0E0A110A120A290A310A340A370A3A0A3B0A3D0A43-0A460A490A4A0A4E-0A500A52-0A580A5D0A5F-0A650A76-0A800A840A8E0A920AA90AB10AB40ABA0ABB0AC60ACA0ACE0ACF0AD1-0ADF0AE40AE50AF00AF2-0B000B040B0D0B0E0B110B120B290B310B340B3A0B3B0B450B460B490B4A0B4E-0B550B58-0B5B0B5E0B640B650B72-0B810B840B8B-0B8D0B910B96-0B980B9B0B9D0BA0-0BA20BA5-0BA70BAB-0BAD0BBA-0BBD0BC3-0BC50BC90BCE0BCF0BD1-0BD60BD8-0BE50BFB-0C000C040C0D0C110C290C340C3A-0C3C0C450C490C4E-0C540C570C5A-0C5F0C640C650C70-0C770C800C810C840C8D0C910CA90CB40CBA0CBB0CC50CC90CCE-0CD40CD7-0CDD0CDF0CE40CE50CF00CF3-0D010D040D0D0D110D290D3A-0D3C0D450D490D4E-0D560D58-0D5F0D640D650D76-0D780D800D810D840D97-0D990DB20DBC0DBE0DBF0DC7-0DC90DCB-0DCE0DD50DD70DE0-0DF10DF5-0E000E3B-0E3E0E5C-0E800E830E850E860E890E8B0E8C0E8E-0E930E980EA00EA40EA60EA80EA90EAC0EBA0EBE0EBF0EC50EC70ECE0ECF0EDA0EDB0EDE-0EFF0F480F6D-0F700F8C-0F8F0F980FBD0FCD0FD9-0FFF10C6-10CF10FD-10FF1249124E124F12571259125E125F1289128E128F12B112B612B712BF12C112C612C712D7131113161317135B-135E137D-137F139A-139F13F5-13FF169D-169F16F1-16FF170D1715-171F1737-173F1754-175F176D17711774-177F17B417B517DE17DF17EA-17EF17FA-17FF180F181A-181F1878-187F18AB-18AF18F6-18FF191D-191F192C-192F193C-193F1941-1943196E196F1975-197F19AC-19AF19CA-19CF19DB-19DD1A1C1A1D1A5F1A7D1A7E1A8A-1A8F1A9A-1A9F1AAE-1AFF1B4C-1B4F1B7D-1B7F1BAB-1BAD1BBA-1BFF1C38-1C3A1C4A-1C4C1C80-1CCF1CF3-1CFF1DE7-1DFC1F161F171F1E1F1F1F461F471F4E1F4F1F581F5A1F5C1F5E1F7E1F7F1FB51FC51FD41FD51FDC1FF01FF11FF51FFF200B-200F202A-202E2060-206F20722073208F2095-209F20B9-20CF20F1-20FF218A-218F23E9-23FF2427-243F244B-245F26CE26E226E4-26E727002705270A270B2728274C274E2753-2755275F27602795-279727B027BF27CB27CD-27CF2B4D-2B4F2B5A-2BFF2C2F2C5F2CF2-2CF82D26-2D2F2D66-2D6E2D70-2D7F2D97-2D9F2DA72DAF2DB72DBF2DC72DCF2DD72DDF2E32-2E7F2E9A2EF4-2EFF2FD6-2FEF2FFC-2FFF3040309730983100-3104312E-3130318F31B8-31BF31E4-31EF321F32FF4DB6-4DBF9FCC-9FFFA48D-A48FA4C7-A4CFA62C-A63FA660A661A674-A67BA698-A69FA6F8-A6FFA78D-A7FAA82C-A82FA83A-A83FA878-A87FA8C5-A8CDA8DA-A8DFA8FC-A8FFA954-A95EA97D-A97FA9CEA9DA-A9DDA9E0-A9FFAA37-AA3FAA4EAA4FAA5AAA5BAA7C-AA7FAAC3-AADAAAE0-ABBFABEEABEFABFA-ABFFD7A4-D7AFD7C7-D7CAD7FC-F8FFFA2EFA2FFA6EFA6FFADA-FAFFFB07-FB12FB18-FB1CFB37FB3DFB3FFB42FB45FBB2-FBD2FD40-FD4FFD90FD91FDC8-FDEFFDFEFDFFFE1A-FE1FFE27-FE2FFE53FE67FE6C-FE6FFE75FEFD-FF00FFBF-FFC1FFC8FFC9FFD0FFD1FFD8FFD9FFDD-FFDFFFE7FFEF-FFFBFFFEFFFF",Cc:"0000-001F007F-009F",Cf:"00AD0600-060306DD070F17B417B5200B-200F202A-202E2060-2064206A-206FFEFFFFF9-FFFB",Co:"E000-F8FF",Cs:"D800-DFFF",Cn:"03780379037F-0383038B038D03A20526-05300557055805600588058B-059005C8-05CF05EB-05EF05F5-05FF06040605061C061D0620065F070E074B074C07B2-07BF07FB-07FF082E082F083F-08FF093A093B094F095609570973-097809800984098D098E0991099209A909B109B3-09B509BA09BB09C509C609C909CA09CF-09D609D8-09DB09DE09E409E509FC-0A000A040A0B-0A0E0A110A120A290A310A340A370A3A0A3B0A3D0A43-0A460A490A4A0A4E-0A500A52-0A580A5D0A5F-0A650A76-0A800A840A8E0A920AA90AB10AB40ABA0ABB0AC60ACA0ACE0ACF0AD1-0ADF0AE40AE50AF00AF2-0B000B040B0D0B0E0B110B120B290B310B340B3A0B3B0B450B460B490B4A0B4E-0B550B58-0B5B0B5E0B640B650B72-0B810B840B8B-0B8D0B910B96-0B980B9B0B9D0BA0-0BA20BA5-0BA70BAB-0BAD0BBA-0BBD0BC3-0BC50BC90BCE0BCF0BD1-0BD60BD8-0BE50BFB-0C000C040C0D0C110C290C340C3A-0C3C0C450C490C4E-0C540C570C5A-0C5F0C640C650C70-0C770C800C810C840C8D0C910CA90CB40CBA0CBB0CC50CC90CCE-0CD40CD7-0CDD0CDF0CE40CE50CF00CF3-0D010D040D0D0D110D290D3A-0D3C0D450D490D4E-0D560D58-0D5F0D640D650D76-0D780D800D810D840D97-0D990DB20DBC0DBE0DBF0DC7-0DC90DCB-0DCE0DD50DD70DE0-0DF10DF5-0E000E3B-0E3E0E5C-0E800E830E850E860E890E8B0E8C0E8E-0E930E980EA00EA40EA60EA80EA90EAC0EBA0EBE0EBF0EC50EC70ECE0ECF0EDA0EDB0EDE-0EFF0F480F6D-0F700F8C-0F8F0F980FBD0FCD0FD9-0FFF10C6-10CF10FD-10FF1249124E124F12571259125E125F1289128E128F12B112B612B712BF12C112C612C712D7131113161317135B-135E137D-137F139A-139F13F5-13FF169D-169F16F1-16FF170D1715-171F1737-173F1754-175F176D17711774-177F17DE17DF17EA-17EF17FA-17FF180F181A-181F1878-187F18AB-18AF18F6-18FF191D-191F192C-192F193C-193F1941-1943196E196F1975-197F19AC-19AF19CA-19CF19DB-19DD1A1C1A1D1A5F1A7D1A7E1A8A-1A8F1A9A-1A9F1AAE-1AFF1B4C-1B4F1B7D-1B7F1BAB-1BAD1BBA-1BFF1C38-1C3A1C4A-1C4C1C80-1CCF1CF3-1CFF1DE7-1DFC1F161F171F1E1F1F1F461F471F4E1F4F1F581F5A1F5C1F5E1F7E1F7F1FB51FC51FD41FD51FDC1FF01FF11FF51FFF2065-206920722073208F2095-209F20B9-20CF20F1-20FF218A-218F23E9-23FF2427-243F244B-245F26CE26E226E4-26E727002705270A270B2728274C274E2753-2755275F27602795-279727B027BF27CB27CD-27CF2B4D-2B4F2B5A-2BFF2C2F2C5F2CF2-2CF82D26-2D2F2D66-2D6E2D70-2D7F2D97-2D9F2DA72DAF2DB72DBF2DC72DCF2DD72DDF2E32-2E7F2E9A2EF4-2EFF2FD6-2FEF2FFC-2FFF3040309730983100-3104312E-3130318F31B8-31BF31E4-31EF321F32FF4DB6-4DBF9FCC-9FFFA48D-A48FA4C7-A4CFA62C-A63FA660A661A674-A67BA698-A69FA6F8-A6FFA78D-A7FAA82C-A82FA83A-A83FA878-A87FA8C5-A8CDA8DA-A8DFA8FC-A8FFA954-A95EA97D-A97FA9CEA9DA-A9DDA9E0-A9FFAA37-AA3FAA4EAA4FAA5AAA5BAA7C-AA7FAAC3-AADAAAE0-ABBFABEEABEFABFA-ABFFD7A4-D7AFD7C7-D7CAD7FC-D7FFFA2EFA2FFA6EFA6FFADA-FAFFFB07-FB12FB18-FB1CFB37FB3DFB3FFB42FB45FBB2-FBD2FD40-FD4FFD90FD91FDC8-FDEFFDFEFDFFFE1A-FE1FFE27-FE2FFE53FE67FE6C-FE6FFE75FEFDFEFEFF00FFBF-FFC1FFC8FFC9FFD0FFD1FFD8FFD9FFDD-FFDFFFE7FFEF-FFF8FFFEFFFF"})}),ace.define("ace/token_iterator",["require","exports","module"],function(e,t,n){"use strict";var r=function(e,t,n){this.$session=e,this.$row=t,this.$rowTokens=e.getTokens(t);var r=e.getTokenAt(t,n);this.$tokenIndex=r?r.index:-1};(function(){this.stepBackward=function(){this.$tokenIndex-=1;while(this.$tokenIndex<0){this.$row-=1;if(this.$row<0)return this.$row=0,null;this.$rowTokens=this.$session.getTokens(this.$row),this.$tokenIndex=this.$rowTokens.length-1}return this.$rowTokens[this.$tokenIndex]},this.stepForward=function(){this.$tokenIndex+=1;var e;while(this.$tokenIndex>=this.$rowTokens.length){this.$row+=1,e||(e=this.$session.getLength());if(this.$row>=e)return this.$row=e-1,null;this.$rowTokens=this.$session.getTokens(this.$row),this.$tokenIndex=0}return this.$rowTokens[this.$tokenIndex]},this.getCurrentToken=function(){return this.$rowTokens[this.$tokenIndex]},this.getCurrentTokenRow=function(){return this.$row},this.getCurrentTokenColumn=function(){var e=this.$rowTokens,t=this.$tokenIndex,n=e[t].start;if(n!==undefined)return n;n=0;while(t>0)t-=1,n+=e[t].value.length;return n}}).call(r.prototype),t.TokenIterator=r}),ace.define("ace/mode/text",["require","exports","module","ace/tokenizer","ace/mode/text_highlight_rules","ace/mode/behaviour","ace/unicode","ace/lib/lang","ace/token_iterator","ace/range"],function(e,t,n){"use strict";var r=e("../tokenizer").Tokenizer,i=e("./text_highlight_rules").TextHighlightRules,s=e("./behaviour").Behaviour,o=e("../unicode"),u=e("../lib/lang"),a=e("../token_iterator").TokenIterator,f=e("../range").Range,l=function(){this.HighlightRules=i,this.$behaviour=new s};(function(){this.tokenRe=new RegExp("^["+o.packages.L+o.packages.Mn+o.packages.Mc+o.packages.Nd+o.packages.Pc+"\\$_]+","g"),this.nonTokenRe=new RegExp("^(?:[^"+o.packages.L+o.packages.Mn+o.packages.Mc+o.packages.Nd+o.packages.Pc+"\\$_]|\\s])+","g"),this.getTokenizer=function(){return this.$tokenizer||(this.$highlightRules=this.$highlightRules||new this.HighlightRules,this.$tokenizer=new r(this.$highlightRules.getRules())),this.$tokenizer},this.lineCommentStart="",this.blockComment="",this.toggleCommentLines=function(e,t,n,r){function w(e){for(var t=n;t<=r;t++)e(i.getLine(t),t)}var i=t.doc,s=!0,o=!0,a=Infinity,f=t.getTabSize(),l=!1;if(!this.lineCommentStart){if(!this.blockComment)return!1;var c=this.blockComment.start,h=this.blockComment.end,p=new RegExp("^(\\s*)(?:"+u.escapeRegExp(c)+")"),d=new RegExp("(?:"+u.escapeRegExp(h)+")\\s*$"),v=function(e,t){if(g(e,t))return;if(!s||/\S/.test(e))i.insertInLine({row:t,column:e.length},h),i.insertInLine({row:t,column:a},c)},m=function(e,t){var n;(n=e.match(d))&&i.removeInLine(t,e.length-n[0].length,e.length),(n=e.match(p))&&i.removeInLine(t,n[1].length,n[0].length)},g=function(e,n){if(p.test(e))return!0;var r=t.getTokens(n);for(var i=0;i<r.length;i++)if(r[i].type==="comment")return!0}}else{if(Array.isArray(this.lineCommentStart))var p=this.lineCommentStart.map(u.escapeRegExp).join("|"),c=this.lineCommentStart[0];else var p=u.escapeRegExp(this.lineCommentStart),c=this.lineCommentStart;p=new RegExp("^(\\s*)(?:"+p+") ?"),l=t.getUseSoftTabs();var m=function(e,t){var n=e.match(p);if(!n)return;var r=n[1].length,s=n[0].length;!b(e,r,s)&&n[0][s-1]==" "&&s--,i.removeInLine(t,r,s)},y=c+" ",v=function(e,t){if(!s||/\S/.test(e))b(e,a,a)?i.insertInLine({row:t,column:a},y):i.insertInLine({row:t,column:a},c)},g=function(e,t){return p.test(e)},b=function(e,t,n){var r=0;while(t--&&e.charAt(t)==" ")r++;if(r%f!=0)return!1;var r=0;while(e.charAt(n++)==" ")r++;return f>2?r%f!=f-1:r%f==0}}var E=Infinity;w(function(e,t){var n=e.search(/\S/);n!==-1?(n<a&&(a=n),o&&!g(e,t)&&(o=!1)):E>e.length&&(E=e.length)}),a==Infinity&&(a=E,s=!1,o=!1),l&&a%f!=0&&(a=Math.floor(a/f)*f),w(o?m:v)},this.toggleBlockComment=function(e,t,n,r){var i=this.blockComment;if(!i)return;!i.start&&i[0]&&(i=i[0]);var s=new a(t,r.row,r.column),o=s.getCurrentToken(),u=t.selection,l=t.selection.toOrientedRange(),c,h;if(o&&/comment/.test(o.type)){var p,d;while(o&&/comment/.test(o.type)){var v=o.value.indexOf(i.start);if(v!=-1){var m=s.getCurrentTokenRow(),g=s.getCurrentTokenColumn()+v;p=new f(m,g,m,g+i.start.length);break}o=s.stepBackward()}var s=new a(t,r.row,r.column),o=s.getCurrentToken();while(o&&/comment/.test(o.type)){var v=o.value.indexOf(i.end);if(v!=-1){var m=s.getCurrentTokenRow(),g=s.getCurrentTokenColumn()+v;d=new f(m,g,m,g+i.end.length);break}o=s.stepForward()}d&&t.remove(d),p&&(t.remove(p),c=p.start.row,h=-i.start.length)}else h=i.start.length,c=n.start.row,t.insert(n.end,i.end),t.insert(n.start,i.start);l.start.row==c&&(l.start.column+=h),l.end.row==c&&(l.end.column+=h),t.selection.fromOrientedRange(l)},this.getNextLineIndent=function(e,t,n){return this.$getIndent(t)},this.checkOutdent=function(e,t,n){return!1},this.autoOutdent=function(e,t,n){},this.$getIndent=function(e){return e.match(/^\s*/)[0]},this.createWorker=function(e){return null},this.createModeDelegates=function(e){this.$embeds=[],this.$modes={};for(var t in e)e[t]&&(this.$embeds.push(t),this.$modes[t]=new e[t]);var n=["toggleBlockComment","toggleCommentLines","getNextLineIndent","checkOutdent","autoOutdent","transformAction","getCompletions"];for(var t=0;t<n.length;t++)(function(e){var r=n[t],i=e[r];e[n[t]]=function(){return this.$delegator(r,arguments,i)}})(this)},this.$delegator=function(e,t,n){var r=t[0];typeof r!="string"&&(r=r[0]);for(var i=0;i<this.$embeds.length;i++){if(!this.$modes[this.$embeds[i]])continue;var s=r.split(this.$embeds[i]);if(!s[0]&&s[1]){t[0]=s[1];var o=this.$modes[this.$embeds[i]];return o[e].apply(o,t)}}var u=n.apply(this,t);return n?u:undefined},this.transformAction=function(e,t,n,r,i){if(this.$behaviour){var s=this.$behaviour.getBehaviours();for(var o in s)if(s[o][t]){var u=s[o][t].apply(this,arguments);if(u)return u}}},this.getKeywords=function(e){if(!this.completionKeywords){var t=this.$tokenizer.rules,n=[];for(var r in t){var i=t[r];for(var s=0,o=i.length;s<o;s++)if(typeof i[s].token=="string")/keyword|support|storage/.test(i[s].token)&&n.push(i[s].regex);else if(typeof i[s].token=="object")for(var u=0,a=i[s].token.length;u<a;u++)if(/keyword|support|storage/.test(i[s].token[u])){var r=i[s].regex.match(/\(.+?\)/g)[u];n.push(r.substr(1,r.length-2))}}this.completionKeywords=n}return e?n.concat(this.$keywordList||[]):this.$keywordList},this.$createKeywordList=function(){return this.$highlightRules||this.getTokenizer(),this.$keywordList=this.$highlightRules.$keywordList||[]},this.getCompletions=function(e,t,n,r){var i=this.$keywordList||this.$createKeywordList();return i.map(function(e){return{name:e,value:e,score:0,meta:"keyword"}})},this.$id="ace/mode/text"}).call(l.prototype),t.Mode=l}),ace.define("ace/anchor",["require","exports","module","ace/lib/oop","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/event_emitter").EventEmitter,s=t.Anchor=function(e,t,n){this.$onChange=this.onChange.bind(this),this.attach(e),typeof n=="undefined"?this.setPosition(t.row,t.column):this.setPosition(t,n)};(function(){r.implement(this,i),this.getPosition=function(){return this.$clipPositionToDocument(this.row,this.column)},this.getDocument=function(){return this.document},this.$insertRight=!1,this.onChange=function(e){var t=e.data,n=t.range;if(n.start.row==n.end.row&&n.start.row!=this.row)return;if(n.start.row>this.row)return;if(n.start.row==this.row&&n.start.column>this.column)return;var r=this.row,i=this.column,s=n.start,o=n.end;if(t.action==="insertText")if(s.row===r&&s.column<=i){if(s.column!==i||!this.$insertRight)s.row===o.row?i+=o.column-s.column:(i-=s.column,r+=o.row-s.row)}else s.row!==o.row&&s.row<r&&(r+=o.row-s.row);else t.action==="insertLines"?(s.row!==r||i!==0||!this.$insertRight)&&s.row<=r&&(r+=o.row-s.row):t.action==="removeText"?s.row===r&&s.column<i?o.column>=i?i=s.column:i=Math.max(0,i-(o.column-s.column)):s.row!==o.row&&s.row<r?(o.row===r&&(i=Math.max(0,i-o.column)+s.column),r-=o.row-s.row):o.row===r&&(r-=o.row-s.row,i=Math.max(0,i-o.column)+s.column):t.action=="removeLines"&&s.row<=r&&(o.row<=r?r-=o.row-s.row:(r=s.row,i=0));this.setPosition(r,i,!0)},this.setPosition=function(e,t,n){var r;n?r={row:e,column:t}:r=this.$clipPositionToDocument(e,t);if(this.row==r.row&&this.column==r.column)return;var i={row:this.row,column:this.column};this.row=r.row,this.column=r.column,this._signal("change",{old:i,value:r})},this.detach=function(){this.document.removeEventListener("change",this.$onChange)},this.attach=function(e){this.document=e||this.document,this.document.on("change",this.$onChange)},this.$clipPositionToDocument=function(e,t){var n={};return e>=this.document.getLength()?(n.row=Math.max(0,this.document.getLength()-1),n.column=this.document.getLine(n.row).length):e<0?(n.row=0,n.column=0):(n.row=e,n.column=Math.min(this.document.getLine(n.row).length,Math.max(0,t))),t<0&&(n.column=0),n}}).call(s.prototype)}),ace.define("ace/document",["require","exports","module","ace/lib/oop","ace/lib/event_emitter","ace/range","ace/anchor"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/event_emitter").EventEmitter,s=e("./range").Range,o=e("./anchor").Anchor,u=function(e){this.$lines=[],e.length===0?this.$lines=[""]:Array.isArray(e)?this._insertLines(0,e):this.insert({row:0,column:0},e)};(function(){r.implement(this,i),this.setValue=function(e){var t=this.getLength();this.remove(new s(0,0,t,this.getLine(t-1).length)),this.insert({row:0,column:0},e)},this.getValue=function(){return this.getAllLines().join(this.getNewLineCharacter())},this.createAnchor=function(e,t){return new o(this,e,t)},"aaa".split(/a/).length===0?this.$split=function(e){return e.replace(/\r\n|\r/g,"\n").split("\n")}:this.$split=function(e){return e.split(/\r\n|\r|\n/)},this.$detectNewLine=function(e){var t=e.match(/^.*?(\r\n|\r|\n)/m);this.$autoNewLine=t?t[1]:"\n",this._signal("changeNewLineMode")},this.getNewLineCharacter=function(){switch(this.$newLineMode){case"windows":return"\r\n";case"unix":return"\n";default:return this.$autoNewLine||"\n"}},this.$autoNewLine="",this.$newLineMode="auto",this.setNewLineMode=function(e){if(this.$newLineMode===e)return;this.$newLineMode=e,this._signal("changeNewLineMode")},this.getNewLineMode=function(){return this.$newLineMode},this.isNewLine=function(e){return e=="\r\n"||e=="\r"||e=="\n"},this.getLine=function(e){return this.$lines[e]||""},this.getLines=function(e,t){return this.$lines.slice(e,t+1)},this.getAllLines=function(){return this.getLines(0,this.getLength())},this.getLength=function(){return this.$lines.length},this.getTextRange=function(e){if(e.start.row==e.end.row)return this.getLine(e.start.row).substring(e.start.column,e.end.column);var t=this.getLines(e.start.row,e.end.row);t[0]=(t[0]||"").substring(e.start.column);var n=t.length-1;return e.end.row-e.start.row==n&&(t[n]=t[n].substring(0,e.end.column)),t.join(this.getNewLineCharacter())},this.$clipPosition=function(e){var t=this.getLength();return e.row>=t?(e.row=Math.max(0,t-1),e.column=this.getLine(t-1).length):e.row<0&&(e.row=0),e},this.insert=function(e,t){if(!t||t.length===0)return e;e=this.$clipPosition(e),this.getLength()<=1&&this.$detectNewLine(t);var n=this.$split(t),r=n.splice(0,1)[0],i=n.length==0?null:n.splice(n.length-1,1)[0];return e=this.insertInLine(e,r),i!==null&&(e=this.insertNewLine(e),e=this._insertLines(e.row,n),e=this.insertInLine(e,i||"")),e},this.insertLines=function(e,t){return e>=this.getLength()?this.insert({row:e,column:0},"\n"+t.join("\n")):this._insertLines(Math.max(e,0),t)},this._insertLines=function(e,t){if(t.length==0)return{row:e,column:0};while(t.length>61440){var n=this._insertLines(e,t.slice(0,61440));t=t.slice(61440),e=n.row}var r=[e,0];r.push.apply(r,t),this.$lines.splice.apply(this.$lines,r);var i=new s(e,0,e+t.length,0),o={action:"insertLines",range:i,lines:t};return this._signal("change",{data:o}),i.end},this.insertNewLine=function(e){e=this.$clipPosition(e);var t=this.$lines[e.row]||"";this.$lines[e.row]=t.substring(0,e.column),this.$lines.splice(e.row+1,0,t.substring(e.column,t.length));var n={row:e.row+1,column:0},r={action:"insertText",range:s.fromPoints(e,n),text:this.getNewLineCharacter()};return this._signal("change",{data:r}),n},this.insertInLine=function(e,t){if(t.length==0)return e;var n=this.$lines[e.row]||"";this.$lines[e.row]=n.substring(0,e.column)+t+n.substring(e.column);var r={row:e.row,column:e.column+t.length},i={action:"insertText",range:s.fromPoints(e,r),text:t};return this._signal("change",{data:i}),r},this.remove=function(e){e instanceof s||(e=s.fromPoints(e.start,e.end)),e.start=this.$clipPosition(e.start),e.end=this.$clipPosition(e.end);if(e.isEmpty())return e.start;var t=e.start.row,n=e.end.row;if(e.isMultiLine()){var r=e.start.column==0?t:t+1,i=n-1;e.end.column>0&&this.removeInLine(n,0,e.end.column),i>=r&&this._removeLines(r,i),r!=t&&(this.removeInLine(t,e.start.column,this.getLine(t).length),this.removeNewLine(e.start.row))}else this.removeInLine(t,e.start.column,e.end.column);return e.start},this.removeInLine=function(e,t,n){if(t==n)return;var r=new s(e,t,e,n),i=this.getLine(e),o=i.substring(t,n),u=i.substring(0,t)+i.substring(n,i.length);this.$lines.splice(e,1,u);var a={action:"removeText",range:r,text:o};return this._signal("change",{data:a}),r.start},this.removeLines=function(e,t){return e<0||t>=this.getLength()?this.remove(new s(e,0,t+1,0)):this._removeLines(e,t)},this._removeLines=function(e,t){var n=new s(e,0,t+1,0),r=this.$lines.splice(e,t-e+1),i={action:"removeLines",range:n,nl:this.getNewLineCharacter(),lines:r};return this._signal("change",{data:i}),r},this.removeNewLine=function(e){var t=this.getLine(e),n=this.getLine(e+1),r=new s(e,t.length,e+1,0),i=t+n;this.$lines.splice(e,2,i);var o={action:"removeText",range:r,text:this.getNewLineCharacter()};this._signal("change",{data:o})},this.replace=function(e,t){e instanceof s||(e=s.fromPoints(e.start,e.end));if(t.length==0&&e.isEmpty())return e.start;if(t==this.getTextRange(e))return e.end;this.remove(e);if(t)var n=this.insert(e.start,t);else n=e.start;return n},this.applyDeltas=function(e){for(var t=0;t<e.length;t++){var n=e[t],r=s.fromPoints(n.range.start,n.range.end);n.action=="insertLines"?this.insertLines(r.start.row,n.lines):n.action=="insertText"?this.insert(r.start,n.text):n.action=="removeLines"?this._removeLines(r.start.row,r.end.row-1):n.action=="removeText"&&this.remove(r)}},this.revertDeltas=function(e){for(var t=e.length-1;t>=0;t--){var n=e[t],r=s.fromPoints(n.range.start,n.range.end);n.action=="insertLines"?this._removeLines(r.start.row,r.end.row-1):n.action=="insertText"?this.remove(r):n.action=="removeLines"?this._insertLines(r.start.row,n.lines):n.action=="removeText"&&this.insert(r.start,n.text)}},this.indexToPosition=function(e,t){var n=this.$lines||this.getAllLines(),r=this.getNewLineCharacter().length;for(var i=t||0,s=n.length;i<s;i++){e-=n[i].length+r;if(e<0)return{row:i,column:e+n[i].length+r}}return{row:s-1,column:n[s-1].length}},this.positionToIndex=function(e,t){var n=this.$lines||this.getAllLines(),r=this.getNewLineCharacter().length,i=0,s=Math.min(e.row,n.length);for(var o=t||0;o<s;++o)i+=n[o].length+r;return i+e.column}}).call(u.prototype),t.Document=u}),ace.define("ace/background_tokenizer",["require","exports","module","ace/lib/oop","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/event_emitter").EventEmitter,s=function(e,t){this.running=!1,this.lines=[],this.states=[],this.currentLine=0,this.tokenizer=e;var n=this;this.$worker=function(){if(!n.running)return;var e=new Date,t=n.currentLine,r=-1,i=n.doc;while(n.lines[t])t++;var s=t,o=i.getLength(),u=0;n.running=!1;while(t<o){n.$tokenizeRow(t),r=t;do t++;while(n.lines[t]);u++;if(u%5==0&&new Date-e>20){n.running=setTimeout(n.$worker,20),n.currentLine=t;return}}n.currentLine=t,s<=r&&n.fireUpdateEvent(s,r)}};(function(){r.implement(this,i),this.setTokenizer=function(e){this.tokenizer=e,this.lines=[],this.states=[],this.start(0)},this.setDocument=function(e){this.doc=e,this.lines=[],this.states=[],this.stop()},this.fireUpdateEvent=function(e,t){var n={first:e,last:t};this._signal("update",{data:n})},this.start=function(e){this.currentLine=Math.min(e||0,this.currentLine,this.doc.getLength()),this.lines.splice(this.currentLine,this.lines.length),this.states.splice(this.currentLine,this.states.length),this.stop(),this.running=setTimeout(this.$worker,700)},this.scheduleStart=function(){this.running||(this.running=setTimeout(this.$worker,700))},this.$updateOnChange=function(e){var t=e.range,n=t.start.row,r=t.end.row-n;if(r===0)this.lines[n]=null;else if(e.action=="removeText"||e.action=="removeLines")this.lines.splice(n,r+1,null),this.states.splice(n,r+1,null);else{var i=Array(r+1);i.unshift(n,1),this.lines.splice.apply(this.lines,i),this.states.splice.apply(this.states,i)}this.currentLine=Math.min(n,this.currentLine,this.doc.getLength()),this.stop()},this.stop=function(){this.running&&clearTimeout(this.running),this.running=!1},this.getTokens=function(e){return this.lines[e]||this.$tokenizeRow(e)},this.getState=function(e){return this.currentLine==e&&this.$tokenizeRow(e),this.states[e]||"start"},this.$tokenizeRow=function(e){var t=this.doc.getLine(e),n=this.states[e-1],r=this.tokenizer.getLineTokens(t,n,e);return this.states[e]+""!=r.state+""?(this.states[e]=r.state,this.lines[e+1]=null,this.currentLine>e+1&&(this.currentLine=e+1)):this.currentLine==e&&(this.currentLine=e+1),this.lines[e]=r.tokens}}).call(s.prototype),t.BackgroundTokenizer=s}),ace.define("ace/search_highlight",["require","exports","module","ace/lib/lang","ace/lib/oop","ace/range"],function(e,t,n){"use strict";var r=e("./lib/lang"),i=e("./lib/oop"),s=e("./range").Range,o=function(e,t,n){this.setRegexp(e),this.clazz=t,this.type=n||"text"};(function(){this.MAX_RANGES=500,this.setRegexp=function(e){if(this.regExp+""==e+"")return;this.regExp=e,this.cache=[]},this.update=function(e,t,n,i){if(!this.regExp)return;var o=i.firstRow,u=i.lastRow;for(var a=o;a<=u;a++){var f=this.cache[a];f==null&&(f=r.getMatchOffsets(n.getLine(a),this.regExp),f.length>this.MAX_RANGES&&(f=f.slice(0,this.MAX_RANGES)),f=f.map(function(e){return new s(a,e.offset,a,e.offset+e.length)}),this.cache[a]=f.length?f:"");for(var l=f.length;l--;)t.drawSingleLineMarker(e,f[l].toScreenRange(n),this.clazz,i)}}}).call(o.prototype),t.SearchHighlight=o}),ace.define("ace/edit_session/fold_line",["require","exports","module","ace/range"],function(e,t,n){"use strict";function i(e,t){this.foldData=e,Array.isArray(t)?this.folds=t:t=this.folds=[t];var n=t[t.length-1];this.range=new r(t[0].start.row,t[0].start.column,n.end.row,n.end.column),this.start=this.range.start,this.end=this.range.end,this.folds.forEach(function(e){e.setFoldLine(this)},this)}var r=e("../range").Range;(function(){this.shiftRow=function(e){this.start.row+=e,this.end.row+=e,this.folds.forEach(function(t){t.start.row+=e,t.end.row+=e})},this.addFold=function(e){if(e.sameRow){if(e.start.row<this.startRow||e.endRow>this.endRow)throw new Error("Can't add a fold to this FoldLine as it has no connection");this.folds.push(e),this.folds.sort(function(e,t){return-e.range.compareEnd(t.start.row,t.start.column)}),this.range.compareEnd(e.start.row,e.start.column)>0?(this.end.row=e.end.row,this.end.column=e.end.column):this.range.compareStart(e.end.row,e.end.column)<0&&(this.start.row=e.start.row,this.start.column=e.start.column)}else if(e.start.row==this.end.row)this.folds.push(e),this.end.row=e.end.row,this.end.column=e.end.column;else{if(e.end.row!=this.start.row)throw new Error("Trying to add fold to FoldRow that doesn't have a matching row");this.folds.unshift(e),this.start.row=e.start.row,this.start.column=e.start.column}e.foldLine=this},this.containsRow=function(e){return e>=this.start.row&&e<=this.end.row},this.walk=function(e,t,n){var r=0,i=this.folds,s,o,u,a=!0;t==null&&(t=this.end.row,n=this.end.column);for(var f=0;f<i.length;f++){s=i[f],o=s.range.compareStart(t,n);if(o==-1){e(null,t,n,r,a);return}u=e(null,s.start.row,s.start.column,r,a),u=!u&&e(s.placeholder,s.start.row,s.start.column,r);if(u||o==0)return;a=!s.sameRow,r=s.end.column}e(null,t,n,r,a)},this.getNextFoldTo=function(e,t){var n,r;for(var i=0;i<this.folds.length;i++){n=this.folds[i],r=n.range.compareEnd(e,t);if(r==-1)return{fold:n,kind:"after"};if(r==0)return{fold:n,kind:"inside"}}return null},this.addRemoveChars=function(e,t,n){var r=this.getNextFoldTo(e,t),i,s;if(r){i=r.fold;if(r.kind=="inside"&&i.start.column!=t&&i.start.row!=e)window.console&&window.console.log(e,t,i);else if(i.start.row==e){s=this.folds;var o=s.indexOf(i);o==0&&(this.start.column+=n);for(o;o<s.length;o++){i=s[o],i.start.column+=n;if(!i.sameRow)return;i.end.column+=n}this.end.column+=n}}},this.split=function(e,t){var n=this.getNextFoldTo(e,t).fold,r=this.folds,s=this.foldData;if(!n)return null;var o=r.indexOf(n),u=r[o-1];this.end.row=u.end.row,this.end.column=u.end.column,r=r.splice(o,r.length-o);var a=new i(s,r);return s.splice(s.indexOf(this)+1,0,a),a},this.merge=function(e){var t=e.folds;for(var n=0;n<t.length;n++)this.addFold(t[n]);var r=this.foldData;r.splice(r.indexOf(e),1)},this.toString=function(){var e=[this.range.toString()+": ["];return this.folds.forEach(function(t){e.push("  "+t.toString())}),e.push("]"),e.join("\n")},this.idxToPosition=function(e){var t=0,n;for(var r=0;r<this.folds.length;r++){var n=this.folds[r];e-=n.start.column-t;if(e<0)return{row:n.start.row,column:n.start.column+e};e-=n.placeholder.length;if(e<0)return n.start;t=n.end.column}return{row:this.end.row,column:this.end.column+e}}}).call(i.prototype),t.FoldLine=i}),ace.define("ace/range_list",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("./range").Range,i=r.comparePoints,s=function(){this.ranges=[]};(function(){this.comparePoints=i,this.pointIndex=function(e,t,n){var r=this.ranges;for(var s=n||0;s<r.length;s++){var o=r[s],u=i(e,o.end);if(u>0)continue;var a=i(e,o.start);return u===0?t&&a!==0?-s-2:s:a>0||a===0&&!t?s:-s-1}return-s-1},this.add=function(e){var t=!e.isEmpty(),n=this.pointIndex(e.start,t);n<0&&(n=-n-1);var r=this.pointIndex(e.end,t,n);return r<0?r=-r-1:r++,this.ranges.splice(n,r-n,e)},this.addList=function(e){var t=[];for(var n=e.length;n--;)t.push.call(t,this.add(e[n]));return t},this.substractPoint=function(e){var t=this.pointIndex(e);if(t>=0)return this.ranges.splice(t,1)},this.merge=function(){var e=[],t=this.ranges;t=t.sort(function(e,t){return i(e.start,t.start)});var n=t[0],r;for(var s=1;s<t.length;s++){r=n,n=t[s];var o=i(r.end,n.start);if(o<0)continue;if(o==0&&!r.isEmpty()&&!n.isEmpty())continue;i(r.end,n.end)<0&&(r.end.row=n.end.row,r.end.column=n.end.column),t.splice(s,1),e.push(n),n=r,s--}return this.ranges=t,e},this.contains=function(e,t){return this.pointIndex({row:e,column:t})>=0},this.containsPoint=function(e){return this.pointIndex(e)>=0},this.rangeAtPoint=function(e){var t=this.pointIndex(e);if(t>=0)return this.ranges[t]},this.clipRows=function(e,t){var n=this.ranges;if(n[0].start.row>t||n[n.length-1].start.row<e)return[];var r=this.pointIndex({row:e,column:0});r<0&&(r=-r-1);var i=this.pointIndex({row:t,column:0},r);i<0&&(i=-i-1);var s=[];for(var o=r;o<i;o++)s.push(n[o]);return s},this.removeAll=function(){return this.ranges.splice(0,this.ranges.length)},this.attach=function(e){this.session&&this.detach(),this.session=e,this.onChange=this.$onChange.bind(this),this.session.on("change",this.onChange)},this.detach=function(){if(!this.session)return;this.session.removeListener("change",this.onChange),this.session=null},this.$onChange=function(e){var t=e.data.range;if(e.data.action[0]=="i")var n=t.start,r=t.end;else var r=t.start,n=t.end;var i=n.row,s=r.row,o=s-i,u=-n.column+r.column,a=this.ranges;for(var f=0,l=a.length;f<l;f++){var c=a[f];if(c.end.row<i)continue;if(c.start.row>i)break;c.start.row==i&&c.start.column>=n.column&&(c.start.column!=n.column||!this.$insertRight)&&(c.start.column+=u,c.start.row+=o);if(c.end.row==i&&c.end.column>=n.column){if(c.end.column==n.column&&this.$insertRight)continue;c.end.column==n.column&&u>0&&f<l-1&&c.end.column>c.start.column&&c.end.column==a[f+1].start.column&&(c.end.column-=u),c.end.column+=u,c.end.row+=o}}if(o!=0&&f<l)for(;f<l;f++){var c=a[f];c.start.row+=o,c.end.row+=o}}}).call(s.prototype),t.RangeList=s}),ace.define("ace/edit_session/fold",["require","exports","module","ace/range","ace/range_list","ace/lib/oop"],function(e,t,n){"use strict";function u(e,t){e.row-=t.row,e.row==0&&(e.column-=t.column)}function a(e,t){u(e.start,t),u(e.end,t)}function f(e,t){e.row==0&&(e.column+=t.column),e.row+=t.row}function l(e,t){f(e.start,t),f(e.end,t)}var r=e("../range").Range,i=e("../range_list").RangeList,s=e("../lib/oop"),o=t.Fold=function(e,t){this.foldLine=null,this.placeholder=t,this.range=e,this.start=e.start,this.end=e.end,this.sameRow=e.start.row==e.end.row,this.subFolds=this.ranges=[]};s.inherits(o,i),function(){this.toString=function(){return'"'+this.placeholder+'" '+this.range.toString()},this.setFoldLine=function(e){this.foldLine=e,this.subFolds.forEach(function(t){t.setFoldLine(e)})},this.clone=function(){var e=this.range.clone(),t=new o(e,this.placeholder);return this.subFolds.forEach(function(e){t.subFolds.push(e.clone())}),t.collapseChildren=this.collapseChildren,t},this.addSubFold=function(e){if(this.range.isEqual(e))return;if(!this.range.containsRange(e))throw new Error("A fold can't intersect already existing fold"+e.range+this.range);a(e,this.start);var t=e.start.row,n=e.start.column;for(var r=0,i=-1;r<this.subFolds.length;r++){i=this.subFolds[r].range.compare(t,n);if(i!=1)break}var s=this.subFolds[r];if(i==0)return s.addSubFold(e);var t=e.range.end.row,n=e.range.end.column;for(var o=r,i=-1;o<this.subFolds.length;o++){i=this.subFolds[o].range.compare(t,n);if(i!=1)break}var u=this.subFolds[o];if(i==0)throw new Error("A fold can't intersect already existing fold"+e.range+this.range);var f=this.subFolds.splice(r,o-r,e);return e.setFoldLine(this.foldLine),e},this.restoreRange=function(e){return l(e,this.start)}}.call(o.prototype)}),ace.define("ace/edit_session/folding",["require","exports","module","ace/range","ace/edit_session/fold_line","ace/edit_session/fold","ace/token_iterator"],function(e,t,n){"use strict";function u(){this.getFoldAt=function(e,t,n){var r=this.getFoldLine(e);if(!r)return null;var i=r.folds;for(var s=0;s<i.length;s++){var o=i[s];if(o.range.contains(e,t)){if(n==1&&o.range.isEnd(e,t))continue;if(n==-1&&o.range.isStart(e,t))continue;return o}}},this.getFoldsInRange=function(e){var t=e.start,n=e.end,r=this.$foldData,i=[];t.column+=1,n.column-=1;for(var s=0;s<r.length;s++){var o=r[s].range.compareRange(e);if(o==2)continue;if(o==-2)break;var u=r[s].folds;for(var a=0;a<u.length;a++){var f=u[a];o=f.range.compareRange(e);if(o==-2)break;if(o==2)continue;if(o==42)break;i.push(f)}}return t.column-=1,n.column+=1,i},this.getFoldsInRangeList=function(e){if(Array.isArray(e)){var t=[];e.forEach(function(e){t=t.concat(this.getFoldsInRange(e))},this)}else var t=this.getFoldsInRange(e);return t},this.getAllFolds=function(){var e=[],t=this.$foldData;for(var n=0;n<t.length;n++)for(var r=0;r<t[n].folds.length;r++)e.push(t[n].folds[r]);return e},this.getFoldStringAt=function(e,t,n,r){r=r||this.getFoldLine(e);if(!r)return null;var i={end:{column:0}},s,o;for(var u=0;u<r.folds.length;u++){o=r.folds[u];var a=o.range.compareEnd(e,t);if(a==-1){s=this.getLine(o.start.row).substring(i.end.column,o.start.column);break}if(a===0)return null;i=o}return s||(s=this.getLine(o.start.row).substring(i.end.column)),n==-1?s.substring(0,t-i.end.column):n==1?s.substring(t-i.end.column):s},this.getFoldLine=function(e,t){var n=this.$foldData,r=0;t&&(r=n.indexOf(t)),r==-1&&(r=0);for(r;r<n.length;r++){var i=n[r];if(i.start.row<=e&&i.end.row>=e)return i;if(i.end.row>e)return null}return null},this.getNextFoldLine=function(e,t){var n=this.$foldData,r=0;t&&(r=n.indexOf(t)),r==-1&&(r=0);for(r;r<n.length;r++){var i=n[r];if(i.end.row>=e)return i}return null},this.getFoldedRowCount=function(e,t){var n=this.$foldData,r=t-e+1;for(var i=0;i<n.length;i++){var s=n[i],o=s.end.row,u=s.start.row;if(o>=t){u<t&&(u>=e?r-=t-u:r=0);break}o>=e&&(u>=e?r-=o-u:r-=o-e+1)}return r},this.$addFoldLine=function(e){return this.$foldData.push(e),this.$foldData.sort(function(e,t){return e.start.row-t.start.row}),e},this.addFold=function(e,t){var n=this.$foldData,r=!1,o;e instanceof s?o=e:(o=new s(t,e),o.collapseChildren=t.collapseChildren),this.$clipRangeToDocument(o.range);var u=o.start.row,a=o.start.column,f=o.end.row,l=o.end.column;if(u<f||u==f&&a<=l-2){var c=this.getFoldAt(u,a,1),h=this.getFoldAt(f,l,-1);if(c&&h==c)return c.addSubFold(o);if(c&&!c.range.isStart(u,a)||h&&!h.range.isEnd(f,l))throw new Error("A fold can't intersect already existing fold"+o.range+c.range);var p=this.getFoldsInRange(o.range);p.length>0&&(this.removeFolds(p),p.forEach(function(e){o.addSubFold(e)}));for(var d=0;d<n.length;d++){var v=n[d];if(f==v.start.row){v.addFold(o),r=!0;break}if(u==v.end.row){v.addFold(o),r=!0;if(!o.sameRow){var m=n[d+1];if(m&&m.start.row==f){v.merge(m);break}}break}if(f<=v.start.row)break}return r||(v=this.$addFoldLine(new i(this.$foldData,o))),this.$useWrapMode?this.$updateWrapData(v.start.row,v.start.row):this.$updateRowLengthCache(v.start.row,v.start.row),this.$modified=!0,this._emit("changeFold",{data:o,action:"add"}),o}throw new Error("The range has to be at least 2 characters width")},this.addFolds=function(e){e.forEach(function(e){this.addFold(e)},this)},this.removeFold=function(e){var t=e.foldLine,n=t.start.row,r=t.end.row,i=this.$foldData,s=t.folds;if(s.length==1)i.splice(i.indexOf(t),1);else if(t.range.isEnd(e.end.row,e.end.column))s.pop(),t.end.row=s[s.length-1].end.row,t.end.column=s[s.length-1].end.column;else if(t.range.isStart(e.start.row,e.start.column))s.shift(),t.start.row=s[0].start.row,t.start.column=s[0].start.column;else if(e.sameRow)s.splice(s.indexOf(e),1);else{var o=t.split(e.start.row,e.start.column);s=o.folds,s.shift(),o.start.row=s[0].start.row,o.start.column=s[0].start.column}this.$updating||(this.$useWrapMode?this.$updateWrapData(n,r):this.$updateRowLengthCache(n,r)),this.$modified=!0,this._emit("changeFold",{data:e,action:"remove"})},this.removeFolds=function(e){var t=[];for(var n=0;n<e.length;n++)t.push(e[n]);t.forEach(function(e){this.removeFold(e)},this),this.$modified=!0},this.expandFold=function(e){this.removeFold(e),e.subFolds.forEach(function(t){e.restoreRange(t),this.addFold(t)},this),e.collapseChildren>0&&this.foldAll(e.start.row+1,e.end.row,e.collapseChildren-1),e.subFolds=[]},this.expandFolds=function(e){e.forEach(function(e){this.expandFold(e)},this)},this.unfold=function(e,t){var n,i;e==null?(n=new r(0,0,this.getLength(),0),t=!0):typeof e=="number"?n=new r(e,0,e,this.getLine(e).length):"row"in e?n=r.fromPoints(e,e):n=e,i=this.getFoldsInRangeList(n);if(t)this.removeFolds(i);else{var s=i;while(s.length)this.expandFolds(s),s=this.getFoldsInRangeList(n)}if(i.length)return i},this.isRowFolded=function(e,t){return!!this.getFoldLine(e,t)},this.getRowFoldEnd=function(e,t){var n=this.getFoldLine(e,t);return n?n.end.row:e},this.getRowFoldStart=function(e,t){var n=this.getFoldLine(e,t);return n?n.start.row:e},this.getFoldDisplayLine=function(e,t,n,r,i){r==null&&(r=e.start.row),i==null&&(i=0),t==null&&(t=e.end.row),n==null&&(n=this.getLine(t).length);var s=this.doc,o="";return e.walk(function(e,t,n,u){if(t<r)return;if(t==r){if(n<i)return;u=Math.max(i,u)}e!=null?o+=e:o+=s.getLine(t).substring(u,n)},t,n),o},this.getDisplayLine=function(e,t,n,r){var i=this.getFoldLine(e);if(!i){var s;return s=this.doc.getLine(e),s.substring(r||0,t||s.length)}return this.getFoldDisplayLine(i,e,t,n,r)},this.$cloneFoldData=function(){var e=[];return e=this.$foldData.map(function(t){var n=t.folds.map(function(e){return e.clone()});return new i(e,n)}),e},this.toggleFold=function(e){var t=this.selection,n=t.getRange(),r,i;if(n.isEmpty()){var s=n.start;r=this.getFoldAt(s.row,s.column);if(r){this.expandFold(r);return}(i=this.findMatchingBracket(s))?n.comparePoint(i)==1?n.end=i:(n.start=i,n.start.column++,n.end.column--):(i=this.findMatchingBracket({row:s.row,column:s.column+1}))?(n.comparePoint(i)==1?n.end=i:n.start=i,n.start.column++):n=this.getCommentFoldRange(s.row,s.column)||n}else{var o=this.getFoldsInRange(n);if(e&&o.length){this.expandFolds(o);return}o.length==1&&(r=o[0])}r||(r=this.getFoldAt(n.start.row,n.start.column));if(r&&r.range.toString()==n.toString()){this.expandFold(r);return}var u="...";if(!n.isMultiLine()){u=this.getTextRange(n);if(u.length<4)return;u=u.trim().substring(0,2)+".."}this.addFold(u,n)},this.getCommentFoldRange=function(e,t,n){var i=new o(this,e,t),s=i.getCurrentToken();if(s&&/^comment|string/.test(s.type)){var u=new r,a=new RegExp(s.type.replace(/\..*/,"\\."));if(n!=1){do s=i.stepBackward();while(s&&a.test(s.type));i.stepForward()}u.start.row=i.getCurrentTokenRow(),u.start.column=i.getCurrentTokenColumn()+2,i=new o(this,e,t);if(n!=-1){do s=i.stepForward();while(s&&a.test(s.type));s=i.stepBackward()}else s=i.getCurrentToken();return u.end.row=i.getCurrentTokenRow(),u.end.column=i.getCurrentTokenColumn()+s.value.length-2,u}},this.foldAll=function(e,t,n){n==undefined&&(n=1e5);var r=this.foldWidgets;if(!r)return;t=t||this.getLength(),e=e||0;for(var i=e;i<t;i++){r[i]==null&&(r[i]=this.getFoldWidget(i));if(r[i]!="start")continue;var s=this.getFoldWidgetRange(i);if(s&&s.isMultiLine()&&s.end.row<=t&&s.start.row>=e){i=s.end.row;try{var o=this.addFold("...",s);o&&(o.collapseChildren=n)}catch(u){}}}},this.$foldStyles={manual:1,markbegin:1,markbeginend:1},this.$foldStyle="markbegin",this.setFoldStyle=function(e){if(!this.$foldStyles[e])throw new Error("invalid fold style: "+e+"["+Object.keys(this.$foldStyles).join(", ")+"]");if(this.$foldStyle==e)return;this.$foldStyle=e,e=="manual"&&this.unfold();var t=this.$foldMode;this.$setFolding(null),this.$setFolding(t)},this.$setFolding=function(e){if(this.$foldMode==e)return;this.$foldMode=e,this.removeListener("change",this.$updateFoldWidgets),this._emit("changeAnnotation");if(!e||this.$foldStyle=="manual"){this.foldWidgets=null;return}this.foldWidgets=[],this.getFoldWidget=e.getFoldWidget.bind(e,this,this.$foldStyle),this.getFoldWidgetRange=e.getFoldWidgetRange.bind(e,this,this.$foldStyle),this.$updateFoldWidgets=this.updateFoldWidgets.bind(this),this.on("change",this.$updateFoldWidgets)},this.getParentFoldRangeData=function(e,t){var n=this.foldWidgets;if(!n||t&&n[e])return{};var r=e-1,i;while(r>=0){var s=n[r];s==null&&(s=n[r]=this.getFoldWidget(r));if(s=="start"){var o=this.getFoldWidgetRange(r);i||(i=o);if(o&&o.end.row>=e)break}r--}return{range:r!==-1&&o,firstRange:i}},this.onFoldWidgetClick=function(e,t){t=t.domEvent;var n={children:t.shiftKey,all:t.ctrlKey||t.metaKey,siblings:t.altKey},r=this.$toggleFoldWidget(e,n);if(!r){var i=t.target||t.srcElement;i&&/ace_fold-widget/.test(i.className)&&(i.className+=" ace_invalid")}},this.$toggleFoldWidget=function(e,t){if(!this.getFoldWidget)return;var n=this.getFoldWidget(e),r=this.getLine(e),i=n==="end"?-1:1,s=this.getFoldAt(e,i===-1?0:r.length,i);if(s){t.children||t.all?this.removeFold(s):this.expandFold(s);return}var o=this.getFoldWidgetRange(e,!0);if(o&&!o.isMultiLine()){s=this.getFoldAt(o.start.row,o.start.column,1);if(s&&o.isEqual(s.range)){this.removeFold(s);return}}if(t.siblings){var u=this.getParentFoldRangeData(e);if(u.range)var a=u.range.start.row+1,f=u.range.end.row;this.foldAll(a,f,t.all?1e4:0)}else t.children?(f=o?o.end.row:this.getLength(),this.foldAll(e+1,o.end.row,t.all?1e4:0)):o&&(t.all&&(o.collapseChildren=1e4),this.addFold("...",o));return o},this.toggleFoldWidget=function(e){var t=this.selection.getCursor().row;t=this.getRowFoldStart(t);var n=this.$toggleFoldWidget(t,{});if(n)return;var r=this.getParentFoldRangeData(t,!0);n=r.range||r.firstRange;if(n){t=n.start.row;var i=this.getFoldAt(t,this.getLine(t).length,1);i?this.removeFold(i):this.addFold("...",n)}},this.updateFoldWidgets=function(e){var t=e.data,n=t.range,r=n.start.row,i=n.end.row-r;if(i===0)this.foldWidgets[r]=null;else if(t.action=="removeText"||t.action=="removeLines")this.foldWidgets.splice(r,i+1,null);else{var s=Array(i+1);s.unshift(r,1),this.foldWidgets.splice.apply(this.foldWidgets,s)}}}var r=e("../range").Range,i=e("./fold_line").FoldLine,s=e("./fold").Fold,o=e("../token_iterator").TokenIterator;t.Folding=u}),ace.define("ace/edit_session/bracket_match",["require","exports","module","ace/token_iterator","ace/range"],function(e,t,n){"use strict";function s(){this.findMatchingBracket=function(e,t){if(e.column==0)return null;var n=t||this.getLine(e.row).charAt(e.column-1);if(n=="")return null;var r=n.match(/([\(\[\{])|([\)\]\}])/);return r?r[1]?this.$findClosingBracket(r[1],e):this.$findOpeningBracket(r[2],e):null},this.getBracketRange=function(e){var t=this.getLine(e.row),n=!0,r,s=t.charAt(e.column-1),o=s&&s.match(/([\(\[\{])|([\)\]\}])/);o||(s=t.charAt(e.column),e={row:e.row,column:e.column+1},o=s&&s.match(/([\(\[\{])|([\)\]\}])/),n=!1);if(!o)return null;if(o[1]){var u=this.$findClosingBracket(o[1],e);if(!u)return null;r=i.fromPoints(e,u),n||(r.end.column++,r.start.column--),r.cursor=r.end}else{var u=this.$findOpeningBracket(o[2],e);if(!u)return null;r=i.fromPoints(u,e),n||(r.start.column++,r.end.column--),r.cursor=r.start}return r},this.$brackets={")":"(","(":")","]":"[","[":"]","{":"}","}":"{"},this.$findOpeningBracket=function(e,t,n){var i=this.$brackets[e],s=1,o=new r(this,t.row,t.column),u=o.getCurrentToken();u||(u=o.stepForward());if(!u)return;n||(n=new RegExp("(\\.?"+u.type.replace(".","\\.").replace("rparen",".paren")+")+"));var a=t.column-o.getCurrentTokenColumn()-2,f=u.value;for(;;){while(a>=0){var l=f.charAt(a);if(l==i){s-=1;if(s==0)return{row:o.getCurrentTokenRow(),column:a+o.getCurrentTokenColumn()}}else l==e&&(s+=1);a-=1}do u=o.stepBackward();while(u&&!n.test(u.type));if(u==null)break;f=u.value,a=f.length-1}return null},this.$findClosingBracket=function(e,t,n){var i=this.$brackets[e],s=1,o=new r(this,t.row,t.column),u=o.getCurrentToken();u||(u=o.stepForward());if(!u)return;n||(n=new RegExp("(\\.?"+u.type.replace(".","\\.").replace("lparen",".paren")+")+"));var a=t.column-o.getCurrentTokenColumn();for(;;){var f=u.value,l=f.length;while(a<l){var c=f.charAt(a);if(c==i){s-=1;if(s==0)return{row:o.getCurrentTokenRow(),column:a+o.getCurrentTokenColumn()}}else c==e&&(s+=1);a+=1}do u=o.stepForward();while(u&&!n.test(u.type));if(u==null)break;a=0}return null}}var r=e("../token_iterator").TokenIterator,i=e("../range").Range;t.BracketMatch=s}),ace.define("ace/edit_session",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/config","ace/lib/event_emitter","ace/selection","ace/mode/text","ace/range","ace/document","ace/background_tokenizer","ace/search_highlight","ace/edit_session/folding","ace/edit_session/bracket_match"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/lang"),s=e("./config"),o=e("./lib/event_emitter").EventEmitter,u=e("./selection").Selection,a=e("./mode/text").Mode,f=e("./range").Range,l=e("./document").Document,c=e("./background_tokenizer").BackgroundTokenizer,h=e("./search_highlight").SearchHighlight,p=function(e,t){this.$breakpoints=[],this.$decorations=[],this.$frontMarkers={},this.$backMarkers={},this.$markerId=1,this.$undoSelect=!0,this.$foldData=[],this.$foldData.toString=function(){return this.join("\n")},this.on("changeFold",this.onChangeFold.bind(this)),this.$onChange=this.onChange.bind(this);if(typeof e!="object"||!e.getLine)e=new l(e);this.setDocument(e),this.selection=new u(this),s.resetOptions(this),this.setMode(t),s._signal("session",this)};(function(){function g(e){return e<4352?!1:e>=4352&&e<=4447||e>=4515&&e<=4519||e>=4602&&e<=4607||e>=9001&&e<=9002||e>=11904&&e<=11929||e>=11931&&e<=12019||e>=12032&&e<=12245||e>=12272&&e<=12283||e>=12288&&e<=12350||e>=12353&&e<=12438||e>=12441&&e<=12543||e>=12549&&e<=12589||e>=12593&&e<=12686||e>=12688&&e<=12730||e>=12736&&e<=12771||e>=12784&&e<=12830||e>=12832&&e<=12871||e>=12880&&e<=13054||e>=13056&&e<=19903||e>=19968&&e<=42124||e>=42128&&e<=42182||e>=43360&&e<=43388||e>=44032&&e<=55203||e>=55216&&e<=55238||e>=55243&&e<=55291||e>=63744&&e<=64255||e>=65040&&e<=65049||e>=65072&&e<=65106||e>=65108&&e<=65126||e>=65128&&e<=65131||e>=65281&&e<=65376||e>=65504&&e<=65510}r.implement(this,o),this.setDocument=function(e){this.doc&&this.doc.removeListener("change",this.$onChange),this.doc=e,e.on("change",this.$onChange),this.bgTokenizer&&this.bgTokenizer.setDocument(this.getDocument()),this.resetCaches()},this.getDocument=function(){return this.doc},this.$resetRowCache=function(e){if(!e){this.$docRowCache=[],this.$screenRowCache=[];return}var t=this.$docRowCache.length,n=this.$getRowCacheIndex(this.$docRowCache,e)+1;t>n&&(this.$docRowCache.splice(n,t),this.$screenRowCache.splice(n,t))},this.$getRowCacheIndex=function(e,t){var n=0,r=e.length-1;while(n<=r){var i=n+r>>1,s=e[i];if(t>s)n=i+1;else{if(!(t<s))return i;r=i-1}}return n-1},this.resetCaches=function(){this.$modified=!0,this.$wrapData=[],this.$rowLengthCache=[],this.$resetRowCache(0),this.bgTokenizer&&this.bgTokenizer.start(0)},this.onChangeFold=function(e){var t=e.data;this.$resetRowCache(t.start.row)},this.onChange=function(e){var t=e.data;this.$modified=!0,this.$resetRowCache(t.range.start.row);var n=this.$updateInternalDataOnChange(e);!this.$fromUndo&&this.$undoManager&&!t.ignore&&(this.$deltasDoc.push(t),n&&n.length!=0&&this.$deltasFold.push({action:"removeFolds",folds:n}),this.$informUndoManager.schedule()),this.bgTokenizer.$updateOnChange(t),this._signal("change",e)},this.setValue=function(e){this.doc.setValue(e),this.selection.moveTo(0,0),this.$resetRowCache(0),this.$deltas=[],this.$deltasDoc=[],this.$deltasFold=[],this.setUndoManager(this.$undoManager),this.getUndoManager().reset()},this.getValue=this.toString=function(){return this.doc.getValue()},this.getSelection=function(){return this.selection},this.getState=function(e){return this.bgTokenizer.getState(e)},this.getTokens=function(e){return this.bgTokenizer.getTokens(e)},this.getTokenAt=function(e,t){var n=this.bgTokenizer.getTokens(e),r,i=0;if(t==null)s=n.length-1,i=this.getLine(e).length;else for(var s=0;s<n.length;s++){i+=n[s].value.length;if(i>=t)break}return r=n[s],r?(r.index=s,r.start=i-r.value.length,r):null},this.setUndoManager=function(e){this.$undoManager=e,this.$deltas=[],this.$deltasDoc=[],this.$deltasFold=[],this.$informUndoManager&&this.$informUndoManager.cancel();if(e){var t=this;this.$syncInformUndoManager=function(){t.$informUndoManager.cancel(),t.$deltasFold.length&&(t.$deltas.push({group:"fold",deltas:t.$deltasFold}),t.$deltasFold=[]),t.$deltasDoc.length&&(t.$deltas.push({group:"doc",deltas:t.$deltasDoc}),t.$deltasDoc=[]),t.$deltas.length>0&&e.execute({action:"aceupdate",args:[t.$deltas,t],merge:t.mergeUndoDeltas}),t.mergeUndoDeltas=!1,t.$deltas=[]},this.$informUndoManager=i.delayedCall(this.$syncInformUndoManager)}},this.markUndoGroup=function(){this.$syncInformUndoManager&&this.$syncInformUndoManager()},this.$defaultUndoManager={undo:function(){},redo:function(){},reset:function(){}},this.getUndoManager=function(){return this.$undoManager||this.$defaultUndoManager},this.getTabString=function(){return this.getUseSoftTabs()?i.stringRepeat(" ",this.getTabSize()):"	"},this.setUseSoftTabs=function(e){this.setOption("useSoftTabs",e)},this.getUseSoftTabs=function(){return this.$useSoftTabs&&!this.$mode.$indentWithTabs},this.setTabSize=function(e){this.setOption("tabSize",e)},this.getTabSize=function(){return this.$tabSize},this.isTabStop=function(e){return this.$useSoftTabs&&e.column%this.$tabSize===0},this.$overwrite=!1,this.setOverwrite=function(e){this.setOption("overwrite",e)},this.getOverwrite=function(){return this.$overwrite},this.toggleOverwrite=function(){this.setOverwrite(!this.$overwrite)},this.addGutterDecoration=function(e,t){this.$decorations[e]||(this.$decorations[e]=""),this.$decorations[e]+=" "+t,this._signal("changeBreakpoint",{})},this.removeGutterDecoration=function(e,t){this.$decorations[e]=(this.$decorations[e]||"").replace(" "+t,""),this._signal("changeBreakpoint",{})},this.getBreakpoints=function(){return this.$breakpoints},this.setBreakpoints=function(e){this.$breakpoints=[];for(var t=0;t<e.length;t++)this.$breakpoints[e[t]]="ace_breakpoint";this._signal("changeBreakpoint",{})},this.clearBreakpoints=function(){this.$breakpoints=[],this._signal("changeBreakpoint",{})},this.setBreakpoint=function(e,t){t===undefined&&(t="ace_breakpoint"),t?this.$breakpoints[e]=t:delete this.$breakpoints[e],this._signal("changeBreakpoint",{})},this.clearBreakpoint=function(e){delete this.$breakpoints[e],this._signal("changeBreakpoint",{})},this.addMarker=function(e,t,n,r){var i=this.$markerId++,s={range:e,type:n||"line",renderer:typeof n=="function"?n:null,clazz:t,inFront:!!r,id:i};return r?(this.$frontMarkers[i]=s,this._signal("changeFrontMarker")):(this.$backMarkers[i]=s,this._signal("changeBackMarker")),i},this.addDynamicMarker=function(e,t){if(!e.update)return;var n=this.$markerId++;return e.id=n,e.inFront=!!t,t?(this.$frontMarkers[n]=e,this._signal("changeFrontMarker")):(this.$backMarkers[n]=e,this._signal("changeBackMarker")),e},this.removeMarker=function(e){var t=this.$frontMarkers[e]||this.$backMarkers[e];if(!t)return;var n=t.inFront?this.$frontMarkers:this.$backMarkers;t&&(delete n[e],this._signal(t.inFront?"changeFrontMarker":"changeBackMarker"))},this.getMarkers=function(e){return e?this.$frontMarkers:this.$backMarkers},this.highlight=function(e){if(!this.$searchHighlight){var t=new h(null,"ace_selected-word","text");this.$searchHighlight=this.addDynamicMarker(t)}this.$searchHighlight.setRegexp(e)},this.highlightLines=function(e,t,n,r){typeof t!="number"&&(n=t,t=e),n||(n="ace_step");var i=new f(e,0,t,Infinity);return i.id=this.addMarker(i,n,"fullLine",r),i},this.setAnnotations=function(e){this.$annotations=e,this._signal("changeAnnotation",{})},this.getAnnotations=function(){return this.$annotations||[]},this.clearAnnotations=function(){this.setAnnotations([])},this.$detectNewLine=function(e){var t=e.match(/^.*?(\r?\n)/m);t?this.$autoNewLine=t[1]:this.$autoNewLine="\n"},this.getWordRange=function(e,t){var n=this.getLine(e),r=!1;t>0&&(r=!!n.charAt(t-1).match(this.tokenRe)),r||(r=!!n.charAt(t).match(this.tokenRe));if(r)var i=this.tokenRe;else if(/^\s+$/.test(n.slice(t-1,t+1)))var i=/\s/;else var i=this.nonTokenRe;var s=t;if(s>0){do s--;while(s>=0&&n.charAt(s).match(i));s++}var o=t;while(o<n.length&&n.charAt(o).match(i))o++;return new f(e,s,e,o)},this.getAWordRange=function(e,t){var n=this.getWordRange(e,t),r=this.getLine(n.end.row);while(r.charAt(n.end.column).match(/[ \t]/))n.end.column+=1;return n},this.setNewLineMode=function(e){this.doc.setNewLineMode(e)},this.getNewLineMode=function(){return this.doc.getNewLineMode()},this.setUseWorker=function(e){this.setOption("useWorker",e)},this.getUseWorker=function(){return this.$useWorker},this.onReloadTokenizer=function(e){var t=e.data;this.bgTokenizer.start(t.first),this._signal("tokenizerUpdate",e)},this.$modes={},this.$mode=null,this.$modeId=null,this.setMode=function(e,t){if(e&&typeof e=="object"){if(e.getTokenizer)return this.$onChangeMode(e);var n=e,r=n.path}else r=e||"ace/mode/text";this.$modes["ace/mode/text"]||(this.$modes["ace/mode/text"]=new a);if(this.$modes[r]&&!n){this.$onChangeMode(this.$modes[r]),t&&t();return}this.$modeId=r,s.loadModule(["mode",r],function(e){if(this.$modeId!==r)return t&&t();if(this.$modes[r]&&!n)return this.$onChangeMode(this.$modes[r]);e&&e.Mode&&(e=new e.Mode(n),n||(this.$modes[r]=e,e.$id=r),this.$onChangeMode(e),t&&t())}.bind(this)),this.$mode||this.$onChangeMode(this.$modes["ace/mode/text"],!0)},this.$onChangeMode=function(e,t){t||(this.$modeId=e.$id);if(this.$mode===e)return;this.$mode=e,this.$stopWorker(),this.$useWorker&&this.$startWorker();var n=e.getTokenizer();if(n.addEventListener!==undefined){var r=this.onReloadTokenizer.bind(this);n.addEventListener("update",r)}if(!this.bgTokenizer){this.bgTokenizer=new c(n);var i=this;this.bgTokenizer.addEventListener("update",function(e){i._signal("tokenizerUpdate",e)})}else this.bgTokenizer.setTokenizer(n);this.bgTokenizer.setDocument(this.getDocument()),this.tokenRe=e.tokenRe,this.nonTokenRe=e.nonTokenRe,t||(this.$options.wrapMethod.set.call(this,this.$wrapMethod),this.$setFolding(e.foldingRules),this.bgTokenizer.start(0),this._emit("changeMode"))},this.$stopWorker=function(){this.$worker&&this.$worker.terminate(),this.$worker=null},this.$startWorker=function(){if(typeof Worker!="undefined"&&!e.noWorker)try{this.$worker=this.$mode.createWorker(this)}catch(t){console.log("Could not load worker"),console.log(t),this.$worker=null}else this.$worker=null},this.getMode=function(){return this.$mode},this.$scrollTop=0,this.setScrollTop=function(e){if(this.$scrollTop===e||isNaN(e))return;this.$scrollTop=e,this._signal("changeScrollTop",e)},this.getScrollTop=function(){return this.$scrollTop},this.$scrollLeft=0,this.setScrollLeft=function(e){if(this.$scrollLeft===e||isNaN(e))return;this.$scrollLeft=e,this._signal("changeScrollLeft",e)},this.getScrollLeft=function(){return this.$scrollLeft},this.getScreenWidth=function(){return this.$computeWidth(),this.lineWidgets?Math.max(this.getLineWidgetMaxWidth(),this.screenWidth):this.screenWidth},this.getLineWidgetMaxWidth=function(){if(this.lineWidgetsWidth!=null)return this.lineWidgetsWidth;var e=0;return this.lineWidgets.forEach(function(t){t&&t.screenWidth>e&&(e=t.screenWidth)}),this.lineWidgetWidth=e},this.$computeWidth=function(e){if(this.$modified||e){this.$modified=!1;if(this.$useWrapMode)return this.screenWidth=this.$wrapLimit;var t=this.doc.getAllLines(),n=this.$rowLengthCache,r=0,i=0,s=this.$foldData[i],o=s?s.start.row:Infinity,u=t.length;for(var a=0;a<u;a++){if(a>o){a=s.end.row+1;if(a>=u)break;s=this.$foldData[i++],o=s?s.start.row:Infinity}n[a]==null&&(n[a]=this.$getStringScreenWidth(t[a])[0]),n[a]>r&&(r=n[a])}this.screenWidth=r}},this.getLine=function(e){return this.doc.getLine(e)},this.getLines=function(e,t){return this.doc.getLines(e,t)},this.getLength=function(){return this.doc.getLength()},this.getTextRange=function(e){return this.doc.getTextRange(e||this.selection.getRange())},this.insert=function(e,t){return this.doc.insert(e,t)},this.remove=function(e){return this.doc.remove(e)},this.undoChanges=function(e,t){if(!e.length)return;this.$fromUndo=!0;var n=null;for(var r=e.length-1;r!=-1;r--){var i=e[r];i.group=="doc"?(this.doc.revertDeltas(i.deltas),n=this.$getUndoSelection(i.deltas,!0,n)):i.deltas.forEach(function(e){this.addFolds(e.folds)},this)}return this.$fromUndo=!1,n&&this.$undoSelect&&!t&&this.selection.setSelectionRange(n),n},this.redoChanges=function(e,t){if(!e.length)return;this.$fromUndo=!0;var n=null;for(var r=0;r<e.length;r++){var i=e[r];i.group=="doc"&&(this.doc.applyDeltas(i.deltas),n=this.$getUndoSelection(i.deltas,!1,n))}return this.$fromUndo=!1,n&&this.$undoSelect&&!t&&this.selection.setSelectionRange(n),n},this.setUndoSelect=function(e){this.$undoSelect=e},this.$getUndoSelection=function(e,t,n){function r(e){var n=e.action==="insertText"||e.action==="insertLines";return t?!n:n}var i=e[0],s,o,u=!1;r(i)?(s=f.fromPoints(i.range.start,i.range.end),u=!0):(s=f.fromPoints(i.range.start,i.range.start),u=!1);for(var a=1;a<e.length;a++)i=e[a],r(i)?(o=i.range.start,s.compare(o.row,o.column)==-1&&s.setStart(i.range.start),o=i.range.end,s.compare(o.row,o.column)==1&&s.setEnd(i.range.end),u=!0):(o=i.range.start,s.compare(o.row,o.column)==-1&&(s=f.fromPoints(i.range.start,i.range.start)),u=!1);if(n!=null){f.comparePoints(n.start,s.start)===0&&(n.start.column+=s.end.column-s.start.column,n.end.column+=s.end.column-s.start.column);var l=n.compareRange(s);l==1?s.setStart(n.start):l==-1&&s.setEnd(n.end)}return s},this.replace=function(e,t){return this.doc.replace(e,t)},this.moveText=function(e,t,n){var r=this.getTextRange(e),i=this.getFoldsInRange(e),s=f.fromPoints(t,t);if(!n){this.remove(e);var o=e.start.row-e.end.row,u=o?-e.end.column:e.start.column-e.end.column;u&&(s.start.row==e.end.row&&s.start.column>e.end.column&&(s.start.column+=u),s.end.row==e.end.row&&s.end.column>e.end.column&&(s.end.column+=u)),o&&s.start.row>=e.end.row&&(s.start.row+=o,s.end.row+=o)}s.end=this.insert(s.start,r);if(i.length){var a=e.start,l=s.start,o=l.row-a.row,u=l.column-a.column;this.addFolds(i.map(function(e){return e=e.clone(),e.start.row==a.row&&(e.start.column+=u),e.end.row==a.row&&(e.end.column+=u),e.start.row+=o,e.end.row+=o,e}))}return s},this.indentRows=function(e,t,n){n=n.replace(/\t/g,this.getTabString());for(var r=e;r<=t;r++)this.insert({row:r,column:0},n)},this.outdentRows=function(e){var t=e.collapseRows(),n=new f(0,0,0,0),r=this.getTabSize();for(var i=t.start.row;i<=t.end.row;++i){var s=this.getLine(i);n.start.row=i,n.end.row=i;for(var o=0;o<r;++o)if(s.charAt(o)!=" ")break;o<r&&s.charAt(o)=="	"?(n.start.column=o,n.end.column=o+1):(n.start.column=0,n.end.column=o),this.remove(n)}},this.$moveLines=function(e,t,n){e=this.getRowFoldStart(e),t=this.getRowFoldEnd(t);if(n<0){var r=this.getRowFoldStart(e+n);if(r<0)return 0;var i=r-e}else if(n>0){var r=this.getRowFoldEnd(t+n);if(r>this.doc.getLength()-1)return 0;var i=r-t}else{e=this.$clipRowToDocument(e),t=this.$clipRowToDocument(t);var i=t-e+1}var s=new f(e,0,t,Number.MAX_VALUE),o=this.getFoldsInRange(s).map(function(e){return e=e.clone(),e.start.row+=i,e.end.row+=i,e}),u=n==0?this.doc.getLines(e,t):this.doc.removeLines(e,t);return this.doc.insertLines(e+i,u),o.length&&this.addFolds(o),i},this.moveLinesUp=function(e,t){return this.$moveLines(e,t,-1)},this.moveLinesDown=function(e,t){return this.$moveLines(e,t,1)},this.duplicateLines=function(e,t){return this.$moveLines(e,t,0)},this.$clipRowToDocument=function(e){return Math.max(0,Math.min(e,this.doc.getLength()-1))},this.$clipColumnToRow=function(e,t){return t<0?0:Math.min(this.doc.getLine(e).length,t)},this.$clipPositionToDocument=function(e,t){t=Math.max(0,t);if(e<0)e=0,t=0;else{var n=this.doc.getLength();e>=n?(e=n-1,t=this.doc.getLine(n-1).length):t=Math.min(this.doc.getLine(e).length,t)}return{row:e,column:t}},this.$clipRangeToDocument=function(e){e.start.row<0?(e.start.row=0,e.start.column=0):e.start.column=this.$clipColumnToRow(e.start.row,e.start.column);var t=this.doc.getLength()-1;return e.end.row>t?(e.end.row=t,e.end.column=this.doc.getLine(t).length):e.end.column=this.$clipColumnToRow(e.end.row,e.end.column),e},this.$wrapLimit=80,this.$useWrapMode=!1,this.$wrapLimitRange={min:null,max:null},this.setUseWrapMode=function(e){if(e!=this.$useWrapMode){this.$useWrapMode=e,this.$modified=!0,this.$resetRowCache(0);if(e){var t=this.getLength();this.$wrapData=Array(t),this.$updateWrapData(0,t-1)}this._signal("changeWrapMode")}},this.getUseWrapMode=function(){return this.$useWrapMode},this.setWrapLimitRange=function(e,t){if(this.$wrapLimitRange.min!==e||this.$wrapLimitRange.max!==t)this.$wrapLimitRange={min:e,max:t},this.$modified=!0,this._signal("changeWrapMode")},this.adjustWrapLimit=function(e,t){var n=this.$wrapLimitRange;n.max<0&&(n={min:t,max:t});var r=this.$constrainWrapLimit(e,n.min,n.max);return r!=this.$wrapLimit&&r>1?(this.$wrapLimit=r,this.$modified=!0,this.$useWrapMode&&(this.$updateWrapData(0,this.getLength()-1),this.$resetRowCache(0),this._signal("changeWrapLimit")),!0):!1},this.$constrainWrapLimit=function(e,t,n){return t&&(e=Math.max(t,e)),n&&(e=Math.min(n,e)),e},this.getWrapLimit=function(){return this.$wrapLimit},this.setWrapLimit=function(e){this.setWrapLimitRange(e,e)},this.getWrapLimitRange=function(){return{min:this.$wrapLimitRange.min,max:this.$wrapLimitRange.max}},this.$updateInternalDataOnChange=function(e){var t=this.$useWrapMode,n,r=e.data.action,i=e.data.range.start.row,s=e.data.range.end.row,o=e.data.range.start,u=e.data.range.end,a=null;r.indexOf("Lines")!=-1?(r=="insertLines"?s=i+e.data.lines.length:s=i,n=e.data.lines?e.data.lines.length:s-i):n=s-i,this.$updating=!0;if(n!=0)if(r.indexOf("remove")!=-1){this[t?"$wrapData":"$rowLengthCache"].splice(i,n);var f=this.$foldData;a=this.getFoldsInRange(e.data.range),this.removeFolds(a);var l=this.getFoldLine(u.row),c=0;if(l){l.addRemoveChars(u.row,u.column,o.column-u.column),l.shiftRow(-n);var h=this.getFoldLine(i);h&&h!==l&&(h.merge(l),l=h),c=f.indexOf(l)+1}for(c;c<f.length;c++){var l=f[c];l.start.row>=u.row&&l.shiftRow(-n)}s=i}else{var p=Array(n);p.unshift(i,0);var d=t?this.$wrapData:this.$rowLengthCache;d.splice.apply(d,p);var f=this.$foldData,l=this.getFoldLine(i),c=0;if(l){var v=l.range.compareInside(o.row,o.column);v==0?(l=l.split(o.row,o.column),l.shiftRow(n),l.addRemoveChars(s,0,u.column-o.column)):v==-1&&(l.addRemoveChars(i,0,u.column-o.column),l.shiftRow(n)),c=f.indexOf(l)+1}for(c;c<f.length;c++){var l=f[c];l.start.row>=i&&l.shiftRow(n)}}else{n=Math.abs(e.data.range.start.column-e.data.range.end.column),r.indexOf("remove")!=-1&&(a=this.getFoldsInRange(e.data.range),this.removeFolds(a),n=-n);var l=this.getFoldLine(i);l&&l.addRemoveChars(i,o.column,n)}return t&&this.$wrapData.length!=this.doc.getLength()&&console.error("doc.getLength() and $wrapData.length have to be the same!"),this.$updating=!1,t?this.$updateWrapData(i,s):this.$updateRowLengthCache(i,s),a},this.$updateRowLengthCache=function(e,t,n){this.$rowLengthCache[e]=null,this.$rowLengthCache[t]=null},this.$updateWrapData=function(e,t){var n=this.doc.getAllLines(),r=this.getTabSize(),i=this.$wrapData,s=this.$wrapLimit,o,a,f=e;t=Math.min(t,n.length-1);while(f<=t)a=this.getFoldLine(f,a),a?(o=[],a.walk(function(e,t,r,i){var s;if(e!=null){s=this.$getDisplayTokens(e,o.length),s[0]=u;for(var a=1;a<s.length;a++)s[a]=l}else s=this.$getDisplayTokens(n[t].substring(i,r),o.length);o=o.concat(s)}.bind(this),a.end.row,n[a.end.row].length+1),i[a.start.row]=this.$computeWrapSplits(o,s,r),f=a.end.row+1):(o=this.$getDisplayTokens(n[f]),i[f]=this.$computeWrapSplits(o,s,r),f++)};var t=1,n=2,u=3,l=4,p=9,d=10,v=11,m=12;this.$computeWrapSplits=function(e,t){function a(t){var r=e.slice(i,t),o=r.length;r.join("").replace(/12/g,function(){o-=1}).replace(/2/g,function(){o-=1}),s+=o,n.push(s),i=t}if(e.length==0)return[];var n=[],r=e.length,i=0,s=0,o=this.$wrapAsCode;while(r-i>t){var f=i+t;if(e[f-1]>=d&&e[f]>=d){a(f);continue}if(e[f]==u||e[f]==l){for(f;f!=i-1;f--)if(e[f]==u)break;if(f>i){a(f);continue}f=i+t;for(f;f<e.length;f++)if(e[f]!=l)break;if(f==e.length)break;a(f);continue}var c=Math.max(f-(o?10:t-(t>>2)),i-1);while(f>c&&e[f]<u)f--;if(o){while(f>c&&e[f]<u)f--;while(f>c&&e[f]==p)f--}else while(f>c&&e[f]<d)f--;if(f>c){a(++f);continue}f=i+t,a(f)}return n},this.$getDisplayTokens=function(e,r){var i=[],s;r=r||0;for(var o=0;o<e.length;o++){var u=e.charCodeAt(o);if(u==9){s=this.getScreenTabSize(i.length+r),i.push(v);for(var a=1;a<s;a++)i.push(m)}else u==32?i.push(d):u>39&&u<48||u>57&&u<64?i.push(p):u>=4352&&g(u)?i.push(t,n):i.push(t)}return i},this.$getStringScreenWidth=function(e,t,n){if(t==0)return[0,0];t==null&&(t=Infinity),n=n||0;var r,i;for(i=0;i<e.length;i++){r=e.charCodeAt(i),r==9?n+=this.getScreenTabSize(n):r>=4352&&g(r)?n+=2:n+=1;if(n>t)break}return[n,i]},this.lineWidgets=null,this.getRowLength=function(e){if(this.lineWidgets)var t=this.lineWidgets[e]&&this.lineWidgets[e].rowCount||0;else t=0;return!this.$useWrapMode||!this.$wrapData[e]?1+t:this.$wrapData[e].length+1+t},this.getRowLineCount=function(e){return!this.$useWrapMode||!this.$wrapData[e]?1:this.$wrapData[e].length+1},this.getScreenLastRowColumn=function(e){var t=this.screenToDocumentPosition(e,Number.MAX_VALUE);return this.documentToScreenColumn(t.row,t.column)},this.getDocumentLastRowColumn=function(e,t){var n=this.documentToScreenRow(e,t);return this.getScreenLastRowColumn(n)},this.getDocumentLastRowColumnPosition=function(e,t){var n=this.documentToScreenRow(e,t);return this.screenToDocumentPosition(n,Number.MAX_VALUE/10)},this.getRowSplitData=function(e){return this.$useWrapMode?this.$wrapData[e]:undefined},this.getScreenTabSize=function(e){return this.$tabSize-e%this.$tabSize},this.screenToDocumentRow=function(e,t){return this.screenToDocumentPosition(e,t).row},this.screenToDocumentColumn=function(e,t){return this.screenToDocumentPosition(e,t).column},this.screenToDocumentPosition=function(e,t){if(e<0)return{row:0,column:0};var n,r=0,i=0,s,o=0,u=0,a=this.$screenRowCache,f=this.$getRowCacheIndex(a,e),l=a.length;if(l&&f>=0)var o=a[f],r=this.$docRowCache[f],c=e>a[l-1];else var c=!l;var h=this.getLength()-1,p=this.getNextFoldLine(r),d=p?p.start.row:Infinity;while(o<=e){u=this.getRowLength(r);if(o+u>e||r>=h)break;o+=u,r++,r>d&&(r=p.end.row+1,p=this.getNextFoldLine(r,p),d=p?p.start.row:Infinity),c&&(this.$docRowCache.push(r),this.$screenRowCache.push(o))}if(p&&p.start.row<=r)n=this.getFoldDisplayLine(p),r=p.start.row;else{if(o+u<=e||r>h)return{row:h,column:this.getLine(h).length};n=this.getLine(r),p=null}if(this.$useWrapMode){var v=this.$wrapData[r];if(v){var m=Math.floor(e-o);s=v[m],m>0&&v.length&&(i=v[m-1]||v[v.length-1],n=n.substring(i))}}return i+=this.$getStringScreenWidth(n,t)[1],this.$useWrapMode&&i>=s&&(i=s-1),p?p.idxToPosition(i):{row:r,column:i}},this.documentToScreenPosition=function(e,t){if(typeof t=="undefined")var n=this.$clipPositionToDocument(e.row,e.column);else n=this.$clipPositionToDocument(e,t);e=n.row,t=n.column;var r=0,i=null,s=null;s=this.getFoldAt(e,t,1),s&&(e=s.start.row,t=s.start.column);var o,u=0,a=this.$docRowCache,f=this.$getRowCacheIndex(a,e),l=a.length;if(l&&f>=0)var u=a[f],r=this.$screenRowCache[f],c=e>a[l-1];else var c=!l;var h=this.getNextFoldLine(u),p=h?h.start.row:Infinity;while(u<e){if(u>=p){o=h.end.row+1;if(o>e)break;h=this.getNextFoldLine(o,h),p=h?h.start.row:Infinity}else o=u+1;r+=this.getRowLength(u),u=o,c&&(this.$docRowCache.push(u),this.$screenRowCache.push(r))}var d="";h&&u>=p?(d=this.getFoldDisplayLine(h,e,t),i=h.start.row):(d=this.getLine(e).substring(0,t),i=e);if(this.$useWrapMode){var v=this.$wrapData[i];if(v){var m=0;while(d.length>=v[m])r++,m++;d=d.substring(v[m-1]||0,d.length)}}return{row:r,column:this.$getStringScreenWidth(d)[0]}},this.documentToScreenColumn=function(e,t){return this.documentToScreenPosition(e,t).column},this.documentToScreenRow=function(e,t){return this.documentToScreenPosition(e,t).row},this.getScreenLength=function(){var e=0,t=null;if(!this.$useWrapMode){e=this.getLength();var n=this.$foldData;for(var r=0;r<n.length;r++)t=n[r],e-=t.end.row-t.start.row}else{var i=this.$wrapData.length,s=0,r=0,t=this.$foldData[r++],o=t?t.start.row:Infinity;while(s<i){var u=this.$wrapData[s];e+=u?u.length+1:1,s++,s>o&&(s=t.end.row+1,t=this.$foldData[r++],o=t?t.start.row:Infinity)}}return this.lineWidgets&&(e+=this.$getWidgetScreenLength()),e},this.$setFontMetrics=function(e){}}).call(p.prototype),e("./edit_session/folding").Folding.call(p.prototype),e("./edit_session/bracket_match").BracketMatch.call(p.prototype),s.defineOptions(p.prototype,"session",{wrap:{set:function(e){!e||e=="off"?e=!1:e=="free"?e=!0:e=="printMargin"?e=-1:typeof e=="string"&&(e=parseInt(e,10)||!1);if(this.$wrap==e)return;if(!e)this.setUseWrapMode(!1);else{var t=typeof e=="number"?e:null;this.setWrapLimitRange(t,t),this.setUseWrapMode(!0)}this.$wrap=e},get:function(){return this.getUseWrapMode()?this.$wrap==-1?"printMargin":this.getWrapLimitRange().min?this.$wrap:"free":"off"},handlesSet:!0},wrapMethod:{set:function(e){e=e=="auto"?this.$mode.type!="text":e!="text",e!=this.$wrapAsCode&&(this.$wrapAsCode=e,this.$useWrapMode&&(this.$modified=!0,this.$resetRowCache(0),this.$updateWrapData(0,this.getLength()-1)))},initialValue:"auto"},firstLineNumber:{set:function(){this._signal("changeBreakpoint")},initialValue:1},useWorker:{set:function(e){this.$useWorker=e,this.$stopWorker(),e&&this.$startWorker()},initialValue:!0},useSoftTabs:{initialValue:!0},tabSize:{set:function(e){if(isNaN(e)||this.$tabSize===e)return;this.$modified=!0,this.$rowLengthCache=[],this.$tabSize=e,this._signal("changeTabSize")},initialValue:4,handlesSet:!0},overwrite:{set:function(e){this._signal("changeOverwrite")},initialValue:!1},newLineMode:{set:function(e){this.doc.setNewLineMode(e)},get:function(){return this.doc.getNewLineMode()},handlesSet:!0},mode:{set:function(e){this.setMode(e)},get:function(){return this.$modeId}}}),t.EditSession=p}),ace.define("ace/search",["require","exports","module","ace/lib/lang","ace/lib/oop","ace/range"],function(e,t,n){"use strict";var r=e("./lib/lang"),i=e("./lib/oop"),s=e("./range").Range,o=function(){this.$options={}};(function(){this.set=function(e){return i.mixin(this.$options,e),this},this.getOptions=function(){return r.copyObject(this.$options)},this.setOptions=function(e){this.$options=e},this.find=function(e){var t=this.$matchIterator(e,this.$options);if(!t)return!1;var n=null;return t.forEach(function(e,t,r){if(!e.start){var i=e.offset+(r||0);n=new s(t,i,t,i+e.length)}else n=e;return!0}),n},this.findAll=function(e){var t=this.$options;if(!t.needle)return[];this.$assembleRegExp(t);var n=t.range,i=n?e.getLines(n.start.row,n.end.row):e.doc.getAllLines(),o=[],u=t.re;if(t.$isMultiLine){var a=u.length,f=i.length-a,l;e:for(var c=u.offset||0;c<=f;c++){for(var h=0;h<a;h++)if(i[c+h].search(u[h])==-1)continue e;var p=i[c],d=i[c+a-1],v=p.length-p.match(u[0])[0].length,m=d.match(u[a-1])[0].length;if(l&&l.end.row===c&&l.end.column>v)continue;o.push(l=new s(c,v,c+a-1,m)),a>2&&(c=c+a-2)}}else for(var g=0;g<i.length;g++){var y=r.getMatchOffsets(i[g],u);for(var h=0;h<y.length;h++){var b=y[h];o.push(new s(g,b.offset,g,b.offset+b.length))}}if(n){var w=n.start.column,E=n.start.column,g=0,h=o.length-1;while(g<h&&o[g].start.column<w&&o[g].start.row==n.start.row)g++;while(g<h&&o[h].end.column>E&&o[h].end.row==n.end.row)h--;o=o.slice(g,h+1);for(g=0,h=o.length;g<h;g++)o[g].start.row+=n.start.row,o[g].end.row+=n.start.row}return o},this.replace=function(e,t){var n=this.$options,r=this.$assembleRegExp(n);if(n.$isMultiLine)return t;if(!r)return;var i=r.exec(e);if(!i||i[0].length!=e.length)return null;t=e.replace(r,t);if(n.preserveCase){t=t.split("");for(var s=Math.min(e.length,e.length);s--;){var o=e[s];o&&o.toLowerCase()!=o?t[s]=t[s].toUpperCase():t[s]=t[s].toLowerCase()}t=t.join("")}return t},this.$matchIterator=function(e,t){var n=this.$assembleRegExp(t);if(!n)return!1;var i=this,o,u=t.backwards;if(t.$isMultiLine)var a=n.length,f=function(t,r,i){var u=t.search(n[0]);if(u==-1)return;for(var f=1;f<a;f++){t=e.getLine(r+f);if(t.search(n[f])==-1)return}var l=t.match(n[a-1])[0].length,c=new s(r,u,r+a-1,l);n.offset==1?(c.start.row--,c.start.column=Number.MAX_VALUE):i&&(c.start.column+=i);if(o(c))return!0};else if(u)var f=function(e,t,i){var s=r.getMatchOffsets(e,n);for(var u=s.length-1;u>=0;u--)if(o(s[u],t,i))return!0};else var f=function(e,t,i){var s=r.getMatchOffsets(e,n);for(var u=0;u<s.length;u++)if(o(s[u],t,i))return!0};return{forEach:function(n){o=n,i.$lineIterator(e,t).forEach(f)}}},this.$assembleRegExp=function(e,t){if(e.needle instanceof RegExp)return e.re=e.needle;var n=e.needle;if(!e.needle)return e.re=!1;e.regExp||(n=r.escapeRegExp(n)),e.wholeWord&&(n="\\b"+n+"\\b");var i=e.caseSensitive?"g":"gi";e.$isMultiLine=!t&&/[\n\r]/.test(n);if(e.$isMultiLine)return e.re=this.$assembleMultilineRegExp(n,i);try{var s=new RegExp(n,i)}catch(o){s=!1}return e.re=s},this.$assembleMultilineRegExp=function(e,t){var n=e.replace(/\r\n|\r|\n/g,"$\n^").split("\n"),r=[];for(var i=0;i<n.length;i++)try{r.push(new RegExp(n[i],t))}catch(s){return!1}return n[0]==""?(r.shift(),r.offset=1):r.offset=0,r},this.$lineIterator=function(e,t){var n=t.backwards==1,r=t.skipCurrent!=0,i=t.range,s=t.start;s||(s=i?i[n?"end":"start"]:e.selection.getRange()),s.start&&(s=s[r!=n?"end":"start"]);var o=i?i.start.row:0,u=i?i.end.row:e.getLength()-1,a=n?function(n){var r=s.row,i=e.getLine(r).substring(0,s.column);if(n(i,r))return;for(r--;r>=o;r--)if(n(e.getLine(r),r))return;if(t.wrap==0)return;for(r=u,o=s.row;r>=o;r--)if(n(e.getLine(r),r))return}:function(n){var r=s.row,i=e.getLine(r).substr(s.column);if(n(i,r,s.column))return;for(r+=1;r<=u;r++)if(n(e.getLine(r),r))return;if(t.wrap==0)return;for(r=o,u=s.row;r<=u;r++)if(n(e.getLine(r),r))return};return{forEach:a}}}).call(o.prototype),t.Search=o}),ace.define("ace/keyboard/hash_handler",["require","exports","module","ace/lib/keys","ace/lib/useragent"],function(e,t,n){"use strict";function s(e,t){this.platform=t||(i.isMac?"mac":"win"),this.commands={},this.commandKeyBinding={};if(this.__defineGetter__&&this.__defineSetter__&&typeof console!="undefined"&&console.error){var n=!1,r=function(){n||(n=!0,console.error("commmandKeyBinding has too many m's. use commandKeyBinding"))};this.__defineGetter__("commmandKeyBinding",function(){return r(),this.commandKeyBinding}),this.__defineSetter__("commmandKeyBinding",function(e){return r(),this.commandKeyBinding=e})}else this.commmandKeyBinding=this.commandKeyBinding;this.addCommands(e)}var r=e("../lib/keys"),i=e("../lib/useragent");(function(){this.addCommand=function(e){this.commands[e.name]&&this.removeCommand(e),this.commands[e.name]=e,e.bindKey&&this._buildKeyHash(e)},this.removeCommand=function(e){var t=typeof e=="string"?e:e.name;e=this.commands[t],delete this.commands[t];var n=this.commandKeyBinding;for(var r in n)for(var i in n[r])n[r][i]==e&&delete n[r][i]},this.bindKey=function(e,t){if(!e)return;if(typeof t=="function"){this.addCommand({exec:t,bindKey:e,name:t.name||e});return}var n=this.commandKeyBinding;e.split("|").forEach(function(e){var r=this.parseKeys(e,t),i=r.hashId;(n[i]||(n[i]={}))[r.key]=t},this)},this.addCommands=function(e){e&&Object.keys(e).forEach(function(t){var n=e[t];if(!n)return;if(typeof n=="string")return this.bindKey(n,t);typeof n=="function"&&(n={exec:n});if(typeof n!="object")return;n.name||(n.name=t),this.addCommand(n)},this)},this.removeCommands=function(e){Object.keys(e).forEach(function(t){this.removeCommand(e[t])},this)},this.bindKeys=function(e){Object.keys(e).forEach(function(t){this.bindKey(t,e[t])},this)},this._buildKeyHash=function(e){var t=e.bindKey;if(!t)return;var n=typeof t=="string"?t:t[this.platform];this.bindKey(n,e)},this.parseKeys=function(e){e.indexOf(" ")!=-1&&(e=e.split(/\s+/).pop());var t=e.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function(e){return e}),n=t.pop(),i=r[n];if(r.FUNCTION_KEYS[i])n=r.FUNCTION_KEYS[i].toLowerCase();else{if(!t.length)return{key:n,hashId:-1};if(t.length==1&&t[0]=="shift")return{key:n.toUpperCase(),hashId:-1}}var s=0;for(var o=t.length;o--;){var u=r.KEY_MODS[t[o]];if(u==null)return typeof console!="undefined"&&console.error("invalid modifier "+t[o]+" in "+e),!1;s|=u}return{key:n,hashId:s}},this.findKeyCommand=function(t,n){var r=this.commandKeyBinding;return r[t]&&r[t][n]},this.handleKeyboard=function(e,t,n,r){return{command:this.findKeyCommand(t,n)}}}).call(s.prototype),t.HashHandler=s}),ace.define("ace/commands/command_manager",["require","exports","module","ace/lib/oop","ace/keyboard/hash_handler","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../keyboard/hash_handler").HashHandler,s=e("../lib/event_emitter").EventEmitter,o=function(e,t){i.call(this,t,e),this.byName=this.commands,this.setDefaultHandler("exec",function(e){return e.command.exec(e.editor,e.args||{})})};r.inherits(o,i),function(){r.implement(this,s),this.exec=function(e,t,n){typeof e=="string"&&(e=this.commands[e]);if(!e)return!1;if(t&&t.$readOnly&&!e.readOnly)return!1;var r={editor:t,command:e,args:n},i=this._emit("exec",r);return this._signal("afterExec",r),i===!1?!1:!0},this.toggleRecording=function(e){if(this.$inReplay)return;return e&&e._emit("changeStatus"),this.recording?(this.macro.pop(),this.removeEventListener("exec",this.$addCommandToMacro),this.macro.length||(this.macro=this.oldMacro),this.recording=!1):(this.$addCommandToMacro||(this.$addCommandToMacro=function(e){this.macro.push([e.command,e.args])}.bind(this)),this.oldMacro=this.macro,this.macro=[],this.on("exec",this.$addCommandToMacro),this.recording=!0)},this.replay=function(e){if(this.$inReplay||!this.macro)return;if(this.recording)return this.toggleRecording(e);try{this.$inReplay=!0,this.macro.forEach(function(t){typeof t=="string"?this.exec(t,e):this.exec(t[0],e,t[1])},this)}finally{this.$inReplay=!1}},this.trimMacro=function(e){return e.map(function(e){return typeof e[0]!="string"&&(e[0]=e[0].name),e[1]||(e=e[0]),e})}}.call(o.prototype),t.CommandManager=o}),ace.define("ace/commands/default_commands",["require","exports","module","ace/lib/lang","ace/config","ace/range"],function(e,t,n){"use strict";function o(e,t){return{win:e,mac:t}}var r=e("../lib/lang"),i=e("../config"),s=e("../range").Range;t.commands=[{name:"showSettingsMenu",bindKey:o("Ctrl-,","Command-,"),exec:function(e){i.loadModule("ace/ext/settings_menu",function(t){t.init(e),e.showSettingsMenu()})},readOnly:!0},{name:"goToNextError",bindKey:o("Alt-E","Ctrl-E"),exec:function(e){i.loadModule("ace/ext/error_marker",function(t){t.showErrorMarker(e,1)})},scrollIntoView:"animate",readOnly:!0},{name:"goToPreviousError",bindKey:o("Alt-Shift-E","Ctrl-Shift-E"),exec:function(e){i.loadModule("ace/ext/error_marker",function(t){t.showErrorMarker(e,-1)})},scrollIntoView:"animate",readOnly:!0},{name:"selectall",bindKey:o("Ctrl-A","Command-A"),exec:function(e){e.selectAll()},readOnly:!0},{name:"centerselection",bindKey:o(null,"Ctrl-L"),exec:function(e){e.centerSelection()},readOnly:!0},{name:"gotoline",bindKey:o("Ctrl-L","Command-L"),exec:function(e){var t=parseInt(prompt("Enter line number:"),10);isNaN(t)||e.gotoLine(t)},readOnly:!0},{name:"fold",bindKey:o("Alt-L|Ctrl-F1","Command-Alt-L|Command-F1"),exec:function(e){e.session.toggleFold(!1)},scrollIntoView:"center",readOnly:!0},{name:"unfold",bindKey:o("Alt-Shift-L|Ctrl-Shift-F1","Command-Alt-Shift-L|Command-Shift-F1"),exec:function(e){e.session.toggleFold(!0)},scrollIntoView:"center",readOnly:!0},{name:"toggleFoldWidget",bindKey:o("F2","F2"),exec:function(e){e.session.toggleFoldWidget()},scrollIntoView:"center",readOnly:!0},{name:"toggleParentFoldWidget",bindKey:o("Alt-F2","Alt-F2"),exec:function(e){e.session.toggleFoldWidget(!0)},scrollIntoView:"center",readOnly:!0},{name:"foldall",bindKey:o("Ctrl-Alt-0","Ctrl-Command-Option-0"),exec:function(e){e.session.foldAll()},scrollIntoView:"center",readOnly:!0},{name:"foldOther",bindKey:o("Alt-0","Command-Option-0"),exec:function(e){e.session.foldAll(),e.session.unfold(e.selection.getAllRanges())},scrollIntoView:"center",readOnly:!0},{name:"unfoldall",bindKey:o("Alt-Shift-0","Command-Option-Shift-0"),exec:function(e){e.session.unfold()},scrollIntoView:"center",readOnly:!0},{name:"findnext",bindKey:o("Ctrl-K","Command-G"),exec:function(e){e.findNext()},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"findprevious",bindKey:o("Ctrl-Shift-K","Command-Shift-G"),exec:function(e){e.findPrevious()},multiSelectAction:"forEach",scrollIntoView:"center",readOnly:!0},{name:"selectOrFindNext",bindKey:o("Alt-K","Ctrl-G"),exec:function(e){e.selection.isEmpty()?e.selection.selectWord():e.findNext()},readOnly:!0},{name:"selectOrFindPrevious",bindKey:o("Alt-Shift-K","Ctrl-Shift-G"),exec:function(e){e.selection.isEmpty()?e.selection.selectWord():e.findPrevious()},readOnly:!0},{name:"find",bindKey:o("Ctrl-F","Command-F"),exec:function(e){i.loadModule("ace/ext/searchbox",function(t){t.Search(e)})},readOnly:!0},{name:"overwrite",bindKey:"Insert",exec:function(e){e.toggleOverwrite()},readOnly:!0},{name:"selecttostart",bindKey:o("Ctrl-Shift-Home","Command-Shift-Up"),exec:function(e){e.getSelection().selectFileStart()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"gotostart",bindKey:o("Ctrl-Home","Command-Home|Command-Up"),exec:function(e){e.navigateFileStart()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"selectup",bindKey:o("Shift-Up","Shift-Up"),exec:function(e){e.getSelection().selectUp()},multiSelectAction:"forEach",readOnly:!0},{name:"golineup",bindKey:o("Up","Up|Ctrl-P"),exec:function(e,t){e.navigateUp(t.times)},multiSelectAction:"forEach",readOnly:!0},{name:"selecttoend",bindKey:o("Ctrl-Shift-End","Command-Shift-Down"),exec:function(e){e.getSelection().selectFileEnd()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"gotoend",bindKey:o("Ctrl-End","Command-End|Command-Down"),exec:function(e){e.navigateFileEnd()},multiSelectAction:"forEach",readOnly:!0,scrollIntoView:"animate",aceCommandGroup:"fileJump"},{name:"selectdown",bindKey:o("Shift-Down","Shift-Down"),exec:function(e){e.getSelection().selectDown()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"golinedown",bindKey:o("Down","Down|Ctrl-N"),exec:function(e,t){e.navigateDown(t.times)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectwordleft",bindKey:o("Ctrl-Shift-Left","Option-Shift-Left"),exec:function(e){e.getSelection().selectWordLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotowordleft",bindKey:o("Ctrl-Left","Option-Left"),exec:function(e){e.navigateWordLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selecttolinestart",bindKey:o("Alt-Shift-Left","Command-Shift-Left"),exec:function(e){e.getSelection().selectLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotolinestart",bindKey:o("Alt-Left|Home","Command-Left|Home|Ctrl-A"),exec:function(e){e.navigateLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectleft",bindKey:o("Shift-Left","Shift-Left"),exec:function(e){e.getSelection().selectLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotoleft",bindKey:o("Left","Left|Ctrl-B"),exec:function(e,t){e.navigateLeft(t.times)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectwordright",bindKey:o("Ctrl-Shift-Right","Option-Shift-Right"),exec:function(e){e.getSelection().selectWordRight()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotowordright",bindKey:o("Ctrl-Right","Option-Right"),exec:function(e){e.navigateWordRight()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selecttolineend",bindKey:o("Alt-Shift-Right","Command-Shift-Right"),exec:function(e){e.getSelection().selectLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotolineend",bindKey:o("Alt-Right|End","Command-Right|End|Ctrl-E"),exec:function(e){e.navigateLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectright",bindKey:o("Shift-Right","Shift-Right"),exec:function(e){e.getSelection().selectRight()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"gotoright",bindKey:o("Right","Right|Ctrl-F"),exec:function(e,t){e.navigateRight(t.times)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectpagedown",bindKey:"Shift-PageDown",exec:function(e){e.selectPageDown()},readOnly:!0},{name:"pagedown",bindKey:o(null,"Option-PageDown"),exec:function(e){e.scrollPageDown()},readOnly:!0},{name:"gotopagedown",bindKey:o("PageDown","PageDown|Ctrl-V"),exec:function(e){e.gotoPageDown()},readOnly:!0},{name:"selectpageup",bindKey:"Shift-PageUp",exec:function(e){e.selectPageUp()},readOnly:!0},{name:"pageup",bindKey:o(null,"Option-PageUp"),exec:function(e){e.scrollPageUp()},readOnly:!0},{name:"gotopageup",bindKey:"PageUp",exec:function(e){e.gotoPageUp()},readOnly:!0},{name:"scrollup",bindKey:o("Ctrl-Up",null),exec:function(e){e.renderer.scrollBy(0,-2*e.renderer.layerConfig.lineHeight)},readOnly:!0},{name:"scrolldown",bindKey:o("Ctrl-Down",null),exec:function(e){e.renderer.scrollBy(0,2*e.renderer.layerConfig.lineHeight)},readOnly:!0},{name:"selectlinestart",bindKey:"Shift-Home",exec:function(e){e.getSelection().selectLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"selectlineend",bindKey:"Shift-End",exec:function(e){e.getSelection().selectLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"togglerecording",bindKey:o("Ctrl-Alt-E","Command-Option-E"),exec:function(e){e.commands.toggleRecording(e)},readOnly:!0},{name:"replaymacro",bindKey:o("Ctrl-Shift-E","Command-Shift-E"),exec:function(e){e.commands.replay(e)},readOnly:!0},{name:"jumptomatching",bindKey:o("Ctrl-P","Ctrl-P"),exec:function(e){e.jumpToMatching()},multiSelectAction:"forEach",readOnly:!0},{name:"selecttomatching",bindKey:o("Ctrl-Shift-P","Ctrl-Shift-P"),exec:function(e){e.jumpToMatching(!0)},multiSelectAction:"forEach",readOnly:!0},{name:"passKeysToBrowser",bindKey:o("null","null"),exec:function(){},passEvent:!0,readOnly:!0},{name:"cut",exec:function(e){var t=e.getSelectionRange();e._emit("cut",t),e.selection.isEmpty()||(e.session.remove(t),e.clearSelection())},scrollIntoView:"cursor",multiSelectAction:"forEach"},{name:"removeline",bindKey:o("Ctrl-D","Command-D"),exec:function(e){e.removeLines()},scrollIntoView:"cursor",multiSelectAction:"forEachLine"},{name:"duplicateSelection",bindKey:o("Ctrl-Shift-D","Command-Shift-D"),exec:function(e){e.duplicateSelection()},scrollIntoView:"cursor",multiSelectAction:"forEach"},{name:"sortlines",bindKey:o("Ctrl-Alt-S","Command-Alt-S"),exec:function(e){e.sortLines()},scrollIntoView:"selection",multiSelectAction:"forEachLine"},{name:"togglecomment",bindKey:o("Ctrl-/","Command-/"),exec:function(e){e.toggleCommentLines()},multiSelectAction:"forEachLine",scrollIntoView:"selectionPart"},{name:"toggleBlockComment",bindKey:o("Ctrl-Shift-/","Command-Shift-/"),exec:function(e){e.toggleBlockComment()},multiSelectAction:"forEach",scrollIntoView:"selectionPart"},{name:"modifyNumberUp",bindKey:o("Ctrl-Shift-Up","Alt-Shift-Up"),exec:function(e){e.modifyNumber(1)},multiSelectAction:"forEach"},{name:"modifyNumberDown",bindKey:o("Ctrl-Shift-Down","Alt-Shift-Down"),exec:function(e){e.modifyNumber(-1)},multiSelectAction:"forEach"},{name:"replace",bindKey:o("Ctrl-H","Command-Option-F"),exec:function(e){i.loadModule("ace/ext/searchbox",function(t){t.Search(e,!0)})}},{name:"undo",bindKey:o("Ctrl-Z","Command-Z"),exec:function(e){e.undo()}},{name:"redo",bindKey:o("Ctrl-Shift-Z|Ctrl-Y","Command-Shift-Z|Command-Y"),exec:function(e){e.redo()}},{name:"copylinesup",bindKey:o("Alt-Shift-Up","Command-Option-Up"),exec:function(e){e.copyLinesUp()},scrollIntoView:"cursor"},{name:"movelinesup",bindKey:o("Alt-Up","Option-Up"),exec:function(e){e.moveLinesUp()},scrollIntoView:"cursor"},{name:"copylinesdown",bindKey:o("Alt-Shift-Down","Command-Option-Down"),exec:function(e){e.copyLinesDown()},scrollIntoView:"cursor"},{name:"movelinesdown",bindKey:o("Alt-Down","Option-Down"),exec:function(e){e.moveLinesDown()},scrollIntoView:"cursor"},{name:"del",bindKey:o("Delete","Delete|Ctrl-D|Shift-Delete"),exec:function(e){e.remove("right")},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"backspace",bindKey:o("Shift-Backspace|Backspace","Ctrl-Backspace|Shift-Backspace|Backspace|Ctrl-H"),exec:function(e){e.remove("left")},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"cut_or_delete",bindKey:o("Shift-Delete",null),exec:function(e){if(!e.selection.isEmpty())return!1;e.remove("left")},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removetolinestart",bindKey:o("Alt-Backspace","Command-Backspace"),exec:function(e){e.removeToLineStart()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removetolineend",bindKey:o("Alt-Delete","Ctrl-K"),exec:function(e){e.removeToLineEnd()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removewordleft",bindKey:o("Ctrl-Backspace","Alt-Backspace|Ctrl-Alt-Backspace"),exec:function(e){e.removeWordLeft()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"removewordright",bindKey:o("Ctrl-Delete","Alt-Delete"),exec:function(e){e.removeWordRight()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"outdent",bindKey:o("Shift-Tab","Shift-Tab"),exec:function(e){e.blockOutdent()},multiSelectAction:"forEach",scrollIntoView:"selectionPart"},{name:"indent",bindKey:o("Tab","Tab"),exec:function(e){e.indent()},multiSelectAction:"forEach",scrollIntoView:"selectionPart"},{name:"blockoutdent",bindKey:o("Ctrl-[","Ctrl-["),exec:function(e){e.blockOutdent()},multiSelectAction:"forEachLine",scrollIntoView:"selectionPart"},{name:"blockindent",bindKey:o("Ctrl-]","Ctrl-]"),exec:function(e){e.blockIndent()},multiSelectAction:"forEachLine",scrollIntoView:"selectionPart"},{name:"insertstring",exec:function(e,t){e.insert(t)},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"inserttext",exec:function(e,t){e.insert(r.stringRepeat(t.text||"",t.times||1))},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"splitline",bindKey:o(null,"Ctrl-O"),exec:function(e){e.splitLine()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"transposeletters",bindKey:o("Ctrl-T","Ctrl-T"),exec:function(e){e.transposeLetters()},multiSelectAction:function(e){e.transposeSelections(1)},scrollIntoView:"cursor"},{name:"touppercase",bindKey:o("Ctrl-U","Ctrl-U"),exec:function(e){e.toUpperCase()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"tolowercase",bindKey:o("Ctrl-Shift-U","Ctrl-Shift-U"),exec:function(e){e.toLowerCase()},multiSelectAction:"forEach",scrollIntoView:"cursor"},{name:"expandtoline",bindKey:o("Ctrl-Shift-L","Command-Shift-L"),exec:function(e){var t=e.selection.getRange();t.start.column=t.end.column=0,t.end.row++,e.selection.setRange(t,!1)},multiSelectAction:"forEach",scrollIntoView:"cursor",readOnly:!0},{name:"joinlines",bindKey:o(null,null),exec:function(e){var t=e.selection.isBackwards(),n=t?e.selection.getSelectionLead():e.selection.getSelectionAnchor(),i=t?e.selection.getSelectionAnchor():e.selection.getSelectionLead(),o=e.session.doc.getLine(n.row).length,u=e.session.doc.getTextRange(e.selection.getRange()),a=u.replace(/\n\s*/," ").length,f=e.session.doc.getLine(n.row);for(var l=n.row+1;l<=i.row+1;l++){var c=r.stringTrimLeft(r.stringTrimRight(e.session.doc.getLine(l)));c.length!==0&&(c=" "+c),f+=c}i.row+1<e.session.doc.getLength()-1&&(f+=e.session.doc.getNewLineCharacter()),e.clearSelection(),e.session.doc.replace(new s(n.row,0,i.row+2,0),f),a>0?(e.selection.moveCursorTo(n.row,n.column),e.selection.selectTo(n.row,n.column+a)):(o=e.session.doc.getLine(n.row).length>o?o+1:o,e.selection.moveCursorTo(n.row,o))},multiSelectAction:"forEach",readOnly:!0},{name:"invertSelection",bindKey:o(null,null),exec:function(e){var t=e.session.doc.getLength()-1,n=e.session.doc.getLine(t).length,r=e.selection.rangeList.ranges,i=[];r.length<1&&(r=[e.selection.getRange()]);for(var o=0;o<r.length;o++)o==r.length-1&&(r[o].end.row!==t||r[o].end.column!==n)&&i.push(new s(r[o].end.row,r[o].end.column,t,n)),o===0?(r[o].start.row!==0||r[o].start.column!==0)&&i.push(new s(0,0,r[o].start.row,r[o].start.column)):i.push(new s(r[o-1].end.row,r[o-1].end.column,r[o].start.row,r[o].start.column));e.exitMultiSelectMode(),e.clearSelection();for(var o=0;o<i.length;o++)e.selection.addRange(i[o],!1)},readOnly:!0,scrollIntoView:"none"}]}),ace.define("ace/editor",["require","exports","module","ace/lib/fixoldbrowsers","ace/lib/oop","ace/lib/dom","ace/lib/lang","ace/lib/useragent","ace/keyboard/textinput","ace/mouse/mouse_handler","ace/mouse/fold_handler","ace/keyboard/keybinding","ace/edit_session","ace/search","ace/range","ace/lib/event_emitter","ace/commands/command_manager","ace/commands/default_commands","ace/config","ace/token_iterator"],function(e,t,n){"use strict";e("./lib/fixoldbrowsers");var r=e("./lib/oop"),i=e("./lib/dom"),s=e("./lib/lang"),o=e("./lib/useragent"),u=e("./keyboard/textinput").TextInput,a=e("./mouse/mouse_handler").MouseHandler,f=e("./mouse/fold_handler").FoldHandler,l=e("./keyboard/keybinding").KeyBinding,c=e("./edit_session").EditSession,h=e("./search").Search,p=e("./range").Range,d=e("./lib/event_emitter").EventEmitter,v=e("./commands/command_manager").CommandManager,m=e("./commands/default_commands").commands,g=e("./config"),y=e("./token_iterator").TokenIterator,b=function(e,t){var n=e.getContainerElement();this.container=n,this.renderer=e,this.commands=new v(o.isMac?"mac":"win",m),this.textInput=new u(e.getTextAreaContainer(),this),this.renderer.textarea=this.textInput.getElement(),this.keyBinding=new l(this),this.$mouseHandler=new a(this),new f(this),this.$blockScrolling=0,this.$search=(new h).set({wrap:!0}),this.$historyTracker=this.$historyTracker.bind(this),this.commands.on("exec",this.$historyTracker),this.$initOperationListeners(),this._$emitInputEvent=s.delayedCall(function(){this._signal("input",{}),this.session.bgTokenizer&&this.session.bgTokenizer.scheduleStart()}.bind(this)),this.on("change",function(e,t){t._$emitInputEvent.schedule(31)}),this.setSession(t||new c("")),g.resetOptions(this),g._signal("editor",this)};(function(){r.implement(this,d),this.$initOperationListeners=function(){function e(e){return e[e.length-1]}this.selections=[],this.commands.on("exec",function(t){this.startOperation(t);var n=t.command;if(n.aceCommandGroup=="fileJump"){var r=this.prevOp;if(!r||r.command.aceCommandGroup!="fileJump")this.lastFileJumpPos=e(this.selections)}else this.lastFileJumpPos=null}.bind(this),!0),this.commands.on("afterExec",function(e){var t=e.command;t.aceCommandGroup=="fileJump"&&this.lastFileJumpPos&&!this.curOp.selectionChanged&&this.selection.fromJSON(this.lastFileJumpPos),this.endOperation(e)}.bind(this),!0),this.$opResetTimer=s.delayedCall(this.endOperation.bind(this)),this.on("change",function(){this.curOp||this.startOperation(),this.curOp.docChanged=!0}.bind(this),!0),this.on("changeSelection",function(){this.curOp||this.startOperation(),this.curOp.selectionChanged=!0}.bind(this),!0)},this.curOp=null,this.prevOp={},this.startOperation=function(e){if(this.curOp){if(!e||this.curOp.command)return;this.prevOp=this.curOp}e||(this.previousCommand=null,e={}),this.$opResetTimer.schedule(),this.curOp={command:e.command||{},args:e.args,scrollTop:this.renderer.scrollTop};var t=this.curOp.command;t&&t.scrollIntoView&&this.$blockScrolling++,this.selections.push(this.selection.toJSON())},this.endOperation=function(){if(this.curOp){var e=this.curOp.command;if(e&&e.scrollIntoView){this.$blockScrolling--;switch(e.scrollIntoView){case"center":this.renderer.scrollCursorIntoView(null,.5);break;case"animate":case"cursor":this.renderer.scrollCursorIntoView();break;case"selectionPart":var t=this.selection.getRange(),n=this.renderer.layerConfig;(t.start.row>=n.lastRow||t.end.row<=n.firstRow)&&this.renderer.scrollSelectionIntoView(this.selection.anchor,this.selection.lead);break;default:}e.scrollIntoView=="animate"&&this.renderer.animateScrolling(this.curOp.scrollTop)}this.prevOp=this.curOp,this.curOp=null}},this.$mergeableCommands=["backspace","del","insertstring"],this.$historyTracker=function(e){if(!this.$mergeUndoDeltas)return;var t=this.prevOp,n=this.$mergeableCommands,r=t.command&&e.command.name==t.command.name;if(e.command.name=="insertstring"){var i=e.args;this.mergeNextCommand===undefined&&(this.mergeNextCommand=!0),r=r&&this.mergeNextCommand&&(!/\s/.test(i)||/\s/.test(t.args)),this.mergeNextCommand=!0}else r=r&&n.indexOf(e.command.name)!==-1;this.$mergeUndoDeltas!="always"&&Date.now()-this.sequenceStartTime>2e3&&(r=!1),r?this.session.mergeUndoDeltas=!0:n.indexOf(e.command.name)!==-1&&(this.sequenceStartTime=Date.now())},this.setKeyboardHandler=function(e){if(!e)this.keyBinding.setKeyboardHandler(null);else if(typeof e=="string"){this.$keybindingId=e;var t=this;g.loadModule(["keybinding",e],function(n){t.$keybindingId==e&&t.keyBinding.setKeyboardHandler(n&&n.handler)})}else this.$keybindingId=null,this.keyBinding.setKeyboardHandler(e)},this.getKeyboardHandler=function(){return this.keyBinding.getKeyboardHandler()},this.setSession=function(e){if(this.session==e)return;var t=this.session;if(t){this.session.removeEventListener("change",this.$onDocumentChange),this.session.removeEventListener("changeMode",this.$onChangeMode),this.session.removeEventListener("tokenizerUpdate",this.$onTokenizerUpdate),this.session.removeEventListener("changeTabSize",this.$onChangeTabSize),this.session.removeEventListener("changeWrapLimit",this.$onChangeWrapLimit),this.session.removeEventListener("changeWrapMode",this.$onChangeWrapMode),this.session.removeEventListener("onChangeFold",this.$onChangeFold),this.session.removeEventListener("changeFrontMarker",this.$onChangeFrontMarker),this.session.removeEventListener("changeBackMarker",this.$onChangeBackMarker),this.session.removeEventListener("changeBreakpoint",this.$onChangeBreakpoint),this.session.removeEventListener("changeAnnotation",this.$onChangeAnnotation),this.session.removeEventListener("changeOverwrite",this.$onCursorChange),this.session.removeEventListener("changeScrollTop",this.$onScrollTopChange),this.session.removeEventListener("changeScrollLeft",this.$onScrollLeftChange);var n=this.session.getSelection();n.removeEventListener("changeCursor",this.$onCursorChange),n.removeEventListener("changeSelection",this.$onSelectionChange)}this.session=e,e&&(this.$onDocumentChange=this.onDocumentChange.bind(this),e.addEventListener("change",this.$onDocumentChange),this.renderer.setSession(e),this.$onChangeMode=this.onChangeMode.bind(this),e.addEventListener("changeMode",this.$onChangeMode),this.$onTokenizerUpdate=this.onTokenizerUpdate.bind(this),e.addEventListener("tokenizerUpdate",this.$onTokenizerUpdate),this.$onChangeTabSize=this.renderer.onChangeTabSize.bind(this.renderer),e.addEventListener("changeTabSize",this.$onChangeTabSize),this.$onChangeWrapLimit=this.onChangeWrapLimit.bind(this),e.addEventListener("changeWrapLimit",this.$onChangeWrapLimit),this.$onChangeWrapMode=this.onChangeWrapMode.bind(this),e.addEventListener("changeWrapMode",this.$onChangeWrapMode),this.$onChangeFold=this.onChangeFold.bind(this),e.addEventListener("changeFold",this.$onChangeFold),this.$onChangeFrontMarker=this.onChangeFrontMarker.bind(this),this.session.addEventListener("changeFrontMarker",this.$onChangeFrontMarker),this.$onChangeBackMarker=this.onChangeBackMarker.bind(this),this.session.addEventListener("changeBackMarker",this.$onChangeBackMarker),this.$onChangeBreakpoint=this.onChangeBreakpoint.bind(this),this.session.addEventListener("changeBreakpoint",this.$onChangeBreakpoint),this.$onChangeAnnotation=this.onChangeAnnotation.bind(this),this.session.addEventListener("changeAnnotation",this.$onChangeAnnotation),this.$onCursorChange=this.onCursorChange.bind(this),this.session.addEventListener("changeOverwrite",this.$onCursorChange),this.$onScrollTopChange=this.onScrollTopChange.bind(this),this.session.addEventListener("changeScrollTop",this.$onScrollTopChange),this.$onScrollLeftChange=this.onScrollLeftChange.bind(this),this.session.addEventListener("changeScrollLeft",this.$onScrollLeftChange),this.selection=e.getSelection(),this.selection.addEventListener("changeCursor",this.$onCursorChange),this.$onSelectionChange=this.onSelectionChange.bind(this),this.selection.addEventListener("changeSelection",this.$onSelectionChange),this.onChangeMode(),this.$blockScrolling+=1,this.onCursorChange(),this.$blockScrolling-=1,this.onScrollTopChange(),this.onScrollLeftChange(),this.onSelectionChange(),this.onChangeFrontMarker(),this.onChangeBackMarker(),this.onChangeBreakpoint(),this.onChangeAnnotation(),this.session.getUseWrapMode()&&this.renderer.adjustWrapLimit(),this.renderer.updateFull()),this._signal("changeSession",{session:e,oldSession:t}),t&&t._signal("changeEditor",{oldEditor:this}),e&&e._signal("changeEditor",{editor:this})},this.getSession=function(){return this.session},this.setValue=function(e,t){return this.session.doc.setValue(e),t?t==1?this.navigateFileEnd():t==-1&&this.navigateFileStart():this.selectAll(),e},this.getValue=function(){return this.session.getValue()},this.getSelection=function(){return this.selection},this.resize=function(e){this.renderer.onResize(e)},this.setTheme=function(e,t){this.renderer.setTheme(e,t)},this.getTheme=function(){return this.renderer.getTheme()},this.setStyle=function(e){this.renderer.setStyle(e)},this.unsetStyle=function(e){this.renderer.unsetStyle(e)},this.getFontSize=function(){return this.getOption("fontSize")||i.computedStyle(this.container,"fontSize")},this.setFontSize=function(e){this.setOption("fontSize",e)},this.$highlightBrackets=function(){this.session.$bracketHighlight&&(this.session.removeMarker(this.session.$bracketHighlight),this.session.$bracketHighlight=null);if(this.$highlightPending)return;var e=this;this.$highlightPending=!0,setTimeout(function(){e.$highlightPending=!1;var t=e.session.findMatchingBracket(e.getCursorPosition());if(t)var n=new p(t.row,t.column,t.row,t.column+1);else if(e.session.$mode.getMatching)var n=e.session.$mode.getMatching(e.session);n&&(e.session.$bracketHighlight=e.session.addMarker(n,"ace_bracket","text"))},50)},this.$highlightTags=function(){var e=this.session;if(this.$highlightTagPending)return;var t=this;this.$highlightTagPending=!0,setTimeout(function(){t.$highlightTagPending=!1;var n=t.getCursorPosition(),r=new y(t.session,n.row,n.column),i=r.getCurrentToken();if(!i||i.type.indexOf("tag-name")===-1){e.removeMarker(e.$tagHighlight),e.$tagHighlight=null;return}var s=i.value,o=0,u=r.stepBackward();if(u.value=="<"){do u=i,i=r.stepForward(),i&&i.value===s&&i.type.indexOf("tag-name")!==-1&&(u.value==="<"?o++:u.value==="</"&&o--);while(i&&o>=0)}else{do i=u,u=r.stepBackward(),i&&i.value===s&&i.type.indexOf("tag-name")!==-1&&(u.value==="<"?o++:u.value==="</"&&o--);while(u&&o<=0);r.stepForward()}if(!i){e.removeMarker(e.$tagHighlight),e.$tagHighlight=null;return}var a=r.getCurrentTokenRow(),f=r.getCurrentTokenColumn(),l=new p(a,f,a,f+i.value.length);e.$tagHighlight&&l.compareRange(e.$backMarkers[e.$tagHighlight].range)!==0&&(e.removeMarker(e.$tagHighlight),e.$tagHighlight=null),l&&!e.$tagHighlight&&(e.$tagHighlight=e.addMarker(l,"ace_bracket","text"))},50)},this.focus=function(){var e=this;setTimeout(function(){e.textInput.focus()}),this.textInput.focus()},this.isFocused=function(){return this.textInput.isFocused()},this.blur=function(){this.textInput.blur()},this.onFocus=function(){if(this.$isFocused)return;this.$isFocused=!0,this.renderer.showCursor(),this.renderer.visualizeFocus(),this._emit("focus")},this.onBlur=function(){if(!this.$isFocused)return;this.$isFocused=!1,this.renderer.hideCursor(),this.renderer.visualizeBlur(),this._emit("blur")},this.$cursorChange=function(){this.renderer.updateCursor()},this.onDocumentChange=function(e){var t=e.data,n=t.range,r;n.start.row==n.end.row&&t.action!="insertLines"&&t.action!="removeLines"?r=n.end.row:r=Infinity,this.renderer.updateLines(n.start.row,r),this._signal("change",e),this.$cursorChange()},this.onTokenizerUpdate=function(e){var t=e.data;this.renderer.updateLines(t.first,t.last)},this.onScrollTopChange=function(){this.renderer.scrollToY(this.session.getScrollTop())},this.onScrollLeftChange=function(){this.renderer.scrollToX(this.session.getScrollLeft())},this.onCursorChange=function(){this.$cursorChange(),this.$blockScrolling||this.renderer.scrollCursorIntoView(),this.$highlightBrackets(),this.$highlightTags(),this.$updateHighlightActiveLine(),this._signal("changeSelection")},this.$updateHighlightActiveLine=function(){var e=this.getSession(),t;if(this.$highlightActiveLine){if(this.$selectionStyle!="line"||!this.selection.isMultiLine())t=this.getCursorPosition();this.renderer.$maxLines&&this.session.getLength()===1&&!(this.renderer.$minLines>1)&&(t=!1)}if(e.$highlightLineMarker&&!t)e.removeMarker(e.$highlightLineMarker.id),e.$highlightLineMarker=null;else if(!e.$highlightLineMarker&&t){var n=new p(t.row,t.column,t.row,Infinity);n.id=e.addMarker(n,"ace_active-line","screenLine"),e.$highlightLineMarker=n}else t&&(e.$highlightLineMarker.start.row=t.row,e.$highlightLineMarker.end.row=t.row,e.$highlightLineMarker.start.column=t.column,e._signal("changeBackMarker"))},this.onSelectionChange=function(e){var t=this.session;t.$selectionMarker&&t.removeMarker(t.$selectionMarker),t.$selectionMarker=null;if(!this.selection.isEmpty()){var n=this.selection.getRange(),r=this.getSelectionStyle();t.$selectionMarker=t.addMarker(n,"ace_selection",r)}else this.$updateHighlightActiveLine();var i=this.$highlightSelectedWord&&this.$getSelectionHighLightRegexp();this.session.highlight(i),this._signal("changeSelection")},this.$getSelectionHighLightRegexp=function(){var e=this.session,t=this.getSelectionRange();if(t.isEmpty()||t.isMultiLine())return;var n=t.start.column-1,r=t.end.column+1,i=e.getLine(t.start.row),s=i.length,o=i.substring(Math.max(n,0),Math.min(r,s));if(n>=0&&/^[\w\d]/.test(o)||r<=s&&/[\w\d]$/.test(o))return;o=i.substring(t.start.column,t.end.column);if(!/^[\w\d]+$/.test(o))return;var u=this.$search.$assembleRegExp({wholeWord:!0,caseSensitive:!0,needle:o});return u},this.onChangeFrontMarker=function(){this.renderer.updateFrontMarkers()},this.onChangeBackMarker=function(){this.renderer.updateBackMarkers()},this.onChangeBreakpoint=function(){this.renderer.updateBreakpoints()},this.onChangeAnnotation=function(){this.renderer.setAnnotations(this.session.getAnnotations())},this.onChangeMode=function(e){this.renderer.updateText(),this._emit("changeMode",e)},this.onChangeWrapLimit=function(){this.renderer.updateFull()},this.onChangeWrapMode=function(){this.renderer.onResize(!0)},this.onChangeFold=function(){this.$updateHighlightActiveLine(),this.renderer.updateFull()},this.getSelectedText=function(){return this.session.getTextRange(this.getSelectionRange())},this.getCopyText=function(){var e=this.getSelectedText();return this._signal("copy",e),e},this.onCopy=function(){this.commands.exec("copy",this)},this.onCut=function(){this.commands.exec("cut",this)},this.onPaste=function(e){if(this.$readOnly)return;var t={text:e};this._signal("paste",t),this.insert(t.text,!0)},this.execCommand=function(e,t){this.commands.exec(e,this,t)},this.insert=function(e,t){var n=this.session,r=n.getMode(),i=this.getCursorPosition();if(this.getBehavioursEnabled()&&!t){var s=r.transformAction(n.getState(i.row),"insertion",this,n,e);s&&(e!==s.text&&(this.session.mergeUndoDeltas=!1,this.$mergeNextCommand=!1),e=s.text)}e=="	"&&(e=this.session.getTabString());if(!this.selection.isEmpty()){var o=this.getSelectionRange();i=this.session.remove(o),this.clearSelection()}else if(this.session.getOverwrite()){var o=new p.fromPoints(i,i);o.end.column+=e.length,this.session.remove(o)}if(e=="\n"||e=="\r\n"){var u=n.getLine(i.row);if(i.column>u.search(/\S|$/)){var a=u.substr(i.column).search(/\S|$/);n.doc.removeInLine(i.row,i.column,i.column+a)}}this.clearSelection();var f=i.column,l=n.getState(i.row),u=n.getLine(i.row),c=r.checkOutdent(l,u,e),h=n.insert(i,e);s&&s.selection&&(s.selection.length==2?this.selection.setSelectionRange(new p(i.row,f+s.selection[0],i.row,f+s.selection[1])):this.selection.setSelectionRange(new p(i.row+s.selection[0],s.selection[1],i.row+s.selection[2],s.selection[3])));if(n.getDocument().isNewLine(e)){var d=r.getNextLineIndent(l,u.slice(0,i.column),n.getTabString());n.insert({row:i.row+1,column:0},d)}c&&r.autoOutdent(l,n,i.row)},this.onTextInput=function(e){this.keyBinding.onTextInput(e)},this.onCommandKey=function(e,t,n){this.keyBinding.onCommandKey(e,t,n)},this.setOverwrite=function(e){this.session.setOverwrite(e)},this.getOverwrite=function(){return this.session.getOverwrite()},this.toggleOverwrite=function(){this.session.toggleOverwrite()},this.setScrollSpeed=function(e){this.setOption("scrollSpeed",e)},this.getScrollSpeed=function(){return this.getOption("scrollSpeed")},this.setDragDelay=function(e){this.setOption("dragDelay",e)},this.getDragDelay=function(){return this.getOption("dragDelay")},this.setSelectionStyle=function(e){this.setOption("selectionStyle",e)},this.getSelectionStyle=function(){return this.getOption("selectionStyle")},this.setHighlightActiveLine=function(e){this.setOption("highlightActiveLine",e)},this.getHighlightActiveLine=function(){return this.getOption("highlightActiveLine")},this.setHighlightGutterLine=function(e){this.setOption("highlightGutterLine",e)},this.getHighlightGutterLine=function(){return this.getOption("highlightGutterLine")},this.setHighlightSelectedWord=function(e){this.setOption("highlightSelectedWord",e)},this.getHighlightSelectedWord=function(){return this.$highlightSelectedWord},this.setAnimatedScroll=function(e){this.renderer.setAnimatedScroll(e)},this.getAnimatedScroll=function(){return this.renderer.getAnimatedScroll()},this.setShowInvisibles=function(e){this.renderer.setShowInvisibles(e)},this.getShowInvisibles=function(){return this.renderer.getShowInvisibles()},this.setDisplayIndentGuides=function(e){this.renderer.setDisplayIndentGuides(e)},this.getDisplayIndentGuides=function(){return this.renderer.getDisplayIndentGuides()},this.setShowPrintMargin=function(e){this.renderer.setShowPrintMargin(e)},this.getShowPrintMargin=function(){return this.renderer.getShowPrintMargin()},this.setPrintMarginColumn=function(e){this.renderer.setPrintMarginColumn(e)},this.getPrintMarginColumn=function(){return this.renderer.getPrintMarginColumn()},this.setReadOnly=function(e){this.setOption("readOnly",e)},this.getReadOnly=function(){return this.getOption("readOnly")},this.setBehavioursEnabled=function(e){this.setOption("behavioursEnabled",e)},this.getBehavioursEnabled=function(){return this.getOption("behavioursEnabled")},this.setWrapBehavioursEnabled=function(e){this.setOption("wrapBehavioursEnabled",e)},this.getWrapBehavioursEnabled=function(){return this.getOption("wrapBehavioursEnabled")},this.setShowFoldWidgets=function(e){this.setOption("showFoldWidgets",e)},this.getShowFoldWidgets=function(){return this.getOption("showFoldWidgets")},this.setFadeFoldWidgets=function(e){this.setOption("fadeFoldWidgets",e)},this.getFadeFoldWidgets=function(){return this.getOption("fadeFoldWidgets")},this.remove=function(e){this.selection.isEmpty()&&(e=="left"?this.selection.selectLeft():this.selection.selectRight());var t=this.getSelectionRange();if(this.getBehavioursEnabled()){var n=this.session,r=n.getState(t.start.row),i=n.getMode().transformAction(r,"deletion",this,n,t);if(t.end.column===0){var s=n.getTextRange(t);if(s[s.length-1]=="\n"){var o=n.getLine(t.end.row);/^\s+$/.test(o)&&(t.end.column=o.length)}}i&&(t=i)}this.session.remove(t),this.clearSelection()},this.removeWordRight=function(){this.selection.isEmpty()&&this.selection.selectWordRight(),this.session.remove(this.getSelectionRange()),this.clearSelection()},this.removeWordLeft=function(){this.selection.isEmpty()&&this.selection.selectWordLeft(),this.session.remove(this.getSelectionRange()),this.clearSelection()},this.removeToLineStart=function(){this.selection.isEmpty()&&this.selection.selectLineStart(),this.session.remove(this.getSelectionRange()),this.clearSelection()},this.removeToLineEnd=function(){this.selection.isEmpty()&&this.selection.selectLineEnd();var e=this.getSelectionRange();e.start.column==e.end.column&&e.start.row==e.end.row&&(e.end.column=0,e.end.row++),this.session.remove(e),this.clearSelection()},this.splitLine=function(){this.selection.isEmpty()||(this.session.remove(this.getSelectionRange()),this.clearSelection());var e=this.getCursorPosition();this.insert("\n"),this.moveCursorToPosition(e)},this.transposeLetters=function(){if(!this.selection.isEmpty())return;var e=this.getCursorPosition(),t=e.column;if(t===0)return;var n=this.session.getLine(e.row),r,i;t<n.length?(r=n.charAt(t)+n.charAt(t-1),i=new p(e.row,t-1,e.row,t+1)):(r=n.charAt(t-1)+n.charAt(t-2),i=new p(e.row,t-2,e.row,t)),this.session.replace(i,r)},this.toLowerCase=function(){var e=this.getSelectionRange();this.selection.isEmpty()&&this.selection.selectWord();var t=this.getSelectionRange(),n=this.session.getTextRange(t);this.session.replace(t,n.toLowerCase()),this.selection.setSelectionRange(e)},this.toUpperCase=function(){var e=this.getSelectionRange();this.selection.isEmpty()&&this.selection.selectWord();var t=this.getSelectionRange(),n=this.session.getTextRange(t);this.session.replace(t,n.toUpperCase()),this.selection.setSelectionRange(e)},this.indent=function(){var e=this.session,t=this.getSelectionRange();if(t.start.row<t.end.row){var n=this.$getSelectedRows();e.indentRows(n.first,n.last,"	");return}if(t.start.column<t.end.column){var r=e.getTextRange(t);if(!/^\s+$/.test(r)){var n=this.$getSelectedRows();e.indentRows(n.first,n.last,"	");return}}var i=e.getLine(t.start.row),o=t.start,u=e.getTabSize(),a=e.documentToScreenColumn(o.row,o.column);if(this.session.getUseSoftTabs())var f=u-a%u,l=s.stringRepeat(" ",f);else{var f=a%u;while(i[t.start.column]==" "&&f)t.start.column--,f--;this.selection.setSelectionRange(t),l="	"}return this.insert(l)},this.blockIndent=function(){var e=this.$getSelectedRows();this.session.indentRows(e.first,e.last,"	")},this.blockOutdent=function(){var e=this.session.getSelection();this.session.outdentRows(e.getRange())},this.sortLines=function(){var e=this.$getSelectedRows(),t=this.session,n=[];for(i=e.first;i<=e.last;i++)n.push(t.getLine(i));n.sort(function(e,t){return e.toLowerCase()<t.toLowerCase()?-1:e.toLowerCase()>t.toLowerCase()?1:0});var r=new p(0,0,0,0);for(var i=e.first;i<=e.last;i++){var s=t.getLine(i);r.start.row=i,r.end.row=i,r.end.column=s.length,t.replace(r,n[i-e.first])}},this.toggleCommentLines=function(){var e=this.session.getState(this.getCursorPosition().row),t=this.$getSelectedRows();this.session.getMode().toggleCommentLines(e,this.session,t.first,t.last)},this.toggleBlockComment=function(){var e=this.getCursorPosition(),t=this.session.getState(e.row),n=this.getSelectionRange();this.session.getMode().toggleBlockComment(t,this.session,n,e)},this.getNumberAt=function(e,t){var n=/[\-]?[0-9]+(?:\.[0-9]+)?/g;n.lastIndex=0;var r=this.session.getLine(e);while(n.lastIndex<t){var i=n.exec(r);if(i.index<=t&&i.index+i[0].length>=t){var s={value:i[0],start:i.index,end:i.index+i[0].length};return s}}return null},this.modifyNumber=function(e){var t=this.selection.getCursor().row,n=this.selection.getCursor().column,r=new p(t,n-1,t,n),i=this.session.getTextRange(r);if(!isNaN(parseFloat(i))&&isFinite(i)){var s=this.getNumberAt(t,n);if(s){var o=s.value.indexOf(".")>=0?s.start+s.value.indexOf(".")+1:s.end,u=s.start+s.value.length-o,a=parseFloat(s.value);a*=Math.pow(10,u),o!==s.end&&n<o?e*=Math.pow(10,s.end-n-1):e*=Math.pow(10,s.end-n),a+=e,a/=Math.pow(10,u);var f=a.toFixed(u),l=new p(t,s.start,t,s.end);this.session.replace(l,f),this.moveCursorTo(t,Math.max(s.start+1,n+f.length-s.value.length))}}},this.removeLines=function(){var e=this.$getSelectedRows(),t;e.first===0||e.last+1<this.session.getLength()?t=new p(e.first,0,e.last+1,0):t=new p(e.first-1,this.session.getLine(e.first-1).length,e.last,this.session.getLine(e.last).length),this.session.remove(t),this.clearSelection()},this.duplicateSelection=function(){var e=this.selection,t=this.session,n=e.getRange(),r=e.isBackwards();if(n.isEmpty()){var i=n.start.row;t.duplicateLines(i,i)}else{var s=r?n.start:n.end,o=t.insert(s,t.getTextRange(n),!1);n.start=s,n.end=o,e.setSelectionRange(n,r)}},this.moveLinesDown=function(){this.$moveLines(function(e,t){return this.session.moveLinesDown(e,t)})},this.moveLinesUp=function(){this.$moveLines(function(e,t){return this.session.moveLinesUp(e,t)})},this.moveText=function(e,t,n){return this.session.moveText(e,t,n)},this.copyLinesUp=function(){this.$moveLines(function(e,t){return this.session.duplicateLines(e,t),0})},this.copyLinesDown=function(){this.$moveLines(function(e,t){return this.session.duplicateLines(e,t)})},this.$moveLines=function(e){var t=this.selection;if(!t.inMultiSelectMode||this.inVirtualSelectionMode){var n=t.toOrientedRange(),r=this.$getSelectedRows(n),i=e.call(this,r.first,r.last);n.moveBy(i,0),t.fromOrientedRange(n)}else{var s=t.rangeList.ranges;t.rangeList.detach(this.session);for(var o=s.length;o--;){var u=o,r=s[o].collapseRows(),a=r.end.row,f=r.start.row;while(o--){r=s[o].collapseRows();if(!(f-r.end.row<=1))break;f=r.end.row}o++;var i=e.call(this,f,a);while(u>=o)s[u].moveBy(i,0),u--}t.fromOrientedRange(t.ranges[0]),t.rangeList.attach(this.session)}},this.$getSelectedRows=function(){var e=this.getSelectionRange().collapseRows();return{first:this.session.getRowFoldStart(e.start.row),last:this.session.getRowFoldEnd(e.end.row)}},this.onCompositionStart=function(e){this.renderer.showComposition(this.getCursorPosition())},this.onCompositionUpdate=function(e){this.renderer.setCompositionText(e)},this.onCompositionEnd=function(){this.renderer.hideComposition()},this.getFirstVisibleRow=function(){return this.renderer.getFirstVisibleRow()},this.getLastVisibleRow=function(){return this.renderer.getLastVisibleRow()},this.isRowVisible=function(e){return e>=this.getFirstVisibleRow()&&e<=this.getLastVisibleRow()},this.isRowFullyVisible=function(e){return e>=this.renderer.getFirstFullyVisibleRow()&&e<=this.renderer.getLastFullyVisibleRow()},this.$getVisibleRowCount=function(){return this.renderer.getScrollBottomRow()-this.renderer.getScrollTopRow()+1},this.$moveByPage=function(e,t){var n=this.renderer,r=this.renderer.layerConfig,i=e*Math.floor(r.height/r.lineHeight);this.$blockScrolling++,t===!0?this.selection.$moveSelection(function(){this.moveCursorBy(i,0)}):t===!1&&(this.selection.moveCursorBy(i,0),this.selection.clearSelection()),this.$blockScrolling--;var s=n.scrollTop;n.scrollBy(0,i*r.lineHeight),t!=null&&n.scrollCursorIntoView(null,.5),n.animateScrolling(s)},this.selectPageDown=function(){this.$moveByPage(1,!0)},this.selectPageUp=function(){this.$moveByPage(-1,!0)},this.gotoPageDown=function(){this.$moveByPage(1,!1)},this.gotoPageUp=function(){this.$moveByPage(-1,!1)},this.scrollPageDown=function(){this.$moveByPage(1)},this.scrollPageUp=function(){this.$moveByPage(-1)},this.scrollToRow=function(e){this.renderer.scrollToRow(e)},this.scrollToLine=function(e,t,n,r){this.renderer.scrollToLine(e,t,n,r)},this.centerSelection=function(){var e=this.getSelectionRange(),t={row:Math.floor(e.start.row+(e.end.row-e.start.row)/2),column:Math.floor(e.start.column+(e.end.column-e.start.column)/2)};this.renderer.alignCursor(t,.5)},this.getCursorPosition=function(){return this.selection.getCursor()},this.getCursorPositionScreen=function(){return this.session.documentToScreenPosition(this.getCursorPosition())},this.getSelectionRange=function(){return this.selection.getRange()},this.selectAll=function(){this.$blockScrolling+=1,this.selection.selectAll(),this.$blockScrolling-=1},this.clearSelection=function(){this.selection.clearSelection()},this.moveCursorTo=function(e,t){this.selection.moveCursorTo(e,t)},this.moveCursorToPosition=function(e){this.selection.moveCursorToPosition(e)},this.jumpToMatching=function(e){var t=this.getCursorPosition(),n=new y(this.session,t.row,t.column),r=n.getCurrentToken(),i=r;i||(i=n.stepForward());if(!i)return;var s,o=!1,u={},a=t.column-i.start,f,l={")":"(","(":"(","]":"[","[":"[","{":"{","}":"{"};do{if(i.value.match(/[{}()\[\]]/g))for(;a<i.value.length&&!o;a++){if(!l[i.value[a]])continue;f=l[i.value[a]]+"."+i.type.replace("rparen","lparen"),isNaN(u[f])&&(u[f]=0);switch(i.value[a]){case"(":case"[":case"{":u[f]++;break;case")":case"]":case"}":u[f]--,u[f]===-1&&(s="bracket",o=!0)}}else i&&i.type.indexOf("tag-name")!==-1&&(isNaN(u[i.value])&&(u[i.value]=0),r.value==="<"?u[i.value]++:r.value==="</"&&u[i.value]--,u[i.value]===-1&&(s="tag",o=!0));o||(r=i,i=n.stepForward(),a=0)}while(i&&!o);if(!s)return;var c;if(s==="bracket"){c=this.session.getBracketRange(t);if(!c){c=new p(n.getCurrentTokenRow(),n.getCurrentTokenColumn()+a-1,n.getCurrentTokenRow(),n.getCurrentTokenColumn()+a-1);if(!c)return;var h=c.start;h.row===t.row&&Math.abs(h.column-t.column)<2&&(c=this.session.getBracketRange(h))}}else if(s==="tag"){if(!i||i.type.indexOf("tag-name")===-1)return;var d=i.value,c=new p(n.getCurrentTokenRow(),n.getCurrentTokenColumn()-2,n.getCurrentTokenRow(),n.getCurrentTokenColumn()-2);if(c.compare(t.row,t.column)===0){o=!1;do i=r,r=n.stepBackward(),r&&(r.type.indexOf("tag-close")!==-1&&c.setEnd(n.getCurrentTokenRow(),n.getCurrentTokenColumn()+1),i.value===d&&i.type.indexOf("tag-name")!==-1&&(r.value==="<"?u[d]++:r.value==="</"&&u[d]--,u[d]===0&&(o=!0)));while(r&&!o)}if(i&&i.type.indexOf("tag-name")){var h=c.start;h.row==t.row&&Math.abs(h.column-t.column)<2&&(h=c.end)}}h=c&&c.cursor||h,h&&(e?c&&c.isEqual(this.getSelectionRange())?this.clearSelection():this.selection.selectTo(h.row,h.column):this.selection.moveTo(h.row,h.column))},this.gotoLine=function(e,t,n){this.selection.clearSelection(),this.session.unfold({row:e-1,column:t||0}),this.$blockScrolling+=1,this.exitMultiSelectMode&&this.exitMultiSelectMode(),this.moveCursorTo(e-1,t||0),this.$blockScrolling-=1,this.isRowFullyVisible(e-1)||this.scrollToLine(e-1,!0,n)},this.navigateTo=function(e,t){this.selection.moveTo(e,t)},this.navigateUp=function(e){if(this.selection.isMultiLine()&&!this.selection.isBackwards()){var t=this.selection.anchor.getPosition();return this.moveCursorToPosition(t)}this.selection.clearSelection(),this.selection.moveCursorBy(-e||-1,0)},this.navigateDown=function(e){if(this.selection.isMultiLine()&&this.selection.isBackwards()){var t=this.selection.anchor.getPosition();return this.moveCursorToPosition(t)}this.selection.clearSelection(),this.selection.moveCursorBy(e||1,0)},this.navigateLeft=function(e){if(!this.selection.isEmpty()){var t=this.getSelectionRange().start;this.moveCursorToPosition(t)}else{e=e||1;while(e--)this.selection.moveCursorLeft()}this.clearSelection()},this.navigateRight=function(e){if(!this.selection.isEmpty()){var t=this.getSelectionRange().end;this.moveCursorToPosition(t)}else{e=e||1;while(e--)this.selection.moveCursorRight()}this.clearSelection()},this.navigateLineStart=function(){this.selection.moveCursorLineStart(),this.clearSelection()},this.navigateLineEnd=function(){this.selection.moveCursorLineEnd(),this.clearSelection()},this.navigateFileEnd=function(){this.selection.moveCursorFileEnd(),this.clearSelection()},this.navigateFileStart=function(){this.selection.moveCursorFileStart(),this.clearSelection()},this.navigateWordRight=function(){this.selection.moveCursorWordRight(),this.clearSelection()},this.navigateWordLeft=function(){this.selection.moveCursorWordLeft(),this.clearSelection()},this.replace=function(e,t){t&&this.$search.set(t);var n=this.$search.find(this.session),r=0;return n?(this.$tryReplace(n,e)&&(r=1),n!==null&&(this.selection.setSelectionRange(n),this.renderer.scrollSelectionIntoView(n.start,n.end)),r):r},this.replaceAll=function(e,t){t&&this.$search.set(t);var n=this.$search.findAll(this.session),r=0;if(!n.length)return r;this.$blockScrolling+=1;var i=this.getSelectionRange();this.selection.moveTo(0,0);for(var s=n.length-1;s>=0;--s)this.$tryReplace(n[s],e)&&r++;return this.selection.setSelectionRange(i),this.$blockScrolling-=1,r},this.$tryReplace=function(e,t){var n=this.session.getTextRange(e);return t=this.$search.replace(n,t),t!==null?(e.end=this.session.replace(e,t),e):null},this.getLastSearchOptions=function(){return this.$search.getOptions()},this.find=function(e,t,n){t||(t={}),typeof e=="string"||e instanceof RegExp?t.needle=e:typeof e=="object"&&r.mixin(t,e);var i=this.selection.getRange();t.needle==null&&(e=this.session.getTextRange(i)||this.$search.$options.needle,e||(i=this.session.getWordRange(i.start.row,i.start.column),e=this.session.getTextRange(i)),this.$search.set({needle:e})),this.$search.set(t),t.start||this.$search.set({start:i});var s=this.$search.find(this.session);if(t.preventScroll)return s;if(s)return this.revealRange(s,n),s;t.backwards?i.start=i.end:i.end=i.start,this.selection.setRange(i)},this.findNext=function(e,t){this.find({skipCurrent:!0,backwards:!1},e,t)},this.findPrevious=function(e,t){this.find(e,{skipCurrent:!0,backwards:!0},t)},this.revealRange=function(e,t){this.$blockScrolling+=1,this.session.unfold(e),this.selection.setSelectionRange(e),this.$blockScrolling-=1;var n=this.renderer.scrollTop;this.renderer.scrollSelectionIntoView(e.start,e.end,.5),t!==!1&&this.renderer.animateScrolling(n)},this.undo=function(){this.$blockScrolling++,this.session.getUndoManager().undo(),this.$blockScrolling--,this.renderer.scrollCursorIntoView(null,.5)},this.redo=function(){this.$blockScrolling++,this.session.getUndoManager().redo(),this.$blockScrolling--,this.renderer.scrollCursorIntoView(null,.5)},this.destroy=function(){this.renderer.destroy(),this._signal("destroy",this)},this.setAutoScrollEditorIntoView=function(e){if(!e)return;var t,n=this,r=!1;this.$scrollAnchor||(this.$scrollAnchor=document.createElement("div"));var i=this.$scrollAnchor;i.style.cssText="position:absolute",this.container.insertBefore(i,this.container.firstChild);var s=this.on("changeSelection",function(){r=!0}),o=this.renderer.on("beforeRender",function(){r&&(t=n.renderer.container.getBoundingClientRect())}),u=this.renderer.on("afterRender",function(){if(r&&t&&n.isFocused()){var e=n.renderer,s=e.$cursorLayer.$pixelPos,o=e.layerConfig,u=s.top-o.offset;s.top>=0&&u+t.top<0?r=!0:s.top<o.height&&s.top+t.top+o.lineHeight>window.innerHeight?r=!1:r=null,r!=null&&(i.style.top=u+"px",i.style.left=s.left+"px",i.style.height=o.lineHeight+"px",i.scrollIntoView(r)),r=t=null}});this.setAutoScrollEditorIntoView=function(e){if(e)return;delete this.setAutoScrollEditorIntoView,this.removeEventListener("changeSelection",s),this.renderer.removeEventListener("afterRender",u),this.renderer.removeEventListener("beforeRender",o)}},this.$resetCursorStyle=function(){var e=this.$cursorStyle||"ace",t=this.renderer.$cursorLayer;if(!t)return;t.setSmoothBlinking(/smooth/.test(e)),t.isBlinking=!this.$readOnly&&e!="wide",i.setCssClass(t.element,"ace_slim-cursors",/slim/.test(e))}}).call(b.prototype),g.defineOptions(b.prototype,"editor",{selectionStyle:{set:function(e){this.onSelectionChange(),this._signal("changeSelectionStyle",{data:e})},initialValue:"line"},highlightActiveLine:{set:function(){this.$updateHighlightActiveLine()},initialValue:!0},highlightSelectedWord:{set:function(e){this.$onSelectionChange()},initialValue:!0},readOnly:{set:function(e){this.$resetCursorStyle()},initialValue:!1},cursorStyle:{set:function(e){this.$resetCursorStyle()},values:["ace","slim","smooth","wide"],initialValue:"ace"},mergeUndoDeltas:{values:[!1,!0,"always"],initialValue:!0},behavioursEnabled:{initialValue:!0},wrapBehavioursEnabled:{initialValue:!0},autoScrollEditorIntoView:{set:function(e){this.setAutoScrollEditorIntoView(e)}},hScrollBarAlwaysVisible:"renderer",vScrollBarAlwaysVisible:"renderer",highlightGutterLine:"renderer",animatedScroll:"renderer",showInvisibles:"renderer",showPrintMargin:"renderer",printMarginColumn:"renderer",printMargin:"renderer",fadeFoldWidgets:"renderer",showFoldWidgets:"renderer",showLineNumbers:"renderer",showGutter:"renderer",displayIndentGuides:"renderer",fontSize:"renderer",fontFamily:"renderer",maxLines:"renderer",minLines:"renderer",scrollPastEnd:"renderer",fixedWidthGutter:"renderer",theme:"renderer",scrollSpeed:"$mouseHandler",dragDelay:"$mouseHandler",dragEnabled:"$mouseHandler",focusTimout:"$mouseHandler",tooltipFollowsMouse:"$mouseHandler",firstLineNumber:"session",overwrite:"session",newLineMode:"session",useWorker:"session",useSoftTabs:"session",tabSize:"session",wrap:"session",foldStyle:"session",mode:"session"}),t.Editor=b}),ace.define("ace/undomanager",["require","exports","module"],function(e,t,n){"use strict";var r=function(){this.reset()};(function(){this.execute=function(e){var t=e.args[0];this.$doc=e.args[1],e.merge&&this.hasUndo()&&(this.dirtyCounter--,t=this.$undoStack.pop().concat(t)),this.$undoStack.push(t),this.$redoStack=[],this.dirtyCounter<0&&(this.dirtyCounter=NaN),this.dirtyCounter++},this.undo=function(e){var t=this.$undoStack.pop(),n=null;return t&&(n=this.$doc.undoChanges(t,e),this.$redoStack.push(t),this.dirtyCounter--),n},this.redo=function(e){var t=this.$redoStack.pop(),n=null;return t&&(n=this.$doc.redoChanges(t,e),this.$undoStack.push(t),this.dirtyCounter++),n},this.reset=function(){this.$undoStack=[],this.$redoStack=[],this.dirtyCounter=0},this.hasUndo=function(){return this.$undoStack.length>0},this.hasRedo=function(){return this.$redoStack.length>0},this.markClean=function(){this.dirtyCounter=0},this.isClean=function(){return this.dirtyCounter===0}}).call(r.prototype),t.UndoManager=r}),ace.define("ace/layer/gutter",["require","exports","module","ace/lib/dom","ace/lib/oop","ace/lib/lang","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("../lib/dom"),i=e("../lib/oop"),s=e("../lib/lang"),o=e("../lib/event_emitter").EventEmitter,u=function(e){this.element=r.createElement("div"),this.element.className="ace_layer ace_gutter-layer",e.appendChild(this.element),this.setShowFoldWidgets(this.$showFoldWidgets),this.gutterWidth=0,this.$annotations=[],this.$updateAnnotations=this.$updateAnnotations.bind(this),this.$cells=[]};(function(){i.implement(this,o),this.setSession=function(e){this.session&&this.session.removeEventListener("change",this.$updateAnnotations),this.session=e,e.on("change",this.$updateAnnotations)},this.addGutterDecoration=function(e,t){window.console&&console.warn&&console.warn("deprecated use session.addGutterDecoration"),this.session.addGutterDecoration(e,t)},this.removeGutterDecoration=function(e,t){window.console&&console.warn&&console.warn("deprecated use session.removeGutterDecoration"),this.session.removeGutterDecoration(e,t)},this.setAnnotations=function(e){this.$annotations=[];for(var t=0;t<e.length;t++){var n=e[t],r=n.row,i=this.$annotations[r];i||(i=this.$annotations[r]={text:[]});var o=n.text;o=o?s.escapeHTML(o):n.html||"",i.text.indexOf(o)===-1&&i.text.push(o);var u=n.type;u=="error"?i.className=" ace_error":u=="warning"&&i.className!=" ace_error"?i.className=" ace_warning":u=="info"&&!i.className&&(i.className=" ace_info")}},this.$updateAnnotations=function(e){if(!this.$annotations.length)return;var t=e.data,n=t.range,r=n.start.row,i=n.end.row-r;if(i!==0)if(t.action=="removeText"||t.action=="removeLines")this.$annotations.splice(r,i+1,null);else{var s=new Array(i+1);s.unshift(r,1),this.$annotations.splice.apply(this.$annotations,s)}},this.update=function(e){var t=this.session,n=e.firstRow,i=Math.min(e.lastRow+e.gutterOffset,t.getLength()-1),s=t.getNextFoldLine(n),o=s?s.start.row:Infinity,u=this.$showFoldWidgets&&t.foldWidgets,a=t.$breakpoints,f=t.$decorations,l=t.$firstLineNumber,c=0,h=t.gutterRenderer||this.$renderer,p=null,d=-1,v=n;for(;;){v>o&&(v=s.end.row+1,s=t.getNextFoldLine(v,s),o=s?s.start.row:Infinity);if(v>i){while(this.$cells.length>d+1)p=this.$cells.pop(),this.element.removeChild(p.element);break}p=this.$cells[++d],p||(p={element:null,textNode:null,foldWidget:null},p.element=r.createElement("div"),p.textNode=document.createTextNode(""),p.element.appendChild(p.textNode),this.element.appendChild(p.element),this.$cells[d]=p);var m="ace_gutter-cell ";a[v]&&(m+=a[v]),f[v]&&(m+=f[v]),this.$annotations[v]&&(m+=this.$annotations[v].className),p.element.className!=m&&(p.element.className=m);var g=t.getRowLength(v)*e.lineHeight+"px";g!=p.element.style.height&&(p.element.style.height=g);if(u){var y=u[v];y==null&&(y=u[v]=t.getFoldWidget(v))}if(y){p.foldWidget||(p.foldWidget=r.createElement("span"),p.element.appendChild(p.foldWidget));var m="ace_fold-widget ace_"+y;y=="start"&&v==o&&v<s.end.row?m+=" ace_closed":m+=" ace_open",p.foldWidget.className!=m&&(p.foldWidget.className=m);var g=e.lineHeight+"px";p.foldWidget.style.height!=g&&(p.foldWidget.style.height=g)}else p.foldWidget&&(p.element.removeChild(p.foldWidget),p.foldWidget=null);var b=c=h?h.getText(t,v):v+l;b!=p.textNode.data&&(p.textNode.data=b),v++}this.element.style.height=e.minHeight+"px";if(this.$fixedWidth||t.$useWrapMode)c=t.getLength()+l;var w=h?h.getWidth(t,c,e):c.toString().length*e.characterWidth,E=this.$padding||this.$computePadding();w+=E.left+E.right,w!==this.gutterWidth&&!isNaN(w)&&(this.gutterWidth=w,this.element.style.width=Math.ceil(this.gutterWidth)+"px",this._emit("changeGutterWidth",w))},this.$fixedWidth=!1,this.$showLineNumbers=!0,this.$renderer="",this.setShowLineNumbers=function(e){this.$renderer=!e&&{getWidth:function(){return""},getText:function(){return""}}},this.getShowLineNumbers=function(){return this.$showLineNumbers},this.$showFoldWidgets=!0,this.setShowFoldWidgets=function(e){e?r.addCssClass(this.element,"ace_folding-enabled"):r.removeCssClass(this.element,"ace_folding-enabled"),this.$showFoldWidgets=e,this.$padding=null},this.getShowFoldWidgets=function(){return this.$showFoldWidgets},this.$computePadding=function(){if(!this.element.firstChild)return{left:0,right:0};var e=r.computedStyle(this.element.firstChild);return this.$padding={},this.$padding.left=parseInt(e.paddingLeft)+1||0,this.$padding.right=parseInt(e.paddingRight)||0,this.$padding},this.getRegion=function(e){var t=this.$padding||this.$computePadding(),n=this.element.getBoundingClientRect();if(e.x<t.left+n.left)return"markers";if(this.$showFoldWidgets&&e.x>n.right-t.right)return"foldWidgets"}}).call(u.prototype),t.Gutter=u}),ace.define("ace/layer/marker",["require","exports","module","ace/range","ace/lib/dom"],function(e,t,n){"use strict";var r=e("../range").Range,i=e("../lib/dom"),s=function(e){this.element=i.createElement("div"),this.element.className="ace_layer ace_marker-layer",e.appendChild(this.element)};(function(){this.$padding=0,this.setPadding=function(e){this.$padding=e},this.setSession=function(e){this.session=e},this.setMarkers=function(e){this.markers=e},this.update=function(e){var e=e||this.config;if(!e)return;this.config=e;var t=[];for(var n in this.markers){var r=this.markers[n];if(!r.range){r.update(t,this,this.session,e);continue}var i=r.range.clipRows(e.firstRow,e.lastRow);if(i.isEmpty())continue;i=i.toScreenRange(this.session);if(r.renderer){var s=this.$getTop(i.start.row,e),o=this.$padding+i.start.column*e.characterWidth;r.renderer(t,i,o,s,e)}else r.type=="fullLine"?this.drawFullLineMarker(t,i,r.clazz,e):r.type=="screenLine"?this.drawScreenLineMarker(t,i,r.clazz,e):i.isMultiLine()?r.type=="text"?this.drawTextMarker(t,i,r.clazz,e):this.drawMultiLineMarker(t,i,r.clazz,e):this.drawSingleLineMarker(t,i,r.clazz+" ace_start",e)}this.element.innerHTML=t.join("")},this.$getTop=function(e,t){return(e-t.firstRowScreen)*t.lineHeight},this.drawTextMarker=function(e,t,n,i,s){var o=t.start.row,u=new r(o,t.start.column,o,this.session.getScreenLastRowColumn(o));this.drawSingleLineMarker(e,u,n+" ace_start",i,1,s),o=t.end.row,u=new r(o,0,o,t.end.column),this.drawSingleLineMarker(e,u,n,i,0,s);for(o=t.start.row+1;o<t.end.row;o++)u.start.row=o,u.end.row=o,u.end.column=this.session.getScreenLastRowColumn(o),this.drawSingleLineMarker(e,u,n,i,1,s)},this.drawMultiLineMarker=function(e,t,n,r,i){var s=this.$padding,o=r.lineHeight,u=this.$getTop(t.start.row,r),a=s+t.start.column*r.characterWidth;i=i||"",e.push("<div class='",n," ace_start' style='","height:",o,"px;","right:0;","top:",u,"px;","left:",a,"px;",i,"'></div>"),u=this.$getTop(t.end.row,r);var f=t.end.column*r.characterWidth;e.push("<div class='",n,"' style='","height:",o,"px;","width:",f,"px;","top:",u,"px;","left:",s,"px;",i,"'></div>"),o=(t.end.row-t.start.row-1)*r.lineHeight;if(o<0)return;u=this.$getTop(t.start.row+1,r),e.push("<div class='",n,"' style='","height:",o,"px;","right:0;","top:",u,"px;","left:",s,"px;",i,"'></div>")},this.drawSingleLineMarker=function(e,t,n,r,i,s){var o=r.lineHeight,u=(t.end.column+(i||0)-t.start.column)*r.characterWidth,a=this.$getTop(t.start.row,r),f=this.$padding+t.start.column*r.characterWidth;e.push("<div class='",n,"' style='","height:",o,"px;","width:",u,"px;","top:",a,"px;","left:",f,"px;",s||"","'></div>")},this.drawFullLineMarker=function(e,t,n,r,i){var s=this.$getTop(t.start.row,r),o=r.lineHeight;t.start.row!=t.end.row&&(o+=this.$getTop(t.end.row,r)-s),e.push("<div class='",n,"' style='","height:",o,"px;","top:",s,"px;","left:0;right:0;",i||"","'></div>")},this.drawScreenLineMarker=function(e,t,n,r,i){var s=this.$getTop(t.start.row,r),o=r.lineHeight;e.push("<div class='",n,"' style='","height:",o,"px;","top:",s,"px;","left:0;right:0;",i||"","'></div>")}}).call(s.prototype),t.Marker=s}),ace.define("ace/layer/text",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/lang","ace/lib/useragent","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/dom"),s=e("../lib/lang"),o=e("../lib/useragent"),u=e("../lib/event_emitter").EventEmitter,a=function(e){this.element=i.createElement("div"),this.element.className="ace_layer ace_text-layer",e.appendChild(this.element),this.$updateEolChar=this.$updateEolChar.bind(this)};(function(){r.implement(this,u),this.EOF_CHAR="\u00b6",this.EOL_CHAR_LF="\u00ac",this.EOL_CHAR_CRLF="\u00a4",this.EOL_CHAR=this.EOL_CHAR_LF,this.TAB_CHAR="\u2192",this.SPACE_CHAR="\u00b7",this.$padding=0,this.$updateEolChar=function(){var e=this.session.doc.getNewLineCharacter()=="\n"?this.EOL_CHAR_LF:this.EOL_CHAR_CRLF;if(this.EOL_CHAR!=e)return this.EOL_CHAR=e,!0},this.setPadding=function(e){this.$padding=e,this.element.style.padding="0 "+e+"px"},this.getLineHeight=function(){return this.$fontMetrics.$characterSize.height||0},this.getCharacterWidth=function(){return this.$fontMetrics.$characterSize.width||0},this.$setFontMetrics=function(e){this.$fontMetrics=e,this.$fontMetrics.on("changeCharacterSize",function(e){this._signal("changeCharacterSize",e)}.bind(this)),this.$pollSizeChanges()},this.checkForSizeChanges=function(){this.$fontMetrics.checkForSizeChanges()},this.$pollSizeChanges=function(){return this.$pollSizeChangesTimer=this.$fontMetrics.$pollSizeChanges()},this.setSession=function(e){this.session=e,this.$computeTabString()},this.showInvisibles=!1,this.setShowInvisibles=function(e){return this.showInvisibles==e?!1:(this.showInvisibles=e,this.$computeTabString(),!0)},this.displayIndentGuides=!0,this.setDisplayIndentGuides=function(e){return this.displayIndentGuides==e?!1:(this.displayIndentGuides=e,this.$computeTabString(),!0)},this.$tabStrings=[],this.onChangeTabSize=this.$computeTabString=function(){var e=this.session.getTabSize();this.tabSize=e;var t=this.$tabStrings=[0];for(var n=1;n<e+1;n++)this.showInvisibles?t.push("<span class='ace_invisible ace_invisible_tab'>"+this.TAB_CHAR+s.stringRepeat("\u00a0",n-1)+"</span>"):t.push(s.stringRepeat("\u00a0",n));if(this.displayIndentGuides){this.$indentGuideRe=/\s\S| \t|\t |\s$/;var r="ace_indent-guide",i="",o="";if(this.showInvisibles){r+=" ace_invisible",i=" ace_invisible_space",o=" ace_invisible_tab";var u=s.stringRepeat(this.SPACE_CHAR,this.tabSize),a=this.TAB_CHAR+s.stringRepeat("\u00a0",this.tabSize-1)}else var u=s.stringRepeat("\u00a0",this.tabSize),a=u;this.$tabStrings[" "]="<span class='"+r+i+"'>"+u+"</span>",this.$tabStrings["	"]="<span class='"+r+o+"'>"+a+"</span>"}},this.updateLines=function(e,t,n){(this.config.lastRow!=e.lastRow||this.config.firstRow!=e.firstRow)&&this.scrollLines(e),this.config=e;var r=Math.max(t,e.firstRow),i=Math.min(n,e.lastRow),s=this.element.childNodes,o=0;for(var u=e.firstRow;u<r;u++){var a=this.session.getFoldLine(u);if(a){if(a.containsRow(r)){r=a.start.row;break}u=a.end.row}o++}var u=r,a=this.session.getNextFoldLine(u),f=a?a.start.row:Infinity;for(;;){u>f&&(u=a.end.row+1,a=this.session.getNextFoldLine(u,a),f=a?a.start.row:Infinity);if(u>i)break;var l=s[o++];if(l){var c=[];this.$renderLine(c,u,!this.$useLineGroups(),u==f?a:!1),l.style.height=e.lineHeight*this.session.getRowLength(u)+"px",l.innerHTML=c.join("")}u++}},this.scrollLines=function(e){var t=this.config;this.config=e;if(!t||t.lastRow<e.firstRow)return this.update(e);if(e.lastRow<t.firstRow)return this.update(e);var n=this.element;if(t.firstRow<e.firstRow)for(var r=this.session.getFoldedRowCount(t.firstRow,e.firstRow-1);r>0;r--)n.removeChild(n.firstChild);if(t.lastRow>e.lastRow)for(var r=this.session.getFoldedRowCount(e.lastRow+1,t.lastRow);r>0;r--)n.removeChild(n.lastChild);if(e.firstRow<t.firstRow){var i=this.$renderLinesFragment(e,e.firstRow,t.firstRow-1);n.firstChild?n.insertBefore(i,n.firstChild):n.appendChild(i)}if(e.lastRow>t.lastRow){var i=this.$renderLinesFragment(e,t.lastRow+1,e.lastRow);n.appendChild(i)}},this.$renderLinesFragment=function(e,t,n){var r=this.element.ownerDocument.createDocumentFragment(),s=t,o=this.session.getNextFoldLine(s),u=o?o.start.row:Infinity;for(;;){s>u&&(s=o.end.row+1,o=this.session.getNextFoldLine(s,o),u=o?o.start.row:Infinity);if(s>n)break;var a=i.createElement("div"),f=[];this.$renderLine(f,s,!1,s==u?o:!1),a.innerHTML=f.join("");if(this.$useLineGroups())a.className="ace_line_group",r.appendChild(a),a.style.height=e.lineHeight*this.session.getRowLength(s)+"px";else while(a.firstChild)r.appendChild(a.firstChild);s++}return r},this.update=function(e){this.config=e;var t=[],n=e.firstRow,r=e.lastRow,i=n,s=this.session.getNextFoldLine(i),o=s?s.start.row:Infinity;for(;;){i>o&&(i=s.end.row+1,s=this.session.getNextFoldLine(i,s),o=s?s.start.row:Infinity);if(i>r)break;this.$useLineGroups()&&t.push("<div class='ace_line_group' style='height:",e.lineHeight*this.session.getRowLength(i),"px'>"),this.$renderLine(t,i,!1,i==o?s:!1),this.$useLineGroups()&&t.push("</div>"),i++}this.element.innerHTML=t.join("")},this.$textToken={text:!0,rparen:!0,lparen:!0},this.$renderToken=function(e,t,n,r){var i=this,o=/\t|&|<|( +)|([\x00-\x1f\x80-\xa0\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\u3000\uFEFF])|[\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6]/g,u=function(e,n,r,o,u){if(n)return i.showInvisibles?"<span class='ace_invisible ace_invisible_space'>"+s.stringRepeat(i.SPACE_CHAR,e.length)+"</span>":s.stringRepeat("\u00a0",e.length);if(e=="&")return"&#38;";if(e=="<")return"&#60;";if(e=="	"){var a=i.session.getScreenTabSize(t+o);return t+=a-1,i.$tabStrings[a]}if(e=="\u3000"){var f=i.showInvisibles?"ace_cjk ace_invisible ace_invisible_space":"ace_cjk",l=i.showInvisibles?i.SPACE_CHAR:"";return t+=1,"<span class='"+f+"' style='width:"+i.config.characterWidth*2+"px'>"+l+"</span>"}return r?"<span class='ace_invisible ace_invisible_space ace_invalid'>"+i.SPACE_CHAR+"</span>":(t+=1,"<span class='ace_cjk' style='width:"+i.config.characterWidth*2+"px'>"+e+"</span>")},a=r.replace(o,u);if(!this.$textToken[n.type]){var f="ace_"+n.type.replace(/\./g," ace_"),l="";n.type=="fold"&&(l=" style='width:"+n.value.length*this.config.characterWidth+"px;' "),e.push("<span class='",f,"'",l,">",a,"</span>")}else e.push(a);return t+r.length},this.renderIndentGuide=function(e,t,n){var r=t.search(this.$indentGuideRe);return r<=0||r>=n?t:t[0]==" "?(r-=r%this.tabSize,e.push(s.stringRepeat(this.$tabStrings[" "],r/this.tabSize)),t.substr(r)):t[0]=="	"?(e.push(s.stringRepeat(this.$tabStrings["	"],r)),t.substr(r)):t},this.$renderWrappedLine=function(e,t,n,r){var i=0,s=0,o=n[0],u=0;for(var a=0;a<t.length;a++){var f=t[a],l=f.value;if(a==0&&this.displayIndentGuides){i=l.length,l=this.renderIndentGuide(e,l,o);if(!l)continue;i-=l.length}if(i+l.length<o)u=this.$renderToken(e,u,f,l),i+=l.length;else{while(i+l.length>=o)u=this.$renderToken(e,u,f,l.substring(0,o-i)),l=l.substring(o-i),i=o,r||e.push("</div>","<div class='ace_line' style='height:",this.config.lineHeight,"px'>"),s++,u=0,o=n[s]||Number.MAX_VALUE;l.length!=0&&(i+=l.length,u=this.$renderToken(e,u,f,l))}}},this.$renderSimpleLine=function(e,t){var n=0,r=t[0],i=r.value;this.displayIndentGuides&&(i=this.renderIndentGuide(e,i)),i&&(n=this.$renderToken(e,n,r,i));for(var s=1;s<t.length;s++)r=t[s],i=r.value,n=this.$renderToken(e,n,r,i)},this.$renderLine=function(e,t,n,r){!r&&r!=0&&(r=this.session.getFoldLine(t));if(r)var i=this.$getFoldLineTokens(t,r);else var i=this.session.getTokens(t);n||e.push("<div class='ace_line' style='height:",this.config.lineHeight*(this.$useLineGroups()?1:this.session.getRowLength(t)),"px'>");if(i.length){var s=this.session.getRowSplitData(t);s&&s.length?this.$renderWrappedLine(e,i,s,n):this.$renderSimpleLine(e,i)}this.showInvisibles&&(r&&(t=r.end.row),e.push("<span class='ace_invisible ace_invisible_eol'>",t==this.session.getLength()-1?this.EOF_CHAR:this.EOL_CHAR,"</span>")),n||e.push("</div>")},this.$getFoldLineTokens=function(e,t){function i(e,t,n){var i=0,s=0;while(s+e[i].value.length<t){s+=e[i].value.length,i++;if(i==e.length)return}if(s!=t){var o=e[i].value.substring(t-s);o.length>n-t&&(o=o.substring(0,n-t)),r.push({type:e[i].type,value:o}),s=t+o.length,i+=1}while(s<n&&i<e.length){var o=e[i].value;o.length+s>n?r.push({type:e[i].type,value:o.substring(0,n-s)}):r.push(e[i]),s+=o.length,i+=1}}var n=this.session,r=[],s=n.getTokens(e);return t.walk(function(e,t,o,u,a){e!=null?r.push({type:"fold",value:e}):(a&&(s=n.getTokens(t)),s.length&&i(s,u,o))},t.end.row,this.session.getLine(t.end.row).length),r},this.$useLineGroups=function(){return this.session.getUseWrapMode()},this.destroy=function(){clearInterval(this.$pollSizeChangesTimer),this.$measureNode&&this.$measureNode.parentNode.removeChild(this.$measureNode),delete this.$measureNode}}).call(a.prototype),t.Text=a}),ace.define("ace/layer/cursor",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";var r=e("../lib/dom"),i,s=function(e){this.element=r.createElement("div"),this.element.className="ace_layer ace_cursor-layer",e.appendChild(this.element),i===undefined&&(i="opacity"in this.element),this.isVisible=!1,this.isBlinking=!0,this.blinkInterval=1e3,this.smoothBlinking=!1,this.cursors=[],this.cursor=this.addCursor(),r.addCssClass(this.element,"ace_hidden-cursors"),this.$updateCursors=this.$updateVisibility.bind(this)};(function(){this.$updateVisibility=function(e){var t=this.cursors;for(var n=t.length;n--;)t[n].style.visibility=e?"":"hidden"},this.$updateOpacity=function(e){var t=this.cursors;for(var n=t.length;n--;)t[n].style.opacity=e?"":"0"},this.$padding=0,this.setPadding=function(e){this.$padding=e},this.setSession=function(e){this.session=e},this.setBlinking=function(e){e!=this.isBlinking&&(this.isBlinking=e,this.restartTimer())},this.setBlinkInterval=function(e){e!=this.blinkInterval&&(this.blinkInterval=e,this.restartTimer())},this.setSmoothBlinking=function(e){e!=this.smoothBlinking&&!i&&(this.smoothBlinking=e,r.setCssClass(this.element,"ace_smooth-blinking",e),this.$updateCursors(!0),this.$updateCursors=(e?this.$updateOpacity:this.$updateVisibility).bind(this),this.restartTimer())},this.addCursor=function(){var e=r.createElement("div");return e.className="ace_cursor",this.element.appendChild(e),this.cursors.push(e),e},this.removeCursor=function(){if(this.cursors.length>1){var e=this.cursors.pop();return e.parentNode.removeChild(e),e}},this.hideCursor=function(){this.isVisible=!1,r.addCssClass(this.element,"ace_hidden-cursors"),this.restartTimer()},this.showCursor=function(){this.isVisible=!0,r.removeCssClass(this.element,"ace_hidden-cursors"),this.restartTimer()},this.restartTimer=function(){var e=this.$updateCursors;clearInterval(this.intervalId),clearTimeout(this.timeoutId),this.smoothBlinking&&r.removeCssClass(this.element,"ace_smooth-blinking"),e(!0);if(!this.isBlinking||!this.blinkInterval||!this.isVisible)return;this.smoothBlinking&&setTimeout(function(){r.addCssClass(this.element,"ace_smooth-blinking")}.bind(this));var t=function(){this.timeoutId=setTimeout(function(){e(!1)},.6*this.blinkInterval)}.bind(this);this.intervalId=setInterval(function(){e(!0),t()},this.blinkInterval),t()},this.getPixelPosition=function(e,t){if(!this.config||!this.session)return{left:0,top:0};e||(e=this.session.selection.getCursor());var n=this.session.documentToScreenPosition(e),r=this.$padding+n.column*this.config.characterWidth,i=(n.row-(t?this.config.firstRowScreen:0))*this.config.lineHeight;return{left:r,top:i}},this.update=function(e){this.config=e;var t=this.session.$selectionMarkers,n=0,r=0;if(t===undefined||t.length===0)t=[{cursor:null}];for(var n=0,i=t.length;n<i;n++){var s=this.getPixelPosition(t[n].cursor,!0);if((s.top>e.height+e.offset||s.top<0)&&n>1)continue;var o=(this.cursors[r++]||this.addCursor()).style;o.left=s.left+"px",o.top=s.top+"px",o.width=e.characterWidth+"px",o.height=e.lineHeight+"px"}while(this.cursors.length>r)this.removeCursor();var u=this.session.getOverwrite();this.$setOverwrite(u),this.$pixelPos=s,this.restartTimer()},this.$setOverwrite=function(e){e!=this.overwrite&&(this.overwrite=e,e?r.addCssClass(this.element,"ace_overwrite-cursors"):r.removeCssClass(this.element,"ace_overwrite-cursors"))},this.destroy=function(){clearInterval(this.intervalId),clearTimeout(this.timeoutId)}}).call(s.prototype),t.Cursor=s}),ace.define("ace/scrollbar",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/event","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/dom"),s=e("./lib/event"),o=e("./lib/event_emitter").EventEmitter,u=function(e){this.element=i.createElement("div"),this.element.className="ace_scrollbar ace_scrollbar"+this.classSuffix,this.inner=i.createElement("div"),this.inner.className="ace_scrollbar-inner",this.element.appendChild(this.inner),e.appendChild(this.element),this.setVisible(!1),this.skipEvent=!1,s.addListener(this.element,"scroll",this.onScroll.bind(this)),s.addListener(this.element,"mousedown",s.preventDefault)};(function(){r.implement(this,o),this.setVisible=function(e){this.element.style.display=e?"":"none",this.isVisible=e}}).call(u.prototype);var a=function(e,t){u.call(this,e),this.scrollTop=0,t.$scrollbarWidth=this.width=i.scrollbarWidth(e.ownerDocument),this.inner.style.width=this.element.style.width=(this.width||15)+5+"px"};r.inherits(a,u),function(){this.classSuffix="-v",this.onScroll=function(){this.skipEvent||(this.scrollTop=this.element.scrollTop,this._emit("scroll",{data:this.scrollTop})),this.skipEvent=!1},this.getWidth=function(){return this.isVisible?this.width:0},this.setHeight=function(e){this.element.style.height=e+"px"},this.setInnerHeight=function(e){this.inner.style.height=e+"px"},this.setScrollHeight=function(e){this.inner.style.height=e+"px"},this.setScrollTop=function(e){this.scrollTop!=e&&(this.skipEvent=!0,this.scrollTop=this.element.scrollTop=e)}}.call(a.prototype);var f=function(e,t){u.call(this,e),this.scrollLeft=0,this.height=t.$scrollbarWidth,this.inner.style.height=this.element.style.height=(this.height||15)+5+"px"};r.inherits(f,u),function(){this.classSuffix="-h",this.onScroll=function(){this.skipEvent||(this.scrollLeft=this.element.scrollLeft,this._emit("scroll",{data:this.scrollLeft})),this.skipEvent=!1},this.getHeight=function(){return this.isVisible?this.height:0},this.setWidth=function(e){this.element.style.width=e+"px"},this.setInnerWidth=function(e){this.inner.style.width=e+"px"},this.setScrollWidth=function(e){this.inner.style.width=e+"px"},this.setScrollLeft=function(e){this.scrollLeft!=e&&(this.skipEvent=!0,this.scrollLeft=this.element.scrollLeft=e)}}.call(f.prototype),t.ScrollBar=a,t.ScrollBarV=a,t.ScrollBarH=f,t.VScrollBar=a,t.HScrollBar=f}),ace.define("ace/renderloop",["require","exports","module","ace/lib/event"],function(e,t,n){"use strict";var r=e("./lib/event"),i=function(e,t){this.onRender=e,this.pending=!1,this.changes=0,this.window=t||window};(function(){this.schedule=function(e){this.changes=this.changes|e;if(!this.pending&&this.changes){this.pending=!0;var t=this;r.nextFrame(function(){t.pending=!1;var e;while(e=t.changes)t.changes=0,t.onRender(e)},this.window)}}}).call(i.prototype),t.RenderLoop=i}),ace.define("ace/layer/font_metrics",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/lib/lang","ace/lib/useragent","ace/lib/event_emitter"],function(e,t,n){var r=e("../lib/oop"),i=e("../lib/dom"),s=e("../lib/lang"),o=e("../lib/useragent"),u=e("../lib/event_emitter").EventEmitter,a=0,f=t.FontMetrics=function(e,t){this.el=i.createElement("div"),this.$setMeasureNodeStyles(this.el.style,!0),this.$main=i.createElement("div"),this.$setMeasureNodeStyles(this.$main.style),this.$measureNode=i.createElement("div"),this.$setMeasureNodeStyles(this.$measureNode.style),this.el.appendChild(this.$main),this.el.appendChild(this.$measureNode),e.appendChild(this.el),a||this.$testFractionalRect(),this.$measureNode.innerHTML=s.stringRepeat("X",a),this.$characterSize={width:0,height:0},this.checkForSizeChanges()};(function(){r.implement(this,u),this.$characterSize={width:0,height:0},this.$testFractionalRect=function(){var e=i.createElement("div");this.$setMeasureNodeStyles(e.style),e.style.width="0.2px",document.documentElement.appendChild(e);var t=e.getBoundingClientRect().width;t>0&&t<1?a=1:a=100,e.parentNode.removeChild(e)},this.$setMeasureNodeStyles=function(e,t){e.width=e.height="auto",e.left=e.top="-100px",e.visibility="hidden",e.position="fixed",e.whiteSpace="pre",o.isIE<8?e["font-family"]="inherit":e.font="inherit",e.overflow=t?"hidden":"visible"},this.checkForSizeChanges=function(){var e=this.$measureSizes();if(e&&(this.$characterSize.width!==e.width||this.$characterSize.height!==e.height)){this.$measureNode.style.fontWeight="bold";var t=this.$measureSizes();this.$measureNode.style.fontWeight="",this.$characterSize=e,this.charSizes=Object.create(null),this.allowBoldFonts=t&&t.width===e.width&&t.height===e.height,this._emit("changeCharacterSize",{data:e})}},this.$pollSizeChanges=function(){if(this.$pollSizeChangesTimer)return this.$pollSizeChangesTimer;var e=this;return this.$pollSizeChangesTimer=setInterval(function(){e.checkForSizeChanges()},500)},this.setPolling=function(e){e?this.$pollSizeChanges():this.$pollSizeChangesTimer&&this.$pollSizeChangesTimer},this.$measureSizes=function(){if(a===1)var e=this.$measureNode.getBoundingClientRect(),t={height:e.height,width:e.width};else var t={height:this.$measureNode.clientHeight,width:this.$measureNode.clientWidth/a};return t.width===0||t.height===0?null:t},this.$measureCharWidth=function(e){this.$main.innerHTML=s.stringRepeat(e,a);var t=this.$main.getBoundingClientRect();return t.width/a},this.getCharacterWidth=function(e){var t=this.charSizes[e];return t===undefined&&(this.charSizes[e]=this.$measureCharWidth(e)/this.$characterSize.width),t},this.destroy=function(){clearInterval(this.$pollSizeChangesTimer),this.el&&this.el.parentNode&&this.el.parentNode.removeChild(this.el)}}).call(f.prototype)}),ace.define("ace/virtual_renderer",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/config","ace/lib/useragent","ace/layer/gutter","ace/layer/marker","ace/layer/text","ace/layer/cursor","ace/scrollbar","ace/scrollbar","ace/renderloop","ace/layer/font_metrics","ace/lib/event_emitter"],function(e,t,n){"use strict";var r=e("./lib/oop"),i=e("./lib/dom"),s=e("./config"),o=e("./lib/useragent"),u=e("./layer/gutter").Gutter,a=e("./layer/marker").Marker,f=e("./layer/text").Text,l=e("./layer/cursor").Cursor,c=e("./scrollbar").HScrollBar,h=e("./scrollbar").VScrollBar,p=e("./renderloop").RenderLoop,d=e("./layer/font_metrics").FontMetrics,v=e("./lib/event_emitter").EventEmitter,m='.ace_editor {position: relative;overflow: hidden;font-family: \'Monaco\', \'Menlo\', \'Ubuntu Mono\', \'Consolas\', \'source-code-pro\', monospace;font-size: 12px;line-height: normal;direction: ltr;}.ace_scroller {position: absolute;overflow: hidden;top: 0;bottom: 0;background-color: inherit;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;}.ace_content {position: absolute;-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;cursor: text;min-width: 100%;}.ace_dragging, .ace_dragging * {cursor: move !important;}.ace_dragging .ace_scroller:before{position: absolute;top: 0;left: 0;right: 0;bottom: 0;content: \'\';background: rgba(250, 250, 250, 0.01);z-index: 1000;}.ace_dragging.ace_dark .ace_scroller:before{background: rgba(0, 0, 0, 0.01);}.ace_selecting, .ace_selecting * {cursor: text !important;}.ace_gutter {position: absolute;overflow : hidden;width: auto;top: 0;bottom: 0;left: 0;cursor: default;z-index: 4;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;}.ace_gutter-active-line {position: absolute;left: 0;right: 0;}.ace_scroller.ace_scroll-left {box-shadow: 17px 0 16px -16px rgba(0, 0, 0, 0.4) inset;}.ace_gutter-cell {padding-left: 19px;padding-right: 6px;background-repeat: no-repeat;}.ace_gutter-cell.ace_error {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABOFBMVEX/////////QRswFAb/Ui4wFAYwFAYwFAaWGAfDRymzOSH/PxswFAb/SiUwFAYwFAbUPRvjQiDllog5HhHdRybsTi3/Tyv9Tir+Syj/UC3////XurebMBIwFAb/RSHbPx/gUzfdwL3kzMivKBAwFAbbvbnhPx66NhowFAYwFAaZJg8wFAaxKBDZurf/RB6mMxb/SCMwFAYwFAbxQB3+RB4wFAb/Qhy4Oh+4QifbNRcwFAYwFAYwFAb/QRzdNhgwFAYwFAbav7v/Uy7oaE68MBK5LxLewr/r2NXewLswFAaxJw4wFAbkPRy2PyYwFAaxKhLm1tMwFAazPiQwFAaUGAb/QBrfOx3bvrv/VC/maE4wFAbRPBq6MRO8Qynew8Dp2tjfwb0wFAbx6eju5+by6uns4uH9/f36+vr/GkHjAAAAYnRSTlMAGt+64rnWu/bo8eAA4InH3+DwoN7j4eLi4xP99Nfg4+b+/u9B/eDs1MD1mO7+4PHg2MXa347g7vDizMLN4eG+Pv7i5evs/v79yu7S3/DV7/498Yv24eH+4ufQ3Ozu/v7+y13sRqwAAADLSURBVHjaZc/XDsFgGIBhtDrshlitmk2IrbHFqL2pvXf/+78DPokj7+Fz9qpU/9UXJIlhmPaTaQ6QPaz0mm+5gwkgovcV6GZzd5JtCQwgsxoHOvJO15kleRLAnMgHFIESUEPmawB9ngmelTtipwwfASilxOLyiV5UVUyVAfbG0cCPHig+GBkzAENHS0AstVF6bacZIOzgLmxsHbt2OecNgJC83JERmePUYq8ARGkJx6XtFsdddBQgZE2nPR6CICZhawjA4Fb/chv+399kfR+MMMDGOQAAAABJRU5ErkJggg==");background-repeat: no-repeat;background-position: 2px center;}.ace_gutter-cell.ace_warning {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAmVBMVEX///8AAAD///8AAAAAAABPSzb/5sAAAAB/blH/73z/ulkAAAAAAAD85pkAAAAAAAACAgP/vGz/rkDerGbGrV7/pkQICAf////e0IsAAAD/oED/qTvhrnUAAAD/yHD/njcAAADuv2r/nz//oTj/p064oGf/zHAAAAA9Nir/tFIAAAD/tlTiuWf/tkIAAACynXEAAAAAAAAtIRW7zBpBAAAAM3RSTlMAABR1m7RXO8Ln31Z36zT+neXe5OzooRDfn+TZ4p3h2hTf4t3k3ucyrN1K5+Xaks52Sfs9CXgrAAAAjklEQVR42o3PbQ+CIBQFYEwboPhSYgoYunIqqLn6/z8uYdH8Vmdnu9vz4WwXgN/xTPRD2+sgOcZjsge/whXZgUaYYvT8QnuJaUrjrHUQreGczuEafQCO/SJTufTbroWsPgsllVhq3wJEk2jUSzX3CUEDJC84707djRc5MTAQxoLgupWRwW6UB5fS++NV8AbOZgnsC7BpEAAAAABJRU5ErkJggg==");background-position: 2px center;}.ace_gutter-cell.ace_info {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAJ0Uk5TAAB2k804AAAAPklEQVQY02NgIB68QuO3tiLznjAwpKTgNyDbMegwisCHZUETUZV0ZqOquBpXj2rtnpSJT1AEnnRmL2OgGgAAIKkRQap2htgAAAAASUVORK5CYII=");background-position: 2px center;}.ace_dark .ace_gutter-cell.ace_info {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAChoaGAgIAqKiq+vr6tra1ZWVmUlJSbm5s8PDxubm56enrdgzg3AAAAAXRSTlMAQObYZgAAAClJREFUeNpjYMAPdsMYHegyJZFQBlsUlMFVCWUYKkAZMxZAGdxlDMQBAG+TBP4B6RyJAAAAAElFTkSuQmCC");}.ace_scrollbar {position: absolute;right: 0;bottom: 0;z-index: 6;}.ace_scrollbar-inner {position: absolute;cursor: text;left: 0;top: 0;}.ace_scrollbar-v{overflow-x: hidden;overflow-y: scroll;top: 0;}.ace_scrollbar-h {overflow-x: scroll;overflow-y: hidden;left: 0;}.ace_print-margin {position: absolute;height: 100%;}.ace_text-input {position: absolute;z-index: 0;width: 0.5em;height: 1em;opacity: 0;background: transparent;-moz-appearance: none;appearance: none;border: none;resize: none;outline: none;overflow: hidden;font: inherit;padding: 0 1px;margin: 0 -1px;text-indent: -1em;-ms-user-select: text;-moz-user-select: text;-webkit-user-select: text;user-select: text;}.ace_text-input.ace_composition {background: #f8f8f8;color: #111;z-index: 1000;opacity: 1;text-indent: 0;}.ace_layer {z-index: 1;position: absolute;overflow: hidden;white-space: pre;height: 100%;width: 100%;-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;pointer-events: none;}.ace_gutter-layer {position: relative;width: auto;text-align: right;pointer-events: auto;}.ace_text-layer {font: inherit !important;}.ace_cjk {display: inline-block;text-align: center;}.ace_cursor-layer {z-index: 4;}.ace_cursor {z-index: 4;position: absolute;-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;border-left: 2px solid}.ace_slim-cursors .ace_cursor {border-left-width: 1px;}.ace_overwrite-cursors .ace_cursor {border-left-width: 0px;border-bottom: 1px solid;}.ace_hidden-cursors .ace_cursor {opacity: 0.2;}.ace_smooth-blinking .ace_cursor {-moz-transition: opacity 0.18s;-webkit-transition: opacity 0.18s;-o-transition: opacity 0.18s;-ms-transition: opacity 0.18s;transition: opacity 0.18s;}.ace_editor.ace_multiselect .ace_cursor {border-left-width: 1px;}.ace_marker-layer .ace_step, .ace_marker-layer .ace_stack {position: absolute;z-index: 3;}.ace_marker-layer .ace_selection {position: absolute;z-index: 5;}.ace_marker-layer .ace_bracket {position: absolute;z-index: 6;}.ace_marker-layer .ace_active-line {position: absolute;z-index: 2;}.ace_marker-layer .ace_selected-word {position: absolute;z-index: 4;-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;}.ace_line .ace_fold {-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;display: inline-block;height: 11px;margin-top: -2px;vertical-align: middle;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpi+P//fxgTAwPDBxDxD078RSX+YeEyDFMCIMAAI3INmXiwf2YAAAAASUVORK5CYII=");background-repeat: no-repeat, repeat-x;background-position: center center, top left;color: transparent;border: 1px solid black;-moz-border-radius: 2px;-webkit-border-radius: 2px;border-radius: 2px;cursor: pointer;pointer-events: auto;}.ace_dark .ace_fold {}.ace_fold:hover{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACBJREFUeNpi+P//fz4TAwPDZxDxD5X4i5fLMEwJgAADAEPVDbjNw87ZAAAAAElFTkSuQmCC");}.ace_tooltip {background-color: #FFF;background-image: -webkit-linear-gradient(top, transparent, rgba(0, 0, 0, 0.1));background-image: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.1));border: 1px solid gray;border-radius: 1px;box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);color: black;display: block;max-width: 100%;padding: 3px 4px;position: fixed;z-index: 999999;-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;cursor: default;white-space: pre;word-wrap: break-word;line-height: normal;font-style: normal;font-weight: normal;letter-spacing: normal;pointer-events: none;}.ace_folding-enabled > .ace_gutter-cell {padding-right: 13px;}.ace_fold-widget {-moz-box-sizing: border-box;-webkit-box-sizing: border-box;box-sizing: border-box;margin: 0 -12px 0 1px;display: none;width: 11px;vertical-align: top;background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42mWKsQ0AMAzC8ixLlrzQjzmBiEjp0A6WwBCSPgKAXoLkqSot7nN3yMwR7pZ32NzpKkVoDBUxKAAAAABJRU5ErkJggg==");background-repeat: no-repeat;background-position: center;border-radius: 3px;border: 1px solid transparent;cursor: pointer;}.ace_folding-enabled .ace_fold-widget {display: inline-block;   }.ace_fold-widget.ace_end {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42m3HwQkAMAhD0YzsRchFKI7sAikeWkrxwScEB0nh5e7KTPWimZki4tYfVbX+MNl4pyZXejUO1QAAAABJRU5ErkJggg==");}.ace_fold-widget.ace_closed {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAGCAYAAAAG5SQMAAAAOUlEQVR42jXKwQkAMAgDwKwqKD4EwQ26sSOkVWjgIIHAzPiCgaqiqnJHZnKICBERHN194O5b9vbLuAVRL+l0YWnZAAAAAElFTkSuQmCCXA==");}.ace_fold-widget:hover {border: 1px solid rgba(0, 0, 0, 0.3);background-color: rgba(255, 255, 255, 0.2);-moz-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);-webkit-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);}.ace_fold-widget:active {border: 1px solid rgba(0, 0, 0, 0.4);background-color: rgba(0, 0, 0, 0.05);-moz-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);-webkit-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);}.ace_dark .ace_fold-widget {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHklEQVQIW2P4//8/AzoGEQ7oGCaLLAhWiSwB146BAQCSTPYocqT0AAAAAElFTkSuQmCC");}.ace_dark .ace_fold-widget.ace_end {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH0lEQVQIW2P4//8/AxQ7wNjIAjDMgC4AxjCVKBirIAAF0kz2rlhxpAAAAABJRU5ErkJggg==");}.ace_dark .ace_fold-widget.ace_closed {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAHElEQVQIW2P4//+/AxAzgDADlOOAznHAKgPWAwARji8UIDTfQQAAAABJRU5ErkJggg==");}.ace_dark .ace_fold-widget:hover {box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);background-color: rgba(255, 255, 255, 0.1);}.ace_dark .ace_fold-widget:active {-moz-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);-webkit-box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);}.ace_fold-widget.ace_invalid {background-color: #FFB4B4;border-color: #DE5555;}.ace_fade-fold-widgets .ace_fold-widget {-moz-transition: opacity 0.4s ease 0.05s;-webkit-transition: opacity 0.4s ease 0.05s;-o-transition: opacity 0.4s ease 0.05s;-ms-transition: opacity 0.4s ease 0.05s;transition: opacity 0.4s ease 0.05s;opacity: 0;}.ace_fade-fold-widgets:hover .ace_fold-widget {-moz-transition: opacity 0.05s ease 0.05s;-webkit-transition: opacity 0.05s ease 0.05s;-o-transition: opacity 0.05s ease 0.05s;-ms-transition: opacity 0.05s ease 0.05s;transition: opacity 0.05s ease 0.05s;opacity:1;}.ace_underline {text-decoration: underline;}.ace_bold {font-weight: bold;}.ace_nobold .ace_bold {font-weight: normal;}.ace_italic {font-style: italic;}.ace_error-marker {background-color: rgba(255, 0, 0,0.2);position: absolute;z-index: 9;}.ace_highlight-marker {background-color: rgba(255, 255, 0,0.2);position: absolute;z-index: 8;}';i.importCssString(m,"ace_editor");var g=function(e,t){var n=this;this.container=e||i.createElement("div"),this.$keepTextAreaAtCursor=!o.isOldIE,i.addCssClass(this.container,"ace_editor"),this.setTheme(t),this.$gutter=i.createElement("div"),this.$gutter.className="ace_gutter",this.container.appendChild(this.$gutter),this.scroller=i.createElement("div"),this.scroller.className="ace_scroller",this.container.appendChild(this.scroller),this.content=i.createElement("div"),this.content.className="ace_content",this.scroller.appendChild(this.content),this.$gutterLayer=new u(this.$gutter),this.$gutterLayer.on("changeGutterWidth",this.onGutterResize.bind(this)),this.$markerBack=new a(this.content);var r=this.$textLayer=new f(this.content);this.canvas=r.element,this.$markerFront=new a(this.content),this.$cursorLayer=new l(this.content),this.$horizScroll=!1,this.$vScroll=!1,this.scrollBar=this.scrollBarV=new h(this.container,this),this.scrollBarH=new c(this.container,this),this.scrollBarV.addEventListener("scroll",function(e){n.$scrollAnimation||n.session.setScrollTop(e.data-n.scrollMargin.top)}),this.scrollBarH.addEventListener("scroll",function(e){n.$scrollAnimation||n.session.setScrollLeft(e.data-n.scrollMargin.left)}),this.scrollTop=0,this.scrollLeft=0,this.cursorPos={row:0,column:0},this.$fontMetrics=new d(this.container,500),this.$textLayer.$setFontMetrics(this.$fontMetrics),this.$textLayer.addEventListener("changeCharacterSize",function(e){n.updateCharacterSize(),n.onResize(!0,n.gutterWidth,n.$size.width,n.$size.height),n._signal("changeCharacterSize",e)}),this.$size={width:0,height:0,scrollerHeight:0,scrollerWidth:0,$dirty:!0},this.layerConfig={width:1,padding:0,firstRow:0,firstRowScreen:0,lastRow:0,lineHeight:0,characterWidth:0,minHeight:1,maxHeight:1,offset:0,height:1,gutterOffset:1},this.scrollMargin={left:0,right:0,top:0,bottom:0,v:0,h:0},this.$loop=new p(this.$renderChanges.bind(this),this.container.ownerDocument.defaultView),this.$loop.schedule(this.CHANGE_FULL),this.updateCharacterSize(),this.setPadding(4),s.resetOptions(this),s._emit("renderer",this)};(function(){this.CHANGE_CURSOR=1,this.CHANGE_MARKER=2,this.CHANGE_GUTTER=4,this.CHANGE_SCROLL=8,this.CHANGE_LINES=16,this.CHANGE_TEXT=32,this.CHANGE_SIZE=64,this.CHANGE_MARKER_BACK=128,this.CHANGE_MARKER_FRONT=256,this.CHANGE_FULL=512,this.CHANGE_H_SCROLL=1024,r.implement(this,v),this.updateCharacterSize=function(){this.$textLayer.allowBoldFonts!=this.$allowBoldFonts&&(this.$allowBoldFonts=this.$textLayer.allowBoldFonts,this.setStyle("ace_nobold",!this.$allowBoldFonts)),this.layerConfig.characterWidth=this.characterWidth=this.$textLayer.getCharacterWidth(),this.layerConfig.lineHeight=this.lineHeight=this.$textLayer.getLineHeight(),this.$updatePrintMargin()},this.setSession=function(e){this.session&&this.session.doc.off("changeNewLineMode",this.onChangeNewLineMode),this.session=e;if(!e)return;this.scrollMargin.top&&e.getScrollTop()<=0&&e.setScrollTop(-this.scrollMargin.top),this.$cursorLayer.setSession(e),this.$markerBack.setSession(e),this.$markerFront.setSession(e),this.$gutterLayer.setSession(e),this.$textLayer.setSession(e),this.$loop.schedule(this.CHANGE_FULL),this.session.$setFontMetrics(this.$fontMetrics),this.onChangeNewLineMode=this.onChangeNewLineMode.bind(this),this.onChangeNewLineMode(),this.session.doc.on("changeNewLineMode",this.onChangeNewLineMode)},this.updateLines=function(e,t){t===undefined&&(t=Infinity),this.$changedLines?(this.$changedLines.firstRow>e&&(this.$changedLines.firstRow=e),this.$changedLines.lastRow<t&&(this.$changedLines.lastRow=t)):this.$changedLines={firstRow:e,lastRow:t};if(this.$changedLines.firstRow>this.layerConfig.lastRow||this.$changedLines.lastRow<this.layerConfig.firstRow)return;this.$loop.schedule(this.CHANGE_LINES)},this.onChangeNewLineMode=function(){this.$loop.schedule(this.CHANGE_TEXT),this.$textLayer.$updateEolChar()},this.onChangeTabSize=function(){this.$loop.schedule(this.CHANGE_TEXT|this.CHANGE_MARKER),this.$textLayer.onChangeTabSize()},this.updateText=function(){this.$loop.schedule(this.CHANGE_TEXT)},this.updateFull=function(e){e?this.$renderChanges(this.CHANGE_FULL,!0):this.$loop.schedule(this.CHANGE_FULL)},this.updateFontSize=function(){this.$textLayer.checkForSizeChanges()},this.$changes=0,this.$updateSizeAsync=function(){this.$loop.pending?this.$size.$dirty=!0:this.onResize()},this.onResize=function(e,t,n,r){if(this.resizing>2)return;this.resizing>0?this.resizing++:this.resizing=e?1:0;var i=this.container;r||(r=i.clientHeight||i.scrollHeight),n||(n=i.clientWidth||i.scrollWidth);var s=this.$updateCachedSize(e,t,n,r);if(!this.$size.scrollerHeight||!n&&!r)return this.resizing=0;e&&(this.$gutterLayer.$padding=null),e?this.$renderChanges(s|this.$changes,!0):this.$loop.schedule(s|this.$changes),this.resizing&&(this.resizing=0)},this.$updateCachedSize=function(e,t,n,r){r-=this.$extraHeight||0;var i=0,s=this.$size,o={width:s.width,height:s.height,scrollerHeight:s.scrollerHeight,scrollerWidth:s.scrollerWidth};r&&(e||s.height!=r)&&(s.height=r,i|=this.CHANGE_SIZE,s.scrollerHeight=s.height,this.$horizScroll&&(s.scrollerHeight-=this.scrollBarH.getHeight()),this.scrollBarV.element.style.bottom=this.scrollBarH.getHeight()+"px",i|=this.CHANGE_SCROLL);if(n&&(e||s.width!=n)){i|=this.CHANGE_SIZE,s.width=n,t==null&&(t=this.$showGutter?this.$gutter.offsetWidth:0),this.gutterWidth=t,this.scrollBarH.element.style.left=this.scroller.style.left=t+"px",s.scrollerWidth=Math.max(0,n-t-this.scrollBarV.getWidth()),this.scrollBarH.element.style.right=this.scroller.style.right=this.scrollBarV.getWidth()+"px",this.scroller.style.bottom=this.scrollBarH.getHeight()+"px";if(this.session&&this.session.getUseWrapMode()&&this.adjustWrapLimit()||e)i|=this.CHANGE_FULL}return s.$dirty=!n||!r,i&&this._signal("resize",o),i},this.onGutterResize=function(){var e=this.$showGutter?this.$gutter.offsetWidth:0;e!=this.gutterWidth&&(this.$changes|=this.$updateCachedSize(!0,e,this.$size.width,this.$size.height)),this.session.getUseWrapMode()&&this.adjustWrapLimit()?this.$loop.schedule(this.CHANGE_FULL):this.$size.$dirty?this.$loop.schedule(this.CHANGE_FULL):(this.$computeLayerConfig(),this.$loop.schedule(this.CHANGE_MARKER))},this.adjustWrapLimit=function(){var e=this.$size.scrollerWidth-this.$padding*2,t=Math.floor(e/this.characterWidth);return this.session.adjustWrapLimit(t,this.$showPrintMargin&&this.$printMarginColumn)},this.setAnimatedScroll=function(e){this.setOption("animatedScroll",e)},this.getAnimatedScroll=function(){return this.$animatedScroll},this.setShowInvisibles=function(e){this.setOption("showInvisibles",e)},this.getShowInvisibles=function(){return this.getOption("showInvisibles")},this.getDisplayIndentGuides=function(){return this.getOption("displayIndentGuides")},this.setDisplayIndentGuides=function(e){this.setOption("displayIndentGuides",e)},this.setShowPrintMargin=function(e){this.setOption("showPrintMargin",e)},this.getShowPrintMargin=function(){return this.getOption("showPrintMargin")},this.setPrintMarginColumn=function(e){this.setOption("printMarginColumn",e)},this.getPrintMarginColumn=function(){return this.getOption("printMarginColumn")},this.getShowGutter=function(){return this.getOption("showGutter")},this.setShowGutter=function(e){return this.setOption("showGutter",e)},this.getFadeFoldWidgets=function(){return this.getOption("fadeFoldWidgets")},this.setFadeFoldWidgets=function(e){this.setOption("fadeFoldWidgets",e)},this.setHighlightGutterLine=function(e){this.setOption("highlightGutterLine",e)},this.getHighlightGutterLine=function(){return this.getOption("highlightGutterLine")},this.$updateGutterLineHighlight=function(){var e=this.$cursorLayer.$pixelPos,t=this.layerConfig.lineHeight;if(this.session.getUseWrapMode()){var n=this.session.selection.getCursor();n.column=0,e=this.$cursorLayer.getPixelPosition(n,!0),t*=this.session.getRowLength(n.row)}this.$gutterLineHighlight.style.top=e.top-this.layerConfig.offset+"px",this.$gutterLineHighlight.style.height=t+"px"},this.$updatePrintMargin=function(){if(!this.$showPrintMargin&&!this.$printMarginEl)return;if(!this.$printMarginEl){var e=i.createElement("div");e.className="ace_layer ace_print-margin-layer",this.$printMarginEl=i.createElement("div"),this.$printMarginEl.className="ace_print-margin",e.appendChild(this.$printMarginEl),this.content.insertBefore(e,this.content.firstChild)}var t=this.$printMarginEl.style;t.left=this.characterWidth*this.$printMarginColumn+this.$padding+"px",t.visibility=this.$showPrintMargin?"visible":"hidden",this.session&&this.session.$wrap==-1&&this.adjustWrapLimit()},this.getContainerElement=function(){return this.container},this.getMouseEventTarget=function(){return this.content},this.getTextAreaContainer=function(){return this.container},this.$moveTextAreaToCursor=function(){if(!this.$keepTextAreaAtCursor)return;var e=this.layerConfig,t=this.$cursorLayer.$pixelPos.top,n=this.$cursorLayer.$pixelPos.left;t-=e.offset;var r=this.lineHeight;if(t<0||t>e.height-r)return;var i=this.characterWidth;if(this.$composition){var s=this.textarea.value.replace(/^\x01+/,"");i*=this.session.$getStringScreenWidth(s)[0]+2,r+=2,t-=1}n-=this.scrollLeft,n>this.$size.scrollerWidth-i&&(n=this.$size.scrollerWidth-i),n-=this.scrollBar.width,this.textarea.style.height=r+"px",this.textarea.style.width=i+"px",this.textarea.style.right=Math.max(0,this.$size.scrollerWidth-n-i)+"px",this.textarea.style.bottom=Math.max(0,this.$size.height-t-r)+"px"},this.getFirstVisibleRow=function(){return this.layerConfig.firstRow},this.getFirstFullyVisibleRow=function(){return this.layerConfig.firstRow+(this.layerConfig.offset===0?0:1)},this.getLastFullyVisibleRow=function(){var e=Math.floor((this.layerConfig.height+this.layerConfig.offset)/this.layerConfig.lineHeight);return this.layerConfig.firstRow-1+e},this.getLastVisibleRow=function(){return this.layerConfig.lastRow},this.$padding=null,this.setPadding=function(e){this.$padding=e,this.$textLayer.setPadding(e),this.$cursorLayer.setPadding(e),this.$markerFront.setPadding(e),this.$markerBack.setPadding(e),this.$loop.schedule(this.CHANGE_FULL),this.$updatePrintMargin()},this.setScrollMargin=function(e,t,n,r){var i=this.scrollMargin;i.top=e|0,i.bottom=t|0,i.right=r|0,i.left=n|0,i.v=i.top+i.bottom,i.h=i.left+i.right,i.top&&this.scrollTop<=0&&this.session&&this.session.setScrollTop(-i.top),this.updateFull()},this.getHScrollBarAlwaysVisible=function(){return this.$hScrollBarAlwaysVisible},this.setHScrollBarAlwaysVisible=function(e){this.setOption("hScrollBarAlwaysVisible",e)},this.getVScrollBarAlwaysVisible=function(){return this.$hScrollBarAlwaysVisible},this.setVScrollBarAlwaysVisible=function(e){this.setOption("vScrollBarAlwaysVisible",e)},this.$updateScrollBarV=function(){var e=this.layerConfig.maxHeight,t=this.$size.scrollerHeight;!this.$maxLines&&this.$scrollPastEnd&&(e-=(t-this.lineHeight)*this.$scrollPastEnd,this.scrollTop>e-t&&(e=this.scrollTop+t,this.scrollBarV.scrollTop=null)),this.scrollBarV.setScrollHeight(e+this.scrollMargin.v),this.scrollBarV.setScrollTop(this.scrollTop+this.scrollMargin.top)},this.$updateScrollBarH=function(){this.scrollBarH.setScrollWidth(this.layerConfig.width+2*this.$padding+this.scrollMargin.h),this.scrollBarH.setScrollLeft(this.scrollLeft+this.scrollMargin.left)},this.$frozen=!1,this.freeze=function(){this.$frozen=!0},this.unfreeze=function(){this.$frozen=!1},this.$renderChanges=function(e,t){this.$changes&&(e|=this.$changes,this.$changes=0);if(!this.session||!this.container.offsetWidth||this.$frozen||!e&&!t){this.$changes|=e;return}if(this.$size.$dirty)return this.$changes|=e,this.onResize(!0);this.lineHeight||this.$textLayer.checkForSizeChanges(),this._signal("beforeRender");var n=this.layerConfig;if(e&this.CHANGE_FULL||e&this.CHANGE_SIZE||e&this.CHANGE_TEXT||e&this.CHANGE_LINES||e&this.CHANGE_SCROLL||e&this.CHANGE_H_SCROLL)e|=this.$computeLayerConfig(),n=this.layerConfig,this.$updateScrollBarV(),e&this.CHANGE_H_SCROLL&&this.$updateScrollBarH(),this.$gutterLayer.element.style.marginTop=-n.offset+"px",this.content.style.marginTop=-n.offset+"px",this.content.style.width=n.width+2*this.$padding+"px",this.content.style.height=n.minHeight+"px";e&this.CHANGE_H_SCROLL&&(this.content.style.marginLeft=-this.scrollLeft+"px",this.scroller.className=this.scrollLeft<=0?"ace_scroller":"ace_scroller ace_scroll-left");if(e&this.CHANGE_FULL){this.$textLayer.update(n),this.$showGutter&&this.$gutterLayer.update(n),this.$markerBack.update(n),this.$markerFront.update(n),this.$cursorLayer.update(n),this.$moveTextAreaToCursor(),this.$highlightGutterLine&&this.$updateGutterLineHighlight(),this._signal("afterRender");return}if(e&this.CHANGE_SCROLL){e&this.CHANGE_TEXT||e&this.CHANGE_LINES?this.$textLayer.update(n):this.$textLayer.scrollLines(n),this.$showGutter&&this.$gutterLayer.update(n),this.$markerBack.update(n),this.$markerFront.update(n),this.$cursorLayer.update(n),this.$highlightGutterLine&&this.$updateGutterLineHighlight(),this.$moveTextAreaToCursor(),this._signal("afterRender");return}e&this.CHANGE_TEXT?(this.$textLayer.update(n),this.$showGutter&&this.$gutterLayer.update(n)):e&this.CHANGE_LINES?(this.$updateLines()||e&this.CHANGE_GUTTER&&this.$showGutter)&&this.$gutterLayer.update(n):(e&this.CHANGE_TEXT||e&this.CHANGE_GUTTER)&&this.$showGutter&&this.$gutterLayer.update(n),e&this.CHANGE_CURSOR&&(this.$cursorLayer.update(n),this.$moveTextAreaToCursor(),this.$highlightGutterLine&&this.$updateGutterLineHighlight()),e&(this.CHANGE_MARKER|this.CHANGE_MARKER_FRONT)&&this.$markerFront.update(n),e&(this.CHANGE_MARKER|this.CHANGE_MARKER_BACK)&&this.$markerBack.update(n),this._signal("afterRender")},this.$autosize=function(){var e=this.session.getScreenLength()*this.lineHeight,t=this.$maxLines*this.lineHeight,n=Math.max((this.$minLines||1)*this.lineHeight,Math.min(t,e))+this.scrollMargin.v+(this.$extraHeight||0),r=e>t;if(n!=this.desiredHeight||this.$size.height!=this.desiredHeight||r!=this.$vScroll){r!=this.$vScroll&&(this.$vScroll=r,this.scrollBarV.setVisible(r));var i=this.container.clientWidth;this.container.style.height=n+"px",this.$updateCachedSize(!0,this.$gutterWidth,i,n),this.desiredHeight=n}},this.$computeLayerConfig=function(){this.$maxLines&&this.lineHeight>1&&this.$autosize();var e=this.session,t=this.$size,n=t.height<=2*this.lineHeight,r=this.session.getScreenLength(),i=r*this.lineHeight,s=this.scrollTop%this.lineHeight,o=t.scrollerHeight+this.lineHeight,u=this.$getLongestLine(),a=!n&&(this.$hScrollBarAlwaysVisible||t.scrollerWidth-u-2*this.$padding<0),f=this.$horizScroll!==a;f&&(this.$horizScroll=a,this.scrollBarH.setVisible(a)),!this.$maxLines&&this.$scrollPastEnd&&(i+=(t.scrollerHeight-this.lineHeight)*this.$scrollPastEnd);var l=!n&&(this.$vScrollBarAlwaysVisible||t.scrollerHeight-i<0),c=this.$vScroll!==l;c&&(this.$vScroll=l,this.scrollBarV.setVisible(l)),this.session.setScrollTop(Math.max(-this.scrollMargin.top,Math.min(this.scrollTop,i-t.scrollerHeight+this.scrollMargin.bottom))),this.session.setScrollLeft(Math.max(-this.scrollMargin.left,Math.min(this.scrollLeft,u+2*this.$padding-t.scrollerWidth+this.scrollMargin.right)));var h=Math.ceil(o/this.lineHeight)-1,p=Math.max(0,Math.round((this.scrollTop-s)/this.lineHeight)),d=p+h,v,m,g=this.lineHeight;p=e.screenToDocumentRow(p,0);var y=e.getFoldLine(p);y&&(p=y.start.row),v=e.documentToScreenRow(p,0),m=e.getRowLength(p)*g,d=Math.min(e.screenToDocumentRow(d,0),e.getLength()-1),o=t.scrollerHeight+e.getRowLength(d)*g+m,s=this.scrollTop-v*g;var b=0;this.layerConfig.width!=u&&(b=this.CHANGE_H_SCROLL);if(f||c)b=this.$updateCachedSize(!0,this.gutterWidth,t.width,t.height),this._signal("scrollbarVisibilityChanged"),c&&(u=this.$getLongestLine());return this.layerConfig={width:u,padding:this.$padding,firstRow:p,firstRowScreen:v,lastRow:d,lineHeight:g,characterWidth:this.characterWidth,minHeight:o,maxHeight:i,offset:s,gutterOffset:Math.max(0,Math.ceil((s+t.height-t.scrollerHeight)/g)),height:this.$size.scrollerHeight},b},this.$updateLines=function(){var e=this.$changedLines.firstRow,t=this.$changedLines.lastRow;this.$changedLines=null;var n=this.layerConfig;if(e>n.lastRow+1)return;if(t<n.firstRow)return;if(t===Infinity){this.$showGutter&&this.$gutterLayer.update(n),this.$textLayer.update(n);return}return this.$textLayer.updateLines(n,e,t),!0},this.$getLongestLine=function(){var e=this.session.getScreenWidth();return this.showInvisibles&&!this.session.$useWrapMode&&(e+=1),Math.max(this.$size.scrollerWidth-2*this.$padding,Math.round(e*this.characterWidth))},this.updateFrontMarkers=function(){this.$markerFront.setMarkers(this.session.getMarkers(!0)),this.$loop.schedule(this.CHANGE_MARKER_FRONT)},this.updateBackMarkers=function(){this.$markerBack.setMarkers(this.session.getMarkers()),this.$loop.schedule(this.CHANGE_MARKER_BACK)},this.addGutterDecoration=function(e,t){this.$gutterLayer.addGutterDecoration(e,t)},this.removeGutterDecoration=function(e,t){this.$gutterLayer.removeGutterDecoration(e,t)},this.updateBreakpoints=function(e){this.$loop.schedule(this.CHANGE_GUTTER)},this.setAnnotations=function(e){this.$gutterLayer.setAnnotations(e),this.$loop.schedule(this.CHANGE_GUTTER)},this.updateCursor=function(){this.$loop.schedule(this.CHANGE_CURSOR)},this.hideCursor=function(){this.$cursorLayer.hideCursor()},this.showCursor=function(){this.$cursorLayer.showCursor()},this.scrollSelectionIntoView=function(e,t,n){this.scrollCursorIntoView(e,n),this.scrollCursorIntoView(t,n)},this.scrollCursorIntoView=function(e,t,n){if(this.$size.scrollerHeight===0)return;var r=this.$cursorLayer.getPixelPosition(e),i=r.left,s=r.top,o=n&&n.top||0,u=n&&n.bottom||0,a=this.$scrollAnimation?this.session.getScrollTop():this.scrollTop;a+o>s?(t&&(s-=t*this.$size.scrollerHeight),s===0&&(s=-this.scrollMargin.top),this.session.setScrollTop(s)):a+this.$size.scrollerHeight-u<s+this.lineHeight&&(t&&(s+=t*this.$size.scrollerHeight),this.session.setScrollTop(s+this.lineHeight-this.$size.scrollerHeight));var f=this.scrollLeft;f>i?(i<this.$padding+2*this.layerConfig.characterWidth&&(i=-this.scrollMargin.left),this.session.setScrollLeft(i)):f+this.$size.scrollerWidth<i+this.characterWidth?this.session.setScrollLeft(Math.round(i+this.characterWidth-this.$size.scrollerWidth)):f<=this.$padding&&i-f<this.characterWidth&&this.session.setScrollLeft(0)},this.getScrollTop=function(){return this.session.getScrollTop()},this.getScrollLeft=function(){return this.session.getScrollLeft()},this.getScrollTopRow=function(){return this.scrollTop/this.lineHeight},this.getScrollBottomRow=function(){return Math.max(0,Math.floor((this.scrollTop+this.$size.scrollerHeight)/this.lineHeight)-1)},this.scrollToRow=function(e){this.session.setScrollTop(e*this.lineHeight)},this.alignCursor=function(e,t){typeof e=="number"&&(e={row:e,column:0});var n=this.$cursorLayer.getPixelPosition(e),r=this.$size.scrollerHeight-this.lineHeight,i=n.top-r*(t||0);return this.session.setScrollTop(i),i},this.STEPS=8,this.$calcSteps=function(e,t){var n=0,r=this.STEPS,i=[],s=function(e,t,n){return n*(Math.pow(e-1,3)+1)+t};for(n=0;n<r;++n)i.push(s(n/this.STEPS,e,t-e));return i},this.scrollToLine=function(e,t,n,r){var i=this.$cursorLayer.getPixelPosition({row:e,column:0}),s=i.top;t&&(s-=this.$size.scrollerHeight/2);var o=this.scrollTop;this.session.setScrollTop(s),n!==!1&&this.animateScrolling(o,r)},this.animateScrolling=function(e,t){var n=this.scrollTop;if(!this.$animatedScroll)return;var r=this;if(e==n)return;if(this.$scrollAnimation){var i=this.$scrollAnimation.steps;if(i.length){e=i[0];if(e==n)return}}var s=r.$calcSteps(e,n);this.$scrollAnimation={from:e,to:n,steps:s},clearInterval(this.$timer),r.session.setScrollTop(s.shift()),r.session.$scrollTop=n,this.$timer=setInterval(function(){s.length?(r.session.setScrollTop(s.shift()),r.session.$scrollTop=n):n!=null?(r.session.$scrollTop=-1,r.session.setScrollTop(n),n=null):(r.$timer=clearInterval(r.$timer),r.$scrollAnimation=null,t&&t())},10)},this.scrollToY=function(e){this.scrollTop!==e&&(this.$loop.schedule(this.CHANGE_SCROLL),this.scrollTop=e)},this.scrollToX=function(e){this.scrollLeft!==e&&(this.scrollLeft=e),this.$loop.schedule(this.CHANGE_H_SCROLL)},this.scrollTo=function(e,t){this.session.setScrollTop(t),this.session.setScrollLeft(t)},this.scrollBy=function(e,t){t&&this.session.setScrollTop(this.session.getScrollTop()+t),e&&this.session.setScrollLeft(this.session.getScrollLeft()+e)},this.isScrollableBy=function(e,t){if(t<0&&this.session.getScrollTop()>=1-this.scrollMargin.top)return!0;if(t>0&&this.session.getScrollTop()+this.$size.scrollerHeight-this.layerConfig.maxHeight<-1+this.scrollMargin.bottom)return!0;if(e<0&&this.session.getScrollLeft()>=1-this.scrollMargin.left)return!0;if(e>0&&this.session.getScrollLeft()+this.$size.scrollerWidth-this.layerConfig.width<-1+this.scrollMargin.right)return!0},this.pixelToScreenCoordinates=function(e,t){var n=this.scroller.getBoundingClientRect(),r=(e+this.scrollLeft-n.left-this.$padding)/this.characterWidth,i=Math.floor((t+this.scrollTop-n.top)/this.lineHeight),s=Math.round(r);return{row:i,column:s,side:r-s>0?1:-1}},this.screenToTextCoordinates=function(e,t){var n=this.scroller.getBoundingClientRect(),r=Math.round((e+this.scrollLeft-n.left-this.$padding)/this.characterWidth),i=(t+this.scrollTop-n.top)/this.lineHeight;return this.session.screenToDocumentPosition(i,Math.max(r,0))},this.textToScreenCoordinates=function(e,t){var n=this.scroller.getBoundingClientRect(),r=this.session.documentToScreenPosition(e,t),i=this.$padding+Math.round(r.column*this.characterWidth),s=r.row*this.lineHeight;return{pageX:n.left+i-this.scrollLeft,pageY:n.top+s-this.scrollTop}},this.visualizeFocus=function(){i.addCssClass(this.container,"ace_focus")},this.visualizeBlur=function(){i.removeCssClass(this.container,"ace_focus")},this.showComposition=function(e){this.$composition||(this.$composition={keepTextAreaAtCursor:this.$keepTextAreaAtCursor,cssText:this.textarea.style.cssText}),this.$keepTextAreaAtCursor=!0,i.addCssClass(this.textarea,"ace_composition"),this.textarea.style.cssText="",this.$moveTextAreaToCursor()},this.setCompositionText=function(e){this.$moveTextAreaToCursor()},this.hideComposition=function(){if(!this.$composition)return;i.removeCssClass(this.textarea,"ace_composition"),this.$keepTextAreaAtCursor=this.$composition.keepTextAreaAtCursor,this.textarea.style.cssText=this.$composition.cssText,this.$composition=null},this.setTheme=function(e,t){function o(r){if(n.$themeId!=e)return t&&t();if(!r.cssClass)return;i.importCssString(r.cssText,r.cssClass,n.container.ownerDocument),n.theme&&i.removeCssClass(n.container,n.theme.cssClass);var s="padding"in r?r.padding:"padding"in(n.theme||{})?4:n.$padding;n.$padding&&s!=n.$padding&&n.setPadding(s),n.$theme=r.cssClass,n.theme=r,i.addCssClass(n.container,r.cssClass),i.setCssClass(n.container,"ace_dark",r.isDark),n.$size&&(n.$size.width=0,n.$updateSizeAsync()),n._dispatchEvent("themeLoaded",{theme:r}),t&&t()}var n=this;this.$themeId=e,n._dispatchEvent("themeChange",{theme:e});if(!e||typeof e=="string"){var r=e||this.$options.theme.initialValue;s.loadModule(["theme",r],o)}else o(e)},this.getTheme=function(){return this.$themeId},this.setStyle=function(e,t){i.setCssClass(this.container,e,t!==!1)},this.unsetStyle=function(e){i.removeCssClass(this.container,e)},this.setCursorStyle=function(e){this.content.style.cursor!=e&&(this.content.style.cursor=e)},this.setMouseCursor=function(e){this.content.style.cursor=e},this.destroy=function(){this.$textLayer.destroy(),this.$cursorLayer.destroy()}}).call(g.prototype),s.defineOptions(g.prototype,"renderer",{animatedScroll:{initialValue:!1},showInvisibles:{set:function(e){this.$textLayer.setShowInvisibles(e)&&this.$loop.schedule(this.CHANGE_TEXT)},initialValue:!1},showPrintMargin:{set:function(){this.$updatePrintMargin()},initialValue:!0},printMarginColumn:{set:function(){this.$updatePrintMargin()},initialValue:80},printMargin:{set:function(e){typeof e=="number"&&(this.$printMarginColumn=e),this.$showPrintMargin=!!e,this.$updatePrintMargin()},get:function(){return this.$showPrintMargin&&this.$printMarginColumn}},showGutter:{set:function(e){this.$gutter.style.display=e?"block":"none",this.$loop.schedule(this.CHANGE_FULL),this.onGutterResize()},initialValue:!0},fadeFoldWidgets:{set:function(e){i.setCssClass(this.$gutter,"ace_fade-fold-widgets",e)},initialValue:!1},showFoldWidgets:{set:function(e){this.$gutterLayer.setShowFoldWidgets(e)},initialValue:!0},showLineNumbers:{set:function(e){this.$gutterLayer.setShowLineNumbers(e),this.$loop.schedule(this.CHANGE_GUTTER)},initialValue:!0},displayIndentGuides:{set:function(e){this.$textLayer.setDisplayIndentGuides(e)&&this.$loop.schedule(this.CHANGE_TEXT)},initialValue:!0},highlightGutterLine:{set:function(e){if(!this.$gutterLineHighlight){this.$gutterLineHighlight=i.createElement("div"),this.$gutterLineHighlight.className="ace_gutter-active-line",this.$gutter.appendChild(this.$gutterLineHighlight);return}this.$gutterLineHighlight.style.display=e?"":"none",this.$cursorLayer.$pixelPos&&this.$updateGutterLineHighlight()},initialValue:!1,value:!0},hScrollBarAlwaysVisible:{set:function(e){(!this.$hScrollBarAlwaysVisible||!this.$horizScroll)&&this.$loop.schedule(this.CHANGE_SCROLL)},initialValue:!1},vScrollBarAlwaysVisible:{set:function(e){(!this.$vScrollBarAlwaysVisible||!this.$vScroll)&&this.$loop.schedule(this.CHANGE_SCROLL)},initialValue:!1},fontSize:{set:function(e){typeof e=="number"&&(e+="px"),this.container.style.fontSize=e,this.updateFontSize()},initialValue:12},fontFamily:{set:function(e){this.container.style.fontFamily=e,this.updateFontSize()}},maxLines:{set:function(e){this.updateFull()}},minLines:{set:function(e){this.updateFull()}},scrollPastEnd:{set:function(e){e=+e||0;if(this.$scrollPastEnd==e)return;this.$scrollPastEnd=e,this.$loop.schedule(this.CHANGE_SCROLL)},initialValue:0,handlesSet:!0},fixedWidthGutter:{set:function(e){this.$gutterLayer.$fixedWidth=!!e,this.$loop.schedule(this.CHANGE_GUTTER)}},theme:{set:function(e){this.setTheme(e)},get:function(){return this.$themeId||this.theme},initialValue:"./theme/textmate",handlesSet:!0}}),t.VirtualRenderer=g}),ace.define("ace/worker/worker_client",["require","exports","module","ace/lib/oop","ace/lib/net","ace/lib/event_emitter","ace/config"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("../lib/net"),s=e("../lib/event_emitter").EventEmitter,o=e("../config"),u=function(t,n,r,i){this.$sendDeltaQueue=this.$sendDeltaQueue.bind(this),this.changeListener=this.changeListener.bind(this),this.onMessage=this.onMessage.bind(this),e.nameToUrl&&!e.toUrl&&(e.toUrl=e.nameToUrl);if(o.get("packaged")||!e.toUrl)i=i||o.moduleUrl(n,"worker");else{var s=this.$normalizePath;i=i||s(e.toUrl("ace/worker/worker.js",null,"_"));var u={};t.forEach(function(t){u[t]=s(e.toUrl(t,null,"_").replace(/(\.js)?(\?.*)?$/,""))})}try{this.$worker=new Worker(i)}catch(a){if(!(a instanceof window.DOMException))throw a;var f=this.$workerBlob(i),l=window.URL||window.webkitURL,c=l.createObjectURL(f);this.$worker=new Worker(c),l.revokeObjectURL(c)}this.$worker.postMessage({init:!0,tlns:u,module:n,classname:r}),this.callbackId=1,this.callbacks={},this.$worker.onmessage=this.onMessage};(function(){r.implement(this,s),this.onMessage=function(e){var t=e.data;switch(t.type){case"log":window.console&&console.log&&console.log.apply(console,t.data);break;case"event":this._signal(t.name,{data:t.data});break;case"call":var n=this.callbacks[t.id];n&&(n(t.data),delete this.callbacks[t.id])}},this.$normalizePath=function(e){return i.qualifyURL(e)},this.terminate=function(){this._signal("terminate",{}),this.deltaQueue=null,this.$worker.terminate(),this.$worker=null,this.$doc.removeEventListener("change",this.changeListener),this.$doc=null},this.send=function(e,t){this.$worker.postMessage({command:e,args:t})},this.call=function(e,t,n){if(n){var r=this.callbackId++;this.callbacks[r]=n,t.push(r)}this.send(e,t)},this.emit=function(e,t){try{this.$worker.postMessage({event:e,data:{data:t.data}})}catch(n){console.error(n.stack)}},this.attachToDocument=function(e){this.$doc&&this.terminate(),this.$doc=e,this.call("setValue",[e.getValue()]),e.on("change",this.changeListener)},this.changeListener=function(e){this.deltaQueue?this.deltaQueue.push(e.data):(this.deltaQueue=[e.data],setTimeout(this.$sendDeltaQueue,0))},this.$sendDeltaQueue=function(){var e=this.deltaQueue;if(!e)return;this.deltaQueue=null,e.length>20&&e.length>this.$doc.getLength()>>1?this.call("setValue",[this.$doc.getValue()]):this.emit("change",{data:e})},this.$workerBlob=function(e){var t="importScripts('"+i.qualifyURL(e)+"');";try{return new Blob([t],{type:"application/javascript"})}catch(n){var r=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder,s=new r;return s.append(t),s.getBlob("application/javascript")}}}).call(u.prototype);var a=function(e,t,n){this.$sendDeltaQueue=this.$sendDeltaQueue.bind(this),this.changeListener=this.changeListener.bind(this),this.callbackId=1,this.callbacks={},this.messageBuffer=[];var r=null,i=!1,u=Object.create(s),a=this;this.$worker={},this.$worker.terminate=function(){},this.$worker.postMessage=function(e){a.messageBuffer.push(e),r&&(i?setTimeout(f):f())},this.setEmitSync=function(e){i=e};var f=function(){var e=a.messageBuffer.shift();e.command?r[e.command].apply(r,e.args):e.event&&u._signal(e.event,e.data)};u.postMessage=function(e){a.onMessage({data:e})},u.callback=function(e,t){this.postMessage({type:"call",id:t,data:e})},u.emit=function(e,t){this.postMessage({type:"event",name:e,data:t})},o.loadModule(["worker",t],function(e){r=new e[n](u);while(a.messageBuffer.length)f()})};a.prototype=u.prototype,t.UIWorkerClient=a,t.WorkerClient=u}),ace.define("ace/placeholder",["require","exports","module","ace/range","ace/lib/event_emitter","ace/lib/oop"],function(e,t,n){"use strict";var r=e("./range").Range,i=e("./lib/event_emitter").EventEmitter,s=e("./lib/oop"),o=function(e,t,n,r,i,s){var o=this;this.length=t,this.session=e,this.doc=e.getDocument(),this.mainClass=i,this.othersClass=s,this.$onUpdate=this.onUpdate.bind(this),this.doc.on("change",this.$onUpdate),this.$others=r,this.$onCursorChange=function(){setTimeout(function(){o.onCursorChange()})},this.$pos=n;var u=e.getUndoManager().$undoStack||e.getUndoManager().$undostack||{length:-1};this.$undoStackDepth=u.length,this.setup(),e.selection.on("changeCursor",this.$onCursorChange)};(function(){s.implement(this,i),this.setup=function(){var e=this,t=this.doc,n=this.session,i=this.$pos;this.pos=t.createAnchor(i.row,i.column),this.markerId=n.addMarker(new r(i.row,i.column,i.row,i.column+this.length),this.mainClass,null,!1),this.pos.on("change",function(t){n.removeMarker(e.markerId),e.markerId=n.addMarker(new r(t.value.row,t.value.column,t.value.row,t.value.column+e.length),e.mainClass,null,!1)}),this.others=[],this.$others.forEach(function(n){var r=t.createAnchor(n.row,n.column);e.others.push(r)}),n.setUndoSelect(!1)},this.showOtherMarkers=function(){if(this.othersActive)return;var e=this.session,t=this;this.othersActive=!0,this.others.forEach(function(n){n.markerId=e.addMarker(new r(n.row,n.column,n.row,n.column+t.length),t.othersClass,null,!1),n.on("change",function(i){e.removeMarker(n.markerId),n.markerId=e.addMarker(new r(i.value.row,i.value.column,i.value.row,i.value.column+t.length),t.othersClass,null,!1)})})},this.hideOtherMarkers=function(){if(!this.othersActive)return;this.othersActive=!1;for(var e=0;e<this.others.length;e++)this.session.removeMarker(this.others[e].markerId)},this.onUpdate=function(e){var t=e.data,n=t.range;if(n.start.row!==n.end.row)return;if(n.start.row!==this.pos.row)return;if(this.$updating)return;this.$updating=!0;var i=t.action==="insertText"?n.end.column-n.start.column:n.start.column-n.end.column;if(n.start.column>=this.pos.column&&n.start.column<=this.pos.column+this.length+1){var s=n.start.column-this.pos.column;this.length+=i;if(!this.session.$fromUndo){if(t.action==="insertText")for(var o=this.others.length-1;o>=0;o--){var u=this.others[o],a={row:u.row,column:u.column+s};u.row===n.start.row&&n.start.column<u.column&&(a.column+=i),this.doc.insert(a,t.text)}else if(t.action==="removeText")for(var o=this.others.length-1;o>=0;o--){var u=this.others[o],a={row:u.row,column:u.column+s};u.row===n.start.row&&n.start.column<u.column&&(a.column+=i),this.doc.remove(new r(a.row,a.column,a.row,a.column-i))}n.start.column===this.pos.column&&t.action==="insertText"?setTimeout(function(){this.pos.setPosition(this.pos.row,this.pos.column-i);for(var e=0;e<this.others.length;e++){var t=this.others[e],r={row:t.row,column:t.column-i};t.row===n.start.row&&n.start.column<t.column&&(r.column+=i),t.setPosition(r.row,r.column)}}.bind(this),0):n.start.column===this.pos.column&&t.action==="removeText"&&setTimeout(function(){for(var e=0;e<this.others.length;e++){var t=this.others[e];t.row===n.start.row&&n.start.column<t.column&&t.setPosition(t.row,t.column-i)}}.bind(this),0)}this.pos._emit("change",{value:this.pos});for(var o=0;o<this.others.length;o++)this.others[o]._emit("change",{value:this.others[o]})}this.$updating=!1},this.onCursorChange=function(e){if(this.$updating)return;var t=this.session.selection.getCursor();t.row===this.pos.row&&t.column>=this.pos.column&&t.column<=this.pos.column+this.length?(this.showOtherMarkers(),this._emit("cursorEnter",e)):(this.hideOtherMarkers(),this._emit("cursorLeave",e))},this.detach=function(){this.session.removeMarker(this.markerId),this.hideOtherMarkers(),this.doc.removeEventListener("change",this.$onUpdate),this.session.selection.removeEventListener("changeCursor",this.$onCursorChange),this.pos.detach();for(var e=0;e<this.others.length;e++)this.others[e].detach();this.session.setUndoSelect(!0)},this.cancel=function(){if(this.$undoStackDepth===-1)throw Error("Canceling placeholders only supported with undo manager attached to session.");var e=this.session.getUndoManager(),t=(e.$undoStack||e.$undostack).length-this.$undoStackDepth;for(var n=0;n<t;n++)e.undo(!0)}}).call(o.prototype),t.PlaceHolder=o}),ace.define("ace/mouse/multi_select_handler",["require","exports","module","ace/lib/event","ace/lib/useragent"],function(e,t,n){function s(e,t){return e.row==t.row&&e.column==t.column}function o(e){var t=e.domEvent,n=t.altKey,o=t.shiftKey,u=t.ctrlKey,a=e.getAccelKey(),f=e.getButton();u&&i.isMac&&(f=t.button);if(e.editor.inMultiSelectMode&&f==2){e.editor.textInput.onContextMenu(e.domEvent);return}if(!u&&!n&&!a){f===0&&e.editor.inMultiSelectMode&&e.editor.exitMultiSelectMode();return}if(f!==0)return;var l=e.editor,c=l.selection,h=l.inMultiSelectMode,p=e.getDocumentPosition(),d=c.getCursor(),v=e.inSelection()||c.isEmpty()&&s(p,d),m=e.x,g=e.y,y=function(e){m=e.clientX,g=e.clientY},b=l.session,w=l.renderer.pixelToScreenCoordinates(m,g),E=w,S;if(l.$mouseHandler.$enableJumpToDef)u&&n||a&&n?S="add":n&&(S="block");else if(a&&!n){S="add";if(!h&&o)return}else n&&(S="block");S&&i.isMac&&t.ctrlKey&&l.$mouseHandler.cancelContextMenu();if(S=="add"){if(!h&&v)return;if(!h){var x=c.toOrientedRange();l.addSelectionMarker(x)}var T=c.rangeList.rangeAtPoint(p);l.$blockScrolling++,l.inVirtualSelectionMode=!0,o&&(T=null,x=c.ranges[0],l.removeSelectionMarker(x)),l.once("mouseup",function(){var e=c.toOrientedRange();T&&e.isEmpty()&&s(T.cursor,e.cursor)?c.substractPoint(e.cursor):(o?c.substractPoint(x.cursor):x&&(l.removeSelectionMarker(x),c.addRange(x)),c.addRange(e)),l.$blockScrolling--,l.inVirtualSelectionMode=!1})}else if(S=="block"){e.stop(),l.inVirtualSelectionMode=!0;var N,C=[],k=function(){var e=l.renderer.pixelToScreenCoordinates(m,g),t=b.screenToDocumentPosition(e.row,e.column);if(s(E,e)&&s(t,c.lead))return;E=e,l.selection.moveToPosition(t),l.renderer.scrollCursorIntoView(),l.removeSelectionMarkers(C),C=c.rectangularRangeBlock(E,w),l.$mouseHandler.$clickSelection&&C.length==1&&C[0].isEmpty()&&(C[0]=l.$mouseHandler.$clickSelection.clone()),C.forEach(l.addSelectionMarker,l),l.updateSelectionMarkers()};h&&!a?c.toSingleRange():!h&&a&&(N=c.toOrientedRange(),l.addSelectionMarker(N)),o?w=b.documentToScreenPosition(c.lead):c.moveToPosition(p),E={row:-1,column:-1};var L=function(e){clearInterval(O),l.removeSelectionMarkers(C),C.length||(C=[c.toOrientedRange()]),l.$blockScrolling++,N&&(l.removeSelectionMarker(N),c.toSingleRange(N));for(var t=0;t<C.length;t++)c.addRange(C[t]);l.inVirtualSelectionMode=!1,l.$mouseHandler.$clickSelection=null,l.$blockScrolling--},A=k;r.capture(l.container,y,L);var O=setInterval(function(){A()},20);return e.preventDefault()}}var r=e("../lib/event"),i=e("../lib/useragent");t.onMouseDown=o}),ace.define("ace/commands/multi_select_commands",["require","exports","module","ace/keyboard/hash_handler"],function(e,t,n){t.defaultCommands=[{name:"addCursorAbove",exec:function(e){e.selectMoreLines(-1)},bindKey:{win:"Ctrl-Alt-Up",mac:"Ctrl-Alt-Up"},readonly:!0},{name:"addCursorBelow",exec:function(e){e.selectMoreLines(1)},bindKey:{win:"Ctrl-Alt-Down",mac:"Ctrl-Alt-Down"},readonly:!0},{name:"addCursorAboveSkipCurrent",exec:function(e){e.selectMoreLines(-1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Up",mac:"Ctrl-Alt-Shift-Up"},readonly:!0},{name:"addCursorBelowSkipCurrent",exec:function(e){e.selectMoreLines(1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Down",mac:"Ctrl-Alt-Shift-Down"},readonly:!0},{name:"selectMoreBefore",exec:function(e){e.selectMore(-1)},bindKey:{win:"Ctrl-Alt-Left",mac:"Ctrl-Alt-Left"},readonly:!0},{name:"selectMoreAfter",exec:function(e){e.selectMore(1)},bindKey:{win:"Ctrl-Alt-Right",mac:"Ctrl-Alt-Right"},readonly:!0},{name:"selectNextBefore",exec:function(e){e.selectMore(-1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Left",mac:"Ctrl-Alt-Shift-Left"},readonly:!0},{name:"selectNextAfter",exec:function(e){e.selectMore(1,!0)},bindKey:{win:"Ctrl-Alt-Shift-Right",mac:"Ctrl-Alt-Shift-Right"},readonly:!0},{name:"splitIntoLines",exec:function(e){e.multiSelect.splitIntoLines()},bindKey:{win:"Ctrl-Alt-L",mac:"Ctrl-Alt-L"},readonly:!0},{name:"alignCursors",exec:function(e){e.alignCursors()},bindKey:{win:"Ctrl-Alt-A",mac:"Ctrl-Alt-A"}},{name:"findAll",exec:function(e){e.findAll()},bindKey:{win:"Ctrl-Alt-K",mac:"Ctrl-Alt-G"},readonly:!0}],t.multiSelectCommands=[{name:"singleSelection",bindKey:"esc",exec:function(e){e.exitMultiSelectMode()},readonly:!0,isAvailable:function(e){return e&&e.inMultiSelectMode}}];var r=e("../keyboard/hash_handler").HashHandler;t.keyboardHandler=new r(t.multiSelectCommands)}),ace.define("ace/multi_select",["require","exports","module","ace/range_list","ace/range","ace/selection","ace/mouse/multi_select_handler","ace/lib/event","ace/lib/lang","ace/commands/multi_select_commands","ace/search","ace/edit_session","ace/editor","ace/config"],function(e,t,n){function h(e,t,n){return c.$options.wrap=!0,c.$options.needle=t,c.$options.backwards=n==-1,c.find(e)}function v(e,t){return e.row==t.row&&e.column==t.column}function m(e){if(e.$multiselectOnSessionChange)return;e.$onAddRange=e.$onAddRange.bind(e),e.$onRemoveRange=e.$onRemoveRange.bind(e),e.$onMultiSelect=e.$onMultiSelect.bind(e),e.$onSingleSelect=e.$onSingleSelect.bind(e),e.$multiselectOnSessionChange=t.onSessionChange.bind(e),e.$checkMultiselectChange=e.$checkMultiselectChange.bind(e),e.$multiselectOnSessionChange(e),e.on("changeSession",e.$multiselectOnSessionChange),e.on("mousedown",o),e.commands.addCommands(f.defaultCommands),g(e)}function g(e){function r(t){n&&(e.renderer.setMouseCursor(""),n=!1)}var t=e.textInput.getElement(),n=!1;u.addListener(t,"keydown",function(t){t.keyCode==18&&!(t.ctrlKey||t.shiftKey||t.metaKey)?n||(e.renderer.setMouseCursor("crosshair"),n=!0):n&&r()}),u.addListener(t,"keyup",r),u.addListener(t,"blur",r)}var r=e("./range_list").RangeList,i=e("./range").Range,s=e("./selection").Selection,o=e("./mouse/multi_select_handler").onMouseDown,u=e("./lib/event"),a=e("./lib/lang"),f=e("./commands/multi_select_commands");t.commands=f.defaultCommands.concat(f.multiSelectCommands);var l=e("./search").Search,c=new l,p=e("./edit_session").EditSession;(function(){this.getSelectionMarkers=function(){return this.$selectionMarkers}}).call(p.prototype),function(){this.ranges=null,this.rangeList=null,this.addRange=function(e,t){if(!e)return;if(!this.inMultiSelectMode&&this.rangeCount===0){var n=this.toOrientedRange();this.rangeList.add(n),this.rangeList.add(e);if(this.rangeList.ranges.length!=2)return this.rangeList.removeAll(),t||this.fromOrientedRange(e);this.rangeList.removeAll(),this.rangeList.add(n),this.$onAddRange(n)}e.cursor||(e.cursor=e.end);var r=this.rangeList.add(e);return this.$onAddRange(e),r.length&&this.$onRemoveRange(r),this.rangeCount>1&&!this.inMultiSelectMode&&(this._signal("multiSelect"),this.inMultiSelectMode=!0,this.session.$undoSelect=!1,this.rangeList.attach(this.session)),t||this.fromOrientedRange(e)},this.toSingleRange=function(e){e=e||this.ranges[0];var t=this.rangeList.removeAll();t.length&&this.$onRemoveRange(t),e&&this.fromOrientedRange(e)},this.substractPoint=function(e){var t=this.rangeList.substractPoint(e);if(t)return this.$onRemoveRange(t),t[0]},this.mergeOverlappingRanges=function(){var e=this.rangeList.merge();e.length?this.$onRemoveRange(e):this.ranges[0]&&this.fromOrientedRange(this.ranges[0])},this.$onAddRange=function(e){this.rangeCount=this.rangeList.ranges.length,this.ranges.unshift(e),this._signal("addRange",{range:e})},this.$onRemoveRange=function(e){this.rangeCount=this.rangeList.ranges.length;if(this.rangeCount==1&&this.inMultiSelectMode){var t=this.rangeList.ranges.pop();e.push(t),this.rangeCount=0}for(var n=e.length;n--;){var r=this.ranges.indexOf(e[n]);this.ranges.splice(r,1)}this._signal("removeRange",{ranges:e}),this.rangeCount===0&&this.inMultiSelectMode&&(this.inMultiSelectMode=!1,this._signal("singleSelect"),this.session.$undoSelect=!0,this.rangeList.detach(this.session)),t=t||this.ranges[0],t&&!t.isEqual(this.getRange())&&this.fromOrientedRange(t)},this.$initRangeList=function(){if(this.rangeList)return;this.rangeList=new r,this.ranges=[],this.rangeCount=0},this.getAllRanges=function(){return this.rangeCount?this.rangeList.ranges.concat():[this.getRange()]},this.splitIntoLines=function(){if(this.rangeCount>1){var e=this.rangeList.ranges,t=e[e.length-1],n=i.fromPoints(e[0].start,t.end);this.toSingleRange(),this.setSelectionRange(n,t.cursor==t.start)}else{var n=this.getRange(),r=this.isBackwards(),s=n.start.row,o=n.end.row;if(s==o){if(r)var u=n.end,a=n.start;else var u=n.start,a=n.end;this.addRange(i.fromPoints(a,a)),this.addRange(i.fromPoints(u,u));return}var f=[],l=this.getLineRange(s,!0);l.start.column=n.start.column,f.push(l);for(var c=s+1;c<o;c++)f.push(this.getLineRange(c,!0));l=this.getLineRange(o,!0),l.end.column=n.end.column,f.push(l),f.forEach(this.addRange,this)}},this.toggleBlockSelection=function(){if(this.rangeCount>1){var e=this.rangeList.ranges,t=e[e.length-1],n=i.fromPoints(e[0].start,t.end);this.toSingleRange(),this.setSelectionRange(n,t.cursor==t.start)}else{var r=this.session.documentToScreenPosition(this.selectionLead),s=this.session.documentToScreenPosition(this.selectionAnchor),o=this.rectangularRangeBlock(r,s);o.forEach(this.addRange,this)}},this.rectangularRangeBlock=function(e,t,n){var r=[],s=e.column<t.column;if(s)var o=e.column,u=t.column;else var o=t.column,u=e.column;var a=e.row<t.row;if(a)var f=e.row,l=t.row;else var f=t.row,l=e.row;o<0&&(o=0),f<0&&(f=0),f==l&&(n=!0);for(var c=f;c<=l;c++){var h=i.fromPoints(this.session.screenToDocumentPosition(c,o),this.session.screenToDocumentPosition(c,u));if(h.isEmpty()){if(p&&v(h.end,p))break;var p=h.end}h.cursor=s?h.start:h.end,r.push(h)}a&&r.reverse();if(!n){var d=r.length-1;while(r[d].isEmpty()&&d>0)d--;if(d>0){var m=0;while(r[m].isEmpty())m++}for(var g=d;g>=m;g--)r[g].isEmpty()&&r.splice(g,1)}return r}}.call(s.prototype);var d=e("./editor").Editor;(function(){this.updateSelectionMarkers=function(){this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.addSelectionMarker=function(e){e.cursor||(e.cursor=e.end);var t=this.getSelectionStyle();return e.marker=this.session.addMarker(e,"ace_selection",t),this.session.$selectionMarkers.push(e),this.session.selectionMarkerCount=this.session.$selectionMarkers.length,e},this.removeSelectionMarker=function(e){if(!e.marker)return;this.session.removeMarker(e.marker);var t=this.session.$selectionMarkers.indexOf(e);t!=-1&&this.session.$selectionMarkers.splice(t,1),this.session.selectionMarkerCount=this.session.$selectionMarkers.length},this.removeSelectionMarkers=function(e){var t=this.session.$selectionMarkers;for(var n=e.length;n--;){var r=e[n];if(!r.marker)continue;this.session.removeMarker(r.marker);var i=t.indexOf(r);i!=-1&&t.splice(i,1)}this.session.selectionMarkerCount=t.length},this.$onAddRange=function(e){this.addSelectionMarker(e.range),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onRemoveRange=function(e){this.removeSelectionMarkers(e.ranges),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onMultiSelect=function(e){if(this.inMultiSelectMode)return;this.inMultiSelectMode=!0,this.setStyle("ace_multiselect"),this.keyBinding.addKeyboardHandler(f.keyboardHandler),this.commands.setDefaultHandler("exec",this.$onMultiSelectExec),this.renderer.updateCursor(),this.renderer.updateBackMarkers()},this.$onSingleSelect=function(e){if(this.session.multiSelect.inVirtualMode)return;this.inMultiSelectMode=!1,this.unsetStyle("ace_multiselect"),this.keyBinding.removeKeyboardHandler(f.keyboardHandler),this.commands.removeDefaultHandler("exec",this.$onMultiSelectExec),this.renderer.updateCursor(),this.renderer.updateBackMarkers(),this._emit("changeSelection")},this.$onMultiSelectExec=function(e){var t=e.command,n=e.editor;if(!n.multiSelect)return;if(!t.multiSelectAction){var r=t.exec(n,e.args||{});n.multiSelect.addRange(n.multiSelect.toOrientedRange()),n.multiSelect.mergeOverlappingRanges()}else t.multiSelectAction=="forEach"?r=n.forEachSelection(t,e.args):t.multiSelectAction=="forEachLine"?r=n.forEachSelection(t,e.args,!0):t.multiSelectAction=="single"?(n.exitMultiSelectMode(),r=t.exec(n,e.args||{})):r=t.multiSelectAction(n,e.args||{});return r},this.forEachSelection=function(e,t,n){if(this.inVirtualSelectionMode)return;var r=n&&n.keepOrder,i=n==1||n&&n.$byLines,o=this.session,u=this.selection,a=u.rangeList,f=(r?u:a).ranges,l;if(!f.length)return e.exec?e.exec(this,t||{}):e(this,t||{});var c=u._eventRegistry;u._eventRegistry={};var h=new s(o);this.inVirtualSelectionMode=!0;for(var p=f.length;p--;){if(i)while(p>0&&f[p].start.row==f[p-1].end.row)p--;h.fromOrientedRange(f[p]),h.index=p,this.selection=o.selection=h;var d=e.exec?e.exec(this,t||{}):e(this,t||{});!l&&d!==undefined&&(l=d),h.toOrientedRange(f[p])}h.detach(),this.selection=o.selection=u,this.inVirtualSelectionMode=!1,u._eventRegistry=c,u.mergeOverlappingRanges();var v=this.renderer.$scrollAnimation;return this.onCursorChange(),this.onSelectionChange(),v&&v.from==v.to&&this.renderer.animateScrolling(v.from),l},this.exitMultiSelectMode=function(){if(!this.inMultiSelectMode||this.inVirtualSelectionMode)return;this.multiSelect.toSingleRange()},this.getSelectedText=function(){var e="";if(this.inMultiSelectMode&&!this.inVirtualSelectionMode){var t=this.multiSelect.rangeList.ranges,n=[];for(var r=0;r<t.length;r++)n.push(this.session.getTextRange(t[r]));var i=this.session.getDocument().getNewLineCharacter();e=n.join(i),e.length==(n.length-1)*i.length&&(e="")}else this.selection.isEmpty()||(e=this.session.getTextRange(this.getSelectionRange()));return e},this.$checkMultiselectChange=function(e,t){if(this.inMultiSelectMode&&!this.inVirtualSelectionMode){var n=this.multiSelect.ranges[0];if(this.multiSelect.isEmpty()&&t==this.multiSelect.anchor)return;var r=t==this.multiSelect.anchor?n.cursor==n.start?n.end:n.start:n.cursor;v(r,t)||this.multiSelect.toSingleRange(this.multiSelect.toOrientedRange())}},this.onPaste=function(e){if(this.$readOnly)return;var t={text:e};this._signal("paste",t),e=t.text;if(!this.inMultiSelectMode||this.inVirtualSelectionMode)return this.insert(e);var n=e.split(/\r\n|\r|\n/),r=this.selection.rangeList.ranges;if(n.length>r.length||n.length<2||!n[1])return this.commands.exec("insertstring",this,e);for(var i=r.length;i--;){var s=r[i];s.isEmpty()||this.session.remove(s),this.session.insert(s.start,n[i])}},this.findAll=function(e,t,n){t=t||{},t.needle=e||t.needle;if(t.needle==undefined){var r=this.selection.isEmpty()?this.selection.getWordRange():this.selection.getRange();t.needle=this.session.getTextRange(r)}this.$search.set(t);var i=this.$search.findAll(this.session);if(!i.length)return 0;this.$blockScrolling+=1;var s=this.multiSelect;n||s.toSingleRange(i[0]);for(var o=i.length;o--;)s.addRange(i[o],!0);return r&&s.rangeList.rangeAtPoint(r.start)&&s.addRange(r,!0),this.$blockScrolling-=1,i.length},this.selectMoreLines=function(e,t){var n=this.selection.toOrientedRange(),r=n.cursor==n.end,s=this.session.documentToScreenPosition(n.cursor);this.selection.$desiredColumn&&(s.column=this.selection.$desiredColumn);var o=this.session.screenToDocumentPosition(s.row+e,s.column);if(!n.isEmpty())var u=this.session.documentToScreenPosition(r?n.end:n.start),a=this.session.screenToDocumentPosition(u.row+e,u.column);else var a=o;if(r){var f=i.fromPoints(o,a);f.cursor=f.start}else{var f=i.fromPoints(a,o);f.cursor=f.end}f.desiredColumn=s.column;if(!this.selection.inMultiSelectMode)this.selection.addRange(n);else if(t)var l=n.cursor;this.selection.addRange(f),l&&this.selection.substractPoint(l)},this.transposeSelections=function(e){var t=this.session,n=t.multiSelect,r=n.ranges;for(var i=r.length;i--;){var s=r[i];if(s.isEmpty()){var o=t.getWordRange(s.start.row,s.start.column);s.start.row=o.start.row,s.start.column=o.start.column,s.end.row=o.end.row,s.end.column=o.end.column}}n.mergeOverlappingRanges();var u=[];for(var i=r.length;i--;){var s=r[i];u.unshift(t.getTextRange(s))}e<0?u.unshift(u.pop()):u.push(u.shift());for(var i=r.length;i--;){var s=r[i],o=s.clone();t.replace(s,u[i]),s.start.row=o.start.row,s.start.column=o.start.column}},this.selectMore=function(e,t){var n=this.session,r=n.multiSelect,i=r.toOrientedRange();i.isEmpty()&&(i=n.getWordRange(i.start.row,i.start.column),i.cursor=e==-1?i.start:i.end,this.multiSelect.addRange(i));var s=n.getTextRange(i),o=h(n,s,e);o&&(o.cursor=e==-1?o.start:o.end,this.$blockScrolling+=1,this.session.unfold(o),this.multiSelect.addRange(o),this.$blockScrolling-=1,this.renderer.scrollCursorIntoView(null,.5)),t&&this.multiSelect.substractPoint(i.cursor)},this.alignCursors=function(){var e=this.session,t=e.multiSelect,n=t.ranges,r=-1,s=n.filter(function(e){if(e.cursor.row==r)return!0;r=e.cursor.row});if(!n.length||s.length==n.length-1){var o=this.selection.getRange(),u=o.start.row,f=o.end.row,l=u==f;if(l){var c=this.session.getLength(),h;do h=this.session.getLine(f);while(/[=:]/.test(h)&&++f<c);do h=this.session.getLine(u);while(/[=:]/.test(h)&&--u>0);u<0&&(u=0),f>=c&&(f=c-1)}var p=this.session.doc.removeLines(u,f);p=this.$reAlignText(p,l),this.session.doc.insert({row:u,column:0},p.join("\n")+"\n"),l||(o.start.column=0,o.end.column=p[p.length-1].length),this.selection.setRange(o)}else{s.forEach(function(e){t.substractPoint(e.cursor)});var d=0,v=Infinity,m=n.map(function(t){var n=t.cursor,r=e.getLine(n.row),i=r.substr(n.column).search(/\S/g);return i==-1&&(i=0),n.column>d&&(d=n.column),i<v&&(v=i),i});n.forEach(function(t,n){var r=t.cursor,s=d-r.column,o=m[n]-v;s>o?e.insert(r,a.stringRepeat(" ",s-o)):e.remove(new i(r.row,r.column,r.row,r.column-s+o)),t.start.column=t.end.column=d,t.start.row=t.end.row=r.row,t.cursor=t.end}),t.fromOrientedRange(n[0]),this.renderer.updateCursor(),this.renderer.updateBackMarkers()}},this.$reAlignText=function(e,t){function u(e){return a.stringRepeat(" ",e)}function f(e){return e[2]?u(i)+e[2]+u(s-e[2].length+o)+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}function l(e){return e[2]?u(i+s-e[2].length)+e[2]+u(o," ")+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}function c(e){return e[2]?u(i)+e[2]+u(o)+e[4].replace(/^([=:])\s+/,"$1 "):e[0]}var n=!0,r=!0,i,s,o;return e.map(function(e){var t=e.match(/(\s*)(.*?)(\s*)([=:].*)/);return t?i==null?(i=t[1].length,s=t[2].length,o=t[3].length,t):(i+s+o!=t[1].length+t[2].length+t[3].length&&(r=!1),i!=t[1].length&&(n=!1),i>t[1].length&&(i=t[1].length),s<t[2].length&&(s=t[2].length),o>t[3].length&&(o=t[3].length),t):[e]}).map(t?f:n?r?l:f:c)}}).call(d.prototype),t.onSessionChange=function(e){var t=e.session;t.multiSelect||(t.$selectionMarkers=[],t.selection.$initRangeList(),t.multiSelect=t.selection),this.multiSelect=t.multiSelect;var n=e.oldSession;n&&(n.multiSelect.off("addRange",this.$onAddRange),n.multiSelect.off("removeRange",this.$onRemoveRange),n.multiSelect.off("multiSelect",this.$onMultiSelect),n.multiSelect.off("singleSelect",this.$onSingleSelect),n.multiSelect.lead.off("change",this.$checkMultiselectChange),n.multiSelect.anchor.off("change",this.$checkMultiselectChange)),t.multiSelect.on("addRange",this.$onAddRange),t.multiSelect.on("removeRange",this.$onRemoveRange),t.multiSelect.on("multiSelect",this.$onMultiSelect),t.multiSelect.on("singleSelect",this.$onSingleSelect),t.multiSelect.lead.on("change",this.$checkMultiselectChange),t.multiSelect.anchor.on("change",this.$checkMultiselectChange),this.inMultiSelectMode!=t.selection.inMultiSelectMode&&(t.selection.inMultiSelectMode?this.$onMultiSelect():this.$onSingleSelect())},t.MultiSelect=m,e("./config").defineOptions(d.prototype,"editor",{enableMultiselect:{set:function(e){m(this),e?(this.on("changeSession",this.$multiselectOnSessionChange),this.on("mousedown",o)):(this.off("changeSession",this.$multiselectOnSessionChange),this.off("mousedown",o))},value:!0}})}),ace.define("ace/mode/folding/fold_mode",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../../range").Range,i=t.FoldMode=function(){};(function(){this.foldingStartMarker=null,this.foldingStopMarker=null,this.getFoldWidget=function(e,t,n){var r=e.getLine(n);return this.foldingStartMarker.test(r)?"start":t=="markbeginend"&&this.foldingStopMarker&&this.foldingStopMarker.test(r)?"end":""},this.getFoldWidgetRange=function(e,t,n){return null},this.indentationBlock=function(e,t,n){var i=/\S/,s=e.getLine(t),o=s.search(i);if(o==-1)return;var u=n||s.length,a=e.getLength(),f=t,l=t;while(++t<a){var c=e.getLine(t).search(i);if(c==-1)continue;if(c<=o)break;l=t}if(l>f){var h=e.getLine(l).length;return new r(f,u,l,h)}},this.openingBracketBlock=function(e,t,n,i,s){var o={row:n,column:i+1},u=e.$findClosingBracket(t,o,s);if(!u)return;var a=e.foldWidgets[u.row];return a==null&&(a=e.getFoldWidget(u.row)),a=="start"&&u.row>o.row&&(u.row--,u.column=e.getLine(u.row).length),r.fromPoints(o,u)},this.closingBracketBlock=function(e,t,n,i,s){var o={row:n,column:i},u=e.$findOpeningBracket(t,o);if(!u)return;return u.column++,o.column--,r.fromPoints(u,o)}}).call(i.prototype)}),ace.define("ace/theme/textmate",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";t.isDark=!1,t.cssClass="ace-tm",t.cssText='.ace-tm .ace_gutter {background: #f0f0f0;color: #333;}.ace-tm .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-tm .ace_fold {background-color: #6B72E6;}.ace-tm {background-color: #FFFFFF;color: black;}.ace-tm .ace_cursor {color: black;}.ace-tm .ace_invisible {color: rgb(191, 191, 191);}.ace-tm .ace_storage,.ace-tm .ace_keyword {color: blue;}.ace-tm .ace_constant {color: rgb(197, 6, 11);}.ace-tm .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-tm .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-tm .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-tm .ace_invalid {background-color: rgba(255, 0, 0, 0.1);color: red;}.ace-tm .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-tm .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-tm .ace_support.ace_type,.ace-tm .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-tm .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-tm .ace_string {color: rgb(3, 106, 7);}.ace-tm .ace_comment {color: rgb(76, 136, 107);}.ace-tm .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-tm .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-tm .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-tm .ace_variable {color: rgb(49, 132, 149);}.ace-tm .ace_xml-pe {color: rgb(104, 104, 91);}.ace-tm .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-tm .ace_heading {color: rgb(12, 7, 255);}.ace-tm .ace_list {color:rgb(185, 6, 144);}.ace-tm .ace_meta.ace_tag {color:rgb(0, 22, 142);}.ace-tm .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-tm .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-tm.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;border-radius: 2px;}.ace-tm .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-tm .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-tm .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-tm .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-tm .ace_gutter-active-line {background-color : #dcdcdc;}.ace-tm .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-tm .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}),ace.define("ace/line_widgets",["require","exports","module","ace/lib/oop","ace/lib/dom","ace/range"],function(e,t,n){"use strict";function o(e){this.session=e,this.session.widgetManager=this,this.session.getRowLength=this.getRowLength,this.session.$getWidgetScreenLength=this.$getWidgetScreenLength,this.updateOnChange=this.updateOnChange.bind(this),this.renderWidgets=this.renderWidgets.bind(this),this.measureWidgets=this.measureWidgets.bind(this),this.session._changedWidgets=[],this.detach=this.detach.bind(this),this.session.on("change",this.updateOnChange)}var r=e("./lib/oop"),i=e("./lib/dom"),s=e("./range").Range;(function(){this.getRowLength=function(e){var t;return this.lineWidgets?t=this.lineWidgets[e]&&this.lineWidgets[e].rowCount||0:t=0,!this.$useWrapMode||!this.$wrapData[e]?1+t:this.$wrapData[e].length+1+t},this.$getWidgetScreenLength=function(){var e=0;return this.lineWidgets.forEach(function(t){t&&t.rowCount&&(e+=t.rowCount)}),e},this.attach=function(e){e.widgetManager&&e.widgetManager!=this&&e.widgetManager.detach();if(this.editor==e)return;this.detach(),this.editor=e,this.editor.on("changeSession",this.detach),e.widgetManager=this,e.renderer.on("beforeRender",this.measureWidgets),e.renderer.on("afterRender",this.renderWidgets)},this.detach=function(e){if(e&&e.session==this.session)return;var t=this.editor;if(!t)return;t.off("changeSession",this.detach),this.editor=null,t.widgetManager=null,t.renderer.off("beforeRender",this.measureWidgets),t.renderer.off("afterRender",this.renderWidgets);var n=this.session.lineWidgets;n&&n.forEach(function(e){e&&e.el&&e.el.parentNode&&(e._inDocument=!1,e.el.parentNode.removeChild(e.el))})},this.updateOnChange=function(e){var t=this.session.lineWidgets;if(!t)return;var n=e.data,r=n.range,i=r.start.row,s=r.end.row-i;if(s!==0)if(n.action=="removeText"||n.action=="removeLines"){var o=t.splice(i+1,s);o.forEach(function(e){e&&this.removeLineWidget(e)},this),this.$updateRows()}else{var u=new Array(s);u.unshift(i,0),t.splice.apply(t,u),this.$updateRows()}},this.$updateRows=function(){var e=this.session.lineWidgets;if(!e)return;var t=!0;e.forEach(function(e,n){e&&(t=!1,e.row=n)}),t&&(this.session.lineWidgets=null)},this.addLineWidget=function(e){this.session.lineWidgets||(this.session.lineWidgets=new Array(this.session.getLength())),this.session.lineWidgets[e.row]=e;var t=this.editor.renderer;return e.html&&!e.el&&(e.el=i.createElement("div"),e.el.innerHTML=e.html),e.el&&(i.addCssClass(e.el,"ace_lineWidgetContainer"),e.el.style.position="absolute",e.el.style.zIndex=5,t.container.appendChild(e.el),e._inDocument=!0),e.coverGutter||(e.el.style.zIndex=3),e.pixelHeight||(e.pixelHeight=e.el.offsetHeight),e.rowCount==null&&(e.rowCount=e.pixelHeight/t.layerConfig.lineHeight),this.session._emit("changeFold",{data:{start:{row:e.row}}}),this.$updateRows(),this.renderWidgets(null,t),e},this.removeLineWidget=function(e){e._inDocument=!1,e.el&&e.el.parentNode&&e.el.parentNode.removeChild(e.el);if(e.editor&&e.editor.destroy)try{e.editor.destroy()}catch(t){}this.session.lineWidgets&&(this.session.lineWidgets[e.row]=undefined),this.session._emit("changeFold",{data:{start:{row:e.row}}}),this.$updateRows()},this.onWidgetChanged=function(e){this.session._changedWidgets.push(e),this.editor&&this.editor.renderer.updateFull()},this.measureWidgets=function(e,t){var n=this.session._changedWidgets,r=t.layerConfig;if(!n||!n.length)return;var i=Infinity;for(var s=0;s<n.length;s++){var o=n[s];o._inDocument||(o._inDocument=!0,t.container.appendChild(o.el)),o.h=o.el.offsetHeight,o.fixedWidth||(o.w=o.el.offsetWidth,o.screenWidth=Math.ceil(o.w/r.characterWidth));var u=o.h/r.lineHeight;o.coverLine&&(u-=this.session.getRowLineCount(o.row),u<0&&(u=0)),o.rowCount!=u&&(o.rowCount=u,o.row<i&&(i=o.row))}i!=Infinity&&(this.session._emit("changeFold",{data:{start:{row:i}}}),this.session.lineWidgetWidth=null),this.session._changedWidgets=[]},this.renderWidgets=function(e,t){var n=t.layerConfig,r=this.session.lineWidgets;if(!r)return;var i=Math.min(this.firstRow,n.firstRow),s=Math.max(this.lastRow,n.lastRow,r.length);while(i>0&&!r[i])i--;this.firstRow=n.firstRow,this.lastRow=n.lastRow,t.$cursorLayer.config=n;for(var o=i;o<=s;o++){var u=r[o];if(!u||!u.el)continue;u._inDocument||(u._inDocument=!0,t.container.appendChild(u.el));var a=t.$cursorLayer.getPixelPosition({row:o,column:0},!0).top;u.coverLine||(a+=n.lineHeight*this.session.getRowLineCount(u.row)),u.el.style.top=a-n.offset+"px";var f=u.coverGutter?0:t.gutterWidth;u.fixedWidth||(f-=t.scrollLeft),u.el.style.left=f+"px",u.fixedWidth?u.el.style.right=t.scrollBar.getWidth()+"px":u.el.style.right=""}}}).call(o.prototype),t.LineWidgets=o}),ace.define("ace/ext/error_marker",["require","exports","module","ace/line_widgets","ace/lib/dom","ace/range"],function(e,t,n){"use strict";function o(e,t,n){var r=0,i=e.length-1;while(r<=i){var s=r+i>>1,o=n(t,e[s]);if(o>0)r=s+1;else{if(!(o<0))return s;i=s-1}}return-(r+1)}function u(e,t,n){var r=e.getAnnotations().sort(s.comparePoints);if(!r.length)return;var i=o(r,{row:t,column:-1},s.comparePoints);i<0&&(i=-i-1),i>=r.length-1?i=n>0?0:r.length-1:i===0&&n<0&&(i=r.length-1);var u=r[i];if(!u||!n)return;if(u.row===t){do u=r[i+=n];while(u&&u.row===t);if(!u)return r.slice()}var a=[];t=u.row;do a[n<0?"unshift":"push"](u),u=r[i+=n];while(u&&u.row==t);return a.length&&a}var r=e("ace/line_widgets").LineWidgets,i=e("ace/lib/dom"),s=e("ace/range").Range;t.showErrorMarker=function(e,t){var n=e.session;n.widgetManager||(n.widgetManager=new r(n),n.widgetManager.attach(e));var s=e.getCursorPosition(),o=s.row,a=n.lineWidgets&&n.lineWidgets[o];a?a.destroy():o-=t;var f=u(n,o,t),l;if(f){var c=f[0];s.column=(c.pos&&typeof c.column!="number"?c.pos.sc:c.column)||0,s.row=c.row,l=e.renderer.$gutterLayer.$annotations[s.row]}else{if(a)return;l={text:["Looks good!"],className:"ace_ok"}}e.session.unfold(s.row),e.selection.moveToPosition(s);var h={row:s.row,fixedWidth:!0,coverGutter:!0,el:i.createElement("div")},p=h.el.appendChild(i.createElement("div")),d=h.el.appendChild(i.createElement("div"));d.className="error_widget_arrow "+l.className;var v=e.renderer.$cursorLayer.getPixelPosition(s).left;d.style.left=v+e.renderer.gutterWidth-5+"px",h.el.className="error_widget_wrapper",p.className="error_widget "+l.className,p.innerHTML=l.text.join("<br>"),p.appendChild(i.createElement("div"));var m=function(e,t,n){if(t===0&&(n==="esc"||n==="return"))return h.destroy(),{command:"null"}};h.destroy=function(){if(e.$mouseHandler.isMousePressed)return;e.keyBinding.removeKeyboardHandler(m),n.widgetManager.removeLineWidget(h),e.off("changeSelection",h.destroy),e.off("changeSession",h.destroy),e.off("mouseup",h.destroy),e.off("change",h.destroy)},e.keyBinding.addKeyboardHandler(m),e.on("changeSelection",h.destroy),e.on("changeSession",h.destroy),e.on("mouseup",h.destroy),e.on("change",h.destroy),e.session.widgetManager.addLineWidget(h),h.el.onmousedown=e.focus.bind(e),e.renderer.scrollCursorIntoView(null,.5,{bottom:h.el.offsetHeight})},i.importCssString("    .error_widget_wrapper {        background: inherit;        color: inherit;        border:none    }    .error_widget {        border-top: solid 2px;        border-bottom: solid 2px;        margin: 5px 0;        padding: 10px 40px;        white-space: pre-wrap;    }    .error_widget.ace_error, .error_widget_arrow.ace_error{        border-color: #ff5a5a    }    .error_widget.ace_warning, .error_widget_arrow.ace_warning{        border-color: #F1D817    }    .error_widget.ace_info, .error_widget_arrow.ace_info{        border-color: #5a5a5a    }    .error_widget.ace_ok, .error_widget_arrow.ace_ok{        border-color: #5aaa5a    }    .error_widget_arrow {        position: absolute;        border: solid 5px;        border-top-color: transparent!important;        border-right-color: transparent!important;        border-left-color: transparent!important;        top: -5px;    }","")}),ace.define("ace/ace",["require","exports","module","ace/lib/fixoldbrowsers","ace/lib/dom","ace/lib/event","ace/editor","ace/edit_session","ace/undomanager","ace/virtual_renderer","ace/worker/worker_client","ace/keyboard/hash_handler","ace/placeholder","ace/multi_select","ace/mode/folding/fold_mode","ace/theme/textmate","ace/ext/error_marker","ace/config"],function(e,t,n){"use strict";e("./lib/fixoldbrowsers");var r=e("./lib/dom"),i=e("./lib/event"),s=e("./editor").Editor,o=e("./edit_session").EditSession,u=e("./undomanager").UndoManager,a=e("./virtual_renderer").VirtualRenderer;e("./worker/worker_client"),e("./keyboard/hash_handler"),e("./placeholder"),e("./multi_select"),e("./mode/folding/fold_mode"),e("./theme/textmate"),e("./ext/error_marker"),t.config=e("./config"),t.require=e,t.edit=function(e){if(typeof e=="string"){var n=e;e=document.getElementById(n);if(!e)throw new Error("ace.edit can't find div #"+n)}if(e.env&&e.env.editor instanceof s)return e.env.editor;var o=t.createEditSession(r.getInnerText(e));e.innerHTML="";var u=new s(new a(e));u.setSession(o);var f={document:o,editor:u,onResize:u.resize.bind(u,null)};return i.addListener(window,"resize",f.onResize),u.on("destroy",function(){i.removeListener(window,"resize",f.onResize)}),e.env=u.env=f,u},t.createEditSession=function(e,t){var n=new o(e,t);return n.setUndoManager(new u),n},t.EditSession=o,t.UndoManager=u});
        -            (function() {
        -                ace.require(["ace/ace"], function(a) {
        -                    a && a.config.init(true);
        -                    if (!window.ace)
        -                        window.ace = a;
        -                    for (var key in a) if (a.hasOwnProperty(key))
        -                        window.ace[key] = a[key];
        -                });
        -            })();
        -        
        \ No newline at end of file
        diff --git a/docs/yuidoc-p5-theme/assets/js/vendor/ace-nc/mode-javascript.js b/docs/yuidoc-p5-theme/assets/js/vendor/ace-nc/mode-javascript.js
        deleted file mode 100644
        index 30dcda85df..0000000000
        --- a/docs/yuidoc-p5-theme/assets/js/vendor/ace-nc/mode-javascript.js
        +++ /dev/null
        @@ -1 +0,0 @@
        -ace.define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment.doc.tag",regex:"@[\\w\\d_]+"},{token:"comment.doc.tag",regex:"\\bTODO\\b"},{defaultToken:"comment.doc"}]}};r.inherits(s,i),s.getStartRule=function(e){return{token:"comment.doc",regex:"\\/\\*(?=\\*)",next:e}},s.getEndRule=function(e){return{token:"comment.doc",regex:"\\*\\/",next:e}},t.DocCommentHighlightRules=s}),ace.define("ace/mode/javascript_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./doc_comment_highlight_rules").DocCommentHighlightRules,s=e("./text_highlight_rules").TextHighlightRules,o=function(){var e=this.createKeywordMapper({"variable.language":"Array|Boolean|Date|Function|Iterator|Number|Object|RegExp|String|Proxy|Namespace|QName|XML|XMLList|ArrayBuffer|Float32Array|Float64Array|Int16Array|Int32Array|Int8Array|Uint16Array|Uint32Array|Uint8Array|Uint8ClampedArray|Error|EvalError|InternalError|RangeError|ReferenceError|StopIteration|SyntaxError|TypeError|URIError|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|parseFloat|parseInt|JSON|Math|this|arguments|prototype|window|document",keyword:"const|yield|import|get|set|break|case|catch|continue|default|delete|do|else|finally|for|function|if|in|instanceof|new|return|switch|throw|try|typeof|let|var|while|with|debugger|__parent__|__count__|escape|unescape|with|__proto__|class|enum|extends|super|export|implements|private|public|interface|package|protected|static","storage.type":"const|let|var|function","constant.language":"null|Infinity|NaN|undefined","support.function":"alert","constant.language.boolean":"true|false"},"identifier"),t="case|do|else|finally|in|instanceof|return|throw|try|typeof|yield|void",n="[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*\\b",r="\\\\(?:x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)";this.$rules={no_regex:[{token:"comment",regex:"\\/\\/",next:"line_comment"},i.getStartRule("doc-start"),{token:"comment",regex:/\/\*/,next:"comment"},{token:"string",regex:"'(?=.)",next:"qstring"},{token:"string",regex:'"(?=.)',next:"qqstring"},{token:"constant.numeric",regex:/0[xX][0-9a-fA-F]+\b/},{token:"constant.numeric",regex:/[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/},{token:["storage.type","punctuation.operator","support.function","punctuation.operator","entity.name.function","text","keyword.operator"],regex:"("+n+")(\\.)(prototype)(\\.)("+n+")(\\s*)(=)",next:"function_arguments"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","paren.lparen"],regex:"("+n+")(\\.)("+n+")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["entity.name.function","text","keyword.operator","text","storage.type","text","paren.lparen"],regex:"("+n+")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["storage.type","punctuation.operator","entity.name.function","text","keyword.operator","text","storage.type","text","entity.name.function","text","paren.lparen"],regex:"("+n+")(\\.)("+n+")(\\s*)(=)(\\s*)(function)(\\s+)(\\w+)(\\s*)(\\()",next:"function_arguments"},{token:["storage.type","text","entity.name.function","text","paren.lparen"],regex:"(function)(\\s+)("+n+")(\\s*)(\\()",next:"function_arguments"},{token:["entity.name.function","text","punctuation.operator","text","storage.type","text","paren.lparen"],regex:"("+n+")(\\s*)(:)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:["text","text","storage.type","text","paren.lparen"],regex:"(:)(\\s*)(function)(\\s*)(\\()",next:"function_arguments"},{token:"keyword",regex:"(?:"+t+")\\b",next:"start"},{token:["punctuation.operator","support.function"],regex:/(\.)(s(?:h(?:ift|ow(?:Mod(?:elessDialog|alDialog)|Help))|croll(?:X|By(?:Pages|Lines)?|Y|To)?|t(?:op|rike)|i(?:n|zeToContent|debar|gnText)|ort|u(?:p|b(?:str(?:ing)?)?)|pli(?:ce|t)|e(?:nd|t(?:Re(?:sizable|questHeader)|M(?:i(?:nutes|lliseconds)|onth)|Seconds|Ho(?:tKeys|urs)|Year|Cursor|Time(?:out)?|Interval|ZOptions|Date|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Date|FullYear)|FullYear|Active)|arch)|qrt|lice|avePreferences|mall)|h(?:ome|andleEvent)|navigate|c(?:har(?:CodeAt|At)|o(?:s|n(?:cat|textual|firm)|mpile)|eil|lear(?:Timeout|Interval)?|a(?:ptureEvents|ll)|reate(?:StyleSheet|Popup|EventObject))|t(?:o(?:GMTString|S(?:tring|ource)|U(?:TCString|pperCase)|Lo(?:caleString|werCase))|est|a(?:n|int(?:Enabled)?))|i(?:s(?:NaN|Finite)|ndexOf|talics)|d(?:isableExternalCapture|ump|etachEvent)|u(?:n(?:shift|taint|escape|watch)|pdateCommands)|j(?:oin|avaEnabled)|p(?:o(?:p|w)|ush|lugins.refresh|a(?:ddings|rse(?:Int|Float)?)|r(?:int|ompt|eference))|e(?:scape|nableExternalCapture|val|lementFromPoint|x(?:p|ec(?:Script|Command)?))|valueOf|UTC|queryCommand(?:State|Indeterm|Enabled|Value)|f(?:i(?:nd|le(?:ModifiedDate|Size|CreatedDate|UpdatedDate)|xed)|o(?:nt(?:size|color)|rward)|loor|romCharCode)|watch|l(?:ink|o(?:ad|g)|astIndexOf)|a(?:sin|nchor|cos|t(?:tachEvent|ob|an(?:2)?)|pply|lert|b(?:s|ort))|r(?:ou(?:nd|teEvents)|e(?:size(?:By|To)|calc|turnValue|place|verse|l(?:oad|ease(?:Capture|Events)))|andom)|g(?:o|et(?:ResponseHeader|M(?:i(?:nutes|lliseconds)|onth)|Se(?:conds|lection)|Hours|Year|Time(?:zoneOffset)?|Da(?:y|te)|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Da(?:y|te)|FullYear)|FullYear|A(?:ttention|llResponseHeaders)))|m(?:in|ove(?:B(?:y|elow)|To(?:Absolute)?|Above)|ergeAttributes|a(?:tch|rgins|x))|b(?:toa|ig|o(?:ld|rderWidths)|link|ack))\b(?=\()/},{token:["punctuation.operator","support.function.dom"],regex:/(\.)(s(?:ub(?:stringData|mit)|plitText|e(?:t(?:NamedItem|Attribute(?:Node)?)|lect))|has(?:ChildNodes|Feature)|namedItem|c(?:l(?:ick|o(?:se|neNode))|reate(?:C(?:omment|DATASection|aption)|T(?:Head|extNode|Foot)|DocumentFragment|ProcessingInstruction|E(?:ntityReference|lement)|Attribute))|tabIndex|i(?:nsert(?:Row|Before|Cell|Data)|tem)|open|delete(?:Row|C(?:ell|aption)|T(?:Head|Foot)|Data)|focus|write(?:ln)?|a(?:dd|ppend(?:Child|Data))|re(?:set|place(?:Child|Data)|move(?:NamedItem|Child|Attribute(?:Node)?)?)|get(?:NamedItem|Element(?:sBy(?:Name|TagName)|ById)|Attribute(?:Node)?)|blur)\b(?=\()/},{token:["punctuation.operator","support.constant"],regex:/(\.)(s(?:ystemLanguage|cr(?:ipts|ollbars|een(?:X|Y|Top|Left))|t(?:yle(?:Sheets)?|atus(?:Text|bar)?)|ibling(?:Below|Above)|ource|uffixes|e(?:curity(?:Policy)?|l(?:ection|f)))|h(?:istory|ost(?:name)?|as(?:h|Focus))|y|X(?:MLDocument|SLDocument)|n(?:ext|ame(?:space(?:s|URI)|Prop))|M(?:IN_VALUE|AX_VALUE)|c(?:haracterSet|o(?:n(?:structor|trollers)|okieEnabled|lorDepth|mp(?:onents|lete))|urrent|puClass|l(?:i(?:p(?:boardData)?|entInformation)|osed|asses)|alle(?:e|r)|rypto)|t(?:o(?:olbar|p)|ext(?:Transform|Indent|Decoration|Align)|ags)|SQRT(?:1_2|2)|i(?:n(?:ner(?:Height|Width)|put)|ds|gnoreCase)|zIndex|o(?:scpu|n(?:readystatechange|Line)|uter(?:Height|Width)|p(?:sProfile|ener)|ffscreenBuffering)|NEGATIVE_INFINITY|d(?:i(?:splay|alog(?:Height|Top|Width|Left|Arguments)|rectories)|e(?:scription|fault(?:Status|Ch(?:ecked|arset)|View)))|u(?:ser(?:Profile|Language|Agent)|n(?:iqueID|defined)|pdateInterval)|_content|p(?:ixelDepth|ort|ersonalbar|kcs11|l(?:ugins|atform)|a(?:thname|dding(?:Right|Bottom|Top|Left)|rent(?:Window|Layer)?|ge(?:X(?:Offset)?|Y(?:Offset)?))|r(?:o(?:to(?:col|type)|duct(?:Sub)?|mpter)|e(?:vious|fix)))|e(?:n(?:coding|abledPlugin)|x(?:ternal|pando)|mbeds)|v(?:isibility|endor(?:Sub)?|Linkcolor)|URLUnencoded|P(?:I|OSITIVE_INFINITY)|f(?:ilename|o(?:nt(?:Size|Family|Weight)|rmName)|rame(?:s|Element)|gColor)|E|whiteSpace|l(?:i(?:stStyleType|n(?:eHeight|kColor))|o(?:ca(?:tion(?:bar)?|lName)|wsrc)|e(?:ngth|ft(?:Context)?)|a(?:st(?:M(?:odified|atch)|Index|Paren)|yer(?:s|X)|nguage))|a(?:pp(?:MinorVersion|Name|Co(?:deName|re)|Version)|vail(?:Height|Top|Width|Left)|ll|r(?:ity|guments)|Linkcolor|bove)|r(?:ight(?:Context)?|e(?:sponse(?:XML|Text)|adyState))|global|x|m(?:imeTypes|ultiline|enubar|argin(?:Right|Bottom|Top|Left))|L(?:N(?:10|2)|OG(?:10E|2E))|b(?:o(?:ttom|rder(?:Width|RightWidth|BottomWidth|Style|Color|TopWidth|LeftWidth))|ufferDepth|elow|ackground(?:Color|Image)))\b/},{token:["support.constant"],regex:/that\b/},{token:["storage.type","punctuation.operator","support.function.firebug"],regex:/(console)(\.)(warn|info|log|error|time|trace|timeEnd|assert)\b/},{token:e,regex:n},{token:"keyword.operator",regex:/--|\+\+|[!$%&*+\-~]|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\|\||\?\:|\*=|%=|\+=|\-=|&=|\^=/,next:"start"},{token:"punctuation.operator",regex:/\?|\:|\,|\;|\./,next:"start"},{token:"paren.lparen",regex:/[\[({]/,next:"start"},{token:"paren.rparen",regex:/[\])}]/},{token:"keyword.operator",regex:/\/=?/,next:"start"},{token:"comment",regex:/^#!.*$/}],start:[i.getStartRule("doc-start"),{token:"comment",regex:"\\/\\*",next:"comment_regex_allowed"},{token:"comment",regex:"\\/\\/",next:"line_comment_regex_allowed"},{token:"string.regexp",regex:"\\/",next:"regex"},{token:"text",regex:"\\s+|^$",next:"start"},{token:"empty",regex:"",next:"no_regex"}],regex:[{token:"regexp.keyword.operator",regex:"\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"},{token:"string.regexp",regex:"/[sxngimy]*",next:"no_regex"},{token:"invalid",regex:/\{\d+\b,?\d*\}[+*]|[+*$^?][+*]|[$^][?]|\?{3,}/},{token:"constant.language.escape",regex:/\(\?[:=!]|\)|\{\d+\b,?\d*\}|[+*]\?|[()$^+*?.]/},{token:"constant.language.delimiter",regex:/\|/},{token:"constant.language.escape",regex:/\[\^?/,next:"regex_character_class"},{token:"empty",regex:"$",next:"no_regex"},{defaultToken:"string.regexp"}],regex_character_class:[{token:"regexp.keyword.operator",regex:"\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"},{token:"constant.language.escape",regex:"]",next:"regex"},{token:"constant.language.escape",regex:"-"},{token:"empty",regex:"$",next:"no_regex"},{defaultToken:"string.regexp.charachterclass"}],function_arguments:[{token:"variable.parameter",regex:n},{token:"punctuation.operator",regex:"[, ]+"},{token:"punctuation.operator",regex:"$"},{token:"empty",regex:"",next:"no_regex"}],comment_regex_allowed:[{token:"comment",regex:"\\*\\/",next:"start"},{defaultToken:"comment"}],comment:[{token:"comment",regex:"\\*\\/",next:"no_regex"},{defaultToken:"comment"}],line_comment_regex_allowed:[{token:"comment",regex:"$|^",next:"start"},{defaultToken:"comment"}],line_comment:[{token:"comment",regex:"$|^",next:"no_regex"},{defaultToken:"comment"}],qqstring:[{token:"constant.language.escape",regex:r},{token:"string",regex:"\\\\$",next:"qqstring"},{token:"string",regex:'"|$',next:"no_regex"},{defaultToken:"string"}],qstring:[{token:"constant.language.escape",regex:r},{token:"string",regex:"\\\\$",next:"qstring"},{token:"string",regex:"'|$",next:"no_regex"},{defaultToken:"string"}]},this.embedRules(i,"doc-",[i.getEndRule("no_regex")])};r.inherits(o,s),t.JavaScriptHighlightRules=o}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/behaviour/cstyle",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../behaviour").Behaviour,s=e("../../token_iterator").TokenIterator,o=e("../../lib/lang"),u=["text","paren.rparen","punctuation.operator"],a=["text","paren.rparen","punctuation.operator","comment"],f,l={},c=function(e){var t=-1;e.multiSelect&&(t=e.selection.id,l.rangeCount!=e.multiSelect.rangeCount&&(l={rangeCount:e.multiSelect.rangeCount}));if(l[t])return f=l[t];f=l[t]={autoInsertedBrackets:0,autoInsertedRow:-1,autoInsertedLineEnd:"",maybeInsertedBrackets:0,maybeInsertedRow:-1,maybeInsertedLineStart:"",maybeInsertedLineEnd:""}},h=function(){this.add("braces","insertion",function(e,t,n,r,i){var s=n.getCursorPosition(),u=r.doc.getLine(s.row);if(i=="{"){c(n);var a=n.getSelectionRange(),l=r.doc.getTextRange(a);if(l!==""&&l!=="{"&&n.getWrapBehavioursEnabled())return{text:"{"+l+"}",selection:!1};if(h.isSaneInsertion(n,r))return/[\]\}\)]/.test(u[s.column])||n.inMultiSelectMode?(h.recordAutoInsert(n,r,"}"),{text:"{}",selection:[1,1]}):(h.recordMaybeInsert(n,r,"{"),{text:"{",selection:[1,1]})}else if(i=="}"){c(n);var p=u.substring(s.column,s.column+1);if(p=="}"){var d=r.$findOpeningBracket("}",{column:s.column+1,row:s.row});if(d!==null&&h.isAutoInsertedClosing(s,u,i))return h.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}else{if(i=="\n"||i=="\r\n"){c(n);var v="";h.isMaybeInsertedClosing(s,u)&&(v=o.stringRepeat("}",f.maybeInsertedBrackets),h.clearMaybeInsertedClosing());var p=u.substring(s.column,s.column+1);if(p==="}"){var m=r.findMatchingBracket({row:s.row,column:s.column+1},"}");if(!m)return null;var g=this.$getIndent(r.getLine(m.row))}else{if(!v){h.clearMaybeInsertedClosing();return}var g=this.$getIndent(u)}var y=g+r.getTabString();return{text:"\n"+y+"\n"+g+v,selection:[1,y.length,1,y.length]}}h.clearMaybeInsertedClosing()}}),this.add("braces","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="{"){c(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.end.column,i.end.column+1);if(u=="}")return i.end.column++,i;f.maybeInsertedBrackets--}}),this.add("parens","insertion",function(e,t,n,r,i){if(i=="("){c(n);var s=n.getSelectionRange(),o=r.doc.getTextRange(s);if(o!==""&&n.getWrapBehavioursEnabled())return{text:"("+o+")",selection:!1};if(h.isSaneInsertion(n,r))return h.recordAutoInsert(n,r,")"),{text:"()",selection:[1,1]}}else if(i==")"){c(n);var u=n.getCursorPosition(),a=r.doc.getLine(u.row),f=a.substring(u.column,u.column+1);if(f==")"){var l=r.$findOpeningBracket(")",{column:u.column+1,row:u.row});if(l!==null&&h.isAutoInsertedClosing(u,a,i))return h.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}}),this.add("parens","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="("){c(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u==")")return i.end.column++,i}}),this.add("brackets","insertion",function(e,t,n,r,i){if(i=="["){c(n);var s=n.getSelectionRange(),o=r.doc.getTextRange(s);if(o!==""&&n.getWrapBehavioursEnabled())return{text:"["+o+"]",selection:!1};if(h.isSaneInsertion(n,r))return h.recordAutoInsert(n,r,"]"),{text:"[]",selection:[1,1]}}else if(i=="]"){c(n);var u=n.getCursorPosition(),a=r.doc.getLine(u.row),f=a.substring(u.column,u.column+1);if(f=="]"){var l=r.$findOpeningBracket("]",{column:u.column+1,row:u.row});if(l!==null&&h.isAutoInsertedClosing(u,a,i))return h.popAutoInsertedClosing(),{text:"",selection:[1,1]}}}}),this.add("brackets","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&s=="["){c(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u=="]")return i.end.column++,i}}),this.add("string_dquotes","insertion",function(e,t,n,r,i){if(i=='"'||i=="'"){c(n);var s=i,o=n.getSelectionRange(),u=r.doc.getTextRange(o);if(u!==""&&u!=="'"&&u!='"'&&n.getWrapBehavioursEnabled())return{text:s+u+s,selection:!1};var a=n.getCursorPosition(),f=r.doc.getLine(a.row),l=f.substring(a.column-1,a.column);if(l=="\\")return null;var p=r.getTokens(o.start.row),d=0,v,m=-1;for(var g=0;g<p.length;g++){v=p[g],v.type=="string"?m=-1:m<0&&(m=v.value.indexOf(s));if(v.value.length+d>o.start.column)break;d+=p[g].value.length}if(!v||m<0&&v.type!=="comment"&&(v.type!=="string"||o.start.column!==v.value.length+d-1&&v.value.lastIndexOf(s)===v.value.length-1)){if(!h.isSaneInsertion(n,r))return;return{text:s+s,selection:[1,1]}}if(v&&v.type==="string"){var y=f.substring(a.column,a.column+1);if(y==s)return{text:"",selection:[1,1]}}}}),this.add("string_dquotes","deletion",function(e,t,n,r,i){var s=r.doc.getTextRange(i);if(!i.isMultiLine()&&(s=='"'||s=="'")){c(n);var o=r.doc.getLine(i.start.row),u=o.substring(i.start.column+1,i.start.column+2);if(u==s)return i.end.column++,i}})};h.isSaneInsertion=function(e,t){var n=e.getCursorPosition(),r=new s(t,n.row,n.column);if(!this.$matchTokenType(r.getCurrentToken()||"text",u)){var i=new s(t,n.row,n.column+1);if(!this.$matchTokenType(i.getCurrentToken()||"text",u))return!1}return r.stepForward(),r.getCurrentTokenRow()!==n.row||this.$matchTokenType(r.getCurrentToken()||"text",a)},h.$matchTokenType=function(e,t){return t.indexOf(e.type||e)>-1},h.recordAutoInsert=function(e,t,n){var r=e.getCursorPosition(),i=t.doc.getLine(r.row);this.isAutoInsertedClosing(r,i,f.autoInsertedLineEnd[0])||(f.autoInsertedBrackets=0),f.autoInsertedRow=r.row,f.autoInsertedLineEnd=n+i.substr(r.column),f.autoInsertedBrackets++},h.recordMaybeInsert=function(e,t,n){var r=e.getCursorPosition(),i=t.doc.getLine(r.row);this.isMaybeInsertedClosing(r,i)||(f.maybeInsertedBrackets=0),f.maybeInsertedRow=r.row,f.maybeInsertedLineStart=i.substr(0,r.column)+n,f.maybeInsertedLineEnd=i.substr(r.column),f.maybeInsertedBrackets++},h.isAutoInsertedClosing=function(e,t,n){return f.autoInsertedBrackets>0&&e.row===f.autoInsertedRow&&n===f.autoInsertedLineEnd[0]&&t.substr(e.column)===f.autoInsertedLineEnd},h.isMaybeInsertedClosing=function(e,t){return f.maybeInsertedBrackets>0&&e.row===f.maybeInsertedRow&&t.substr(e.column)===f.maybeInsertedLineEnd&&t.substr(0,e.column)==f.maybeInsertedLineStart},h.popAutoInsertedClosing=function(){f.autoInsertedLineEnd=f.autoInsertedLineEnd.substr(1),f.autoInsertedBrackets--},h.clearMaybeInsertedClosing=function(){f&&(f.maybeInsertedBrackets=0,f.maybeInsertedRow=-1)},r.inherits(h,i),t.CstyleBehaviour=h}),ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("../../range").Range,s=e("./fold_mode").FoldMode,o=t.FoldMode=function(e){e&&(this.foldingStartMarker=new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/,"|"+e.start)),this.foldingStopMarker=new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/,"|"+e.end)))};r.inherits(o,s),function(){this.foldingStartMarker=/(\{|\[)[^\}\]]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/,this.getFoldWidgetRange=function(e,t,n,r){var i=e.getLine(n),s=i.match(this.foldingStartMarker);if(s){var o=s.index;if(s[1])return this.openingBracketBlock(e,s[1],n,o);var u=e.getCommentFoldRange(n,o+s[0].length,1);return u&&!u.isMultiLine()&&(r?u=this.getSectionRange(e,n):t!="all"&&(u=null)),u}if(t==="markbegin")return;var s=i.match(this.foldingStopMarker);if(s){var o=s.index+s[0].length;return s[1]?this.closingBracketBlock(e,s[1],n,o):e.getCommentFoldRange(n,o,-1)}},this.getSectionRange=function(e,t){var n=e.getLine(t),r=n.search(/\S/),s=t,o=n.length;t+=1;var u=t,a=e.getLength();while(++t<a){n=e.getLine(t);var f=n.search(/\S/);if(f===-1)continue;if(r>f)break;var l=this.getFoldWidgetRange(e,"all",t);if(l){if(l.start.row<=s)break;if(l.isMultiLine())t=l.end.row;else if(r==f)break}u=t}return new i(s,o,u,e.getLine(u).length)}}.call(o.prototype)}),ace.define("ace/mode/javascript",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/javascript_highlight_rules","ace/mode/matching_brace_outdent","ace/range","ace/worker/worker_client","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./javascript_highlight_rules").JavaScriptHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("../range").Range,a=e("../worker/worker_client").WorkerClient,f=e("./behaviour/cstyle").CstyleBehaviour,l=e("./folding/cstyle").FoldMode,c=function(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=new f,this.foldingRules=new l};r.inherits(c,i),function(){this.lineCommentStart="//",this.blockComment={start:"/*",end:"*/"},this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e),s=i.tokens,o=i.state;if(s.length&&s[s.length-1].type=="comment")return r;if(e=="start"||e=="no_regex"){var u=t.match(/^.*(?:\bcase\b.*\:|[\{\(\[])\s*$/);u&&(r+=n)}else if(e=="doc-start"){if(o=="start"||o=="no_regex")return"";var u=t.match(/^\s*(\/?)\*/);u&&(u[1]&&(r+=" "),r+="* ")}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.createWorker=function(e){var t=new a(["ace"],"ace/mode/javascript_worker","JavaScriptWorker");return t.attachToDocument(e.getDocument()),t.on("jslint",function(t){e.setAnnotations(t.data)}),t.on("terminate",function(){e.clearAnnotations()}),t},this.$id="ace/mode/javascript"}.call(c.prototype),t.Mode=c})
        \ No newline at end of file
        diff --git a/docs/yuidoc-p5-theme/assets/js/vendor/backbone-min.js b/docs/yuidoc-p5-theme/assets/js/vendor/backbone-min.js
        deleted file mode 100644
        index 19b8c90691..0000000000
        --- a/docs/yuidoc-p5-theme/assets/js/vendor/backbone-min.js
        +++ /dev/null
        @@ -1 +0,0 @@
        -(function(t){var e=typeof self=="object"&&self.self===self&&self||typeof global=="object"&&global.global===global&&global;if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,n){e.Backbone=t(e,n,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore"),r;try{r=require("jquery")}catch(n){}t(e,exports,i,r)}else{e.Backbone=t(e,{},e._,e.jQuery||e.Zepto||e.ender||e.$)}})(function(t,e,i,r){var n=t.Backbone;var s=Array.prototype.slice;e.VERSION="1.3.3";e.$=r;e.noConflict=function(){t.Backbone=n;return this};e.emulateHTTP=false;e.emulateJSON=false;var a=function(t,e,r){switch(t){case 1:return function(){return i[e](this[r])};case 2:return function(t){return i[e](this[r],t)};case 3:return function(t,n){return i[e](this[r],o(t,this),n)};case 4:return function(t,n,s){return i[e](this[r],o(t,this),n,s)};default:return function(){var t=s.call(arguments);t.unshift(this[r]);return i[e].apply(i,t)}}};var h=function(t,e,r){i.each(e,function(e,n){if(i[n])t.prototype[n]=a(e,n,r)})};var o=function(t,e){if(i.isFunction(t))return t;if(i.isObject(t)&&!e._isModel(t))return l(t);if(i.isString(t))return function(e){return e.get(t)};return t};var l=function(t){var e=i.matches(t);return function(t){return e(t.attributes)}};var u=e.Events={};var c=/\s+/;var f=function(t,e,r,n,s){var a=0,h;if(r&&typeof r==="object"){if(n!==void 0&&"context"in s&&s.context===void 0)s.context=n;for(h=i.keys(r);a<h.length;a++){e=f(t,e,h[a],r[h[a]],s)}}else if(r&&c.test(r)){for(h=r.split(c);a<h.length;a++){e=t(e,h[a],n,s)}}else{e=t(e,r,n,s)}return e};u.on=function(t,e,i){return d(this,t,e,i)};var d=function(t,e,i,r,n){t._events=f(v,t._events||{},e,i,{context:r,ctx:t,listening:n});if(n){var s=t._listeners||(t._listeners={});s[n.id]=n}return t};u.listenTo=function(t,e,r){if(!t)return this;var n=t._listenId||(t._listenId=i.uniqueId("l"));var s=this._listeningTo||(this._listeningTo={});var a=s[n];if(!a){var h=this._listenId||(this._listenId=i.uniqueId("l"));a=s[n]={obj:t,objId:n,id:h,listeningTo:s,count:0}}d(t,e,r,this,a);return this};var v=function(t,e,i,r){if(i){var n=t[e]||(t[e]=[]);var s=r.context,a=r.ctx,h=r.listening;if(h)h.count++;n.push({callback:i,context:s,ctx:s||a,listening:h})}return t};u.off=function(t,e,i){if(!this._events)return this;this._events=f(g,this._events,t,e,{context:i,listeners:this._listeners});return this};u.stopListening=function(t,e,r){var n=this._listeningTo;if(!n)return this;var s=t?[t._listenId]:i.keys(n);for(var a=0;a<s.length;a++){var h=n[s[a]];if(!h)break;h.obj.off(e,r,this)}return this};var g=function(t,e,r,n){if(!t)return;var s=0,a;var h=n.context,o=n.listeners;if(!e&&!r&&!h){var l=i.keys(o);for(;s<l.length;s++){a=o[l[s]];delete o[a.id];delete a.listeningTo[a.objId]}return}var u=e?[e]:i.keys(t);for(;s<u.length;s++){e=u[s];var c=t[e];if(!c)break;var f=[];for(var d=0;d<c.length;d++){var v=c[d];if(r&&r!==v.callback&&r!==v.callback._callback||h&&h!==v.context){f.push(v)}else{a=v.listening;if(a&&--a.count===0){delete o[a.id];delete a.listeningTo[a.objId]}}}if(f.length){t[e]=f}else{delete t[e]}}return t};u.once=function(t,e,r){var n=f(p,{},t,e,i.bind(this.off,this));if(typeof t==="string"&&r==null)e=void 0;return this.on(n,e,r)};u.listenToOnce=function(t,e,r){var n=f(p,{},e,r,i.bind(this.stopListening,this,t));return this.listenTo(t,n)};var p=function(t,e,r,n){if(r){var s=t[e]=i.once(function(){n(e,s);r.apply(this,arguments)});s._callback=r}return t};u.trigger=function(t){if(!this._events)return this;var e=Math.max(0,arguments.length-1);var i=Array(e);for(var r=0;r<e;r++)i[r]=arguments[r+1];f(m,this._events,t,void 0,i);return this};var m=function(t,e,i,r){if(t){var n=t[e];var s=t.all;if(n&&s)s=s.slice();if(n)_(n,r);if(s)_(s,[e].concat(r))}return t};var _=function(t,e){var i,r=-1,n=t.length,s=e[0],a=e[1],h=e[2];switch(e.length){case 0:while(++r<n)(i=t[r]).callback.call(i.ctx);return;case 1:while(++r<n)(i=t[r]).callback.call(i.ctx,s);return;case 2:while(++r<n)(i=t[r]).callback.call(i.ctx,s,a);return;case 3:while(++r<n)(i=t[r]).callback.call(i.ctx,s,a,h);return;default:while(++r<n)(i=t[r]).callback.apply(i.ctx,e);return}};u.bind=u.on;u.unbind=u.off;i.extend(e,u);var y=e.Model=function(t,e){var r=t||{};e||(e={});this.cid=i.uniqueId(this.cidPrefix);this.attributes={};if(e.collection)this.collection=e.collection;if(e.parse)r=this.parse(r,e)||{};var n=i.result(this,"defaults");r=i.defaults(i.extend({},n,r),n);this.set(r,e);this.changed={};this.initialize.apply(this,arguments)};i.extend(y.prototype,u,{changed:null,validationError:null,idAttribute:"id",cidPrefix:"c",initialize:function(){},toJSON:function(t){return i.clone(this.attributes)},sync:function(){return e.sync.apply(this,arguments)},get:function(t){return this.attributes[t]},escape:function(t){return i.escape(this.get(t))},has:function(t){return this.get(t)!=null},matches:function(t){return!!i.iteratee(t,this)(this.attributes)},set:function(t,e,r){if(t==null)return this;var n;if(typeof t==="object"){n=t;r=e}else{(n={})[t]=e}r||(r={});if(!this._validate(n,r))return false;var s=r.unset;var a=r.silent;var h=[];var o=this._changing;this._changing=true;if(!o){this._previousAttributes=i.clone(this.attributes);this.changed={}}var l=this.attributes;var u=this.changed;var c=this._previousAttributes;for(var f in n){e=n[f];if(!i.isEqual(l[f],e))h.push(f);if(!i.isEqual(c[f],e)){u[f]=e}else{delete u[f]}s?delete l[f]:l[f]=e}if(this.idAttribute in n)this.id=this.get(this.idAttribute);if(!a){if(h.length)this._pending=r;for(var d=0;d<h.length;d++){this.trigger("change:"+h[d],this,l[h[d]],r)}}if(o)return this;if(!a){while(this._pending){r=this._pending;this._pending=false;this.trigger("change",this,r)}}this._pending=false;this._changing=false;return this},unset:function(t,e){return this.set(t,void 0,i.extend({},e,{unset:true}))},clear:function(t){var e={};for(var r in this.attributes)e[r]=void 0;return this.set(e,i.extend({},t,{unset:true}))},hasChanged:function(t){if(t==null)return!i.isEmpty(this.changed);return i.has(this.changed,t)},changedAttributes:function(t){if(!t)return this.hasChanged()?i.clone(this.changed):false;var e=this._changing?this._previousAttributes:this.attributes;var r={};for(var n in t){var s=t[n];if(i.isEqual(e[n],s))continue;r[n]=s}return i.size(r)?r:false},previous:function(t){if(t==null||!this._previousAttributes)return null;return this._previousAttributes[t]},previousAttributes:function(){return i.clone(this._previousAttributes)},fetch:function(t){t=i.extend({parse:true},t);var e=this;var r=t.success;t.success=function(i){var n=t.parse?e.parse(i,t):i;if(!e.set(n,t))return false;if(r)r.call(t.context,e,i,t);e.trigger("sync",e,i,t)};B(this,t);return this.sync("read",this,t)},save:function(t,e,r){var n;if(t==null||typeof t==="object"){n=t;r=e}else{(n={})[t]=e}r=i.extend({validate:true,parse:true},r);var s=r.wait;if(n&&!s){if(!this.set(n,r))return false}else if(!this._validate(n,r)){return false}var a=this;var h=r.success;var o=this.attributes;r.success=function(t){a.attributes=o;var e=r.parse?a.parse(t,r):t;if(s)e=i.extend({},n,e);if(e&&!a.set(e,r))return false;if(h)h.call(r.context,a,t,r);a.trigger("sync",a,t,r)};B(this,r);if(n&&s)this.attributes=i.extend({},o,n);var l=this.isNew()?"create":r.patch?"patch":"update";if(l==="patch"&&!r.attrs)r.attrs=n;var u=this.sync(l,this,r);this.attributes=o;return u},destroy:function(t){t=t?i.clone(t):{};var e=this;var r=t.success;var n=t.wait;var s=function(){e.stopListening();e.trigger("destroy",e,e.collection,t)};t.success=function(i){if(n)s();if(r)r.call(t.context,e,i,t);if(!e.isNew())e.trigger("sync",e,i,t)};var a=false;if(this.isNew()){i.defer(t.success)}else{B(this,t);a=this.sync("delete",this,t)}if(!n)s();return a},url:function(){var t=i.result(this,"urlRoot")||i.result(this.collection,"url")||F();if(this.isNew())return t;var e=this.get(this.idAttribute);return t.replace(/[^\/]$/,"$&/")+encodeURIComponent(e)},parse:function(t,e){return t},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return!this.has(this.idAttribute)},isValid:function(t){return this._validate({},i.extend({},t,{validate:true}))},_validate:function(t,e){if(!e.validate||!this.validate)return true;t=i.extend({},this.attributes,t);var r=this.validationError=this.validate(t,e)||null;if(!r)return true;this.trigger("invalid",this,r,i.extend(e,{validationError:r}));return false}});var b={keys:1,values:1,pairs:1,invert:1,pick:0,omit:0,chain:1,isEmpty:1};h(y,b,"attributes");var x=e.Collection=function(t,e){e||(e={});if(e.model)this.model=e.model;if(e.comparator!==void 0)this.comparator=e.comparator;this._reset();this.initialize.apply(this,arguments);if(t)this.reset(t,i.extend({silent:true},e))};var w={add:true,remove:true,merge:true};var E={add:true,remove:false};var I=function(t,e,i){i=Math.min(Math.max(i,0),t.length);var r=Array(t.length-i);var n=e.length;var s;for(s=0;s<r.length;s++)r[s]=t[s+i];for(s=0;s<n;s++)t[s+i]=e[s];for(s=0;s<r.length;s++)t[s+n+i]=r[s]};i.extend(x.prototype,u,{model:y,initialize:function(){},toJSON:function(t){return this.map(function(e){return e.toJSON(t)})},sync:function(){return e.sync.apply(this,arguments)},add:function(t,e){return this.set(t,i.extend({merge:false},e,E))},remove:function(t,e){e=i.extend({},e);var r=!i.isArray(t);t=r?[t]:t.slice();var n=this._removeModels(t,e);if(!e.silent&&n.length){e.changes={added:[],merged:[],removed:n};this.trigger("update",this,e)}return r?n[0]:n},set:function(t,e){if(t==null)return;e=i.extend({},w,e);if(e.parse&&!this._isModel(t)){t=this.parse(t,e)||[]}var r=!i.isArray(t);t=r?[t]:t.slice();var n=e.at;if(n!=null)n=+n;if(n>this.length)n=this.length;if(n<0)n+=this.length+1;var s=[];var a=[];var h=[];var o=[];var l={};var u=e.add;var c=e.merge;var f=e.remove;var d=false;var v=this.comparator&&n==null&&e.sort!==false;var g=i.isString(this.comparator)?this.comparator:null;var p,m;for(m=0;m<t.length;m++){p=t[m];var _=this.get(p);if(_){if(c&&p!==_){var y=this._isModel(p)?p.attributes:p;if(e.parse)y=_.parse(y,e);_.set(y,e);h.push(_);if(v&&!d)d=_.hasChanged(g)}if(!l[_.cid]){l[_.cid]=true;s.push(_)}t[m]=_}else if(u){p=t[m]=this._prepareModel(p,e);if(p){a.push(p);this._addReference(p,e);l[p.cid]=true;s.push(p)}}}if(f){for(m=0;m<this.length;m++){p=this.models[m];if(!l[p.cid])o.push(p)}if(o.length)this._removeModels(o,e)}var b=false;var x=!v&&u&&f;if(s.length&&x){b=this.length!==s.length||i.some(this.models,function(t,e){return t!==s[e]});this.models.length=0;I(this.models,s,0);this.length=this.models.length}else if(a.length){if(v)d=true;I(this.models,a,n==null?this.length:n);this.length=this.models.length}if(d)this.sort({silent:true});if(!e.silent){for(m=0;m<a.length;m++){if(n!=null)e.index=n+m;p=a[m];p.trigger("add",p,this,e)}if(d||b)this.trigger("sort",this,e);if(a.length||o.length||h.length){e.changes={added:a,removed:o,merged:h};this.trigger("update",this,e)}}return r?t[0]:t},reset:function(t,e){e=e?i.clone(e):{};for(var r=0;r<this.models.length;r++){this._removeReference(this.models[r],e)}e.previousModels=this.models;this._reset();t=this.add(t,i.extend({silent:true},e));if(!e.silent)this.trigger("reset",this,e);return t},push:function(t,e){return this.add(t,i.extend({at:this.length},e))},pop:function(t){var e=this.at(this.length-1);return this.remove(e,t)},unshift:function(t,e){return this.add(t,i.extend({at:0},e))},shift:function(t){var e=this.at(0);return this.remove(e,t)},slice:function(){return s.apply(this.models,arguments)},get:function(t){if(t==null)return void 0;return this._byId[t]||this._byId[this.modelId(t.attributes||t)]||t.cid&&this._byId[t.cid]},has:function(t){return this.get(t)!=null},at:function(t){if(t<0)t+=this.length;return this.models[t]},where:function(t,e){return this[e?"find":"filter"](t)},findWhere:function(t){return this.where(t,true)},sort:function(t){var e=this.comparator;if(!e)throw new Error("Cannot sort a set without a comparator");t||(t={});var r=e.length;if(i.isFunction(e))e=i.bind(e,this);if(r===1||i.isString(e)){this.models=this.sortBy(e)}else{this.models.sort(e)}if(!t.silent)this.trigger("sort",this,t);return this},pluck:function(t){return this.map(t+"")},fetch:function(t){t=i.extend({parse:true},t);var e=t.success;var r=this;t.success=function(i){var n=t.reset?"reset":"set";r[n](i,t);if(e)e.call(t.context,r,i,t);r.trigger("sync",r,i,t)};B(this,t);return this.sync("read",this,t)},create:function(t,e){e=e?i.clone(e):{};var r=e.wait;t=this._prepareModel(t,e);if(!t)return false;if(!r)this.add(t,e);var n=this;var s=e.success;e.success=function(t,e,i){if(r)n.add(t,i);if(s)s.call(i.context,t,e,i)};t.save(null,e);return t},parse:function(t,e){return t},clone:function(){return new this.constructor(this.models,{model:this.model,comparator:this.comparator})},modelId:function(t){return t[this.model.prototype.idAttribute||"id"]},_reset:function(){this.length=0;this.models=[];this._byId={}},_prepareModel:function(t,e){if(this._isModel(t)){if(!t.collection)t.collection=this;return t}e=e?i.clone(e):{};e.collection=this;var r=new this.model(t,e);if(!r.validationError)return r;this.trigger("invalid",this,r.validationError,e);return false},_removeModels:function(t,e){var i=[];for(var r=0;r<t.length;r++){var n=this.get(t[r]);if(!n)continue;var s=this.indexOf(n);this.models.splice(s,1);this.length--;delete this._byId[n.cid];var a=this.modelId(n.attributes);if(a!=null)delete this._byId[a];if(!e.silent){e.index=s;n.trigger("remove",n,this,e)}i.push(n);this._removeReference(n,e)}return i},_isModel:function(t){return t instanceof y},_addReference:function(t,e){this._byId[t.cid]=t;var i=this.modelId(t.attributes);if(i!=null)this._byId[i]=t;t.on("all",this._onModelEvent,this)},_removeReference:function(t,e){delete this._byId[t.cid];var i=this.modelId(t.attributes);if(i!=null)delete this._byId[i];if(this===t.collection)delete t.collection;t.off("all",this._onModelEvent,this)},_onModelEvent:function(t,e,i,r){if(e){if((t==="add"||t==="remove")&&i!==this)return;if(t==="destroy")this.remove(e,r);if(t==="change"){var n=this.modelId(e.previousAttributes());var s=this.modelId(e.attributes);if(n!==s){if(n!=null)delete this._byId[n];if(s!=null)this._byId[s]=e}}}this.trigger.apply(this,arguments)}});var S={forEach:3,each:3,map:3,collect:3,reduce:0,foldl:0,inject:0,reduceRight:0,foldr:0,find:3,detect:3,filter:3,select:3,reject:3,every:3,all:3,some:3,any:3,include:3,includes:3,contains:3,invoke:0,max:3,min:3,toArray:1,size:1,first:3,head:3,take:3,initial:3,rest:3,tail:3,drop:3,last:3,without:0,difference:0,indexOf:3,shuffle:1,lastIndexOf:3,isEmpty:1,chain:1,sample:3,partition:3,groupBy:3,countBy:3,sortBy:3,indexBy:3,findIndex:3,findLastIndex:3};h(x,S,"models");var k=e.View=function(t){this.cid=i.uniqueId("view");i.extend(this,i.pick(t,P));this._ensureElement();this.initialize.apply(this,arguments)};var T=/^(\S+)\s*(.*)$/;var P=["model","collection","el","id","attributes","className","tagName","events"];i.extend(k.prototype,u,{tagName:"div",$:function(t){return this.$el.find(t)},initialize:function(){},render:function(){return this},remove:function(){this._removeElement();this.stopListening();return this},_removeElement:function(){this.$el.remove()},setElement:function(t){this.undelegateEvents();this._setElement(t);this.delegateEvents();return this},_setElement:function(t){this.$el=t instanceof e.$?t:e.$(t);this.el=this.$el[0]},delegateEvents:function(t){t||(t=i.result(this,"events"));if(!t)return this;this.undelegateEvents();for(var e in t){var r=t[e];if(!i.isFunction(r))r=this[r];if(!r)continue;var n=e.match(T);this.delegate(n[1],n[2],i.bind(r,this))}return this},delegate:function(t,e,i){this.$el.on(t+".delegateEvents"+this.cid,e,i);return this},undelegateEvents:function(){if(this.$el)this.$el.off(".delegateEvents"+this.cid);return this},undelegate:function(t,e,i){this.$el.off(t+".delegateEvents"+this.cid,e,i);return this},_createElement:function(t){return document.createElement(t)},_ensureElement:function(){if(!this.el){var t=i.extend({},i.result(this,"attributes"));if(this.id)t.id=i.result(this,"id");if(this.className)t["class"]=i.result(this,"className");this.setElement(this._createElement(i.result(this,"tagName")));this._setAttributes(t)}else{this.setElement(i.result(this,"el"))}},_setAttributes:function(t){this.$el.attr(t)}});e.sync=function(t,r,n){var s=H[t];i.defaults(n||(n={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:s,dataType:"json"};if(!n.url){a.url=i.result(r,"url")||F()}if(n.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(n.attrs||r.toJSON(n))}if(n.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(n.emulateHTTP&&(s==="PUT"||s==="DELETE"||s==="PATCH")){a.type="POST";if(n.emulateJSON)a.data._method=s;var h=n.beforeSend;n.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",s);if(h)return h.apply(this,arguments)}}if(a.type!=="GET"&&!n.emulateJSON){a.processData=false}var o=n.error;n.error=function(t,e,i){n.textStatus=e;n.errorThrown=i;if(o)o.call(n.context,t,e,i)};var l=n.xhr=e.ajax(i.extend(a,n));r.trigger("request",r,l,n);return l};var H={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var A=/\((.*?)\)/g;var C=/(\(\?)?:\w+/g;var R=/\*\w+/g;var j=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,n){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){n=r;r=""}if(!n)n=this[r];var s=this;e.history.route(t,function(i){var a=s._extractParameters(t,i);if(s.execute(n,a,r)!==false){s.trigger.apply(s,["route:"+r].concat(a));s.trigger("route",r,a);e.history.trigger("route",s,r,a)}});return this},execute:function(t,e,i){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(j,"\\$&").replace(A,"(?:$1)?").replace(C,function(t,e){return e?t:"([^/?]+)"}).replace(R,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];this.checkUrl=i.bind(this.checkUrl,this);if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var M=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var U=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){var t=this.location.pathname.replace(/[^\/]$/,"$&/");return t===this.root&&!this.getSearch()},matchRoot:function(){var t=this.decodeFragment(this.location.pathname);var e=t.slice(0,this.root.length-1)+"/";return e===this.root},decodeFragment:function(t){return decodeURI(t.replace(/%25/g,"%2525"))},getSearch:function(){var t=this.location.href.replace(/#.*/,"").match(/\?.+/);return t?t[0]:""},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getPath:function(){var t=this.decodeFragment(this.location.pathname+this.getSearch()).slice(this.root.length-1);return t.charAt(0)==="/"?t.slice(1):t},getFragment:function(t){if(t==null){if(this._usePushState||!this._wantsHashChange){t=this.getPath()}else{t=this.getHash()}}return t.replace(M,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._hasHashChange="onhashchange"in window&&(document.documentMode===void 0||document.documentMode>7);this._useHashChange=this._wantsHashChange&&this._hasHashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.history&&this.history.pushState);this._usePushState=this._wantsPushState&&this._hasPushState;this.fragment=this.getFragment();this.root=("/"+this.root+"/").replace(O,"/");if(this._wantsHashChange&&this._wantsPushState){if(!this._hasPushState&&!this.atRoot()){var e=this.root.slice(0,-1)||"/";this.location.replace(e+"#"+this.getPath());return true}else if(this._hasPushState&&this.atRoot()){this.navigate(this.getHash(),{replace:true})}}if(!this._hasHashChange&&this._wantsHashChange&&!this._usePushState){this.iframe=document.createElement("iframe");this.iframe.src="javascript:0";this.iframe.style.display="none";this.iframe.tabIndex=-1;var r=document.body;var n=r.insertBefore(this.iframe,r.firstChild).contentWindow;n.document.open();n.document.close();n.location.hash="#"+this.fragment}var s=window.addEventListener||function(t,e){return attachEvent("on"+t,e)};if(this._usePushState){s("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){s("hashchange",this.checkUrl,false)}else if(this._wantsHashChange){this._checkUrlInterval=setInterval(this.checkUrl,this.interval)}if(!this.options.silent)return this.loadUrl()},stop:function(){var t=window.removeEventListener||function(t,e){return detachEvent("on"+t,e)};if(this._usePushState){t("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){t("hashchange",this.checkUrl,false)}if(this.iframe){document.body.removeChild(this.iframe);this.iframe=null}if(this._checkUrlInterval)clearInterval(this._checkUrlInterval);N.started=false},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();if(e===this.fragment&&this.iframe){e=this.getHash(this.iframe.contentWindow)}if(e===this.fragment)return false;if(this.iframe)this.navigate(e);this.loadUrl()},loadUrl:function(t){if(!this.matchRoot())return false;t=this.fragment=this.getFragment(t);return i.some(this.handlers,function(e){if(e.route.test(t)){e.callback(t);return true}})},navigate:function(t,e){if(!N.started)return false;if(!e||e===true)e={trigger:!!e};t=this.getFragment(t||"");var i=this.root;if(t===""||t.charAt(0)==="?"){i=i.slice(0,-1)||"/"}var r=i+t;t=this.decodeFragment(t.replace(U,""));if(this.fragment===t)return;this.fragment=t;if(this._usePushState){this.history[e.replace?"replaceState":"pushState"]({},document.title,r)}else if(this._wantsHashChange){this._updateHash(this.location,t,e.replace);if(this.iframe&&t!==this.getHash(this.iframe.contentWindow)){var n=this.iframe.contentWindow;if(!e.replace){n.document.open();n.document.close()}this._updateHash(n.location,t,e.replace)}}else{return this.location.assign(r)}if(e.trigger)return this.loadUrl(t)},_updateHash:function(t,e,i){if(i){var r=t.href.replace(/(javascript:|#).*$/,"");t.replace(r+"#"+e)}else{t.hash="#"+e}}});e.history=new N;var q=function(t,e){var r=this;var n;if(t&&i.has(t,"constructor")){n=t.constructor}else{n=function(){return r.apply(this,arguments)}}i.extend(n,r,e);n.prototype=i.create(r.prototype,t);n.prototype.constructor=n;n.__super__=r.prototype;return n};y.extend=x.extend=$.extend=k.extend=N.extend=q;var F=function(){throw new Error('A "url" property or function must be specified')};var B=function(t,e){var i=e.error;e.error=function(r){if(i)i.call(e.context,t,r,e);t.trigger("error",t,r,e)}};return e});
        diff --git a/docs/yuidoc-p5-theme/assets/js/vendor/jquery-1.12.4.min.js b/docs/yuidoc-p5-theme/assets/js/vendor/jquery-1.12.4.min.js
        deleted file mode 100644
        index e836475870..0000000000
        --- a/docs/yuidoc-p5-theme/assets/js/vendor/jquery-1.12.4.min.js
        +++ /dev/null
        @@ -1,5 +0,0 @@
        -/*! jQuery v1.12.4 | (c) jQuery Foundation | jquery.org/license */
        -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=la(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=ma(b);function pa(){}pa.prototype=d.filters=d.pseudos,d.setFilters=new pa,g=fa.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=R.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=S.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(Q," ")}),h=h.slice(c.length));for(g in d.filter)!(e=W[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fa.error(a):z(a,i).slice(0)};function qa(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){n.each(b,function(b,c){n.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==n.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return n.each(arguments,function(a,b){var c;while((c=n.inArray(b,f,c))>-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0;
        -}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),"object"!=typeof b&&"function"!=typeof b||(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=n._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}}),function(){var a;l.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,e;return c=d.getElementsByTagName("body")[0],c&&c.style?(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(d.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(e),a):void 0}}();var T=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,U=new RegExp("^(?:([+-])=|)("+T+")([a-z%]*)$","i"),V=["Top","Right","Bottom","Left"],W=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)};function X(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return n.css(a,b,"")},i=h(),j=c&&c[3]||(n.cssNumber[b]?"":"px"),k=(n.cssNumber[b]||"px"!==j&&+i)&&U.exec(n.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,n.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var Y=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)Y(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav></:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="<textarea>x</textarea>",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:l.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/<tbody/i;function ia(a){Z.test(a.type)&&(a.defaultChecked=a.checked)}function ja(a,b,c,d,e){for(var f,g,h,i,j,k,m,o=a.length,p=ca(b),q=[],r=0;o>r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?"<table>"!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[n.expando])return a;var b,c,e,f=a.type,g=a,h=this.fixHooks[f];h||(this.fixHooks[f]=h=ma.test(f)?this.mouseHooks:la.test(f)?this.keyHooks:{}),e=h.props?this.props.concat(h.props):this.props,a=new n.Event(g),b=e.length;while(b--)c=e[b],a[c]=g[c];return a.target||(a.target=g.srcElement||d),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,h.filter?h.filter(a,g):a},props:"altKey bubbles cancelable ctrlKey currentTarget detail eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,e,f,g=b.button,h=b.fromElement;return null==a.pageX&&null!=b.clientX&&(e=a.target.ownerDocument||d,f=e.documentElement,c=e.body,a.pageX=b.clientX+(f&&f.scrollLeft||c&&c.scrollLeft||0)-(f&&f.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(f&&f.scrollTop||c&&c.scrollTop||0)-(f&&f.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&h&&(a.relatedTarget=h===a.target?b.toElement:h),a.which||void 0===g||(a.which=1&g?1:2&g?3:4&g?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==ra()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===ra()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return n.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c){var d=n.extend(new n.Event,c,{type:a,isSimulated:!0});n.event.trigger(d,null,b),d.isDefaultPrevented()&&c.preventDefault()}},n.removeEvent=d.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)}:function(a,b,c){var d="on"+b;a.detachEvent&&("undefined"==typeof a[d]&&(a[d]=null),a.detachEvent(d,c))},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?pa:qa):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={constructor:n.Event,isDefaultPrevented:qa,isPropagationStopped:qa,isImmediatePropagationStopped:qa,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=pa,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=pa,a&&!this.isSimulated&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=pa,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||n.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),l.submit||(n.event.special.submit={setup:function(){return n.nodeName(this,"form")?!1:void n.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=n.nodeName(b,"input")||n.nodeName(b,"button")?n.prop(b,"form"):void 0;c&&!n._data(c,"submit")&&(n.event.add(c,"submit._submit",function(a){a._submitBubble=!0}),n._data(c,"submit",!0))})},postDispatch:function(a){a._submitBubble&&(delete a._submitBubble,this.parentNode&&!a.isTrigger&&n.event.simulate("submit",this.parentNode,a))},teardown:function(){return n.nodeName(this,"form")?!1:void n.event.remove(this,"._submit")}}),l.change||(n.event.special.change={setup:function(){return ka.test(this.nodeName)?("checkbox"!==this.type&&"radio"!==this.type||(n.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._justChanged=!0)}),n.event.add(this,"click._change",function(a){this._justChanged&&!a.isTrigger&&(this._justChanged=!1),n.event.simulate("change",this,a)})),!1):void n.event.add(this,"beforeactivate._change",function(a){var b=a.target;ka.test(b.nodeName)&&!n._data(b,"change")&&(n.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||n.event.simulate("change",this.parentNode,a)}),n._data(b,"change",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return n.event.remove(this,"._change"),!ka.test(this.nodeName)}}),l.focusin||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a))};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=n._data(d,b);e||d.addEventListener(a,c,!0),n._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=n._data(d,b)-1;e?n._data(d,b,e):(d.removeEventListener(a,c,!0),n._removeData(d,b))}}}),n.fn.extend({on:function(a,b,c,d){return sa(this,a,b,c,d)},one:function(a,b,c,d){return sa(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=qa),this.each(function(){n.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}});var ta=/ jQuery\d+="(?:null|\d+)"/g,ua=new RegExp("<(?:"+ba+")[\\s/>]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/<script|<style|<link/i,xa=/checked\s*(?:[^=]|=\s*.checked.)/i,ya=/^true\/(.*)/,za=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Ja[0].contentWindow||Ja[0].contentDocument).document,b.write(),b.close(),c=La(a,b),Ja.detach()),Ka[a]=c),c}var Na=/^margin/,Oa=new RegExp("^("+T+")(?!px)[a-z%]+$","i"),Pa=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e},Qa=d.documentElement;!function(){var b,c,e,f,g,h,i=d.createElement("div"),j=d.createElement("div");if(j.style){j.style.cssText="float:left;opacity:.5",l.opacity="0.5"===j.style.opacity,l.cssFloat=!!j.style.cssFloat,j.style.backgroundClip="content-box",j.cloneNode(!0).style.backgroundClip="",l.clearCloneStyle="content-box"===j.style.backgroundClip,i=d.createElement("div"),i.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",j.innerHTML="",i.appendChild(j),l.boxSizing=""===j.style.boxSizing||""===j.style.MozBoxSizing||""===j.style.WebkitBoxSizing,n.extend(l,{reliableHiddenOffsets:function(){return null==b&&k(),f},boxSizingReliable:function(){return null==b&&k(),e},pixelMarginRight:function(){return null==b&&k(),c},pixelPosition:function(){return null==b&&k(),b},reliableMarginRight:function(){return null==b&&k(),g},reliableMarginLeft:function(){return null==b&&k(),h}});function k(){var k,l,m=d.documentElement;m.appendChild(i),j.style.cssText="-webkit-box-sizing:border-box;box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",b=e=h=!1,c=g=!0,a.getComputedStyle&&(l=a.getComputedStyle(j),b="1%"!==(l||{}).top,h="2px"===(l||{}).marginLeft,e="4px"===(l||{width:"4px"}).width,j.style.marginRight="50%",c="4px"===(l||{marginRight:"4px"}).marginRight,k=j.appendChild(d.createElement("div")),k.style.cssText=j.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",k.style.marginRight=k.style.width="0",j.style.width="1px",g=!parseFloat((a.getComputedStyle(k)||{}).marginRight),j.removeChild(k)),j.style.display="none",f=0===j.getClientRects().length,f&&(j.style.display="",j.innerHTML="<table><tr><td></td><td>t</td></tr></table>",j.childNodes[0].style.borderCollapse="separate",k=j.getElementsByTagName("td"),k[0].style.cssText="margin:0;border:0;padding:0;display:none",f=0===k[0].offsetHeight,f&&(k[0].style.display="",k[1].style.display="none",f=0===k[0].offsetHeight)),m.removeChild(i)}}}();var Ra,Sa,Ta=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ra=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)},Sa=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ra(a),g=c?c.getPropertyValue(b)||c[b]:void 0,""!==g&&void 0!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),c&&!l.pixelMarginRight()&&Oa.test(g)&&Na.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f),void 0===g?g:g+""}):Qa.currentStyle&&(Ra=function(a){return a.currentStyle},Sa=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ra(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Oa.test(g)&&!Ta.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function Ua(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Va=/alpha\([^)]*\)/i,Wa=/opacity\s*=\s*([^)]*)/i,Xa=/^(none|table(?!-c[ea]).+)/,Ya=new RegExp("^("+T+")(.*)$","i"),Za={position:"absolute",visibility:"hidden",display:"block"},$a={letterSpacing:"0",fontWeight:"400"},_a=["Webkit","O","Moz","ms"],ab=d.createElement("div").style;function bb(a){if(a in ab)return a;var b=a.charAt(0).toUpperCase()+a.slice(1),c=_a.length;while(c--)if(a=_a[c]+b,a in ab)return a}function cb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=n._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&W(d)&&(f[g]=n._data(d,"olddisplay",Ma(d.nodeName)))):(e=W(d),(c&&"none"!==c||!e)&&n._data(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function db(a,b,c){var d=Ya.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function eb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+V[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+V[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+V[f]+"Width",!0,e))):(g+=n.css(a,"padding"+V[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+V[f]+"Width",!0,e)));return g}function fb(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ra(a),g=l.boxSizing&&"border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Sa(a,b,f),(0>e||null==e)&&(e=a.style[b]),Oa.test(e))return e;d=g&&(l.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+eb(a,b,c||(g?"border":"content"),d,f)+"px"}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Sa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":l.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;if(b=n.cssProps[h]||(n.cssProps[h]=bb(h)||h),g=n.cssHooks[b]||n.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=U.exec(c))&&e[1]&&(c=X(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(n.cssNumber[h]?"":"px")),l.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=bb(h)||h),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Sa(a,b,d)),"normal"===f&&b in $a&&(f=$a[b]),""===c||c?(e=parseFloat(f),c===!0||isFinite(e)?e||0:f):f}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?Xa.test(n.css(a,"display"))&&0===a.offsetWidth?Pa(a,Za,function(){return fb(a,b,d)}):fb(a,b,d):void 0},set:function(a,c,d){var e=d&&Ra(a);return db(a,c,d?eb(a,b,d,l.boxSizing&&"border-box"===n.css(a,"boxSizing",!1,e),e):0)}}}),l.opacity||(n.cssHooks.opacity={get:function(a,b){return Wa.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=n.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===n.trim(f.replace(Va,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Va.test(f)?f.replace(Va,e):f+" "+e)}}),n.cssHooks.marginRight=Ua(l.reliableMarginRight,function(a,b){return b?Pa(a,{display:"inline-block"},Sa,[a,"marginRight"]):void 0}),n.cssHooks.marginLeft=Ua(l.reliableMarginLeft,function(a,b){return b?(parseFloat(Sa(a,"marginLeft"))||(n.contains(a.ownerDocument,a)?a.getBoundingClientRect().left-Pa(a,{
        -marginLeft:0},function(){return a.getBoundingClientRect().left}):0))+"px":void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+V[d]+b]=f[d]||f[d-2]||f[0];return e}},Na.test(a)||(n.cssHooks[a+b].set=db)}),n.fn.extend({css:function(a,b){return Y(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=Ra(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return cb(this,!0)},hide:function(){return cb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){W(this)?n(this).show():n(this).hide()})}});function gb(a,b,c,d,e){return new gb.prototype.init(a,b,c,d,e)}n.Tween=gb,gb.prototype={constructor:gb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||n.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=gb.propHooks[this.prop];return a&&a.get?a.get(this):gb.propHooks._default.get(this)},run:function(a){var b,c=gb.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):gb.propHooks._default.set(this),this}},gb.prototype.init.prototype=gb.prototype,gb.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[n.cssProps[a.prop]]&&!n.cssHooks[a.prop]?a.elem[a.prop]=a.now:n.style(a.elem,a.prop,a.now+a.unit)}}},gb.propHooks.scrollTop=gb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},n.fx=gb.prototype.init,n.fx.step={};var hb,ib,jb=/^(?:toggle|show|hide)$/,kb=/queueHooks$/;function lb(){return a.setTimeout(function(){hb=void 0}),hb=n.now()}function mb(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=V[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function nb(a,b,c){for(var d,e=(qb.tweeners[b]||[]).concat(qb.tweeners["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ob(a,b,c){var d,e,f,g,h,i,j,k,m=this,o={},p=a.style,q=a.nodeType&&W(a),r=n._data(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,m.always(function(){m.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=n.css(a,"display"),k="none"===j?n._data(a,"olddisplay")||Ma(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(l.inlineBlockNeedsLayout&&"inline"!==Ma(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",l.shrinkWrapBlocks()||m.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],jb.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(o))"inline"===("none"===j?Ma(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=n._data(a,"fxshow",{}),f&&(r.hidden=!q),q?n(a).show():m.done(function(){n(a).hide()}),m.done(function(){var b;n._removeData(a,"fxshow");for(b in o)n.style(a,b,o[b])});for(d in o)g=nb(q?r[d]:0,d,m),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function pb(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function qb(a,b,c){var d,e,f=0,g=qb.prefilters.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=hb||lb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{},easing:n.easing._default},c),originalProperties:b,originalOptions:c,startTime:hb||lb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for(pb(k,j.opts.specialEasing);g>f;f++)if(d=qb.prefilters[f].call(j,a,k,j.opts))return n.isFunction(d.stop)&&(n._queueHooks(j.elem,j.opts.queue).stop=n.proxy(d.stop,d)),d;return n.map(k,nb,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(qb,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return X(c.elem,a,U.exec(b),c),c}]},tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.match(G);for(var c,d=0,e=a.length;e>d;d++)c=a[d],qb.tweeners[c]=qb.tweeners[c]||[],qb.tweeners[c].unshift(b)},prefilters:[ob],prefilter:function(a,b){b?qb.prefilters.unshift(a):qb.prefilters.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,null!=d.queue&&d.queue!==!0||(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(W).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=qb(this,n.extend({},a),f);(e||n._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=n._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&kb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=n._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(mb(b,!0),a,d,e)}}),n.each({slideDown:mb("show"),slideUp:mb("hide"),slideToggle:mb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=n.timers,c=0;for(hb=n.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||n.fx.stop(),hb=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){ib||(ib=a.setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){a.clearInterval(ib),ib=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(b,c){return b=n.fx?n.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a,b=d.createElement("input"),c=d.createElement("div"),e=d.createElement("select"),f=e.appendChild(d.createElement("option"));c=d.createElement("div"),c.setAttribute("className","t"),c.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",a=c.getElementsByTagName("a")[0],b.setAttribute("type","checkbox"),c.appendChild(b),a=c.getElementsByTagName("a")[0],a.style.cssText="top:1px",l.getSetAttribute="t"!==c.className,l.style=/top/.test(a.getAttribute("style")),l.hrefNormalized="/a"===a.getAttribute("href"),l.checkOn=!!b.value,l.optSelected=f.selected,l.enctype=!!d.createElement("form").enctype,e.disabled=!0,l.optDisabled=!f.disabled,b=d.createElement("input"),b.setAttribute("value",""),l.input=""===b.getAttribute("value"),b.value="t",b.setAttribute("type","radio"),l.radioValue="t"===b.value}();var rb=/\r/g,sb=/[\x20\t\r\n\f]+/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a)).replace(sb," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],(c.selected||i===e)&&(l.optDisabled?!c.disabled:null===c.getAttribute("disabled"))&&(!c.parentNode.disabled||!n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)if(d=e[g],n.inArray(n.valHooks.option.get(d),f)>-1)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>-1:void 0}},l.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var tb,ub,vb=n.expr.attrHandle,wb=/^(?:checked|selected)$/i,xb=l.getSetAttribute,yb=l.input;n.fn.extend({attr:function(a,b){return Y(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),e=n.attrHooks[b]||(n.expr.match.bool.test(b)?ub:tb)),void 0!==c?null===c?void n.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=n.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!l.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(G);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)?yb&&xb||!wb.test(c)?a[d]=!1:a[n.camelCase("default-"+c)]=a[d]=!1:n.attr(a,c,""),a.removeAttribute(xb?c:d)}}),ub={set:function(a,b,c){return b===!1?n.removeAttr(a,c):yb&&xb||!wb.test(c)?a.setAttribute(!xb&&n.propFix[c]||c,c):a[n.camelCase("default-"+c)]=a[c]=!0,c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=vb[b]||n.find.attr;yb&&xb||!wb.test(b)?vb[b]=function(a,b,d){var e,f;return d||(f=vb[b],vb[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,vb[b]=f),e}:vb[b]=function(a,b,c){return c?void 0:a[n.camelCase("default-"+b)]?b.toLowerCase():null}}),yb&&xb||(n.attrHooks.value={set:function(a,b,c){return n.nodeName(a,"input")?void(a.defaultValue=b):tb&&tb.set(a,b,c)}}),xb||(tb={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},vb.id=vb.name=vb.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},n.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:tb.set},n.attrHooks.contenteditable={set:function(a,b,c){tb.set(a,""===b?!1:b,c)}},n.each(["width","height"],function(a,b){n.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),l.style||(n.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var zb=/^(?:input|select|textarea|button|object)$/i,Ab=/^(?:a|area)$/i;n.fn.extend({prop:function(a,b){return Y(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return a=n.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),n.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&n.isXMLDoc(a)||(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=n.find.attr(a,"tabindex");return b?parseInt(b,10):zb.test(a.nodeName)||Ab.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),l.hrefNormalized||n.each(["href","src"],function(a,b){n.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),l.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this}),l.enctype||(n.propFix.enctype="encoding");var Bb=/[\t\r\n\f]/g;function Cb(a){return n.attr(a,"class")||""}n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,Cb(this)))});if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=Cb(c),d=1===c.nodeType&&(" "+e+" ").replace(Bb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=n.trim(d),e!==h&&n.attr(c,"class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,Cb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=Cb(c),d=1===c.nodeType&&(" "+e+" ").replace(Bb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=n.trim(d),e!==h&&n.attr(c,"class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):n.isFunction(a)?this.each(function(c){n(this).toggleClass(a.call(this,c,Cb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=n(this),f=a.match(G)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=Cb(this),b&&n._data(this,"__className__",b),n.attr(this,"class",b||a===!1?"":n._data(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+Cb(c)+" ").replace(Bb," ").indexOf(b)>-1)return!0;return!1}}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Db=a.location,Eb=n.now(),Fb=/\?/,Gb=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;n.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=n.trim(b+"");return e&&!n.trim(e.replace(Gb,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():n.error("Invalid JSON: "+b)},n.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new a.DOMParser,c=d.parseFromString(b,"text/xml")):(c=new a.ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||n.error("Invalid XML: "+b),c};var Hb=/#.*$/,Ib=/([?&])_=[^&]*/,Jb=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Kb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Lb=/^(?:GET|HEAD)$/,Mb=/^\/\//,Nb=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Ob={},Pb={},Qb="*/".concat("*"),Rb=Db.href,Sb=Nb.exec(Rb.toLowerCase())||[];function Tb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(G)||[];if(n.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Ub(a,b,c,d){var e={},f=a===Pb;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Vb(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&n.extend(!0,a,c),a}function Wb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Xb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Rb,type:"GET",isLocal:Kb.test(Sb[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Qb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Vb(Vb(a,n.ajaxSettings),b):Vb(n.ajaxSettings,a)},ajaxPrefilter:Tb(Ob),ajaxTransport:Tb(Pb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var d,e,f,g,h,i,j,k,l=n.ajaxSetup({},c),m=l.context||l,o=l.context&&(m.nodeType||m.jquery)?n(m):n.event,p=n.Deferred(),q=n.Callbacks("once memory"),r=l.statusCode||{},s={},t={},u=0,v="canceled",w={readyState:0,getResponseHeader:function(a){var b;if(2===u){if(!k){k={};while(b=Jb.exec(g))k[b[1].toLowerCase()]=b[2]}b=k[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===u?g:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return u||(a=t[c]=t[c]||a,s[a]=b),this},overrideMimeType:function(a){return u||(l.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>u)for(b in a)r[b]=[r[b],a[b]];else w.always(a[w.status]);return this},abort:function(a){var b=a||v;return j&&j.abort(b),y(0,b),this}};if(p.promise(w).complete=q.add,w.success=w.done,w.error=w.fail,l.url=((b||l.url||Rb)+"").replace(Hb,"").replace(Mb,Sb[1]+"//"),l.type=c.method||c.type||l.method||l.type,l.dataTypes=n.trim(l.dataType||"*").toLowerCase().match(G)||[""],null==l.crossDomain&&(d=Nb.exec(l.url.toLowerCase()),l.crossDomain=!(!d||d[1]===Sb[1]&&d[2]===Sb[2]&&(d[3]||("http:"===d[1]?"80":"443"))===(Sb[3]||("http:"===Sb[1]?"80":"443")))),l.data&&l.processData&&"string"!=typeof l.data&&(l.data=n.param(l.data,l.traditional)),Ub(Ob,l,c,w),2===u)return w;i=n.event&&l.global,i&&0===n.active++&&n.event.trigger("ajaxStart"),l.type=l.type.toUpperCase(),l.hasContent=!Lb.test(l.type),f=l.url,l.hasContent||(l.data&&(f=l.url+=(Fb.test(f)?"&":"?")+l.data,delete l.data),l.cache===!1&&(l.url=Ib.test(f)?f.replace(Ib,"$1_="+Eb++):f+(Fb.test(f)?"&":"?")+"_="+Eb++)),l.ifModified&&(n.lastModified[f]&&w.setRequestHeader("If-Modified-Since",n.lastModified[f]),n.etag[f]&&w.setRequestHeader("If-None-Match",n.etag[f])),(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&w.setRequestHeader("Content-Type",l.contentType),w.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+("*"!==l.dataTypes[0]?", "+Qb+"; q=0.01":""):l.accepts["*"]);for(e in l.headers)w.setRequestHeader(e,l.headers[e]);if(l.beforeSend&&(l.beforeSend.call(m,w,l)===!1||2===u))return w.abort();v="abort";for(e in{success:1,error:1,complete:1})w[e](l[e]);if(j=Ub(Pb,l,c,w)){if(w.readyState=1,i&&o.trigger("ajaxSend",[w,l]),2===u)return w;l.async&&l.timeout>0&&(h=a.setTimeout(function(){w.abort("timeout")},l.timeout));try{u=1,j.send(s,y)}catch(x){if(!(2>u))throw x;y(-1,x)}}else y(-1,"No Transport");function y(b,c,d,e){var k,s,t,v,x,y=c;2!==u&&(u=2,h&&a.clearTimeout(h),j=void 0,g=e||"",w.readyState=b>0?4:0,k=b>=200&&300>b||304===b,d&&(v=Wb(l,w,d)),v=Xb(l,v,w,k),k?(l.ifModified&&(x=w.getResponseHeader("Last-Modified"),x&&(n.lastModified[f]=x),x=w.getResponseHeader("etag"),x&&(n.etag[f]=x)),204===b||"HEAD"===l.type?y="nocontent":304===b?y="notmodified":(y=v.state,s=v.data,t=v.error,k=!t)):(t=y,!b&&y||(y="error",0>b&&(b=0))),w.status=b,w.statusText=(c||y)+"",k?p.resolveWith(m,[s,y,w]):p.rejectWith(m,[w,y,t]),w.statusCode(r),r=void 0,i&&o.trigger(k?"ajaxSuccess":"ajaxError",[w,l,k?s:t]),q.fireWith(m,[w,y]),i&&(o.trigger("ajaxComplete",[w,l]),--n.active||n.event.trigger("ajaxStop")))}return w},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax(n.extend({url:a,type:b,dataType:e,data:c,success:d},n.isPlainObject(a)&&a))}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){if(n.isFunction(a))return this.each(function(b){n(this).wrapAll(a.call(this,b))});if(this[0]){var b=n(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return n.isFunction(a)?this.each(function(b){n(this).wrapInner(a.call(this,b))}):this.each(function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}});function Yb(a){return a.style&&a.style.display||n.css(a,"display")}function Zb(a){if(!n.contains(a.ownerDocument||d,a))return!0;while(a&&1===a.nodeType){if("none"===Yb(a)||"hidden"===a.type)return!0;a=a.parentNode}return!1}n.expr.filters.hidden=function(a){return l.reliableHiddenOffsets()?a.offsetWidth<=0&&a.offsetHeight<=0&&!a.getClientRects().length:Zb(a)},n.expr.filters.visible=function(a){return!n.expr.filters.hidden(a)};var $b=/%20/g,_b=/\[\]$/,ac=/\r?\n/g,bc=/^(?:submit|button|image|reset|file)$/i,cc=/^(?:input|select|textarea|keygen)/i;function dc(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||_b.test(a)?d(a,e):dc(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)dc(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)dc(c,a[c],b,e);return d.join("&").replace($b,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&cc.test(this.nodeName)&&!bc.test(a)&&(this.checked||!Z.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(ac,"\r\n")}}):{name:b.name,value:c.replace(ac,"\r\n")}}).get()}}),n.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return this.isLocal?ic():d.documentMode>8?hc():/^(get|post|head|put|delete|options)$/i.test(this.type)&&hc()||ic()}:hc;var ec=0,fc={},gc=n.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in fc)fc[a](void 0,!0)}),l.cors=!!gc&&"withCredentials"in gc,gc=l.ajax=!!gc,gc&&n.ajaxTransport(function(b){if(!b.crossDomain||l.cors){var c;return{send:function(d,e){var f,g=b.xhr(),h=++ec;if(g.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(f in b.xhrFields)g[f]=b.xhrFields[f];b.mimeType&&g.overrideMimeType&&g.overrideMimeType(b.mimeType),b.crossDomain||d["X-Requested-With"]||(d["X-Requested-With"]="XMLHttpRequest");for(f in d)void 0!==d[f]&&g.setRequestHeader(f,d[f]+"");g.send(b.hasContent&&b.data||null),c=function(a,d){var f,i,j;if(c&&(d||4===g.readyState))if(delete fc[h],c=void 0,g.onreadystatechange=n.noop,d)4!==g.readyState&&g.abort();else{j={},f=g.status,"string"==typeof g.responseText&&(j.text=g.responseText);try{i=g.statusText}catch(k){i=""}f||!b.isLocal||b.crossDomain?1223===f&&(f=204):f=j.text?200:404}j&&e(f,i,j,g.getAllResponseHeaders())},b.async?4===g.readyState?a.setTimeout(c):g.onreadystatechange=fc[h]=c:c()},abort:function(){c&&c(void 0,!0)}}}});function hc(){try{return new a.XMLHttpRequest}catch(b){}}function ic(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=d.head||n("head")[0]||d.documentElement;return{send:function(e,f){b=d.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||f(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var jc=[],kc=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=jc.pop()||n.expando+"_"+Eb++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(kc.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&kc.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(kc,"$1"+e):b.jsonp!==!1&&(b.url+=(Fb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?n(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,jc.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||d;var e=x.exec(a),f=!c&&[];return e?[b.createElement(e[1])]:(e=ja([a],b,f),f&&f.length&&n(f).remove(),n.merge([],e.childNodes))};var lc=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&lc)return lc.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=n.trim(a.slice(h,a.length)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};function mc(a){return n.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&n.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,n.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,n.contains(b,e)?("undefined"!=typeof e.getBoundingClientRect&&(d=e.getBoundingClientRect()),c=mc(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===n.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(c=a.offset()),c.top+=n.css(a[0],"borderTopWidth",!0),c.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-n.css(d,"marginTop",!0),left:b.left-c.left-n.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&!n.nodeName(a,"html")&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Qa})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);n.fn[a]=function(d){return Y(this,function(a,d,e){var f=mc(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?n(f).scrollLeft():e,c?e:n(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=Ua(l.pixelPosition,function(a,c){return c?(c=Sa(a,b),Oa.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({
        -padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return Y(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var nc=a.jQuery,oc=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=oc),b&&a.jQuery===n&&(a.jQuery=nc),n},b||(a.jQuery=a.$=n),n});
        diff --git a/docs/yuidoc-p5-theme/assets/js/vendor/prism.js b/docs/yuidoc-p5-theme/assets/js/vendor/prism.js
        deleted file mode 100644
        index 1a99ea34c6..0000000000
        --- a/docs/yuidoc-p5-theme/assets/js/vendor/prism.js
        +++ /dev/null
        @@ -1,10 +0,0 @@
        -/* http://prismjs.com/download.html?themes=prism-coy&languages=markup+css+clike+javascript+css-extras+java&plugins=line-numbers+normalize-whitespace */
        -var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function(e){var t=n.util.type(e);switch(t){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=n.util.clone(e[r]));return a;case"Array":return e.map&&e.map(function(e){return n.util.clone(e)})}return e}},languages:{extend:function(e,t){var a=n.util.clone(n.languages[e]);for(var r in t)a[r]=t[r];return a},insertBefore:function(e,t,a,r){r=r||n.languages;var i=r[e];if(2==arguments.length){a=arguments[1];for(var l in a)a.hasOwnProperty(l)&&(i[l]=a[l]);return i}var o={};for(var s in i)if(i.hasOwnProperty(s)){if(s==t)for(var l in a)a.hasOwnProperty(l)&&(o[l]=a[l]);o[s]=i[s]}return n.languages.DFS(n.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,t,a,r){r=r||{};for(var i in e)e.hasOwnProperty(i)&&(t.call(e,i,e[i],a||i),"Object"!==n.util.type(e[i])||r[n.util.objId(e[i])]?"Array"!==n.util.type(e[i])||r[n.util.objId(e[i])]||(r[n.util.objId(e[i])]=!0,n.languages.DFS(e[i],t,i,r)):(r[n.util.objId(e[i])]=!0,n.languages.DFS(e[i],t,null,r)))}},plugins:{},highlightAll:function(e,t){var a={callback:t,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};n.hooks.run("before-highlightall",a);for(var r,i=a.elements||document.querySelectorAll(a.selector),l=0;r=i[l++];)n.highlightElement(r,e===!0,a.callback)},highlightElement:function(t,a,r){for(var i,l,o=t;o&&!e.test(o.className);)o=o.parentNode;o&&(i=(o.className.match(e)||[,""])[1].toLowerCase(),l=n.languages[i]),t.className=t.className.replace(e,"").replace(/\s+/g," ")+" language-"+i,o=t.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,"").replace(/\s+/g," ")+" language-"+i);var s=t.textContent,u={element:t,language:i,grammar:l,code:s};if(n.hooks.run("before-sanity-check",u),!u.code||!u.grammar)return n.hooks.run("complete",u),void 0;if(n.hooks.run("before-highlight",u),a&&_self.Worker){var c=new Worker(n.filename);c.onmessage=function(e){u.highlightedCode=e.data,n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(u.element),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},c.postMessage(JSON.stringify({language:u.language,code:u.code,immediateClose:!0}))}else u.highlightedCode=n.highlight(u.code,u.grammar,u.language),n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(t),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},highlight:function(e,t,r){var i=n.tokenize(e,t);return a.stringify(n.util.encode(i),r)},tokenize:function(e,t){var a=n.Token,r=[e],i=t.rest;if(i){for(var l in i)t[l]=i[l];delete t.rest}e:for(var l in t)if(t.hasOwnProperty(l)&&t[l]){var o=t[l];o="Array"===n.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var u=o[s],c=u.inside,g=!!u.lookbehind,h=!!u.greedy,f=0,d=u.alias;if(h&&!u.pattern.global){var p=u.pattern.toString().match(/[imuy]*$/)[0];u.pattern=RegExp(u.pattern.source,p+"g")}u=u.pattern||u;for(var m=0,y=0;m<r.length;y+=(r[m].matchedStr||r[m]).length,++m){var v=r[m];if(r.length>e.length)break e;if(!(v instanceof a)){u.lastIndex=0;var b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var w=b.index+(g?b[1].length:0),_=b.index+b[0].length,A=m,S=y,P=r.length;P>A&&_>S;++A)S+=(r[A].matchedStr||r[A]).length,w>=S&&(++m,y=S);if(r[m]instanceof a||r[A-1].greedy)continue;k=A-m,v=e.slice(y,S),b.index-=y}if(b){g&&(f=b[1].length);var w=b.index+f,b=b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),j=[m,k];x&&j.push(x);var N=new a(l,c?n.tokenize(b,c):b,d,b,h);j.push(N),O&&j.push(O),Array.prototype.splice.apply(r,j)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.matchedStr=a||null,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var i={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}n.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=(o?" ":"")+s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+(o?" "+o:"")+">"+i.content+"</"+i.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,i=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),i&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
        -Prism.languages.markup={comment:/<!--[\w\W]*?-->/,prolog:/<\?[\w\W]+?\?>/,doctype:/<!DOCTYPE[\w\W]+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&amp;/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup;
        -Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:{pattern:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<style[\w\W]*?>)[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag));
        -Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};
        -Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*\*?|\/|~|\^|%|\.{3}/}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<script[\w\W]*?>)[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript;
        -Prism.languages.css.selector={pattern:/[^\{\}\s][^\{\}]*(?=\s*\{)/,inside:{"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+(?:\(.*\))?/,"class":/\.[-:\.\w]+/,id:/#[-:\.\w]+/,attribute:/\[[^\]]+\]/}},Prism.languages.insertBefore("css","function",{hexcode:/#[\da-f]{3,6}/i,entity:/\\[\da-f]{1,8}/i,number:/[\d%\.]+/});
        -Prism.languages.java=Prism.languages.extend("clike",{keyword:/\b(abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/,number:/\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+(?:e[+-]?\d+)?[df]?\b/i,operator:{pattern:/(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<<?=?|>>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,lookbehind:!0}}),Prism.languages.insertBefore("java","function",{annotation:{alias:"punctuation",pattern:/(^|[^.])@\w+/,lookbehind:!0}});
        -!function(){"undefined"!=typeof self&&self.Prism&&self.document&&Prism.hooks.add("complete",function(e){if(e.code){var t=e.element.parentNode,s=/\s*\bline-numbers\b\s*/;if(t&&/pre/i.test(t.nodeName)&&(s.test(t.className)||s.test(e.element.className))&&!e.element.querySelector(".line-numbers-rows")){s.test(e.element.className)&&(e.element.className=e.element.className.replace(s,"")),s.test(t.className)||(t.className+=" line-numbers");var n,a=e.code.match(/\n(?!$)/g),l=a?a.length+1:1,r=new Array(l+1);r=r.join("<span></span>"),n=document.createElement("span"),n.setAttribute("aria-hidden","true"),n.className="line-numbers-rows",n.innerHTML=r,t.hasAttribute("data-start")&&(t.style.counterReset="linenumber "+(parseInt(t.getAttribute("data-start"),10)-1)),e.element.appendChild(n)}}})}();
        -!function(){function e(e){this.defaults=r({},e)}function n(e){return e.replace(/-(\w)/g,function(e,n){return n.toUpperCase()})}function t(e){for(var n=0,t=0;t<e.length;++t)e.charCodeAt(t)=="	".charCodeAt(0)&&(n+=3);return e.length+n}if("undefined"!=typeof self&&self.Prism&&self.document){var r=Object.assign||function(e,n){for(var t in n)n.hasOwnProperty(t)&&(e[t]=n[t]);return e};e.prototype={setDefaults:function(e){this.defaults=r(this.defaults,e)},normalize:function(e,t){t=r(this.defaults,t);for(var i in t){var o=n(i);"normalize"!==i&&"setDefaults"!==o&&t[i]&&this[o]&&(e=this[o].call(this,e,t[i]))}return e},leftTrim:function(e){return e.replace(/^\s+/,"")},rightTrim:function(e){return e.replace(/\s+$/,"")},tabsToSpaces:function(e,n){return n=0|n||4,e.replace(/\t/g,new Array(++n).join(" "))},spacesToTabs:function(e,n){return n=0|n||4,e.replace(new RegExp(" {"+n+"}","g"),"	")},removeTrailing:function(e){return e.replace(/\s*?$/gm,"")},removeInitialLineFeed:function(e){return e.replace(/^(?:\r?\n|\r)/,"")},removeIndent:function(e){var n=e.match(/^[^\S\n\r]*(?=\S)/gm);return n&&n[0].length?(n.sort(function(e,n){return e.length-n.length}),n[0].length?e.replace(new RegExp("^"+n[0],"gm"),""):e):e},indent:function(e,n){return e.replace(/^[^\S\n\r]*(?=\S)/gm,new Array(++n).join("	")+"$&")},breakLines:function(e,n){n=n===!0?80:0|n||80;for(var r=e.split("\n"),i=0;i<r.length;++i)if(!(t(r[i])<=n)){for(var o=r[i].split(/(\s+)/g),a=0,s=0;s<o.length;++s){var l=t(o[s]);a+=l,a>n&&(o[s]="\n"+o[s],a=l)}r[i]=o.join("")}return r.join("\n")}},Prism.plugins.NormalizeWhitespace=new e({"remove-trailing":!0,"remove-indent":!0,"left-trim":!0,"right-trim":!0}),Prism.hooks.add("before-highlight",function(e){var n=e.element.parentNode,t=/\bno-whitespace-normalization\b/;if(!(!e.code||!n||"pre"!==n.nodeName.toLowerCase()||e.settings&&e.settings["whitespace-normalization"]===!1||t.test(n.className)||t.test(e.element.className))){for(var r=n.childNodes,i="",o="",a=!1,s=Prism.plugins.NormalizeWhitespace,l=0;l<r.length;++l){var c=r[l];c==e.element?a=!0:"#text"===c.nodeName&&(a?o+=c.nodeValue:i+=c.nodeValue,n.removeChild(c),--l)}if(e.element.children.length&&Prism.plugins.KeepMarkup){var u=i+e.element.innerHTML+o;e.element.innerHTML=s.normalize(u,e.settings),e.code=e.element.textContent}else e.code=i+e.code+o,e.code=s.normalize(e.code,e.settings)}})}}();
        diff --git a/docs/yuidoc-p5-theme/assets/js/vendor/underscore-min.js b/docs/yuidoc-p5-theme/assets/js/vendor/underscore-min.js
        deleted file mode 100644
        index f2a20a54a2..0000000000
        --- a/docs/yuidoc-p5-theme/assets/js/vendor/underscore-min.js
        +++ /dev/null
        @@ -1,5 +0,0 @@
        -//     Underscore.js 1.9.1
        -//     http://underscorejs.org
        -//     (c) 2009-2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
        -//     Underscore may be freely distributed under the MIT license.
        -!function(){var n="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global||this||{},r=n._,e=Array.prototype,o=Object.prototype,s="undefined"!=typeof Symbol?Symbol.prototype:null,u=e.push,c=e.slice,p=o.toString,i=o.hasOwnProperty,t=Array.isArray,a=Object.keys,l=Object.create,f=function(){},h=function(n){return n instanceof h?n:this instanceof h?void(this._wrapped=n):new h(n)};"undefined"==typeof exports||exports.nodeType?n._=h:("undefined"!=typeof module&&!module.nodeType&&module.exports&&(exports=module.exports=h),exports._=h),h.VERSION="1.9.1";var v,y=function(u,i,n){if(void 0===i)return u;switch(null==n?3:n){case 1:return function(n){return u.call(i,n)};case 3:return function(n,r,t){return u.call(i,n,r,t)};case 4:return function(n,r,t,e){return u.call(i,n,r,t,e)}}return function(){return u.apply(i,arguments)}},d=function(n,r,t){return h.iteratee!==v?h.iteratee(n,r):null==n?h.identity:h.isFunction(n)?y(n,r,t):h.isObject(n)&&!h.isArray(n)?h.matcher(n):h.property(n)};h.iteratee=v=function(n,r){return d(n,r,1/0)};var g=function(u,i){return i=null==i?u.length-1:+i,function(){for(var n=Math.max(arguments.length-i,0),r=Array(n),t=0;t<n;t++)r[t]=arguments[t+i];switch(i){case 0:return u.call(this,r);case 1:return u.call(this,arguments[0],r);case 2:return u.call(this,arguments[0],arguments[1],r)}var e=Array(i+1);for(t=0;t<i;t++)e[t]=arguments[t];return e[i]=r,u.apply(this,e)}},m=function(n){if(!h.isObject(n))return{};if(l)return l(n);f.prototype=n;var r=new f;return f.prototype=null,r},b=function(r){return function(n){return null==n?void 0:n[r]}},j=function(n,r){return null!=n&&i.call(n,r)},x=function(n,r){for(var t=r.length,e=0;e<t;e++){if(null==n)return;n=n[r[e]]}return t?n:void 0},_=Math.pow(2,53)-1,A=b("length"),w=function(n){var r=A(n);return"number"==typeof r&&0<=r&&r<=_};h.each=h.forEach=function(n,r,t){var e,u;if(r=y(r,t),w(n))for(e=0,u=n.length;e<u;e++)r(n[e],e,n);else{var i=h.keys(n);for(e=0,u=i.length;e<u;e++)r(n[i[e]],i[e],n)}return n},h.map=h.collect=function(n,r,t){r=d(r,t);for(var e=!w(n)&&h.keys(n),u=(e||n).length,i=Array(u),o=0;o<u;o++){var a=e?e[o]:o;i[o]=r(n[a],a,n)}return i};var O=function(c){return function(n,r,t,e){var u=3<=arguments.length;return function(n,r,t,e){var u=!w(n)&&h.keys(n),i=(u||n).length,o=0<c?0:i-1;for(e||(t=n[u?u[o]:o],o+=c);0<=o&&o<i;o+=c){var a=u?u[o]:o;t=r(t,n[a],a,n)}return t}(n,y(r,e,4),t,u)}};h.reduce=h.foldl=h.inject=O(1),h.reduceRight=h.foldr=O(-1),h.find=h.detect=function(n,r,t){var e=(w(n)?h.findIndex:h.findKey)(n,r,t);if(void 0!==e&&-1!==e)return n[e]},h.filter=h.select=function(n,e,r){var u=[];return e=d(e,r),h.each(n,function(n,r,t){e(n,r,t)&&u.push(n)}),u},h.reject=function(n,r,t){return h.filter(n,h.negate(d(r)),t)},h.every=h.all=function(n,r,t){r=d(r,t);for(var e=!w(n)&&h.keys(n),u=(e||n).length,i=0;i<u;i++){var o=e?e[i]:i;if(!r(n[o],o,n))return!1}return!0},h.some=h.any=function(n,r,t){r=d(r,t);for(var e=!w(n)&&h.keys(n),u=(e||n).length,i=0;i<u;i++){var o=e?e[i]:i;if(r(n[o],o,n))return!0}return!1},h.contains=h.includes=h.include=function(n,r,t,e){return w(n)||(n=h.values(n)),("number"!=typeof t||e)&&(t=0),0<=h.indexOf(n,r,t)},h.invoke=g(function(n,t,e){var u,i;return h.isFunction(t)?i=t:h.isArray(t)&&(u=t.slice(0,-1),t=t[t.length-1]),h.map(n,function(n){var r=i;if(!r){if(u&&u.length&&(n=x(n,u)),null==n)return;r=n[t]}return null==r?r:r.apply(n,e)})}),h.pluck=function(n,r){return h.map(n,h.property(r))},h.where=function(n,r){return h.filter(n,h.matcher(r))},h.findWhere=function(n,r){return h.find(n,h.matcher(r))},h.max=function(n,e,r){var t,u,i=-1/0,o=-1/0;if(null==e||"number"==typeof e&&"object"!=typeof n[0]&&null!=n)for(var a=0,c=(n=w(n)?n:h.values(n)).length;a<c;a++)null!=(t=n[a])&&i<t&&(i=t);else e=d(e,r),h.each(n,function(n,r,t){u=e(n,r,t),(o<u||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},h.min=function(n,e,r){var t,u,i=1/0,o=1/0;if(null==e||"number"==typeof e&&"object"!=typeof n[0]&&null!=n)for(var a=0,c=(n=w(n)?n:h.values(n)).length;a<c;a++)null!=(t=n[a])&&t<i&&(i=t);else e=d(e,r),h.each(n,function(n,r,t){((u=e(n,r,t))<o||u===1/0&&i===1/0)&&(i=n,o=u)});return i},h.shuffle=function(n){return h.sample(n,1/0)},h.sample=function(n,r,t){if(null==r||t)return w(n)||(n=h.values(n)),n[h.random(n.length-1)];var e=w(n)?h.clone(n):h.values(n),u=A(e);r=Math.max(Math.min(r,u),0);for(var i=u-1,o=0;o<r;o++){var a=h.random(o,i),c=e[o];e[o]=e[a],e[a]=c}return e.slice(0,r)},h.sortBy=function(n,e,r){var u=0;return e=d(e,r),h.pluck(h.map(n,function(n,r,t){return{value:n,index:u++,criteria:e(n,r,t)}}).sort(function(n,r){var t=n.criteria,e=r.criteria;if(t!==e){if(e<t||void 0===t)return 1;if(t<e||void 0===e)return-1}return n.index-r.index}),"value")};var k=function(o,r){return function(e,u,n){var i=r?[[],[]]:{};return u=d(u,n),h.each(e,function(n,r){var t=u(n,r,e);o(i,n,t)}),i}};h.groupBy=k(function(n,r,t){j(n,t)?n[t].push(r):n[t]=[r]}),h.indexBy=k(function(n,r,t){n[t]=r}),h.countBy=k(function(n,r,t){j(n,t)?n[t]++:n[t]=1});var S=/[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;h.toArray=function(n){return n?h.isArray(n)?c.call(n):h.isString(n)?n.match(S):w(n)?h.map(n,h.identity):h.values(n):[]},h.size=function(n){return null==n?0:w(n)?n.length:h.keys(n).length},h.partition=k(function(n,r,t){n[t?0:1].push(r)},!0),h.first=h.head=h.take=function(n,r,t){return null==n||n.length<1?null==r?void 0:[]:null==r||t?n[0]:h.initial(n,n.length-r)},h.initial=function(n,r,t){return c.call(n,0,Math.max(0,n.length-(null==r||t?1:r)))},h.last=function(n,r,t){return null==n||n.length<1?null==r?void 0:[]:null==r||t?n[n.length-1]:h.rest(n,Math.max(0,n.length-r))},h.rest=h.tail=h.drop=function(n,r,t){return c.call(n,null==r||t?1:r)},h.compact=function(n){return h.filter(n,Boolean)};var M=function(n,r,t,e){for(var u=(e=e||[]).length,i=0,o=A(n);i<o;i++){var a=n[i];if(w(a)&&(h.isArray(a)||h.isArguments(a)))if(r)for(var c=0,l=a.length;c<l;)e[u++]=a[c++];else M(a,r,t,e),u=e.length;else t||(e[u++]=a)}return e};h.flatten=function(n,r){return M(n,r,!1)},h.without=g(function(n,r){return h.difference(n,r)}),h.uniq=h.unique=function(n,r,t,e){h.isBoolean(r)||(e=t,t=r,r=!1),null!=t&&(t=d(t,e));for(var u=[],i=[],o=0,a=A(n);o<a;o++){var c=n[o],l=t?t(c,o,n):c;r&&!t?(o&&i===l||u.push(c),i=l):t?h.contains(i,l)||(i.push(l),u.push(c)):h.contains(u,c)||u.push(c)}return u},h.union=g(function(n){return h.uniq(M(n,!0,!0))}),h.intersection=function(n){for(var r=[],t=arguments.length,e=0,u=A(n);e<u;e++){var i=n[e];if(!h.contains(r,i)){var o;for(o=1;o<t&&h.contains(arguments[o],i);o++);o===t&&r.push(i)}}return r},h.difference=g(function(n,r){return r=M(r,!0,!0),h.filter(n,function(n){return!h.contains(r,n)})}),h.unzip=function(n){for(var r=n&&h.max(n,A).length||0,t=Array(r),e=0;e<r;e++)t[e]=h.pluck(n,e);return t},h.zip=g(h.unzip),h.object=function(n,r){for(var t={},e=0,u=A(n);e<u;e++)r?t[n[e]]=r[e]:t[n[e][0]]=n[e][1];return t};var F=function(i){return function(n,r,t){r=d(r,t);for(var e=A(n),u=0<i?0:e-1;0<=u&&u<e;u+=i)if(r(n[u],u,n))return u;return-1}};h.findIndex=F(1),h.findLastIndex=F(-1),h.sortedIndex=function(n,r,t,e){for(var u=(t=d(t,e,1))(r),i=0,o=A(n);i<o;){var a=Math.floor((i+o)/2);t(n[a])<u?i=a+1:o=a}return i};var E=function(i,o,a){return function(n,r,t){var e=0,u=A(n);if("number"==typeof t)0<i?e=0<=t?t:Math.max(t+u,e):u=0<=t?Math.min(t+1,u):t+u+1;else if(a&&t&&u)return n[t=a(n,r)]===r?t:-1;if(r!=r)return 0<=(t=o(c.call(n,e,u),h.isNaN))?t+e:-1;for(t=0<i?e:u-1;0<=t&&t<u;t+=i)if(n[t]===r)return t;return-1}};h.indexOf=E(1,h.findIndex,h.sortedIndex),h.lastIndexOf=E(-1,h.findLastIndex),h.range=function(n,r,t){null==r&&(r=n||0,n=0),t||(t=r<n?-1:1);for(var e=Math.max(Math.ceil((r-n)/t),0),u=Array(e),i=0;i<e;i++,n+=t)u[i]=n;return u},h.chunk=function(n,r){if(null==r||r<1)return[];for(var t=[],e=0,u=n.length;e<u;)t.push(c.call(n,e,e+=r));return t};var N=function(n,r,t,e,u){if(!(e instanceof r))return n.apply(t,u);var i=m(n.prototype),o=n.apply(i,u);return h.isObject(o)?o:i};h.bind=g(function(r,t,e){if(!h.isFunction(r))throw new TypeError("Bind must be called on a function");var u=g(function(n){return N(r,u,t,this,e.concat(n))});return u}),h.partial=g(function(u,i){var o=h.partial.placeholder,a=function(){for(var n=0,r=i.length,t=Array(r),e=0;e<r;e++)t[e]=i[e]===o?arguments[n++]:i[e];for(;n<arguments.length;)t.push(arguments[n++]);return N(u,a,this,this,t)};return a}),(h.partial.placeholder=h).bindAll=g(function(n,r){var t=(r=M(r,!1,!1)).length;if(t<1)throw new Error("bindAll must be passed function names");for(;t--;){var e=r[t];n[e]=h.bind(n[e],n)}}),h.memoize=function(e,u){var i=function(n){var r=i.cache,t=""+(u?u.apply(this,arguments):n);return j(r,t)||(r[t]=e.apply(this,arguments)),r[t]};return i.cache={},i},h.delay=g(function(n,r,t){return setTimeout(function(){return n.apply(null,t)},r)}),h.defer=h.partial(h.delay,h,1),h.throttle=function(t,e,u){var i,o,a,c,l=0;u||(u={});var f=function(){l=!1===u.leading?0:h.now(),i=null,c=t.apply(o,a),i||(o=a=null)},n=function(){var n=h.now();l||!1!==u.leading||(l=n);var r=e-(n-l);return o=this,a=arguments,r<=0||e<r?(i&&(clearTimeout(i),i=null),l=n,c=t.apply(o,a),i||(o=a=null)):i||!1===u.trailing||(i=setTimeout(f,r)),c};return n.cancel=function(){clearTimeout(i),l=0,i=o=a=null},n},h.debounce=function(t,e,u){var i,o,a=function(n,r){i=null,r&&(o=t.apply(n,r))},n=g(function(n){if(i&&clearTimeout(i),u){var r=!i;i=setTimeout(a,e),r&&(o=t.apply(this,n))}else i=h.delay(a,e,this,n);return o});return n.cancel=function(){clearTimeout(i),i=null},n},h.wrap=function(n,r){return h.partial(r,n)},h.negate=function(n){return function(){return!n.apply(this,arguments)}},h.compose=function(){var t=arguments,e=t.length-1;return function(){for(var n=e,r=t[e].apply(this,arguments);n--;)r=t[n].call(this,r);return r}},h.after=function(n,r){return function(){if(--n<1)return r.apply(this,arguments)}},h.before=function(n,r){var t;return function(){return 0<--n&&(t=r.apply(this,arguments)),n<=1&&(r=null),t}},h.once=h.partial(h.before,2),h.restArguments=g;var I=!{toString:null}.propertyIsEnumerable("toString"),T=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"],B=function(n,r){var t=T.length,e=n.constructor,u=h.isFunction(e)&&e.prototype||o,i="constructor";for(j(n,i)&&!h.contains(r,i)&&r.push(i);t--;)(i=T[t])in n&&n[i]!==u[i]&&!h.contains(r,i)&&r.push(i)};h.keys=function(n){if(!h.isObject(n))return[];if(a)return a(n);var r=[];for(var t in n)j(n,t)&&r.push(t);return I&&B(n,r),r},h.allKeys=function(n){if(!h.isObject(n))return[];var r=[];for(var t in n)r.push(t);return I&&B(n,r),r},h.values=function(n){for(var r=h.keys(n),t=r.length,e=Array(t),u=0;u<t;u++)e[u]=n[r[u]];return e},h.mapObject=function(n,r,t){r=d(r,t);for(var e=h.keys(n),u=e.length,i={},o=0;o<u;o++){var a=e[o];i[a]=r(n[a],a,n)}return i},h.pairs=function(n){for(var r=h.keys(n),t=r.length,e=Array(t),u=0;u<t;u++)e[u]=[r[u],n[r[u]]];return e},h.invert=function(n){for(var r={},t=h.keys(n),e=0,u=t.length;e<u;e++)r[n[t[e]]]=t[e];return r},h.functions=h.methods=function(n){var r=[];for(var t in n)h.isFunction(n[t])&&r.push(t);return r.sort()};var R=function(c,l){return function(n){var r=arguments.length;if(l&&(n=Object(n)),r<2||null==n)return n;for(var t=1;t<r;t++)for(var e=arguments[t],u=c(e),i=u.length,o=0;o<i;o++){var a=u[o];l&&void 0!==n[a]||(n[a]=e[a])}return n}};h.extend=R(h.allKeys),h.extendOwn=h.assign=R(h.keys),h.findKey=function(n,r,t){r=d(r,t);for(var e,u=h.keys(n),i=0,o=u.length;i<o;i++)if(r(n[e=u[i]],e,n))return e};var q,K,z=function(n,r,t){return r in t};h.pick=g(function(n,r){var t={},e=r[0];if(null==n)return t;h.isFunction(e)?(1<r.length&&(e=y(e,r[1])),r=h.allKeys(n)):(e=z,r=M(r,!1,!1),n=Object(n));for(var u=0,i=r.length;u<i;u++){var o=r[u],a=n[o];e(a,o,n)&&(t[o]=a)}return t}),h.omit=g(function(n,t){var r,e=t[0];return h.isFunction(e)?(e=h.negate(e),1<t.length&&(r=t[1])):(t=h.map(M(t,!1,!1),String),e=function(n,r){return!h.contains(t,r)}),h.pick(n,e,r)}),h.defaults=R(h.allKeys,!0),h.create=function(n,r){var t=m(n);return r&&h.extendOwn(t,r),t},h.clone=function(n){return h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,r){return r(n),n},h.isMatch=function(n,r){var t=h.keys(r),e=t.length;if(null==n)return!e;for(var u=Object(n),i=0;i<e;i++){var o=t[i];if(r[o]!==u[o]||!(o in u))return!1}return!0},q=function(n,r,t,e){if(n===r)return 0!==n||1/n==1/r;if(null==n||null==r)return!1;if(n!=n)return r!=r;var u=typeof n;return("function"===u||"object"===u||"object"==typeof r)&&K(n,r,t,e)},K=function(n,r,t,e){n instanceof h&&(n=n._wrapped),r instanceof h&&(r=r._wrapped);var u=p.call(n);if(u!==p.call(r))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+r;case"[object Number]":return+n!=+n?+r!=+r:0==+n?1/+n==1/r:+n==+r;case"[object Date]":case"[object Boolean]":return+n==+r;case"[object Symbol]":return s.valueOf.call(n)===s.valueOf.call(r)}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof r)return!1;var o=n.constructor,a=r.constructor;if(o!==a&&!(h.isFunction(o)&&o instanceof o&&h.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in r)return!1}e=e||[];for(var c=(t=t||[]).length;c--;)if(t[c]===n)return e[c]===r;if(t.push(n),e.push(r),i){if((c=n.length)!==r.length)return!1;for(;c--;)if(!q(n[c],r[c],t,e))return!1}else{var l,f=h.keys(n);if(c=f.length,h.keys(r).length!==c)return!1;for(;c--;)if(l=f[c],!j(r,l)||!q(n[l],r[l],t,e))return!1}return t.pop(),e.pop(),!0},h.isEqual=function(n,r){return q(n,r)},h.isEmpty=function(n){return null==n||(w(n)&&(h.isArray(n)||h.isString(n)||h.isArguments(n))?0===n.length:0===h.keys(n).length)},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=t||function(n){return"[object Array]"===p.call(n)},h.isObject=function(n){var r=typeof n;return"function"===r||"object"===r&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp","Error","Symbol","Map","WeakMap","Set","WeakSet"],function(r){h["is"+r]=function(n){return p.call(n)==="[object "+r+"]"}}),h.isArguments(arguments)||(h.isArguments=function(n){return j(n,"callee")});var D=n.document&&n.document.childNodes;"function"!=typeof/./&&"object"!=typeof Int8Array&&"function"!=typeof D&&(h.isFunction=function(n){return"function"==typeof n||!1}),h.isFinite=function(n){return!h.isSymbol(n)&&isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return h.isNumber(n)&&isNaN(n)},h.isBoolean=function(n){return!0===n||!1===n||"[object Boolean]"===p.call(n)},h.isNull=function(n){return null===n},h.isUndefined=function(n){return void 0===n},h.has=function(n,r){if(!h.isArray(r))return j(n,r);for(var t=r.length,e=0;e<t;e++){var u=r[e];if(null==n||!i.call(n,u))return!1;n=n[u]}return!!t},h.noConflict=function(){return n._=r,this},h.identity=function(n){return n},h.constant=function(n){return function(){return n}},h.noop=function(){},h.property=function(r){return h.isArray(r)?function(n){return x(n,r)}:b(r)},h.propertyOf=function(r){return null==r?function(){}:function(n){return h.isArray(n)?x(r,n):r[n]}},h.matcher=h.matches=function(r){return r=h.extendOwn({},r),function(n){return h.isMatch(n,r)}},h.times=function(n,r,t){var e=Array(Math.max(0,n));r=y(r,t,1);for(var u=0;u<n;u++)e[u]=r(u);return e},h.random=function(n,r){return null==r&&(r=n,n=0),n+Math.floor(Math.random()*(r-n+1))},h.now=Date.now||function(){return(new Date).getTime()};var L={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},P=h.invert(L),W=function(r){var t=function(n){return r[n]},n="(?:"+h.keys(r).join("|")+")",e=RegExp(n),u=RegExp(n,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};h.escape=W(L),h.unescape=W(P),h.result=function(n,r,t){h.isArray(r)||(r=[r]);var e=r.length;if(!e)return h.isFunction(t)?t.call(n):t;for(var u=0;u<e;u++){var i=null==n?void 0:n[r[u]];void 0===i&&(i=t,u=e),n=h.isFunction(i)?i.call(n):i}return n};var C=0;h.uniqueId=function(n){var r=++C+"";return n?n+r:r},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var J=/(.)^/,U={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},V=/\\|'|\r|\n|\u2028|\u2029/g,$=function(n){return"\\"+U[n]};h.template=function(i,n,r){!n&&r&&(n=r),n=h.defaults({},n,h.templateSettings);var t,e=RegExp([(n.escape||J).source,(n.interpolate||J).source,(n.evaluate||J).source].join("|")+"|$","g"),o=0,a="__p+='";i.replace(e,function(n,r,t,e,u){return a+=i.slice(o,u).replace(V,$),o=u+n.length,r?a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":t?a+="'+\n((__t=("+t+"))==null?'':__t)+\n'":e&&(a+="';\n"+e+"\n__p+='"),n}),a+="';\n",n.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{t=new Function(n.variable||"obj","_",a)}catch(n){throw n.source=a,n}var u=function(n){return t.call(this,n,h)},c=n.variable||"obj";return u.source="function("+c+"){\n"+a+"}",u},h.chain=function(n){var r=h(n);return r._chain=!0,r};var G=function(n,r){return n._chain?h(r).chain():r};h.mixin=function(t){return h.each(h.functions(t),function(n){var r=h[n]=t[n];h.prototype[n]=function(){var n=[this._wrapped];return u.apply(n,arguments),G(this,r.apply(h,n))}}),h},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(r){var t=e[r];h.prototype[r]=function(){var n=this._wrapped;return t.apply(n,arguments),"shift"!==r&&"splice"!==r||0!==n.length||delete n[0],G(this,n)}}),h.each(["concat","join","slice"],function(n){var r=e[n];h.prototype[n]=function(){return G(this,r.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},h.prototype.valueOf=h.prototype.toJSON=h.prototype.value,h.prototype.toString=function(){return String(this._wrapped)},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}();
        \ No newline at end of file
        diff --git a/docs/yuidoc-p5-theme/assets/laDefense.jpg b/docs/yuidoc-p5-theme/assets/laDefense.jpg
        deleted file mode 100644
        index 3b8fdfe4b8..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/laDefense.jpg and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/laDefense50.png b/docs/yuidoc-p5-theme/assets/laDefense50.png
        deleted file mode 100644
        index f4937b5f25..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/laDefense50.png and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/large-dark-plate.mp3 b/docs/yuidoc-p5-theme/assets/large-dark-plate.mp3
        deleted file mode 100644
        index b9a15cbed7..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/large-dark-plate.mp3 and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/large-dark-plate.ogg b/docs/yuidoc-p5-theme/assets/large-dark-plate.ogg
        deleted file mode 100644
        index 40115377e5..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/large-dark-plate.ogg and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/lucky_dragons.mp3 b/docs/yuidoc-p5-theme/assets/lucky_dragons.mp3
        deleted file mode 100644
        index c54c70c01a..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/lucky_dragons.mp3 and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/lucky_dragons.ogg b/docs/yuidoc-p5-theme/assets/lucky_dragons.ogg
        deleted file mode 100644
        index 1e5b9e7abc..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/lucky_dragons.ogg and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/mammals.csv b/docs/yuidoc-p5-theme/assets/mammals.csv
        deleted file mode 100644
        index 549984e37e..0000000000
        --- a/docs/yuidoc-p5-theme/assets/mammals.csv
        +++ /dev/null
        @@ -1,4 +0,0 @@
        -id,species,name
        -0,Capra hircus,Goat
        -1,Panthera pardus,Leopard
        -2,Equus zebra,Zebra
        \ No newline at end of file
        diff --git a/docs/yuidoc-p5-theme/assets/mammals.xml b/docs/yuidoc-p5-theme/assets/mammals.xml
        deleted file mode 100644
        index 752da754bf..0000000000
        --- a/docs/yuidoc-p5-theme/assets/mammals.xml
        +++ /dev/null
        @@ -1,6 +0,0 @@
        -<?xml version="1.0"?>
        -<mammals>
        -  <animal id="0" species="Capra hircus">Goat</animal>
        -  <animal id="1" species="Panthera pardus">Leopard</animal>
        -  <animal id="2" species="Equus zebra">Zebra</animal>
        -</mammals>
        \ No newline at end of file
        diff --git a/docs/yuidoc-p5-theme/assets/mask.png b/docs/yuidoc-p5-theme/assets/mask.png
        deleted file mode 100644
        index a6737489b9..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/mask.png and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/mask2.png b/docs/yuidoc-p5-theme/assets/mask2.png
        deleted file mode 100644
        index 2fb4aea99b..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/mask2.png and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/moonwalk.jpg b/docs/yuidoc-p5-theme/assets/moonwalk.jpg
        deleted file mode 100644
        index c418e6f573..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/moonwalk.jpg and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/nancy-liang-wind-loop-forever.gif b/docs/yuidoc-p5-theme/assets/nancy-liang-wind-loop-forever.gif
        deleted file mode 100644
        index a946253ede..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/nancy-liang-wind-loop-forever.gif and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/octahedron.obj b/docs/yuidoc-p5-theme/assets/octahedron.obj
        deleted file mode 100644
        index 61f0cdb73c..0000000000
        --- a/docs/yuidoc-p5-theme/assets/octahedron.obj
        +++ /dev/null
        @@ -1,16 +0,0 @@
        -v 0.000000E+00 0.000000E+00 40.0000
        -v 22.5000 22.5000 0.000000E+00
        -v 22.5000 -22.5000 0.000000E+00
        -v -22.5000 -22.5000 0.000000E+00
        -v -22.5000 22.5000 0.000000E+00
        -v 0.000000E+00 0.000000E+00 -40.0000
        -
        -f     1 2 3
        -f     1 3 4
        -f     1 4 5
        -f     1 5 2
        -f     6 5 4
        -f     6 4 3
        -f     6 3 2
        -f     6 2 1
        -f     6 1 5
        diff --git a/docs/yuidoc-p5-theme/assets/outdoor_image.jpg b/docs/yuidoc-p5-theme/assets/outdoor_image.jpg
        deleted file mode 100644
        index 3e60040595..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/outdoor_image.jpg and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/outdoor_spheremap.jpg b/docs/yuidoc-p5-theme/assets/outdoor_spheremap.jpg
        deleted file mode 100644
        index 69b0bb17aa..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/outdoor_spheremap.jpg and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/rockies.jpg b/docs/yuidoc-p5-theme/assets/rockies.jpg
        deleted file mode 100644
        index 9bc0e4a372..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/rockies.jpg and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/rockies128.jpg b/docs/yuidoc-p5-theme/assets/rockies128.jpg
        deleted file mode 100644
        index bf55d812b2..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/rockies128.jpg and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/shader-gradient.frag b/docs/yuidoc-p5-theme/assets/shader-gradient.frag
        deleted file mode 100644
        index 09a0cdc64b..0000000000
        --- a/docs/yuidoc-p5-theme/assets/shader-gradient.frag
        +++ /dev/null
        @@ -1,22 +0,0 @@
        -// Code adopted from "Creating a Gradient Color in Fragment Shader"
        -// by Bahadır on stackoverflow.com
        -// https://stackoverflow.com/questions/47376499/creating-a-gradient-color-in-fragment-shader
        -
        -
        -precision highp float; varying vec2 vPos;
        -uniform vec2 offset;
        -uniform vec3 colorCenter;
        -uniform vec3 colorBackground;
        -
        -void main() {
        -
        -  vec2 st = vPos.xy + offset.xy;
        -
        -  // color1 = vec3(1.0,0.55,0);
        -  // color2 = vec3(0.226,0.000,0.615);
        -
        -  float mixValue = distance(st,vec2(0,1));
        -  vec3 color = mix(colorCenter,colorBackground,mixValue);
        -
        -  gl_FragColor = vec4(color,mixValue);
        -}
        \ No newline at end of file
        diff --git a/docs/yuidoc-p5-theme/assets/shader.frag b/docs/yuidoc-p5-theme/assets/shader.frag
        deleted file mode 100644
        index d3d76eb77a..0000000000
        --- a/docs/yuidoc-p5-theme/assets/shader.frag
        +++ /dev/null
        @@ -1,16 +0,0 @@
        -precision highp float; varying vec2 vPos;
        -uniform vec2 p;
        -uniform float r;
        -const int I = 500;
        -void main() {
        -  vec2 c = p + vPos * r, z = c;
        -  float n = 0.0;
        -  for (int i = I; i > 0; i --) {
        -    if(z.x*z.x+z.y*z.y > 4.0) {
        -      n = float(i)/float(I);
        -      break;
        -    }
        -    z = vec2(z.x*z.x-z.y*z.y, 2.0*z.x*z.y) + c;
        -  }
        -  gl_FragColor = vec4(0.5-cos(n*17.0)/2.0,0.5-cos(n*13.0)/2.0,0.5-cos(n*23.0)/2.0,1.0);
        -}
        diff --git a/docs/yuidoc-p5-theme/assets/shader.vert b/docs/yuidoc-p5-theme/assets/shader.vert
        deleted file mode 100644
        index ea310c6770..0000000000
        --- a/docs/yuidoc-p5-theme/assets/shader.vert
        +++ /dev/null
        @@ -1,3 +0,0 @@
        -precision highp float; varying vec2 vPos;
        -attribute vec3 aPosition;
        -void main() { vPos = (gl_Position = vec4(aPosition,1.0)).xy; }
        diff --git a/docs/yuidoc-p5-theme/assets/small-plate.mp3 b/docs/yuidoc-p5-theme/assets/small-plate.mp3
        deleted file mode 100644
        index 656e154a5f..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/small-plate.mp3 and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/small-plate.ogg b/docs/yuidoc-p5-theme/assets/small-plate.ogg
        deleted file mode 100644
        index 7b70d3349d..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/small-plate.ogg and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/small.mp4 b/docs/yuidoc-p5-theme/assets/small.mp4
        deleted file mode 100644
        index 1fc478842f..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/small.mp4 and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/small.ogv b/docs/yuidoc-p5-theme/assets/small.ogv
        deleted file mode 100644
        index 2b35a41aa4..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/small.ogv and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/small.webm b/docs/yuidoc-p5-theme/assets/small.webm
        deleted file mode 100644
        index da946da529..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/small.webm and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/studio-b.mp3 b/docs/yuidoc-p5-theme/assets/studio-b.mp3
        deleted file mode 100644
        index cbc792bfc9..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/studio-b.mp3 and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/studio-b.ogg b/docs/yuidoc-p5-theme/assets/studio-b.ogg
        deleted file mode 100644
        index cd68db07e6..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/studio-b.ogg and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/teapot.obj b/docs/yuidoc-p5-theme/assets/teapot.obj
        deleted file mode 100644
        index cedb196a4e..0000000000
        --- a/docs/yuidoc-p5-theme/assets/teapot.obj
        +++ /dev/null
        @@ -1,4663 +0,0 @@
        -# Blender v2.61 (sub 0) OBJ File: ''
        -# www.blender.org
        -v 0.605903 0.005903 -0.000000
        -v 0.000000 0.000000 0.000000
        -v 0.584584 0.005902 -0.162696
        -v 0.524218 0.005902 -0.307888
        -v 0.430191 0.005901 -0.430191
        -v 0.307888 0.005901 -0.524218
        -v 0.162696 0.005901 -0.584584
        -v 0.000000 0.005901 -0.605903
        -v -0.162696 0.005901 -0.584584
        -v -0.307888 0.005901 -0.524218
        -v -0.430191 0.005901 -0.430191
        -v -0.524218 0.005902 -0.307888
        -v -0.584584 0.005902 -0.162696
        -v -0.605903 0.005903 -0.000000
        -v -0.584584 0.005904 0.162696
        -v -0.524218 0.005904 0.307888
        -v -0.430191 0.005905 0.430191
        -v -0.307888 0.005905 0.524218
        -v -0.162696 0.005905 0.584584
        -v 0.000000 0.005905 0.605903
        -v 0.162696 0.005905 0.584584
        -v 0.307888 0.005905 0.524218
        -v 0.430191 0.005905 0.430191
        -v 0.524218 0.005904 0.307888
        -v 0.584584 0.005904 0.162696
        -v 1.400000 2.400000 -0.000008
        -v 1.350740 2.400000 0.375917
        -v 1.332760 2.454690 0.370913
        -v 1.381370 2.454690 -0.000009
        -v 1.384260 2.487500 -0.000009
        -v 1.335550 2.487500 0.371690
        -v 1.403120 2.498440 -0.000009
        -v 1.353760 2.498440 0.376756
        -v 1.382010 2.487500 0.384619
        -v 1.432410 2.487500 -0.000009
        -v 1.414950 2.454690 0.393787
        -v 1.466550 2.454690 -0.000009
        -v 1.447220 2.400000 0.402769
        -v 1.500000 2.400000 -0.000008
        -v 1.211260 2.400000 0.711398
        -v 1.195140 2.454690 0.701929
        -v 1.197640 2.487500 0.703400
        -v 1.213960 2.498440 0.712986
        -v 1.239300 2.487500 0.727866
        -v 1.268840 2.454690 0.745216
        -v 1.297780 2.400000 0.762213
        -v 0.994000 2.400000 0.993991
        -v 0.980770 2.454690 0.980761
        -v 0.982824 2.487500 0.982815
        -v 0.996219 2.498440 0.996210
        -v 1.017010 2.487500 1.017000
        -v 1.041250 2.454690 1.041240
        -v 1.065000 2.400000 1.064990
        -v 0.711407 2.400000 1.211250
        -v 0.701938 2.454690 1.195130
        -v 0.703409 2.487500 1.197630
        -v 0.712995 2.498440 1.213950
        -v 0.727875 2.487500 1.239290
        -v 0.745225 2.454690 1.268830
        -v 0.762222 2.400000 1.297770
        -v 0.375926 2.400010 1.350730
        -v 0.370922 2.454690 1.332750
        -v 0.371699 2.487500 1.335540
        -v 0.376765 2.498450 1.353750
        -v 0.384628 2.487500 1.382000
        -v 0.393796 2.454700 1.414940
        -v 0.402778 2.400010 1.447210
        -v 0.000000 2.400010 1.399990
        -v 0.000000 2.454690 1.381360
        -v 0.000000 2.487500 1.384250
        -v 0.000000 2.498450 1.403110
        -v 0.000000 2.487510 1.432400
        -v 0.000000 2.454700 1.466540
        -v 0.000000 2.400010 1.499990
        -v -0.375926 2.400010 1.350730
        -v -0.370922 2.454690 1.332750
        -v -0.371699 2.487500 1.335540
        -v -0.376765 2.498450 1.353750
        -v -0.384628 2.487500 1.382000
        -v -0.393796 2.454700 1.414940
        -v -0.402778 2.400010 1.447210
        -v -0.711407 2.400000 1.211250
        -v -0.701938 2.454690 1.195130
        -v -0.703409 2.487500 1.197630
        -v -0.712995 2.498440 1.213950
        -v -0.727875 2.487500 1.239290
        -v -0.745225 2.454690 1.268830
        -v -0.762222 2.400000 1.297770
        -v -0.994000 2.400000 0.993991
        -v -0.980770 2.454690 0.980761
        -v -0.982824 2.487500 0.982815
        -v -0.996219 2.498440 0.996210
        -v -1.017010 2.487500 1.017000
        -v -1.041250 2.454690 1.041240
        -v -1.065000 2.400000 1.064990
        -v -1.211260 2.400000 0.711398
        -v -1.195140 2.454690 0.701929
        -v -1.197640 2.487500 0.703400
        -v -1.213960 2.498440 0.712986
        -v -1.239300 2.487500 0.727866
        -v -1.268840 2.454690 0.745216
        -v -1.297780 2.400000 0.762213
        -v -1.350740 2.400000 0.375917
        -v -1.332760 2.454690 0.370913
        -v -1.335550 2.487500 0.371690
        -v -1.353760 2.498440 0.376756
        -v -1.382010 2.487500 0.384619
        -v -1.414950 2.454690 0.393787
        -v -1.447220 2.400000 0.402769
        -v -1.400000 2.400000 -0.000008
        -v -1.381370 2.454690 -0.000009
        -v -1.384260 2.487500 -0.000009
        -v -1.403120 2.498440 -0.000009
        -v -1.432410 2.487500 -0.000009
        -v -1.466550 2.454690 -0.000009
        -v -1.500000 2.400000 -0.000008
        -v -1.350740 2.400000 -0.375935
        -v -1.332760 2.454690 -0.370931
        -v -1.335550 2.487500 -0.371708
        -v -1.353760 2.498440 -0.376774
        -v -1.382010 2.487500 -0.384637
        -v -1.414950 2.454690 -0.393805
        -v -1.447220 2.400000 -0.402787
        -v -1.211260 2.400000 -0.711416
        -v -1.195140 2.454690 -0.701947
        -v -1.197640 2.487500 -0.703418
        -v -1.213960 2.498440 -0.713004
        -v -1.239300 2.487500 -0.727884
        -v -1.268840 2.454690 -0.745234
        -v -1.297780 2.400000 -0.762231
        -v -0.994000 2.400000 -0.994009
        -v -0.980770 2.454690 -0.980779
        -v -0.982824 2.487500 -0.982833
        -v -0.996219 2.498440 -0.996228
        -v -1.017010 2.487500 -1.017020
        -v -1.041250 2.454690 -1.041260
        -v -1.065000 2.400000 -1.065010
        -v -0.711407 2.400000 -1.211270
        -v -0.701938 2.454690 -1.195150
        -v -0.703409 2.487500 -1.197650
        -v -0.712995 2.498440 -1.213970
        -v -0.727875 2.487500 -1.239310
        -v -0.745225 2.454690 -1.268850
        -v -0.762222 2.400000 -1.297790
        -v -0.375926 2.400000 -1.350750
        -v -0.370922 2.454680 -1.332770
        -v -0.371699 2.487490 -1.335560
        -v -0.376765 2.498440 -1.353770
        -v -0.384628 2.487490 -1.382020
        -v -0.393796 2.454680 -1.414960
        -v -0.402778 2.399990 -1.447230
        -v 0.000000 2.399990 -1.400010
        -v 0.000000 2.454680 -1.381380
        -v 0.000000 2.487490 -1.384270
        -v 0.000000 2.498430 -1.403130
        -v 0.000000 2.487490 -1.432420
        -v 0.000000 2.454680 -1.466560
        -v 0.000000 2.399990 -1.500010
        -v 0.375926 2.400000 -1.350750
        -v 0.370922 2.454680 -1.332770
        -v 0.371699 2.487490 -1.335560
        -v 0.376765 2.498440 -1.353770
        -v 0.384628 2.487490 -1.382020
        -v 0.393796 2.454680 -1.414960
        -v 0.402778 2.399990 -1.447230
        -v 0.711407 2.400000 -1.211270
        -v 0.701938 2.454690 -1.195150
        -v 0.703409 2.487500 -1.197650
        -v 0.712995 2.498440 -1.213970
        -v 0.727875 2.487500 -1.239310
        -v 0.745225 2.454690 -1.268850
        -v 0.762222 2.400000 -1.297790
        -v 0.994000 2.400000 -0.994009
        -v 0.980770 2.454690 -0.980779
        -v 0.982824 2.487500 -0.982833
        -v 0.996219 2.498440 -0.996228
        -v 1.017010 2.487500 -1.017020
        -v 1.041250 2.454690 -1.041260
        -v 1.065000 2.400000 -1.065010
        -v 1.211260 2.400000 -0.711416
        -v 1.195140 2.454690 -0.701947
        -v 1.197640 2.487500 -0.703418
        -v 1.213960 2.498440 -0.713004
        -v 1.239300 2.487500 -0.727884
        -v 1.268840 2.454690 -0.745234
        -v 1.297780 2.400000 -0.762231
        -v 1.350740 2.400000 -0.375935
        -v 1.332760 2.454690 -0.370931
        -v 1.335550 2.487500 -0.371708
        -v 1.353760 2.498440 -0.376774
        -v 1.382010 2.487500 -0.384637
        -v 1.414950 2.454690 -0.393805
        -v 1.447220 2.400000 -0.402787
        -v 1.566710 2.137850 0.436024
        -v 1.623840 2.137850 -0.000008
        -v 1.679490 1.877780 0.467414
        -v 1.740740 1.877780 -0.000007
        -v 1.778880 1.621880 0.495075
        -v 1.843750 1.621870 -0.000006
        -v 1.858160 1.372220 0.517142
        -v 1.925930 1.372220 -0.000005
        -v 1.910650 1.130900 0.531750
        -v 1.980320 1.130900 -0.000004
        -v 1.929630 0.900002 0.537034
        -v 2.000000 0.900000 -0.000003
        -v 1.404920 2.137850 0.825145
        -v 1.506060 1.877780 0.884547
        -v 1.595190 1.621880 0.936892
        -v 1.666280 1.372220 0.978651
        -v 1.713350 1.130900 1.006300
        -v 1.730370 0.900004 1.016300
        -v 1.152930 2.137850 1.152920
        -v 1.235930 1.877780 1.235920
        -v 1.309060 1.621870 1.309050
        -v 1.367410 1.372230 1.367400
        -v 1.406030 1.130910 1.406030
        -v 1.420000 0.900005 1.420000
        -v 0.825153 2.137860 1.404910
        -v 0.884554 1.877790 1.506050
        -v 0.936898 1.621890 1.595180
        -v 0.978656 1.372230 1.666270
        -v 1.006300 1.130910 1.713350
        -v 1.016300 0.900006 1.730370
        -v 0.436032 2.137860 1.566700
        -v 0.467421 1.877790 1.679480
        -v 0.495081 1.621880 1.778870
        -v 0.517147 1.372230 1.858150
        -v 0.531754 1.130910 1.910650
        -v 0.537037 0.900007 1.929630
        -v 0.000000 2.137860 1.623830
        -v 0.000000 1.877790 1.740730
        -v 0.000000 1.621880 1.843740
        -v 0.000000 1.372230 1.925920
        -v 0.000000 1.130910 1.980320
        -v 0.000000 0.900007 2.000000
        -v -0.436032 2.137860 1.566700
        -v -0.467421 1.877790 1.679480
        -v -0.495081 1.621890 1.778870
        -v -0.517147 1.372230 1.858150
        -v -0.531754 1.130910 1.910650
        -v -0.537037 0.900007 1.929630
        -v -0.825153 2.137860 1.404910
        -v -0.884554 1.877790 1.506050
        -v -0.936898 1.621890 1.595180
        -v -0.978656 1.372230 1.666270
        -v -1.006300 1.130910 1.713350
        -v -1.016300 0.900006 1.730370
        -v -1.152930 2.137850 1.152920
        -v -1.235930 1.877780 1.235920
        -v -1.309060 1.621870 1.309050
        -v -1.367410 1.372230 1.367400
        -v -1.406030 1.130910 1.406030
        -v -1.420000 0.900005 1.420000
        -v -1.404920 2.137850 0.825145
        -v -1.506060 1.877780 0.884547
        -v -1.595190 1.621880 0.936892
        -v -1.666280 1.372220 0.978651
        -v -1.713350 1.130900 1.006300
        -v -1.730370 0.900004 1.016300
        -v -1.566710 2.137850 0.436024
        -v -1.679490 1.877780 0.467414
        -v -1.778880 1.621870 0.495075
        -v -1.858160 1.372220 0.517142
        -v -1.910650 1.130900 0.531750
        -v -1.929630 0.900002 0.537034
        -v -1.623840 2.137850 -0.000008
        -v -1.740740 1.877780 -0.000007
        -v -1.843750 1.621870 -0.000006
        -v -1.925930 1.372220 -0.000005
        -v -1.980320 1.130900 -0.000004
        -v -2.000000 0.900000 -0.000003
        -v -1.566710 2.137850 -0.436040
        -v -1.679490 1.877780 -0.467428
        -v -1.778880 1.621880 -0.495087
        -v -1.858160 1.372220 -0.517152
        -v -1.910650 1.130900 -0.531758
        -v -1.929630 0.899998 -0.537040
        -v -1.404920 2.137850 -0.825161
        -v -1.506060 1.877780 -0.884561
        -v -1.595190 1.621880 -0.936904
        -v -1.666280 1.372220 -0.978661
        -v -1.713350 1.130900 -1.006300
        -v -1.730370 0.899996 -1.016300
        -v -1.152930 2.137850 -1.152940
        -v -1.235930 1.877780 -1.235940
        -v -1.309060 1.621870 -1.309070
        -v -1.367410 1.372220 -1.367420
        -v -1.406030 1.130890 -1.406030
        -v -1.420000 0.899995 -1.420000
        -v -0.825153 2.137840 -1.404930
        -v -0.884554 1.877770 -1.506070
        -v -0.936898 1.621870 -1.595200
        -v -0.978656 1.372210 -1.666290
        -v -1.006300 1.130890 -1.713350
        -v -1.016300 0.899994 -1.730370
        -v -0.436032 2.137840 -1.566720
        -v -0.467421 1.877770 -1.679500
        -v -0.495081 1.621860 -1.778890
        -v -0.517147 1.372210 -1.858170
        -v -0.531754 1.130890 -1.910650
        -v -0.537037 0.899993 -1.929630
        -v 0.000000 2.137840 -1.623850
        -v 0.000000 1.877770 -1.740750
        -v 0.000000 1.621860 -1.843760
        -v 0.000000 1.372210 -1.925940
        -v 0.000000 1.130890 -1.980320
        -v 0.000000 0.899993 -2.000000
        -v 0.436032 2.137840 -1.566720
        -v 0.467421 1.877770 -1.679500
        -v 0.495081 1.621870 -1.778890
        -v 0.517147 1.372210 -1.858170
        -v 0.531754 1.130890 -1.910650
        -v 0.537037 0.899993 -1.929630
        -v 0.825153 2.137840 -1.404930
        -v 0.884554 1.877770 -1.506070
        -v 0.936898 1.621870 -1.595200
        -v 0.978656 1.372210 -1.666290
        -v 1.006300 1.130890 -1.713350
        -v 1.016300 0.899994 -1.730370
        -v 1.152930 2.137850 -1.152940
        -v 1.235930 1.877780 -1.235940
        -v 1.309060 1.621870 -1.309070
        -v 1.367410 1.372220 -1.367420
        -v 1.406030 1.130890 -1.406030
        -v 1.420000 0.899995 -1.420000
        -v 1.404920 2.137850 -0.825161
        -v 1.506060 1.877780 -0.884561
        -v 1.595190 1.621880 -0.936904
        -v 1.666280 1.372220 -0.978661
        -v 1.713350 1.130900 -1.006300
        -v 1.730370 0.899996 -1.016300
        -v 1.566710 2.137850 -0.436040
        -v 1.679490 1.877780 -0.467428
        -v 1.778880 1.621870 -0.495087
        -v 1.858160 1.372220 -0.517152
        -v 1.910650 1.130900 -0.531758
        -v 1.929630 0.899998 -0.537040
        -v 1.893900 0.693405 0.527089
        -v 1.962960 0.693403 -0.000002
        -v 1.804560 0.522224 0.502227
        -v 1.870370 0.522222 -0.000002
        -v 1.688430 0.384377 0.469906
        -v 1.750000 0.384375 -0.000001
        -v 1.572290 0.277780 0.437585
        -v 1.629630 0.277778 -0.000001
        -v 1.482960 0.200349 0.412722
        -v 1.537040 0.200347 -0.000001
        -v 1.447220 0.150001 0.402777
        -v 1.500000 0.150000 -0.000001
        -v 1.698330 0.693407 0.997473
        -v 1.618220 0.522225 0.950423
        -v 1.514070 0.384378 0.889258
        -v 1.409930 0.277781 0.828092
        -v 1.329820 0.200350 0.781042
        -v 1.297780 0.150003 0.762221
        -v 1.393700 0.693408 1.393700
        -v 1.327960 0.522227 1.327960
        -v 1.242500 0.384380 1.242500
        -v 1.157040 0.277782 1.157040
        -v 1.091300 0.200351 1.091300
        -v 1.065000 0.150004 1.065000
        -v 0.997476 0.693409 1.698330
        -v 0.950425 0.522228 1.618220
        -v 0.889259 0.384381 1.514070
        -v 0.828093 0.277783 1.409930
        -v 0.781043 0.200352 1.329820
        -v 0.762222 0.150005 1.297780
        -v 0.527092 0.693410 1.893900
        -v 0.502229 0.522229 1.804560
        -v 0.469907 0.384381 1.688430
        -v 0.437586 0.277784 1.572290
        -v 0.412723 0.200352 1.482960
        -v 0.402778 0.150005 1.447220
        -v 0.000000 0.693410 1.962960
        -v 0.000000 0.522229 1.870370
        -v 0.000000 0.384381 1.750000
        -v 0.000000 0.277784 1.629630
        -v 0.000000 0.200353 1.537040
        -v 0.000000 0.150006 1.500000
        -v -0.527092 0.693410 1.893900
        -v -0.502229 0.522229 1.804560
        -v -0.469907 0.384381 1.688430
        -v -0.437586 0.277784 1.572290
        -v -0.412723 0.200352 1.482960
        -v -0.402778 0.150005 1.447220
        -v -0.997476 0.693409 1.698330
        -v -0.950425 0.522228 1.618220
        -v -0.889259 0.384381 1.514070
        -v -0.828093 0.277783 1.409930
        -v -0.781043 0.200352 1.329820
        -v -0.762222 0.150005 1.297780
        -v -1.393700 0.693408 1.393700
        -v -1.327960 0.522227 1.327960
        -v -1.242500 0.384380 1.242500
        -v -1.157040 0.277782 1.157040
        -v -1.091300 0.200351 1.091300
        -v -1.065000 0.150004 1.065000
        -v -1.698330 0.693407 0.997473
        -v -1.618220 0.522225 0.950423
        -v -1.514070 0.384378 0.889258
        -v -1.409930 0.277781 0.828092
        -v -1.329820 0.200350 0.781042
        -v -1.297780 0.150003 0.762221
        -v -1.893900 0.693405 0.527089
        -v -1.804560 0.522224 0.502227
        -v -1.688430 0.384377 0.469906
        -v -1.572290 0.277780 0.437585
        -v -1.482960 0.200349 0.412722
        -v -1.447220 0.150001 0.402777
        -v -1.962960 0.693403 -0.000002
        -v -1.870370 0.522222 -0.000002
        -v -1.750000 0.384375 -0.000001
        -v -1.629630 0.277778 -0.000001
        -v -1.537040 0.200347 -0.000001
        -v -1.500000 0.150000 -0.000001
        -v -1.893900 0.693401 -0.527095
        -v -1.804560 0.522220 -0.502231
        -v -1.688430 0.384373 -0.469908
        -v -1.572290 0.277776 -0.437587
        -v -1.482960 0.200345 -0.412724
        -v -1.447220 0.149999 -0.402779
        -v -1.698330 0.693399 -0.997479
        -v -1.618220 0.522218 -0.950427
        -v -1.514070 0.384372 -0.889260
        -v -1.409930 0.277775 -0.828094
        -v -1.329820 0.200344 -0.781044
        -v -1.297780 0.149997 -0.762223
        -v -1.393700 0.693398 -1.393700
        -v -1.327960 0.522217 -1.327960
        -v -1.242500 0.384370 -1.242500
        -v -1.157040 0.277774 -1.157040
        -v -1.091300 0.200343 -1.091300
        -v -1.065000 0.149996 -1.065000
        -v -0.997476 0.693397 -1.698330
        -v -0.950425 0.522216 -1.618220
        -v -0.889259 0.384369 -1.514070
        -v -0.828093 0.277773 -1.409930
        -v -0.781043 0.200342 -1.329820
        -v -0.762222 0.149995 -1.297780
        -v -0.527092 0.693396 -1.893900
        -v -0.502229 0.522215 -1.804560
        -v -0.469907 0.384369 -1.688430
        -v -0.437586 0.277772 -1.572290
        -v -0.412723 0.200342 -1.482960
        -v -0.402778 0.149995 -1.447220
        -v 0.000000 0.693396 -1.962960
        -v 0.000000 0.522215 -1.870370
        -v 0.000000 0.384369 -1.750000
        -v 0.000000 0.277772 -1.629630
        -v 0.000000 0.200341 -1.537040
        -v 0.000000 0.149994 -1.500000
        -v 0.527092 0.693396 -1.893900
        -v 0.502229 0.522215 -1.804560
        -v 0.469907 0.384369 -1.688430
        -v 0.437586 0.277772 -1.572290
        -v 0.412723 0.200342 -1.482960
        -v 0.402778 0.149995 -1.447220
        -v 0.997476 0.693397 -1.698330
        -v 0.950425 0.522216 -1.618220
        -v 0.889259 0.384369 -1.514070
        -v 0.828093 0.277773 -1.409930
        -v 0.781043 0.200342 -1.329820
        -v 0.762222 0.149995 -1.297780
        -v 1.393700 0.693398 -1.393700
        -v 1.327960 0.522217 -1.327960
        -v 1.242500 0.384370 -1.242500
        -v 1.157040 0.277774 -1.157040
        -v 1.091300 0.200343 -1.091300
        -v 1.065000 0.149996 -1.065000
        -v 1.698330 0.693399 -0.997479
        -v 1.618220 0.522218 -0.950427
        -v 1.514070 0.384372 -0.889260
        -v 1.409930 0.277775 -0.828094
        -v 1.329820 0.200344 -0.781044
        -v 1.297780 0.149997 -0.762223
        -v 1.893900 0.693401 -0.527095
        -v 1.804560 0.522220 -0.502231
        -v 1.688430 0.384373 -0.469908
        -v 1.572290 0.277776 -0.437587
        -v 1.482960 0.200345 -0.412724
        -v 1.447220 0.149999 -0.402779
        -v 1.022220 0.022222 -0.000000
        -v 0.986255 0.022221 -0.274486
        -v 1.284370 0.046875 -0.000000
        -v 1.239180 0.046874 -0.344878
        -v 1.427780 0.077778 -0.000000
        -v 1.377540 0.077777 -0.383385
        -v 1.487850 0.112847 -0.000000
        -v 1.435500 0.112846 -0.399515
        -v 0.884412 0.022220 -0.519440
        -v 1.111220 0.046873 -0.652653
        -v 1.235290 0.077775 -0.725523
        -v 1.287260 0.112844 -0.756047
        -v 0.725778 0.022219 -0.725778
        -v 0.911906 0.046872 -0.911906
        -v 1.013720 0.077774 -1.013720
        -v 1.056370 0.112843 -1.056370
        -v 0.519440 0.022219 -0.884412
        -v 0.652653 0.046871 -1.111220
        -v 0.725523 0.077774 -1.235290
        -v 0.756047 0.112842 -1.287260
        -v 0.274486 0.022219 -0.986255
        -v 0.344878 0.046871 -1.239180
        -v 0.383385 0.077773 -1.377540
        -v 0.399515 0.112842 -1.435500
        -v 0.000000 0.022218 -1.022220
        -v 0.000000 0.046871 -1.284370
        -v 0.000000 0.077773 -1.427780
        -v 0.000000 0.112842 -1.487850
        -v -0.274486 0.022219 -0.986255
        -v -0.344878 0.046871 -1.239180
        -v -0.383385 0.077773 -1.377540
        -v -0.399515 0.112842 -1.435500
        -v -0.519440 0.022219 -0.884412
        -v -0.652653 0.046871 -1.111220
        -v -0.725523 0.077774 -1.235290
        -v -0.756047 0.112842 -1.287260
        -v -0.725778 0.022219 -0.725778
        -v -0.911906 0.046872 -0.911906
        -v -1.013720 0.077774 -1.013720
        -v -1.056370 0.112843 -1.056370
        -v -0.884412 0.022220 -0.519440
        -v -1.111220 0.046873 -0.652653
        -v -1.235290 0.077775 -0.725523
        -v -1.287260 0.112844 -0.756047
        -v -0.986255 0.022221 -0.274486
        -v -1.239180 0.046874 -0.344878
        -v -1.377540 0.077777 -0.383385
        -v -1.435500 0.112846 -0.399515
        -v -1.022220 0.022222 -0.000000
        -v -1.284370 0.046875 -0.000000
        -v -1.427780 0.077778 -0.000000
        -v -1.487850 0.112847 -0.000000
        -v -0.986255 0.022223 0.274486
        -v -1.239180 0.046876 0.344878
        -v -1.377540 0.077779 0.383385
        -v -1.435500 0.112848 0.399515
        -v -0.884412 0.022224 0.519440
        -v -1.111220 0.046877 0.652653
        -v -1.235290 0.077781 0.725523
        -v -1.287260 0.112850 0.756047
        -v -0.725778 0.022225 0.725778
        -v -0.911906 0.046878 0.911906
        -v -1.013720 0.077782 1.013720
        -v -1.056370 0.112851 1.056370
        -v -0.519440 0.022225 0.884412
        -v -0.652653 0.046879 1.111220
        -v -0.725523 0.077782 1.235290
        -v -0.756047 0.112852 1.287260
        -v -0.274486 0.022225 0.986255
        -v -0.344878 0.046879 1.239180
        -v -0.383385 0.077783 1.377540
        -v -0.399515 0.112852 1.435500
        -v 0.000000 0.022226 1.022220
        -v 0.000000 0.046879 1.284370
        -v 0.000000 0.077783 1.427780
        -v 0.000000 0.112852 1.487850
        -v 0.274486 0.022225 0.986255
        -v 0.344878 0.046879 1.239180
        -v 0.383385 0.077783 1.377540
        -v 0.399515 0.112852 1.435500
        -v 0.519440 0.022225 0.884412
        -v 0.652653 0.046879 1.111220
        -v 0.725523 0.077782 1.235290
        -v 0.756047 0.112852 1.287260
        -v 0.725778 0.022225 0.725778
        -v 0.911906 0.046878 0.911906
        -v 1.013720 0.077782 1.013720
        -v 1.056370 0.112851 1.056370
        -v 0.884412 0.022224 0.519440
        -v 1.111220 0.046877 0.652653
        -v 1.235290 0.077781 0.725523
        -v 1.287260 0.112850 0.756047
        -v 0.986255 0.022223 0.274486
        -v 1.239180 0.046876 0.344878
        -v 1.377540 0.077779 0.383385
        -v 1.435500 0.112848 0.399515
        -v 0.192963 2.700000 0.053694
        -v 0.200000 2.700000 -0.000010
        -v 0.165279 2.785420 0.046035
        -v 0.171296 2.785420 -0.000010
        -v 0.173037 2.700000 0.101620
        -v 0.148234 2.785420 0.087096
        -v 0.142000 2.700000 0.141990
        -v 0.121672 2.785420 0.121662
        -v 0.101630 2.700000 0.173027
        -v 0.087106 2.785420 0.148224
        -v 0.053704 2.700000 0.192953
        -v 0.046045 2.785420 0.165269
        -v 0.000000 2.700000 0.199990
        -v 0.000000 2.785420 0.171286
        -v -0.053704 2.700000 0.192953
        -v -0.046045 2.785420 0.165269
        -v -0.101630 2.700000 0.173027
        -v -0.087106 2.785420 0.148224
        -v -0.142000 2.700000 0.141990
        -v -0.121672 2.785420 0.121662
        -v -0.173037 2.700000 0.101620
        -v -0.148234 2.785420 0.087096
        -v -0.192963 2.700000 0.053694
        -v -0.165279 2.785420 0.046035
        -v -0.200000 2.700000 -0.000010
        -v -0.171296 2.785420 -0.000010
        -v -0.192963 2.700000 -0.053714
        -v -0.165279 2.785420 -0.046055
        -v -0.173037 2.700000 -0.101640
        -v -0.148234 2.785420 -0.087116
        -v -0.142000 2.700000 -0.142010
        -v -0.121672 2.785420 -0.121682
        -v -0.101630 2.700000 -0.173047
        -v -0.087106 2.785420 -0.148244
        -v -0.053704 2.700000 -0.192973
        -v -0.046045 2.785420 -0.165289
        -v 0.000000 2.700000 -0.200010
        -v 0.000000 2.785420 -0.171306
        -v 0.053704 2.700000 -0.192973
        -v 0.046045 2.785420 -0.165289
        -v 0.101630 2.700000 -0.173047
        -v 0.087106 2.785420 -0.148244
        -v 0.142000 2.700000 -0.142010
        -v 0.121672 2.785420 -0.121682
        -v 0.173037 2.700000 -0.101640
        -v 0.148234 2.785420 -0.087116
        -v 0.192963 2.700000 -0.053714
        -v 0.165279 2.785420 -0.046055
        -v 0.338579 2.636110 0.094221
        -v 0.350926 2.636110 -0.000009
        -v 0.553875 2.588890 0.154140
        -v 0.574074 2.588890 -0.000009
        -v 0.795972 2.550000 0.221519
        -v 0.825000 2.550000 -0.000009
        -v 1.021990 2.511110 0.284422
        -v 1.059260 2.511110 -0.000009
        -v 1.189040 2.463890 0.330915
        -v 1.232410 2.463890 -0.000009
        -v 1.254260 2.400000 0.349065
        -v 1.300000 2.400000 -0.000008
        -v 0.303616 2.636110 0.178312
        -v 0.496680 2.588890 0.291705
        -v 0.713778 2.550000 0.419213
        -v 0.916455 2.511110 0.538252
        -v 1.066260 2.463890 0.626237
        -v 1.124740 2.400000 0.660584
        -v 0.249157 2.636110 0.249147
        -v 0.407593 2.588890 0.407583
        -v 0.585750 2.550000 0.585741
        -v 0.752074 2.511110 0.752065
        -v 0.875009 2.463890 0.875000
        -v 0.923000 2.400000 0.922991
        -v 0.178322 2.636110 0.303606
        -v 0.291715 2.588890 0.496670
        -v 0.419222 2.550000 0.713769
        -v 0.538261 2.511110 0.916446
        -v 0.626246 2.463890 1.066250
        -v 0.660593 2.400000 1.124730
        -v 0.094230 2.636110 0.338569
        -v 0.154150 2.588890 0.553865
        -v 0.221528 2.550000 0.795963
        -v 0.284431 2.511110 1.021980
        -v 0.330924 2.463890 1.189030
        -v 0.349074 2.400000 1.254250
        -v 0.000000 2.636110 0.350916
        -v 0.000000 2.588890 0.574064
        -v 0.000000 2.550000 0.824991
        -v 0.000000 2.511110 1.059250
        -v 0.000000 2.463890 1.232400
        -v 0.000000 2.400000 1.299990
        -v -0.094230 2.636110 0.338569
        -v -0.154150 2.588890 0.553865
        -v -0.221528 2.550000 0.795963
        -v -0.284431 2.511110 1.021980
        -v -0.330924 2.463890 1.189030
        -v -0.349074 2.400000 1.254250
        -v -0.178322 2.636110 0.303606
        -v -0.291715 2.588890 0.496670
        -v -0.419222 2.550000 0.713769
        -v -0.538261 2.511110 0.916446
        -v -0.626246 2.463890 1.066250
        -v -0.660593 2.400000 1.124730
        -v -0.249157 2.636110 0.249147
        -v -0.407593 2.588890 0.407583
        -v -0.585750 2.550000 0.585741
        -v -0.752074 2.511110 0.752065
        -v -0.875009 2.463890 0.875000
        -v -0.923000 2.400000 0.922991
        -v -0.303616 2.636110 0.178312
        -v -0.496680 2.588890 0.291705
        -v -0.713778 2.550000 0.419213
        -v -0.916455 2.511110 0.538252
        -v -1.066260 2.463890 0.626237
        -v -1.124740 2.400000 0.660584
        -v -0.338579 2.636110 0.094221
        -v -0.553875 2.588890 0.154140
        -v -0.795972 2.550000 0.221519
        -v -1.021990 2.511110 0.284422
        -v -1.189040 2.463890 0.330915
        -v -1.254260 2.400000 0.349065
        -v -0.350926 2.636110 -0.000009
        -v -0.574074 2.588890 -0.000009
        -v -0.825000 2.550000 -0.000009
        -v -1.059260 2.511110 -0.000009
        -v -1.232410 2.463890 -0.000009
        -v -1.300000 2.400000 -0.000008
        -v -0.338579 2.636110 -0.094239
        -v -0.553875 2.588890 -0.154160
        -v -0.795972 2.550000 -0.221537
        -v -1.021990 2.511110 -0.284440
        -v -1.189040 2.463890 -0.330933
        -v -1.254260 2.400000 -0.349083
        -v -0.303616 2.636110 -0.178332
        -v -0.496680 2.588890 -0.291725
        -v -0.713778 2.550000 -0.419231
        -v -0.916455 2.511110 -0.538270
        -v -1.066260 2.463890 -0.626255
        -v -1.124740 2.400000 -0.660602
        -v -0.249157 2.636110 -0.249167
        -v -0.407593 2.588890 -0.407603
        -v -0.585750 2.550000 -0.585759
        -v -0.752074 2.511110 -0.752083
        -v -0.875009 2.463890 -0.875018
        -v -0.923000 2.400000 -0.923009
        -v -0.178322 2.636110 -0.303626
        -v -0.291715 2.588890 -0.496690
        -v -0.419222 2.550000 -0.713787
        -v -0.538261 2.511110 -0.916464
        -v -0.626246 2.463890 -1.066270
        -v -0.660593 2.400000 -1.124750
        -v -0.094230 2.636110 -0.338589
        -v -0.154150 2.588890 -0.553885
        -v -0.221528 2.550000 -0.795981
        -v -0.284431 2.511110 -1.022000
        -v -0.330924 2.463890 -1.189050
        -v -0.349074 2.400000 -1.254270
        -v 0.000000 2.636110 -0.350936
        -v 0.000000 2.588890 -0.574084
        -v 0.000000 2.550000 -0.825009
        -v 0.000000 2.511110 -1.059270
        -v 0.000000 2.463890 -1.232420
        -v 0.000000 2.400000 -1.300010
        -v 0.094230 2.636110 -0.338589
        -v 0.154150 2.588890 -0.553885
        -v 0.221528 2.550000 -0.795981
        -v 0.284431 2.511110 -1.022000
        -v 0.330924 2.463890 -1.189050
        -v 0.349074 2.400000 -1.254270
        -v 0.178322 2.636110 -0.303626
        -v 0.291715 2.588890 -0.496690
        -v 0.419222 2.550000 -0.713787
        -v 0.538261 2.511110 -0.916464
        -v 0.626246 2.463890 -1.066270
        -v 0.660593 2.400000 -1.124750
        -v 0.249157 2.636110 -0.249167
        -v 0.407593 2.588890 -0.407603
        -v 0.585750 2.550000 -0.585759
        -v 0.752074 2.511110 -0.752083
        -v 0.875009 2.463890 -0.875018
        -v 0.923000 2.400000 -0.923009
        -v 0.303616 2.636110 -0.178332
        -v 0.496680 2.588890 -0.291725
        -v 0.713778 2.550000 -0.419231
        -v 0.916455 2.511110 -0.538270
        -v 1.066260 2.463890 -0.626255
        -v 1.124740 2.400000 -0.660602
        -v 0.338579 2.636110 -0.094239
        -v 0.553875 2.588890 -0.154160
        -v 0.795972 2.550000 -0.221537
        -v 1.021990 2.511110 -0.284440
        -v 1.189040 2.463890 -0.330933
        -v 1.254260 2.400000 -0.349083
        -v -1.924540 2.023960 -0.000007
        -v -1.600000 2.025000 -0.000007
        -v -1.927040 2.040550 0.124992
        -v -1.592590 2.041670 0.124992
        -v -2.196300 2.016670 -0.000007
        -v -2.206450 2.032720 0.124992
        -v -2.428240 2.011460 0.124993
        -v -2.412500 1.996870 -0.000007
        -v -2.589850 1.970060 0.124993
        -v -2.570370 1.958330 -0.000007
        -v -2.688700 1.901810 0.124993
        -v -2.667130 1.894790 -0.000007
        -v -2.722220 1.800000 0.124993
        -v -2.700000 1.800000 -0.000006
        -v -1.933300 2.082020 0.199992
        -v -1.574070 2.083330 0.199992
        -v -2.231820 2.072840 0.199992
        -v -2.467590 2.047920 0.199992
        -v -2.638550 1.999380 0.199993
        -v -2.742630 1.919370 0.199993
        -v -2.777780 1.800000 0.199993
        -v -1.941440 2.135940 0.224992
        -v -1.550000 2.137500 0.224992
        -v -2.264810 2.125000 0.224992
        -v -2.518750 2.095310 0.224992
        -v -2.701850 2.037500 0.224992
        -v -2.812730 1.942190 0.224993
        -v -2.850000 1.800000 0.224993
        -v -1.949570 2.189850 0.199992
        -v -1.525930 2.191670 0.199992
        -v -2.297810 2.177160 0.199992
        -v -2.569910 2.142710 0.199992
        -v -2.765160 2.075620 0.199992
        -v -2.882840 1.965010 0.199993
        -v -2.922220 1.800000 0.199993
        -v -1.955830 2.231330 0.124992
        -v -1.507410 2.233330 0.124992
        -v -2.323180 2.217280 0.124992
        -v -2.609260 2.179170 0.124992
        -v -2.813850 2.104940 0.124992
        -v -2.936760 1.982560 0.124993
        -v -2.977780 1.800000 0.124993
        -v -1.958330 2.247920 -0.000008
        -v -1.500000 2.250000 -0.000008
        -v -2.333330 2.233330 -0.000008
        -v -2.625000 2.193750 -0.000008
        -v -2.833330 2.116670 -0.000007
        -v -2.958330 1.989580 -0.000007
        -v -3.000000 1.800000 -0.000006
        -v -1.507410 2.233330 -0.125008
        -v -1.955830 2.231330 -0.125008
        -v -2.323180 2.217280 -0.125008
        -v -2.609260 2.179170 -0.125008
        -v -2.813850 2.104940 -0.125008
        -v -2.936760 1.982560 -0.125007
        -v -2.977780 1.800000 -0.125007
        -v -1.525930 2.191670 -0.200008
        -v -1.949570 2.189850 -0.200008
        -v -2.297810 2.177160 -0.200008
        -v -2.569910 2.142710 -0.200008
        -v -2.765160 2.075620 -0.200008
        -v -2.882840 1.965010 -0.200007
        -v -2.922220 1.800000 -0.200007
        -v -1.550000 2.137500 -0.225008
        -v -1.941440 2.135940 -0.225008
        -v -2.264810 2.125000 -0.225008
        -v -2.518750 2.095310 -0.225008
        -v -2.701850 2.037500 -0.225008
        -v -2.812730 1.942190 -0.225007
        -v -2.850000 1.800000 -0.225007
        -v -1.574070 2.083330 -0.200008
        -v -1.933300 2.082020 -0.200008
        -v -2.231820 2.072840 -0.200008
        -v -2.467590 2.047920 -0.200008
        -v -2.638550 1.999380 -0.200007
        -v -2.742630 1.919370 -0.200007
        -v -2.777780 1.800000 -0.200007
        -v -1.592590 2.041670 -0.125008
        -v -1.927040 2.040550 -0.125008
        -v -2.206450 2.032720 -0.125008
        -v -2.428240 2.011460 -0.125007
        -v -2.589850 1.970060 -0.125007
        -v -2.688700 1.901810 -0.125007
        -v -2.722220 1.800000 -0.125007
        -v -2.704180 1.663980 0.124994
        -v -2.682870 1.670830 -0.000006
        -v -2.648290 1.505350 0.124994
        -v -2.629630 1.516670 -0.000005
        -v -2.551850 1.335760 0.124995
        -v -2.537500 1.350000 -0.000005
        -v -2.412210 1.166870 0.124996
        -v -2.403700 1.183330 -0.000004
        -v -2.226680 1.010330 0.124996
        -v -2.225460 1.029170 -0.000004
        -v -1.992590 0.877778 0.124997
        -v -2.000000 0.900000 -0.000003
        -v -2.757470 1.646840 0.199994
        -v -2.694920 1.477060 0.199995
        -v -2.587730 1.300170 0.199995
        -v -2.433470 1.125720 0.199996
        -v -2.229720 0.963228 0.199996
        -v -1.974070 0.822223 0.199997
        -v -2.826740 1.624570 0.224994
        -v -2.755560 1.440280 0.224995
        -v -2.634370 1.253910 0.224995
        -v -2.461110 1.072220 0.224996
        -v -2.233680 0.901998 0.224997
        -v -1.950000 0.750001 0.224997
        -v -2.896000 1.602290 0.199994
        -v -2.816190 1.403500 0.199995
        -v -2.681020 1.207640 0.199996
        -v -2.488750 1.018720 0.199996
        -v -2.237640 0.840767 0.199997
        -v -1.925930 0.677779 0.199997
        -v -2.949290 1.585150 0.124994
        -v -2.862830 1.375210 0.124995
        -v -2.716900 1.172050 0.124996
        -v -2.510010 0.977573 0.124996
        -v -2.240680 0.793666 0.124997
        -v -1.907410 0.622222 0.124998
        -v -2.970600 1.578300 -0.000006
        -v -2.881480 1.363890 -0.000005
        -v -2.731250 1.157810 -0.000004
        -v -2.518520 0.961111 -0.000003
        -v -2.241900 0.774826 -0.000003
        -v -1.900000 0.600000 -0.000002
        -v -2.949290 1.585150 -0.125006
        -v -2.862830 1.375210 -0.125005
        -v -2.716900 1.172050 -0.125004
        -v -2.510010 0.977572 -0.125004
        -v -2.240680 0.793666 -0.125003
        -v -1.907410 0.622222 -0.125002
        -v -2.896000 1.602290 -0.200006
        -v -2.816190 1.403500 -0.200005
        -v -2.681020 1.207640 -0.200004
        -v -2.488750 1.018720 -0.200004
        -v -2.237640 0.840765 -0.200003
        -v -1.925930 0.677777 -0.200003
        -v -2.826740 1.624570 -0.225006
        -v -2.755560 1.440280 -0.225005
        -v -2.634370 1.253910 -0.225005
        -v -2.461110 1.072220 -0.225004
        -v -2.233680 0.901996 -0.225003
        -v -1.950000 0.749999 -0.225003
        -v -2.757470 1.646840 -0.200006
        -v -2.694920 1.477060 -0.200005
        -v -2.587730 1.300170 -0.200005
        -v -2.433470 1.125720 -0.200004
        -v -2.229720 0.963226 -0.200004
        -v -1.974070 0.822221 -0.200003
        -v -2.704180 1.663980 -0.125006
        -v -2.648290 1.505350 -0.125006
        -v -2.551850 1.335760 -0.125005
        -v -2.412210 1.166870 -0.125004
        -v -2.226680 1.010330 -0.125004
        -v -1.992590 0.877778 -0.125003
        -v 1.700000 1.425000 -0.000005
        -v 1.700000 1.363890 0.274995
        -v 2.072380 1.425210 0.262341
        -v 2.058800 1.476390 -0.000005
        -v 2.290120 1.572020 0.230704
        -v 2.270370 1.611110 -0.000006
        -v 2.409720 1.773610 0.189576
        -v 2.387500 1.800000 -0.000006
        -v 2.487650 1.999280 0.148450
        -v 2.462960 2.013890 -0.000007
        -v 2.580400 2.218310 0.116813
        -v 2.549540 2.223610 -0.000008
        -v 2.700000 2.400000 -0.000008
        -v 2.744440 2.400000 0.104158
        -v 1.700000 1.211110 0.439996
        -v 2.106330 1.297250 0.419748
        -v 2.339510 1.474280 0.369131
        -v 2.465280 1.707640 0.303327
        -v 2.549380 1.962760 0.237524
        -v 2.657560 2.205070 0.186906
        -v 2.855560 2.400000 0.166658
        -v 1.700000 1.012500 0.494996
        -v 2.150460 1.130900 0.472218
        -v 2.403700 1.347220 0.415273
        -v 2.537500 1.621870 0.341244
        -v 2.629630 1.915280 0.267215
        -v 2.757870 2.187850 0.210270
        -v 3.000000 2.400000 0.187491
        -v 1.700000 0.813891 0.439997
        -v 2.194600 0.964560 0.419749
        -v 2.467900 1.220160 0.369132
        -v 2.609720 1.536110 0.303327
        -v 2.709880 1.867800 0.237524
        -v 2.858180 2.170630 0.186906
        -v 3.144440 2.400000 0.166658
        -v 1.700000 0.661112 0.274998
        -v 2.228550 0.836601 0.262343
        -v 2.517280 1.122430 0.230706
        -v 2.665280 1.470140 0.189578
        -v 2.771600 1.831280 0.148450
        -v 2.935340 2.157380 0.116813
        -v 3.255560 2.400000 0.104158
        -v 1.700000 0.600000 -0.000002
        -v 2.242130 0.785417 -0.000003
        -v 2.537040 1.083330 -0.000004
        -v 2.687500 1.443750 -0.000005
        -v 2.796300 1.816670 -0.000006
        -v 2.966200 2.152080 -0.000008
        -v 3.300000 2.400000 -0.000008
        -v 1.700000 0.661110 -0.275002
        -v 2.228550 0.836599 -0.262349
        -v 2.517280 1.122430 -0.230714
        -v 2.665280 1.470140 -0.189588
        -v 2.771600 1.831280 -0.148464
        -v 2.935340 2.157380 -0.116829
        -v 3.255560 2.400000 -0.104176
        -v 1.700000 0.813887 -0.440003
        -v 2.194600 0.964556 -0.419757
        -v 2.467900 1.220160 -0.369141
        -v 2.609720 1.536110 -0.303339
        -v 2.709880 1.867800 -0.237538
        -v 2.858180 2.170630 -0.186922
        -v 3.144440 2.400000 -0.166676
        -v 1.700000 1.012500 -0.495004
        -v 2.150460 1.130900 -0.472226
        -v 2.403700 1.347220 -0.415283
        -v 2.537500 1.621870 -0.341256
        -v 2.629630 1.915280 -0.267229
        -v 2.757870 2.187850 -0.210286
        -v 3.000000 2.400000 -0.187509
        -v 1.700000 1.211110 -0.440004
        -v 2.106330 1.297250 -0.419758
        -v 2.339510 1.474280 -0.369141
        -v 2.465280 1.707640 -0.303339
        -v 2.549380 1.962760 -0.237538
        -v 2.657560 2.205070 -0.186922
        -v 2.855560 2.400000 -0.166676
        -v 1.700000 1.363890 -0.275005
        -v 2.072380 1.425210 -0.262351
        -v 2.290120 1.572020 -0.230716
        -v 2.409720 1.773610 -0.189590
        -v 2.487650 1.999280 -0.148464
        -v 2.580400 2.218310 -0.116829
        -v 2.744440 2.400000 -0.104176
        -v 2.749070 2.431250 -0.000009
        -v 2.796410 2.431930 0.101023
        -v 2.792590 2.450000 -0.000009
        -v 2.839780 2.451230 0.092969
        -v 2.825000 2.456250 -0.000009
        -v 2.869680 2.457810 0.082022
        -v 2.881210 2.451540 0.070207
        -v 2.840740 2.450000 -0.000009
        -v 2.869490 2.432310 0.059549
        -v 2.834260 2.431250 -0.000009
        -v 2.829630 2.400000 0.052074
        -v 2.800000 2.400000 -0.000008
        -v 2.914740 2.433610 0.161565
        -v 2.957750 2.454320 0.148139
        -v 2.981370 2.461720 0.129158
        -v 2.982370 2.455400 0.107398
        -v 2.957560 2.434960 0.085639
        -v 2.903700 2.400000 0.066658
        -v 3.068580 2.435810 0.181675
        -v 3.111110 2.458330 0.165963
        -v 3.126560 2.466800 0.142960
        -v 3.113890 2.460420 0.115269
        -v 3.072050 2.438410 0.085495
        -v 3.000000 2.400000 0.056241
        -v 3.222410 2.438000 0.161411
        -v 3.264470 2.462350 0.146905
        -v 3.271760 2.471870 0.124991
        -v 3.245400 2.465430 0.097522
        -v 3.186540 2.441860 0.066349
        -v 3.096300 2.400000 0.033324
        -v 3.340750 2.439690 0.100830
        -v 3.382440 2.465430 0.091426
        -v 3.383450 2.475780 0.076814
        -v 3.346570 2.469290 0.057861
        -v 3.274610 2.444510 0.035437
        -v 3.170370 2.400000 0.010408
        -v 3.388080 2.440360 -0.000009
        -v 3.429630 2.466670 -0.000009
        -v 3.428130 2.477340 -0.000009
        -v 3.387040 2.470830 -0.000009
        -v 3.309840 2.445570 -0.000009
        -v 3.200000 2.400000 -0.000008
        -v 3.340750 2.439690 -0.101089
        -v 3.382440 2.465430 -0.093373
        -v 3.383450 2.475780 -0.083342
        -v 3.346570 2.469290 -0.073312
        -v 3.274610 2.444510 -0.065595
        -v 3.170370 2.400000 -0.062509
        -v 3.222410 2.438000 -0.161737
        -v 3.264470 2.462350 -0.149392
        -v 3.271760 2.471870 -0.133342
        -v 3.245400 2.465430 -0.117293
        -v 3.186540 2.441860 -0.104947
        -v 3.096300 2.400000 -0.100009
        -v 3.068580 2.435810 -0.181953
        -v 3.111110 2.458330 -0.168065
        -v 3.126560 2.466800 -0.150009
        -v 3.113890 2.460420 -0.131953
        -v 3.072050 2.438410 -0.118065
        -v 3.000000 2.400000 -0.112509
        -v 2.914740 2.433610 -0.161737
        -v 2.957750 2.454320 -0.149392
        -v 2.981370 2.461720 -0.133342
        -v 2.982370 2.455400 -0.117293
        -v 2.957560 2.434960 -0.104947
        -v 2.903700 2.400000 -0.100009
        -v 2.796410 2.431930 -0.101089
        -v 2.839780 2.451230 -0.093373
        -v 2.869680 2.457810 -0.083342
        -v 2.881210 2.451540 -0.073312
        -v 2.869490 2.432310 -0.065595
        -v 2.829630 2.400000 -0.062509
        -v 0.278704 3.127080 -0.000011
        -v 0.000000 3.150000 -0.000011
        -v 0.268946 3.127080 0.075067
        -v 0.241285 3.127080 0.141920
        -v 0.198140 3.127080 0.198129
        -v 0.141931 3.127080 0.241274
        -v 0.075078 3.127080 0.268935
        -v 0.000000 3.127080 0.278693
        -v -0.075078 3.127080 0.268935
        -v -0.141931 3.127080 0.241274
        -v -0.198140 3.127080 0.198129
        -v -0.241285 3.127080 0.141920
        -v -0.268946 3.127080 0.075067
        -v -0.278704 3.127080 -0.000011
        -v -0.268946 3.127080 -0.075089
        -v -0.241285 3.127080 -0.141942
        -v -0.198140 3.127080 -0.198151
        -v -0.141931 3.127080 -0.241296
        -v -0.075078 3.127080 -0.268957
        -v 0.000000 3.127080 -0.278715
        -v 0.075078 3.127080 -0.268957
        -v 0.141931 3.127080 -0.241296
        -v 0.198140 3.127080 -0.198151
        -v 0.241285 3.127080 -0.141942
        -v 0.268946 3.127080 -0.075089
        -v 0.350254 3.066670 0.097760
        -v 0.362963 3.066670 -0.000011
        -v 0.313617 2.981250 0.087518
        -v 0.325000 2.981250 -0.000011
        -v 0.228728 2.883330 0.063793
        -v 0.237037 2.883330 -0.000010
        -v 0.165279 2.785420 0.046035
        -v 0.171296 2.785420 -0.000010
        -v 0.314228 3.066670 0.184824
        -v 0.281352 2.981250 0.165470
        -v 0.205180 2.883330 0.120636
        -v 0.148234 2.785420 0.087096
        -v 0.258037 3.066670 0.258027
        -v 0.231031 2.981250 0.231020
        -v 0.168463 2.883330 0.168452
        -v 0.121672 2.785420 0.121662
        -v 0.184834 3.066670 0.314218
        -v 0.165481 2.981250 0.281341
        -v 0.120647 2.883330 0.205169
        -v 0.087106 2.785420 0.148224
        -v 0.097771 3.066670 0.350244
        -v 0.087529 2.981250 0.313606
        -v 0.063803 2.883330 0.228717
        -v 0.046045 2.785420 0.165269
        -v 0.000000 3.066670 0.362953
        -v 0.000000 2.981250 0.324989
        -v 0.000000 2.883330 0.237026
        -v 0.000000 2.785420 0.171286
        -v -0.097771 3.066670 0.350244
        -v -0.087529 2.981250 0.313606
        -v -0.063803 2.883330 0.228717
        -v -0.046045 2.785420 0.165269
        -v -0.184834 3.066670 0.314218
        -v -0.165481 2.981250 0.281341
        -v -0.120647 2.883330 0.205169
        -v -0.087106 2.785420 0.148224
        -v -0.258037 3.066670 0.258027
        -v -0.231031 2.981250 0.231020
        -v -0.168463 2.883330 0.168452
        -v -0.121672 2.785420 0.121662
        -v -0.314228 3.066670 0.184824
        -v -0.281352 2.981250 0.165470
        -v -0.205180 2.883330 0.120636
        -v -0.148234 2.785420 0.087096
        -v -0.350254 3.066670 0.097760
        -v -0.313617 2.981250 0.087518
        -v -0.228728 2.883330 0.063793
        -v -0.165279 2.785420 0.046035
        -v -0.362963 3.066670 -0.000011
        -v -0.325000 2.981250 -0.000011
        -v -0.237037 2.883330 -0.000010
        -v -0.171296 2.785420 -0.000010
        -v -0.350254 3.066670 -0.097782
        -v -0.313617 2.981250 -0.087540
        -v -0.228728 2.883330 -0.063813
        -v -0.165279 2.785420 -0.046055
        -v -0.314228 3.066670 -0.184844
        -v -0.281352 2.981250 -0.165492
        -v -0.205180 2.883330 -0.120658
        -v -0.148234 2.785420 -0.087116
        -v -0.258037 3.066670 -0.258047
        -v -0.231031 2.981250 -0.231042
        -v -0.168463 2.883330 -0.168474
        -v -0.121672 2.785420 -0.121682
        -v -0.184834 3.066670 -0.314238
        -v -0.165481 2.981250 -0.281363
        -v -0.120647 2.883330 -0.205191
        -v -0.087106 2.785420 -0.148244
        -v -0.097771 3.066670 -0.350264
        -v -0.087529 2.981250 -0.313628
        -v -0.063803 2.883330 -0.228739
        -v -0.046045 2.785420 -0.165289
        -v 0.000000 3.066670 -0.362973
        -v 0.000000 2.981250 -0.325011
        -v 0.000000 2.883330 -0.237048
        -v 0.000000 2.785420 -0.171306
        -v 0.097771 3.066670 -0.350264
        -v 0.087529 2.981250 -0.313628
        -v 0.063803 2.883330 -0.228739
        -v 0.046045 2.785420 -0.165289
        -v 0.184834 3.066670 -0.314238
        -v 0.165481 2.981250 -0.281363
        -v 0.120647 2.883330 -0.205191
        -v 0.087106 2.785420 -0.148244
        -v 0.258037 3.066670 -0.258047
        -v 0.231031 2.981250 -0.231042
        -v 0.168463 2.883330 -0.168474
        -v 0.121672 2.785420 -0.121682
        -v 0.314228 3.066670 -0.184844
        -v 0.281352 2.981250 -0.165492
        -v 0.205180 2.883330 -0.120658
        -v 0.148234 2.785420 -0.087116
        -v 0.350254 3.066670 -0.097782
        -v 0.313617 2.981250 -0.087540
        -v 0.228728 2.883330 -0.063813
        -v 0.165279 2.785420 -0.046055
        -vn 0.025666 -0.999664 0.000000
        -vn 0.000000 -1.000000 0.000000
        -vn 0.024781 -0.999664 -0.006623
        -vn 0.022156 -0.999664 -0.012787
        -vn 0.018067 -0.999664 -0.018067
        -vn 0.012787 -0.999664 -0.022126
        -vn 0.006623 -0.999664 -0.024751
        -vn 0.000000 -0.999664 -0.025666
        -vn -0.006623 -0.999664 -0.024751
        -vn -0.012787 -0.999664 -0.022126
        -vn -0.018067 -0.999664 -0.018067
        -vn -0.022156 -0.999664 -0.012787
        -vn -0.024781 -0.999664 -0.006623
        -vn -0.025666 -0.999664 0.000000
        -vn -0.024781 -0.999664 0.006623
        -vn -0.022156 -0.999664 0.012787
        -vn -0.018067 -0.999664 0.018067
        -vn -0.012787 -0.999664 0.022156
        -vn -0.006623 -0.999664 0.024781
        -vn 0.000000 -0.999664 0.025666
        -vn 0.006623 -0.999664 0.024781
        -vn 0.012787 -0.999664 0.022156
        -vn 0.018067 -0.999664 0.018067
        -vn 0.022156 -0.999664 0.012787
        -vn 0.024781 -0.999664 0.006623
        -vn -0.946562 -0.322459 0.000000
        -vn -0.913999 -0.322947 -0.245491
        -vn -0.958617 -0.122227 -0.257057
        -vn -0.992523 -0.122013 0.000000
        -vn -0.832057 0.554674 0.000000
        -vn -0.803217 0.555376 -0.215308
        -vn -0.048616 0.998810 0.000000
        -vn -0.046205 0.998840 -0.012726
        -vn 0.525376 0.839106 0.140843
        -vn 0.544267 0.838893 0.000000
        -vn 0.756340 0.621845 0.202918
        -vn 0.783471 0.621387 0.000000
        -vn 0.850551 0.473769 0.228217
        -vn 0.880886 0.473281 0.000000
        -vn -0.818842 -0.323435 -0.474166
        -vn -0.859004 -0.122410 -0.497085
        -vn -0.719657 0.555559 -0.416425
        -vn -0.041749 0.998810 -0.024415
        -vn 0.470107 0.839625 0.272011
        -vn 0.677236 0.622608 0.391980
        -vn 0.761803 0.474471 0.440962
        -vn -0.669027 -0.323679 -0.669027
        -vn -0.701773 -0.122440 -0.701773
        -vn -0.587878 0.555650 -0.587878
        -vn -0.034272 0.998810 -0.034272
        -vn 0.383831 0.839808 0.383831
        -vn 0.553148 0.622913 0.553148
        -vn 0.622303 0.474776 0.622303
        -vn -0.474166 -0.323435 -0.818842
        -vn -0.497085 -0.122410 -0.859004
        -vn -0.416425 0.555528 -0.719657
        -vn -0.024415 0.998810 -0.041749
        -vn 0.272011 0.839625 0.470077
        -vn 0.392010 0.622608 0.677236
        -vn 0.440962 0.474502 0.761803
        -vn -0.245460 -0.322977 -0.913999
        -vn -0.257057 -0.122257 -0.958617
        -vn -0.215339 0.555193 -0.803308
        -vn -0.012726 0.998840 -0.046236
        -vn 0.140873 0.839076 0.525437
        -vn 0.202918 0.621906 0.756310
        -vn 0.228217 0.473769 0.850551
        -vn 0.000000 -0.322489 -0.946562
        -vn 0.000000 -0.122044 -0.992523
        -vn 0.000000 0.554491 -0.832179
        -vn 0.000000 0.998779 -0.048799
        -vn 0.000000 0.838893 0.544267
        -vn 0.000000 0.621387 0.783471
        -vn 0.000000 0.473281 0.880886
        -vn 0.245460 -0.322977 -0.913999
        -vn 0.257057 -0.122257 -0.958617
        -vn 0.215339 0.555193 -0.803308
        -vn 0.012726 0.998840 -0.046236
        -vn -0.140873 0.839076 0.525437
        -vn -0.202918 0.621906 0.756310
        -vn -0.228217 0.473769 0.850551
        -vn 0.474166 -0.323435 -0.818842
        -vn 0.497085 -0.122410 -0.859004
        -vn 0.416425 0.555528 -0.719657
        -vn 0.024415 0.998810 -0.041749
        -vn -0.272011 0.839625 0.470077
        -vn -0.392010 0.622608 0.677236
        -vn -0.440962 0.474502 0.761803
        -vn 0.669027 -0.323679 -0.669027
        -vn 0.701773 -0.122440 -0.701773
        -vn 0.587878 0.555650 -0.587878
        -vn 0.034272 0.998810 -0.034272
        -vn -0.383831 0.839808 0.383831
        -vn -0.553148 0.622913 0.553148
        -vn -0.622303 0.474776 0.622303
        -vn 0.818842 -0.323435 -0.474166
        -vn 0.859004 -0.122410 -0.497085
        -vn 0.719657 0.555559 -0.416425
        -vn 0.041749 0.998810 -0.024415
        -vn -0.470107 0.839625 0.272011
        -vn -0.677236 0.622608 0.391980
        -vn -0.761803 0.474471 0.440962
        -vn 0.913999 -0.322947 -0.245491
        -vn 0.958617 -0.122227 -0.257057
        -vn 0.803217 0.555376 -0.215308
        -vn 0.046205 0.998840 -0.012726
        -vn -0.525376 0.839106 0.140843
        -vn -0.756340 0.621845 0.202918
        -vn -0.850551 0.473769 0.228217
        -vn 0.946562 -0.322459 0.000000
        -vn 0.992523 -0.122013 0.000000
        -vn 0.832057 0.554674 0.000000
        -vn 0.048616 0.998810 0.000000
        -vn -0.544267 0.838893 0.000000
        -vn -0.783471 0.621387 0.000000
        -vn -0.880886 0.473281 0.000000
        -vn 0.913999 -0.322947 0.245491
        -vn 0.958617 -0.122227 0.257057
        -vn 0.803217 0.555376 0.215308
        -vn 0.046205 0.998840 0.012726
        -vn -0.525376 0.839106 -0.140843
        -vn -0.756340 0.621845 -0.202918
        -vn -0.850551 0.473769 -0.228217
        -vn 0.818842 -0.323435 0.474166
        -vn 0.859004 -0.122410 0.497085
        -vn 0.719657 0.555559 0.416425
        -vn 0.041749 0.998810 0.024415
        -vn -0.470107 0.839625 -0.272011
        -vn -0.677236 0.622608 -0.391980
        -vn -0.761803 0.474471 -0.440962
        -vn 0.669027 -0.323679 0.669027
        -vn 0.701773 -0.122440 0.701773
        -vn 0.587878 0.555650 0.587878
        -vn 0.034272 0.998810 0.034272
        -vn -0.383831 0.839808 -0.383831
        -vn -0.553148 0.622913 -0.553148
        -vn -0.622303 0.474776 -0.622303
        -vn 0.474166 -0.323435 0.818842
        -vn 0.497085 -0.122410 0.859004
        -vn 0.416425 0.555559 0.719657
        -vn 0.024415 0.998810 0.041749
        -vn -0.272011 0.839625 -0.470107
        -vn -0.391980 0.622608 -0.677236
        -vn -0.440962 0.474471 -0.761803
        -vn 0.245460 -0.322977 0.913999
        -vn 0.257027 -0.122257 0.958617
        -vn 0.215369 0.555193 0.803308
        -vn 0.012726 0.998840 0.046236
        -vn -0.140873 0.839045 -0.525498
        -vn -0.202918 0.621845 -0.756371
        -vn -0.228187 0.473769 -0.850551
        -vn 0.000000 -0.322459 0.946562
        -vn 0.000000 -0.122013 0.992523
        -vn 0.000000 0.554674 0.832057
        -vn 0.000000 0.998810 0.048616
        -vn 0.000000 0.838893 -0.544267
        -vn 0.000000 0.621387 -0.783471
        -vn 0.000000 0.473281 -0.880886
        -vn -0.245460 -0.322977 0.913999
        -vn -0.257027 -0.122257 0.958617
        -vn -0.215369 0.555193 0.803308
        -vn -0.012726 0.998840 0.046236
        -vn 0.140873 0.839045 -0.525498
        -vn 0.202918 0.621845 -0.756371
        -vn 0.228187 0.473769 -0.850551
        -vn -0.474166 -0.323435 0.818842
        -vn -0.497085 -0.122410 0.859004
        -vn -0.416425 0.555559 0.719657
        -vn -0.024415 0.998810 0.041749
        -vn 0.272011 0.839625 -0.470107
        -vn 0.391980 0.622608 -0.677236
        -vn 0.440962 0.474471 -0.761803
        -vn -0.669027 -0.323679 0.669027
        -vn -0.701773 -0.122440 0.701773
        -vn -0.587878 0.555650 0.587878
        -vn -0.034272 0.998810 0.034272
        -vn 0.383831 0.839808 -0.383831
        -vn 0.553148 0.622913 -0.553148
        -vn 0.622303 0.474776 -0.622303
        -vn -0.818842 -0.323435 0.474166
        -vn -0.859004 -0.122410 0.497085
        -vn -0.719657 0.555559 0.416425
        -vn -0.041749 0.998810 0.024415
        -vn 0.470107 0.839625 -0.272011
        -vn 0.677236 0.622608 -0.391980
        -vn 0.761803 0.474471 -0.440962
        -vn -0.913999 -0.322947 0.245491
        -vn -0.958617 -0.122227 0.257057
        -vn -0.803217 0.555376 0.215308
        -vn -0.046205 0.998840 0.012726
        -vn 0.525376 0.839106 -0.140843
        -vn 0.756340 0.621845 -0.202918
        -vn 0.850551 0.473769 -0.228217
        -vn 0.877041 0.418744 0.235298
        -vn 0.908292 0.418256 0.000000
        -vn 0.888668 0.391644 0.238441
        -vn 0.920286 0.391156 0.000000
        -vn 0.907315 0.342753 0.243446
        -vn 0.939543 0.342357 0.000000
        -vn 0.931028 0.265908 0.249855
        -vn 0.964080 0.265542 0.000000
        -vn 0.954558 0.152104 0.256172
        -vn 0.988372 0.151891 0.000000
        -vn 0.964782 -0.045717 0.258980
        -vn 0.998932 -0.045656 0.000000
        -vn 0.785638 0.419416 0.454756
        -vn 0.796075 0.392285 0.460799
        -vn 0.812830 0.343333 0.470504
        -vn 0.834162 0.266366 0.482864
        -vn 0.855312 0.152409 0.495132
        -vn 0.864498 -0.045808 0.500504
        -vn 0.641804 0.419691 0.641804
        -vn 0.650349 0.392529 0.650349
        -vn 0.664052 0.343577 0.664052
        -vn 0.681509 0.266579 0.681509
        -vn 0.698813 0.152501 0.698813
        -vn 0.706351 -0.045869 0.706351
        -vn 0.454756 0.419416 0.785638
        -vn 0.460799 0.392285 0.796075
        -vn 0.470504 0.343333 0.812830
        -vn 0.482864 0.266396 0.834162
        -vn 0.495132 0.152409 0.855312
        -vn 0.500504 -0.045808 0.864498
        -vn 0.235298 0.418744 0.877041
        -vn 0.238441 0.391644 0.888668
        -vn 0.243446 0.342753 0.907315
        -vn 0.249825 0.265908 0.931028
        -vn 0.256172 0.152135 0.954558
        -vn 0.258980 -0.045717 0.964782
        -vn 0.000000 0.418256 0.908292
        -vn 0.000000 0.391156 0.920286
        -vn 0.000000 0.342357 0.939543
        -vn 0.000000 0.265572 0.964080
        -vn 0.000000 0.151921 0.988372
        -vn 0.000000 -0.045656 0.998932
        -vn -0.235298 0.418744 0.877041
        -vn -0.238441 0.391644 0.888668
        -vn -0.243446 0.342753 0.907315
        -vn -0.249825 0.265908 0.931028
        -vn -0.256172 0.152135 0.954558
        -vn -0.258980 -0.045717 0.964782
        -vn -0.454756 0.419416 0.785638
        -vn -0.460799 0.392285 0.796075
        -vn -0.470504 0.343333 0.812830
        -vn -0.482864 0.266396 0.834162
        -vn -0.495132 0.152409 0.855312
        -vn -0.500504 -0.045808 0.864498
        -vn -0.641804 0.419691 0.641804
        -vn -0.650349 0.392529 0.650349
        -vn -0.664052 0.343577 0.664052
        -vn -0.681509 0.266579 0.681509
        -vn -0.698813 0.152501 0.698813
        -vn -0.706351 -0.045869 0.706351
        -vn -0.785638 0.419416 0.454756
        -vn -0.796075 0.392285 0.460799
        -vn -0.812830 0.343333 0.470504
        -vn -0.834162 0.266366 0.482864
        -vn -0.855312 0.152409 0.495132
        -vn -0.864498 -0.045808 0.500504
        -vn -0.877041 0.418744 0.235298
        -vn -0.888668 0.391644 0.238441
        -vn -0.907315 0.342753 0.243446
        -vn -0.931028 0.265908 0.249825
        -vn -0.954558 0.152104 0.256172
        -vn -0.964782 -0.045717 0.258980
        -vn -0.908292 0.418256 0.000000
        -vn -0.920286 0.391156 0.000000
        -vn -0.939543 0.342357 0.000000
        -vn -0.964080 0.265542 0.000000
        -vn -0.988372 0.151891 0.000000
        -vn -0.998932 -0.045656 0.000000
        -vn -0.877041 0.418744 -0.235298
        -vn -0.888668 0.391644 -0.238441
        -vn -0.907315 0.342753 -0.243446
        -vn -0.931028 0.265877 -0.249855
        -vn -0.954558 0.152104 -0.256172
        -vn -0.964782 -0.045717 -0.258980
        -vn -0.785638 0.419416 -0.454756
        -vn -0.796075 0.392285 -0.460799
        -vn -0.812830 0.343333 -0.470504
        -vn -0.834162 0.266366 -0.482864
        -vn -0.855312 0.152379 -0.495132
        -vn -0.864498 -0.045808 -0.500504
        -vn -0.641804 0.419691 -0.641804
        -vn -0.650349 0.392529 -0.650349
        -vn -0.664052 0.343547 -0.664052
        -vn -0.681509 0.266549 -0.681509
        -vn -0.698813 0.152470 -0.698813
        -vn -0.706351 -0.045869 -0.706351
        -vn -0.454756 0.419416 -0.785638
        -vn -0.460768 0.392285 -0.796075
        -vn -0.470504 0.343333 -0.812830
        -vn -0.482864 0.266366 -0.834162
        -vn -0.495132 0.152379 -0.855312
        -vn -0.500504 -0.045808 -0.864498
        -vn -0.235298 0.418744 -0.877041
        -vn -0.238441 0.391644 -0.888668
        -vn -0.243446 0.342753 -0.907315
        -vn -0.249855 0.265877 -0.931059
        -vn -0.256172 0.152074 -0.954558
        -vn -0.258980 -0.045717 -0.964782
        -vn 0.000000 0.418256 -0.908292
        -vn 0.000000 0.391156 -0.920286
        -vn 0.000000 0.342357 -0.939543
        -vn 0.000000 0.265511 -0.964080
        -vn 0.000000 0.151891 -0.988372
        -vn 0.000000 -0.045656 -0.998932
        -vn 0.235298 0.418744 -0.877041
        -vn 0.238441 0.391644 -0.888668
        -vn 0.243446 0.342753 -0.907315
        -vn 0.249855 0.265877 -0.931059
        -vn 0.256172 0.152074 -0.954558
        -vn 0.258980 -0.045717 -0.964782
        -vn 0.454756 0.419416 -0.785638
        -vn 0.460768 0.392285 -0.796075
        -vn 0.470504 0.343333 -0.812830
        -vn 0.482864 0.266366 -0.834162
        -vn 0.495132 0.152379 -0.855312
        -vn 0.500504 -0.045808 -0.864498
        -vn 0.641804 0.419691 -0.641804
        -vn 0.650349 0.392529 -0.650349
        -vn 0.664052 0.343547 -0.664052
        -vn 0.681509 0.266549 -0.681509
        -vn 0.698813 0.152470 -0.698813
        -vn 0.706351 -0.045869 -0.706351
        -vn 0.785638 0.419416 -0.454756
        -vn 0.796075 0.392285 -0.460799
        -vn 0.812830 0.343333 -0.470504
        -vn 0.834162 0.266366 -0.482864
        -vn 0.855312 0.152379 -0.495132
        -vn 0.864498 -0.045808 -0.500504
        -vn 0.877041 0.418744 -0.235298
        -vn 0.888668 0.391644 -0.238441
        -vn 0.907315 0.342753 -0.243446
        -vn 0.931028 0.265908 -0.249825
        -vn 0.954558 0.152104 -0.256172
        -vn 0.964782 -0.045717 -0.258980
        -vn 0.912839 -0.326609 0.245003
        -vn 0.945250 -0.326273 0.000000
        -vn 0.795892 -0.566485 0.213538
        -vn 0.824396 -0.565996 0.000000
        -vn 0.687399 -0.702445 0.184393
        -vn 0.712180 -0.701987 0.000000
        -vn 0.630146 -0.757805 0.169012
        -vn 0.652974 -0.757347 0.000000
        -vn 0.698752 -0.690329 0.187445
        -vn 0.724021 -0.689749 0.000000
        -vn 0.855861 -0.463454 0.229530
        -vn 0.886380 -0.462905 0.000000
        -vn 0.817774 -0.327158 0.473434
        -vn 0.712729 -0.567248 0.412549
        -vn 0.615375 -0.703146 0.356151
        -vn 0.564043 -0.758446 0.326456
        -vn 0.625660 -0.690939 0.362102
        -vn 0.766625 -0.464125 0.443678
        -vn 0.668111 -0.327403 0.668111
        -vn 0.582171 -0.567522 0.582171
        -vn 0.502579 -0.703421 0.502579
        -vn 0.460646 -0.758660 0.460646
        -vn 0.510971 -0.691183 0.510971
        -vn 0.626209 -0.464370 0.626209
        -vn 0.473434 -0.327158 0.817774
        -vn 0.412549 -0.567248 0.712729
        -vn 0.356151 -0.703146 0.615375
        -vn 0.326456 -0.758446 0.564043
        -vn 0.362102 -0.690939 0.625660
        -vn 0.443678 -0.464125 0.766625
        -vn 0.245003 -0.326609 0.912839
        -vn 0.213538 -0.566485 0.795892
        -vn 0.184393 -0.702445 0.687399
        -vn 0.169012 -0.757805 0.630146
        -vn 0.187414 -0.690329 0.698752
        -vn 0.229530 -0.463454 0.855831
        -vn 0.000000 -0.326273 0.945250
        -vn 0.000000 -0.565996 0.824396
        -vn 0.000000 -0.701987 0.712180
        -vn 0.000000 -0.757347 0.652974
        -vn 0.000000 -0.689749 0.724021
        -vn 0.000000 -0.462905 0.886380
        -vn -0.245003 -0.326609 0.912839
        -vn -0.213538 -0.566485 0.795892
        -vn -0.184393 -0.702445 0.687399
        -vn -0.169012 -0.757805 0.630146
        -vn -0.187414 -0.690329 0.698752
        -vn -0.229530 -0.463454 0.855831
        -vn -0.473434 -0.327158 0.817774
        -vn -0.412549 -0.567248 0.712729
        -vn -0.356151 -0.703146 0.615375
        -vn -0.326456 -0.758446 0.564043
        -vn -0.362102 -0.690939 0.625660
        -vn -0.443678 -0.464125 0.766625
        -vn -0.668111 -0.327403 0.668111
        -vn -0.582171 -0.567522 0.582171
        -vn -0.502579 -0.703421 0.502579
        -vn -0.460646 -0.758660 0.460646
        -vn -0.510971 -0.691183 0.510971
        -vn -0.626209 -0.464370 0.626209
        -vn -0.817774 -0.327158 0.473434
        -vn -0.712729 -0.567248 0.412549
        -vn -0.615375 -0.703146 0.356151
        -vn -0.564043 -0.758446 0.326456
        -vn -0.625660 -0.690939 0.362102
        -vn -0.766625 -0.464125 0.443678
        -vn -0.912839 -0.326609 0.245003
        -vn -0.795892 -0.566485 0.213538
        -vn -0.687399 -0.702445 0.184393
        -vn -0.630146 -0.757805 0.169012
        -vn -0.698752 -0.690329 0.187445
        -vn -0.855861 -0.463454 0.229530
        -vn -0.945250 -0.326273 0.000000
        -vn -0.824396 -0.565996 0.000000
        -vn -0.712180 -0.701987 0.000000
        -vn -0.652974 -0.757347 0.000000
        -vn -0.724021 -0.689749 0.000000
        -vn -0.886380 -0.462905 0.000000
        -vn -0.912839 -0.326609 -0.245003
        -vn -0.795892 -0.566485 -0.213538
        -vn -0.687399 -0.702445 -0.184393
        -vn -0.630146 -0.757805 -0.169012
        -vn -0.698752 -0.690329 -0.187414
        -vn -0.855831 -0.463454 -0.229530
        -vn -0.817774 -0.327158 -0.473434
        -vn -0.712729 -0.567248 -0.412549
        -vn -0.615375 -0.703146 -0.356151
        -vn -0.564043 -0.758446 -0.326456
        -vn -0.625660 -0.690939 -0.362102
        -vn -0.766625 -0.464125 -0.443678
        -vn -0.668111 -0.327403 -0.668111
        -vn -0.582171 -0.567522 -0.582171
        -vn -0.502579 -0.703421 -0.502579
        -vn -0.460646 -0.758660 -0.460646
        -vn -0.510971 -0.691183 -0.510971
        -vn -0.626209 -0.464370 -0.626209
        -vn -0.473434 -0.327158 -0.817774
        -vn -0.412549 -0.567248 -0.712729
        -vn -0.356151 -0.703146 -0.615375
        -vn -0.326456 -0.758446 -0.564043
        -vn -0.362102 -0.690939 -0.625660
        -vn -0.443678 -0.464125 -0.766625
        -vn -0.245003 -0.326609 -0.912839
        -vn -0.213538 -0.566485 -0.795892
        -vn -0.184393 -0.702445 -0.687399
        -vn -0.169012 -0.757805 -0.630146
        -vn -0.187414 -0.690329 -0.698752
        -vn -0.229530 -0.463454 -0.855831
        -vn 0.000000 -0.326273 -0.945250
        -vn 0.000000 -0.565996 -0.824396
        -vn 0.000000 -0.701987 -0.712149
        -vn 0.000000 -0.757347 -0.652974
        -vn 0.000000 -0.689749 -0.724021
        -vn 0.000000 -0.462905 -0.886380
        -vn 0.245003 -0.326609 -0.912839
        -vn 0.213538 -0.566485 -0.795892
        -vn 0.184393 -0.702445 -0.687399
        -vn 0.169012 -0.757805 -0.630146
        -vn 0.187414 -0.690329 -0.698752
        -vn 0.229530 -0.463454 -0.855831
        -vn 0.473434 -0.327158 -0.817774
        -vn 0.412549 -0.567248 -0.712729
        -vn 0.356151 -0.703146 -0.615375
        -vn 0.326456 -0.758446 -0.564043
        -vn 0.362102 -0.690939 -0.625660
        -vn 0.443678 -0.464125 -0.766625
        -vn 0.668111 -0.327403 -0.668111
        -vn 0.582171 -0.567522 -0.582171
        -vn 0.502579 -0.703421 -0.502579
        -vn 0.460646 -0.758660 -0.460646
        -vn 0.510971 -0.691183 -0.510971
        -vn 0.626209 -0.464370 -0.626209
        -vn 0.817774 -0.327158 -0.473434
        -vn 0.712729 -0.567248 -0.412549
        -vn 0.615375 -0.703146 -0.356151
        -vn 0.564043 -0.758446 -0.326456
        -vn 0.625660 -0.690939 -0.362102
        -vn 0.766625 -0.464125 -0.443678
        -vn 0.912839 -0.326609 -0.245003
        -vn 0.795892 -0.566485 -0.213538
        -vn 0.687399 -0.702445 -0.184393
        -vn 0.630146 -0.757805 -0.169012
        -vn 0.698752 -0.690329 -0.187414
        -vn 0.855831 -0.463454 -0.229530
        -vn 0.068667 -0.997620 0.000000
        -vn 0.066256 -0.997620 -0.017731
        -vn 0.157170 -0.987548 0.000000
        -vn 0.151677 -0.987579 -0.040620
        -vn 0.373150 -0.927763 0.000000
        -vn 0.360149 -0.927885 -0.096469
        -vn 0.789148 -0.614154 0.000000
        -vn 0.762017 -0.614399 -0.204474
        -vn 0.059236 -0.997650 -0.034242
        -vn 0.135624 -0.987640 -0.078463
        -vn 0.322153 -0.928129 -0.186346
        -vn 0.682333 -0.615131 -0.394971
        -vn 0.048341 -0.997650 -0.048341
        -vn 0.110691 -0.987640 -0.110691
        -vn 0.262947 -0.928251 -0.262947
        -vn 0.557329 -0.615375 -0.557329
        -vn 0.034272 -0.997650 -0.059236
        -vn 0.078463 -0.987640 -0.135624
        -vn 0.186377 -0.928129 -0.322153
        -vn 0.394971 -0.615131 -0.682333
        -vn 0.017731 -0.997620 -0.066256
        -vn 0.040620 -0.987579 -0.151677
        -vn 0.096469 -0.927885 -0.360118
        -vn 0.204505 -0.614399 -0.762017
        -vn 0.000000 -0.997620 -0.068667
        -vn 0.000000 -0.987548 -0.157170
        -vn 0.000000 -0.927763 -0.373150
        -vn 0.000000 -0.614154 -0.789148
        -vn -0.017731 -0.997620 -0.066256
        -vn -0.040620 -0.987579 -0.151677
        -vn -0.096469 -0.927885 -0.360118
        -vn -0.204505 -0.614399 -0.762017
        -vn -0.034272 -0.997650 -0.059236
        -vn -0.078463 -0.987640 -0.135624
        -vn -0.186377 -0.928129 -0.322153
        -vn -0.394971 -0.615131 -0.682333
        -vn -0.048341 -0.997650 -0.048341
        -vn -0.110691 -0.987640 -0.110691
        -vn -0.262947 -0.928251 -0.262947
        -vn -0.557329 -0.615375 -0.557329
        -vn -0.059236 -0.997650 -0.034242
        -vn -0.135624 -0.987640 -0.078463
        -vn -0.322153 -0.928129 -0.186346
        -vn -0.682333 -0.615131 -0.394971
        -vn -0.066256 -0.997620 -0.017731
        -vn -0.151677 -0.987579 -0.040620
        -vn -0.360149 -0.927885 -0.096469
        -vn -0.762017 -0.614399 -0.204474
        -vn -0.068667 -0.997620 0.000000
        -vn -0.157170 -0.987548 0.000000
        -vn -0.373150 -0.927763 0.000000
        -vn -0.789148 -0.614154 0.000000
        -vn -0.066256 -0.997620 0.017731
        -vn -0.151677 -0.987579 0.040620
        -vn -0.360118 -0.927885 0.096469
        -vn -0.762017 -0.614399 0.204505
        -vn -0.059236 -0.997650 0.034272
        -vn -0.135624 -0.987640 0.078463
        -vn -0.322153 -0.928129 0.186377
        -vn -0.682333 -0.615131 0.394971
        -vn -0.048341 -0.997650 0.048341
        -vn -0.110691 -0.987640 0.110691
        -vn -0.262947 -0.928251 0.262947
        -vn -0.557329 -0.615375 0.557329
        -vn -0.034272 -0.997650 0.059236
        -vn -0.078463 -0.987640 0.135624
        -vn -0.186377 -0.928129 0.322153
        -vn -0.394971 -0.615131 0.682333
        -vn -0.017731 -0.997620 0.066256
        -vn -0.040620 -0.987579 0.151677
        -vn -0.096469 -0.927885 0.360149
        -vn -0.204474 -0.614399 0.762017
        -vn 0.000000 -0.997620 0.068667
        -vn 0.000000 -0.987548 0.157170
        -vn 0.000000 -0.927763 0.373150
        -vn 0.000000 -0.614154 0.789148
        -vn 0.017731 -0.997620 0.066256
        -vn 0.040620 -0.987579 0.151677
        -vn 0.096469 -0.927885 0.360149
        -vn 0.204474 -0.614399 0.762017
        -vn 0.034272 -0.997650 0.059236
        -vn 0.078463 -0.987640 0.135624
        -vn 0.186377 -0.928129 0.322153
        -vn 0.394971 -0.615131 0.682333
        -vn 0.048341 -0.997650 0.048341
        -vn 0.110691 -0.987640 0.110691
        -vn 0.262947 -0.928251 0.262947
        -vn 0.557329 -0.615375 0.557329
        -vn 0.059236 -0.997650 0.034272
        -vn 0.135624 -0.987640 0.078463
        -vn 0.322153 -0.928129 0.186377
        -vn 0.682333 -0.615101 0.394971
        -vn 0.066256 -0.997620 0.017731
        -vn 0.151677 -0.987579 0.040620
        -vn 0.360118 -0.927885 0.096469
        -vn 0.762017 -0.614399 0.204505
        -vn 0.692129 0.697470 0.185583
        -vn 0.717063 0.696982 0.000000
        -vn 0.915586 0.318766 0.245064
        -vn 0.947905 0.318522 0.000000
        -vn 0.620045 0.697653 0.358837
        -vn 0.820429 0.318979 0.474410
        -vn 0.506546 0.697684 0.506546
        -vn 0.670125 0.319041 0.670125
        -vn 0.358837 0.697653 0.620045
        -vn 0.474410 0.318979 0.820429
        -vn 0.185583 0.697470 0.692129
        -vn 0.245064 0.318766 0.915586
        -vn 0.000000 0.696982 0.717063
        -vn 0.000000 0.318522 0.947905
        -vn -0.185583 0.697470 0.692129
        -vn -0.245064 0.318766 0.915586
        -vn -0.358837 0.697653 0.620045
        -vn -0.474410 0.318979 0.820429
        -vn -0.506546 0.697684 0.506546
        -vn -0.670125 0.319041 0.670125
        -vn -0.620045 0.697653 0.358837
        -vn -0.820429 0.318979 0.474410
        -vn -0.692129 0.697470 0.185583
        -vn -0.915586 0.318766 0.245064
        -vn -0.717063 0.696982 0.000000
        -vn -0.947905 0.318522 0.000000
        -vn -0.692129 0.697470 -0.185583
        -vn -0.915586 0.318766 -0.245064
        -vn -0.620045 0.697653 -0.358837
        -vn -0.820429 0.318979 -0.474410
        -vn -0.506546 0.697684 -0.506546
        -vn -0.670125 0.319041 -0.670125
        -vn -0.358837 0.697653 -0.620045
        -vn -0.474410 0.318979 -0.820429
        -vn -0.185583 0.697470 -0.692129
        -vn -0.245064 0.318766 -0.915586
        -vn 0.000000 0.696982 -0.717063
        -vn 0.000000 0.318522 -0.947905
        -vn 0.185583 0.697470 -0.692129
        -vn 0.245064 0.318766 -0.915586
        -vn 0.358837 0.697653 -0.620045
        -vn 0.474410 0.318979 -0.820429
        -vn 0.506546 0.697684 -0.506546
        -vn 0.670125 0.319041 -0.670125
        -vn 0.620045 0.697653 -0.358837
        -vn 0.820429 0.318979 -0.474410
        -vn 0.692129 0.697470 -0.185583
        -vn 0.915586 0.318766 -0.245064
        -vn 0.282083 0.956389 0.075686
        -vn 0.292520 0.956236 0.000000
        -vn 0.171606 0.984069 0.046022
        -vn 0.177953 0.984008 0.000000
        -vn 0.153264 0.987304 0.041078
        -vn 0.158879 0.987274 0.000000
        -vn 0.210059 0.976043 0.056276
        -vn 0.217719 0.975982 0.000000
        -vn 0.487197 0.863460 0.130558
        -vn 0.504715 0.863277 0.000000
        -vn 0.662801 0.727226 0.178198
        -vn 0.686911 0.726707 0.000000
        -vn 0.252388 0.956511 0.146092
        -vn 0.153508 0.984130 0.088839
        -vn 0.137059 0.987365 0.079318
        -vn 0.187872 0.976135 0.108676
        -vn 0.435926 0.863887 0.252205
        -vn 0.593310 0.727866 0.343730
        -vn 0.206091 0.956572 0.206091
        -vn 0.125340 0.984161 0.125340
        -vn 0.111911 0.987396 0.111911
        -vn 0.153356 0.976196 0.153356
        -vn 0.355907 0.864071 0.355907
        -vn 0.484664 0.728111 0.484664
        -vn 0.146092 0.956511 0.252388
        -vn 0.088839 0.984130 0.153508
        -vn 0.079318 0.987365 0.137059
        -vn 0.108676 0.976135 0.187872
        -vn 0.252205 0.863887 0.435926
        -vn 0.343730 0.727866 0.593310
        -vn 0.075686 0.956389 0.282083
        -vn 0.046022 0.984069 0.171606
        -vn 0.041078 0.987304 0.153264
        -vn 0.056276 0.976043 0.210059
        -vn 0.130558 0.863460 0.487197
        -vn 0.178198 0.727226 0.662801
        -vn 0.000000 0.956236 0.292520
        -vn 0.000000 0.984008 0.177953
        -vn 0.000000 0.987274 0.158879
        -vn 0.000000 0.975982 0.217719
        -vn 0.000000 0.863277 0.504715
        -vn 0.000000 0.726707 0.686911
        -vn -0.075686 0.956389 0.282083
        -vn -0.046022 0.984069 0.171606
        -vn -0.041078 0.987304 0.153264
        -vn -0.056276 0.976043 0.210059
        -vn -0.130558 0.863460 0.487197
        -vn -0.178198 0.727226 0.662801
        -vn -0.146092 0.956511 0.252388
        -vn -0.088839 0.984130 0.153508
        -vn -0.079318 0.987365 0.137059
        -vn -0.108676 0.976135 0.187872
        -vn -0.252205 0.863887 0.435926
        -vn -0.343730 0.727866 0.593310
        -vn -0.206091 0.956572 0.206091
        -vn -0.125340 0.984161 0.125340
        -vn -0.111911 0.987396 0.111911
        -vn -0.153356 0.976196 0.153356
        -vn -0.355907 0.864071 0.355907
        -vn -0.484664 0.728111 0.484664
        -vn -0.252388 0.956511 0.146092
        -vn -0.153508 0.984130 0.088839
        -vn -0.137059 0.987365 0.079318
        -vn -0.187872 0.976135 0.108676
        -vn -0.435926 0.863887 0.252205
        -vn -0.593310 0.727866 0.343730
        -vn -0.282083 0.956389 0.075686
        -vn -0.171606 0.984069 0.046022
        -vn -0.153264 0.987304 0.041078
        -vn -0.210059 0.976043 0.056276
        -vn -0.487197 0.863460 0.130558
        -vn -0.662801 0.727226 0.178198
        -vn -0.292520 0.956236 0.000000
        -vn -0.177953 0.984008 0.000000
        -vn -0.158879 0.987274 0.000000
        -vn -0.217719 0.975982 0.000000
        -vn -0.504715 0.863277 0.000000
        -vn -0.686911 0.726707 0.000000
        -vn -0.282083 0.956389 -0.075686
        -vn -0.171606 0.984069 -0.046022
        -vn -0.153264 0.987304 -0.041078
        -vn -0.210059 0.976043 -0.056276
        -vn -0.487197 0.863460 -0.130558
        -vn -0.662801 0.727226 -0.178198
        -vn -0.252388 0.956511 -0.146092
        -vn -0.153508 0.984130 -0.088839
        -vn -0.137059 0.987365 -0.079318
        -vn -0.187872 0.976135 -0.108676
        -vn -0.435926 0.863887 -0.252205
        -vn -0.593310 0.727866 -0.343730
        -vn -0.206091 0.956572 -0.206091
        -vn -0.125340 0.984161 -0.125340
        -vn -0.111911 0.987396 -0.111911
        -vn -0.153356 0.976196 -0.153356
        -vn -0.355907 0.864071 -0.355907
        -vn -0.484664 0.728111 -0.484664
        -vn -0.146092 0.956511 -0.252388
        -vn -0.088839 0.984130 -0.153508
        -vn -0.079318 0.987365 -0.137059
        -vn -0.108676 0.976135 -0.187872
        -vn -0.252205 0.863887 -0.435926
        -vn -0.343730 0.727866 -0.593310
        -vn -0.075686 0.956389 -0.282083
        -vn -0.046022 0.984069 -0.171606
        -vn -0.041078 0.987304 -0.153264
        -vn -0.056276 0.976043 -0.210059
        -vn -0.130558 0.863460 -0.487197
        -vn -0.178198 0.727226 -0.662801
        -vn 0.000000 0.956236 -0.292520
        -vn 0.000000 0.984008 -0.177953
        -vn 0.000000 0.987274 -0.158879
        -vn 0.000000 0.975982 -0.217719
        -vn 0.000000 0.863277 -0.504715
        -vn 0.000000 0.726707 -0.686911
        -vn 0.075686 0.956389 -0.282083
        -vn 0.046022 0.984069 -0.171606
        -vn 0.041078 0.987304 -0.153264
        -vn 0.056276 0.976043 -0.210059
        -vn 0.130558 0.863460 -0.487197
        -vn 0.178198 0.727226 -0.662801
        -vn 0.146092 0.956511 -0.252388
        -vn 0.088839 0.984130 -0.153508
        -vn 0.079318 0.987365 -0.137059
        -vn 0.108676 0.976135 -0.187872
        -vn 0.252205 0.863887 -0.435926
        -vn 0.343730 0.727866 -0.593310
        -vn 0.206091 0.956572 -0.206091
        -vn 0.125340 0.984161 -0.125340
        -vn 0.111911 0.987396 -0.111911
        -vn 0.153356 0.976196 -0.153356
        -vn 0.355907 0.864071 -0.355907
        -vn 0.484664 0.728111 -0.484664
        -vn 0.252388 0.956511 -0.146092
        -vn 0.153508 0.984130 -0.088839
        -vn 0.137059 0.987365 -0.079318
        -vn 0.187872 0.976135 -0.108676
        -vn 0.435926 0.863887 -0.252205
        -vn 0.593310 0.727866 -0.343730
        -vn 0.282083 0.956389 -0.075686
        -vn 0.171606 0.984069 -0.046022
        -vn 0.153264 0.987304 -0.041078
        -vn 0.210059 0.976043 -0.056276
        -vn 0.487197 0.863460 -0.130558
        -vn 0.662801 0.727226 -0.178198
        -vn 0.015290 -0.999878 0.000000
        -vn 0.003296 -0.999969 0.000000
        -vn 0.015168 -0.949339 0.313852
        -vn 0.003265 -0.944395 0.328715
        -vn 0.058870 -0.998260 0.000000
        -vn 0.058046 -0.947630 0.314005
        -vn 0.158361 -0.934690 0.318155
        -vn 0.159764 -0.987152 0.000000
        -vn 0.373943 -0.860958 0.344798
        -vn 0.391583 -0.920103 0.000000
        -vn 0.726829 -0.553880 0.406049
        -vn 0.784570 -0.620014 0.000000
        -vn 0.908139 -0.082766 0.410321
        -vn 0.994995 -0.099796 0.000000
        -vn 0.011902 -0.679403 0.733634
        -vn 0.002380 -0.636219 0.771477
        -vn 0.046449 -0.674398 0.736869
        -vn 0.125980 -0.648946 0.750298
        -vn 0.270089 -0.562120 0.781671
        -vn 0.460067 -0.316263 0.829615
        -vn 0.563036 -0.041200 0.825373
        -vn 0.000153 0.004242 0.999969
        -vn -0.000519 0.113254 0.993561
        -vn 0.003510 0.014008 0.999878
        -vn 0.005921 0.035951 0.999329
        -vn -0.007813 0.058840 0.998230
        -vn -0.046510 0.041536 0.998047
        -vn -0.039155 0.003113 0.999207
        -vn -0.014161 0.682394 0.730796
        -vn -0.003204 0.727744 0.685812
        -vn -0.055361 0.680074 0.731010
        -vn -0.150029 0.655660 0.739952
        -vn -0.322520 0.565203 0.759239
        -vn -0.537645 0.315806 0.781762
        -vn -0.611530 0.029939 0.790613
        -vn -0.020569 0.949400 0.313334
        -vn -0.004273 0.954772 0.297281
        -vn -0.082705 0.944945 0.316507
        -vn -0.229591 0.914548 0.332926
        -vn -0.502335 0.785943 0.360454
        -vn -0.810633 0.443220 0.382611
        -vn -0.921232 0.039705 0.386944
        -vn -0.021851 0.999756 0.000000
        -vn -0.004517 0.999969 0.000000
        -vn -0.087649 0.996124 0.000000
        -vn -0.246223 0.969207 0.000000
        -vn -0.549211 0.835658 0.000000
        -vn -0.881039 0.472976 0.000000
        -vn -0.999115 0.041444 0.000000
        -vn -0.004273 0.954772 -0.297281
        -vn -0.020569 0.949400 -0.313334
        -vn -0.082705 0.944945 -0.316507
        -vn -0.229591 0.914548 -0.332926
        -vn -0.502335 0.785943 -0.360454
        -vn -0.810633 0.443220 -0.382611
        -vn -0.921232 0.039705 -0.386944
        -vn -0.003204 0.727744 -0.685812
        -vn -0.014161 0.682394 -0.730796
        -vn -0.055361 0.680074 -0.731010
        -vn -0.150029 0.655660 -0.739952
        -vn -0.322520 0.565203 -0.759239
        -vn -0.537645 0.315806 -0.781762
        -vn -0.611530 0.029939 -0.790613
        -vn -0.000519 0.113254 -0.993561
        -vn 0.000153 0.004242 -0.999969
        -vn 0.003510 0.014008 -0.999878
        -vn 0.005921 0.035951 -0.999329
        -vn -0.007813 0.058809 -0.998230
        -vn -0.046510 0.041536 -0.998047
        -vn -0.039155 0.003113 -0.999207
        -vn 0.002380 -0.636219 -0.771477
        -vn 0.011902 -0.679403 -0.733634
        -vn 0.046449 -0.674398 -0.736869
        -vn 0.125980 -0.648946 -0.750298
        -vn 0.270089 -0.562151 -0.781671
        -vn 0.460067 -0.316263 -0.829615
        -vn 0.563036 -0.041231 -0.825373
        -vn 0.003265 -0.944395 -0.328715
        -vn 0.015168 -0.949339 -0.313852
        -vn 0.058046 -0.947630 -0.314005
        -vn 0.158361 -0.934690 -0.318155
        -vn 0.373943 -0.860958 -0.344798
        -vn 0.726829 -0.553880 -0.406049
        -vn 0.908139 -0.082766 -0.410321
        -vn 0.890500 0.214759 0.401044
        -vn 0.972930 0.231025 0.000000
        -vn 0.836634 0.384075 0.390515
        -vn 0.912503 0.408979 0.000000
        -vn 0.765191 0.530198 0.365123
        -vn 0.828791 0.559496 0.000000
        -vn 0.671041 0.663228 0.331339
        -vn 0.718955 0.695029 0.000000
        -vn 0.549455 0.776238 0.309000
        -vn 0.580859 0.813990 0.000000
        -vn 0.461165 0.821528 0.335215
        -vn 0.497085 0.867672 0.000000
        -vn 0.559679 0.139714 0.816828
        -vn 0.528581 0.255501 0.809473
        -vn 0.494888 0.359783 0.790948
        -vn 0.445143 0.467879 0.763451
        -vn 0.376049 0.559984 0.738212
        -vn 0.287332 0.527940 0.799188
        -vn -0.024537 -0.005737 0.999664
        -vn -0.020844 -0.012207 0.999695
        -vn -0.014466 -0.014466 0.999786
        -vn -0.009796 -0.013276 0.999847
        -vn -0.014771 -0.013886 0.999786
        -vn -0.101779 -0.196661 0.975158
        -vn -0.585437 -0.154668 0.795801
        -vn -0.538499 -0.291696 0.790490
        -vn -0.487228 -0.408918 0.771599
        -vn -0.428327 -0.511948 0.744560
        -vn -0.360820 -0.584735 0.726524
        -vn -0.357311 -0.691549 0.627735
        -vn -0.889126 -0.238868 0.390332
        -vn -0.807001 -0.448500 0.384075
        -vn -0.700980 -0.613392 0.363750
        -vn -0.590442 -0.733757 0.336009
        -vn -0.486190 -0.814966 0.315256
        -vn -0.440138 -0.855586 0.272439
        -vn -0.965453 -0.260506 0.000000
        -vn -0.872097 -0.489273 0.000000
        -vn -0.748253 -0.663381 0.000000
        -vn -0.621784 -0.783166 0.000000
        -vn -0.507614 -0.861568 0.000000
        -vn -0.456954 -0.889462 0.000000
        -vn -0.889126 -0.238868 -0.390332
        -vn -0.807001 -0.448531 -0.384075
        -vn -0.700980 -0.613392 -0.363750
        -vn -0.590442 -0.733757 -0.336009
        -vn -0.486190 -0.814966 -0.315256
        -vn -0.440138 -0.855586 -0.272439
        -vn -0.585437 -0.154668 -0.795801
        -vn -0.538499 -0.291696 -0.790490
        -vn -0.487228 -0.408918 -0.771599
        -vn -0.428327 -0.511948 -0.744560
        -vn -0.360820 -0.584735 -0.726524
        -vn -0.357311 -0.691549 -0.627705
        -vn -0.024537 -0.005737 -0.999664
        -vn -0.020844 -0.012238 -0.999695
        -vn -0.014466 -0.014466 -0.999786
        -vn -0.009766 -0.013276 -0.999847
        -vn -0.014771 -0.013916 -0.999786
        -vn -0.101779 -0.196661 -0.975158
        -vn 0.559679 0.139714 -0.816828
        -vn 0.528581 0.255501 -0.809473
        -vn 0.494888 0.359783 -0.790948
        -vn 0.445143 0.467879 -0.763451
        -vn 0.376049 0.559984 -0.738212
        -vn 0.287332 0.527940 -0.799188
        -vn 0.890500 0.214759 -0.401044
        -vn 0.836634 0.384075 -0.390515
        -vn 0.765191 0.530198 -0.365123
        -vn 0.671041 0.663228 -0.331339
        -vn 0.549455 0.776238 -0.309000
        -vn 0.461165 0.821528 -0.335215
        -vn -0.149937 0.988678 0.000000
        -vn -0.137028 0.872402 0.469131
        -vn -0.297769 0.840358 0.452895
        -vn -0.350505 0.936552 0.000000
        -vn -0.617512 0.663961 0.421613
        -vn -0.715506 0.698569 0.000000
        -vn -0.801324 0.450209 0.393872
        -vn -0.900845 0.434065 0.000000
        -vn -0.828028 0.379803 0.412397
        -vn -0.929289 0.369274 0.000000
        -vn -0.729179 0.503464 0.463393
        -vn -0.857875 0.513810 0.000000
        -vn -0.663076 0.748527 0.000000
        -vn -0.531449 0.686514 0.496170
        -vn -0.066713 0.491440 0.868313
        -vn -0.117893 0.503159 0.856105
        -vn -0.254341 0.474349 0.842769
        -vn -0.411115 0.399182 0.819483
        -vn -0.459395 0.346446 0.817835
        -vn -0.385876 0.395734 0.833338
        -vn -0.270669 0.487838 0.829890
        -vn 0.062716 -0.043458 0.997070
        -vn 0.135929 -0.002472 0.990692
        -vn 0.247963 0.095187 0.964049
        -vn 0.209296 0.170660 0.962828
        -vn 0.096194 0.178625 0.979186
        -vn 0.009552 0.154332 0.987945
        -vn -0.000122 0.151952 0.988372
        -vn 0.202582 -0.542894 0.814966
        -vn 0.360088 -0.479232 0.800378
        -vn 0.611988 -0.282235 0.738762
        -vn 0.679220 -0.106754 0.726096
        -vn 0.583911 -0.078524 0.807978
        -vn 0.402722 -0.205237 0.891995
        -vn 0.279519 -0.338694 0.898404
        -vn 0.294107 -0.855037 0.427015
        -vn 0.488418 -0.768700 0.412915
        -vn 0.784570 -0.501511 0.364544
        -vn 0.893918 -0.279611 0.350291
        -vn 0.861415 -0.285287 0.420179
        -vn 0.679373 -0.540422 0.496323
        -vn 0.458357 -0.754540 0.469588
        -vn 0.320780 -0.947142 0.000000
        -vn 0.525101 -0.851009 0.000000
        -vn 0.827570 -0.561327 0.000000
        -vn 0.943419 -0.331523 0.000000
        -vn 0.933561 -0.358409 0.000000
        -vn 0.756340 -0.654134 0.000000
        -vn 0.491928 -0.870602 0.000092
        -vn 0.294107 -0.855037 -0.427015
        -vn 0.488418 -0.768700 -0.412915
        -vn 0.784570 -0.501511 -0.364544
        -vn 0.893918 -0.279611 -0.350291
        -vn 0.861385 -0.285287 -0.420179
        -vn 0.679373 -0.540422 -0.496323
        -vn 0.457839 -0.755608 -0.468368
        -vn 0.202582 -0.542894 -0.814966
        -vn 0.360088 -0.479232 -0.800378
        -vn 0.611988 -0.282235 -0.738762
        -vn 0.679220 -0.106754 -0.726096
        -vn 0.583911 -0.078524 -0.807978
        -vn 0.402722 -0.205237 -0.891995
        -vn 0.279153 -0.342235 -0.897153
        -vn 0.062716 -0.043458 -0.997070
        -vn 0.135929 -0.002472 -0.990692
        -vn 0.247963 0.095187 -0.964049
        -vn 0.209296 0.170629 -0.962828
        -vn 0.096194 0.178625 -0.979186
        -vn 0.009552 0.154332 -0.987945
        -vn -0.000458 0.149358 -0.988769
        -vn -0.066713 0.491440 -0.868313
        -vn -0.117893 0.503159 -0.856105
        -vn -0.254341 0.474319 -0.842769
        -vn -0.411115 0.399182 -0.819514
        -vn -0.459395 0.346446 -0.817835
        -vn -0.385876 0.395734 -0.833338
        -vn -0.271035 0.487136 -0.830164
        -vn -0.137028 0.872402 -0.469131
        -vn -0.297769 0.840358 -0.452895
        -vn -0.617512 0.663961 -0.421613
        -vn -0.801324 0.450209 -0.393872
        -vn -0.828028 0.379803 -0.412397
        -vn -0.729209 0.503464 -0.463393
        -vn -0.531541 0.686453 -0.496200
        -vn -0.480697 0.876858 0.000000
        -vn -0.394635 0.815363 0.423536
        -vn -0.320750 0.947142 0.000092
        -vn -0.255287 0.921964 0.291086
        -vn 0.002686 0.999969 -0.000732
        -vn -0.007172 0.999939 -0.007599
        -vn 0.366832 0.704398 -0.607624
        -vn 0.853236 0.521226 -0.016388
        -vn 0.567492 -0.154088 -0.808802
        -vn 0.803766 -0.594409 -0.024964
        -vn 0.580920 -0.584490 -0.566424
        -vn 0.673757 -0.738639 -0.020966
        -vn -0.206824 0.638203 0.741539
        -vn -0.129490 0.862056 0.489944
        -vn -0.034486 0.999023 0.026704
        -vn 0.041597 0.871334 -0.488876
        -vn 0.103488 0.553880 -0.826136
        -vn 0.189642 0.174200 -0.966247
        -vn 0.020112 0.322611 0.946287
        -vn 0.021943 0.748894 0.662282
        -vn -0.025697 0.995392 0.092166
        -vn -0.056551 0.931608 -0.358989
        -vn -0.070711 0.782006 -0.619190
        -vn -0.066408 0.651509 -0.755699
        -vn 0.281747 -0.174993 0.943388
        -vn 0.303903 0.444136 0.842830
        -vn 0.035279 0.983856 0.175329
        -vn -0.109928 0.953551 -0.280435
        -vn -0.149571 0.847682 -0.508927
        -vn -0.145634 0.777520 -0.611744
        -vn 0.467238 -0.683218 0.561144
        -vn 0.699515 0.004364 0.714560
        -vn 0.354900 0.892758 0.277444
        -vn -0.174383 0.969054 -0.174596
        -vn -0.252998 0.894314 -0.368938
        -vn -0.191443 0.807947 -0.557237
        -vn 0.495346 -0.868679 0.001587
        -vn 0.933897 -0.357311 0.011017
        -vn 0.704215 0.709830 0.013337
        -vn -0.205634 0.978576 -0.006623
        -vn -0.322367 0.945708 -0.041169
        -vn -0.314951 0.916288 -0.247322
        -vn 0.459120 -0.703757 -0.542100
        -vn 0.693655 -0.096530 -0.713767
        -vn 0.408673 0.848415 -0.336344
        -vn -0.198248 0.963439 0.180151
        -vn -0.306833 0.888516 0.341075
        -vn -0.335978 0.808863 0.482498
        -vn 0.277047 -0.215796 -0.936277
        -vn 0.306192 0.349864 -0.885311
        -vn 0.056246 0.971099 -0.231819
        -vn -0.146733 0.912168 0.382611
        -vn -0.202612 0.700797 0.683950
        -vn -0.159368 0.444777 0.881314
        -vn 0.016907 0.300088 -0.953734
        -vn 0.019349 0.701865 -0.712027
        -vn -0.023713 0.995361 -0.093081
        -vn -0.047395 0.829371 0.556658
        -vn -0.015259 0.386608 0.922086
        -vn 0.080721 0.001404 0.996704
        -vn -0.209967 0.632160 -0.745811
        -vn -0.137394 0.849117 -0.509995
        -vn -0.023438 0.999695 -0.004883
        -vn 0.120426 0.724540 0.678579
        -vn 0.260750 0.006531 0.965361
        -vn 0.341502 -0.385510 0.857143
        -vn -0.395489 0.814814 -0.423841
        -vn -0.257942 0.920621 -0.293039
        -vn 0.005005 0.999908 0.011628
        -vn 0.466628 0.599811 0.649983
        -vn 0.621937 -0.400861 0.672628
        -vn 0.584826 -0.661397 0.469558
        -vn 0.363842 0.931455 0.000000
        -vn 0.000000 1.000000 0.000000
        -vn 0.351451 0.931516 0.093509
        -vn 0.314432 0.931791 0.181280
        -vn 0.256386 0.931913 0.256386
        -vn 0.181280 0.931791 0.314432
        -vn 0.093509 0.931516 0.351451
        -vn 0.000000 0.931455 0.363842
        -vn -0.093509 0.931516 0.351451
        -vn -0.181280 0.931791 0.314432
        -vn -0.256386 0.931913 0.256386
        -vn -0.314432 0.931791 0.181280
        -vn -0.351451 0.931516 0.093509
        -vn -0.363842 0.931455 0.000000
        -vn -0.351451 0.931516 -0.093509
        -vn -0.314432 0.931791 -0.181280
        -vn -0.256417 0.931913 -0.256417
        -vn -0.181280 0.931791 -0.314432
        -vn -0.093509 0.931516 -0.351451
        -vn 0.000000 0.931455 -0.363842
        -vn 0.093509 0.931516 -0.351451
        -vn 0.181280 0.931791 -0.314432
        -vn 0.256417 0.931913 -0.256417
        -vn 0.314432 0.931791 -0.181280
        -vn 0.351451 0.931516 -0.093509
        -vn 0.935423 0.249763 0.250160
        -vn 0.968261 0.249916 0.000000
        -vn 0.813959 -0.538713 0.217322
        -vn 0.842860 -0.538102 0.000000
        -vn 0.759484 -0.618030 0.202887
        -vn 0.786767 -0.617206 0.000000
        -vn 0.801569 -0.558184 0.214148
        -vn 0.830195 -0.557421 0.000000
        -vn 0.838404 0.249825 0.484359
        -vn 0.729026 -0.539720 0.420881
        -vn 0.680013 -0.619098 0.392712
        -vn 0.717765 -0.559343 0.414594
        -vn 0.684652 0.249886 0.684652
        -vn 0.595050 -0.540147 0.595050
        -vn 0.555040 -0.619526 0.555040
        -vn 0.585894 -0.559862 0.585894
        -vn 0.484359 0.249825 0.838404
        -vn 0.420881 -0.539720 0.729026
        -vn 0.392712 -0.619098 0.680013
        -vn 0.414594 -0.559343 0.717765
        -vn 0.250160 0.249763 0.935423
        -vn 0.217322 -0.538713 0.813959
        -vn 0.202887 -0.618030 0.759514
        -vn 0.214148 -0.558184 0.801569
        -vn 0.000000 0.249916 0.968261
        -vn 0.000000 -0.538102 0.842860
        -vn 0.000000 -0.617206 0.786767
        -vn 0.000000 -0.557421 0.830195
        -vn -0.250160 0.249763 0.935423
        -vn -0.217322 -0.538713 0.813959
        -vn -0.202887 -0.618030 0.759514
        -vn -0.214148 -0.558184 0.801569
        -vn -0.484359 0.249825 0.838404
        -vn -0.420881 -0.539720 0.729026
        -vn -0.392712 -0.619098 0.680013
        -vn -0.414594 -0.559343 0.717765
        -vn -0.684652 0.249886 0.684652
        -vn -0.595050 -0.540147 0.595050
        -vn -0.555040 -0.619526 0.555040
        -vn -0.585894 -0.559862 0.585894
        -vn -0.838404 0.249825 0.484359
        -vn -0.729026 -0.539720 0.420881
        -vn -0.680013 -0.619098 0.392712
        -vn -0.717765 -0.559343 0.414594
        -vn -0.935423 0.249763 0.250160
        -vn -0.813959 -0.538713 0.217322
        -vn -0.759484 -0.618030 0.202887
        -vn -0.801569 -0.558184 0.214148
        -vn -0.968261 0.249916 0.000000
        -vn -0.842860 -0.538102 0.000000
        -vn -0.786767 -0.617206 0.000000
        -vn -0.830195 -0.557421 0.000000
        -vn -0.935423 0.249763 -0.250160
        -vn -0.813959 -0.538713 -0.217322
        -vn -0.759484 -0.618030 -0.202887
        -vn -0.801569 -0.558184 -0.214148
        -vn -0.838404 0.249825 -0.484359
        -vn -0.729026 -0.539720 -0.420881
        -vn -0.680013 -0.619098 -0.392712
        -vn -0.717765 -0.559343 -0.414594
        -vn -0.684652 0.249886 -0.684652
        -vn -0.595050 -0.540147 -0.595050
        -vn -0.555040 -0.619526 -0.555040
        -vn -0.585864 -0.559862 -0.585894
        -vn -0.484359 0.249825 -0.838404
        -vn -0.420881 -0.539720 -0.729026
        -vn -0.392712 -0.619098 -0.680013
        -vn -0.414594 -0.559343 -0.717765
        -vn -0.250160 0.249763 -0.935423
        -vn -0.217322 -0.538713 -0.813959
        -vn -0.202887 -0.618030 -0.759484
        -vn -0.214148 -0.558214 -0.801569
        -vn 0.000000 0.249916 -0.968261
        -vn 0.000000 -0.538102 -0.842860
        -vn 0.000000 -0.617206 -0.786767
        -vn 0.000000 -0.557421 -0.830195
        -vn 0.250160 0.249763 -0.935423
        -vn 0.217322 -0.538713 -0.813959
        -vn 0.202887 -0.618030 -0.759484
        -vn 0.214148 -0.558214 -0.801569
        -vn 0.484359 0.249825 -0.838404
        -vn 0.420881 -0.539720 -0.729026
        -vn 0.392712 -0.619098 -0.680013
        -vn 0.414594 -0.559343 -0.717765
        -vn 0.684652 0.249886 -0.684652
        -vn 0.595050 -0.540147 -0.595050
        -vn 0.555040 -0.619526 -0.555040
        -vn 0.585864 -0.559862 -0.585894
        -vn 0.838404 0.249825 -0.484359
        -vn 0.729026 -0.539720 -0.420881
        -vn 0.680013 -0.619098 -0.392712
        -vn 0.717765 -0.559343 -0.414594
        -vn 0.935423 0.249763 -0.250160
        -vn 0.813959 -0.538713 -0.217322
        -vn 0.759484 -0.618030 -0.202887
        -vn 0.801569 -0.558184 -0.214148
        -s 1
        -f 1//1 2//2 3//3
        -f 3//3 2//2 4//4
        -f 4//4 2//2 5//5
        -f 5//5 2//2 6//6
        -f 6//6 2//2 7//7
        -f 7//7 2//2 8//8
        -f 8//8 2//2 9//9
        -f 9//9 2//2 10//10
        -f 10//10 2//2 11//11
        -f 11//11 2//2 12//12
        -f 12//12 2//2 13//13
        -f 13//13 2//2 14//14
        -f 14//14 2//2 15//15
        -f 15//15 2//2 16//16
        -f 16//16 2//2 17//17
        -f 17//17 2//2 18//18
        -f 18//18 2//2 19//19
        -f 19//19 2//2 20//20
        -f 20//20 2//2 21//21
        -f 21//21 2//2 22//22
        -f 22//22 2//2 23//23
        -f 23//23 2//2 24//24
        -f 24//24 2//2 25//25
        -f 2//2 1//1 25//25
        -f 26//26 27//27 28//28
        -f 28//28 29//29 26//26
        -f 29//29 28//28 30//30
        -f 31//31 30//30 28//28
        -f 30//30 31//31 32//32
        -f 33//33 32//32 31//31
        -f 34//34 35//35 33//33
        -f 32//32 33//33 35//35
        -f 36//36 37//37 34//34
        -f 35//35 34//34 37//37
        -f 38//38 39//39 36//36
        -f 37//37 36//36 39//39
        -f 27//27 40//40 41//41
        -f 41//41 28//28 27//27
        -f 28//28 41//41 31//31
        -f 42//42 31//31 41//41
        -f 31//31 42//42 33//33
        -f 43//43 33//33 42//42
        -f 44//44 34//34 43//43
        -f 33//33 43//43 34//34
        -f 45//45 36//36 44//44
        -f 34//34 44//44 36//36
        -f 46//46 38//38 45//45
        -f 36//36 45//45 38//38
        -f 40//40 47//47 48//48
        -f 48//48 41//41 40//40
        -f 41//41 48//48 42//42
        -f 49//49 42//42 48//48
        -f 42//42 49//49 43//43
        -f 50//50 43//43 49//49
        -f 51//51 44//44 50//50
        -f 43//43 50//50 44//44
        -f 52//52 45//45 51//51
        -f 44//44 51//51 45//45
        -f 53//53 46//46 52//52
        -f 45//45 52//52 46//46
        -f 47//47 54//54 48//48
        -f 55//55 48//48 54//54
        -f 48//48 55//55 49//49
        -f 56//56 49//49 55//55
        -f 49//49 56//56 57//57
        -f 57//57 50//50 49//49
        -f 58//58 51//51 50//50
        -f 50//50 57//57 58//58
        -f 59//59 52//52 51//51
        -f 51//51 58//58 59//59
        -f 60//60 53//53 52//52
        -f 52//52 59//59 60//60
        -f 54//54 61//61 55//55
        -f 62//62 55//55 61//61
        -f 55//55 62//62 63//63
        -f 63//63 56//56 55//55
        -f 56//56 63//63 64//64
        -f 64//64 57//57 56//56
        -f 65//65 58//58 57//57
        -f 57//57 64//64 65//65
        -f 66//66 59//59 58//58
        -f 58//58 65//65 66//66
        -f 67//67 60//60 59//59
        -f 59//59 66//66 67//67
        -f 61//61 68//68 62//62
        -f 69//69 62//62 68//68
        -f 62//62 69//69 70//70
        -f 70//70 63//63 62//62
        -f 63//63 70//70 71//71
        -f 71//71 64//64 63//63
        -f 72//72 65//65 64//64
        -f 64//64 71//71 72//72
        -f 73//73 66//66 65//65
        -f 65//65 72//72 73//73
        -f 74//74 67//67 66//66
        -f 66//66 73//73 74//74
        -f 68//68 75//75 76//76
        -f 76//76 69//69 68//68
        -f 69//69 76//76 70//70
        -f 77//77 70//70 76//76
        -f 70//70 77//77 71//71
        -f 78//78 71//71 77//77
        -f 79//79 72//72 78//78
        -f 71//71 78//78 72//72
        -f 80//80 73//73 79//79
        -f 72//72 79//79 73//73
        -f 81//81 74//74 80//80
        -f 73//73 80//80 74//74
        -f 75//75 82//82 83//83
        -f 83//83 76//76 75//75
        -f 76//76 83//83 77//77
        -f 84//84 77//77 83//83
        -f 77//77 84//84 78//78
        -f 85//85 78//78 84//84
        -f 86//86 79//79 85//85
        -f 78//78 85//85 79//79
        -f 87//87 80//80 86//86
        -f 79//79 86//86 80//80
        -f 88//88 81//81 87//87
        -f 80//80 87//87 81//81
        -f 82//82 89//89 90//90
        -f 90//90 83//83 82//82
        -f 83//83 90//90 91//91
        -f 91//91 84//84 83//83
        -f 84//84 91//91 85//85
        -f 92//92 85//85 91//91
        -f 93//93 86//86 92//92
        -f 85//85 92//92 86//86
        -f 94//94 87//87 93//93
        -f 86//86 93//93 87//87
        -f 95//95 88//88 94//94
        -f 87//87 94//94 88//88
        -f 89//89 96//96 90//90
        -f 97//97 90//90 96//96
        -f 90//90 97//97 98//98
        -f 98//98 91//91 90//90
        -f 91//91 98//98 99//99
        -f 99//99 92//92 91//91
        -f 100//100 93//93 92//92
        -f 92//92 99//99 100//100
        -f 101//101 94//94 93//93
        -f 93//93 100//100 101//101
        -f 102//102 95//95 94//94
        -f 94//94 101//101 102//102
        -f 96//96 103//103 97//97
        -f 104//104 97//97 103//103
        -f 97//97 104//104 105//105
        -f 105//105 98//98 97//97
        -f 98//98 105//105 106//106
        -f 106//106 99//99 98//98
        -f 107//107 100//100 99//99
        -f 99//99 106//106 107//107
        -f 108//108 101//101 100//100
        -f 100//100 107//107 108//108
        -f 109//109 102//102 101//101
        -f 101//101 108//108 109//109
        -f 103//103 110//110 104//104
        -f 111//111 104//104 110//110
        -f 104//104 111//111 112//112
        -f 112//112 105//105 104//104
        -f 105//105 112//112 113//113
        -f 113//113 106//106 105//105
        -f 114//114 107//107 106//106
        -f 106//106 113//113 114//114
        -f 115//115 108//108 107//107
        -f 107//107 114//114 115//115
        -f 116//116 109//109 108//108
        -f 108//108 115//115 116//116
        -f 110//110 117//117 118//118
        -f 118//118 111//111 110//110
        -f 111//111 118//118 119//119
        -f 119//119 112//112 111//111
        -f 112//112 119//119 113//113
        -f 120//120 113//113 119//119
        -f 121//121 114//114 120//120
        -f 113//113 120//120 114//114
        -f 122//122 115//115 121//121
        -f 114//114 121//121 115//115
        -f 123//123 116//116 122//122
        -f 115//115 122//122 116//116
        -f 117//117 124//124 125//125
        -f 125//125 118//118 117//117
        -f 118//118 125//125 119//119
        -f 126//126 119//119 125//125
        -f 119//119 126//126 120//120
        -f 127//127 120//120 126//126
        -f 128//128 121//121 127//127
        -f 120//120 127//127 121//121
        -f 129//129 122//122 128//128
        -f 121//121 128//128 122//122
        -f 130//130 123//123 129//129
        -f 122//122 129//129 123//123
        -f 124//124 131//131 132//132
        -f 132//132 125//125 124//124
        -f 125//125 132//132 133//133
        -f 133//133 126//126 125//125
        -f 126//126 133//133 127//127
        -f 134//134 127//127 133//133
        -f 135//135 128//128 134//134
        -f 127//127 134//134 128//128
        -f 136//136 129//129 135//135
        -f 128//128 135//135 129//129
        -f 137//137 130//130 136//136
        -f 129//129 136//136 130//130
        -f 131//131 138//138 132//132
        -f 139//139 132//132 138//138
        -f 132//132 139//139 140//140
        -f 140//140 133//133 132//132
        -f 133//133 140//140 141//141
        -f 141//141 134//134 133//133
        -f 142//142 135//135 134//134
        -f 134//134 141//141 142//142
        -f 143//143 136//136 135//135
        -f 135//135 142//142 143//143
        -f 144//144 137//137 136//136
        -f 136//136 143//143 144//144
        -f 138//138 145//145 139//139
        -f 146//146 139//139 145//145
        -f 139//139 146//146 147//147
        -f 147//147 140//140 139//139
        -f 140//140 147//147 148//148
        -f 148//148 141//141 140//140
        -f 149//149 142//142 141//141
        -f 141//141 148//148 149//149
        -f 150//150 143//143 142//142
        -f 142//142 149//149 150//150
        -f 151//151 144//144 143//143
        -f 143//143 150//150 151//151
        -f 145//145 152//152 146//146
        -f 153//153 146//146 152//152
        -f 146//146 153//153 154//154
        -f 154//154 147//147 146//146
        -f 147//147 154//154 155//155
        -f 155//155 148//148 147//147
        -f 156//156 149//149 148//148
        -f 148//148 155//155 156//156
        -f 157//157 150//150 149//149
        -f 149//149 156//156 157//157
        -f 158//158 151//151 150//150
        -f 150//150 157//157 158//158
        -f 152//152 159//159 160//160
        -f 160//160 153//153 152//152
        -f 153//153 160//160 154//154
        -f 161//161 154//154 160//160
        -f 154//154 161//161 155//155
        -f 162//162 155//155 161//161
        -f 163//163 156//156 162//162
        -f 155//155 162//162 156//156
        -f 164//164 157//157 163//163
        -f 156//156 163//163 157//157
        -f 165//165 158//158 164//164
        -f 157//157 164//164 158//158
        -f 159//159 166//166 167//167
        -f 167//167 160//160 159//159
        -f 160//160 167//167 161//161
        -f 168//168 161//161 167//167
        -f 161//161 168//168 162//162
        -f 169//169 162//162 168//168
        -f 170//170 163//163 169//169
        -f 162//162 169//169 163//163
        -f 171//171 164//164 170//170
        -f 163//163 170//170 164//164
        -f 172//172 165//165 171//171
        -f 164//164 171//171 165//165
        -f 166//166 173//173 174//174
        -f 174//174 167//167 166//166
        -f 167//167 174//174 168//168
        -f 175//175 168//168 174//174
        -f 168//168 175//175 169//169
        -f 176//176 169//169 175//175
        -f 177//177 170//170 176//176
        -f 169//169 176//176 170//170
        -f 178//178 171//171 177//177
        -f 170//170 177//177 171//171
        -f 179//179 172//172 178//178
        -f 171//171 178//178 172//172
        -f 173//173 180//180 174//174
        -f 181//181 174//174 180//180
        -f 174//174 181//181 175//175
        -f 182//182 175//175 181//181
        -f 175//175 182//182 183//183
        -f 183//183 176//176 175//175
        -f 184//184 177//177 176//176
        -f 176//176 183//183 184//184
        -f 185//185 178//178 177//177
        -f 177//177 184//184 185//185
        -f 186//186 179//179 178//178
        -f 178//178 185//185 186//186
        -f 180//180 187//187 181//181
        -f 188//188 181//181 187//187
        -f 181//181 188//188 189//189
        -f 189//189 182//182 181//181
        -f 182//182 189//189 190//190
        -f 190//190 183//183 182//182
        -f 191//191 184//184 183//183
        -f 183//183 190//190 191//191
        -f 192//192 185//185 184//184
        -f 184//184 191//191 192//192
        -f 193//193 186//186 185//185
        -f 185//185 192//192 193//193
        -f 187//187 26//26 188//188
        -f 29//29 188//188 26//26
        -f 188//188 29//29 189//189
        -f 30//30 189//189 29//29
        -f 189//189 30//30 32//32
        -f 32//32 190//190 189//189
        -f 35//35 191//191 190//190
        -f 190//190 32//32 35//35
        -f 37//37 192//192 191//191
        -f 191//191 35//35 37//37
        -f 39//39 193//193 192//192
        -f 192//192 37//37 39//39
        -f 194//194 195//195 38//38
        -f 39//39 38//38 195//195
        -f 196//196 197//197 194//194
        -f 195//195 194//194 197//197
        -f 198//198 199//199 196//196
        -f 197//197 196//196 199//199
        -f 200//200 201//201 198//198
        -f 199//199 198//198 201//201
        -f 202//202 203//203 200//200
        -f 201//201 200//200 203//203
        -f 204//204 205//205 202//202
        -f 203//203 202//202 205//205
        -f 206//206 194//194 46//46
        -f 38//38 46//46 194//194
        -f 207//207 196//196 206//206
        -f 194//194 206//206 196//196
        -f 208//208 198//198 207//207
        -f 196//196 207//207 198//198
        -f 209//209 200//200 208//208
        -f 198//198 208//208 200//200
        -f 210//210 202//202 209//209
        -f 200//200 209//209 202//202
        -f 211//211 204//204 210//210
        -f 202//202 210//210 204//204
        -f 212//212 206//206 53//53
        -f 46//46 53//53 206//206
        -f 213//213 207//207 212//212
        -f 206//206 212//212 207//207
        -f 214//214 208//208 213//213
        -f 207//207 213//213 208//208
        -f 215//215 209//209 214//214
        -f 208//208 214//214 209//209
        -f 216//216 210//210 215//215
        -f 209//209 215//215 210//210
        -f 217//217 211//211 216//216
        -f 210//210 216//216 211//211
        -f 218//218 212//212 53//53
        -f 53//53 60//60 218//218
        -f 219//219 213//213 212//212
        -f 212//212 218//218 219//219
        -f 220//220 214//214 213//213
        -f 213//213 219//219 220//220
        -f 221//221 215//215 214//214
        -f 214//214 220//220 221//221
        -f 222//222 216//216 215//215
        -f 215//215 221//221 222//222
        -f 223//223 217//217 216//216
        -f 216//216 222//222 223//223
        -f 224//224 218//218 60//60
        -f 60//60 67//67 224//224
        -f 225//225 219//219 218//218
        -f 218//218 224//224 225//225
        -f 226//226 220//220 219//219
        -f 219//219 225//225 226//226
        -f 227//227 221//221 220//220
        -f 220//220 226//226 227//227
        -f 228//228 222//222 221//221
        -f 221//221 227//227 228//228
        -f 229//229 223//223 222//222
        -f 222//222 228//228 229//229
        -f 230//230 224//224 67//67
        -f 67//67 74//74 230//230
        -f 231//231 225//225 224//224
        -f 224//224 230//230 231//231
        -f 232//232 226//226 225//225
        -f 225//225 231//231 232//232
        -f 233//233 227//227 226//226
        -f 226//226 232//232 233//233
        -f 234//234 228//228 227//227
        -f 227//227 233//233 234//234
        -f 235//235 229//229 228//228
        -f 228//228 234//234 235//235
        -f 236//236 230//230 81//81
        -f 74//74 81//81 230//230
        -f 237//237 231//231 236//236
        -f 230//230 236//236 231//231
        -f 238//238 232//232 237//237
        -f 231//231 237//237 232//232
        -f 239//239 233//233 238//238
        -f 232//232 238//238 233//233
        -f 240//240 234//234 239//239
        -f 233//233 239//239 234//234
        -f 241//241 235//235 240//240
        -f 234//234 240//240 235//235
        -f 242//242 236//236 88//88
        -f 81//81 88//88 236//236
        -f 243//243 237//237 242//242
        -f 236//236 242//242 237//237
        -f 244//244 238//238 243//243
        -f 237//237 243//243 238//238
        -f 245//245 239//239 244//244
        -f 238//238 244//244 239//239
        -f 246//246 240//240 245//245
        -f 239//239 245//245 240//240
        -f 247//247 241//241 246//246
        -f 240//240 246//246 241//241
        -f 248//248 242//242 95//95
        -f 88//88 95//95 242//242
        -f 249//249 243//243 248//248
        -f 242//242 248//248 243//243
        -f 250//250 244//244 249//249
        -f 243//243 249//249 244//244
        -f 251//251 245//245 250//250
        -f 244//244 250//250 245//245
        -f 252//252 246//246 251//251
        -f 245//245 251//251 246//246
        -f 253//253 247//247 252//252
        -f 246//246 252//252 247//247
        -f 254//254 248//248 95//95
        -f 95//95 102//102 254//254
        -f 255//255 249//249 248//248
        -f 248//248 254//254 255//255
        -f 256//256 250//250 249//249
        -f 249//249 255//255 256//256
        -f 257//257 251//251 250//250
        -f 250//250 256//256 257//257
        -f 258//258 252//252 251//251
        -f 251//251 257//257 258//258
        -f 259//259 253//253 252//252
        -f 252//252 258//258 259//259
        -f 260//260 254//254 102//102
        -f 102//102 109//109 260//260
        -f 261//261 255//255 254//254
        -f 254//254 260//260 261//261
        -f 262//262 256//256 255//255
        -f 255//255 261//261 262//262
        -f 263//263 257//257 256//256
        -f 256//256 262//262 263//263
        -f 264//264 258//258 257//257
        -f 257//257 263//263 264//264
        -f 265//265 259//259 258//258
        -f 258//258 264//264 265//265
        -f 266//266 260//260 109//109
        -f 109//109 116//116 266//266
        -f 267//267 261//261 260//260
        -f 260//260 266//266 267//267
        -f 268//268 262//262 261//261
        -f 261//261 267//267 268//268
        -f 269//269 263//263 262//262
        -f 262//262 268//268 269//269
        -f 270//270 264//264 263//263
        -f 263//263 269//269 270//270
        -f 271//271 265//265 264//264
        -f 264//264 270//270 271//271
        -f 272//272 266//266 123//123
        -f 116//116 123//123 266//266
        -f 273//273 267//267 272//272
        -f 266//266 272//272 267//267
        -f 274//274 268//268 273//273
        -f 267//267 273//273 268//268
        -f 275//275 269//269 274//274
        -f 268//268 274//274 269//269
        -f 276//276 270//270 275//275
        -f 269//269 275//275 270//270
        -f 277//277 271//271 276//276
        -f 270//270 276//276 271//271
        -f 278//278 272//272 130//130
        -f 123//123 130//130 272//272
        -f 279//279 273//273 278//278
        -f 272//272 278//278 273//273
        -f 280//280 274//274 279//279
        -f 273//273 279//279 274//274
        -f 281//281 275//275 280//280
        -f 274//274 280//280 275//275
        -f 282//282 276//276 281//281
        -f 275//275 281//281 276//276
        -f 283//283 277//277 282//282
        -f 276//276 282//282 277//277
        -f 284//284 278//278 137//137
        -f 130//130 137//137 278//278
        -f 285//285 279//279 284//284
        -f 278//278 284//284 279//279
        -f 286//286 280//280 285//285
        -f 279//279 285//285 280//280
        -f 287//287 281//281 286//286
        -f 280//280 286//286 281//281
        -f 288//288 282//282 287//287
        -f 281//281 287//287 282//282
        -f 289//289 283//283 288//288
        -f 282//282 288//288 283//283
        -f 290//290 284//284 137//137
        -f 137//137 144//144 290//290
        -f 291//291 285//285 284//284
        -f 284//284 290//290 291//291
        -f 292//292 286//286 285//285
        -f 285//285 291//291 292//292
        -f 293//293 287//287 286//286
        -f 286//286 292//292 293//293
        -f 294//294 288//288 287//287
        -f 287//287 293//293 294//294
        -f 295//295 289//289 288//288
        -f 288//288 294//294 295//295
        -f 296//296 290//290 144//144
        -f 144//144 151//151 296//296
        -f 297//297 291//291 290//290
        -f 290//290 296//296 297//297
        -f 298//298 292//292 291//291
        -f 291//291 297//297 298//298
        -f 299//299 293//293 292//292
        -f 292//292 298//298 299//299
        -f 300//300 294//294 293//293
        -f 293//293 299//299 300//300
        -f 301//301 295//295 294//294
        -f 294//294 300//300 301//301
        -f 302//302 296//296 151//151
        -f 151//151 158//158 302//302
        -f 303//303 297//297 296//296
        -f 296//296 302//302 303//303
        -f 304//304 298//298 297//297
        -f 297//297 303//303 304//304
        -f 305//305 299//299 298//298
        -f 298//298 304//304 305//305
        -f 306//306 300//300 299//299
        -f 299//299 305//305 306//306
        -f 307//307 301//301 300//300
        -f 300//300 306//306 307//307
        -f 308//308 302//302 165//165
        -f 158//158 165//165 302//302
        -f 309//309 303//303 308//308
        -f 302//302 308//308 303//303
        -f 310//310 304//304 309//309
        -f 303//303 309//309 304//304
        -f 311//311 305//305 310//310
        -f 304//304 310//310 305//305
        -f 312//312 306//306 311//311
        -f 305//305 311//311 306//306
        -f 313//313 307//307 312//312
        -f 306//306 312//312 307//307
        -f 314//314 308//308 172//172
        -f 165//165 172//172 308//308
        -f 315//315 309//309 314//314
        -f 308//308 314//314 309//309
        -f 316//316 310//310 315//315
        -f 309//309 315//315 310//310
        -f 317//317 311//311 316//316
        -f 310//310 316//316 311//311
        -f 318//318 312//312 317//317
        -f 311//311 317//317 312//312
        -f 319//319 313//313 318//318
        -f 312//312 318//318 313//313
        -f 320//320 314//314 179//179
        -f 172//172 179//179 314//314
        -f 321//321 315//315 320//320
        -f 314//314 320//320 315//315
        -f 322//322 316//316 321//321
        -f 315//315 321//321 316//316
        -f 323//323 317//317 322//322
        -f 316//316 322//322 317//317
        -f 324//324 318//318 323//323
        -f 317//317 323//323 318//318
        -f 325//325 319//319 324//324
        -f 318//318 324//324 319//319
        -f 326//326 320//320 179//179
        -f 179//179 186//186 326//326
        -f 327//327 321//321 320//320
        -f 320//320 326//326 327//327
        -f 328//328 322//322 321//321
        -f 321//321 327//327 328//328
        -f 329//329 323//323 322//322
        -f 322//322 328//328 329//329
        -f 330//330 324//324 323//323
        -f 323//323 329//329 330//330
        -f 331//331 325//325 324//324
        -f 324//324 330//330 331//331
        -f 332//332 326//326 186//186
        -f 186//186 193//193 332//332
        -f 333//333 327//327 326//326
        -f 326//326 332//332 333//333
        -f 334//334 328//328 327//327
        -f 327//327 333//333 334//334
        -f 335//335 329//329 328//328
        -f 328//328 334//334 335//335
        -f 336//336 330//330 329//329
        -f 329//329 335//335 336//336
        -f 337//337 331//331 330//330
        -f 330//330 336//336 337//337
        -f 195//195 332//332 193//193
        -f 193//193 39//39 195//195
        -f 197//197 333//333 332//332
        -f 332//332 195//195 197//197
        -f 199//199 334//334 333//333
        -f 333//333 197//197 199//199
        -f 201//201 335//335 334//334
        -f 334//334 199//199 201//201
        -f 203//203 336//336 335//335
        -f 335//335 201//201 203//203
        -f 205//205 337//337 336//336
        -f 336//336 203//203 205//205
        -f 338//338 339//339 205//205
        -f 205//205 204//204 338//338
        -f 340//340 341//341 339//339
        -f 339//339 338//338 340//340
        -f 342//342 343//343 341//341
        -f 341//341 340//340 342//342
        -f 344//344 345//345 343//343
        -f 343//343 342//342 344//344
        -f 346//346 347//347 345//345
        -f 345//345 344//344 346//346
        -f 348//348 349//349 347//347
        -f 347//347 346//346 348//348
        -f 350//350 338//338 204//204
        -f 204//204 211//211 350//350
        -f 351//351 340//340 338//338
        -f 338//338 350//350 351//351
        -f 352//352 342//342 340//340
        -f 340//340 351//351 352//352
        -f 353//353 344//344 342//342
        -f 342//342 352//352 353//353
        -f 354//354 346//346 344//344
        -f 344//344 353//353 354//354
        -f 355//355 348//348 346//346
        -f 346//346 354//354 355//355
        -f 356//356 350//350 211//211
        -f 211//211 217//217 356//356
        -f 357//357 351//351 350//350
        -f 350//350 356//356 357//357
        -f 358//358 352//352 351//351
        -f 351//351 357//357 358//358
        -f 359//359 353//353 352//352
        -f 352//352 358//358 359//359
        -f 360//360 354//354 353//353
        -f 353//353 359//359 360//360
        -f 361//361 355//355 354//354
        -f 354//354 360//360 361//361
        -f 362//362 356//356 223//223
        -f 217//217 223//223 356//356
        -f 363//363 357//357 362//362
        -f 356//356 362//362 357//357
        -f 364//364 358//358 363//363
        -f 357//357 363//363 358//358
        -f 365//365 359//359 364//364
        -f 358//358 364//364 359//359
        -f 366//366 360//360 365//365
        -f 359//359 365//365 360//360
        -f 367//367 361//361 366//366
        -f 360//360 366//366 361//361
        -f 368//368 362//362 229//229
        -f 223//223 229//229 362//362
        -f 369//369 363//363 368//368
        -f 362//362 368//368 363//363
        -f 370//370 364//364 369//369
        -f 363//363 369//369 364//364
        -f 371//371 365//365 370//370
        -f 364//364 370//370 365//365
        -f 372//372 366//366 371//371
        -f 365//365 371//371 366//366
        -f 373//373 367//367 372//372
        -f 366//366 372//372 367//367
        -f 374//374 368//368 235//235
        -f 229//229 235//235 368//368
        -f 375//375 369//369 374//374
        -f 368//368 374//374 369//369
        -f 376//376 370//370 375//375
        -f 369//369 375//375 370//370
        -f 377//377 371//371 376//376
        -f 370//370 376//376 371//371
        -f 378//378 372//372 377//377
        -f 371//371 377//377 372//372
        -f 379//379 373//373 378//378
        -f 372//372 378//378 373//373
        -f 380//380 374//374 235//235
        -f 235//235 241//241 380//380
        -f 381//381 375//375 374//374
        -f 374//374 380//380 381//381
        -f 382//382 376//376 375//375
        -f 375//375 381//381 382//382
        -f 383//383 377//377 376//376
        -f 376//376 382//382 383//383
        -f 384//384 378//378 377//377
        -f 377//377 383//383 384//384
        -f 385//385 379//379 378//378
        -f 378//378 384//384 385//385
        -f 386//386 380//380 241//241
        -f 241//241 247//247 386//386
        -f 387//387 381//381 380//380
        -f 380//380 386//386 387//387
        -f 388//388 382//382 381//381
        -f 381//381 387//387 388//388
        -f 389//389 383//383 382//382
        -f 382//382 388//388 389//389
        -f 390//390 384//384 383//383
        -f 383//383 389//389 390//390
        -f 391//391 385//385 384//384
        -f 384//384 390//390 391//391
        -f 392//392 386//386 247//247
        -f 247//247 253//253 392//392
        -f 393//393 387//387 386//386
        -f 386//386 392//392 393//393
        -f 394//394 388//388 387//387
        -f 387//387 393//393 394//394
        -f 395//395 389//389 388//388
        -f 388//388 394//394 395//395
        -f 396//396 390//390 389//389
        -f 389//389 395//395 396//396
        -f 397//397 391//391 390//390
        -f 390//390 396//396 397//397
        -f 398//398 392//392 259//259
        -f 253//253 259//259 392//392
        -f 399//399 393//393 398//398
        -f 392//392 398//398 393//393
        -f 400//400 394//394 399//399
        -f 393//393 399//399 394//394
        -f 401//401 395//395 400//400
        -f 394//394 400//400 395//395
        -f 402//402 396//396 401//401
        -f 395//395 401//401 396//396
        -f 403//403 397//397 402//402
        -f 396//396 402//402 397//397
        -f 404//404 398//398 265//265
        -f 259//259 265//265 398//398
        -f 405//405 399//399 404//404
        -f 398//398 404//404 399//399
        -f 406//406 400//400 405//405
        -f 399//399 405//405 400//400
        -f 407//407 401//401 406//406
        -f 400//400 406//406 401//401
        -f 408//408 402//402 407//407
        -f 401//401 407//407 402//402
        -f 409//409 403//403 408//408
        -f 402//402 408//408 403//403
        -f 410//410 404//404 271//271
        -f 265//265 271//271 404//404
        -f 411//411 405//405 410//410
        -f 404//404 410//410 405//405
        -f 412//412 406//406 411//411
        -f 405//405 411//411 406//406
        -f 413//413 407//407 412//412
        -f 406//406 412//412 407//407
        -f 414//414 408//408 413//413
        -f 407//407 413//413 408//408
        -f 415//415 409//409 414//414
        -f 408//408 414//414 409//409
        -f 416//416 410//410 271//271
        -f 271//271 277//277 416//416
        -f 417//417 411//411 410//410
        -f 410//410 416//416 417//417
        -f 418//418 412//412 411//411
        -f 411//411 417//417 418//418
        -f 419//419 413//413 412//412
        -f 412//412 418//418 419//419
        -f 420//420 414//414 413//413
        -f 413//413 419//419 420//420
        -f 421//421 415//415 414//414
        -f 414//414 420//420 421//421
        -f 422//422 416//416 277//277
        -f 277//277 283//283 422//422
        -f 423//423 417//417 416//416
        -f 416//416 422//422 423//423
        -f 424//424 418//418 417//417
        -f 417//417 423//423 424//424
        -f 425//425 419//419 418//418
        -f 418//418 424//424 425//425
        -f 426//426 420//420 419//419
        -f 419//419 425//425 426//426
        -f 427//427 421//421 420//420
        -f 420//420 426//426 427//427
        -f 428//428 422//422 283//283
        -f 283//283 289//289 428//428
        -f 429//429 423//423 422//422
        -f 422//422 428//428 429//429
        -f 430//430 424//424 423//423
        -f 423//423 429//429 430//430
        -f 431//431 425//425 424//424
        -f 424//424 430//430 431//431
        -f 432//432 426//426 425//425
        -f 425//425 431//431 432//432
        -f 433//433 427//427 426//426
        -f 426//426 432//432 433//433
        -f 434//434 428//428 295//295
        -f 289//289 295//295 428//428
        -f 435//435 429//429 434//434
        -f 428//428 434//434 429//429
        -f 436//436 430//430 435//435
        -f 429//429 435//435 430//430
        -f 437//437 431//431 436//436
        -f 430//430 436//436 431//431
        -f 438//438 432//432 437//437
        -f 431//431 437//437 432//432
        -f 439//439 433//433 438//438
        -f 432//432 438//438 433//433
        -f 440//440 434//434 301//301
        -f 295//295 301//301 434//434
        -f 441//441 435//435 440//440
        -f 434//434 440//440 435//435
        -f 442//442 436//436 441//441
        -f 435//435 441//441 436//436
        -f 443//443 437//437 442//442
        -f 436//436 442//442 437//437
        -f 444//444 438//438 443//443
        -f 437//437 443//443 438//438
        -f 445//445 439//439 444//444
        -f 438//438 444//444 439//439
        -f 446//446 440//440 307//307
        -f 301//301 307//307 440//440
        -f 447//447 441//441 446//446
        -f 440//440 446//446 441//441
        -f 448//448 442//442 447//447
        -f 441//441 447//447 442//442
        -f 449//449 443//443 448//448
        -f 442//442 448//448 443//443
        -f 450//450 444//444 449//449
        -f 443//443 449//449 444//444
        -f 451//451 445//445 450//450
        -f 444//444 450//450 445//445
        -f 452//452 446//446 307//307
        -f 307//307 313//313 452//452
        -f 453//453 447//447 446//446
        -f 446//446 452//452 453//453
        -f 454//454 448//448 447//447
        -f 447//447 453//453 454//454
        -f 455//455 449//449 448//448
        -f 448//448 454//454 455//455
        -f 456//456 450//450 449//449
        -f 449//449 455//455 456//456
        -f 457//457 451//451 450//450
        -f 450//450 456//456 457//457
        -f 458//458 452//452 313//313
        -f 313//313 319//319 458//458
        -f 459//459 453//453 452//452
        -f 452//452 458//458 459//459
        -f 460//460 454//454 453//453
        -f 453//453 459//459 460//460
        -f 461//461 455//455 454//454
        -f 454//454 460//460 461//461
        -f 462//462 456//456 455//455
        -f 455//455 461//461 462//462
        -f 463//463 457//457 456//456
        -f 456//456 462//462 463//463
        -f 464//464 458//458 319//319
        -f 319//319 325//325 464//464
        -f 465//465 459//459 458//458
        -f 458//458 464//464 465//465
        -f 466//466 460//460 459//459
        -f 459//459 465//465 466//466
        -f 467//467 461//461 460//460
        -f 460//460 466//466 467//467
        -f 468//468 462//462 461//461
        -f 461//461 467//467 468//468
        -f 469//469 463//463 462//462
        -f 462//462 468//468 469//469
        -f 470//470 464//464 331//331
        -f 325//325 331//331 464//464
        -f 471//471 465//465 470//470
        -f 464//464 470//470 465//465
        -f 472//472 466//466 471//471
        -f 465//465 471//471 466//466
        -f 473//473 467//467 472//472
        -f 466//466 472//472 467//467
        -f 474//474 468//468 473//473
        -f 467//467 473//473 468//468
        -f 475//475 469//469 474//474
        -f 468//468 474//474 469//469
        -f 476//476 470//470 337//337
        -f 331//331 337//337 470//470
        -f 477//477 471//471 476//476
        -f 470//470 476//476 471//471
        -f 478//478 472//472 477//477
        -f 471//471 477//477 472//472
        -f 479//479 473//473 478//478
        -f 472//472 478//478 473//473
        -f 480//480 474//474 479//479
        -f 473//473 479//479 474//474
        -f 481//481 475//475 480//480
        -f 474//474 480//480 475//475
        -f 339//339 476//476 205//205
        -f 337//337 205//205 476//476
        -f 341//341 477//477 339//339
        -f 476//476 339//339 477//477
        -f 343//343 478//478 341//341
        -f 477//477 341//341 478//478
        -f 345//345 479//479 343//343
        -f 478//478 343//343 479//479
        -f 347//347 480//480 345//345
        -f 479//479 345//345 480//480
        -f 349//349 481//481 347//347
        -f 480//480 347//347 481//481
        -f 1//1 3//3 482//482
        -f 483//483 482//482 3//3
        -f 482//482 483//483 484//484
        -f 485//485 484//484 483//483
        -f 484//484 485//485 486//486
        -f 487//487 486//486 485//485
        -f 486//486 487//487 488//488
        -f 489//489 488//488 487//487
        -f 488//488 489//489 349//349
        -f 481//481 349//349 489//489
        -f 3//3 4//4 483//483
        -f 490//490 483//483 4//4
        -f 483//483 490//490 485//485
        -f 491//491 485//485 490//490
        -f 485//485 491//491 487//487
        -f 492//492 487//487 491//491
        -f 487//487 492//492 489//489
        -f 493//493 489//489 492//492
        -f 489//489 493//493 481//481
        -f 475//475 481//481 493//493
        -f 4//4 5//5 490//490
        -f 494//494 490//490 5//5
        -f 490//490 494//494 491//491
        -f 495//495 491//491 494//494
        -f 491//491 495//495 492//492
        -f 496//496 492//492 495//495
        -f 492//492 496//496 493//493
        -f 497//497 493//493 496//496
        -f 493//493 497//497 475//475
        -f 469//469 475//475 497//497
        -f 5//5 6//6 498//498
        -f 498//498 494//494 5//5
        -f 494//494 498//498 499//499
        -f 499//499 495//495 494//494
        -f 495//495 499//499 500//500
        -f 500//500 496//496 495//495
        -f 496//496 500//500 501//501
        -f 501//501 497//497 496//496
        -f 497//497 501//501 463//463
        -f 463//463 469//469 497//497
        -f 6//6 7//7 502//502
        -f 502//502 498//498 6//6
        -f 498//498 502//502 503//503
        -f 503//503 499//499 498//498
        -f 499//499 503//503 504//504
        -f 504//504 500//500 499//499
        -f 500//500 504//504 505//505
        -f 505//505 501//501 500//500
        -f 501//501 505//505 457//457
        -f 457//457 463//463 501//501
        -f 7//7 8//8 506//506
        -f 506//506 502//502 7//7
        -f 502//502 506//506 507//507
        -f 507//507 503//503 502//502
        -f 503//503 507//507 508//508
        -f 508//508 504//504 503//503
        -f 504//504 508//508 509//509
        -f 509//509 505//505 504//504
        -f 505//505 509//509 451//451
        -f 451//451 457//457 505//505
        -f 8//8 9//9 506//506
        -f 510//510 506//506 9//9
        -f 506//506 510//510 507//507
        -f 511//511 507//507 510//510
        -f 507//507 511//511 508//508
        -f 512//512 508//508 511//511
        -f 508//508 512//512 509//509
        -f 513//513 509//509 512//512
        -f 509//509 513//513 451//451
        -f 445//445 451//451 513//513
        -f 9//9 10//10 510//510
        -f 514//514 510//510 10//10
        -f 510//510 514//514 511//511
        -f 515//515 511//511 514//514
        -f 511//511 515//515 512//512
        -f 516//516 512//512 515//515
        -f 512//512 516//516 513//513
        -f 517//517 513//513 516//516
        -f 513//513 517//517 445//445
        -f 439//439 445//445 517//517
        -f 10//10 11//11 514//514
        -f 518//518 514//514 11//11
        -f 514//514 518//518 515//515
        -f 519//519 515//515 518//518
        -f 515//515 519//519 516//516
        -f 520//520 516//516 519//519
        -f 516//516 520//520 517//517
        -f 521//521 517//517 520//520
        -f 517//517 521//521 439//439
        -f 433//433 439//439 521//521
        -f 11//11 12//12 522//522
        -f 522//522 518//518 11//11
        -f 518//518 522//522 523//523
        -f 523//523 519//519 518//518
        -f 519//519 523//523 524//524
        -f 524//524 520//520 519//519
        -f 520//520 524//524 525//525
        -f 525//525 521//521 520//520
        -f 521//521 525//525 427//427
        -f 427//427 433//433 521//521
        -f 12//12 13//13 526//526
        -f 526//526 522//522 12//12
        -f 522//522 526//526 527//527
        -f 527//527 523//523 522//522
        -f 523//523 527//527 528//528
        -f 528//528 524//524 523//523
        -f 524//524 528//528 529//529
        -f 529//529 525//525 524//524
        -f 525//525 529//529 421//421
        -f 421//421 427//427 525//525
        -f 13//13 14//14 530//530
        -f 530//530 526//526 13//13
        -f 526//526 530//530 531//531
        -f 531//531 527//527 526//526
        -f 527//527 531//531 532//532
        -f 532//532 528//528 527//527
        -f 528//528 532//532 533//533
        -f 533//533 529//529 528//528
        -f 529//529 533//533 415//415
        -f 415//415 421//421 529//529
        -f 14//14 15//15 530//530
        -f 534//534 530//530 15//15
        -f 530//530 534//534 531//531
        -f 535//535 531//531 534//534
        -f 531//531 535//535 532//532
        -f 536//536 532//532 535//535
        -f 532//532 536//536 533//533
        -f 537//537 533//533 536//536
        -f 533//533 537//537 415//415
        -f 409//409 415//415 537//537
        -f 15//15 16//16 534//534
        -f 538//538 534//534 16//16
        -f 534//534 538//538 535//535
        -f 539//539 535//535 538//538
        -f 535//535 539//539 536//536
        -f 540//540 536//536 539//539
        -f 536//536 540//540 537//537
        -f 541//541 537//537 540//540
        -f 537//537 541//541 409//409
        -f 403//403 409//409 541//541
        -f 16//16 17//17 538//538
        -f 542//542 538//538 17//17
        -f 538//538 542//542 539//539
        -f 543//543 539//539 542//542
        -f 539//539 543//543 540//540
        -f 544//544 540//540 543//543
        -f 540//540 544//544 541//541
        -f 545//545 541//541 544//544
        -f 541//541 545//545 403//403
        -f 397//397 403//403 545//545
        -f 17//17 18//18 546//546
        -f 546//546 542//542 17//17
        -f 542//542 546//546 547//547
        -f 547//547 543//543 542//542
        -f 543//543 547//547 548//548
        -f 548//548 544//544 543//543
        -f 544//544 548//548 549//549
        -f 549//549 545//545 544//544
        -f 545//545 549//549 391//391
        -f 391//391 397//397 545//545
        -f 18//18 19//19 550//550
        -f 550//550 546//546 18//18
        -f 546//546 550//550 551//551
        -f 551//551 547//547 546//546
        -f 547//547 551//551 552//552
        -f 552//552 548//548 547//547
        -f 548//548 552//552 553//553
        -f 553//553 549//549 548//548
        -f 549//549 553//553 385//385
        -f 385//385 391//391 549//549
        -f 19//19 20//20 554//554
        -f 554//554 550//550 19//19
        -f 550//550 554//554 555//555
        -f 555//555 551//551 550//550
        -f 551//551 555//555 556//556
        -f 556//556 552//552 551//551
        -f 552//552 556//556 557//557
        -f 557//557 553//553 552//552
        -f 553//553 557//557 379//379
        -f 379//379 385//385 553//553
        -f 20//20 21//21 554//554
        -f 558//558 554//554 21//21
        -f 554//554 558//558 555//555
        -f 559//559 555//555 558//558
        -f 555//555 559//559 556//556
        -f 560//560 556//556 559//559
        -f 556//556 560//560 557//557
        -f 561//561 557//557 560//560
        -f 557//557 561//561 379//379
        -f 373//373 379//379 561//561
        -f 21//21 22//22 558//558
        -f 562//562 558//558 22//22
        -f 558//558 562//562 559//559
        -f 563//563 559//559 562//562
        -f 559//559 563//563 560//560
        -f 564//564 560//560 563//563
        -f 560//560 564//564 561//561
        -f 565//565 561//561 564//564
        -f 561//561 565//565 373//373
        -f 367//367 373//373 565//565
        -f 22//22 23//23 562//562
        -f 566//566 562//562 23//23
        -f 562//562 566//566 563//563
        -f 567//567 563//563 566//566
        -f 563//563 567//567 564//564
        -f 568//568 564//564 567//567
        -f 564//564 568//568 565//565
        -f 569//569 565//565 568//568
        -f 565//565 569//569 367//367
        -f 361//361 367//367 569//569
        -f 23//23 24//24 570//570
        -f 570//570 566//566 23//23
        -f 566//566 570//570 571//571
        -f 571//571 567//567 566//566
        -f 567//567 571//571 572//572
        -f 572//572 568//568 567//567
        -f 568//568 572//572 573//573
        -f 573//573 569//569 568//568
        -f 569//569 573//573 355//355
        -f 355//355 361//361 569//569
        -f 24//24 25//25 574//574
        -f 574//574 570//570 24//24
        -f 570//570 574//574 575//575
        -f 575//575 571//571 570//570
        -f 571//571 575//575 576//576
        -f 576//576 572//572 571//571
        -f 572//572 576//576 577//577
        -f 577//577 573//573 572//572
        -f 573//573 577//577 348//348
        -f 348//348 355//355 573//573
        -f 25//25 1//1 482//482
        -f 482//482 574//574 25//25
        -f 574//574 482//482 484//484
        -f 484//484 575//575 574//574
        -f 575//575 484//484 486//486
        -f 486//486 576//576 575//575
        -f 576//576 486//486 488//488
        -f 488//488 577//577 576//576
        -f 577//577 488//488 349//349
        -f 349//349 348//348 577//577
        -f 578//578 579//579 580//580
        -f 581//581 580//580 579//579
        -f 582//582 578//578 583//583
        -f 580//580 583//583 578//578
        -f 584//584 582//582 585//585
        -f 583//583 585//585 582//582
        -f 586//586 584//584 585//585
        -f 585//585 587//587 586//586
        -f 588//588 586//586 587//587
        -f 587//587 589//589 588//588
        -f 590//590 588//588 589//589
        -f 589//589 591//591 590//590
        -f 592//592 590//590 593//593
        -f 591//591 593//593 590//590
        -f 594//594 592//592 595//595
        -f 593//593 595//595 592//592
        -f 596//596 594//594 597//597
        -f 595//595 597//597 594//594
        -f 598//598 596//596 597//597
        -f 597//597 599//599 598//598
        -f 600//600 598//598 599//599
        -f 599//599 601//601 600//600
        -f 602//602 600//600 601//601
        -f 601//601 603//603 602//602
        -f 604//604 602//602 605//605
        -f 603//603 605//605 602//602
        -f 606//606 604//604 607//607
        -f 605//605 607//607 604//604
        -f 608//608 606//606 609//609
        -f 607//607 609//609 606//606
        -f 610//610 608//608 609//609
        -f 609//609 611//611 610//610
        -f 612//612 610//610 611//611
        -f 611//611 613//613 612//612
        -f 614//614 612//612 613//613
        -f 613//613 615//615 614//614
        -f 616//616 614//614 617//617
        -f 615//615 617//617 614//614
        -f 618//618 616//616 619//619
        -f 617//617 619//619 616//616
        -f 620//620 618//618 621//621
        -f 619//619 621//621 618//618
        -f 622//622 620//620 621//621
        -f 621//621 623//623 622//622
        -f 624//624 622//622 623//623
        -f 623//623 625//625 624//624
        -f 579//579 624//624 625//625
        -f 625//625 581//581 579//579
        -f 626//626 627//627 578//578
        -f 579//579 578//578 627//627
        -f 628//628 629//629 626//626
        -f 627//627 626//626 629//629
        -f 630//630 631//631 628//628
        -f 629//629 628//628 631//631
        -f 632//632 633//633 630//630
        -f 631//631 630//630 633//633
        -f 634//634 635//635 632//632
        -f 633//633 632//632 635//635
        -f 636//636 637//637 634//634
        -f 635//635 634//634 637//637
        -f 638//638 626//626 582//582
        -f 578//578 582//582 626//626
        -f 639//639 628//628 638//638
        -f 626//626 638//638 628//628
        -f 640//640 630//630 639//639
        -f 628//628 639//639 630//630
        -f 641//641 632//632 640//640
        -f 630//630 640//640 632//632
        -f 642//642 634//634 641//641
        -f 632//632 641//641 634//634
        -f 643//643 636//636 642//642
        -f 634//634 642//642 636//636
        -f 644//644 638//638 584//584
        -f 582//582 584//584 638//638
        -f 645//645 639//639 644//644
        -f 638//638 644//644 639//639
        -f 646//646 640//640 645//645
        -f 639//639 645//645 640//640
        -f 647//647 641//641 646//646
        -f 640//640 646//646 641//641
        -f 648//648 642//642 647//647
        -f 641//641 647//647 642//642
        -f 649//649 643//643 648//648
        -f 642//642 648//648 643//643
        -f 650//650 644//644 584//584
        -f 584//584 586//586 650//650
        -f 651//651 645//645 644//644
        -f 644//644 650//650 651//651
        -f 652//652 646//646 645//645
        -f 645//645 651//651 652//652
        -f 653//653 647//647 646//646
        -f 646//646 652//652 653//653
        -f 654//654 648//648 647//647
        -f 647//647 653//653 654//654
        -f 655//655 649//649 648//648
        -f 648//648 654//654 655//655
        -f 656//656 650//650 586//586
        -f 586//586 588//588 656//656
        -f 657//657 651//651 650//650
        -f 650//650 656//656 657//657
        -f 658//658 652//652 651//651
        -f 651//651 657//657 658//658
        -f 659//659 653//653 652//652
        -f 652//652 658//658 659//659
        -f 660//660 654//654 653//653
        -f 653//653 659//659 660//660
        -f 661//661 655//655 654//654
        -f 654//654 660//660 661//661
        -f 662//662 656//656 588//588
        -f 588//588 590//590 662//662
        -f 663//663 657//657 656//656
        -f 656//656 662//662 663//663
        -f 664//664 658//658 657//657
        -f 657//657 663//663 664//664
        -f 665//665 659//659 658//658
        -f 658//658 664//664 665//665
        -f 666//666 660//660 659//659
        -f 659//659 665//665 666//666
        -f 667//667 661//661 660//660
        -f 660//660 666//666 667//667
        -f 668//668 662//662 592//592
        -f 590//590 592//592 662//662
        -f 669//669 663//663 668//668
        -f 662//662 668//668 663//663
        -f 670//670 664//664 669//669
        -f 663//663 669//669 664//664
        -f 671//671 665//665 670//670
        -f 664//664 670//670 665//665
        -f 672//672 666//666 671//671
        -f 665//665 671//671 666//666
        -f 673//673 667//667 672//672
        -f 666//666 672//672 667//667
        -f 674//674 668//668 594//594
        -f 592//592 594//594 668//668
        -f 675//675 669//669 674//674
        -f 668//668 674//674 669//669
        -f 676//676 670//670 675//675
        -f 669//669 675//675 670//670
        -f 677//677 671//671 676//676
        -f 670//670 676//676 671//671
        -f 678//678 672//672 677//677
        -f 671//671 677//677 672//672
        -f 679//679 673//673 678//678
        -f 672//672 678//678 673//673
        -f 680//680 674//674 596//596
        -f 594//594 596//596 674//674
        -f 681//681 675//675 680//680
        -f 674//674 680//680 675//675
        -f 682//682 676//676 681//681
        -f 675//675 681//681 676//676
        -f 683//683 677//677 682//682
        -f 676//676 682//682 677//677
        -f 684//684 678//678 683//683
        -f 677//677 683//683 678//678
        -f 685//685 679//679 684//684
        -f 678//678 684//684 679//679
        -f 686//686 680//680 596//596
        -f 596//596 598//598 686//686
        -f 687//687 681//681 680//680
        -f 680//680 686//686 687//687
        -f 688//688 682//682 681//681
        -f 681//681 687//687 688//688
        -f 689//689 683//683 682//682
        -f 682//682 688//688 689//689
        -f 690//690 684//684 683//683
        -f 683//683 689//689 690//690
        -f 691//691 685//685 684//684
        -f 684//684 690//690 691//691
        -f 692//692 686//686 598//598
        -f 598//598 600//600 692//692
        -f 693//693 687//687 686//686
        -f 686//686 692//692 693//693
        -f 694//694 688//688 687//687
        -f 687//687 693//693 694//694
        -f 695//695 689//689 688//688
        -f 688//688 694//694 695//695
        -f 696//696 690//690 689//689
        -f 689//689 695//695 696//696
        -f 697//697 691//691 690//690
        -f 690//690 696//696 697//697
        -f 698//698 692//692 600//600
        -f 600//600 602//602 698//698
        -f 699//699 693//693 692//692
        -f 692//692 698//698 699//699
        -f 700//700 694//694 693//693
        -f 693//693 699//699 700//700
        -f 701//701 695//695 694//694
        -f 694//694 700//700 701//701
        -f 702//702 696//696 695//695
        -f 695//695 701//701 702//702
        -f 703//703 697//697 696//696
        -f 696//696 702//702 703//703
        -f 704//704 698//698 604//604
        -f 602//602 604//604 698//698
        -f 705//705 699//699 704//704
        -f 698//698 704//704 699//699
        -f 706//706 700//700 705//705
        -f 699//699 705//705 700//700
        -f 707//707 701//701 706//706
        -f 700//700 706//706 701//701
        -f 708//708 702//702 707//707
        -f 701//701 707//707 702//702
        -f 709//709 703//703 708//708
        -f 702//702 708//708 703//703
        -f 710//710 704//704 606//606
        -f 604//604 606//606 704//704
        -f 711//711 705//705 710//710
        -f 704//704 710//710 705//705
        -f 712//712 706//706 711//711
        -f 705//705 711//711 706//706
        -f 713//713 707//707 712//712
        -f 706//706 712//712 707//707
        -f 714//714 708//708 713//713
        -f 707//707 713//713 708//708
        -f 715//715 709//709 714//714
        -f 708//708 714//714 709//709
        -f 716//716 710//710 608//608
        -f 606//606 608//608 710//710
        -f 717//717 711//711 716//716
        -f 710//710 716//716 711//711
        -f 718//718 712//712 717//717
        -f 711//711 717//717 712//712
        -f 719//719 713//713 718//718
        -f 712//712 718//718 713//713
        -f 720//720 714//714 719//719
        -f 713//713 719//719 714//714
        -f 721//721 715//715 720//720
        -f 714//714 720//720 715//715
        -f 722//722 716//716 608//608
        -f 608//608 610//610 722//722
        -f 723//723 717//717 716//716
        -f 716//716 722//722 723//723
        -f 724//724 718//718 717//717
        -f 717//717 723//723 724//724
        -f 725//725 719//719 718//718
        -f 718//718 724//724 725//725
        -f 726//726 720//720 719//719
        -f 719//719 725//725 726//726
        -f 727//727 721//721 720//720
        -f 720//720 726//726 727//727
        -f 728//728 722//722 610//610
        -f 610//610 612//612 728//728
        -f 729//729 723//723 722//722
        -f 722//722 728//728 729//729
        -f 730//730 724//724 723//723
        -f 723//723 729//729 730//730
        -f 731//731 725//725 724//724
        -f 724//724 730//730 731//731
        -f 732//732 726//726 725//725
        -f 725//725 731//731 732//732
        -f 733//733 727//727 726//726
        -f 726//726 732//732 733//733
        -f 734//734 728//728 612//612
        -f 612//612 614//614 734//734
        -f 735//735 729//729 728//728
        -f 728//728 734//734 735//735
        -f 736//736 730//730 729//729
        -f 729//729 735//735 736//736
        -f 737//737 731//731 730//730
        -f 730//730 736//736 737//737
        -f 738//738 732//732 731//731
        -f 731//731 737//737 738//738
        -f 739//739 733//733 732//732
        -f 732//732 738//738 739//739
        -f 740//740 734//734 616//616
        -f 614//614 616//616 734//734
        -f 741//741 735//735 740//740
        -f 734//734 740//740 735//735
        -f 742//742 736//736 741//741
        -f 735//735 741//741 736//736
        -f 743//743 737//737 742//742
        -f 736//736 742//742 737//737
        -f 744//744 738//738 743//743
        -f 737//737 743//743 738//738
        -f 745//745 739//739 744//744
        -f 738//738 744//744 739//739
        -f 746//746 740//740 618//618
        -f 616//616 618//618 740//740
        -f 747//747 741//741 746//746
        -f 740//740 746//746 741//741
        -f 748//748 742//742 747//747
        -f 741//741 747//747 742//742
        -f 749//749 743//743 748//748
        -f 742//742 748//748 743//743
        -f 750//750 744//744 749//749
        -f 743//743 749//749 744//744
        -f 751//751 745//745 750//750
        -f 744//744 750//750 745//745
        -f 752//752 746//746 620//620
        -f 618//618 620//620 746//746
        -f 753//753 747//747 752//752
        -f 746//746 752//752 747//747
        -f 754//754 748//748 753//753
        -f 747//747 753//753 748//748
        -f 755//755 749//749 754//754
        -f 748//748 754//754 749//749
        -f 756//756 750//750 755//755
        -f 749//749 755//755 750//750
        -f 757//757 751//751 756//756
        -f 750//750 756//756 751//751
        -f 758//758 752//752 620//620
        -f 620//620 622//622 758//758
        -f 759//759 753//753 752//752
        -f 752//752 758//758 759//759
        -f 760//760 754//754 753//753
        -f 753//753 759//759 760//760
        -f 761//761 755//755 754//754
        -f 754//754 760//760 761//761
        -f 762//762 756//756 755//755
        -f 755//755 761//761 762//762
        -f 763//763 757//757 756//756
        -f 756//756 762//762 763//763
        -f 764//764 758//758 622//622
        -f 622//622 624//624 764//764
        -f 765//765 759//759 758//758
        -f 758//758 764//764 765//765
        -f 766//766 760//760 759//759
        -f 759//759 765//765 766//766
        -f 767//767 761//761 760//760
        -f 760//760 766//766 767//767
        -f 768//768 762//762 761//761
        -f 761//761 767//767 768//768
        -f 769//769 763//763 762//762
        -f 762//762 768//768 769//769
        -f 627//627 764//764 624//624
        -f 624//624 579//579 627//627
        -f 629//629 765//765 764//764
        -f 764//764 627//627 629//629
        -f 631//631 766//766 765//765
        -f 765//765 629//629 631//631
        -f 633//633 767//767 766//766
        -f 766//766 631//631 633//633
        -f 635//635 768//768 767//767
        -f 767//767 633//633 635//635
        -f 637//637 769//769 768//768
        -f 768//768 635//635 637//637
        -f 770//770 771//771 772//772
        -f 773//773 772//772 771//771
        -f 774//774 770//770 775//775
        -f 772//772 775//775 770//770
        -f 776//776 777//777 774//774
        -f 774//774 775//775 776//776
        -f 778//778 779//779 776//776
        -f 777//777 776//776 779//779
        -f 780//780 781//781 778//778
        -f 779//779 778//778 781//781
        -f 782//782 783//783 780//780
        -f 781//781 780//780 783//783
        -f 772//772 773//773 784//784
        -f 785//785 784//784 773//773
        -f 775//775 772//772 786//786
        -f 784//784 786//786 772//772
        -f 776//776 775//775 787//787
        -f 786//786 787//787 775//775
        -f 788//788 778//778 776//776
        -f 776//776 787//787 788//788
        -f 789//789 780//780 788//788
        -f 778//778 788//788 780//780
        -f 790//790 782//782 789//789
        -f 780//780 789//789 782//782
        -f 784//784 785//785 791//791
        -f 792//792 791//791 785//785
        -f 786//786 784//784 793//793
        -f 791//791 793//793 784//784
        -f 787//787 786//786 794//794
        -f 793//793 794//794 786//786
        -f 795//795 788//788 787//787
        -f 787//787 794//794 795//795
        -f 796//796 789//789 795//795
        -f 788//788 795//795 789//789
        -f 797//797 790//790 796//796
        -f 789//789 796//796 790//790
        -f 791//791 792//792 798//798
        -f 799//799 798//798 792//792
        -f 793//793 791//791 800//800
        -f 798//798 800//800 791//791
        -f 794//794 793//793 801//801
        -f 800//800 801//801 793//793
        -f 802//802 795//795 794//794
        -f 794//794 801//801 802//802
        -f 803//803 796//796 802//802
        -f 795//795 802//802 796//796
        -f 804//804 797//797 803//803
        -f 796//796 803//803 797//797
        -f 798//798 799//799 805//805
        -f 806//806 805//805 799//799
        -f 800//800 798//798 807//807
        -f 805//805 807//807 798//798
        -f 801//801 800//800 808//808
        -f 807//807 808//808 800//800
        -f 809//809 802//802 801//801
        -f 801//801 808//808 809//809
        -f 810//810 803//803 809//809
        -f 802//802 809//809 803//803
        -f 811//811 804//804 810//810
        -f 803//803 810//810 804//804
        -f 805//805 806//806 812//812
        -f 813//813 812//812 806//806
        -f 807//807 805//805 814//814
        -f 812//812 814//814 805//805
        -f 815//815 808//808 814//814
        -f 807//807 814//814 808//808
        -f 816//816 809//809 815//815
        -f 808//808 815//815 809//809
        -f 817//817 810//810 816//816
        -f 809//809 816//816 810//810
        -f 818//818 811//811 817//817
        -f 810//810 817//817 811//811
        -f 819//819 820//820 812//812
        -f 812//812 813//813 819//819
        -f 820//820 821//821 814//814
        -f 814//814 812//812 820//820
        -f 822//822 815//815 814//814
        -f 814//814 821//821 822//822
        -f 823//823 816//816 815//815
        -f 815//815 822//822 823//823
        -f 824//824 817//817 816//816
        -f 816//816 823//823 824//824
        -f 825//825 818//818 817//817
        -f 817//817 824//824 825//825
        -f 826//826 827//827 820//820
        -f 820//820 819//819 826//826
        -f 827//827 828//828 821//821
        -f 821//821 820//820 827//827
        -f 828//828 829//829 822//822
        -f 822//822 821//821 828//828
        -f 830//830 823//823 829//829
        -f 822//822 829//829 823//823
        -f 831//831 824//824 823//823
        -f 823//823 830//830 831//831
        -f 832//832 825//825 824//824
        -f 824//824 831//831 832//832
        -f 833//833 834//834 827//827
        -f 827//827 826//826 833//833
        -f 834//834 835//835 828//828
        -f 828//828 827//827 834//834
        -f 835//835 836//836 829//829
        -f 829//829 828//828 835//835
        -f 837//837 830//830 836//836
        -f 829//829 836//836 830//830
        -f 838//838 831//831 830//830
        -f 830//830 837//837 838//838
        -f 839//839 832//832 831//831
        -f 831//831 838//838 839//839
        -f 840//840 841//841 834//834
        -f 834//834 833//833 840//840
        -f 841//841 842//842 835//835
        -f 835//835 834//834 841//841
        -f 842//842 843//843 836//836
        -f 836//836 835//835 842//842
        -f 844//844 837//837 843//843
        -f 836//836 843//843 837//837
        -f 845//845 838//838 837//837
        -f 837//837 844//844 845//845
        -f 846//846 839//839 838//838
        -f 838//838 845//845 846//846
        -f 847//847 848//848 841//841
        -f 841//841 840//840 847//847
        -f 848//848 849//849 842//842
        -f 842//842 841//841 848//848
        -f 849//849 850//850 843//843
        -f 843//843 842//842 849//849
        -f 851//851 844//844 850//850
        -f 843//843 850//850 844//844
        -f 852//852 845//845 844//844
        -f 844//844 851//851 852//852
        -f 853//853 846//846 845//845
        -f 845//845 852//852 853//853
        -f 771//771 770//770 848//848
        -f 848//848 847//847 771//771
        -f 770//770 774//774 849//849
        -f 849//849 848//848 770//770
        -f 777//777 850//850 774//774
        -f 849//849 774//774 850//850
        -f 779//779 851//851 850//850
        -f 850//850 777//777 779//779
        -f 781//781 852//852 851//851
        -f 851//851 779//779 781//781
        -f 783//783 853//853 852//852
        -f 852//852 781//781 783//783
        -f 854//854 855//855 782//782
        -f 783//783 782//782 855//855
        -f 856//856 857//857 855//855
        -f 855//855 854//854 856//856
        -f 858//858 859//859 857//857
        -f 857//857 856//856 858//858
        -f 860//860 861//861 859//859
        -f 859//859 858//858 860//860
        -f 862//862 863//863 861//861
        -f 861//861 860//860 862//862
        -f 864//864 865//865 863//863
        -f 863//863 862//862 864//864
        -f 866//866 854//854 782//782
        -f 782//782 790//790 866//866
        -f 867//867 856//856 854//854
        -f 854//854 866//866 867//867
        -f 868//868 858//858 856//856
        -f 856//856 867//867 868//868
        -f 869//869 860//860 858//858
        -f 858//858 868//868 869//869
        -f 870//870 862//862 860//860
        -f 860//860 869//869 870//870
        -f 871//871 864//864 870//870
        -f 862//862 870//870 864//864
        -f 872//872 866//866 790//790
        -f 790//790 797//797 872//872
        -f 873//873 867//867 866//866
        -f 866//866 872//872 873//873
        -f 874//874 868//868 867//867
        -f 867//867 873//873 874//874
        -f 875//875 869//869 874//874
        -f 868//868 874//874 869//869
        -f 876//876 870//870 875//875
        -f 869//869 875//875 870//870
        -f 877//877 871//871 876//876
        -f 870//870 876//876 871//871
        -f 878//878 872//872 797//797
        -f 797//797 804//804 878//878
        -f 879//879 873//873 872//872
        -f 872//872 878//878 879//879
        -f 880//880 874//874 873//873
        -f 873//873 879//879 880//880
        -f 881//881 875//875 880//880
        -f 874//874 880//880 875//875
        -f 882//882 876//876 881//881
        -f 875//875 881//881 876//876
        -f 883//883 877//877 882//882
        -f 876//876 882//882 877//877
        -f 884//884 878//878 804//804
        -f 804//804 811//811 884//884
        -f 885//885 879//879 878//878
        -f 878//878 884//884 885//885
        -f 886//886 880//880 879//879
        -f 879//879 885//885 886//886
        -f 887//887 881//881 880//880
        -f 880//880 886//886 887//887
        -f 888//888 882//882 881//881
        -f 881//881 887//887 888//888
        -f 889//889 883//883 888//888
        -f 882//882 888//888 883//883
        -f 890//890 884//884 811//811
        -f 811//811 818//818 890//890
        -f 891//891 885//885 884//884
        -f 884//884 890//890 891//891
        -f 892//892 886//886 885//885
        -f 885//885 891//891 892//892
        -f 893//893 887//887 886//886
        -f 886//886 892//892 893//893
        -f 894//894 888//888 887//887
        -f 887//887 893//893 894//894
        -f 895//895 889//889 888//888
        -f 888//888 894//894 895//895
        -f 896//896 890//890 825//825
        -f 818//818 825//825 890//890
        -f 897//897 891//891 896//896
        -f 890//890 896//896 891//891
        -f 898//898 892//892 897//897
        -f 891//891 897//897 892//892
        -f 899//899 893//893 898//898
        -f 892//892 898//898 893//893
        -f 900//900 894//894 899//899
        -f 893//893 899//899 894//894
        -f 901//901 895//895 900//900
        -f 894//894 900//900 895//895
        -f 902//902 896//896 832//832
        -f 825//825 832//832 896//896
        -f 903//903 897//897 902//902
        -f 896//896 902//902 897//897
        -f 904//904 898//898 903//903
        -f 897//897 903//903 898//898
        -f 905//905 899//899 904//904
        -f 898//898 904//904 899//899
        -f 906//906 900//900 905//905
        -f 899//899 905//905 900//900
        -f 907//907 901//901 900//900
        -f 900//900 906//906 907//907
        -f 908//908 902//902 839//839
        -f 832//832 839//839 902//902
        -f 909//909 903//903 908//908
        -f 902//902 908//908 903//903
        -f 910//910 904//904 909//909
        -f 903//903 909//909 904//904
        -f 911//911 905//905 904//904
        -f 904//904 910//910 911//911
        -f 912//912 906//906 905//905
        -f 905//905 911//911 912//912
        -f 913//913 907//907 906//906
        -f 906//906 912//912 913//913
        -f 914//914 908//908 846//846
        -f 839//839 846//846 908//908
        -f 915//915 909//909 914//914
        -f 908//908 914//914 909//909
        -f 916//916 910//910 915//915
        -f 909//909 915//915 910//910
        -f 917//917 911//911 910//910
        -f 910//910 916//916 917//917
        -f 918//918 912//912 911//911
        -f 911//911 917//917 918//918
        -f 919//919 913//913 912//912
        -f 912//912 918//918 919//919
        -f 920//920 914//914 853//853
        -f 846//846 853//853 914//914
        -f 921//921 915//915 920//920
        -f 914//914 920//920 915//915
        -f 922//922 916//916 921//921
        -f 915//915 921//921 916//916
        -f 923//923 917//917 922//922
        -f 916//916 922//922 917//917
        -f 924//924 918//918 923//923
        -f 917//917 923//923 918//918
        -f 925//925 919//919 918//918
        -f 918//918 924//924 925//925
        -f 855//855 920//920 853//853
        -f 853//853 783//783 855//855
        -f 857//857 921//921 855//855
        -f 920//920 855//855 921//921
        -f 859//859 922//922 857//857
        -f 921//921 857//857 922//922
        -f 861//861 923//923 859//859
        -f 922//922 859//859 923//923
        -f 863//863 924//924 861//861
        -f 923//923 861//861 924//924
        -f 865//865 925//925 863//863
        -f 924//924 863//863 925//925
        -f 926//926 927//927 928//928
        -f 928//928 929//929 926//926
        -f 929//929 928//928 930//930
        -f 930//930 931//931 929//929
        -f 931//931 930//930 932//932
        -f 932//932 933//933 931//931
        -f 933//933 932//932 934//934
        -f 934//934 935//935 933//933
        -f 935//935 934//934 936//936
        -f 936//936 937//937 935//935
        -f 937//937 936//936 938//938
        -f 939//939 938//938 936//936
        -f 940//940 941//941 927//927
        -f 928//928 927//927 941//941
        -f 928//928 941//941 942//942
        -f 942//942 930//930 928//928
        -f 930//930 942//942 943//943
        -f 943//943 932//932 930//930
        -f 932//932 943//943 944//944
        -f 944//944 934//934 932//932
        -f 934//934 944//944 936//936
        -f 945//945 936//936 944//944
        -f 936//936 945//945 939//939
        -f 946//946 939//939 945//945
        -f 947//947 948//948 940//940
        -f 941//941 940//940 948//948
        -f 941//941 948//948 949//949
        -f 949//949 942//942 941//941
        -f 942//942 949//949 943//943
        -f 950//950 943//943 949//949
        -f 943//943 950//950 944//944
        -f 951//951 944//944 950//950
        -f 944//944 951//951 945//945
        -f 952//952 945//945 951//951
        -f 945//945 952//952 946//946
        -f 953//953 946//946 952//952
        -f 954//954 955//955 947//947
        -f 948//948 947//947 955//955
        -f 948//948 955//955 956//956
        -f 956//956 949//949 948//948
        -f 949//949 956//956 950//950
        -f 957//957 950//950 956//956
        -f 950//950 957//957 951//951
        -f 958//958 951//951 957//957
        -f 951//951 958//958 952//952
        -f 959//959 952//952 958//958
        -f 952//952 959//959 953//953
        -f 960//960 953//953 959//959
        -f 954//954 961//961 962//962
        -f 962//962 955//955 954//954
        -f 955//955 962//962 956//956
        -f 963//963 956//956 962//962
        -f 956//956 963//963 957//957
        -f 964//964 957//957 963//963
        -f 957//957 964//964 958//958
        -f 965//965 958//958 964//964
        -f 958//958 965//965 959//959
        -f 966//966 959//959 965//965
        -f 959//959 966//966 960//960
        -f 967//967 960//960 966//966
        -f 961//961 968//968 962//962
        -f 969//969 962//962 968//968
        -f 962//962 969//969 963//963
        -f 970//970 963//963 969//969
        -f 963//963 970//970 964//964
        -f 971//971 964//964 970//970
        -f 964//964 971//971 965//965
        -f 972//972 965//965 971//971
        -f 965//965 972//972 966//966
        -f 973//973 966//966 972//972
        -f 966//966 973//973 967//967
        -f 974//974 967//967 973//973
        -f 968//968 975//975 976//976
        -f 976//976 969//969 968//968
        -f 969//969 976//976 977//977
        -f 977//977 970//970 969//969
        -f 970//970 977//977 978//978
        -f 978//978 971//971 970//970
        -f 971//971 978//978 979//979
        -f 979//979 972//972 971//971
        -f 972//972 979//979 980//980
        -f 980//980 973//973 972//972
        -f 973//973 980//980 981//981
        -f 981//981 974//974 973//973
        -f 975//975 982//982 976//976
        -f 983//983 976//976 982//982
        -f 976//976 983//983 984//984
        -f 984//984 977//977 976//976
        -f 977//977 984//984 985//985
        -f 985//985 978//978 977//977
        -f 978//978 985//985 986//986
        -f 986//986 979//979 978//978
        -f 979//979 986//986 987//987
        -f 987//987 980//980 979//979
        -f 980//980 987//987 988//988
        -f 988//988 981//981 980//980
        -f 983//983 982//982 989//989
        -f 989//989 990//990 983//983
        -f 983//983 990//990 984//984
        -f 991//991 984//984 990//990
        -f 984//984 991//991 992//992
        -f 992//992 985//985 984//984
        -f 985//985 992//992 993//993
        -f 993//993 986//986 985//985
        -f 986//986 993//993 994//994
        -f 994//994 987//987 986//986
        -f 987//987 994//994 995//995
        -f 995//995 988//988 987//987
        -f 990//990 989//989 996//996
        -f 996//996 997//997 990//990
        -f 990//990 997//997 991//991
        -f 998//998 991//991 997//997
        -f 991//991 998//998 999//999
        -f 999//999 992//992 991//991
        -f 992//992 999//999 1000//1000
        -f 1000//1000 993//993 992//992
        -f 993//993 1000//1000 1001//1001
        -f 1001//1001 994//994 993//993
        -f 994//994 1001//1001 1002//1002
        -f 1002//1002 995//995 994//994
        -f 997//997 996//996 1003//1003
        -f 1003//1003 1004//1004 997//997
        -f 997//997 1004//1004 998//998
        -f 1005//1005 998//998 1004//1004
        -f 998//998 1005//1005 999//999
        -f 1006//1006 999//999 1005//1005
        -f 999//999 1006//1006 1000//1000
        -f 1007//1007 1000//1000 1006//1006
        -f 1000//1000 1007//1007 1008//1008
        -f 1008//1008 1001//1001 1000//1000
        -f 1001//1001 1008//1008 1009//1009
        -f 1009//1009 1002//1002 1001//1001
        -f 1003//1003 926//926 1004//1004
        -f 929//929 1004//1004 926//926
        -f 1004//1004 929//929 1005//1005
        -f 931//931 1005//1005 929//929
        -f 1005//1005 931//931 1006//1006
        -f 933//933 1006//1006 931//931
        -f 1006//1006 933//933 1007//1007
        -f 935//935 1007//1007 933//933
        -f 1007//1007 935//935 1008//1008
        -f 937//937 1008//1008 935//935
        -f 1008//1008 937//937 938//938
        -f 938//938 1009//1009 1008//1008
        -f 938//938 939//939 1010//1010
        -f 1011//1011 1010//1010 939//939
        -f 1010//1010 1011//1011 1012//1012
        -f 1013//1013 1012//1012 1011//1011
        -f 1012//1012 1013//1013 1014//1014
        -f 1015//1015 1014//1014 1013//1013
        -f 1016//1016 1017//1017 1014//1014
        -f 1014//1014 1015//1015 1016//1016
        -f 1018//1018 1019//1019 1017//1017
        -f 1017//1017 1016//1016 1018//1018
        -f 1020//1020 1021//1021 1019//1019
        -f 1019//1019 1018//1018 1020//1020
        -f 939//939 946//946 1011//1011
        -f 1022//1022 1011//1011 946//946
        -f 1011//1011 1022//1022 1013//1013
        -f 1023//1023 1013//1013 1022//1022
        -f 1013//1013 1023//1023 1015//1015
        -f 1024//1024 1015//1015 1023//1023
        -f 1025//1025 1016//1016 1015//1015
        -f 1015//1015 1024//1024 1025//1025
        -f 1026//1026 1018//1018 1016//1016
        -f 1016//1016 1025//1025 1026//1026
        -f 1027//1027 1020//1020 1018//1018
        -f 1018//1018 1026//1026 1027//1027
        -f 946//946 953//953 1022//1022
        -f 1028//1028 1022//1022 953//953
        -f 1022//1022 1028//1028 1023//1023
        -f 1029//1029 1023//1023 1028//1028
        -f 1023//1023 1029//1029 1024//1024
        -f 1030//1030 1024//1024 1029//1029
        -f 1031//1031 1025//1025 1024//1024
        -f 1024//1024 1030//1030 1031//1031
        -f 1032//1032 1026//1026 1025//1025
        -f 1025//1025 1031//1031 1032//1032
        -f 1033//1033 1027//1027 1026//1026
        -f 1026//1026 1032//1032 1033//1033
        -f 953//953 960//960 1028//1028
        -f 1034//1034 1028//1028 960//960
        -f 1028//1028 1034//1034 1029//1029
        -f 1035//1035 1029//1029 1034//1034
        -f 1029//1029 1035//1035 1030//1030
        -f 1036//1036 1030//1030 1035//1035
        -f 1037//1037 1031//1031 1030//1030
        -f 1030//1030 1036//1036 1037//1037
        -f 1038//1038 1032//1032 1031//1031
        -f 1031//1031 1037//1037 1038//1038
        -f 1039//1039 1033//1033 1032//1032
        -f 1032//1032 1038//1038 1039//1039
        -f 960//960 967//967 1034//1034
        -f 1040//1040 1034//1034 967//967
        -f 1034//1034 1040//1040 1035//1035
        -f 1041//1041 1035//1035 1040//1040
        -f 1035//1035 1041//1041 1036//1036
        -f 1042//1042 1036//1036 1041//1041
        -f 1043//1043 1037//1037 1036//1036
        -f 1036//1036 1042//1042 1043//1043
        -f 1044//1044 1038//1038 1037//1037
        -f 1037//1037 1043//1043 1044//1044
        -f 1045//1045 1039//1039 1038//1038
        -f 1038//1038 1044//1044 1045//1045
        -f 967//967 974//974 1040//1040
        -f 1046//1046 1040//1040 974//974
        -f 1040//1040 1046//1046 1041//1041
        -f 1047//1047 1041//1041 1046//1046
        -f 1041//1041 1047//1047 1042//1042
        -f 1048//1048 1042//1042 1047//1047
        -f 1049//1049 1043//1043 1042//1042
        -f 1042//1042 1048//1048 1049//1049
        -f 1050//1050 1044//1044 1043//1043
        -f 1043//1043 1049//1049 1050//1050
        -f 1051//1051 1045//1045 1044//1044
        -f 1044//1044 1050//1050 1051//1051
        -f 974//974 981//981 1052//1052
        -f 1052//1052 1046//1046 974//974
        -f 1046//1046 1052//1052 1053//1053
        -f 1053//1053 1047//1047 1046//1046
        -f 1047//1047 1053//1053 1054//1054
        -f 1054//1054 1048//1048 1047//1047
        -f 1055//1055 1049//1049 1054//1054
        -f 1048//1048 1054//1054 1049//1049
        -f 1056//1056 1050//1050 1055//1055
        -f 1049//1049 1055//1055 1050//1050
        -f 1057//1057 1051//1051 1056//1056
        -f 1050//1050 1056//1056 1051//1051
        -f 981//981 988//988 1058//1058
        -f 1058//1058 1052//1052 981//981
        -f 1052//1052 1058//1058 1059//1059
        -f 1059//1059 1053//1053 1052//1052
        -f 1053//1053 1059//1059 1060//1060
        -f 1060//1060 1054//1054 1053//1053
        -f 1061//1061 1055//1055 1060//1060
        -f 1054//1054 1060//1060 1055//1055
        -f 1062//1062 1056//1056 1061//1061
        -f 1055//1055 1061//1061 1056//1056
        -f 1063//1063 1057//1057 1062//1062
        -f 1056//1056 1062//1062 1057//1057
        -f 988//988 995//995 1064//1064
        -f 1064//1064 1058//1058 988//988
        -f 1058//1058 1064//1064 1065//1065
        -f 1065//1065 1059//1059 1058//1058
        -f 1059//1059 1065//1065 1066//1066
        -f 1066//1066 1060//1060 1059//1059
        -f 1067//1067 1061//1061 1066//1066
        -f 1060//1060 1066//1066 1061//1061
        -f 1068//1068 1062//1062 1067//1067
        -f 1061//1061 1067//1067 1062//1062
        -f 1069//1069 1063//1063 1068//1068
        -f 1062//1062 1068//1068 1063//1063
        -f 995//995 1002//1002 1070//1070
        -f 1070//1070 1064//1064 995//995
        -f 1064//1064 1070//1070 1071//1071
        -f 1071//1071 1065//1065 1064//1064
        -f 1065//1065 1071//1071 1072//1072
        -f 1072//1072 1066//1066 1065//1065
        -f 1073//1073 1067//1067 1072//1072
        -f 1066//1066 1072//1072 1067//1067
        -f 1074//1074 1068//1068 1073//1073
        -f 1067//1067 1073//1073 1068//1068
        -f 1075//1075 1069//1069 1074//1074
        -f 1068//1068 1074//1074 1069//1069
        -f 1002//1002 1009//1009 1076//1076
        -f 1076//1076 1070//1070 1002//1002
        -f 1070//1070 1076//1076 1077//1077
        -f 1077//1077 1071//1071 1070//1070
        -f 1071//1071 1077//1077 1078//1078
        -f 1078//1078 1072//1072 1071//1071
        -f 1079//1079 1073//1073 1078//1078
        -f 1072//1072 1078//1078 1073//1073
        -f 1080//1080 1074//1074 1079//1079
        -f 1073//1073 1079//1079 1074//1074
        -f 1081//1081 1075//1075 1080//1080
        -f 1074//1074 1080//1080 1075//1075
        -f 1009//1009 938//938 1010//1010
        -f 1010//1010 1076//1076 1009//1009
        -f 1076//1076 1010//1010 1012//1012
        -f 1012//1012 1077//1077 1076//1076
        -f 1077//1077 1012//1012 1014//1014
        -f 1014//1014 1078//1078 1077//1077
        -f 1017//1017 1079//1079 1014//1014
        -f 1078//1078 1014//1014 1079//1079
        -f 1019//1019 1080//1080 1017//1017
        -f 1079//1079 1017//1017 1080//1080
        -f 1021//1021 1081//1081 1019//1019
        -f 1080//1080 1019//1019 1081//1081
        -f 1082//1082 1083//1083 1084//1084
        -f 1084//1084 1083//1083 1085//1085
        -f 1085//1085 1083//1083 1086//1086
        -f 1086//1086 1083//1083 1087//1087
        -f 1087//1087 1083//1083 1088//1088
        -f 1088//1088 1083//1083 1089//1089
        -f 1089//1089 1083//1083 1090//1090
        -f 1090//1090 1083//1083 1091//1091
        -f 1091//1091 1083//1083 1092//1092
        -f 1092//1092 1083//1083 1093//1093
        -f 1093//1093 1083//1083 1094//1094
        -f 1094//1094 1083//1083 1095//1095
        -f 1095//1095 1083//1083 1096//1096
        -f 1096//1096 1083//1083 1097//1097
        -f 1097//1097 1083//1083 1098//1098
        -f 1098//1098 1083//1083 1099//1099
        -f 1099//1099 1083//1083 1100//1100
        -f 1100//1100 1083//1083 1101//1101
        -f 1101//1101 1083//1083 1102//1102
        -f 1102//1102 1083//1083 1103//1103
        -f 1103//1103 1083//1083 1104//1104
        -f 1104//1104 1083//1083 1105//1105
        -f 1105//1105 1083//1083 1106//1106
        -f 1106//1106 1083//1083 1082//1082
        -f 1107//1107 1108//1108 1084//1084
        -f 1082//1082 1084//1084 1108//1108
        -f 1109//1109 1110//1110 1108//1108
        -f 1108//1108 1107//1107 1109//1109
        -f 1111//1111 1112//1112 1110//1110
        -f 1110//1110 1109//1109 1111//1111
        -f 1113//1113 1114//1114 1112//1112
        -f 1112//1112 1111//1111 1113//1113
        -f 1115//1115 1107//1107 1085//1085
        -f 1084//1084 1085//1085 1107//1107
        -f 1116//1116 1109//1109 1107//1107
        -f 1107//1107 1115//1115 1116//1116
        -f 1117//1117 1111//1111 1109//1109
        -f 1109//1109 1116//1116 1117//1117
        -f 1118//1118 1113//1113 1111//1111
        -f 1111//1111 1117//1117 1118//1118
        -f 1119//1119 1115//1115 1086//1086
        -f 1085//1085 1086//1086 1115//1115
        -f 1120//1120 1116//1116 1115//1115
        -f 1115//1115 1119//1119 1120//1120
        -f 1121//1121 1117//1117 1116//1116
        -f 1116//1116 1120//1120 1121//1121
        -f 1122//1122 1118//1118 1117//1117
        -f 1117//1117 1121//1121 1122//1122
        -f 1123//1123 1119//1119 1086//1086
        -f 1086//1086 1087//1087 1123//1123
        -f 1124//1124 1120//1120 1123//1123
        -f 1119//1119 1123//1123 1120//1120
        -f 1125//1125 1121//1121 1124//1124
        -f 1120//1120 1124//1124 1121//1121
        -f 1126//1126 1122//1122 1125//1125
        -f 1121//1121 1125//1125 1122//1122
        -f 1127//1127 1123//1123 1087//1087
        -f 1087//1087 1088//1088 1127//1127
        -f 1128//1128 1124//1124 1127//1127
        -f 1123//1123 1127//1127 1124//1124
        -f 1129//1129 1125//1125 1128//1128
        -f 1124//1124 1128//1128 1125//1125
        -f 1130//1130 1126//1126 1129//1129
        -f 1125//1125 1129//1129 1126//1126
        -f 1131//1131 1127//1127 1088//1088
        -f 1088//1088 1089//1089 1131//1131
        -f 1132//1132 1128//1128 1131//1131
        -f 1127//1127 1131//1131 1128//1128
        -f 1133//1133 1129//1129 1132//1132
        -f 1128//1128 1132//1132 1129//1129
        -f 1134//1134 1130//1130 1133//1133
        -f 1129//1129 1133//1133 1130//1130
        -f 1135//1135 1131//1131 1090//1090
        -f 1089//1089 1090//1090 1131//1131
        -f 1136//1136 1132//1132 1131//1131
        -f 1131//1131 1135//1135 1136//1136
        -f 1137//1137 1133//1133 1132//1132
        -f 1132//1132 1136//1136 1137//1137
        -f 1138//1138 1134//1134 1133//1133
        -f 1133//1133 1137//1137 1138//1138
        -f 1139//1139 1135//1135 1091//1091
        -f 1090//1090 1091//1091 1135//1135
        -f 1140//1140 1136//1136 1135//1135
        -f 1135//1135 1139//1139 1140//1140
        -f 1141//1141 1137//1137 1136//1136
        -f 1136//1136 1140//1140 1141//1141
        -f 1142//1142 1138//1138 1137//1137
        -f 1137//1137 1141//1141 1142//1142
        -f 1143//1143 1139//1139 1092//1092
        -f 1091//1091 1092//1092 1139//1139
        -f 1144//1144 1140//1140 1139//1139
        -f 1139//1139 1143//1143 1144//1144
        -f 1145//1145 1141//1141 1140//1140
        -f 1140//1140 1144//1144 1145//1145
        -f 1146//1146 1142//1142 1141//1141
        -f 1141//1141 1145//1145 1146//1146
        -f 1147//1147 1143//1143 1092//1092
        -f 1092//1092 1093//1093 1147//1147
        -f 1148//1148 1144//1144 1147//1147
        -f 1143//1143 1147//1147 1144//1144
        -f 1149//1149 1145//1145 1148//1148
        -f 1144//1144 1148//1148 1145//1145
        -f 1150//1150 1146//1146 1149//1149
        -f 1145//1145 1149//1149 1146//1146
        -f 1151//1151 1147//1147 1093//1093
        -f 1093//1093 1094//1094 1151//1151
        -f 1152//1152 1148//1148 1151//1151
        -f 1147//1147 1151//1151 1148//1148
        -f 1153//1153 1149//1149 1152//1152
        -f 1148//1148 1152//1152 1149//1149
        -f 1154//1154 1150//1150 1153//1153
        -f 1149//1149 1153//1153 1150//1150
        -f 1155//1155 1151//1151 1094//1094
        -f 1094//1094 1095//1095 1155//1155
        -f 1156//1156 1152//1152 1155//1155
        -f 1151//1151 1155//1155 1152//1152
        -f 1157//1157 1153//1153 1156//1156
        -f 1152//1152 1156//1156 1153//1153
        -f 1158//1158 1154//1154 1157//1157
        -f 1153//1153 1157//1157 1154//1154
        -f 1159//1159 1155//1155 1096//1096
        -f 1095//1095 1096//1096 1155//1155
        -f 1160//1160 1156//1156 1155//1155
        -f 1155//1155 1159//1159 1160//1160
        -f 1161//1161 1157//1157 1156//1156
        -f 1156//1156 1160//1160 1161//1161
        -f 1162//1162 1158//1158 1157//1157
        -f 1157//1157 1161//1161 1162//1162
        -f 1163//1163 1159//1159 1097//1097
        -f 1096//1096 1097//1097 1159//1159
        -f 1164//1164 1160//1160 1159//1159
        -f 1159//1159 1163//1163 1164//1164
        -f 1165//1165 1161//1161 1160//1160
        -f 1160//1160 1164//1164 1165//1165
        -f 1166//1166 1162//1162 1161//1161
        -f 1161//1161 1165//1165 1166//1166
        -f 1167//1167 1163//1163 1098//1098
        -f 1097//1097 1098//1098 1163//1163
        -f 1168//1168 1164//1164 1163//1163
        -f 1163//1163 1167//1167 1168//1168
        -f 1169//1169 1165//1165 1164//1164
        -f 1164//1164 1168//1168 1169//1169
        -f 1170//1170 1166//1166 1165//1165
        -f 1165//1165 1169//1169 1170//1170
        -f 1171//1171 1167//1167 1098//1098
        -f 1098//1098 1099//1099 1171//1171
        -f 1172//1172 1168//1168 1171//1171
        -f 1167//1167 1171//1171 1168//1168
        -f 1173//1173 1169//1169 1172//1172
        -f 1168//1168 1172//1172 1169//1169
        -f 1174//1174 1170//1170 1173//1173
        -f 1169//1169 1173//1173 1170//1170
        -f 1175//1175 1171//1171 1099//1099
        -f 1099//1099 1100//1100 1175//1175
        -f 1176//1176 1172//1172 1175//1175
        -f 1171//1171 1175//1175 1172//1172
        -f 1177//1177 1173//1173 1176//1176
        -f 1172//1172 1176//1176 1173//1173
        -f 1178//1178 1174//1174 1177//1177
        -f 1173//1173 1177//1177 1174//1174
        -f 1179//1179 1175//1175 1100//1100
        -f 1100//1100 1101//1101 1179//1179
        -f 1180//1180 1176//1176 1179//1179
        -f 1175//1175 1179//1179 1176//1176
        -f 1181//1181 1177//1177 1180//1180
        -f 1176//1176 1180//1180 1177//1177
        -f 1182//1182 1178//1178 1181//1181
        -f 1177//1177 1181//1181 1178//1178
        -f 1183//1183 1179//1179 1102//1102
        -f 1101//1101 1102//1102 1179//1179
        -f 1184//1184 1180//1180 1179//1179
        -f 1179//1179 1183//1183 1184//1184
        -f 1185//1185 1181//1181 1180//1180
        -f 1180//1180 1184//1184 1185//1185
        -f 1186//1186 1182//1182 1181//1181
        -f 1181//1181 1185//1185 1186//1186
        -f 1187//1187 1183//1183 1103//1103
        -f 1102//1102 1103//1103 1183//1183
        -f 1188//1188 1184//1184 1183//1183
        -f 1183//1183 1187//1187 1188//1188
        -f 1189//1189 1185//1185 1184//1184
        -f 1184//1184 1188//1188 1189//1189
        -f 1190//1190 1186//1186 1185//1185
        -f 1185//1185 1189//1189 1190//1190
        -f 1191//1191 1187//1187 1104//1104
        -f 1103//1103 1104//1104 1187//1187
        -f 1192//1192 1188//1188 1187//1187
        -f 1187//1187 1191//1191 1192//1192
        -f 1193//1193 1189//1189 1188//1188
        -f 1188//1188 1192//1192 1193//1193
        -f 1194//1194 1190//1190 1189//1189
        -f 1189//1189 1193//1193 1194//1194
        -f 1195//1195 1191//1191 1104//1104
        -f 1104//1104 1105//1105 1195//1195
        -f 1196//1196 1192//1192 1195//1195
        -f 1191//1191 1195//1195 1192//1192
        -f 1197//1197 1193//1193 1196//1196
        -f 1192//1192 1196//1196 1193//1193
        -f 1198//1198 1194//1194 1197//1197
        -f 1193//1193 1197//1197 1194//1194
        -f 1199//1199 1195//1195 1105//1105
        -f 1105//1105 1106//1106 1199//1199
        -f 1200//1200 1196//1196 1199//1199
        -f 1195//1195 1199//1199 1196//1196
        -f 1201//1201 1197//1197 1200//1200
        -f 1196//1196 1200//1200 1197//1197
        -f 1202//1202 1198//1198 1201//1201
        -f 1197//1197 1201//1201 1198//1198
        -f 1108//1108 1199//1199 1106//1106
        -f 1106//1106 1082//1082 1108//1108
        -f 1110//1110 1200//1200 1108//1108
        -f 1199//1199 1108//1108 1200//1200
        -f 1112//1112 1201//1201 1110//1110
        -f 1200//1200 1110//1110 1201//1201
        -f 1114//1114 1202//1202 1112//1112
        -f 1201//1201 1112//1112 1202//1202
        diff --git a/docs/yuidoc-p5-theme/assets/test.txt b/docs/yuidoc-p5-theme/assets/test.txt
        deleted file mode 100644
        index 9dc2445af3..0000000000
        --- a/docs/yuidoc-p5-theme/assets/test.txt
        +++ /dev/null
        @@ -1,6 +0,0 @@
        -I am a cat
        -I like apples
        -I have three feet
        -I like my nose
        -I smell like butter
        -I talk like an orange
        \ No newline at end of file
        diff --git a/docs/yuidoc-p5-theme/assets/transformation-matrix-4-4.png b/docs/yuidoc-p5-theme/assets/transformation-matrix-4-4.png
        deleted file mode 100644
        index f4203b3312..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/transformation-matrix-4-4.png and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/assets/transformation-matrix.png b/docs/yuidoc-p5-theme/assets/transformation-matrix.png
        deleted file mode 100644
        index 0d42542922..0000000000
        Binary files a/docs/yuidoc-p5-theme/assets/transformation-matrix.png and /dev/null differ
        diff --git a/docs/yuidoc-p5-theme/helpers/helpers_prod.js b/docs/yuidoc-p5-theme/helpers/helpers_prod.js
        deleted file mode 100644
        index 42db31837d..0000000000
        --- a/docs/yuidoc-p5-theme/helpers/helpers_prod.js
        +++ /dev/null
        @@ -1,18 +0,0 @@
        -var configHelpers = {};
        -
        -// For production, the reference docs are put in the /reference/ folder
        -// of the p5 website, so we'll define our paths with this assumption.
        -// For more context, see https://github.com/processing/p5.js-website.
        -var config = {
        -  p5SiteRoot: '..',
        -  p5Lib: '../js/p5.min.js',
        -  p5SoundLib: '../js/p5.sound.min.js'
        -};
        -
        -Object.keys(config).forEach(function(key) {
        -  configHelpers[key] = function() {
        -    return config[key];
        -  };
        -});
        -
        -module.exports = configHelpers;
        diff --git a/docs/yuidoc-p5-theme/layouts/main.handlebars b/docs/yuidoc-p5-theme/layouts/main.handlebars
        deleted file mode 100755
        index e7d3a68225..0000000000
        --- a/docs/yuidoc-p5-theme/layouts/main.handlebars
        +++ /dev/null
        @@ -1,67 +0,0 @@
        -<!DOCTYPE html>
        -<html class="no-js">
        -  <head>
        -    <meta charset="utf-8">
        -    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        -    <meta name="viewport" content="width=device-width">
        -    <meta name="description" content="p5.js a JS client-side library for creating graphic and interactive experiences, based on the core principles of Processing.">
        -    <title tabindex="1">p5.js | reference</title>
        -    <link rel="stylesheet" href="{{projectAssets}}/all.css">
        -
        -    <script src="{{projectAssets}}/js/vendor/jquery-1.12.4.min.js"></script>
        -    <script src='/assets/js/p5.min.js'></script>
        -    <script src='/assets/js/p5.sound.min.js'></script>
        -    <script src="{{projectAssets}}/js/vendor/underscore-min.js"></script>
        -    <script src="{{projectAssets}}/js/vendor/backbone-min.js"></script>
        -    <script type="text/javascript" src="{{projectAssets}}/js/vendor/ace-nc/ace.js"></script>
        -    <script type="text/javascript" src="{{projectAssets}}/js/vendor/ace-nc/mode-javascript.js"></script>
        -    <script type="text/javascript" src="{{projectAssets}}/js/vendor/prism.js"></script>
        -
        -    <link rel="shortcut icon" href="{{projectAssets}}/favicon.ico">
        -    <link rel="icon" href="{{projectAssets}}/favicon.ico">
        -  </head>
        -
        -  <body id='reference'>
        -    <div id="reference-page" class="container">
        -
        -      <!-- logo -->
        -      <header id="lockup">
        -        <a href="{{p5SiteRoot}}/">
        -          <img src="{{projectAssets}}/img/p5js.svg" alt="link to p5 home page" id="logo_image" class="logo" />
        -          <div id="p5_logo"></div>
        -        </a>
        -        <p class='tagline' style='display:block !important'><span id="reference-tagline">Processing Intuition times JavaScript power</span></p>
        -      </header>
        -
        -      <div class="column-span">
        -        <section id="content" role="region" label="main content">
        -
        -          <a href="./" class="anchor"><h1>Reference</h1></a>
        -
        -          <div id="search" class="search-wrapper"></div>
        -          <div id="collection-list-nav"></div>
        -
        -          <!--class="container-fluid"-->
        -          <div id="list" tabindex="2" class="list-wrapper allItems-collection"></div>
        -          <div id="item" tabindex="1" class="item-wrapper apidocs"></div>
        -          <div id="file" class="file-wrapper"></div>
        -
        -        </section>
        -
        -        <footer>
        -          <p><span id="footer1">p5.js was created by </span><a href='http://lauren-mccarthy.com' target="_blank">Lauren McCarthy</a><span id="footer2"> and is developed by a community of collaborators, with support from the </span><a href="https://processing.org/foundation/" target="_blank">Processing Foundation</a><span id="footer3">
        -          and </span><a href="http://itp.nyu.edu/itp/" target="_blank">NYU ITP</a>. <span id="footer4">Identity and graphic design by </span><a href="http://jereljohnson.com/" target="_blank">Jerel Johnson</a>.
        -          <a href="https://p5js.org/copyright.html">&copy; Info.</a></p>
        -        </footer>
        -      </div><!-- end column-span -->
        -
        -      <!-- outside of column for footer to go across both -->
        -      <p class="clearfix"> &nbsp; </p>
        -    </div>
        -
        -    <script src="{{projectAssets}}/js/require.min.js"></script>
        -    <script src="{{projectAssets}}/js/render.js"></script>
        -    <script src="{{projectAssets}}/js/reference.js"></script>
        -
        -  </body>
        -</html>
        diff --git a/eslint.config.mjs b/eslint.config.mjs
        new file mode 100644
        index 0000000000..85716f7d93
        --- /dev/null
        +++ b/eslint.config.mjs
        @@ -0,0 +1,12 @@
        +export default [
        +  {
        +    ignores: [
        +      "preview/*",
        +      "docs/reference/assets/**/*",
        +      "docs/assets/**/*",
        +      "lib/*",
        +      "rollup.config.mjs",
        +      "utils/sample-linter.mjs"
        +    ]
        +  }
        +];
        diff --git a/lib/empty-example/index.html b/lib/empty-example/index.html
        index 54b1bfdfe2..56c88a89b8 100644
        --- a/lib/empty-example/index.html
        +++ b/lib/empty-example/index.html
        @@ -12,7 +12,7 @@
               background-color: #1b1b1b;
             }
           </style>
        -  <script src="../p5.min.js"></script>
        +  <script src="../p5.rollup.min.js"></script>
           <!-- <script src="../addons/p5.sound.js"></script> -->
           <script src="sketch.js"></script>
         </head>
        diff --git a/lib/index.html b/lib/index.html
        new file mode 100644
        index 0000000000..6e2e3837cc
        --- /dev/null
        +++ b/lib/index.html
        @@ -0,0 +1,30 @@
        +<!DOCTYPE html>
        +<html>
        +<head>
        +	<title>P5 test</title>
        +	<meta http-equiv="pragma" content="no-cache" />
        +	<meta http-equiv="cache-control" content="no-cache" />
        +	<meta charset="utf-8">
        +
        +	<script src="./p5.vite.js"></script>
        +	
        +	<style>
        +	body{
        +		margin:0;
        +		overflow: hidden;
        +	}
        +	</style>
        +</head>
        +<body>
        +<script>
        +function setup(){
        +	createCanvas(200, 200, WEBGL);
        +}
        +
        +function draw(){
        +	background(200);
        +	circle(100, 100, 50);
        +}
        +</script>
        +</body>
        +</html>
        \ No newline at end of file
        diff --git a/package-lock.json b/package-lock.json
        index 7040c18256..53cde2c1a6 100644
        --- a/package-lock.json
        +++ b/package-lock.json
        @@ -1,117 +1,109 @@
         {
           "name": "p5",
        -  "version": "1.11.3",
        +  "version": "2.0.0-beta.1",
           "lockfileVersion": 3,
           "requires": true,
           "packages": {
             "": {
               "name": "p5",
        -      "version": "1.11.3",
        +      "version": "2.0.0-beta.1",
               "license": "LGPL-2.1",
        -      "devDependencies": {
        -        "@babel/core": "^7.7.7",
        -        "@babel/preset-env": "^7.10.2",
        -        "@babel/register": "^7.7.7",
        -        "all-contributors-cli": "^6.19.0",
        -        "babel-plugin-i18next-extract": "^0.5.0",
        -        "babel-plugin-istanbul": "^5.2.0",
        -        "babelify": "^10.0.0",
        -        "brfs-babel": "^2.0.0",
        -        "browserify": "^16.5.0",
        -        "chai": "^3.5.0",
        -        "connect-modrewrite": "^0.10.1",
        -        "core-js": "^3.6.5",
        -        "derequire": "^2.0.0",
        -        "es6-promise": "^4.2.8",
        -        "eslint": "^8.23.1",
        -        "fetch-jsonp": "^1.1.3",
        +      "dependencies": {
        +        "@davepagurek/bezier-path": "^0.0.2",
        +        "acorn": "^8.12.1",
        +        "acorn-walk": "^8.3.4",
        +        "colorjs.io": "^0.5.2",
                 "file-saver": "^1.3.8",
                 "gifenc": "^1.0.3",
        -        "grunt": "^1.6.1",
        -        "grunt-cli": "^1.4.3",
        -        "grunt-contrib-clean": "^2.0.1",
        -        "grunt-contrib-connect": "^3.0.0",
        -        "grunt-contrib-uglify": "^5.2.2",
        -        "grunt-contrib-watch": "^1.1.0",
        -        "grunt-contrib-yuidoc": "1.0.0",
        -        "grunt-eslint": "^24.0.0",
        -        "grunt-minjson": "^0.4.0",
        -        "grunt-mocha-test": "^0.13.3",
        -        "grunt-newer": "^1.3.0",
        -        "grunt-simple-nyc": "^3.0.1",
        -        "html-entities": "^1.3.1",
        +        "libtess": "^1.2.2",
        +        "omggif": "^1.0.10",
        +        "pako": "^2.1.0"
        +      },
        +      "devDependencies": {
        +        "@rollup/plugin-alias": "^5.1.1",
        +        "@rollup/plugin-commonjs": "^25.0.7",
        +        "@rollup/plugin-json": "^6.1.0",
        +        "@rollup/plugin-node-resolve": "^15.2.3",
        +        "@rollup/plugin-replace": "^5.0.7",
        +        "@rollup/plugin-terser": "^0.4.4",
        +        "@vitest/browser": "^2.1.5",
        +        "all-contributors-cli": "^6.19.0",
        +        "concurrently": "^8.2.2",
        +        "dayjs": "^1.11.10",
        +        "documentation": "^14.0.3",
        +        "eslint": "^8.54.0",
                 "husky": "^4.2.3",
                 "i18next": "^19.0.2",
                 "i18next-browser-languagedetector": "^4.0.1",
        -        "libtess": "^1.2.2",
        -        "lint-staged": "^4.3.0",
        -        "marked": "^4.0.10",
        -        "mocha": "^10.2.0",
        -        "np": "^8.0.4",
        -        "omggif": "^1.0.10",
        -        "open": "^7.0.3",
        -        "opentype.js": "^0.9.0",
        -        "pretty-fast": "^0.2.7",
        -        "promise-map-series": "^0.2.3",
        -        "puppeteer": "^18.2.1",
        -        "regenerator-runtime": "^0.13.3",
        -        "simple-git": "^3.16.1",
        -        "whatwg-fetch": "^2.0.4"
        +        "lint-staged": "^15.1.0",
        +        "msw": "^2.6.3",
        +        "rollup": "^4.9.6",
        +        "rollup-plugin-string": "^3.0.0",
        +        "rollup-plugin-visualizer": "^5.12.0",
        +        "vite": "^5.0.2",
        +        "vite-plugin-string": "^1.2.2",
        +        "vitest": "^2.1.5",
        +        "webdriverio": "^9.0.7",
        +        "zod": "^3.23.8"
        +      }
        +    },
        +    "node_modules/@ampproject/remapping": {
        +      "version": "2.3.0",
        +      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
        +      "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
        +      "dev": true,
        +      "dependencies": {
        +        "@jridgewell/gen-mapping": "^0.3.5",
        +        "@jridgewell/trace-mapping": "^0.3.24"
        +      },
        +      "engines": {
        +        "node": ">=6.0.0"
               }
             },
             "node_modules/@babel/code-frame": {
        -      "version": "7.22.13",
        -      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
        -      "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
        +      "version": "7.26.2",
        +      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
        +      "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
               "dev": true,
               "dependencies": {
        -        "@babel/highlight": "^7.22.13",
        -        "chalk": "^2.4.2"
        +        "@babel/helper-validator-identifier": "^7.25.9",
        +        "js-tokens": "^4.0.0",
        +        "picocolors": "^1.0.0"
               },
               "engines": {
                 "node": ">=6.9.0"
               }
             },
             "node_modules/@babel/compat-data": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.1.tgz",
        -      "integrity": "sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw==",
        +      "version": "7.26.5",
        +      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz",
        +      "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==",
               "dev": true,
        -      "dependencies": {
        -        "browserslist": "^4.12.0",
        -        "invariant": "^2.2.4",
        -        "semver": "^5.5.0"
        -      }
        -    },
        -    "node_modules/@babel/compat-data/node_modules/semver": {
        -      "version": "5.7.1",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
        -      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
        -      "dev": true,
        -      "bin": {
        -        "semver": "bin/semver"
        +      "engines": {
        +        "node": ">=6.9.0"
               }
             },
             "node_modules/@babel/core": {
        -      "version": "7.7.7",
        -      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.7.tgz",
        -      "integrity": "sha512-jlSjuj/7z138NLZALxVgrx13AOtqip42ATZP7+kYl53GvDV6+4dCek1mVUo8z8c8Xnw/mx2q3d9HWh3griuesQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "@babel/code-frame": "^7.5.5",
        -        "@babel/generator": "^7.7.7",
        -        "@babel/helpers": "^7.7.4",
        -        "@babel/parser": "^7.7.7",
        -        "@babel/template": "^7.7.4",
        -        "@babel/traverse": "^7.7.4",
        -        "@babel/types": "^7.7.4",
        -        "convert-source-map": "^1.7.0",
        +      "version": "7.26.0",
        +      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
        +      "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
        +      "dev": true,
        +      "dependencies": {
        +        "@ampproject/remapping": "^2.2.0",
        +        "@babel/code-frame": "^7.26.0",
        +        "@babel/generator": "^7.26.0",
        +        "@babel/helper-compilation-targets": "^7.25.9",
        +        "@babel/helper-module-transforms": "^7.26.0",
        +        "@babel/helpers": "^7.26.0",
        +        "@babel/parser": "^7.26.0",
        +        "@babel/template": "^7.25.9",
        +        "@babel/traverse": "^7.25.9",
        +        "@babel/types": "^7.26.0",
        +        "convert-source-map": "^2.0.0",
                 "debug": "^4.1.0",
        -        "json5": "^2.1.0",
        -        "lodash": "^4.17.13",
        -        "resolve": "^1.3.2",
        -        "semver": "^5.4.1",
        -        "source-map": "^0.5.0"
        +        "gensync": "^1.0.0-beta.2",
        +        "json5": "^2.2.3",
        +        "semver": "^6.3.1"
               },
               "engines": {
                 "node": ">=6.9.0"
        @@ -122,13 +114,10 @@
               }
             },
             "node_modules/@babel/core/node_modules/convert-source-map": {
        -      "version": "1.7.0",
        -      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
        -      "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
        -      "dev": true,
        -      "dependencies": {
        -        "safe-buffer": "~5.1.1"
        -      }
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
        +      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
        +      "dev": true
             },
             "node_modules/@babel/core/node_modules/debug": {
               "version": "4.1.1",
        @@ -146,2062 +135,2064 @@
               "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
               "dev": true
             },
        -    "node_modules/@babel/core/node_modules/source-map": {
        -      "version": "0.5.7",
        -      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
        -      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
        +    "node_modules/@babel/core/node_modules/semver": {
        +      "version": "6.3.1",
        +      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
        +      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "bin": {
        +        "semver": "bin/semver.js"
               }
             },
             "node_modules/@babel/generator": {
        -      "version": "7.23.0",
        -      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
        -      "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
        +      "version": "7.26.5",
        +      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz",
        +      "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
               "dev": true,
               "dependencies": {
        -        "@babel/types": "^7.23.0",
        -        "@jridgewell/gen-mapping": "^0.3.2",
        -        "@jridgewell/trace-mapping": "^0.3.17",
        -        "jsesc": "^2.5.1"
        +        "@babel/parser": "^7.26.5",
        +        "@babel/types": "^7.26.5",
        +        "@jridgewell/gen-mapping": "^0.3.5",
        +        "@jridgewell/trace-mapping": "^0.3.25",
        +        "jsesc": "^3.0.2"
               },
               "engines": {
                 "node": ">=6.9.0"
               }
             },
        -    "node_modules/@babel/helper-annotate-as-pure": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz",
        -      "integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==",
        -      "dev": true,
        -      "dependencies": {
        -        "@babel/types": "^7.10.1"
        -      }
        -    },
        -    "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz",
        -      "integrity": "sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw==",
        +    "node_modules/@babel/helper-compilation-targets": {
        +      "version": "7.26.5",
        +      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz",
        +      "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-explode-assignable-expression": "^7.10.1",
        -        "@babel/types": "^7.10.1"
        +        "@babel/compat-data": "^7.26.5",
        +        "@babel/helper-validator-option": "^7.25.9",
        +        "browserslist": "^4.24.0",
        +        "lru-cache": "^5.1.1",
        +        "semver": "^6.3.1"
        +      },
        +      "engines": {
        +        "node": ">=6.9.0"
               }
             },
        -    "node_modules/@babel/helper-compilation-targets": {
        -      "version": "7.10.2",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz",
        -      "integrity": "sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA==",
        +    "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
        +      "version": "5.1.1",
        +      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
        +      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
               "dev": true,
               "dependencies": {
        -        "@babel/compat-data": "^7.10.1",
        -        "browserslist": "^4.12.0",
        -        "invariant": "^2.2.4",
        -        "levenary": "^1.1.1",
        -        "semver": "^5.5.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0"
        +        "yallist": "^3.0.2"
               }
             },
             "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
        -      "version": "5.7.1",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
        -      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
        +      "version": "6.3.1",
        +      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
        +      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
               "dev": true,
               "bin": {
        -        "semver": "bin/semver"
        +        "semver": "bin/semver.js"
               }
             },
        -    "node_modules/@babel/helper-create-class-features-plugin": {
        -      "version": "7.10.2",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz",
        -      "integrity": "sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ==",
        +    "node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
        +      "version": "3.1.1",
        +      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
        +      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
        +      "dev": true
        +    },
        +    "node_modules/@babel/helper-module-imports": {
        +      "version": "7.25.9",
        +      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
        +      "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-function-name": "^7.10.1",
        -        "@babel/helper-member-expression-to-functions": "^7.10.1",
        -        "@babel/helper-optimise-call-expression": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/helper-replace-supers": "^7.10.1",
        -        "@babel/helper-split-export-declaration": "^7.10.1"
        +        "@babel/traverse": "^7.25.9",
        +        "@babel/types": "^7.25.9"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0"
        +      "engines": {
        +        "node": ">=6.9.0"
               }
             },
        -    "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/helper-create-regexp-features-plugin": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz",
        -      "integrity": "sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA==",
        +    "node_modules/@babel/helper-module-transforms": {
        +      "version": "7.26.0",
        +      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
        +      "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-annotate-as-pure": "^7.10.1",
        -        "@babel/helper-regex": "^7.10.1",
        -        "regexpu-core": "^4.7.0"
        +        "@babel/helper-module-imports": "^7.25.9",
        +        "@babel/helper-validator-identifier": "^7.25.9",
        +        "@babel/traverse": "^7.25.9"
        +      },
        +      "engines": {
        +        "node": ">=6.9.0"
               },
               "peerDependencies": {
                 "@babel/core": "^7.0.0"
               }
             },
        -    "node_modules/@babel/helper-define-map": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz",
        -      "integrity": "sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg==",
        +    "node_modules/@babel/helper-string-parser": {
        +      "version": "7.25.9",
        +      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
        +      "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-function-name": "^7.10.1",
        -        "@babel/types": "^7.10.1",
        -        "lodash": "^4.17.13"
        +      "engines": {
        +        "node": ">=6.9.0"
               }
             },
        -    "node_modules/@babel/helper-environment-visitor": {
        -      "version": "7.22.20",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
        -      "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
        +    "node_modules/@babel/helper-validator-identifier": {
        +      "version": "7.25.9",
        +      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
        +      "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
               "dev": true,
               "engines": {
                 "node": ">=6.9.0"
               }
             },
        -    "node_modules/@babel/helper-explode-assignable-expression": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz",
        -      "integrity": "sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg==",
        +    "node_modules/@babel/helper-validator-option": {
        +      "version": "7.25.9",
        +      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
        +      "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/traverse": "^7.10.1",
        -        "@babel/types": "^7.10.1"
        +      "engines": {
        +        "node": ">=6.9.0"
               }
             },
        -    "node_modules/@babel/helper-function-name": {
        -      "version": "7.23.0",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
        -      "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
        +    "node_modules/@babel/helpers": {
        +      "version": "7.26.0",
        +      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
        +      "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
               "dev": true,
               "dependencies": {
        -        "@babel/template": "^7.22.15",
        -        "@babel/types": "^7.23.0"
        +        "@babel/template": "^7.25.9",
        +        "@babel/types": "^7.26.0"
               },
               "engines": {
                 "node": ">=6.9.0"
               }
             },
        -    "node_modules/@babel/helper-get-function-arity": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz",
        -      "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==",
        +    "node_modules/@babel/parser": {
        +      "version": "7.26.5",
        +      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz",
        +      "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==",
               "dev": true,
               "dependencies": {
        -        "@babel/types": "^7.10.1"
        +        "@babel/types": "^7.26.5"
        +      },
        +      "bin": {
        +        "parser": "bin/babel-parser.js"
        +      },
        +      "engines": {
        +        "node": ">=6.0.0"
               }
             },
        -    "node_modules/@babel/helper-hoist-variables": {
        -      "version": "7.22.5",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
        -      "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
        +    "node_modules/@babel/runtime": {
        +      "version": "7.26.0",
        +      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
        +      "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
               "dev": true,
               "dependencies": {
        -        "@babel/types": "^7.22.5"
        +        "regenerator-runtime": "^0.14.0"
               },
               "engines": {
                 "node": ">=6.9.0"
               }
             },
        -    "node_modules/@babel/helper-member-expression-to-functions": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz",
        -      "integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==",
        -      "dev": true,
        -      "dependencies": {
        -        "@babel/types": "^7.10.1"
        -      }
        -    },
        -    "node_modules/@babel/helper-module-imports": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz",
        -      "integrity": "sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==",
        +    "node_modules/@babel/template": {
        +      "version": "7.25.9",
        +      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
        +      "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
               "dev": true,
               "dependencies": {
        -        "@babel/types": "^7.10.1"
        +        "@babel/code-frame": "^7.25.9",
        +        "@babel/parser": "^7.25.9",
        +        "@babel/types": "^7.25.9"
        +      },
        +      "engines": {
        +        "node": ">=6.9.0"
               }
             },
        -    "node_modules/@babel/helper-module-transforms": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz",
        -      "integrity": "sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==",
        +    "node_modules/@babel/traverse": {
        +      "version": "7.26.5",
        +      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.5.tgz",
        +      "integrity": "sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-module-imports": "^7.10.1",
        -        "@babel/helper-replace-supers": "^7.10.1",
        -        "@babel/helper-simple-access": "^7.10.1",
        -        "@babel/helper-split-export-declaration": "^7.10.1",
        -        "@babel/template": "^7.10.1",
        -        "@babel/types": "^7.10.1",
        -        "lodash": "^4.17.13"
        +        "@babel/code-frame": "^7.26.2",
        +        "@babel/generator": "^7.26.5",
        +        "@babel/parser": "^7.26.5",
        +        "@babel/template": "^7.25.9",
        +        "@babel/types": "^7.26.5",
        +        "debug": "^4.3.1",
        +        "globals": "^11.1.0"
        +      },
        +      "engines": {
        +        "node": ">=6.9.0"
               }
             },
        -    "node_modules/@babel/helper-optimise-call-expression": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz",
        -      "integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==",
        +    "node_modules/@babel/traverse/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
               "dependencies": {
        -        "@babel/types": "^7.10.1"
        +        "ms": "^2.1.3"
        +      },
        +      "engines": {
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.0.0",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
        -      "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==",
        +    "node_modules/@babel/traverse/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
               "dev": true
             },
        -    "node_modules/@babel/helper-regex": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.1.tgz",
        -      "integrity": "sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g==",
        +    "node_modules/@babel/types": {
        +      "version": "7.26.5",
        +      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz",
        +      "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==",
               "dev": true,
               "dependencies": {
        -        "lodash": "^4.17.13"
        +        "@babel/helper-string-parser": "^7.25.9",
        +        "@babel/helper-validator-identifier": "^7.25.9"
        +      },
        +      "engines": {
        +        "node": ">=6.9.0"
               }
             },
        -    "node_modules/@babel/helper-remap-async-to-generator": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz",
        -      "integrity": "sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A==",
        +    "node_modules/@bundled-es-modules/cookie": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz",
        +      "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-annotate-as-pure": "^7.10.1",
        -        "@babel/helper-wrap-function": "^7.10.1",
        -        "@babel/template": "^7.10.1",
        -        "@babel/traverse": "^7.10.1",
        -        "@babel/types": "^7.10.1"
        +        "cookie": "^0.7.2"
               }
             },
        -    "node_modules/@babel/helper-replace-supers": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz",
        -      "integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==",
        +    "node_modules/@bundled-es-modules/cookie/node_modules/cookie": {
        +      "version": "0.7.2",
        +      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
        +      "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-member-expression-to-functions": "^7.10.1",
        -        "@babel/helper-optimise-call-expression": "^7.10.1",
        -        "@babel/traverse": "^7.10.1",
        -        "@babel/types": "^7.10.1"
        +      "engines": {
        +        "node": ">= 0.6"
               }
             },
        -    "node_modules/@babel/helper-simple-access": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz",
        -      "integrity": "sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==",
        +    "node_modules/@bundled-es-modules/statuses": {
        +      "version": "1.0.1",
        +      "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz",
        +      "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==",
               "dev": true,
               "dependencies": {
        -        "@babel/template": "^7.10.1",
        -        "@babel/types": "^7.10.1"
        +        "statuses": "^2.0.1"
               }
             },
        -    "node_modules/@babel/helper-split-export-declaration": {
        -      "version": "7.22.6",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
        -      "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
        +    "node_modules/@bundled-es-modules/statuses/node_modules/statuses": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
        +      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/types": "^7.22.5"
        -      },
               "engines": {
        -        "node": ">=6.9.0"
        +        "node": ">= 0.8"
               }
             },
        -    "node_modules/@babel/helper-string-parser": {
        -      "version": "7.22.5",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
        -      "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
        +    "node_modules/@bundled-es-modules/tough-cookie": {
        +      "version": "0.1.6",
        +      "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz",
        +      "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==",
               "dev": true,
        -      "engines": {
        -        "node": ">=6.9.0"
        +      "dependencies": {
        +        "@types/tough-cookie": "^4.0.5",
        +        "tough-cookie": "^4.1.4"
               }
             },
        -    "node_modules/@babel/helper-validator-identifier": {
        -      "version": "7.22.20",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
        -      "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
        +    "node_modules/@davepagurek/bezier-path": {
        +      "version": "0.0.2",
        +      "resolved": "https://registry.npmjs.org/@davepagurek/bezier-path/-/bezier-path-0.0.2.tgz",
        +      "integrity": "sha512-4L9ddgzZc9DRGyl1RrS3z5nwnVJoyjsAelVG4X1jh4tVxryEHr4H9QavhxW/my6Rn3669Qz6mhv8gd5O/WeFTA=="
        +    },
        +    "node_modules/@esbuild/aix-ppc64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
        +      "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
        +      "cpu": [
        +        "ppc64"
        +      ],
               "dev": true,
        +      "optional": true,
        +      "os": [
        +        "aix"
        +      ],
               "engines": {
        -        "node": ">=6.9.0"
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/helper-wrap-function": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz",
        -      "integrity": "sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ==",
        +    "node_modules/@esbuild/android-arm": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
        +      "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
        +      "cpu": [
        +        "arm"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-function-name": "^7.10.1",
        -        "@babel/template": "^7.10.1",
        -        "@babel/traverse": "^7.10.1",
        -        "@babel/types": "^7.10.1"
        +      "optional": true,
        +      "os": [
        +        "android"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/helpers": {
        -      "version": "7.7.4",
        -      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.7.4.tgz",
        -      "integrity": "sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg==",
        +    "node_modules/@esbuild/android-arm64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
        +      "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
        +      "cpu": [
        +        "arm64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/template": "^7.7.4",
        -        "@babel/traverse": "^7.7.4",
        -        "@babel/types": "^7.7.4"
        +      "optional": true,
        +      "os": [
        +        "android"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/highlight": {
        -      "version": "7.22.20",
        -      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
        -      "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
        +    "node_modules/@esbuild/android-x64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
        +      "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
        +      "cpu": [
        +        "x64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-validator-identifier": "^7.22.20",
        -        "chalk": "^2.4.2",
        -        "js-tokens": "^4.0.0"
        -      },
        +      "optional": true,
        +      "os": [
        +        "android"
        +      ],
               "engines": {
        -        "node": ">=6.9.0"
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/parser": {
        -      "version": "7.23.0",
        -      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
        -      "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
        +    "node_modules/@esbuild/darwin-arm64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
        +      "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
        +      "cpu": [
        +        "arm64"
        +      ],
               "dev": true,
        -      "bin": {
        -        "parser": "bin/babel-parser.js"
        -      },
        +      "optional": true,
        +      "os": [
        +        "darwin"
        +      ],
               "engines": {
        -        "node": ">=6.0.0"
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-async-generator-functions": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz",
        -      "integrity": "sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw==",
        +    "node_modules/@esbuild/darwin-x64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
        +      "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
        +      "cpu": [
        +        "x64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/helper-remap-async-to-generator": "^7.10.1",
        -        "@babel/plugin-syntax-async-generators": "^7.8.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "darwin"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-async-generator-functions/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-proposal-class-properties": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz",
        -      "integrity": "sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw==",
        +    "node_modules/@esbuild/freebsd-arm64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
        +      "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
        +      "cpu": [
        +        "arm64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-create-class-features-plugin": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "freebsd"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-class-properties/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-proposal-dynamic-import": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz",
        -      "integrity": "sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA==",
        +    "node_modules/@esbuild/freebsd-x64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
        +      "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
        +      "cpu": [
        +        "x64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/plugin-syntax-dynamic-import": "^7.8.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "freebsd"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-dynamic-import/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-proposal-json-strings": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz",
        -      "integrity": "sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg==",
        +    "node_modules/@esbuild/linux-arm": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
        +      "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
        +      "cpu": [
        +        "arm"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/plugin-syntax-json-strings": "^7.8.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-json-strings/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz",
        -      "integrity": "sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA==",
        +    "node_modules/@esbuild/linux-arm64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
        +      "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
        +      "cpu": [
        +        "arm64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-nullish-coalescing-operator/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-proposal-numeric-separator": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz",
        -      "integrity": "sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA==",
        +    "node_modules/@esbuild/linux-ia32": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
        +      "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
        +      "cpu": [
        +        "ia32"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/plugin-syntax-numeric-separator": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-numeric-separator/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-proposal-object-rest-spread": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz",
        -      "integrity": "sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ==",
        +    "node_modules/@esbuild/linux-loong64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
        +      "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
        +      "cpu": [
        +        "loong64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
        -        "@babel/plugin-transform-parameters": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-object-rest-spread/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-proposal-optional-catch-binding": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz",
        -      "integrity": "sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA==",
        +    "node_modules/@esbuild/linux-mips64el": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
        +      "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
        +      "cpu": [
        +        "mips64el"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/plugin-syntax-optional-catch-binding": "^7.8.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-optional-catch-binding/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-proposal-optional-chaining": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz",
        -      "integrity": "sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA==",
        +    "node_modules/@esbuild/linux-ppc64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
        +      "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
        +      "cpu": [
        +        "ppc64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/plugin-syntax-optional-chaining": "^7.8.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-optional-chaining/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-proposal-private-methods": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz",
        -      "integrity": "sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg==",
        +    "node_modules/@esbuild/linux-riscv64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
        +      "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
        +      "cpu": [
        +        "riscv64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-create-class-features-plugin": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-private-methods/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-proposal-unicode-property-regex": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz",
        -      "integrity": "sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ==",
        +    "node_modules/@esbuild/linux-s390x": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
        +      "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
        +      "cpu": [
        +        "s390x"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-create-regexp-features-plugin": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ],
               "engines": {
        -        "node": ">=4"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-proposal-unicode-property-regex/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-syntax-async-generators": {
        -      "version": "7.8.4",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
        -      "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
        +    "node_modules/@esbuild/linux-x64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
        +      "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
        +      "cpu": [
        +        "x64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.8.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-syntax-async-generators/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-syntax-class-properties": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz",
        -      "integrity": "sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ==",
        +    "node_modules/@esbuild/netbsd-x64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
        +      "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
        +      "cpu": [
        +        "x64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "netbsd"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-syntax-class-properties/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-syntax-dynamic-import": {
        -      "version": "7.8.3",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
        -      "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
        +    "node_modules/@esbuild/openbsd-x64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
        +      "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
        +      "cpu": [
        +        "x64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.8.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "openbsd"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-syntax-dynamic-import/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-syntax-json-strings": {
        -      "version": "7.8.3",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
        -      "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
        +    "node_modules/@esbuild/sunos-x64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
        +      "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
        +      "cpu": [
        +        "x64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.8.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "sunos"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-syntax-json-strings/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
        -      "version": "7.8.3",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
        -      "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
        +    "node_modules/@esbuild/win32-arm64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
        +      "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
        +      "cpu": [
        +        "arm64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.8.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "win32"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-syntax-nullish-coalescing-operator/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-syntax-numeric-separator": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz",
        -      "integrity": "sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg==",
        +    "node_modules/@esbuild/win32-ia32": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
        +      "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
        +      "cpu": [
        +        "ia32"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "win32"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-syntax-numeric-separator/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-syntax-object-rest-spread": {
        -      "version": "7.8.3",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
        -      "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
        +    "node_modules/@esbuild/win32-x64": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
        +      "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
        +      "cpu": [
        +        "x64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.8.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "optional": true,
        +      "os": [
        +        "win32"
        +      ],
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-syntax-object-rest-spread/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-syntax-optional-catch-binding": {
        -      "version": "7.8.3",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
        -      "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
        +    "node_modules/@eslint-community/eslint-utils": {
        +      "version": "4.4.1",
        +      "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
        +      "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.8.0"
        +        "eslint-visitor-keys": "^3.4.3"
        +      },
        +      "engines": {
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        +      },
        +      "funding": {
        +        "url": "https://opencollective.com/eslint"
               },
               "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +        "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
               }
             },
        -    "node_modules/@babel/plugin-syntax-optional-catch-binding/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        +    "node_modules/@eslint-community/regexpp": {
        +      "version": "4.12.1",
        +      "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
        +      "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
        +      "dev": true,
        +      "engines": {
        +        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
        +      }
             },
        -    "node_modules/@babel/plugin-syntax-optional-chaining": {
        -      "version": "7.8.3",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
        -      "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
        +    "node_modules/@eslint/eslintrc": {
        +      "version": "2.1.4",
        +      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
        +      "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.8.0"
        +        "ajv": "^6.12.4",
        +        "debug": "^4.3.2",
        +        "espree": "^9.6.0",
        +        "globals": "^13.19.0",
        +        "ignore": "^5.2.0",
        +        "import-fresh": "^3.2.1",
        +        "js-yaml": "^4.1.0",
        +        "minimatch": "^3.1.2",
        +        "strip-json-comments": "^3.1.1"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        +      },
        +      "funding": {
        +        "url": "https://opencollective.com/eslint"
               }
             },
        -    "node_modules/@babel/plugin-syntax-optional-chaining/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        +    "node_modules/@eslint/eslintrc/node_modules/argparse": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
        +      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
               "dev": true
             },
        -    "node_modules/@babel/plugin-syntax-top-level-await": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz",
        -      "integrity": "sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ==",
        +    "node_modules/@eslint/eslintrc/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "ms": "^2.1.3"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/@babel/plugin-syntax-top-level-await/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-arrow-functions": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz",
        -      "integrity": "sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA==",
        +    "node_modules/@eslint/eslintrc/node_modules/globals": {
        +      "version": "13.24.0",
        +      "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
        +      "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "type-fest": "^0.20.2"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/@babel/plugin-transform-arrow-functions/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-async-to-generator": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz",
        -      "integrity": "sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg==",
        +    "node_modules/@eslint/eslintrc/node_modules/js-yaml": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
        +      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-module-imports": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/helper-remap-async-to-generator": "^7.10.1"
        +        "argparse": "^2.0.1"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "bin": {
        +        "js-yaml": "bin/js-yaml.js"
               }
             },
        -    "node_modules/@babel/plugin-transform-async-to-generator/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-block-scoped-functions": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz",
        -      "integrity": "sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q==",
        +    "node_modules/@eslint/eslintrc/node_modules/minimatch": {
        +      "version": "3.1.2",
        +      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
        +      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "brace-expansion": "^1.1.7"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": "*"
               }
             },
        -    "node_modules/@babel/plugin-transform-block-scoped-functions/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        +    "node_modules/@eslint/eslintrc/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
               "dev": true
             },
        -    "node_modules/@babel/plugin-transform-block-scoping": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz",
        -      "integrity": "sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw==",
        +    "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": {
        +      "version": "3.1.1",
        +      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
        +      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "lodash": "^4.17.13"
        +      "engines": {
        +        "node": ">=8"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/@babel/plugin-transform-block-scoping/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-classes": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz",
        -      "integrity": "sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ==",
        +    "node_modules/@eslint/eslintrc/node_modules/type-fest": {
        +      "version": "0.20.2",
        +      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
        +      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-annotate-as-pure": "^7.10.1",
        -        "@babel/helper-define-map": "^7.10.1",
        -        "@babel/helper-function-name": "^7.10.1",
        -        "@babel/helper-optimise-call-expression": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/helper-replace-supers": "^7.10.1",
        -        "@babel/helper-split-export-declaration": "^7.10.1",
        -        "globals": "^11.1.0"
        +      "engines": {
        +        "node": ">=10"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        +    "node_modules/@eslint/js": {
        +      "version": "8.57.1",
        +      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
        +      "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
        +      "dev": true,
        +      "engines": {
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        +      }
             },
        -    "node_modules/@babel/plugin-transform-computed-properties": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz",
        -      "integrity": "sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ==",
        +    "node_modules/@humanwhocodes/config-array": {
        +      "version": "0.13.0",
        +      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
        +      "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
        +      "deprecated": "Use @eslint/config-array instead",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "@humanwhocodes/object-schema": "^2.0.3",
        +        "debug": "^4.3.1",
        +        "minimatch": "^3.0.5"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=10.10.0"
               }
             },
        -    "node_modules/@babel/plugin-transform-computed-properties/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-destructuring": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz",
        -      "integrity": "sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA==",
        +    "node_modules/@humanwhocodes/config-array/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "ms": "^2.1.3"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/@babel/plugin-transform-destructuring/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        +    "node_modules/@humanwhocodes/config-array/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
               "dev": true
             },
        -    "node_modules/@babel/plugin-transform-dotall-regex": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz",
        -      "integrity": "sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA==",
        +    "node_modules/@humanwhocodes/module-importer": {
        +      "version": "1.0.1",
        +      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
        +      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-create-regexp-features-plugin": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +      "engines": {
        +        "node": ">=12.22"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/nzakas"
               }
             },
        -    "node_modules/@babel/plugin-transform-dotall-regex/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        +    "node_modules/@humanwhocodes/object-schema": {
        +      "version": "2.0.3",
        +      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
        +      "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
        +      "deprecated": "Use @eslint/object-schema instead",
               "dev": true
             },
        -    "node_modules/@babel/plugin-transform-duplicate-keys": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz",
        -      "integrity": "sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA==",
        +    "node_modules/@inquirer/confirm": {
        +      "version": "5.1.3",
        +      "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.3.tgz",
        +      "integrity": "sha512-fuF9laMmHoOgWapF9h9hv6opA5WvmGFHsTYGCmuFxcghIhEhb3dN0CdQR4BUMqa2H506NCj8cGX4jwMsE4t6dA==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "@inquirer/core": "^10.1.4",
        +        "@inquirer/type": "^3.0.2"
        +      },
        +      "engines": {
        +        "node": ">=18"
               },
               "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +        "@types/node": ">=18"
               }
             },
        -    "node_modules/@babel/plugin-transform-duplicate-keys/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-exponentiation-operator": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz",
        -      "integrity": "sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA==",
        +    "node_modules/@inquirer/core": {
        +      "version": "10.1.4",
        +      "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.4.tgz",
        +      "integrity": "sha512-5y4/PUJVnRb4bwWY67KLdebWOhOc7xj5IP2J80oWXa64mVag24rwQ1VAdnj7/eDY/odhguW0zQ1Mp1pj6fO/2w==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "@inquirer/figures": "^1.0.9",
        +        "@inquirer/type": "^3.0.2",
        +        "ansi-escapes": "^4.3.2",
        +        "cli-width": "^4.1.0",
        +        "mute-stream": "^2.0.0",
        +        "signal-exit": "^4.1.0",
        +        "strip-ansi": "^6.0.1",
        +        "wrap-ansi": "^6.2.0",
        +        "yoctocolors-cjs": "^2.1.2"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=18"
               }
             },
        -    "node_modules/@babel/plugin-transform-exponentiation-operator/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-for-of": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz",
        -      "integrity": "sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w==",
        +    "node_modules/@inquirer/core/node_modules/ansi-regex": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=8"
               }
             },
        -    "node_modules/@babel/plugin-transform-for-of/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-function-name": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz",
        -      "integrity": "sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw==",
        +    "node_modules/@inquirer/core/node_modules/cli-width": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
        +      "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-function-name": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">= 12"
               }
             },
        -    "node_modules/@babel/plugin-transform-function-name/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-literals": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz",
        -      "integrity": "sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw==",
        +    "node_modules/@inquirer/core/node_modules/mute-stream": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
        +      "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": "^18.17.0 || >=20.5.0"
               }
             },
        -    "node_modules/@babel/plugin-transform-literals/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-member-expression-literals": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz",
        -      "integrity": "sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA==",
        +    "node_modules/@inquirer/core/node_modules/signal-exit": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
        +      "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +      "engines": {
        +        "node": ">=14"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/@babel/plugin-transform-member-expression-literals/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-modules-amd": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz",
        -      "integrity": "sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw==",
        +    "node_modules/@inquirer/core/node_modules/strip-ansi": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        +      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-module-transforms": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "babel-plugin-dynamic-import-node": "^2.3.3"
        +        "ansi-regex": "^5.0.1"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=8"
               }
             },
        -    "node_modules/@babel/plugin-transform-modules-amd/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-modules-commonjs": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz",
        -      "integrity": "sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg==",
        +    "node_modules/@inquirer/figures": {
        +      "version": "1.0.9",
        +      "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.9.tgz",
        +      "integrity": "sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-module-transforms": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/helper-simple-access": "^7.10.1",
        -        "babel-plugin-dynamic-import-node": "^2.3.3"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=18"
               }
             },
        -    "node_modules/@babel/plugin-transform-modules-commonjs/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-modules-systemjs": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz",
        -      "integrity": "sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA==",
        +    "node_modules/@inquirer/type": {
        +      "version": "3.0.2",
        +      "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.2.tgz",
        +      "integrity": "sha512-ZhQ4TvhwHZF+lGhQ2O/rsjo80XoZR5/5qhOY3t6FJuX5XBg5Be8YzYTvaUGJnc12AUGI2nr4QSUE4PhKSigx7g==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-hoist-variables": "^7.10.1",
        -        "@babel/helper-module-transforms": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "babel-plugin-dynamic-import-node": "^2.3.3"
        +      "engines": {
        +        "node": ">=18"
               },
               "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +        "@types/node": ">=18"
               }
             },
        -    "node_modules/@babel/plugin-transform-modules-systemjs/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-modules-umd": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz",
        -      "integrity": "sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA==",
        +    "node_modules/@isaacs/cliui": {
        +      "version": "8.0.2",
        +      "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
        +      "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-module-transforms": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "string-width": "^5.1.2",
        +        "string-width-cjs": "npm:string-width@^4.2.0",
        +        "strip-ansi": "^7.0.1",
        +        "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
        +        "wrap-ansi": "^8.1.0",
        +        "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/plugin-transform-modules-umd/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
        -      "version": "7.8.3",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz",
        -      "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==",
        +    "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
        +      "version": "6.1.0",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
        +      "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-create-regexp-features-plugin": "^7.8.3"
        +      "engines": {
        +        "node": ">=12"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0"
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
               }
             },
        -    "node_modules/@babel/plugin-transform-new-target": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz",
        -      "integrity": "sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw==",
        +    "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
        +      "version": "6.2.1",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
        +      "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +      "engines": {
        +        "node": ">=12"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/@babel/plugin-transform-new-target/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        +    "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
        +      "version": "9.2.2",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
        +      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
               "dev": true
             },
        -    "node_modules/@babel/plugin-transform-object-super": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz",
        -      "integrity": "sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw==",
        +    "node_modules/@isaacs/cliui/node_modules/string-width": {
        +      "version": "5.1.2",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
        +      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/helper-replace-supers": "^7.10.1"
        +        "eastasianwidth": "^0.2.0",
        +        "emoji-regex": "^9.2.2",
        +        "strip-ansi": "^7.0.1"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/@babel/plugin-transform-object-super/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-parameters": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz",
        -      "integrity": "sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==",
        +    "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
        +      "version": "7.1.0",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
        +      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-get-function-arity": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "ansi-regex": "^6.0.1"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
               }
             },
        -    "node_modules/@babel/plugin-transform-parameters/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-property-literals": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz",
        -      "integrity": "sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA==",
        +    "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
        +      "version": "8.1.0",
        +      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
        +      "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "ansi-styles": "^6.1.0",
        +        "string-width": "^5.0.1",
        +        "strip-ansi": "^7.0.1"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
               }
             },
        -    "node_modules/@babel/plugin-transform-property-literals/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-regenerator": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz",
        -      "integrity": "sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw==",
        +    "node_modules/@jridgewell/gen-mapping": {
        +      "version": "0.3.8",
        +      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
        +      "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
               "dev": true,
               "dependencies": {
        -        "regenerator-transform": "^0.14.2"
        +        "@jridgewell/set-array": "^1.2.1",
        +        "@jridgewell/sourcemap-codec": "^1.4.10",
        +        "@jridgewell/trace-mapping": "^0.3.24"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=6.0.0"
               }
             },
        -    "node_modules/@babel/plugin-transform-reserved-words": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz",
        -      "integrity": "sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ==",
        +    "node_modules/@jridgewell/resolve-uri": {
        +      "version": "3.1.2",
        +      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
        +      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=6.0.0"
               }
             },
        -    "node_modules/@babel/plugin-transform-reserved-words/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-shorthand-properties": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz",
        -      "integrity": "sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==",
        +    "node_modules/@jridgewell/set-array": {
        +      "version": "1.2.1",
        +      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
        +      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=6.0.0"
               }
             },
        -    "node_modules/@babel/plugin-transform-shorthand-properties/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-spread": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz",
        -      "integrity": "sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw==",
        +    "node_modules/@jridgewell/source-map": {
        +      "version": "0.3.6",
        +      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
        +      "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +        "@jridgewell/gen-mapping": "^0.3.5",
        +        "@jridgewell/trace-mapping": "^0.3.25"
               }
             },
        -    "node_modules/@babel/plugin-transform-spread/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        +    "node_modules/@jridgewell/sourcemap-codec": {
        +      "version": "1.5.0",
        +      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
        +      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
               "dev": true
             },
        -    "node_modules/@babel/plugin-transform-sticky-regex": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz",
        -      "integrity": "sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA==",
        +    "node_modules/@jridgewell/trace-mapping": {
        +      "version": "0.3.25",
        +      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
        +      "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/helper-regex": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +        "@jridgewell/resolve-uri": "^3.1.0",
        +        "@jridgewell/sourcemap-codec": "^1.4.14"
               }
             },
        -    "node_modules/@babel/plugin-transform-sticky-regex/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-template-literals": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz",
        -      "integrity": "sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg==",
        +    "node_modules/@mswjs/interceptors": {
        +      "version": "0.37.5",
        +      "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.5.tgz",
        +      "integrity": "sha512-AAwRb5vXFcY4L+FvZ7LZusDuZ0vEe0Zm8ohn1FM6/X7A3bj4mqmkAcGRWuvC2JwSygNwHAAmMnAI73vPHeqsHA==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-annotate-as-pure": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "@open-draft/deferred-promise": "^2.2.0",
        +        "@open-draft/logger": "^0.3.0",
        +        "@open-draft/until": "^2.0.0",
        +        "is-node-process": "^1.2.0",
        +        "outvariant": "^1.4.3",
        +        "strict-event-emitter": "^0.5.1"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=18"
               }
             },
        -    "node_modules/@babel/plugin-transform-template-literals/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/plugin-transform-typeof-symbol": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz",
        -      "integrity": "sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==",
        +    "node_modules/@nodelib/fs.scandir": {
        +      "version": "2.1.5",
        +      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
        +      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "@nodelib/fs.stat": "2.0.5",
        +        "run-parallel": "^1.1.9"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">= 8"
               }
             },
        -    "node_modules/@babel/plugin-transform-typeof-symbol/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        -      "dev": true
        +    "node_modules/@nodelib/fs.stat": {
        +      "version": "2.0.5",
        +      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
        +      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">= 8"
        +      }
             },
        -    "node_modules/@babel/plugin-transform-unicode-escapes": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz",
        -      "integrity": "sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw==",
        +    "node_modules/@nodelib/fs.walk": {
        +      "version": "1.2.8",
        +      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
        +      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.10.1"
        +        "@nodelib/fs.scandir": "2.1.5",
        +        "fastq": "^1.6.0"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">= 8"
               }
             },
        -    "node_modules/@babel/plugin-transform-unicode-escapes/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        +    "node_modules/@open-draft/deferred-promise": {
        +      "version": "2.2.0",
        +      "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz",
        +      "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==",
               "dev": true
             },
        -    "node_modules/@babel/plugin-transform-unicode-regex": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz",
        -      "integrity": "sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw==",
        +    "node_modules/@open-draft/logger": {
        +      "version": "0.3.0",
        +      "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz",
        +      "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-create-regexp-features-plugin": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +        "is-node-process": "^1.2.0",
        +        "outvariant": "^1.4.0"
               }
             },
        -    "node_modules/@babel/plugin-transform-unicode-regex/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        +    "node_modules/@open-draft/until": {
        +      "version": "2.1.0",
        +      "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz",
        +      "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==",
               "dev": true
             },
        -    "node_modules/@babel/preset-env": {
        -      "version": "7.10.2",
        -      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.2.tgz",
        -      "integrity": "sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA==",
        -      "dev": true,
        -      "dependencies": {
        -        "@babel/compat-data": "^7.10.1",
        -        "@babel/helper-compilation-targets": "^7.10.2",
        -        "@babel/helper-module-imports": "^7.10.1",
        -        "@babel/helper-plugin-utils": "^7.10.1",
        -        "@babel/plugin-proposal-async-generator-functions": "^7.10.1",
        -        "@babel/plugin-proposal-class-properties": "^7.10.1",
        -        "@babel/plugin-proposal-dynamic-import": "^7.10.1",
        -        "@babel/plugin-proposal-json-strings": "^7.10.1",
        -        "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.1",
        -        "@babel/plugin-proposal-numeric-separator": "^7.10.1",
        -        "@babel/plugin-proposal-object-rest-spread": "^7.10.1",
        -        "@babel/plugin-proposal-optional-catch-binding": "^7.10.1",
        -        "@babel/plugin-proposal-optional-chaining": "^7.10.1",
        -        "@babel/plugin-proposal-private-methods": "^7.10.1",
        -        "@babel/plugin-proposal-unicode-property-regex": "^7.10.1",
        -        "@babel/plugin-syntax-async-generators": "^7.8.0",
        -        "@babel/plugin-syntax-class-properties": "^7.10.1",
        -        "@babel/plugin-syntax-dynamic-import": "^7.8.0",
        -        "@babel/plugin-syntax-json-strings": "^7.8.0",
        -        "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0",
        -        "@babel/plugin-syntax-numeric-separator": "^7.10.1",
        -        "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
        -        "@babel/plugin-syntax-optional-catch-binding": "^7.8.0",
        -        "@babel/plugin-syntax-optional-chaining": "^7.8.0",
        -        "@babel/plugin-syntax-top-level-await": "^7.10.1",
        -        "@babel/plugin-transform-arrow-functions": "^7.10.1",
        -        "@babel/plugin-transform-async-to-generator": "^7.10.1",
        -        "@babel/plugin-transform-block-scoped-functions": "^7.10.1",
        -        "@babel/plugin-transform-block-scoping": "^7.10.1",
        -        "@babel/plugin-transform-classes": "^7.10.1",
        -        "@babel/plugin-transform-computed-properties": "^7.10.1",
        -        "@babel/plugin-transform-destructuring": "^7.10.1",
        -        "@babel/plugin-transform-dotall-regex": "^7.10.1",
        -        "@babel/plugin-transform-duplicate-keys": "^7.10.1",
        -        "@babel/plugin-transform-exponentiation-operator": "^7.10.1",
        -        "@babel/plugin-transform-for-of": "^7.10.1",
        -        "@babel/plugin-transform-function-name": "^7.10.1",
        -        "@babel/plugin-transform-literals": "^7.10.1",
        -        "@babel/plugin-transform-member-expression-literals": "^7.10.1",
        -        "@babel/plugin-transform-modules-amd": "^7.10.1",
        -        "@babel/plugin-transform-modules-commonjs": "^7.10.1",
        -        "@babel/plugin-transform-modules-systemjs": "^7.10.1",
        -        "@babel/plugin-transform-modules-umd": "^7.10.1",
        -        "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3",
        -        "@babel/plugin-transform-new-target": "^7.10.1",
        -        "@babel/plugin-transform-object-super": "^7.10.1",
        -        "@babel/plugin-transform-parameters": "^7.10.1",
        -        "@babel/plugin-transform-property-literals": "^7.10.1",
        -        "@babel/plugin-transform-regenerator": "^7.10.1",
        -        "@babel/plugin-transform-reserved-words": "^7.10.1",
        -        "@babel/plugin-transform-shorthand-properties": "^7.10.1",
        -        "@babel/plugin-transform-spread": "^7.10.1",
        -        "@babel/plugin-transform-sticky-regex": "^7.10.1",
        -        "@babel/plugin-transform-template-literals": "^7.10.1",
        -        "@babel/plugin-transform-typeof-symbol": "^7.10.1",
        -        "@babel/plugin-transform-unicode-escapes": "^7.10.1",
        -        "@babel/plugin-transform-unicode-regex": "^7.10.1",
        -        "@babel/preset-modules": "^0.1.3",
        -        "@babel/types": "^7.10.2",
        -        "browserslist": "^4.12.0",
        -        "core-js-compat": "^3.6.2",
        -        "invariant": "^2.2.2",
        -        "levenary": "^1.1.1",
        -        "semver": "^5.5.0"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +    "node_modules/@pkgjs/parseargs": {
        +      "version": "0.11.0",
        +      "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
        +      "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
        +      "dev": true,
        +      "optional": true,
        +      "engines": {
        +        "node": ">=14"
               }
             },
        -    "node_modules/@babel/preset-env/node_modules/@babel/helper-plugin-utils": {
        -      "version": "7.10.1",
        -      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
        -      "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
        +    "node_modules/@polka/url": {
        +      "version": "1.0.0-next.28",
        +      "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
        +      "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
               "dev": true
             },
        -    "node_modules/@babel/preset-env/node_modules/semver": {
        -      "version": "5.7.1",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
        -      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
        +    "node_modules/@promptbook/utils": {
        +      "version": "0.69.5",
        +      "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.69.5.tgz",
        +      "integrity": "sha512-xm5Ti/Hp3o4xHrsK9Yy3MS6KbDxYbq485hDsFvxqaNA7equHLPdo8H8faTitTeb14QCDfLW4iwCxdVYu5sn6YQ==",
               "dev": true,
        -      "bin": {
        -        "semver": "bin/semver"
        +      "funding": [
        +        {
        +          "type": "individual",
        +          "url": "https://buymeacoffee.com/hejny"
        +        },
        +        {
        +          "type": "github",
        +          "url": "https://github.com/webgptorg/promptbook/blob/main/README.md#%EF%B8%8F-contributing"
        +        }
        +      ],
        +      "dependencies": {
        +        "spacetrim": "0.11.59"
               }
             },
        -    "node_modules/@babel/preset-modules": {
        -      "version": "0.1.3",
        -      "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz",
        -      "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==",
        +    "node_modules/@puppeteer/browsers": {
        +      "version": "2.3.0",
        +      "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz",
        +      "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==",
               "dev": true,
               "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.0.0",
        -        "@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
        -        "@babel/plugin-transform-dotall-regex": "^7.4.4",
        -        "@babel/types": "^7.4.4",
        -        "esutils": "^2.0.2"
        +        "debug": "^4.3.5",
        +        "extract-zip": "^2.0.1",
        +        "progress": "^2.0.3",
        +        "proxy-agent": "^6.4.0",
        +        "semver": "^7.6.3",
        +        "tar-fs": "^3.0.6",
        +        "unbzip2-stream": "^1.4.3",
        +        "yargs": "^17.7.2"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "bin": {
        +        "browsers": "lib/cjs/main-cli.js"
        +      },
        +      "engines": {
        +        "node": ">=18"
               }
             },
        -    "node_modules/@babel/register": {
        -      "version": "7.7.7",
        -      "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.7.7.tgz",
        -      "integrity": "sha512-S2mv9a5dc2pcpg/ConlKZx/6wXaEwHeqfo7x/QbXsdCAZm+WJC1ekVvL1TVxNsedTs5y/gG63MhJTEsmwmjtiA==",
        +    "node_modules/@puppeteer/browsers/node_modules/ansi-regex": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
               "dev": true,
        -      "dependencies": {
        -        "find-cache-dir": "^2.0.0",
        -        "lodash": "^4.17.13",
        -        "make-dir": "^2.1.0",
        -        "pirates": "^4.0.0",
        -        "source-map-support": "^0.5.16"
        -      },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0-0"
        +      "engines": {
        +        "node": ">=8"
               }
             },
        -    "node_modules/@babel/register/node_modules/make-dir": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
        -      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
        +    "node_modules/@puppeteer/browsers/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
               "dependencies": {
        -        "pify": "^4.0.1",
        -        "semver": "^5.6.0"
        +        "color-convert": "^2.0.1"
               },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/@babel/register/node_modules/semver": {
        -      "version": "5.7.1",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
        -      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
        +    "node_modules/@puppeteer/browsers/node_modules/cliui": {
        +      "version": "8.0.1",
        +      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
        +      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
               "dev": true,
        -      "bin": {
        -        "semver": "bin/semver"
        +      "dependencies": {
        +        "string-width": "^4.2.0",
        +        "strip-ansi": "^6.0.1",
        +        "wrap-ansi": "^7.0.0"
        +      },
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/@babel/runtime": {
        -      "version": "7.3.4",
        -      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.4.tgz",
        -      "integrity": "sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g==",
        +    "node_modules/@puppeteer/browsers/node_modules/color-convert": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
               "dependencies": {
        -        "regenerator-runtime": "^0.12.0"
        +        "color-name": "~1.1.4"
        +      },
        +      "engines": {
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/@babel/runtime/node_modules/regenerator-runtime": {
        -      "version": "0.12.1",
        -      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
        -      "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==",
        -      "dev": true
        -    },
        -    "node_modules/@babel/template": {
        -      "version": "7.22.15",
        -      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
        -      "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
        +    "node_modules/@puppeteer/browsers/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
               "dependencies": {
        -        "@babel/code-frame": "^7.22.13",
        -        "@babel/parser": "^7.22.15",
        -        "@babel/types": "^7.22.15"
        +        "ms": "^2.1.3"
               },
               "engines": {
        -        "node": ">=6.9.0"
        -      }
        -    },
        -    "node_modules/@babel/traverse": {
        -      "version": "7.23.2",
        -      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
        -      "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
        -      "dev": true,
        -      "dependencies": {
        -        "@babel/code-frame": "^7.22.13",
        -        "@babel/generator": "^7.23.0",
        -        "@babel/helper-environment-visitor": "^7.22.20",
        -        "@babel/helper-function-name": "^7.23.0",
        -        "@babel/helper-hoist-variables": "^7.22.5",
        -        "@babel/helper-split-export-declaration": "^7.22.6",
        -        "@babel/parser": "^7.23.0",
        -        "@babel/types": "^7.23.0",
        -        "debug": "^4.1.0",
        -        "globals": "^11.1.0"
        +        "node": ">=6.0"
               },
        -      "engines": {
        -        "node": ">=6.9.0"
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/@babel/traverse/node_modules/debug": {
        -      "version": "4.1.1",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
        -      "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
        -      "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
        +    "node_modules/@puppeteer/browsers/node_modules/emoji-regex": {
        +      "version": "8.0.0",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        +      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        +      "dev": true
        +    },
        +    "node_modules/@puppeteer/browsers/node_modules/is-fullwidth-code-point": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        +      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
               "dev": true,
        -      "dependencies": {
        -        "ms": "^2.1.1"
        +      "engines": {
        +        "node": ">=8"
               }
             },
        -    "node_modules/@babel/traverse/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        +    "node_modules/@puppeteer/browsers/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
               "dev": true
             },
        -    "node_modules/@babel/types": {
        -      "version": "7.23.0",
        -      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
        -      "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
        +    "node_modules/@puppeteer/browsers/node_modules/semver": {
        +      "version": "7.6.3",
        +      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
        +      "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-string-parser": "^7.22.5",
        -        "@babel/helper-validator-identifier": "^7.22.20",
        -        "to-fast-properties": "^2.0.0"
        +      "bin": {
        +        "semver": "bin/semver.js"
               },
               "engines": {
        -        "node": ">=6.9.0"
        +        "node": ">=10"
               }
             },
        -    "node_modules/@bconnorwhite/module": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/@bconnorwhite/module/-/module-2.0.2.tgz",
        -      "integrity": "sha512-ck1me5WMgZKp06gnJrVKEkytpehTTQbvsAMbF1nGPeHri/AZNhj87++PSE2LOxmZqM0EtGMaqeLdx7Lw7SUnTA==",
        +    "node_modules/@puppeteer/browsers/node_modules/string-width": {
        +      "version": "4.2.3",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
        +      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
               "dev": true,
               "dependencies": {
        -        "find-up": "^5.0.0",
        -        "read-json-safe": "^1.0.5",
        -        "types-pkg-json": "^1.1.0"
        +        "emoji-regex": "^8.0.0",
        +        "is-fullwidth-code-point": "^3.0.0",
        +        "strip-ansi": "^6.0.1"
        +      },
        +      "engines": {
        +        "node": ">=8"
               }
             },
        -    "node_modules/@bconnorwhite/module/node_modules/find-up": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
        -      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
        +    "node_modules/@puppeteer/browsers/node_modules/strip-ansi": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        +      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
               "dev": true,
               "dependencies": {
        -        "locate-path": "^6.0.0",
        -        "path-exists": "^4.0.0"
        +        "ansi-regex": "^5.0.1"
               },
               "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">=8"
               }
             },
        -    "node_modules/@bconnorwhite/module/node_modules/locate-path": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
        -      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
        +    "node_modules/@puppeteer/browsers/node_modules/wrap-ansi": {
        +      "version": "7.0.0",
        +      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
        +      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
               "dev": true,
               "dependencies": {
        -        "p-locate": "^5.0.0"
        +        "ansi-styles": "^4.0.0",
        +        "string-width": "^4.1.0",
        +        "strip-ansi": "^6.0.0"
               },
               "engines": {
                 "node": ">=10"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
               }
             },
        -    "node_modules/@bconnorwhite/module/node_modules/p-limit": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
        -      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
        +    "node_modules/@puppeteer/browsers/node_modules/y18n": {
        +      "version": "5.0.8",
        +      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
        +      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
               "dev": true,
        -      "dependencies": {
        -        "yocto-queue": "^0.1.0"
        -      },
               "engines": {
                 "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/@bconnorwhite/module/node_modules/p-locate": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
        -      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
        +    "node_modules/@puppeteer/browsers/node_modules/yargs": {
        +      "version": "17.7.2",
        +      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
        +      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
               "dev": true,
               "dependencies": {
        -        "p-limit": "^3.0.2"
        +        "cliui": "^8.0.1",
        +        "escalade": "^3.1.1",
        +        "get-caller-file": "^2.0.5",
        +        "require-directory": "^2.1.1",
        +        "string-width": "^4.2.3",
        +        "y18n": "^5.0.5",
        +        "yargs-parser": "^21.1.1"
               },
               "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">=12"
               }
             },
        -    "node_modules/@eslint/eslintrc": {
        -      "version": "1.3.2",
        -      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz",
        -      "integrity": "sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==",
        +    "node_modules/@puppeteer/browsers/node_modules/yargs-parser": {
        +      "version": "21.1.1",
        +      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
        +      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
               "dev": true,
        -      "dependencies": {
        -        "ajv": "^6.12.4",
        -        "debug": "^4.3.2",
        -        "espree": "^9.4.0",
        -        "globals": "^13.15.0",
        -        "ignore": "^5.2.0",
        -        "import-fresh": "^3.2.1",
        -        "js-yaml": "^4.1.0",
        -        "minimatch": "^3.1.2",
        -        "strip-json-comments": "^3.1.1"
        -      },
               "engines": {
        -        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://opencollective.com/eslint"
        +        "node": ">=12"
               }
             },
        -    "node_modules/@eslint/eslintrc/node_modules/argparse": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
        -      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
        -      "dev": true
        +    "node_modules/@rollup/plugin-alias": {
        +      "version": "5.1.1",
        +      "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz",
        +      "integrity": "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=14.0.0"
        +      },
        +      "peerDependencies": {
        +        "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "rollup": {
        +          "optional": true
        +        }
        +      }
             },
        -    "node_modules/@eslint/eslintrc/node_modules/debug": {
        -      "version": "4.3.4",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
        -      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
        +    "node_modules/@rollup/plugin-commonjs": {
        +      "version": "25.0.8",
        +      "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz",
        +      "integrity": "sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==",
               "dev": true,
               "dependencies": {
        -        "ms": "2.1.2"
        +        "@rollup/pluginutils": "^5.0.1",
        +        "commondir": "^1.0.1",
        +        "estree-walker": "^2.0.2",
        +        "glob": "^8.0.3",
        +        "is-reference": "1.2.1",
        +        "magic-string": "^0.30.3"
               },
               "engines": {
        -        "node": ">=6.0"
        +        "node": ">=14.0.0"
        +      },
        +      "peerDependencies": {
        +        "rollup": "^2.68.0||^3.0.0||^4.0.0"
               },
               "peerDependenciesMeta": {
        -        "supports-color": {
        +        "rollup": {
                   "optional": true
                 }
               }
             },
        -    "node_modules/@eslint/eslintrc/node_modules/globals": {
        -      "version": "13.17.0",
        -      "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
        -      "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==",
        +    "node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
        +      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
               "dev": true,
               "dependencies": {
        -        "type-fest": "^0.20.2"
        +        "balanced-match": "^1.0.0"
        +      }
        +    },
        +    "node_modules/@rollup/plugin-commonjs/node_modules/glob": {
        +      "version": "8.1.0",
        +      "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
        +      "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
        +      "deprecated": "Glob versions prior to v9 are no longer supported",
        +      "dev": true,
        +      "dependencies": {
        +        "fs.realpath": "^1.0.0",
        +        "inflight": "^1.0.4",
        +        "inherits": "2",
        +        "minimatch": "^5.0.1",
        +        "once": "^1.3.0"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=12"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/@eslint/eslintrc/node_modules/js-yaml": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
        -      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
        +    "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": {
        +      "version": "5.1.6",
        +      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
        +      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
               "dev": true,
               "dependencies": {
        -        "argparse": "^2.0.1"
        +        "brace-expansion": "^2.0.1"
               },
        -      "bin": {
        -        "js-yaml": "bin/js-yaml.js"
        +      "engines": {
        +        "node": ">=10"
               }
             },
        -    "node_modules/@eslint/eslintrc/node_modules/minimatch": {
        -      "version": "3.1.2",
        -      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
        -      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
        +    "node_modules/@rollup/plugin-json": {
        +      "version": "6.1.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
        +      "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==",
               "dev": true,
               "dependencies": {
        -        "brace-expansion": "^1.1.7"
        +        "@rollup/pluginutils": "^5.1.0"
               },
               "engines": {
        -        "node": "*"
        +        "node": ">=14.0.0"
        +      },
        +      "peerDependencies": {
        +        "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "rollup": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/@eslint/eslintrc/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        -      "dev": true
        -    },
        -    "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": {
        -      "version": "3.1.1",
        -      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
        -      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
        +    "node_modules/@rollup/plugin-node-resolve": {
        +      "version": "15.3.1",
        +      "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
        +      "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
               "dev": true,
        +      "dependencies": {
        +        "@rollup/pluginutils": "^5.0.1",
        +        "@types/resolve": "1.20.2",
        +        "deepmerge": "^4.2.2",
        +        "is-module": "^1.0.0",
        +        "resolve": "^1.22.1"
        +      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=14.0.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "peerDependencies": {
        +        "rollup": "^2.78.0||^3.0.0||^4.0.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "rollup": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/@eslint/eslintrc/node_modules/type-fest": {
        -      "version": "0.20.2",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
        -      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
        +    "node_modules/@rollup/plugin-replace": {
        +      "version": "5.0.7",
        +      "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.7.tgz",
        +      "integrity": "sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==",
               "dev": true,
        +      "dependencies": {
        +        "@rollup/pluginutils": "^5.0.1",
        +        "magic-string": "^0.30.3"
        +      },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=14.0.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "peerDependencies": {
        +        "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "rollup": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/@humanwhocodes/config-array": {
        -      "version": "0.10.4",
        -      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz",
        -      "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==",
        +    "node_modules/@rollup/plugin-terser": {
        +      "version": "0.4.4",
        +      "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
        +      "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
               "dev": true,
               "dependencies": {
        -        "@humanwhocodes/object-schema": "^1.2.1",
        -        "debug": "^4.1.1",
        -        "minimatch": "^3.0.4"
        +        "serialize-javascript": "^6.0.1",
        +        "smob": "^1.0.0",
        +        "terser": "^5.17.4"
               },
               "engines": {
        -        "node": ">=10.10.0"
        +        "node": ">=14.0.0"
        +      },
        +      "peerDependencies": {
        +        "rollup": "^2.0.0||^3.0.0||^4.0.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "rollup": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/@humanwhocodes/config-array/node_modules/debug": {
        -      "version": "4.3.4",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
        -      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
        +    "node_modules/@rollup/plugin-terser/node_modules/serialize-javascript": {
        +      "version": "6.0.2",
        +      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
        +      "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
               "dev": true,
               "dependencies": {
        -        "ms": "2.1.2"
        +        "randombytes": "^2.1.0"
        +      }
        +    },
        +    "node_modules/@rollup/pluginutils": {
        +      "version": "5.1.4",
        +      "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
        +      "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
        +      "dev": true,
        +      "dependencies": {
        +        "@types/estree": "^1.0.0",
        +        "estree-walker": "^2.0.2",
        +        "picomatch": "^4.0.2"
               },
               "engines": {
        -        "node": ">=6.0"
        +        "node": ">=14.0.0"
        +      },
        +      "peerDependencies": {
        +        "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
               },
               "peerDependenciesMeta": {
        -        "supports-color": {
        +        "rollup": {
                   "optional": true
                 }
               }
             },
        -    "node_modules/@humanwhocodes/config-array/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        -      "dev": true
        -    },
        -    "node_modules/@humanwhocodes/gitignore-to-minimatch": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz",
        -      "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==",
        -      "dev": true,
        -      "funding": {
        -        "type": "github",
        -        "url": "https://github.com/sponsors/nzakas"
        -      }
        -    },
        -    "node_modules/@humanwhocodes/module-importer": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
        -      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
        +    "node_modules/@rollup/pluginutils/node_modules/picomatch": {
        +      "version": "4.0.2",
        +      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
        +      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
               "dev": true,
               "engines": {
        -        "node": ">=12.22"
        +        "node": ">=12"
               },
               "funding": {
        -        "type": "github",
        -        "url": "https://github.com/sponsors/nzakas"
        +        "url": "https://github.com/sponsors/jonschlinkert"
               }
             },
        -    "node_modules/@humanwhocodes/object-schema": {
        -      "version": "1.2.1",
        -      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
        -      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
        -      "dev": true
        +    "node_modules/@rollup/rollup-android-arm-eabi": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.31.0.tgz",
        +      "integrity": "sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==",
        +      "cpu": [
        +        "arm"
        +      ],
        +      "dev": true,
        +      "optional": true,
        +      "os": [
        +        "android"
        +      ]
             },
        -    "node_modules/@jridgewell/gen-mapping": {
        -      "version": "0.3.3",
        -      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
        -      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
        +    "node_modules/@rollup/rollup-android-arm64": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.31.0.tgz",
        +      "integrity": "sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==",
        +      "cpu": [
        +        "arm64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@jridgewell/set-array": "^1.0.1",
        -        "@jridgewell/sourcemap-codec": "^1.4.10",
        -        "@jridgewell/trace-mapping": "^0.3.9"
        -      },
        -      "engines": {
        -        "node": ">=6.0.0"
        -      }
        +      "optional": true,
        +      "os": [
        +        "android"
        +      ]
             },
        -    "node_modules/@jridgewell/resolve-uri": {
        -      "version": "3.1.1",
        -      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
        -      "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
        +    "node_modules/@rollup/rollup-darwin-arm64": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.31.0.tgz",
        +      "integrity": "sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==",
        +      "cpu": [
        +        "arm64"
        +      ],
               "dev": true,
        -      "engines": {
        -        "node": ">=6.0.0"
        -      }
        +      "optional": true,
        +      "os": [
        +        "darwin"
        +      ]
             },
        -    "node_modules/@jridgewell/set-array": {
        -      "version": "1.1.2",
        -      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
        -      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
        +    "node_modules/@rollup/rollup-darwin-x64": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.31.0.tgz",
        +      "integrity": "sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==",
        +      "cpu": [
        +        "x64"
        +      ],
               "dev": true,
        -      "engines": {
        -        "node": ">=6.0.0"
        -      }
        +      "optional": true,
        +      "os": [
        +        "darwin"
        +      ]
             },
        -    "node_modules/@jridgewell/sourcemap-codec": {
        -      "version": "1.4.15",
        -      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
        -      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
        -      "dev": true
        +    "node_modules/@rollup/rollup-freebsd-arm64": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.31.0.tgz",
        +      "integrity": "sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==",
        +      "cpu": [
        +        "arm64"
        +      ],
        +      "dev": true,
        +      "optional": true,
        +      "os": [
        +        "freebsd"
        +      ]
             },
        -    "node_modules/@jridgewell/trace-mapping": {
        -      "version": "0.3.19",
        -      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
        -      "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
        +    "node_modules/@rollup/rollup-freebsd-x64": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.31.0.tgz",
        +      "integrity": "sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==",
        +      "cpu": [
        +        "x64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@jridgewell/resolve-uri": "^3.1.0",
        -        "@jridgewell/sourcemap-codec": "^1.4.14"
        -      }
        +      "optional": true,
        +      "os": [
        +        "freebsd"
        +      ]
             },
        -    "node_modules/@kwsites/file-exists": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
        -      "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
        +    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.31.0.tgz",
        +      "integrity": "sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==",
        +      "cpu": [
        +        "arm"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "debug": "^4.1.1"
        -      }
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ]
             },
        -    "node_modules/@kwsites/file-exists/node_modules/debug": {
        -      "version": "4.3.4",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
        -      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
        +    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.31.0.tgz",
        +      "integrity": "sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==",
        +      "cpu": [
        +        "arm"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "ms": "2.1.2"
        -      },
        -      "engines": {
        -        "node": ">=6.0"
        -      },
        -      "peerDependenciesMeta": {
        -        "supports-color": {
        -          "optional": true
        -        }
        -      }
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ]
             },
        -    "node_modules/@kwsites/file-exists/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        -      "dev": true
        +    "node_modules/@rollup/rollup-linux-arm64-gnu": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.31.0.tgz",
        +      "integrity": "sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==",
        +      "cpu": [
        +        "arm64"
        +      ],
        +      "dev": true,
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ]
             },
        -    "node_modules/@kwsites/promise-deferred": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
        -      "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
        -      "dev": true
        +    "node_modules/@rollup/rollup-linux-arm64-musl": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.31.0.tgz",
        +      "integrity": "sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==",
        +      "cpu": [
        +        "arm64"
        +      ],
        +      "dev": true,
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ]
             },
        -    "node_modules/@nodelib/fs.scandir": {
        -      "version": "2.1.5",
        -      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
        -      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
        +    "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.31.0.tgz",
        +      "integrity": "sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==",
        +      "cpu": [
        +        "loong64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@nodelib/fs.stat": "2.0.5",
        -        "run-parallel": "^1.1.9"
        -      },
        -      "engines": {
        -        "node": ">= 8"
        -      }
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ]
             },
        -    "node_modules/@nodelib/fs.stat": {
        -      "version": "2.0.5",
        -      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
        -      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
        +    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.31.0.tgz",
        +      "integrity": "sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==",
        +      "cpu": [
        +        "ppc64"
        +      ],
               "dev": true,
        -      "engines": {
        -        "node": ">= 8"
        -      }
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ]
             },
        -    "node_modules/@nodelib/fs.walk": {
        -      "version": "1.2.8",
        -      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
        -      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
        +    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.31.0.tgz",
        +      "integrity": "sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==",
        +      "cpu": [
        +        "riscv64"
        +      ],
               "dev": true,
        -      "dependencies": {
        -        "@nodelib/fs.scandir": "2.1.5",
        -        "fastq": "^1.6.0"
        -      },
        -      "engines": {
        -        "node": ">= 8"
        -      }
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ]
             },
        -    "node_modules/@pnpm/config.env-replace": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz",
        -      "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==",
        +    "node_modules/@rollup/rollup-linux-s390x-gnu": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.31.0.tgz",
        +      "integrity": "sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==",
        +      "cpu": [
        +        "s390x"
        +      ],
               "dev": true,
        -      "engines": {
        -        "node": ">=12.22.0"
        -      }
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ]
             },
        -    "node_modules/@pnpm/network.ca-file": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz",
        -      "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==",
        +    "node_modules/@rollup/rollup-linux-x64-gnu": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.31.0.tgz",
        +      "integrity": "sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==",
        +      "cpu": [
        +        "x64"
        +      ],
        +      "dev": true,
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ]
        +    },
        +    "node_modules/@rollup/rollup-linux-x64-musl": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.31.0.tgz",
        +      "integrity": "sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==",
        +      "cpu": [
        +        "x64"
        +      ],
        +      "dev": true,
        +      "optional": true,
        +      "os": [
        +        "linux"
        +      ]
        +    },
        +    "node_modules/@rollup/rollup-win32-arm64-msvc": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.31.0.tgz",
        +      "integrity": "sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==",
        +      "cpu": [
        +        "arm64"
        +      ],
        +      "dev": true,
        +      "optional": true,
        +      "os": [
        +        "win32"
        +      ]
        +    },
        +    "node_modules/@rollup/rollup-win32-ia32-msvc": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.31.0.tgz",
        +      "integrity": "sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==",
        +      "cpu": [
        +        "ia32"
        +      ],
        +      "dev": true,
        +      "optional": true,
        +      "os": [
        +        "win32"
        +      ]
        +    },
        +    "node_modules/@rollup/rollup-win32-x64-msvc": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.31.0.tgz",
        +      "integrity": "sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==",
        +      "cpu": [
        +        "x64"
        +      ],
        +      "dev": true,
        +      "optional": true,
        +      "os": [
        +        "win32"
        +      ]
        +    },
        +    "node_modules/@testing-library/dom": {
        +      "version": "10.4.0",
        +      "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
        +      "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
               "dev": true,
               "dependencies": {
        -        "graceful-fs": "4.2.10"
        +        "@babel/code-frame": "^7.10.4",
        +        "@babel/runtime": "^7.12.5",
        +        "@types/aria-query": "^5.0.1",
        +        "aria-query": "5.3.0",
        +        "chalk": "^4.1.0",
        +        "dom-accessibility-api": "^0.5.9",
        +        "lz-string": "^1.5.0",
        +        "pretty-format": "^27.0.2"
               },
               "engines": {
        -        "node": ">=12.22.0"
        +        "node": ">=18"
               }
             },
        -    "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": {
        -      "version": "4.2.10",
        -      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
        -      "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
        -      "dev": true
        -    },
        -    "node_modules/@pnpm/npm-conf": {
        -      "version": "2.2.2",
        -      "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz",
        -      "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==",
        +    "node_modules/@testing-library/dom/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
               "dependencies": {
        -        "@pnpm/config.env-replace": "^1.1.0",
        -        "@pnpm/network.ca-file": "^1.0.1",
        -        "config-chain": "^1.1.11"
        +        "color-convert": "^2.0.1"
               },
               "engines": {
        -        "node": ">=12"
        +        "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/@samverschueren/stream-to-observable": {
        -      "version": "0.3.1",
        -      "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz",
        -      "integrity": "sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==",
        +    "node_modules/@testing-library/dom/node_modules/chalk": {
        +      "version": "4.1.2",
        +      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        +      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
               "dev": true,
               "dependencies": {
        -        "any-observable": "^0.3.0"
        +        "ansi-styles": "^4.1.0",
        +        "supports-color": "^7.1.0"
               },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=10"
               },
        -      "peerDependenciesMeta": {
        -        "rxjs": {
        -          "optional": true
        -        },
        -        "zen-observable": {
        -          "optional": true
        -        }
        +      "funding": {
        +        "url": "https://github.com/chalk/chalk?sponsor=1"
               }
             },
        -    "node_modules/@samverschueren/stream-to-observable/node_modules/any-observable": {
        -      "version": "0.3.0",
        -      "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz",
        -      "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==",
        +    "node_modules/@testing-library/dom/node_modules/color-convert": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
        +      "dependencies": {
        +        "color-name": "~1.1.4"
        +      },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/@sindresorhus/is": {
        -      "version": "4.6.0",
        -      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
        -      "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
        +    "node_modules/@testing-library/dom/node_modules/has-flag": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        +      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
               "dev": true,
               "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sindresorhus/is?sponsor=1"
        +        "node": ">=8"
               }
             },
        -    "node_modules/@szmarczak/http-timer": {
        -      "version": "4.0.6",
        -      "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
        -      "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
        +    "node_modules/@testing-library/dom/node_modules/supports-color": {
        +      "version": "7.2.0",
        +      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        +      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
               "dev": true,
               "dependencies": {
        -        "defer-to-connect": "^2.0.0"
        +        "has-flag": "^4.0.0"
               },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=8"
               }
             },
        -    "node_modules/@types/cacheable-request": {
        -      "version": "6.0.3",
        -      "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
        -      "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
        +    "node_modules/@testing-library/user-event": {
        +      "version": "14.6.1",
        +      "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
        +      "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
               "dev": true,
        -      "dependencies": {
        -        "@types/http-cache-semantics": "*",
        -        "@types/keyv": "^3.1.4",
        -        "@types/node": "*",
        -        "@types/responselike": "^1.0.0"
        +      "engines": {
        +        "node": ">=12",
        +        "npm": ">=6"
        +      },
        +      "peerDependencies": {
        +        "@testing-library/dom": ">=7.21.4"
               }
             },
        -    "node_modules/@types/http-cache-semantics": {
        -      "version": "4.0.1",
        -      "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
        -      "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==",
        +    "node_modules/@tootallnate/quickjs-emscripten": {
        +      "version": "0.23.0",
        +      "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
        +      "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
               "dev": true
             },
        -    "node_modules/@types/keyv": {
        -      "version": "3.1.4",
        -      "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
        -      "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
        +    "node_modules/@types/aria-query": {
        +      "version": "5.0.4",
        +      "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
        +      "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
        +      "dev": true
        +    },
        +    "node_modules/@types/cookie": {
        +      "version": "0.6.0",
        +      "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
        +      "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
        +      "dev": true
        +    },
        +    "node_modules/@types/debug": {
        +      "version": "4.1.12",
        +      "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
        +      "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
               "dev": true,
               "dependencies": {
        -        "@types/node": "*"
        +        "@types/ms": "*"
               }
             },
        -    "node_modules/@types/minimist": {
        -      "version": "1.2.2",
        -      "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz",
        -      "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==",
        +    "node_modules/@types/estree": {
        +      "version": "1.0.6",
        +      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
        +      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
               "dev": true
             },
        -    "node_modules/@types/node": {
        -      "version": "13.7.7",
        -      "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.7.tgz",
        -      "integrity": "sha512-Uo4chgKbnPNlxQwoFmYIwctkQVkMMmsAoGGU4JKwLuvBefF0pCq4FybNSnfkfRCpC7ZW7kttcC/TrRtAJsvGtg==",
        +    "node_modules/@types/extend": {
        +      "version": "3.0.4",
        +      "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.4.tgz",
        +      "integrity": "sha512-ArMouDUTJEz1SQRpFsT2rIw7DeqICFv5aaVzLSIYMYQSLcwcGOfT3VyglQs/p7K3F7fT4zxr0NWxYZIdifD6dA==",
        +      "dev": true
        +    },
        +    "node_modules/@types/hast": {
        +      "version": "2.3.10",
        +      "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
        +      "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==",
        +      "dev": true,
        +      "dependencies": {
        +        "@types/unist": "^2"
        +      }
        +    },
        +    "node_modules/@types/mdast": {
        +      "version": "3.0.15",
        +      "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz",
        +      "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==",
        +      "dev": true,
        +      "dependencies": {
        +        "@types/unist": "^2"
        +      }
        +    },
        +    "node_modules/@types/ms": {
        +      "version": "2.1.0",
        +      "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
        +      "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
               "dev": true
             },
        +    "node_modules/@types/node": {
        +      "version": "22.10.7",
        +      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
        +      "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
        +      "dev": true,
        +      "dependencies": {
        +        "undici-types": "~6.20.0"
        +      }
        +    },
             "node_modules/@types/normalize-package-data": {
               "version": "2.4.1",
               "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz",
        @@ -2214,748 +2205,976 @@
               "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
               "dev": true
             },
        -    "node_modules/@types/responselike": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
        -      "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
        +    "node_modules/@types/parse5": {
        +      "version": "6.0.3",
        +      "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz",
        +      "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==",
        +      "dev": true
        +    },
        +    "node_modules/@types/resolve": {
        +      "version": "1.20.2",
        +      "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
        +      "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
        +      "dev": true
        +    },
        +    "node_modules/@types/sinonjs__fake-timers": {
        +      "version": "8.1.5",
        +      "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz",
        +      "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==",
        +      "dev": true
        +    },
        +    "node_modules/@types/statuses": {
        +      "version": "2.0.5",
        +      "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz",
        +      "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==",
        +      "dev": true
        +    },
        +    "node_modules/@types/supports-color": {
        +      "version": "8.1.3",
        +      "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz",
        +      "integrity": "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==",
        +      "dev": true
        +    },
        +    "node_modules/@types/tough-cookie": {
        +      "version": "4.0.5",
        +      "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
        +      "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
        +      "dev": true
        +    },
        +    "node_modules/@types/unist": {
        +      "version": "2.0.11",
        +      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
        +      "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
        +      "dev": true
        +    },
        +    "node_modules/@types/which": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz",
        +      "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==",
        +      "dev": true
        +    },
        +    "node_modules/@types/ws": {
        +      "version": "8.5.13",
        +      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
        +      "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
               "dev": true,
               "dependencies": {
                 "@types/node": "*"
               }
             },
             "node_modules/@types/yauzl": {
        -      "version": "2.10.0",
        -      "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz",
        -      "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==",
        +      "version": "2.10.3",
        +      "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
        +      "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
               "dev": true,
               "optional": true,
               "dependencies": {
                 "@types/node": "*"
               }
             },
        -    "node_modules/abbrev": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
        -      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
        +    "node_modules/@ungap/structured-clone": {
        +      "version": "1.2.1",
        +      "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz",
        +      "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==",
               "dev": true
             },
        -    "node_modules/accepts": {
        -      "version": "1.3.8",
        -      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
        -      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
        +    "node_modules/@vitest/browser": {
        +      "version": "2.1.8",
        +      "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-2.1.8.tgz",
        +      "integrity": "sha512-OWVvEJThRgxlNMYNVLEK/9qVkpRcLvyuKLngIV3Hob01P56NjPHprVBYn+rx4xAJudbM9yrCrywPIEuA3Xyo8A==",
               "dev": true,
               "dependencies": {
        -        "mime-types": "~2.1.34",
        -        "negotiator": "0.6.3"
        +        "@testing-library/dom": "^10.4.0",
        +        "@testing-library/user-event": "^14.5.2",
        +        "@vitest/mocker": "2.1.8",
        +        "@vitest/utils": "2.1.8",
        +        "magic-string": "^0.30.12",
        +        "msw": "^2.6.4",
        +        "sirv": "^3.0.0",
        +        "tinyrainbow": "^1.2.0",
        +        "ws": "^8.18.0"
        +      },
        +      "funding": {
        +        "url": "https://opencollective.com/vitest"
        +      },
        +      "peerDependencies": {
        +        "playwright": "*",
        +        "vitest": "2.1.8",
        +        "webdriverio": "*"
               },
        +      "peerDependenciesMeta": {
        +        "playwright": {
        +          "optional": true
        +        },
        +        "safaridriver": {
        +          "optional": true
        +        },
        +        "webdriverio": {
        +          "optional": true
        +        }
        +      }
        +    },
        +    "node_modules/@vitest/browser/node_modules/ws": {
        +      "version": "8.18.0",
        +      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
        +      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
        +      "dev": true,
               "engines": {
        -        "node": ">= 0.6"
        +        "node": ">=10.0.0"
        +      },
        +      "peerDependencies": {
        +        "bufferutil": "^4.0.1",
        +        "utf-8-validate": ">=5.0.2"
        +      },
        +      "peerDependenciesMeta": {
        +        "bufferutil": {
        +          "optional": true
        +        },
        +        "utf-8-validate": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/acorn": {
        -      "version": "8.8.0",
        -      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
        -      "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
        +    "node_modules/@vitest/expect": {
        +      "version": "2.1.8",
        +      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz",
        +      "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==",
               "dev": true,
        -      "bin": {
        -        "acorn": "bin/acorn"
        +      "dependencies": {
        +        "@vitest/spy": "2.1.8",
        +        "@vitest/utils": "2.1.8",
        +        "chai": "^5.1.2",
        +        "tinyrainbow": "^1.2.0"
               },
        -      "engines": {
        -        "node": ">=0.4.0"
        +      "funding": {
        +        "url": "https://opencollective.com/vitest"
               }
             },
        -    "node_modules/acorn-jsx": {
        -      "version": "5.3.2",
        -      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
        -      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
        +    "node_modules/@vitest/mocker": {
        +      "version": "2.1.8",
        +      "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz",
        +      "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==",
               "dev": true,
        +      "dependencies": {
        +        "@vitest/spy": "2.1.8",
        +        "estree-walker": "^3.0.3",
        +        "magic-string": "^0.30.12"
        +      },
        +      "funding": {
        +        "url": "https://opencollective.com/vitest"
        +      },
               "peerDependencies": {
        -        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
        +        "msw": "^2.4.9",
        +        "vite": "^5.0.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "msw": {
        +          "optional": true
        +        },
        +        "vite": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/acorn-node": {
        -      "version": "1.8.2",
        -      "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
        -      "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
        +    "node_modules/@vitest/mocker/node_modules/estree-walker": {
        +      "version": "3.0.3",
        +      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
        +      "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
               "dev": true,
               "dependencies": {
        -        "acorn": "^7.0.0",
        -        "acorn-walk": "^7.0.0",
        -        "xtend": "^4.0.2"
        +        "@types/estree": "^1.0.0"
               }
             },
        -    "node_modules/acorn-node/node_modules/acorn": {
        -      "version": "7.4.1",
        -      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
        -      "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
        +    "node_modules/@vitest/pretty-format": {
        +      "version": "2.1.8",
        +      "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz",
        +      "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==",
               "dev": true,
        -      "bin": {
        -        "acorn": "bin/acorn"
        +      "dependencies": {
        +        "tinyrainbow": "^1.2.0"
               },
        -      "engines": {
        -        "node": ">=0.4.0"
        +      "funding": {
        +        "url": "https://opencollective.com/vitest"
               }
             },
        -    "node_modules/acorn-node/node_modules/xtend": {
        -      "version": "4.0.2",
        -      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
        -      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
        +    "node_modules/@vitest/runner": {
        +      "version": "2.1.8",
        +      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz",
        +      "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.4"
        +      "dependencies": {
        +        "@vitest/utils": "2.1.8",
        +        "pathe": "^1.1.2"
        +      },
        +      "funding": {
        +        "url": "https://opencollective.com/vitest"
               }
             },
        -    "node_modules/acorn-walk": {
        -      "version": "7.0.0",
        -      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.0.0.tgz",
        -      "integrity": "sha512-7Bv1We7ZGuU79zZbb6rRqcpxo3OY+zrdtloZWoyD8fmGX+FeXRjE+iuGkZjSXLVovLzrsvMGMy0EkwA0E0umxg==",
        +    "node_modules/@vitest/snapshot": {
        +      "version": "2.1.8",
        +      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz",
        +      "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.4.0"
        +      "dependencies": {
        +        "@vitest/pretty-format": "2.1.8",
        +        "magic-string": "^0.30.12",
        +        "pathe": "^1.1.2"
        +      },
        +      "funding": {
        +        "url": "https://opencollective.com/vitest"
               }
             },
        -    "node_modules/agent-base": {
        -      "version": "6.0.2",
        -      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
        -      "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
        +    "node_modules/@vitest/spy": {
        +      "version": "2.1.8",
        +      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz",
        +      "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==",
               "dev": true,
               "dependencies": {
        -        "debug": "4"
        +        "tinyspy": "^3.0.2"
               },
        -      "engines": {
        -        "node": ">= 6.0.0"
        +      "funding": {
        +        "url": "https://opencollective.com/vitest"
               }
             },
        -    "node_modules/agent-base/node_modules/debug": {
        -      "version": "4.3.4",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
        -      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
        +    "node_modules/@vitest/utils": {
        +      "version": "2.1.8",
        +      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz",
        +      "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==",
               "dev": true,
               "dependencies": {
        -        "ms": "2.1.2"
        -      },
        -      "engines": {
        -        "node": ">=6.0"
        +        "@vitest/pretty-format": "2.1.8",
        +        "loupe": "^3.1.2",
        +        "tinyrainbow": "^1.2.0"
               },
        -      "peerDependenciesMeta": {
        -        "supports-color": {
        -          "optional": true
        -        }
        +      "funding": {
        +        "url": "https://opencollective.com/vitest"
               }
             },
        -    "node_modules/agent-base/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        -      "dev": true
        -    },
        -    "node_modules/aggregate-error": {
        -      "version": "4.0.1",
        -      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz",
        -      "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==",
        +    "node_modules/@vue/compiler-core": {
        +      "version": "3.5.13",
        +      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
        +      "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
               "dev": true,
        +      "optional": true,
               "dependencies": {
        -        "clean-stack": "^4.0.0",
        -        "indent-string": "^5.0.0"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "@babel/parser": "^7.25.3",
        +        "@vue/shared": "3.5.13",
        +        "entities": "^4.5.0",
        +        "estree-walker": "^2.0.2",
        +        "source-map-js": "^1.2.0"
               }
             },
        -    "node_modules/aggregate-error/node_modules/indent-string": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
        -      "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
        +    "node_modules/@vue/compiler-core/node_modules/entities": {
        +      "version": "4.5.0",
        +      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
        +      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
               "dev": true,
        +      "optional": true,
               "engines": {
        -        "node": ">=12"
        +        "node": ">=0.12"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/fb55/entities?sponsor=1"
               }
             },
        -    "node_modules/ajv": {
        -      "version": "6.12.6",
        -      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
        -      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
        +    "node_modules/@vue/compiler-dom": {
        +      "version": "3.5.13",
        +      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
        +      "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
               "dev": true,
        +      "optional": true,
               "dependencies": {
        -        "fast-deep-equal": "^3.1.1",
        -        "fast-json-stable-stringify": "^2.0.0",
        -        "json-schema-traverse": "^0.4.1",
        -        "uri-js": "^4.2.2"
        -      },
        -      "funding": {
        -        "type": "github",
        -        "url": "https://github.com/sponsors/epoberezkin"
        +        "@vue/compiler-core": "3.5.13",
        +        "@vue/shared": "3.5.13"
               }
             },
        -    "node_modules/all-contributors-cli": {
        -      "version": "6.19.0",
        -      "resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.19.0.tgz",
        -      "integrity": "sha512-QJN4iLeTeYpTZJES8XFTzQ+itA1qSyBbxLapJLtwrnY+kipyRhCX49fS/s/qftQQym9XLATMZUpUeEeJSox1sw==",
        +    "node_modules/@vue/compiler-sfc": {
        +      "version": "3.5.13",
        +      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
        +      "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
               "dev": true,
        +      "optional": true,
               "dependencies": {
        -        "@babel/runtime": "^7.7.6",
        -        "async": "^3.0.1",
        -        "chalk": "^4.0.0",
        -        "didyoumean": "^1.2.1",
        -        "inquirer": "^7.0.4",
        -        "json-fixer": "^1.5.1",
        -        "lodash": "^4.11.2",
        -        "node-fetch": "^2.6.0",
        -        "pify": "^5.0.0",
        -        "yargs": "^15.0.1"
        -      },
        -      "bin": {
        -        "all-contributors": "dist/cli.js"
        +        "@babel/parser": "^7.25.3",
        +        "@vue/compiler-core": "3.5.13",
        +        "@vue/compiler-dom": "3.5.13",
        +        "@vue/compiler-ssr": "3.5.13",
        +        "@vue/shared": "3.5.13",
        +        "estree-walker": "^2.0.2",
        +        "magic-string": "^0.30.11",
        +        "postcss": "^8.4.48",
        +        "source-map-js": "^1.2.0"
        +      }
        +    },
        +    "node_modules/@vue/compiler-ssr": {
        +      "version": "3.5.13",
        +      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
        +      "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
        +      "dev": true,
        +      "optional": true,
        +      "dependencies": {
        +        "@vue/compiler-dom": "3.5.13",
        +        "@vue/shared": "3.5.13"
        +      }
        +    },
        +    "node_modules/@vue/shared": {
        +      "version": "3.5.13",
        +      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
        +      "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
        +      "dev": true,
        +      "optional": true
        +    },
        +    "node_modules/@wdio/config": {
        +      "version": "9.5.0",
        +      "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.5.0.tgz",
        +      "integrity": "sha512-ty0laZy9J6pYpPd9BzNS4/P9RcRFCQfiacQuJFCkaM0NXjOtkWnyMnrqLP09nyUEQYhOGwANwShbsS+EaUkmSQ==",
        +      "dev": true,
        +      "dependencies": {
        +        "@wdio/logger": "9.4.4",
        +        "@wdio/types": "9.5.0",
        +        "@wdio/utils": "9.5.0",
        +        "deepmerge-ts": "^7.0.3",
        +        "glob": "^10.2.2",
        +        "import-meta-resolve": "^4.0.0"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=18.20.0"
               }
             },
        -    "node_modules/all-contributors-cli/node_modules/@babel/runtime": {
        -      "version": "7.11.2",
        -      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
        -      "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
        +    "node_modules/@wdio/config/node_modules/brace-expansion": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
        +      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
               "dev": true,
               "dependencies": {
        -        "regenerator-runtime": "^0.13.4"
        +        "balanced-match": "^1.0.0"
               }
             },
        -    "node_modules/all-contributors-cli/node_modules/ansi-styles": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        -      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
        +    "node_modules/@wdio/config/node_modules/foreground-child": {
        +      "version": "3.3.0",
        +      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
        +      "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
               "dev": true,
               "dependencies": {
        -        "color-convert": "^2.0.1"
        +        "cross-spawn": "^7.0.0",
        +        "signal-exit": "^4.0.1"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=14"
               },
               "funding": {
        -        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/all-contributors-cli/node_modules/async": {
        -      "version": "3.2.4",
        -      "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
        -      "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
        -      "dev": true
        -    },
        -    "node_modules/all-contributors-cli/node_modules/chalk": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
        -      "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
        +    "node_modules/@wdio/config/node_modules/glob": {
        +      "version": "10.4.5",
        +      "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
        +      "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
               "dev": true,
               "dependencies": {
        -        "ansi-styles": "^4.1.0",
        -        "supports-color": "^7.1.0"
        +        "foreground-child": "^3.1.0",
        +        "jackspeak": "^3.1.2",
        +        "minimatch": "^9.0.4",
        +        "minipass": "^7.1.2",
        +        "package-json-from-dist": "^1.0.0",
        +        "path-scurry": "^1.11.1"
               },
        -      "engines": {
        -        "node": ">=10"
        +      "bin": {
        +        "glob": "dist/esm/bin.mjs"
               },
               "funding": {
        -        "url": "https://github.com/chalk/chalk?sponsor=1"
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/all-contributors-cli/node_modules/color-convert": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        -      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
        +    "node_modules/@wdio/config/node_modules/minimatch": {
        +      "version": "9.0.5",
        +      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
        +      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
               "dev": true,
               "dependencies": {
        -        "color-name": "~1.1.4"
        +        "brace-expansion": "^2.0.1"
               },
               "engines": {
        -        "node": ">=7.0.0"
        +        "node": ">=16 || 14 >=14.17"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/all-contributors-cli/node_modules/has-flag": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        -      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        +    "node_modules/@wdio/config/node_modules/signal-exit": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
        +      "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=14"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/all-contributors-cli/node_modules/pify": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
        -      "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==",
        +    "node_modules/@wdio/logger": {
        +      "version": "9.4.4",
        +      "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.4.4.tgz",
        +      "integrity": "sha512-BXx8RXFUW2M4dcO6t5Le95Hi2ZkTQBRsvBQqLekT2rZ6Xmw8ZKZBPf0FptnoftFGg6dYmwnDidYv/0+4PiHjpQ==",
               "dev": true,
        -      "engines": {
        -        "node": ">=10"
        +      "dependencies": {
        +        "chalk": "^5.1.2",
        +        "loglevel": "^1.6.0",
        +        "loglevel-plugin-prefix": "^0.8.4",
        +        "strip-ansi": "^7.1.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "engines": {
        +        "node": ">=18.20.0"
               }
             },
        -    "node_modules/all-contributors-cli/node_modules/regenerator-runtime": {
        -      "version": "0.13.7",
        -      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
        -      "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
        -      "dev": true
        -    },
        -    "node_modules/all-contributors-cli/node_modules/supports-color": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        -      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        +    "node_modules/@wdio/logger/node_modules/ansi-regex": {
        +      "version": "6.1.0",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
        +      "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
               "dev": true,
        -      "dependencies": {
        -        "has-flag": "^4.0.0"
        -      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
               }
             },
        -    "node_modules/all-package-names": {
        -      "version": "2.0.685",
        -      "resolved": "https://registry.npmjs.org/all-package-names/-/all-package-names-2.0.685.tgz",
        -      "integrity": "sha512-9dYQ6O5e4p1Strn2RH0SY10YXuBrKfe6PEOnKUMjpuOeOJhuc9owQrhvOSELH1fdj2Re21H9idoQnkoI4/Zdlg==",
        +    "node_modules/@wdio/logger/node_modules/chalk": {
        +      "version": "5.4.1",
        +      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
        +      "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
               "dev": true,
        -      "dependencies": {
        -        "commander-version": "^1.1.0",
        -        "p-lock": "^2.0.0",
        -        "parse-json-object": "^2.0.1",
        -        "progress": "^2.0.3",
        -        "types-json": "^1.2.2"
        +      "engines": {
        +        "node": "^12.17.0 || ^14.13 || >=16.0.0"
               },
        -      "bin": {
        -        "all-package-names": "build/bin/index.js"
        +      "funding": {
        +        "url": "https://github.com/chalk/chalk?sponsor=1"
               }
             },
        -    "node_modules/ansi-align": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
        -      "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
        +    "node_modules/@wdio/logger/node_modules/strip-ansi": {
        +      "version": "7.1.0",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
        +      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
               "dev": true,
               "dependencies": {
        -        "string-width": "^4.1.0"
        -      }
        -    },
        -    "node_modules/ansi-align/node_modules/ansi-regex": {
        -      "version": "5.0.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        -      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
        -      "dev": true,
        +        "ansi-regex": "^6.0.1"
        +      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
               }
             },
        -    "node_modules/ansi-align/node_modules/emoji-regex": {
        -      "version": "8.0.0",
        -      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        -      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        +    "node_modules/@wdio/protocols": {
        +      "version": "9.4.4",
        +      "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.4.4.tgz",
        +      "integrity": "sha512-IqbAWe5feY3xOwjbiW/2iwcbDU+nm5CX5Om835mxaNWqEoQiaZuTin4YgtgsPeSEBcSFtQ+2ooswr/6vIZdxSw==",
               "dev": true
             },
        -    "node_modules/ansi-align/node_modules/is-fullwidth-code-point": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        -      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
        +    "node_modules/@wdio/repl": {
        +      "version": "9.4.4",
        +      "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.4.4.tgz",
        +      "integrity": "sha512-kchPRhoG/pCn4KhHGiL/ocNhdpR8OkD2e6sANlSUZ4TGBVi86YSIEjc2yXUwLacHknC/EnQk/SFnqd4MsNjGGg==",
               "dev": true,
        +      "dependencies": {
        +        "@types/node": "^20.1.0"
        +      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=18.20.0"
               }
             },
        -    "node_modules/ansi-align/node_modules/string-width": {
        -      "version": "4.2.3",
        -      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
        -      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
        +    "node_modules/@wdio/repl/node_modules/@types/node": {
        +      "version": "20.17.14",
        +      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.14.tgz",
        +      "integrity": "sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==",
               "dev": true,
               "dependencies": {
        -        "emoji-regex": "^8.0.0",
        -        "is-fullwidth-code-point": "^3.0.0",
        -        "strip-ansi": "^6.0.1"
        -      },
        -      "engines": {
        -        "node": ">=8"
        +        "undici-types": "~6.19.2"
               }
             },
        -    "node_modules/ansi-align/node_modules/strip-ansi": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        -      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
        +    "node_modules/@wdio/repl/node_modules/undici-types": {
        +      "version": "6.19.8",
        +      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
        +      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
        +      "dev": true
        +    },
        +    "node_modules/@wdio/types": {
        +      "version": "9.5.0",
        +      "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.5.0.tgz",
        +      "integrity": "sha512-sX1vH6VebVHvgdpySTOXzKNazHBu+yFr5bMvveJ2T4vKjJTJOAwO6nPftjKcgGDfhyYxM3xOCvboKICdQKFgEg==",
               "dev": true,
               "dependencies": {
        -        "ansi-regex": "^5.0.1"
        +        "@types/node": "^20.1.0"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=18.20.0"
               }
             },
        -    "node_modules/ansi-colors": {
        -      "version": "4.1.1",
        -      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
        -      "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
        +    "node_modules/@wdio/types/node_modules/@types/node": {
        +      "version": "20.17.14",
        +      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.14.tgz",
        +      "integrity": "sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==",
               "dev": true,
        -      "engines": {
        -        "node": ">=6"
        +      "dependencies": {
        +        "undici-types": "~6.19.2"
               }
             },
        -    "node_modules/ansi-escapes": {
        -      "version": "4.3.2",
        -      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
        -      "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
        +    "node_modules/@wdio/types/node_modules/undici-types": {
        +      "version": "6.19.8",
        +      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
        +      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
        +      "dev": true
        +    },
        +    "node_modules/@wdio/utils": {
        +      "version": "9.5.0",
        +      "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.5.0.tgz",
        +      "integrity": "sha512-Lrom21pIdp60IiKznecJT6Za0GGeXxKikPyWHH5z9SY5TmuSoIhuG/bq40lfUjeSW7doiL1JEsFHbRbzt0bHYA==",
               "dev": true,
               "dependencies": {
        -        "type-fest": "^0.21.3"
        +        "@puppeteer/browsers": "^2.2.0",
        +        "@wdio/logger": "9.4.4",
        +        "@wdio/types": "9.5.0",
        +        "decamelize": "^6.0.0",
        +        "deepmerge-ts": "^7.0.3",
        +        "edgedriver": "^6.1.1",
        +        "geckodriver": "^5.0.0",
        +        "get-port": "^7.0.0",
        +        "import-meta-resolve": "^4.0.0",
        +        "locate-app": "^2.2.24",
        +        "safaridriver": "^1.0.0",
        +        "split2": "^4.2.0",
        +        "wait-port": "^1.1.0"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=18.20.0"
        +      }
        +    },
        +    "node_modules/@wdio/utils/node_modules/decamelize": {
        +      "version": "6.0.0",
        +      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz",
        +      "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==",
        +      "dev": true,
        +      "engines": {
        +        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/ansi-regex": {
        -      "version": "2.1.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
        -      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
        +    "node_modules/@zip.js/zip.js": {
        +      "version": "2.7.54",
        +      "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.54.tgz",
        +      "integrity": "sha512-qMrJVg2hoEsZJjMJez9yI2+nZlBUxgYzGV3mqcb2B/6T1ihXp0fWBDYlVHlHquuorgNUQP5a8qSmX6HF5rFJNg==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "bun": ">=0.7.0",
        +        "deno": ">=1.0.0",
        +        "node": ">=16.5.0"
               }
             },
        -    "node_modules/ansi-styles": {
        -      "version": "3.2.1",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
        -      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
        +    "node_modules/abort-controller": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
        +      "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
               "dev": true,
               "dependencies": {
        -        "color-convert": "^1.9.0"
        +        "event-target-shim": "^5.0.0"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=6.5"
               }
             },
        -    "node_modules/anymatch": {
        -      "version": "3.1.3",
        -      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
        -      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
        +    "node_modules/acorn": {
        +      "version": "8.14.0",
        +      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
        +      "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
        +      "bin": {
        +        "acorn": "bin/acorn"
        +      },
        +      "engines": {
        +        "node": ">=0.4.0"
        +      }
        +    },
        +    "node_modules/acorn-jsx": {
        +      "version": "5.3.2",
        +      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
        +      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
               "dev": true,
        +      "peerDependencies": {
        +        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
        +      }
        +    },
        +    "node_modules/acorn-walk": {
        +      "version": "8.3.4",
        +      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
        +      "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
               "dependencies": {
        -        "normalize-path": "^3.0.0",
        -        "picomatch": "^2.0.4"
        +        "acorn": "^8.11.0"
               },
               "engines": {
        -        "node": ">= 8"
        +        "node": ">=0.4.0"
               }
             },
        -    "node_modules/app-root-path": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz",
        -      "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=",
        +    "node_modules/ajv": {
        +      "version": "6.12.6",
        +      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
        +      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
               "dev": true,
        -      "engines": {
        -        "node": ">= 4.0.0"
        +      "dependencies": {
        +        "fast-deep-equal": "^3.1.1",
        +        "fast-json-stable-stringify": "^2.0.0",
        +        "json-schema-traverse": "^0.4.1",
        +        "uri-js": "^4.2.2"
        +      },
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/epoberezkin"
               }
             },
        -    "node_modules/append-transform": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz",
        -      "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==",
        +    "node_modules/all-contributors-cli": {
        +      "version": "6.19.0",
        +      "resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.19.0.tgz",
        +      "integrity": "sha512-QJN4iLeTeYpTZJES8XFTzQ+itA1qSyBbxLapJLtwrnY+kipyRhCX49fS/s/qftQQym9XLATMZUpUeEeJSox1sw==",
               "dev": true,
               "dependencies": {
        -        "default-require-extensions": "^2.0.0"
        +        "@babel/runtime": "^7.7.6",
        +        "async": "^3.0.1",
        +        "chalk": "^4.0.0",
        +        "didyoumean": "^1.2.1",
        +        "inquirer": "^7.0.4",
        +        "json-fixer": "^1.5.1",
        +        "lodash": "^4.11.2",
        +        "node-fetch": "^2.6.0",
        +        "pify": "^5.0.0",
        +        "yargs": "^15.0.1"
        +      },
        +      "bin": {
        +        "all-contributors": "dist/cli.js"
               },
               "engines": {
                 "node": ">=4"
               }
             },
        -    "node_modules/archy": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
        -      "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=",
        -      "dev": true
        -    },
        -    "node_modules/argparse": {
        -      "version": "1.0.10",
        -      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
        -      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
        +    "node_modules/all-contributors-cli/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
               "dependencies": {
        -        "sprintf-js": "~1.0.2"
        +        "color-convert": "^2.0.1"
        +      },
        +      "engines": {
        +        "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/array-each": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz",
        -      "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=",
        +    "node_modules/all-contributors-cli/node_modules/async": {
        +      "version": "3.2.4",
        +      "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
        +      "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
        +      "dev": true
        +    },
        +    "node_modules/all-contributors-cli/node_modules/chalk": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
        +      "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
               "dev": true,
        +      "dependencies": {
        +        "ansi-styles": "^4.1.0",
        +        "supports-color": "^7.1.0"
        +      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/chalk?sponsor=1"
               }
             },
        -    "node_modules/array-flatten": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
        -      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
        -      "dev": true
        -    },
        -    "node_modules/array-slice": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz",
        -      "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==",
        +    "node_modules/all-contributors-cli/node_modules/color-convert": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
        +      "dependencies": {
        +        "color-name": "~1.1.4"
        +      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/array-union": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
        -      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
        +    "node_modules/all-contributors-cli/node_modules/has-flag": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        +      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
               "dev": true,
               "engines": {
                 "node": ">=8"
               }
             },
        -    "node_modules/arrify": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
        -      "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
        +    "node_modules/all-contributors-cli/node_modules/pify": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
        +      "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/asn1": {
        -      "version": "0.1.11",
        -      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz",
        -      "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=",
        +    "node_modules/all-contributors-cli/node_modules/supports-color": {
        +      "version": "7.2.0",
        +      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        +      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
               "dev": true,
        -      "optional": true,
        +      "dependencies": {
        +        "has-flag": "^4.0.0"
        +      },
               "engines": {
        -        "node": ">=0.4.9"
        +        "node": ">=8"
               }
             },
        -    "node_modules/asn1.js": {
        -      "version": "5.4.1",
        -      "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
        -      "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
        +    "node_modules/ansi-escapes": {
        +      "version": "4.3.2",
        +      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
        +      "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
               "dev": true,
               "dependencies": {
        -        "bn.js": "^4.0.0",
        -        "inherits": "^2.0.1",
        -        "minimalistic-assert": "^1.0.0",
        -        "safer-buffer": "^2.1.0"
        +        "type-fest": "^0.21.3"
        +      },
        +      "engines": {
        +        "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/assert": {
        -      "version": "1.5.0",
        -      "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
        -      "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
        +    "node_modules/anymatch": {
        +      "version": "3.1.3",
        +      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
        +      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
               "dev": true,
               "dependencies": {
        -        "object-assign": "^4.1.1",
        -        "util": "0.10.3"
        +        "normalize-path": "^3.0.0",
        +        "picomatch": "^2.0.4"
        +      },
        +      "engines": {
        +        "node": ">= 8"
               }
             },
        -    "node_modules/assert-plus": {
        -      "version": "0.1.5",
        -      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz",
        -      "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=",
        +    "node_modules/archiver": {
        +      "version": "7.0.1",
        +      "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz",
        +      "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==",
               "dev": true,
        -      "optional": true,
        +      "dependencies": {
        +        "archiver-utils": "^5.0.2",
        +        "async": "^3.2.4",
        +        "buffer-crc32": "^1.0.0",
        +        "readable-stream": "^4.0.0",
        +        "readdir-glob": "^1.1.2",
        +        "tar-stream": "^3.0.0",
        +        "zip-stream": "^6.0.1"
        +      },
               "engines": {
        -        "node": ">=0.8"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/assert/node_modules/inherits": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
        -      "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
        -      "dev": true
        -    },
        -    "node_modules/assert/node_modules/util": {
        -      "version": "0.10.3",
        -      "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
        -      "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
        +    "node_modules/archiver-utils": {
        +      "version": "5.0.2",
        +      "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz",
        +      "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==",
               "dev": true,
               "dependencies": {
        -        "inherits": "2.0.1"
        +        "glob": "^10.0.0",
        +        "graceful-fs": "^4.2.0",
        +        "is-stream": "^2.0.1",
        +        "lazystream": "^1.0.0",
        +        "lodash": "^4.17.15",
        +        "normalize-path": "^3.0.0",
        +        "readable-stream": "^4.0.0"
        +      },
        +      "engines": {
        +        "node": ">= 14"
               }
             },
        -    "node_modules/assertion-error": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz",
        -      "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=",
        +    "node_modules/archiver-utils/node_modules/brace-expansion": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
        +      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
               "dev": true,
        -      "engines": {
        -        "node": "*"
        +      "dependencies": {
        +        "balanced-match": "^1.0.0"
               }
             },
        -    "node_modules/async": {
        -      "version": "2.6.4",
        -      "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
        -      "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
        +    "node_modules/archiver-utils/node_modules/buffer": {
        +      "version": "6.0.3",
        +      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
        +      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
        +        }
        +      ],
               "dependencies": {
        -        "lodash": "^4.17.14"
        +        "base64-js": "^1.3.1",
        +        "ieee754": "^1.2.1"
               }
             },
        -    "node_modules/async-limiter": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
        -      "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
        -      "dev": true
        -    },
        -    "node_modules/aws-sign2": {
        -      "version": "0.5.0",
        -      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz",
        -      "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=",
        +    "node_modules/archiver-utils/node_modules/events": {
        +      "version": "3.3.0",
        +      "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
        +      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
               "dev": true,
        -      "optional": true,
               "engines": {
        -        "node": "*"
        -      }
        -    },
        -    "node_modules/babel-plugin-dynamic-import-node": {
        -      "version": "2.3.3",
        -      "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz",
        -      "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "object.assign": "^4.1.0"
        +        "node": ">=0.8.x"
               }
             },
        -    "node_modules/babel-plugin-i18next-extract": {
        -      "version": "0.5.0",
        -      "resolved": "https://registry.npmjs.org/babel-plugin-i18next-extract/-/babel-plugin-i18next-extract-0.5.0.tgz",
        -      "integrity": "sha512-HTb6GVqW6z4KWc3GoZjnj6zLwoaEXoxlo5MkOQs6MC3NxqCt/qORVD3hdWX23dfDa/MlfPHQHTVseoR94xPz3Q==",
        +    "node_modules/archiver-utils/node_modules/foreground-child": {
        +      "version": "3.3.0",
        +      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
        +      "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
               "dev": true,
               "dependencies": {
        -        "@babel/core": "^7.4.5",
        -        "i18next": "^19.0.0",
        -        "json-stable-stringify": "^1.0.1"
        +        "cross-spawn": "^7.0.0",
        +        "signal-exit": "^4.0.1"
               },
               "engines": {
        -        "node": ">=10.0.0"
        +        "node": ">=14"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/babel-plugin-i18next-extract/node_modules/json-stable-stringify": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
        -      "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
        +    "node_modules/archiver-utils/node_modules/glob": {
        +      "version": "10.4.5",
        +      "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
        +      "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
               "dev": true,
               "dependencies": {
        -        "jsonify": "~0.0.0"
        +        "foreground-child": "^3.1.0",
        +        "jackspeak": "^3.1.2",
        +        "minimatch": "^9.0.4",
        +        "minipass": "^7.1.2",
        +        "package-json-from-dist": "^1.0.0",
        +        "path-scurry": "^1.11.1"
        +      },
        +      "bin": {
        +        "glob": "dist/esm/bin.mjs"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/babel-plugin-istanbul": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz",
        -      "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==",
        +    "node_modules/archiver-utils/node_modules/is-stream": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
        +      "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/helper-plugin-utils": "^7.0.0",
        -        "find-up": "^3.0.0",
        -        "istanbul-lib-instrument": "^3.3.0",
        -        "test-exclude": "^5.2.3"
        -      },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/babel-plugin-istanbul/node_modules/find-up": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
        -      "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
        +    "node_modules/archiver-utils/node_modules/minimatch": {
        +      "version": "9.0.5",
        +      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
        +      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
               "dev": true,
               "dependencies": {
        -        "locate-path": "^3.0.0"
        +        "brace-expansion": "^2.0.1"
               },
               "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/babelify": {
        -      "version": "10.0.0",
        -      "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz",
        -      "integrity": "sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=6.9.0"
        +        "node": ">=16 || 14 >=14.17"
               },
        -      "peerDependencies": {
        -        "@babel/core": "^7.0.0"
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/balanced-match": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
        -      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
        -      "dev": true
        -    },
        -    "node_modules/base64-js": {
        -      "version": "1.3.1",
        -      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
        -      "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
        -      "dev": true
        -    },
        -    "node_modules/basic-auth": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
        -      "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
        +    "node_modules/archiver-utils/node_modules/readable-stream": {
        +      "version": "4.7.0",
        +      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
        +      "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
               "dev": true,
               "dependencies": {
        -        "safe-buffer": "5.1.2"
        +        "abort-controller": "^3.0.0",
        +        "buffer": "^6.0.3",
        +        "events": "^3.3.0",
        +        "process": "^0.11.10",
        +        "string_decoder": "^1.3.0"
               },
               "engines": {
        -        "node": ">= 0.8"
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
               }
             },
        -    "node_modules/basic-auth/node_modules/safe-buffer": {
        -      "version": "5.1.2",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
        -      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
        -      "dev": true
        -    },
        -    "node_modules/batch": {
        -      "version": "0.6.1",
        -      "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
        -      "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
        -      "dev": true
        -    },
        -    "node_modules/big-integer": {
        -      "version": "1.6.51",
        -      "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
        -      "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
        +    "node_modules/archiver-utils/node_modules/safe-buffer": {
        +      "version": "5.2.1",
        +      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
        +      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.6"
        -      }
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
        +        }
        +      ]
             },
        -    "node_modules/binary-extensions": {
        -      "version": "2.2.0",
        -      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
        -      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
        +    "node_modules/archiver-utils/node_modules/signal-exit": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
        +      "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=14"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/bl": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
        -      "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
        +    "node_modules/archiver-utils/node_modules/string_decoder": {
        +      "version": "1.3.0",
        +      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
        +      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
               "dev": true,
               "dependencies": {
        -        "buffer": "^5.5.0",
        -        "inherits": "^2.0.4",
        -        "readable-stream": "^3.4.0"
        +        "safe-buffer": "~5.2.0"
               }
             },
        -    "node_modules/bl/node_modules/buffer": {
        -      "version": "5.7.1",
        -      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
        -      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
        +    "node_modules/archiver/node_modules/async": {
        +      "version": "3.2.6",
        +      "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
        +      "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
        +      "dev": true
        +    },
        +    "node_modules/archiver/node_modules/buffer": {
        +      "version": "6.0.3",
        +      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
        +      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
               "dev": true,
               "funding": [
                 {
        @@ -2973,30 +3192,35 @@
               ],
               "dependencies": {
                 "base64-js": "^1.3.1",
        -        "ieee754": "^1.1.13"
        +        "ieee754": "^1.2.1"
               }
             },
        -    "node_modules/bl/node_modules/inherits": {
        -      "version": "2.0.4",
        -      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
        -      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
        -      "dev": true
        +    "node_modules/archiver/node_modules/events": {
        +      "version": "3.3.0",
        +      "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
        +      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=0.8.x"
        +      }
             },
        -    "node_modules/bl/node_modules/readable-stream": {
        -      "version": "3.6.0",
        -      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
        -      "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
        +    "node_modules/archiver/node_modules/readable-stream": {
        +      "version": "4.7.0",
        +      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
        +      "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
               "dev": true,
               "dependencies": {
        -        "inherits": "^2.0.3",
        -        "string_decoder": "^1.1.1",
        -        "util-deprecate": "^1.0.1"
        +        "abort-controller": "^3.0.0",
        +        "buffer": "^6.0.3",
        +        "events": "^3.3.0",
        +        "process": "^0.11.10",
        +        "string_decoder": "^1.3.0"
               },
               "engines": {
        -        "node": ">= 6"
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
               }
             },
        -    "node_modules/bl/node_modules/safe-buffer": {
        +    "node_modules/archiver/node_modules/safe-buffer": {
               "version": "5.2.1",
               "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
               "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
        @@ -3016,7 +3240,7 @@
                 }
               ]
             },
        -    "node_modules/bl/node_modules/string_decoder": {
        +    "node_modules/archiver/node_modules/string_decoder": {
               "version": "1.3.0",
               "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
               "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
        @@ -3025,301 +3249,166 @@
                 "safe-buffer": "~5.2.0"
               }
             },
        -    "node_modules/bn.js": {
        -      "version": "4.11.8",
        -      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
        -      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
        -      "dev": true
        -    },
        -    "node_modules/body": {
        -      "version": "5.1.0",
        -      "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz",
        -      "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=",
        +    "node_modules/aria-query": {
        +      "version": "5.3.0",
        +      "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
        +      "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
               "dev": true,
               "dependencies": {
        -        "continuable-cache": "^0.3.1",
        -        "error": "^7.0.0",
        -        "raw-body": "~1.1.0",
        -        "safe-json-parse": "~1.0.1"
        +        "dequal": "^2.0.3"
               }
             },
        -    "node_modules/body-parser": {
        -      "version": "1.20.3",
        -      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
        -      "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
        +    "node_modules/assertion-error": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
        +      "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
               "dev": true,
        -      "dependencies": {
        -        "bytes": "3.1.2",
        -        "content-type": "~1.0.5",
        -        "debug": "2.6.9",
        -        "depd": "2.0.0",
        -        "destroy": "1.2.0",
        -        "http-errors": "2.0.0",
        -        "iconv-lite": "0.4.24",
        -        "on-finished": "2.4.1",
        -        "qs": "6.13.0",
        -        "raw-body": "2.5.2",
        -        "type-is": "~1.6.18",
        -        "unpipe": "1.0.0"
        -      },
               "engines": {
        -        "node": ">= 0.8",
        -        "npm": "1.2.8000 || >= 1.4.16"
        +        "node": ">=12"
               }
             },
        -    "node_modules/body-parser/node_modules/bytes": {
        -      "version": "3.1.2",
        -      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
        -      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
        +    "node_modules/ast-types": {
        +      "version": "0.13.4",
        +      "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
        +      "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
               "dev": true,
        +      "dependencies": {
        +        "tslib": "^2.0.1"
        +      },
               "engines": {
        -        "node": ">= 0.8"
        +        "node": ">=4"
               }
             },
        -    "node_modules/body-parser/node_modules/depd": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
        -      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        +    "node_modules/ast-types/node_modules/tslib": {
        +      "version": "2.8.1",
        +      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
        +      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
        +      "dev": true
             },
        -    "node_modules/body-parser/node_modules/http-errors": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
        -      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
        +    "node_modules/b4a": {
        +      "version": "1.6.7",
        +      "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
        +      "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==",
        +      "dev": true
        +    },
        +    "node_modules/bail": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
        +      "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
               "dev": true,
        -      "dependencies": {
        -        "depd": "2.0.0",
        -        "inherits": "2.0.4",
        -        "setprototypeof": "1.2.0",
        -        "statuses": "2.0.1",
        -        "toidentifier": "1.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.8"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/body-parser/node_modules/inherits": {
        -      "version": "2.0.4",
        -      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
        -      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
        +    "node_modules/balanced-match": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
        +      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
               "dev": true
             },
        -    "node_modules/body-parser/node_modules/on-finished": {
        -      "version": "2.4.1",
        -      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
        -      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
        +    "node_modules/bare-events": {
        +      "version": "2.5.4",
        +      "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz",
        +      "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==",
               "dev": true,
        -      "dependencies": {
        -        "ee-first": "1.1.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        +      "optional": true
             },
        -    "node_modules/body-parser/node_modules/raw-body": {
        -      "version": "2.5.2",
        -      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
        -      "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
        +    "node_modules/bare-fs": {
        +      "version": "4.0.1",
        +      "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.0.1.tgz",
        +      "integrity": "sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==",
               "dev": true,
        +      "optional": true,
               "dependencies": {
        -        "bytes": "3.1.2",
        -        "http-errors": "2.0.0",
        -        "iconv-lite": "0.4.24",
        -        "unpipe": "1.0.0"
        +        "bare-events": "^2.0.0",
        +        "bare-path": "^3.0.0",
        +        "bare-stream": "^2.0.0"
               },
               "engines": {
        -        "node": ">= 0.8"
        +        "bare": ">=1.7.0"
               }
             },
        -    "node_modules/body-parser/node_modules/setprototypeof": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
        -      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
        -      "dev": true
        -    },
        -    "node_modules/body-parser/node_modules/statuses": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
        -      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
        +    "node_modules/bare-os": {
        +      "version": "3.4.0",
        +      "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.4.0.tgz",
        +      "integrity": "sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA==",
               "dev": true,
        +      "optional": true,
               "engines": {
        -        "node": ">= 0.8"
        +        "bare": ">=1.6.0"
               }
             },
        -    "node_modules/boom": {
        -      "version": "0.4.2",
        -      "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz",
        -      "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=",
        -      "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).",
        +    "node_modules/bare-path": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
        +      "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
               "dev": true,
               "optional": true,
               "dependencies": {
        -        "hoek": "0.9.x"
        -      },
        -      "engines": {
        -        "node": ">=0.8.0"
        +        "bare-os": "^3.0.1"
               }
             },
        -    "node_modules/boxen": {
        -      "version": "7.1.1",
        -      "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz",
        -      "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==",
        +    "node_modules/bare-stream": {
        +      "version": "2.6.4",
        +      "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.4.tgz",
        +      "integrity": "sha512-G6i3A74FjNq4nVrrSTUz5h3vgXzBJnjmWAVlBWaZETkgu+LgKd7AiyOml3EDJY1AHlIbBHKDXE+TUT53Ff8OaA==",
               "dev": true,
        +      "optional": true,
               "dependencies": {
        -        "ansi-align": "^3.0.1",
        -        "camelcase": "^7.0.1",
        -        "chalk": "^5.2.0",
        -        "cli-boxes": "^3.0.0",
        -        "string-width": "^5.1.2",
        -        "type-fest": "^2.13.0",
        -        "widest-line": "^4.0.1",
        -        "wrap-ansi": "^8.1.0"
        +        "streamx": "^2.21.0"
               },
        -      "engines": {
        -        "node": ">=14.16"
        +      "peerDependencies": {
        +        "bare-buffer": "*",
        +        "bare-events": "*"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "peerDependenciesMeta": {
        +        "bare-buffer": {
        +          "optional": true
        +        },
        +        "bare-events": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/boxen/node_modules/ansi-regex": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
        -      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
        +    "node_modules/base64-js": {
        +      "version": "1.3.1",
        +      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
        +      "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
        +      "dev": true
        +    },
        +    "node_modules/basic-ftp": {
        +      "version": "5.0.5",
        +      "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz",
        +      "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==",
               "dev": true,
               "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
        +        "node": ">=10.0.0"
               }
             },
        -    "node_modules/boxen/node_modules/ansi-styles": {
        -      "version": "6.2.1",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
        -      "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
        +    "node_modules/binary-extensions": {
        +      "version": "2.2.0",
        +      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
        +      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
               "dev": true,
               "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
        +        "node": ">=8"
               }
             },
        -    "node_modules/boxen/node_modules/camelcase": {
        -      "version": "7.0.1",
        -      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz",
        -      "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==",
        +    "node_modules/boolbase": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
        +      "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
        +      "dev": true
        +    },
        +    "node_modules/brace-expansion": {
        +      "version": "1.1.8",
        +      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
        +      "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
               "dev": true,
        -      "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/boxen/node_modules/chalk": {
        -      "version": "5.3.0",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
        -      "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
        -      "dev": true,
        -      "engines": {
        -        "node": "^12.17.0 || ^14.13 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/chalk?sponsor=1"
        -      }
        -    },
        -    "node_modules/boxen/node_modules/emoji-regex": {
        -      "version": "9.2.2",
        -      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
        -      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
        -      "dev": true
        -    },
        -    "node_modules/boxen/node_modules/string-width": {
        -      "version": "5.1.2",
        -      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
        -      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
        -      "dev": true,
        -      "dependencies": {
        -        "eastasianwidth": "^0.2.0",
        -        "emoji-regex": "^9.2.2",
        -        "strip-ansi": "^7.0.1"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/boxen/node_modules/strip-ansi": {
        -      "version": "7.1.0",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
        -      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-regex": "^6.0.1"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
        -      }
        -    },
        -    "node_modules/boxen/node_modules/type-fest": {
        -      "version": "2.19.0",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
        -      "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12.20"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/boxen/node_modules/wrap-ansi": {
        -      "version": "8.1.0",
        -      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
        -      "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^6.1.0",
        -        "string-width": "^5.0.1",
        -        "strip-ansi": "^7.0.1"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
        -      }
        -    },
        -    "node_modules/bplist-parser": {
        -      "version": "0.2.0",
        -      "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz",
        -      "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==",
        -      "dev": true,
        -      "dependencies": {
        -        "big-integer": "^1.6.44"
        -      },
        -      "engines": {
        -        "node": ">= 5.10.0"
        -      }
        -    },
        -    "node_modules/brace-expansion": {
        -      "version": "1.1.8",
        -      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
        -      "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
        -      "dev": true,
        -      "dependencies": {
        -        "balanced-match": "^1.0.0",
        -        "concat-map": "0.0.1"
        +      "dependencies": {
        +        "balanced-match": "^1.0.0",
        +        "concat-map": "0.0.1"
               }
             },
             "node_modules/braces": {
        @@ -3334,355 +3423,38 @@
                 "node": ">=8"
               }
             },
        -    "node_modules/brfs-babel": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/brfs-babel/-/brfs-babel-2.0.0.tgz",
        -      "integrity": "sha512-PzvOpd+5b0ScbN35oRiQvU+g60yt2+TazPKGtnsG4uSbLBfS3wm7A/VnYc0jUbrcqwc+cxV9BN9DndV3CT3XTQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "@babel/core": "^7.5.5",
        -        "babel-plugin-static-fs": "^2.0.1",
        -        "through2": "^2.0.0"
        -      }
        -    },
        -    "node_modules/brfs-babel/node_modules/babel-plugin-static-fs": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/babel-plugin-static-fs/-/babel-plugin-static-fs-2.0.1.tgz",
        -      "integrity": "sha512-LIH+OIIEs1oo7xN4zZhDhEV90Zpcjl9ZZIcXfbm20+CmpIiTu2VOY9718uqra0htaHkwkmTGFGZyYgOy2pllGw==",
        -      "dev": true,
        -      "dependencies": {
        -        "@babel/template": "^7.4.4",
        -        "@babel/types": "^7.5.5",
        -        "browser-resolve": "^1.11.3",
        -        "events": "^1.1.0",
        -        "resolve": "^1.11.1"
        -      }
        -    },
        -    "node_modules/brfs-babel/node_modules/resolve": {
        -      "version": "1.22.1",
        -      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
        -      "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
        -      "dev": true,
        -      "dependencies": {
        -        "is-core-module": "^2.9.0",
        -        "path-parse": "^1.0.7",
        -        "supports-preserve-symlinks-flag": "^1.0.0"
        -      },
        -      "bin": {
        -        "resolve": "bin/resolve"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        -      }
        -    },
        -    "node_modules/brorand": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
        -      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
        -      "dev": true
        -    },
        -    "node_modules/browser-pack": {
        -      "version": "6.1.0",
        -      "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz",
        -      "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==",
        -      "dev": true,
        -      "dependencies": {
        -        "combine-source-map": "~0.8.0",
        -        "defined": "^1.0.0",
        -        "JSONStream": "^1.0.3",
        -        "safe-buffer": "^5.1.1",
        -        "through2": "^2.0.0",
        -        "umd": "^3.0.0"
        -      },
        -      "bin": {
        -        "browser-pack": "bin/cmd.js"
        -      }
        -    },
        -    "node_modules/browser-resolve": {
        -      "version": "1.11.3",
        -      "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz",
        -      "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "resolve": "1.1.7"
        -      }
        -    },
        -    "node_modules/browser-resolve/node_modules/resolve": {
        -      "version": "1.1.7",
        -      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
        -      "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
        -      "dev": true
        -    },
        -    "node_modules/browser-stdout": {
        -      "version": "1.3.1",
        -      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
        -      "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
        -      "dev": true
        -    },
        -    "node_modules/browserify": {
        -      "version": "16.5.0",
        -      "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.5.0.tgz",
        -      "integrity": "sha512-6bfI3cl76YLAnCZ75AGu/XPOsqUhRyc0F/olGIJeCxtfxF2HvPKEcmjU9M8oAPxl4uBY1U7Nry33Q6koV3f2iw==",
        -      "dev": true,
        -      "dependencies": {
        -        "assert": "^1.4.0",
        -        "browser-pack": "^6.0.1",
        -        "browser-resolve": "^1.11.0",
        -        "browserify-zlib": "~0.2.0",
        -        "buffer": "^5.0.2",
        -        "cached-path-relative": "^1.0.0",
        -        "concat-stream": "^1.6.0",
        -        "console-browserify": "^1.1.0",
        -        "constants-browserify": "~1.0.0",
        -        "crypto-browserify": "^3.0.0",
        -        "defined": "^1.0.0",
        -        "deps-sort": "^2.0.0",
        -        "domain-browser": "^1.2.0",
        -        "duplexer2": "~0.1.2",
        -        "events": "^2.0.0",
        -        "glob": "^7.1.0",
        -        "has": "^1.0.0",
        -        "htmlescape": "^1.1.0",
        -        "https-browserify": "^1.0.0",
        -        "inherits": "~2.0.1",
        -        "insert-module-globals": "^7.0.0",
        -        "JSONStream": "^1.0.3",
        -        "labeled-stream-splicer": "^2.0.0",
        -        "mkdirp": "^0.5.0",
        -        "module-deps": "^6.0.0",
        -        "os-browserify": "~0.3.0",
        -        "parents": "^1.0.1",
        -        "path-browserify": "~0.0.0",
        -        "process": "~0.11.0",
        -        "punycode": "^1.3.2",
        -        "querystring-es3": "~0.2.0",
        -        "read-only-stream": "^2.0.0",
        -        "readable-stream": "^2.0.2",
        -        "resolve": "^1.1.4",
        -        "shasum": "^1.0.0",
        -        "shell-quote": "^1.6.1",
        -        "stream-browserify": "^2.0.0",
        -        "stream-http": "^3.0.0",
        -        "string_decoder": "^1.1.1",
        -        "subarg": "^1.0.0",
        -        "syntax-error": "^1.1.1",
        -        "through2": "^2.0.0",
        -        "timers-browserify": "^1.0.1",
        -        "tty-browserify": "0.0.1",
        -        "url": "~0.11.0",
        -        "util": "~0.10.1",
        -        "vm-browserify": "^1.0.0",
        -        "xtend": "^4.0.0"
        -      },
        -      "bin": {
        -        "browserify": "bin/cmd.js"
        -      },
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        -    },
        -    "node_modules/browserify-aes": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
        -      "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
        -      "dev": true,
        -      "dependencies": {
        -        "buffer-xor": "^1.0.3",
        -        "cipher-base": "^1.0.0",
        -        "create-hash": "^1.1.0",
        -        "evp_bytestokey": "^1.0.3",
        -        "inherits": "^2.0.1",
        -        "safe-buffer": "^5.0.1"
        -      }
        -    },
        -    "node_modules/browserify-cipher": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
        -      "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
        -      "dev": true,
        -      "dependencies": {
        -        "browserify-aes": "^1.0.4",
        -        "browserify-des": "^1.0.0",
        -        "evp_bytestokey": "^1.0.0"
        -      }
        -    },
        -    "node_modules/browserify-des": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
        -      "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
        -      "dev": true,
        -      "dependencies": {
        -        "cipher-base": "^1.0.1",
        -        "des.js": "^1.0.0",
        -        "inherits": "^2.0.1",
        -        "safe-buffer": "^5.1.2"
        -      }
        -    },
        -    "node_modules/browserify-des/node_modules/safe-buffer": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
        -      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
        -      "dev": true
        -    },
        -    "node_modules/browserify-rsa": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz",
        -      "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==",
        -      "dev": true,
        -      "dependencies": {
        -        "bn.js": "^5.0.0",
        -        "randombytes": "^2.0.1"
        -      }
        -    },
        -    "node_modules/browserify-rsa/node_modules/bn.js": {
        -      "version": "5.2.1",
        -      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
        -      "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
        -      "dev": true
        -    },
        -    "node_modules/browserify-sign": {
        -      "version": "4.2.2",
        -      "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
        -      "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
        -      "dev": true,
        -      "dependencies": {
        -        "bn.js": "^5.2.1",
        -        "browserify-rsa": "^4.1.0",
        -        "create-hash": "^1.2.0",
        -        "create-hmac": "^1.1.7",
        -        "elliptic": "^6.5.4",
        -        "inherits": "^2.0.4",
        -        "parse-asn1": "^5.1.6",
        -        "readable-stream": "^3.6.2",
        -        "safe-buffer": "^5.2.1"
        -      },
        -      "engines": {
        -        "node": ">= 4"
        -      }
        -    },
        -    "node_modules/browserify-sign/node_modules/bn.js": {
        -      "version": "5.2.1",
        -      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
        -      "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
        -      "dev": true
        -    },
        -    "node_modules/browserify-sign/node_modules/inherits": {
        -      "version": "2.0.4",
        -      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
        -      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
        -      "dev": true
        -    },
        -    "node_modules/browserify-sign/node_modules/readable-stream": {
        -      "version": "3.6.2",
        -      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
        -      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
        -      "dev": true,
        -      "dependencies": {
        -        "inherits": "^2.0.3",
        -        "string_decoder": "^1.1.1",
        -        "util-deprecate": "^1.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 6"
        -      }
        -    },
        -    "node_modules/browserify-sign/node_modules/safe-buffer": {
        -      "version": "5.2.1",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
        -      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
        +    "node_modules/browserslist": {
        +      "version": "4.24.4",
        +      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
        +      "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
               "dev": true,
               "funding": [
                 {
        -          "type": "github",
        -          "url": "https://github.com/sponsors/feross"
        +          "type": "opencollective",
        +          "url": "https://opencollective.com/browserslist"
                 },
                 {
        -          "type": "patreon",
        -          "url": "https://www.patreon.com/feross"
        +          "type": "tidelift",
        +          "url": "https://tidelift.com/funding/github/npm/browserslist"
                 },
                 {
        -          "type": "consulting",
        -          "url": "https://feross.org/support"
        +          "type": "github",
        +          "url": "https://github.com/sponsors/ai"
                 }
        -      ]
        -    },
        -    "node_modules/browserify-sign/node_modules/string_decoder": {
        -      "version": "1.3.0",
        -      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
        -      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
        -      "dev": true,
        -      "dependencies": {
        -        "safe-buffer": "~5.2.0"
        -      }
        -    },
        -    "node_modules/browserify-zlib": {
        -      "version": "0.2.0",
        -      "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
        -      "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
        -      "dev": true,
        -      "dependencies": {
        -        "pako": "~1.0.5"
        -      }
        -    },
        -    "node_modules/browserify/node_modules/events": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz",
        -      "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.4.x"
        -      }
        -    },
        -    "node_modules/browserify/node_modules/safe-buffer": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
        -      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
        -      "dev": true
        -    },
        -    "node_modules/browserify/node_modules/string_decoder": {
        -      "version": "1.3.0",
        -      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
        -      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
        -      "dev": true,
        -      "dependencies": {
        -        "safe-buffer": "~5.2.0"
        -      }
        -    },
        -    "node_modules/browserslist": {
        -      "version": "4.16.6",
        -      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
        -      "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
        -      "dev": true,
        +      ],
               "dependencies": {
        -        "caniuse-lite": "^1.0.30001219",
        -        "colorette": "^1.2.2",
        -        "electron-to-chromium": "^1.3.723",
        -        "escalade": "^3.1.1",
        -        "node-releases": "^1.1.71"
        +        "caniuse-lite": "^1.0.30001688",
        +        "electron-to-chromium": "^1.5.73",
        +        "node-releases": "^2.0.19",
        +        "update-browserslist-db": "^1.1.1"
               },
               "bin": {
                 "browserslist": "cli.js"
               },
               "engines": {
                 "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
        -      },
        -      "funding": {
        -        "type": "opencollective",
        -        "url": "https://opencollective.com/browserslist"
               }
             },
        -    "node_modules/browserslist/node_modules/electron-to-chromium": {
        -      "version": "1.3.736",
        -      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.736.tgz",
        -      "integrity": "sha512-DY8dA7gR51MSo66DqitEQoUMQ0Z+A2DSXFi7tK304bdTVqczCAfUuyQw6Wdg8hIoo5zIxkU1L24RQtUce1Ioig==",
        -      "dev": true
        -    },
        -    "node_modules/browserslist/node_modules/node-releases": {
        -      "version": "1.1.72",
        -      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz",
        -      "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==",
        -      "dev": true
        -    },
             "node_modules/buffer": {
               "version": "5.4.3",
               "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz",
        @@ -3694,12 +3466,12 @@
               }
             },
             "node_modules/buffer-crc32": {
        -      "version": "0.2.13",
        -      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
        -      "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
        +      "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
               "dev": true,
               "engines": {
        -        "node": "*"
        +        "node": ">=8.0.0"
               }
             },
             "node_modules/buffer-from": {
        @@ -3708,156 +3480,13 @@
               "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
               "dev": true
             },
        -    "node_modules/buffer-xor": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
        -      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
        -      "dev": true
        -    },
        -    "node_modules/builtin-modules": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
        -      "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
        +    "node_modules/cac": {
        +      "version": "6.7.14",
        +      "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
        +      "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/builtin-status-codes": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
        -      "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
        -      "dev": true
        -    },
        -    "node_modules/builtins": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
        -      "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==",
        -      "dev": true
        -    },
        -    "node_modules/bundle-name": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
        -      "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==",
        -      "dev": true,
        -      "dependencies": {
        -        "run-applescript": "^5.0.0"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/bytes": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz",
        -      "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=",
        -      "dev": true
        -    },
        -    "node_modules/cacheable-lookup": {
        -      "version": "5.0.4",
        -      "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
        -      "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10.6.0"
        -      }
        -    },
        -    "node_modules/cacheable-request": {
        -      "version": "7.0.4",
        -      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
        -      "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
        -      "dev": true,
        -      "dependencies": {
        -        "clone-response": "^1.0.2",
        -        "get-stream": "^5.1.0",
        -        "http-cache-semantics": "^4.0.0",
        -        "keyv": "^4.0.0",
        -        "lowercase-keys": "^2.0.0",
        -        "normalize-url": "^6.0.1",
        -        "responselike": "^2.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/cacheable-request/node_modules/get-stream": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
        -      "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
        -      "dev": true,
        -      "dependencies": {
        -        "pump": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/cached-path-relative": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz",
        -      "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==",
        -      "dev": true
        -    },
        -    "node_modules/caching-transform": {
        -      "version": "3.0.2",
        -      "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz",
        -      "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==",
        -      "dev": true,
        -      "dependencies": {
        -        "hasha": "^3.0.0",
        -        "make-dir": "^2.0.0",
        -        "package-hash": "^3.0.0",
        -        "write-file-atomic": "^2.4.2"
        -      },
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/caching-transform/node_modules/make-dir": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
        -      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
        -      "dev": true,
        -      "dependencies": {
        -        "pify": "^4.0.1",
        -        "semver": "^5.6.0"
        -      },
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/caching-transform/node_modules/semver": {
        -      "version": "5.7.1",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
        -      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
        -      "dev": true,
        -      "bin": {
        -        "semver": "bin/semver"
        -      }
        -    },
        -    "node_modules/call-bind": {
        -      "version": "1.0.7",
        -      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
        -      "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
        -      "dev": true,
        -      "dependencies": {
        -        "es-define-property": "^1.0.0",
        -        "es-errors": "^1.3.0",
        -        "function-bind": "^1.1.2",
        -        "get-intrinsic": "^1.2.4",
        -        "set-function-length": "^1.2.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.4"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        +        "node": ">=8"
               }
             },
             "node_modules/callsites": {
        @@ -3878,52 +3507,10 @@
                 "node": ">=6"
               }
             },
        -    "node_modules/camelcase-keys": {
        -      "version": "8.0.2",
        -      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-8.0.2.tgz",
        -      "integrity": "sha512-qMKdlOfsjlezMqxkUGGMaWWs17i2HoL15tM+wtx8ld4nLrUwU58TFdvyGOz/piNP842KeO8yXvggVQSdQ828NA==",
        -      "dev": true,
        -      "dependencies": {
        -        "camelcase": "^7.0.0",
        -        "map-obj": "^4.3.0",
        -        "quick-lru": "^6.1.1",
        -        "type-fest": "^2.13.0"
        -      },
        -      "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/camelcase-keys/node_modules/camelcase": {
        -      "version": "7.0.1",
        -      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz",
        -      "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/camelcase-keys/node_modules/type-fest": {
        -      "version": "2.19.0",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
        -      "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12.20"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
             "node_modules/caniuse-lite": {
        -      "version": "1.0.30001549",
        -      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz",
        -      "integrity": "sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==",
        +      "version": "1.0.30001695",
        +      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz",
        +      "integrity": "sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==",
               "dev": true,
               "funding": [
                 {
        @@ -3940,4938 +3527,485 @@
                 }
               ]
             },
        -    "node_modules/chai": {
        -      "version": "3.5.0",
        -      "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz",
        -      "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=",
        -      "dev": true,
        -      "dependencies": {
        -        "assertion-error": "^1.0.1",
        -        "deep-eql": "^0.1.3",
        -        "type-detect": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": ">= 0.4.0"
        -      }
        -    },
        -    "node_modules/chalk": {
        -      "version": "2.4.2",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
        -      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^3.2.1",
        -        "escape-string-regexp": "^1.0.5",
        -        "supports-color": "^5.3.0"
        -      },
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/chardet": {
        -      "version": "0.7.0",
        -      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
        -      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
        -      "dev": true
        -    },
        -    "node_modules/chokidar": {
        -      "version": "3.5.3",
        -      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
        -      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
        -      "dev": true,
        -      "funding": [
        -        {
        -          "type": "individual",
        -          "url": "https://paulmillr.com/funding/"
        -        }
        -      ],
        -      "dependencies": {
        -        "anymatch": "~3.1.2",
        -        "braces": "~3.0.2",
        -        "glob-parent": "~5.1.2",
        -        "is-binary-path": "~2.1.0",
        -        "is-glob": "~4.0.1",
        -        "normalize-path": "~3.0.0",
        -        "readdirp": "~3.6.0"
        -      },
        -      "engines": {
        -        "node": ">= 8.10.0"
        -      },
        -      "optionalDependencies": {
        -        "fsevents": "~2.3.2"
        -      }
        -    },
        -    "node_modules/chokidar/node_modules/glob-parent": {
        -      "version": "5.1.2",
        -      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
        -      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
        -      "dev": true,
        -      "dependencies": {
        -        "is-glob": "^4.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 6"
        -      }
        -    },
        -    "node_modules/chownr": {
        -      "version": "1.1.4",
        -      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
        -      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
        -      "dev": true
        -    },
        -    "node_modules/ci-info": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
        -      "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
        -      "dev": true
        -    },
        -    "node_modules/cipher-base": {
        -      "version": "1.0.4",
        -      "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
        -      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
        -      "dev": true,
        -      "dependencies": {
        -        "inherits": "^2.0.1",
        -        "safe-buffer": "^5.0.1"
        -      }
        -    },
        -    "node_modules/clean-stack": {
        -      "version": "4.2.0",
        -      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz",
        -      "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==",
        -      "dev": true,
        -      "dependencies": {
        -        "escape-string-regexp": "5.0.0"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/clean-stack/node_modules/escape-string-regexp": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
        -      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/cli-boxes": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
        -      "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/cli-cursor": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
        -      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
        -      "dev": true,
        -      "dependencies": {
        -        "restore-cursor": "^3.1.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/cli-spinners": {
        -      "version": "0.1.2",
        -      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz",
        -      "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/cli-truncate": {
        -      "version": "0.2.1",
        -      "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
        -      "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=",
        -      "dev": true,
        -      "dependencies": {
        -        "slice-ansi": "0.0.4",
        -        "string-width": "^1.0.1"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/cli-truncate/node_modules/slice-ansi": {
        -      "version": "0.0.4",
        -      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
        -      "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/cli-width": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
        -      "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 10"
        -      }
        -    },
        -    "node_modules/cliui": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
        -      "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "string-width": "^4.2.0",
        -        "strip-ansi": "^6.0.0",
        -        "wrap-ansi": "^6.2.0"
        -      }
        -    },
        -    "node_modules/cliui/node_modules/ansi-regex": {
        -      "version": "5.0.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        -      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/cliui/node_modules/emoji-regex": {
        -      "version": "8.0.0",
        -      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        -      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        -      "dev": true
        -    },
        -    "node_modules/cliui/node_modules/is-fullwidth-code-point": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        -      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/cliui/node_modules/string-width": {
        -      "version": "4.2.2",
        -      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
        -      "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
        -      "dev": true,
        -      "dependencies": {
        -        "emoji-regex": "^8.0.0",
        -        "is-fullwidth-code-point": "^3.0.0",
        -        "strip-ansi": "^6.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/cliui/node_modules/strip-ansi": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
        -      "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-regex": "^5.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/clone": {
        -      "version": "1.0.4",
        -      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
        -      "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.8"
        -      }
        -    },
        -    "node_modules/clone-response": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
        -      "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
        -      "dev": true,
        -      "dependencies": {
        -        "mimic-response": "^1.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/code-point-at": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
        -      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/color-convert": {
        -      "version": "1.9.1",
        -      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
        -      "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "color-name": "^1.1.1"
        -      }
        -    },
        -    "node_modules/color-name": {
        -      "version": "1.1.4",
        -      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
        -      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
        -      "dev": true
        -    },
        -    "node_modules/colorette": {
        -      "version": "1.2.2",
        -      "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
        -      "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
        -      "dev": true
        -    },
        -    "node_modules/colors": {
        -      "version": "1.1.2",
        -      "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
        -      "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.1.90"
        -      }
        -    },
        -    "node_modules/combine-source-map": {
        -      "version": "0.8.0",
        -      "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz",
        -      "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=",
        -      "dev": true,
        -      "dependencies": {
        -        "convert-source-map": "~1.1.0",
        -        "inline-source-map": "~0.6.0",
        -        "lodash.memoize": "~3.0.3",
        -        "source-map": "~0.5.3"
        -      }
        -    },
        -    "node_modules/combine-source-map/node_modules/convert-source-map": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
        -      "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=",
        -      "dev": true
        -    },
        -    "node_modules/combine-source-map/node_modules/source-map": {
        -      "version": "0.5.7",
        -      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
        -      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/combined-stream": {
        -      "version": "0.0.7",
        -      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
        -      "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=",
        -      "dev": true,
        -      "optional": true,
        -      "dependencies": {
        -        "delayed-stream": "0.0.5"
        -      },
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        -    },
        -    "node_modules/commander": {
        -      "version": "2.9.0",
        -      "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
        -      "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
        -      "dev": true,
        -      "dependencies": {
        -        "graceful-readlink": ">= 1.0.0"
        -      },
        -      "engines": {
        -        "node": ">= 0.6.x"
        -      }
        -    },
        -    "node_modules/commander-version": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/commander-version/-/commander-version-1.1.0.tgz",
        -      "integrity": "sha512-9aNW4N6q6EPDUszLRH6k9IwO6OoGYh3HRgUF/fA7Zs+Mz1v1x5akSqT7QGB8JsGY7AG7qMA7oRRB/4yyn33FYA==",
        -      "dev": true,
        -      "dependencies": {
        -        "@bconnorwhite/module": "^2.0.2",
        -        "commander": "^6.1.0"
        -      }
        -    },
        -    "node_modules/commander-version/node_modules/commander": {
        -      "version": "6.2.1",
        -      "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
        -      "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 6"
        -      }
        -    },
        -    "node_modules/commondir": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
        -      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
        -      "dev": true
        -    },
        -    "node_modules/compare-versions": {
        -      "version": "3.6.0",
        -      "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
        -      "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==",
        -      "dev": true
        -    },
        -    "node_modules/concat-map": {
        -      "version": "0.0.1",
        -      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
        -      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
        -      "dev": true
        -    },
        -    "node_modules/concat-stream": {
        -      "version": "1.6.0",
        -      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
        -      "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
        -      "dev": true,
        -      "engines": [
        -        "node >= 0.8"
        -      ],
        -      "dependencies": {
        -        "inherits": "^2.0.3",
        -        "readable-stream": "^2.2.2",
        -        "typedarray": "^0.0.6"
        -      }
        -    },
        -    "node_modules/config-chain": {
        -      "version": "1.1.13",
        -      "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
        -      "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "ini": "^1.3.4",
        -        "proto-list": "~1.2.1"
        -      }
        -    },
        -    "node_modules/configstore": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz",
        -      "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==",
        -      "dev": true,
        -      "dependencies": {
        -        "dot-prop": "^6.0.1",
        -        "graceful-fs": "^4.2.6",
        -        "unique-string": "^3.0.0",
        -        "write-file-atomic": "^3.0.3",
        -        "xdg-basedir": "^5.0.1"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/yeoman/configstore?sponsor=1"
        -      }
        -    },
        -    "node_modules/configstore/node_modules/dot-prop": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
        -      "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
        -      "dev": true,
        -      "dependencies": {
        -        "is-obj": "^2.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/configstore/node_modules/is-obj": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
        -      "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/configstore/node_modules/write-file-atomic": {
        -      "version": "3.0.3",
        -      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
        -      "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
        -      "dev": true,
        -      "dependencies": {
        -        "imurmurhash": "^0.1.4",
        -        "is-typedarray": "^1.0.0",
        -        "signal-exit": "^3.0.2",
        -        "typedarray-to-buffer": "^3.1.5"
        -      }
        -    },
        -    "node_modules/connect": {
        -      "version": "3.7.0",
        -      "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
        -      "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "debug": "2.6.9",
        -        "finalhandler": "1.1.2",
        -        "parseurl": "~1.3.3",
        -        "utils-merge": "1.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.10.0"
        -      }
        -    },
        -    "node_modules/connect-livereload": {
        -      "version": "0.6.1",
        -      "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz",
        -      "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==",
        -      "dev": true,
        -      "engines": {
        -        "node": "*"
        -      }
        -    },
        -    "node_modules/connect-modrewrite": {
        -      "version": "0.10.2",
        -      "resolved": "https://registry.npmjs.org/connect-modrewrite/-/connect-modrewrite-0.10.2.tgz",
        -      "integrity": "sha512-37+kS9t26vxjW5ErNrr8d04F7Us1EH7XhHtxSm8yE8kO2uDF2DsPI+qI2wCeBSaoakXKit0/88sg4vL2Wl8tDw==",
        -      "dev": true,
        -      "dependencies": {
        -        "qs": "^6.3.1"
        -      }
        -    },
        -    "node_modules/console-browserify": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
        -      "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
        -      "dev": true
        -    },
        -    "node_modules/constants-browserify": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
        -      "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
        -      "dev": true
        -    },
        -    "node_modules/content-disposition": {
        -      "version": "0.5.4",
        -      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
        -      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "safe-buffer": "5.2.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.6"
        -      }
        -    },
        -    "node_modules/content-disposition/node_modules/safe-buffer": {
        -      "version": "5.2.1",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
        -      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
        -      "dev": true,
        -      "funding": [
        -        {
        -          "type": "github",
        -          "url": "https://github.com/sponsors/feross"
        -        },
        -        {
        -          "type": "patreon",
        -          "url": "https://www.patreon.com/feross"
        -        },
        -        {
        -          "type": "consulting",
        -          "url": "https://feross.org/support"
        -        }
        -      ]
        -    },
        -    "node_modules/content-type": {
        -      "version": "1.0.5",
        -      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
        -      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.6"
        -      }
        -    },
        -    "node_modules/continuable-cache": {
        -      "version": "0.3.1",
        -      "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz",
        -      "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=",
        -      "dev": true
        -    },
        -    "node_modules/convert-source-map": {
        -      "version": "1.6.0",
        -      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
        -      "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
        -      "dev": true,
        -      "dependencies": {
        -        "safe-buffer": "~5.1.1"
        -      }
        -    },
        -    "node_modules/cookie": {
        -      "version": "0.7.1",
        -      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
        -      "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.6"
        -      }
        -    },
        -    "node_modules/cookie-signature": {
        -      "version": "1.0.6",
        -      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
        -      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
        -      "dev": true
        -    },
        -    "node_modules/core-js": {
        -      "version": "3.6.5",
        -      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
        -      "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==",
        -      "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
        -      "dev": true,
        -      "hasInstallScript": true,
        -      "funding": {
        -        "type": "opencollective",
        -        "url": "https://opencollective.com/core-js"
        -      }
        -    },
        -    "node_modules/core-js-compat": {
        -      "version": "3.6.5",
        -      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz",
        -      "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==",
        -      "dev": true,
        -      "dependencies": {
        -        "browserslist": "^4.8.5",
        -        "semver": "7.0.0"
        -      },
        -      "funding": {
        -        "type": "opencollective",
        -        "url": "https://opencollective.com/core-js"
        -      }
        -    },
        -    "node_modules/core-js-compat/node_modules/semver": {
        -      "version": "7.0.0",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
        -      "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
        -      "dev": true,
        -      "bin": {
        -        "semver": "bin/semver.js"
        -      }
        -    },
        -    "node_modules/core-util-is": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
        -      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
        -      "dev": true
        -    },
        -    "node_modules/cosmiconfig": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-1.1.0.tgz",
        -      "integrity": "sha1-DeoPmATv37kp+7GxiOJVU+oFPTc=",
        -      "dev": true,
        -      "dependencies": {
        -        "graceful-fs": "^4.1.2",
        -        "js-yaml": "^3.4.3",
        -        "minimist": "^1.2.0",
        -        "object-assign": "^4.0.1",
        -        "os-homedir": "^1.0.1",
        -        "parse-json": "^2.2.0",
        -        "pinkie-promise": "^2.0.0",
        -        "require-from-string": "^1.1.0"
        -      }
        -    },
        -    "node_modules/cp-file": {
        -      "version": "6.2.0",
        -      "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz",
        -      "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==",
        -      "dev": true,
        -      "dependencies": {
        -        "graceful-fs": "^4.1.2",
        -        "make-dir": "^2.0.0",
        -        "nested-error-stacks": "^2.0.0",
        -        "pify": "^4.0.1",
        -        "safe-buffer": "^5.0.1"
        -      },
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/cp-file/node_modules/make-dir": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
        -      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
        -      "dev": true,
        -      "dependencies": {
        -        "pify": "^4.0.1",
        -        "semver": "^5.6.0"
        -      },
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/cp-file/node_modules/semver": {
        -      "version": "5.7.1",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
        -      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
        -      "dev": true,
        -      "bin": {
        -        "semver": "bin/semver"
        -      }
        -    },
        -    "node_modules/create-ecdh": {
        -      "version": "4.0.3",
        -      "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
        -      "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
        -      "dev": true,
        -      "dependencies": {
        -        "bn.js": "^4.1.0",
        -        "elliptic": "^6.0.0"
        -      }
        -    },
        -    "node_modules/create-hash": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
        -      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
        -      "dev": true,
        -      "dependencies": {
        -        "cipher-base": "^1.0.1",
        -        "inherits": "^2.0.1",
        -        "md5.js": "^1.3.4",
        -        "ripemd160": "^2.0.1",
        -        "sha.js": "^2.4.0"
        -      }
        -    },
        -    "node_modules/create-hmac": {
        -      "version": "1.1.7",
        -      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
        -      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
        -      "dev": true,
        -      "dependencies": {
        -        "cipher-base": "^1.0.3",
        -        "create-hash": "^1.1.0",
        -        "inherits": "^2.0.1",
        -        "ripemd160": "^2.0.0",
        -        "safe-buffer": "^5.0.1",
        -        "sha.js": "^2.4.8"
        -      }
        -    },
        -    "node_modules/cross-fetch": {
        -      "version": "3.1.5",
        -      "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
        -      "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
        -      "dev": true,
        -      "dependencies": {
        -        "node-fetch": "2.6.7"
        -      }
        -    },
        -    "node_modules/cross-spawn": {
        -      "version": "5.1.0",
        -      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
        -      "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
        -      "dev": true,
        -      "dependencies": {
        -        "lru-cache": "^4.0.1",
        -        "shebang-command": "^1.2.0",
        -        "which": "^1.2.9"
        -      }
        -    },
        -    "node_modules/cryptiles": {
        -      "version": "0.2.2",
        -      "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz",
        -      "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=",
        -      "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).",
        -      "dev": true,
        -      "optional": true,
        -      "dependencies": {
        -        "boom": "0.4.x"
        -      },
        -      "engines": {
        -        "node": ">=0.8.0"
        -      }
        -    },
        -    "node_modules/crypto-browserify": {
        -      "version": "3.12.0",
        -      "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
        -      "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
        -      "dev": true,
        -      "dependencies": {
        -        "browserify-cipher": "^1.0.0",
        -        "browserify-sign": "^4.0.0",
        -        "create-ecdh": "^4.0.0",
        -        "create-hash": "^1.1.0",
        -        "create-hmac": "^1.1.0",
        -        "diffie-hellman": "^5.0.0",
        -        "inherits": "^2.0.1",
        -        "pbkdf2": "^3.0.3",
        -        "public-encrypt": "^4.0.0",
        -        "randombytes": "^2.0.0",
        -        "randomfill": "^1.0.3"
        -      },
        -      "engines": {
        -        "node": "*"
        -      }
        -    },
        -    "node_modules/crypto-random-string": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz",
        -      "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==",
        -      "dev": true,
        -      "dependencies": {
        -        "type-fest": "^1.0.1"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/crypto-random-string/node_modules/type-fest": {
        -      "version": "1.4.0",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
        -      "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/ctype": {
        -      "version": "0.5.3",
        -      "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz",
        -      "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=",
        -      "dev": true,
        -      "optional": true,
        -      "engines": {
        -        "node": ">= 0.4"
        -      }
        -    },
        -    "node_modules/d": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
        -      "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
        -      "dev": true,
        -      "dependencies": {
        -        "es5-ext": "^0.10.9"
        -      }
        -    },
        -    "node_modules/dash-ast": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz",
        -      "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==",
        -      "dev": true
        -    },
        -    "node_modules/date-fns": {
        -      "version": "1.29.0",
        -      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz",
        -      "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==",
        -      "dev": true
        -    },
        -    "node_modules/dateformat": {
        -      "version": "4.6.3",
        -      "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
        -      "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
        -      "dev": true,
        -      "engines": {
        -        "node": "*"
        -      }
        -    },
        -    "node_modules/debug": {
        -      "version": "2.6.9",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
        -      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
        -      "dev": true,
        -      "dependencies": {
        -        "ms": "2.0.0"
        -      }
        -    },
        -    "node_modules/decamelize": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
        -      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/decamelize-keys": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-2.0.1.tgz",
        -      "integrity": "sha512-nrNeSCtU2gV3Apcmn/EZ+aR20zKDuNDStV67jPiupokD3sOAFeMzslLMCFdKv1sPqzwoe5ZUhsSW9IAVgKSL/Q==",
        -      "dev": true,
        -      "dependencies": {
        -        "decamelize": "^6.0.0",
        -        "map-obj": "^4.3.0",
        -        "quick-lru": "^6.1.1",
        -        "type-fest": "^3.1.0"
        -      },
        -      "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/decamelize-keys/node_modules/decamelize": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz",
        -      "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==",
        -      "dev": true,
        -      "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/decamelize-keys/node_modules/type-fest": {
        -      "version": "3.13.0",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.0.tgz",
        -      "integrity": "sha512-Gur3yQGM9qiLNs0KPP7LPgeRbio2QTt4xXouobMCarR0/wyW3F+F/+OWwshg3NG0Adon7uQfSZBpB46NfhoF1A==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/decompress-response": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
        -      "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "mimic-response": "^3.1.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/decompress-response/node_modules/mimic-response": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
        -      "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/deep-eql": {
        -      "version": "0.1.3",
        -      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz",
        -      "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=",
        -      "dev": true,
        -      "dependencies": {
        -        "type-detect": "0.1.1"
        -      },
        -      "engines": {
        -        "node": "*"
        -      }
        -    },
        -    "node_modules/deep-eql/node_modules/type-detect": {
        -      "version": "0.1.1",
        -      "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz",
        -      "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=",
        -      "dev": true,
        -      "engines": {
        -        "node": "*"
        -      }
        -    },
        -    "node_modules/deep-extend": {
        -      "version": "0.6.0",
        -      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
        -      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4.0.0"
        -      }
        -    },
        -    "node_modules/deep-is": {
        -      "version": "0.1.4",
        -      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
        -      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
        -      "dev": true
        -    },
        -    "node_modules/default-browser": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz",
        -      "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==",
        -      "dev": true,
        -      "dependencies": {
        -        "bundle-name": "^3.0.0",
        -        "default-browser-id": "^3.0.0",
        -        "execa": "^7.1.1",
        -        "titleize": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/default-browser-id": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz",
        -      "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==",
        -      "dev": true,
        -      "dependencies": {
        -        "bplist-parser": "^0.2.0",
        -        "untildify": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/default-require-extensions": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz",
        -      "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=",
        -      "dev": true,
        -      "dependencies": {
        -        "strip-bom": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/default-require-extensions/node_modules/strip-bom": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
        -      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/defaults": {
        -      "version": "1.0.4",
        -      "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
        -      "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
        -      "dev": true,
        -      "dependencies": {
        -        "clone": "^1.0.2"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/defer-to-connect": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
        -      "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      }
        -    },
        -    "node_modules/define-data-property": {
        -      "version": "1.1.4",
        -      "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
        -      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
        -      "dev": true,
        -      "dependencies": {
        -        "es-define-property": "^1.0.0",
        -        "es-errors": "^1.3.0",
        -        "gopd": "^1.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.4"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        -      }
        -    },
        -    "node_modules/define-lazy-prop": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
        -      "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/define-properties": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
        -      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "object-keys": "^1.0.12"
        -      },
        -      "engines": {
        -        "node": ">= 0.4"
        -      }
        -    },
        -    "node_modules/defined": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
        -      "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
        -      "dev": true
        -    },
        -    "node_modules/del": {
        -      "version": "7.0.0",
        -      "resolved": "https://registry.npmjs.org/del/-/del-7.0.0.tgz",
        -      "integrity": "sha512-tQbV/4u5WVB8HMJr08pgw0b6nG4RGt/tj+7Numvq+zqcvUFeMaIWWOUFltiU+6go8BSO2/ogsB4EasDaj0y68Q==",
        -      "dev": true,
        -      "dependencies": {
        -        "globby": "^13.1.2",
        -        "graceful-fs": "^4.2.10",
        -        "is-glob": "^4.0.3",
        -        "is-path-cwd": "^3.0.0",
        -        "is-path-inside": "^4.0.0",
        -        "p-map": "^5.5.0",
        -        "rimraf": "^3.0.2",
        -        "slash": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/del/node_modules/globby": {
        -      "version": "13.2.2",
        -      "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz",
        -      "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==",
        -      "dev": true,
        -      "dependencies": {
        -        "dir-glob": "^3.0.1",
        -        "fast-glob": "^3.3.0",
        -        "ignore": "^5.2.4",
        -        "merge2": "^1.4.1",
        -        "slash": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/del/node_modules/p-map": {
        -      "version": "5.5.0",
        -      "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz",
        -      "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==",
        -      "dev": true,
        -      "dependencies": {
        -        "aggregate-error": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/del/node_modules/rimraf": {
        -      "version": "3.0.2",
        -      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
        -      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
        -      "dev": true,
        -      "dependencies": {
        -        "glob": "^7.1.3"
        -      },
        -      "bin": {
        -        "rimraf": "bin.js"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/isaacs"
        -      }
        -    },
        -    "node_modules/del/node_modules/slash": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
        -      "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/delayed-stream": {
        -      "version": "0.0.5",
        -      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz",
        -      "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=",
        -      "dev": true,
        -      "optional": true,
        -      "engines": {
        -        "node": ">=0.4.0"
        -      }
        -    },
        -    "node_modules/depd": {
        -      "version": "1.1.2",
        -      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
        -      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.6"
        -      }
        -    },
        -    "node_modules/deps-sort": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz",
        -      "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==",
        -      "dev": true,
        -      "dependencies": {
        -        "JSONStream": "^1.0.3",
        -        "shasum-object": "^1.0.0",
        -        "subarg": "^1.0.0",
        -        "through2": "^2.0.0"
        -      },
        -      "bin": {
        -        "deps-sort": "bin/cmd.js"
        -      }
        -    },
        -    "node_modules/derequire": {
        -      "version": "2.1.1",
        -      "resolved": "https://registry.npmjs.org/derequire/-/derequire-2.1.1.tgz",
        -      "integrity": "sha512-5hGVgKAEGhSGZM02abtkwDzqEOXun1dP9Ocw0yh7Pz7j70k4SNk7WURm93YyHbs2PcieRyX8m4ta1glGakw84Q==",
        -      "dev": true,
        -      "dependencies": {
        -        "acorn": "^7.1.1",
        -        "concat-stream": "^1.4.6",
        -        "escope": "^3.6.0",
        -        "through2": "^2.0.0",
        -        "yargs": "^15.3.1"
        -      },
        -      "bin": {
        -        "derequire": "bin/cmd.js"
        -      }
        -    },
        -    "node_modules/derequire/node_modules/acorn": {
        -      "version": "7.4.1",
        -      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
        -      "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
        -      "dev": true,
        -      "bin": {
        -        "acorn": "bin/acorn"
        -      },
        -      "engines": {
        -        "node": ">=0.4.0"
        -      }
        -    },
        -    "node_modules/des.js": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
        -      "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
        -      "dev": true,
        -      "dependencies": {
        -        "inherits": "^2.0.1",
        -        "minimalistic-assert": "^1.0.0"
        -      }
        -    },
        -    "node_modules/destroy": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
        -      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.8",
        -        "npm": "1.2.8000 || >= 1.4.16"
        -      }
        -    },
        -    "node_modules/detect-file": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
        -      "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/detective": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
        -      "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
        -      "dev": true,
        -      "dependencies": {
        -        "acorn-node": "^1.6.1",
        -        "defined": "^1.0.0",
        -        "minimist": "^1.1.1"
        -      },
        -      "bin": {
        -        "detective": "bin/detective.js"
        -      },
        -      "engines": {
        -        "node": ">=0.8.0"
        -      }
        -    },
        -    "node_modules/devtools-protocol": {
        -      "version": "0.0.1045489",
        -      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1045489.tgz",
        -      "integrity": "sha512-D+PTmWulkuQW4D1NTiCRCFxF7pQPn0hgp4YyX4wAQ6xYXKOadSWPR3ENGDQ47MW/Ewc9v2rpC/UEEGahgBYpSQ==",
        -      "dev": true
        -    },
        -    "node_modules/didyoumean": {
        -      "version": "1.2.1",
        -      "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.1.tgz",
        -      "integrity": "sha1-6S7f2tplN9SE1zwBcv0eugxJdv8=",
        -      "dev": true
        -    },
        -    "node_modules/diff": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
        -      "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.3.1"
        -      }
        -    },
        -    "node_modules/diffie-hellman": {
        -      "version": "5.0.3",
        -      "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
        -      "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
        -      "dev": true,
        -      "dependencies": {
        -        "bn.js": "^4.1.0",
        -        "miller-rabin": "^4.0.0",
        -        "randombytes": "^2.0.0"
        -      }
        -    },
        -    "node_modules/dir-glob": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
        -      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
        -      "dev": true,
        -      "dependencies": {
        -        "path-type": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/doctrine": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
        -      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
        -      "dev": true,
        -      "dependencies": {
        -        "esutils": "^2.0.2"
        -      },
        -      "engines": {
        -        "node": ">=6.0.0"
        -      }
        -    },
        -    "node_modules/domain-browser": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
        -      "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.4",
        -        "npm": ">=1.2"
        -      }
        -    },
        -    "node_modules/dot-prop": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-7.2.0.tgz",
        -      "integrity": "sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==",
        -      "dev": true,
        -      "dependencies": {
        -        "type-fest": "^2.11.2"
        -      },
        -      "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/dot-prop/node_modules/type-fest": {
        -      "version": "2.19.0",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
        -      "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12.20"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/duplexer": {
        -      "version": "0.1.1",
        -      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
        -      "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
        -      "dev": true
        -    },
        -    "node_modules/duplexer2": {
        -      "version": "0.1.4",
        -      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
        -      "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
        -      "dev": true,
        -      "dependencies": {
        -        "readable-stream": "^2.0.2"
        -      }
        -    },
        -    "node_modules/duplexify": {
        -      "version": "3.7.1",
        -      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
        -      "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
        -      "dev": true,
        -      "dependencies": {
        -        "end-of-stream": "^1.0.0",
        -        "inherits": "^2.0.1",
        -        "readable-stream": "^2.0.0",
        -        "stream-shift": "^1.0.0"
        -      }
        -    },
        -    "node_modules/eastasianwidth": {
        -      "version": "0.2.0",
        -      "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
        -      "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
        -      "dev": true
        -    },
        -    "node_modules/ee-first": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
        -      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
        -      "dev": true
        -    },
        -    "node_modules/elegant-spinner": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz",
        -      "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/elliptic": {
        -      "version": "6.6.0",
        -      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz",
        -      "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==",
        -      "dev": true,
        -      "dependencies": {
        -        "bn.js": "^4.11.9",
        -        "brorand": "^1.1.0",
        -        "hash.js": "^1.0.0",
        -        "hmac-drbg": "^1.0.1",
        -        "inherits": "^2.0.4",
        -        "minimalistic-assert": "^1.0.1",
        -        "minimalistic-crypto-utils": "^1.0.1"
        -      }
        -    },
        -    "node_modules/elliptic/node_modules/bn.js": {
        -      "version": "4.12.0",
        -      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
        -      "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
        -      "dev": true
        -    },
        -    "node_modules/elliptic/node_modules/inherits": {
        -      "version": "2.0.4",
        -      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
        -      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
        -      "dev": true
        -    },
        -    "node_modules/emoji-regex": {
        -      "version": "7.0.3",
        -      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
        -      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
        -      "dev": true
        -    },
        -    "node_modules/encodeurl": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
        -      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        -    },
        -    "node_modules/end-of-stream": {
        -      "version": "1.4.1",
        -      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
        -      "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
        -      "dev": true,
        -      "dependencies": {
        -        "once": "^1.4.0"
        -      }
        -    },
        -    "node_modules/entities": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
        -      "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
        -      "dev": true
        -    },
        -    "node_modules/error": {
        -      "version": "7.0.2",
        -      "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz",
        -      "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=",
        -      "dev": true,
        -      "dependencies": {
        -        "string-template": "~0.2.1",
        -        "xtend": "~4.0.0"
        -      }
        -    },
        -    "node_modules/error-ex": {
        -      "version": "1.3.1",
        -      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
        -      "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
        -      "dev": true,
        -      "dependencies": {
        -        "is-arrayish": "^0.2.1"
        -      }
        -    },
        -    "node_modules/es-define-property": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
        -      "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "get-intrinsic": "^1.2.4"
        -      },
        -      "engines": {
        -        "node": ">= 0.4"
        -      }
        -    },
        -    "node_modules/es-errors": {
        -      "version": "1.3.0",
        -      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
        -      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.4"
        -      }
        -    },
        -    "node_modules/es5-ext": {
        -      "version": "0.10.37",
        -      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz",
        -      "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=",
        -      "dev": true,
        -      "dependencies": {
        -        "es6-iterator": "~2.0.1",
        -        "es6-symbol": "~3.1.1"
        -      }
        -    },
        -    "node_modules/es6-error": {
        -      "version": "4.1.1",
        -      "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
        -      "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
        -      "dev": true
        -    },
        -    "node_modules/es6-iterator": {
        -      "version": "2.0.3",
        -      "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
        -      "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
        -      "dev": true,
        -      "dependencies": {
        -        "d": "1",
        -        "es5-ext": "^0.10.35",
        -        "es6-symbol": "^3.1.1"
        -      }
        -    },
        -    "node_modules/es6-map": {
        -      "version": "0.1.5",
        -      "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz",
        -      "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
        -      "dev": true,
        -      "dependencies": {
        -        "d": "1",
        -        "es5-ext": "~0.10.14",
        -        "es6-iterator": "~2.0.1",
        -        "es6-set": "~0.1.5",
        -        "es6-symbol": "~3.1.1",
        -        "event-emitter": "~0.3.5"
        -      }
        -    },
        -    "node_modules/es6-promise": {
        -      "version": "4.2.8",
        -      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
        -      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
        -      "dev": true
        -    },
        -    "node_modules/es6-set": {
        -      "version": "0.1.5",
        -      "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
        -      "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=",
        -      "dev": true,
        -      "dependencies": {
        -        "d": "1",
        -        "es5-ext": "~0.10.14",
        -        "es6-iterator": "~2.0.1",
        -        "es6-symbol": "3.1.1",
        -        "event-emitter": "~0.3.5"
        -      }
        -    },
        -    "node_modules/es6-symbol": {
        -      "version": "3.1.1",
        -      "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
        -      "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
        -      "dev": true,
        -      "dependencies": {
        -        "d": "1",
        -        "es5-ext": "~0.10.14"
        -      }
        -    },
        -    "node_modules/es6-weak-map": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
        -      "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
        -      "dev": true,
        -      "dependencies": {
        -        "d": "1",
        -        "es5-ext": "^0.10.14",
        -        "es6-iterator": "^2.0.1",
        -        "es6-symbol": "^3.1.1"
        -      }
        -    },
        -    "node_modules/escalade": {
        -      "version": "3.1.1",
        -      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
        -      "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/escape-goat": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz",
        -      "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/escape-html": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
        -      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
        -      "dev": true
        -    },
        -    "node_modules/escape-string-regexp": {
        -      "version": "1.0.5",
        -      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
        -      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.8.0"
        -      }
        -    },
        -    "node_modules/escope": {
        -      "version": "3.6.0",
        -      "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz",
        -      "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=",
        -      "dev": true,
        -      "dependencies": {
        -        "es6-map": "^0.1.3",
        -        "es6-weak-map": "^2.0.1",
        -        "esrecurse": "^4.1.0",
        -        "estraverse": "^4.1.1"
        -      },
        -      "engines": {
        -        "node": ">=0.4.0"
        -      }
        -    },
        -    "node_modules/eslint": {
        -      "version": "8.23.1",
        -      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.1.tgz",
        -      "integrity": "sha512-w7C1IXCc6fNqjpuYd0yPlcTKKmHlHHktRkzmBPZ+7cvNBQuiNjx0xaMTjAJGCafJhQkrFJooREv0CtrVzmHwqg==",
        -      "dev": true,
        -      "dependencies": {
        -        "@eslint/eslintrc": "^1.3.2",
        -        "@humanwhocodes/config-array": "^0.10.4",
        -        "@humanwhocodes/gitignore-to-minimatch": "^1.0.2",
        -        "@humanwhocodes/module-importer": "^1.0.1",
        -        "ajv": "^6.10.0",
        -        "chalk": "^4.0.0",
        -        "cross-spawn": "^7.0.2",
        -        "debug": "^4.3.2",
        -        "doctrine": "^3.0.0",
        -        "escape-string-regexp": "^4.0.0",
        -        "eslint-scope": "^7.1.1",
        -        "eslint-utils": "^3.0.0",
        -        "eslint-visitor-keys": "^3.3.0",
        -        "espree": "^9.4.0",
        -        "esquery": "^1.4.0",
        -        "esutils": "^2.0.2",
        -        "fast-deep-equal": "^3.1.3",
        -        "file-entry-cache": "^6.0.1",
        -        "find-up": "^5.0.0",
        -        "glob-parent": "^6.0.1",
        -        "globals": "^13.15.0",
        -        "globby": "^11.1.0",
        -        "grapheme-splitter": "^1.0.4",
        -        "ignore": "^5.2.0",
        -        "import-fresh": "^3.0.0",
        -        "imurmurhash": "^0.1.4",
        -        "is-glob": "^4.0.0",
        -        "js-sdsl": "^4.1.4",
        -        "js-yaml": "^4.1.0",
        -        "json-stable-stringify-without-jsonify": "^1.0.1",
        -        "levn": "^0.4.1",
        -        "lodash.merge": "^4.6.2",
        -        "minimatch": "^3.1.2",
        -        "natural-compare": "^1.4.0",
        -        "optionator": "^0.9.1",
        -        "regexpp": "^3.2.0",
        -        "strip-ansi": "^6.0.1",
        -        "strip-json-comments": "^3.1.0",
        -        "text-table": "^0.2.0"
        -      },
        -      "bin": {
        -        "eslint": "bin/eslint.js"
        -      },
        -      "engines": {
        -        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://opencollective.com/eslint"
        -      }
        -    },
        -    "node_modules/eslint-scope": {
        -      "version": "7.1.1",
        -      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
        -      "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
        -      "dev": true,
        -      "dependencies": {
        -        "esrecurse": "^4.3.0",
        -        "estraverse": "^5.2.0"
        -      },
        -      "engines": {
        -        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        -      }
        -    },
        -    "node_modules/eslint-scope/node_modules/esrecurse": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
        -      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
        -      "dev": true,
        -      "dependencies": {
        -        "estraverse": "^5.2.0"
        -      },
        -      "engines": {
        -        "node": ">=4.0"
        -      }
        -    },
        -    "node_modules/eslint-scope/node_modules/estraverse": {
        -      "version": "5.3.0",
        -      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
        -      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4.0"
        -      }
        -    },
        -    "node_modules/eslint-utils": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
        -      "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
        -      "dev": true,
        -      "dependencies": {
        -        "eslint-visitor-keys": "^2.0.0"
        -      },
        -      "engines": {
        -        "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/mysticatea"
        -      },
        -      "peerDependencies": {
        -        "eslint": ">=5"
        -      }
        -    },
        -    "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
        -      "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      }
        -    },
        -    "node_modules/eslint-visitor-keys": {
        -      "version": "3.3.0",
        -      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
        -      "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
        -      "dev": true,
        -      "engines": {
        -        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/ansi-regex": {
        -      "version": "5.0.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        -      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/ansi-styles": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        -      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
        -      "dev": true,
        -      "dependencies": {
        -        "color-convert": "^2.0.1"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/argparse": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
        -      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
        -      "dev": true
        -    },
        -    "node_modules/eslint/node_modules/chalk": {
        -      "version": "4.1.2",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        -      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^4.1.0",
        -        "supports-color": "^7.1.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/chalk?sponsor=1"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/color-convert": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        -      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "color-name": "~1.1.4"
        -      },
        -      "engines": {
        -        "node": ">=7.0.0"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/cross-spawn": {
        -      "version": "7.0.3",
        -      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
        -      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
        -      "dev": true,
        -      "dependencies": {
        -        "path-key": "^3.1.0",
        -        "shebang-command": "^2.0.0",
        -        "which": "^2.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 8"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/debug": {
        -      "version": "4.3.4",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
        -      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "ms": "2.1.2"
        -      },
        -      "engines": {
        -        "node": ">=6.0"
        -      },
        -      "peerDependenciesMeta": {
        -        "supports-color": {
        -          "optional": true
        -        }
        -      }
        -    },
        -    "node_modules/eslint/node_modules/escape-string-regexp": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
        -      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/find-up": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
        -      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
        -      "dev": true,
        -      "dependencies": {
        -        "locate-path": "^6.0.0",
        -        "path-exists": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/globals": {
        -      "version": "13.17.0",
        -      "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
        -      "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==",
        -      "dev": true,
        -      "dependencies": {
        -        "type-fest": "^0.20.2"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/has-flag": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        -      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/js-yaml": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
        -      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
        -      "dev": true,
        -      "dependencies": {
        -        "argparse": "^2.0.1"
        -      },
        -      "bin": {
        -        "js-yaml": "bin/js-yaml.js"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/locate-path": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
        -      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
        -      "dev": true,
        -      "dependencies": {
        -        "p-locate": "^5.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/minimatch": {
        -      "version": "3.1.2",
        -      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
        -      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
        -      "dev": true,
        -      "dependencies": {
        -        "brace-expansion": "^1.1.7"
        -      },
        -      "engines": {
        -        "node": "*"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        -      "dev": true
        -    },
        -    "node_modules/eslint/node_modules/p-limit": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
        -      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "yocto-queue": "^0.1.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/p-locate": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
        -      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
        -      "dev": true,
        -      "dependencies": {
        -        "p-limit": "^3.0.2"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/path-key": {
        -      "version": "3.1.1",
        -      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
        -      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/shebang-command": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
        -      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
        -      "dev": true,
        -      "dependencies": {
        -        "shebang-regex": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/shebang-regex": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
        -      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/strip-ansi": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        -      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-regex": "^5.0.1"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/strip-json-comments": {
        -      "version": "3.1.1",
        -      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
        -      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/supports-color": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        -      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        -      "dev": true,
        -      "dependencies": {
        -        "has-flag": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/type-fest": {
        -      "version": "0.20.2",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
        -      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/eslint/node_modules/which": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
        -      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
        -      "dev": true,
        -      "dependencies": {
        -        "isexe": "^2.0.0"
        -      },
        -      "bin": {
        -        "node-which": "bin/node-which"
        -      },
        -      "engines": {
        -        "node": ">= 8"
        -      }
        -    },
        -    "node_modules/espree": {
        -      "version": "9.4.0",
        -      "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz",
        -      "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==",
        -      "dev": true,
        -      "dependencies": {
        -        "acorn": "^8.8.0",
        -        "acorn-jsx": "^5.3.2",
        -        "eslint-visitor-keys": "^3.3.0"
        -      },
        -      "engines": {
        -        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://opencollective.com/eslint"
        -      }
        -    },
        -    "node_modules/esprima": {
        -      "version": "4.0.1",
        -      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
        -      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
        -      "dev": true,
        -      "bin": {
        -        "esparse": "bin/esparse.js",
        -        "esvalidate": "bin/esvalidate.js"
        -      },
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/esquery": {
        -      "version": "1.4.0",
        -      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
        -      "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
        -      "dev": true,
        -      "dependencies": {
        -        "estraverse": "^5.1.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10"
        -      }
        -    },
        -    "node_modules/esquery/node_modules/estraverse": {
        -      "version": "5.3.0",
        -      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
        -      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4.0"
        -      }
        -    },
        -    "node_modules/esrecurse": {
        -      "version": "4.2.0",
        -      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz",
        -      "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=",
        -      "dev": true,
        -      "dependencies": {
        -        "estraverse": "^4.1.0",
        -        "object-assign": "^4.0.1"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/estraverse": {
        -      "version": "4.2.0",
        -      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
        -      "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/esutils": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
        -      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/etag": {
        -      "version": "1.8.1",
        -      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
        -      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.6"
        -      }
        -    },
        -    "node_modules/event-emitter": {
        -      "version": "0.3.5",
        -      "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
        -      "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
        -      "dev": true,
        -      "dependencies": {
        -        "d": "1",
        -        "es5-ext": "~0.10.14"
        -      }
        -    },
        -    "node_modules/eventemitter2": {
        -      "version": "0.4.14",
        -      "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz",
        -      "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=",
        -      "dev": true
        -    },
        -    "node_modules/events": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
        -      "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.4.x"
        -      }
        -    },
        -    "node_modules/evp_bytestokey": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
        -      "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
        -      "dev": true,
        -      "dependencies": {
        -        "md5.js": "^1.3.4",
        -        "safe-buffer": "^5.1.1"
        -      }
        -    },
        -    "node_modules/execa": {
        -      "version": "7.1.1",
        -      "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz",
        -      "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==",
        -      "dev": true,
        -      "dependencies": {
        -        "cross-spawn": "^7.0.3",
        -        "get-stream": "^6.0.1",
        -        "human-signals": "^4.3.0",
        -        "is-stream": "^3.0.0",
        -        "merge-stream": "^2.0.0",
        -        "npm-run-path": "^5.1.0",
        -        "onetime": "^6.0.0",
        -        "signal-exit": "^3.0.7",
        -        "strip-final-newline": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": "^14.18.0 || ^16.14.0 || >=18.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sindresorhus/execa?sponsor=1"
        -      }
        -    },
        -    "node_modules/execa/node_modules/cross-spawn": {
        -      "version": "7.0.3",
        -      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
        -      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
        -      "dev": true,
        -      "dependencies": {
        -        "path-key": "^3.1.0",
        -        "shebang-command": "^2.0.0",
        -        "which": "^2.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 8"
        -      }
        -    },
        -    "node_modules/execa/node_modules/get-stream": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
        -      "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/execa/node_modules/is-stream": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
        -      "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
        -      "dev": true,
        -      "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/execa/node_modules/mimic-fn": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
        -      "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/execa/node_modules/npm-run-path": {
        -      "version": "5.1.0",
        -      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
        -      "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
        -      "dev": true,
        -      "dependencies": {
        -        "path-key": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/execa/node_modules/npm-run-path/node_modules/path-key": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
        -      "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/execa/node_modules/onetime": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
        -      "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "mimic-fn": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/execa/node_modules/path-key": {
        -      "version": "3.1.1",
        -      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
        -      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/execa/node_modules/shebang-command": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
        -      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
        -      "dev": true,
        -      "dependencies": {
        -        "shebang-regex": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/execa/node_modules/shebang-regex": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
        -      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/execa/node_modules/which": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
        -      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
        -      "dev": true,
        -      "dependencies": {
        -        "isexe": "^2.0.0"
        -      },
        -      "bin": {
        -        "node-which": "bin/node-which"
        -      },
        -      "engines": {
        -        "node": ">= 8"
        -      }
        -    },
        -    "node_modules/exit": {
        -      "version": "0.1.2",
        -      "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
        -      "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.8.0"
        -      }
        -    },
        -    "node_modules/exit-hook": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
        -      "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/expand-tilde": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
        -      "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
        -      "dev": true,
        -      "dependencies": {
        -        "homedir-polyfill": "^1.0.1"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/express": {
        -      "version": "4.21.1",
        -      "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
        -      "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "accepts": "~1.3.8",
        -        "array-flatten": "1.1.1",
        -        "body-parser": "1.20.3",
        -        "content-disposition": "0.5.4",
        -        "content-type": "~1.0.4",
        -        "cookie": "0.7.1",
        -        "cookie-signature": "1.0.6",
        -        "debug": "2.6.9",
        -        "depd": "2.0.0",
        -        "encodeurl": "~2.0.0",
        -        "escape-html": "~1.0.3",
        -        "etag": "~1.8.1",
        -        "finalhandler": "1.3.1",
        -        "fresh": "0.5.2",
        -        "http-errors": "2.0.0",
        -        "merge-descriptors": "1.0.3",
        -        "methods": "~1.1.2",
        -        "on-finished": "2.4.1",
        -        "parseurl": "~1.3.3",
        -        "path-to-regexp": "0.1.10",
        -        "proxy-addr": "~2.0.7",
        -        "qs": "6.13.0",
        -        "range-parser": "~1.2.1",
        -        "safe-buffer": "5.2.1",
        -        "send": "0.19.0",
        -        "serve-static": "1.16.2",
        -        "setprototypeof": "1.2.0",
        -        "statuses": "2.0.1",
        -        "type-is": "~1.6.18",
        -        "utils-merge": "1.0.1",
        -        "vary": "~1.1.2"
        -      },
        -      "engines": {
        -        "node": ">= 0.10.0"
        -      }
        -    },
        -    "node_modules/express/node_modules/depd": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
        -      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        -    },
        -    "node_modules/express/node_modules/encodeurl": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
        -      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        -    },
        -    "node_modules/express/node_modules/finalhandler": {
        -      "version": "1.3.1",
        -      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
        -      "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "debug": "2.6.9",
        -        "encodeurl": "~2.0.0",
        -        "escape-html": "~1.0.3",
        -        "on-finished": "2.4.1",
        -        "parseurl": "~1.3.3",
        -        "statuses": "2.0.1",
        -        "unpipe": "~1.0.0"
        -      },
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        -    },
        -    "node_modules/express/node_modules/http-errors": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
        -      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "depd": "2.0.0",
        -        "inherits": "2.0.4",
        -        "setprototypeof": "1.2.0",
        -        "statuses": "2.0.1",
        -        "toidentifier": "1.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        -    },
        -    "node_modules/express/node_modules/inherits": {
        -      "version": "2.0.4",
        -      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
        -      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
        -      "dev": true
        -    },
        -    "node_modules/express/node_modules/on-finished": {
        -      "version": "2.4.1",
        -      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
        -      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
        -      "dev": true,
        -      "dependencies": {
        -        "ee-first": "1.1.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        -    },
        -    "node_modules/express/node_modules/safe-buffer": {
        -      "version": "5.2.1",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
        -      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
        -      "dev": true,
        -      "funding": [
        -        {
        -          "type": "github",
        -          "url": "https://github.com/sponsors/feross"
        -        },
        -        {
        -          "type": "patreon",
        -          "url": "https://www.patreon.com/feross"
        -        },
        -        {
        -          "type": "consulting",
        -          "url": "https://feross.org/support"
        -        }
        -      ]
        -    },
        -    "node_modules/express/node_modules/setprototypeof": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
        -      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
        -      "dev": true
        -    },
        -    "node_modules/express/node_modules/statuses": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
        -      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        -    },
        -    "node_modules/extend": {
        -      "version": "3.0.2",
        -      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
        -      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
        -      "dev": true
        -    },
        -    "node_modules/external-editor": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
        -      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
        -      "dev": true,
        -      "dependencies": {
        -        "chardet": "^0.7.0",
        -        "iconv-lite": "^0.4.24",
        -        "tmp": "^0.0.33"
        -      },
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/extract-zip": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
        -      "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
        -      "dev": true,
        -      "dependencies": {
        -        "debug": "^4.1.1",
        -        "get-stream": "^5.1.0",
        -        "yauzl": "^2.10.0"
        -      },
        -      "bin": {
        -        "extract-zip": "cli.js"
        -      },
        -      "engines": {
        -        "node": ">= 10.17.0"
        -      },
        -      "optionalDependencies": {
        -        "@types/yauzl": "^2.9.1"
        -      }
        -    },
        -    "node_modules/extract-zip/node_modules/debug": {
        -      "version": "4.3.4",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
        -      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "ms": "2.1.2"
        -      },
        -      "engines": {
        -        "node": ">=6.0"
        -      },
        -      "peerDependenciesMeta": {
        -        "supports-color": {
        -          "optional": true
        -        }
        -      }
        -    },
        -    "node_modules/extract-zip/node_modules/get-stream": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
        -      "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
        -      "dev": true,
        -      "dependencies": {
        -        "pump": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/extract-zip/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        -      "dev": true
        -    },
        -    "node_modules/fast-deep-equal": {
        -      "version": "3.1.3",
        -      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
        -      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
        -      "dev": true
        -    },
        -    "node_modules/fast-glob": {
        -      "version": "3.3.0",
        -      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
        -      "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
        -      "dev": true,
        -      "dependencies": {
        -        "@nodelib/fs.stat": "^2.0.2",
        -        "@nodelib/fs.walk": "^1.2.3",
        -        "glob-parent": "^5.1.2",
        -        "merge2": "^1.3.0",
        -        "micromatch": "^4.0.4"
        -      },
        -      "engines": {
        -        "node": ">=8.6.0"
        -      }
        -    },
        -    "node_modules/fast-glob/node_modules/glob-parent": {
        -      "version": "5.1.2",
        -      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
        -      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
        -      "dev": true,
        -      "dependencies": {
        -        "is-glob": "^4.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 6"
        -      }
        -    },
        -    "node_modules/fast-json-stable-stringify": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
        -      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
        -      "dev": true
        -    },
        -    "node_modules/fast-levenshtein": {
        -      "version": "2.0.6",
        -      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
        -      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
        -      "dev": true
        -    },
        -    "node_modules/fast-safe-stringify": {
        -      "version": "2.0.7",
        -      "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
        -      "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==",
        -      "dev": true
        -    },
        -    "node_modules/fastq": {
        -      "version": "1.13.0",
        -      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
        -      "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
        -      "dev": true,
        -      "dependencies": {
        -        "reusify": "^1.0.4"
        -      }
        -    },
        -    "node_modules/faye-websocket": {
        -      "version": "0.10.0",
        -      "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
        -      "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
        -      "dev": true,
        -      "dependencies": {
        -        "websocket-driver": ">=0.5.1"
        -      },
        -      "engines": {
        -        "node": ">=0.4.0"
        -      }
        -    },
        -    "node_modules/fd-slicer": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
        -      "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
        -      "dev": true,
        -      "dependencies": {
        -        "pend": "~1.2.0"
        -      }
        -    },
        -    "node_modules/fetch-jsonp": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/fetch-jsonp/-/fetch-jsonp-1.1.3.tgz",
        -      "integrity": "sha1-nrnlhboIqvcAVjU40Xu+u81aPbI=",
        -      "dev": true
        -    },
        -    "node_modules/figures": {
        -      "version": "1.7.0",
        -      "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
        -      "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
        -      "dev": true,
        -      "dependencies": {
        -        "escape-string-regexp": "^1.0.5",
        -        "object-assign": "^4.1.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/file-entry-cache": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
        -      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
        -      "dev": true,
        -      "dependencies": {
        -        "flat-cache": "^3.0.4"
        -      },
        -      "engines": {
        -        "node": "^10.12.0 || >=12.0.0"
        -      }
        -    },
        -    "node_modules/file-saver": {
        -      "version": "1.3.8",
        -      "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz",
        -      "integrity": "sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg==",
        -      "dev": true
        -    },
        -    "node_modules/fill-range": {
        -      "version": "7.1.1",
        -      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
        -      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
        -      "dev": true,
        -      "dependencies": {
        -        "to-regex-range": "^5.0.1"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/finalhandler": {
        -      "version": "1.1.2",
        -      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
        -      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
        -      "dev": true,
        -      "dependencies": {
        -        "debug": "2.6.9",
        -        "encodeurl": "~1.0.2",
        -        "escape-html": "~1.0.3",
        -        "on-finished": "~2.3.0",
        -        "parseurl": "~1.3.3",
        -        "statuses": "~1.5.0",
        -        "unpipe": "~1.0.0"
        -      },
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        -    },
        -    "node_modules/find-cache-dir": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
        -      "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "commondir": "^1.0.1",
        -        "make-dir": "^2.0.0",
        -        "pkg-dir": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/find-cache-dir/node_modules/make-dir": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
        -      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
        -      "dev": true,
        -      "dependencies": {
        -        "pify": "^4.0.1",
        -        "semver": "^5.6.0"
        -      },
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/find-cache-dir/node_modules/semver": {
        -      "version": "5.7.0",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
        -      "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
        -      "dev": true,
        -      "bin": {
        -        "semver": "bin/semver"
        -      }
        -    },
        -    "node_modules/find-up": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
        -      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
        -      "dev": true,
        -      "dependencies": {
        -        "locate-path": "^5.0.0",
        -        "path-exists": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/find-up/node_modules/locate-path": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
        -      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
        -      "dev": true,
        -      "dependencies": {
        -        "p-locate": "^4.1.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/find-up/node_modules/p-locate": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
        -      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
        -      "dev": true,
        -      "dependencies": {
        -        "p-limit": "^2.2.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/find-versions": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz",
        -      "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "semver-regex": "^3.1.2"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/findup-sync": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz",
        -      "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "detect-file": "^1.0.0",
        -        "is-glob": "^4.0.3",
        -        "micromatch": "^4.0.4",
        -        "resolve-dir": "^1.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 10.13.0"
        -      }
        -    },
        -    "node_modules/fined": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz",
        -      "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==",
        -      "dev": true,
        -      "dependencies": {
        -        "expand-tilde": "^2.0.2",
        -        "is-plain-object": "^2.0.3",
        -        "object.defaults": "^1.1.0",
        -        "object.pick": "^1.2.0",
        -        "parse-filepath": "^1.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.10"
        -      }
        -    },
        -    "node_modules/flagged-respawn": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz",
        -      "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.10"
        -      }
        -    },
        -    "node_modules/flat": {
        -      "version": "5.0.2",
        -      "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
        -      "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
        -      "dev": true,
        -      "bin": {
        -        "flat": "cli.js"
        -      }
        -    },
        -    "node_modules/flat-cache": {
        -      "version": "3.0.4",
        -      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
        -      "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
        -      "dev": true,
        -      "dependencies": {
        -        "flatted": "^3.1.0",
        -        "rimraf": "^3.0.2"
        -      },
        -      "engines": {
        -        "node": "^10.12.0 || >=12.0.0"
        -      }
        -    },
        -    "node_modules/flat-cache/node_modules/rimraf": {
        -      "version": "3.0.2",
        -      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
        -      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
        -      "dev": true,
        -      "dependencies": {
        -        "glob": "^7.1.3"
        -      },
        -      "bin": {
        -        "rimraf": "bin.js"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/isaacs"
        -      }
        -    },
        -    "node_modules/flatted": {
        -      "version": "3.2.7",
        -      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
        -      "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
        -      "dev": true
        -    },
        -    "node_modules/for-in": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
        -      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/for-own": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz",
        -      "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=",
        -      "dev": true,
        -      "dependencies": {
        -        "for-in": "^1.0.1"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/foreground-child": {
        -      "version": "1.5.6",
        -      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz",
        -      "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=",
        -      "dev": true,
        -      "dependencies": {
        -        "cross-spawn": "^4",
        -        "signal-exit": "^3.0.0"
        -      }
        -    },
        -    "node_modules/foreground-child/node_modules/cross-spawn": {
        -      "version": "4.0.2",
        -      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
        -      "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
        -      "dev": true,
        -      "dependencies": {
        -        "lru-cache": "^4.0.1",
        -        "which": "^1.2.9"
        -      }
        -    },
        -    "node_modules/forever-agent": {
        -      "version": "0.5.2",
        -      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz",
        -      "integrity": "sha1-bQ4JxJIflKJ/Y9O0nF/v8epMUTA=",
        -      "dev": true,
        -      "engines": {
        -        "node": "*"
        -      }
        -    },
        -    "node_modules/form-data": {
        -      "version": "0.1.4",
        -      "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz",
        -      "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=",
        -      "dev": true,
        -      "optional": true,
        -      "dependencies": {
        -        "async": "~0.9.0",
        -        "combined-stream": "~0.0.4",
        -        "mime": "~1.2.11"
        -      },
        -      "engines": {
        -        "node": ">= 0.8"
        -      }
        -    },
        -    "node_modules/form-data-encoder": {
        -      "version": "2.1.4",
        -      "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
        -      "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 14.17"
        -      }
        -    },
        -    "node_modules/form-data/node_modules/async": {
        -      "version": "0.9.2",
        -      "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
        -      "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=",
        -      "dev": true,
        -      "optional": true
        -    },
        -    "node_modules/form-data/node_modules/mime": {
        -      "version": "1.2.11",
        -      "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz",
        -      "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=",
        -      "dev": true,
        -      "optional": true
        -    },
        -    "node_modules/forwarded": {
        -      "version": "0.2.0",
        -      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
        -      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.6"
        -      }
        -    },
        -    "node_modules/fresh": {
        -      "version": "0.5.2",
        -      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
        -      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.6"
        -      }
        -    },
        -    "node_modules/fs-constants": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
        -      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
        -      "dev": true
        -    },
        -    "node_modules/fs.realpath": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
        -      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
        -      "dev": true
        -    },
        -    "node_modules/fsevents": {
        -      "version": "2.3.2",
        -      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
        -      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
        -      "dev": true,
        -      "hasInstallScript": true,
        -      "optional": true,
        -      "os": [
        -        "darwin"
        -      ],
        -      "engines": {
        -        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
        -      }
        -    },
        -    "node_modules/function-bind": {
        -      "version": "1.1.2",
        -      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
        -      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
        -      "dev": true,
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        -      }
        -    },
        -    "node_modules/gaze": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
        -      "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
        -      "dev": true,
        -      "dependencies": {
        -        "globule": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": ">= 4.0.0"
        -      }
        -    },
        -    "node_modules/get-assigned-identifiers": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz",
        -      "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==",
        -      "dev": true
        -    },
        -    "node_modules/get-caller-file": {
        -      "version": "2.0.5",
        -      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
        -      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
        -      "dev": true,
        -      "engines": {
        -        "node": "6.* || 8.* || >= 10.*"
        -      }
        -    },
        -    "node_modules/get-intrinsic": {
        -      "version": "1.2.4",
        -      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
        -      "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "es-errors": "^1.3.0",
        -        "function-bind": "^1.1.2",
        -        "has-proto": "^1.0.1",
        -        "has-symbols": "^1.0.3",
        -        "hasown": "^2.0.0"
        -      },
        -      "engines": {
        -        "node": ">= 0.4"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        -      }
        -    },
        -    "node_modules/get-own-enumerable-property-symbols": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz",
        -      "integrity": "sha512-TtY/sbOemiMKPRUDDanGCSgBYe7Mf0vbRsWnBZ+9yghpZ1MvcpSpuZFjHdEeY/LZjZy0vdLjS77L6HosisFiug==",
        -      "dev": true
        -    },
        -    "node_modules/get-stream": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
        -      "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/getobject": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz",
        -      "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      }
        -    },
        -    "node_modules/gifenc": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/gifenc/-/gifenc-1.0.3.tgz",
        -      "integrity": "sha512-xdr6AdrfGBcfzncONUOlXMBuc5wJDtOueE3c5rdG0oNgtINLD+f2iFZltrBRZYzACRbKr+mSVU/x98zv2u3jmw==",
        -      "dev": true
        -    },
        -    "node_modules/github-url-from-git": {
        -      "version": "1.5.0",
        -      "resolved": "https://registry.npmjs.org/github-url-from-git/-/github-url-from-git-1.5.0.tgz",
        -      "integrity": "sha512-WWOec4aRI7YAykQ9+BHmzjyNlkfJFG8QLXnDTsLz/kZefq7qkzdfo4p6fkYYMIq1aj+gZcQs/1HQhQh3DPPxlQ==",
        -      "dev": true
        -    },
        -    "node_modules/glob": {
        -      "version": "7.1.6",
        -      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
        -      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
        -      "dev": true,
        -      "dependencies": {
        -        "fs.realpath": "^1.0.0",
        -        "inflight": "^1.0.4",
        -        "inherits": "2",
        -        "minimatch": "^3.0.4",
        -        "once": "^1.3.0",
        -        "path-is-absolute": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": "*"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/isaacs"
        -      }
        -    },
        -    "node_modules/glob-parent": {
        -      "version": "6.0.2",
        -      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
        -      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
        -      "dev": true,
        -      "dependencies": {
        -        "is-glob": "^4.0.3"
        -      },
        -      "engines": {
        -        "node": ">=10.13.0"
        -      }
        -    },
        -    "node_modules/global-dirs": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz",
        -      "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==",
        -      "dev": true,
        -      "dependencies": {
        -        "ini": "2.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/global-dirs/node_modules/ini": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz",
        -      "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      }
        -    },
        -    "node_modules/global-modules": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
        -      "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
        -      "dev": true,
        -      "dependencies": {
        -        "global-prefix": "^1.0.1",
        -        "is-windows": "^1.0.1",
        -        "resolve-dir": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/global-prefix": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
        -      "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
        -      "dev": true,
        -      "dependencies": {
        -        "expand-tilde": "^2.0.2",
        -        "homedir-polyfill": "^1.0.1",
        -        "ini": "^1.3.4",
        -        "is-windows": "^1.0.1",
        -        "which": "^1.2.14"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/globals": {
        -      "version": "11.7.0",
        -      "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz",
        -      "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/globby": {
        -      "version": "11.1.0",
        -      "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
        -      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
        -      "dev": true,
        -      "dependencies": {
        -        "array-union": "^2.1.0",
        -        "dir-glob": "^3.0.1",
        -        "fast-glob": "^3.2.9",
        -        "ignore": "^5.2.0",
        -        "merge2": "^1.4.1",
        -        "slash": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/globule": {
        -      "version": "1.2.1",
        -      "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
        -      "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "glob": "~7.1.1",
        -        "lodash": "~4.17.10",
        -        "minimatch": "~3.0.2"
        -      },
        -      "engines": {
        -        "node": ">= 0.10"
        -      }
        -    },
        -    "node_modules/gopd": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
        -      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
        -      "dev": true,
        -      "dependencies": {
        -        "get-intrinsic": "^1.1.3"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        -      }
        -    },
        -    "node_modules/got": {
        -      "version": "11.8.6",
        -      "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
        -      "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
        -      "dev": true,
        -      "dependencies": {
        -        "@sindresorhus/is": "^4.0.0",
        -        "@szmarczak/http-timer": "^4.0.5",
        -        "@types/cacheable-request": "^6.0.1",
        -        "@types/responselike": "^1.0.0",
        -        "cacheable-lookup": "^5.0.3",
        -        "cacheable-request": "^7.0.2",
        -        "decompress-response": "^6.0.0",
        -        "http2-wrapper": "^1.0.0-beta.5.2",
        -        "lowercase-keys": "^2.0.0",
        -        "p-cancelable": "^2.0.0",
        -        "responselike": "^2.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10.19.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sindresorhus/got?sponsor=1"
        -      }
        -    },
        -    "node_modules/graceful-fs": {
        -      "version": "4.2.11",
        -      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
        -      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
        -      "dev": true
        -    },
        -    "node_modules/graceful-readlink": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
        -      "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
        -      "dev": true
        -    },
        -    "node_modules/grapheme-splitter": {
        -      "version": "1.0.4",
        -      "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
        -      "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
        -      "dev": true
        -    },
        -    "node_modules/grunt": {
        -      "version": "1.6.1",
        -      "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz",
        -      "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==",
        -      "dev": true,
        -      "dependencies": {
        -        "dateformat": "~4.6.2",
        -        "eventemitter2": "~0.4.13",
        -        "exit": "~0.1.2",
        -        "findup-sync": "~5.0.0",
        -        "glob": "~7.1.6",
        -        "grunt-cli": "~1.4.3",
        -        "grunt-known-options": "~2.0.0",
        -        "grunt-legacy-log": "~3.0.0",
        -        "grunt-legacy-util": "~2.0.1",
        -        "iconv-lite": "~0.6.3",
        -        "js-yaml": "~3.14.0",
        -        "minimatch": "~3.0.4",
        -        "nopt": "~3.0.6"
        -      },
        -      "bin": {
        -        "grunt": "bin/grunt"
        -      },
        -      "engines": {
        -        "node": ">=16"
        -      }
        -    },
        -    "node_modules/grunt-cli": {
        -      "version": "1.4.3",
        -      "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz",
        -      "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "grunt-known-options": "~2.0.0",
        -        "interpret": "~1.1.0",
        -        "liftup": "~3.0.1",
        -        "nopt": "~4.0.1",
        -        "v8flags": "~3.2.0"
        -      },
        -      "bin": {
        -        "grunt": "bin/grunt"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      }
        -    },
        -    "node_modules/grunt-cli/node_modules/nopt": {
        -      "version": "4.0.3",
        -      "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
        -      "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
        -      "dev": true,
        -      "dependencies": {
        -        "abbrev": "1",
        -        "osenv": "^0.1.4"
        -      },
        -      "bin": {
        -        "nopt": "bin/nopt.js"
        -      }
        -    },
        -    "node_modules/grunt-contrib-clean": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz",
        -      "integrity": "sha512-uRvnXfhiZt8akb/ZRDHJpQQtkkVkqc/opWO4Po/9ehC2hPxgptB9S6JHDC/Nxswo4CJSM0iFPT/Iym3cEMWzKA==",
        -      "dev": true,
        -      "dependencies": {
        -        "async": "^3.2.3",
        -        "rimraf": "^2.6.2"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "peerDependencies": {
        -        "grunt": ">=0.4.5"
        -      }
        -    },
        -    "node_modules/grunt-contrib-clean/node_modules/async": {
        -      "version": "3.2.4",
        -      "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
        -      "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
        -      "dev": true
        -    },
        -    "node_modules/grunt-contrib-connect": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/grunt-contrib-connect/-/grunt-contrib-connect-3.0.0.tgz",
        -      "integrity": "sha512-L1GXk6PqDP/meX0IOX1MByBvOph6h8Pvx4/iBIYD7dpokVCAAQPR/IIV1jkTONEM09xig/Y8/y3R9Fqc8U3HSA==",
        -      "dev": true,
        -      "dependencies": {
        -        "async": "^3.2.0",
        -        "connect": "^3.7.0",
        -        "connect-livereload": "^0.6.1",
        -        "morgan": "^1.10.0",
        -        "node-http2": "^4.0.1",
        -        "opn": "^6.0.0",
        -        "portscanner": "^2.2.0",
        -        "serve-index": "^1.9.1",
        -        "serve-static": "^1.14.1"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      }
        -    },
        -    "node_modules/grunt-contrib-connect/node_modules/async": {
        -      "version": "3.2.4",
        -      "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
        -      "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
        -      "dev": true
        -    },
        -    "node_modules/grunt-contrib-uglify": {
        -      "version": "5.2.2",
        -      "resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-5.2.2.tgz",
        -      "integrity": "sha512-ITxiWxrjjP+RZu/aJ5GLvdele+sxlznh+6fK9Qckio5ma8f7Iv8woZjRkGfafvpuygxNefOJNc+hfjjBayRn2Q==",
        -      "dev": true,
        -      "dependencies": {
        -        "chalk": "^4.1.2",
        -        "maxmin": "^3.0.0",
        -        "uglify-js": "^3.16.1",
        -        "uri-path": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      }
        -    },
        -    "node_modules/grunt-contrib-uglify/node_modules/ansi-styles": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        -      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
        -      "dev": true,
        -      "dependencies": {
        -        "color-convert": "^2.0.1"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
        -      }
        -    },
        -    "node_modules/grunt-contrib-uglify/node_modules/chalk": {
        -      "version": "4.1.2",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        -      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^4.1.0",
        -        "supports-color": "^7.1.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/chalk?sponsor=1"
        -      }
        -    },
        -    "node_modules/grunt-contrib-uglify/node_modules/color-convert": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        -      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "color-name": "~1.1.4"
        -      },
        -      "engines": {
        -        "node": ">=7.0.0"
        -      }
        -    },
        -    "node_modules/grunt-contrib-uglify/node_modules/figures": {
        -      "version": "3.2.0",
        -      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
        -      "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
        -      "dev": true,
        -      "dependencies": {
        -        "escape-string-regexp": "^1.0.5"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/grunt-contrib-uglify/node_modules/gzip-size": {
        -      "version": "5.1.1",
        -      "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
        -      "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==",
        -      "dev": true,
        -      "dependencies": {
        -        "duplexer": "^0.1.1",
        -        "pify": "^4.0.1"
        -      },
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/grunt-contrib-uglify/node_modules/has-flag": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        -      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/grunt-contrib-uglify/node_modules/maxmin": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-3.0.0.tgz",
        -      "integrity": "sha512-wcahMInmGtg/7c6a75fr21Ch/Ks1Tb+Jtoan5Ft4bAI0ZvJqyOw8kkM7e7p8hDSzY805vmxwHT50KcjGwKyJ0g==",
        -      "dev": true,
        -      "dependencies": {
        -        "chalk": "^4.1.0",
        -        "figures": "^3.2.0",
        -        "gzip-size": "^5.1.1",
        -        "pretty-bytes": "^5.3.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/grunt-contrib-uglify/node_modules/supports-color": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        -      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        -      "dev": true,
        -      "dependencies": {
        -        "has-flag": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/grunt-contrib-uglify/node_modules/uglify-js": {
        -      "version": "3.17.0",
        -      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.0.tgz",
        -      "integrity": "sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg==",
        -      "dev": true,
        -      "bin": {
        -        "uglifyjs": "bin/uglifyjs"
        -      },
        -      "engines": {
        -        "node": ">=0.8.0"
        -      }
        -    },
        -    "node_modules/grunt-contrib-watch": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz",
        -      "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==",
        -      "dev": true,
        -      "dependencies": {
        -        "async": "^2.6.0",
        -        "gaze": "^1.1.0",
        -        "lodash": "^4.17.10",
        -        "tiny-lr": "^1.1.1"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/grunt-contrib-yuidoc": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/grunt-contrib-yuidoc/-/grunt-contrib-yuidoc-1.0.0.tgz",
        -      "integrity": "sha1-IqLEphsgGKLRtZVGLYhWJXGH0/I=",
        -      "dev": true,
        -      "dependencies": {
        -        "yuidocjs": "^0.10.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/grunt-eslint": {
        -      "version": "24.0.0",
        -      "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-24.0.0.tgz",
        -      "integrity": "sha512-WpTeBBFweyhMuPjGwRSQV9JFJ+EczIdlsc7Dd/1g78QVI1aZsk4g/H3e+3S5HEwsS1RKL2YZIrGj8hMLlBfN8w==",
        -      "dev": true,
        -      "dependencies": {
        -        "chalk": "^4.1.2",
        -        "eslint": "^8.0.1"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      },
        -      "peerDependencies": {
        -        "grunt": ">=1"
        -      }
        -    },
        -    "node_modules/grunt-eslint/node_modules/ansi-styles": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        -      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
        -      "dev": true,
        -      "dependencies": {
        -        "color-convert": "^2.0.1"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
        -      }
        -    },
        -    "node_modules/grunt-eslint/node_modules/chalk": {
        -      "version": "4.1.2",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        -      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^4.1.0",
        -        "supports-color": "^7.1.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/chalk?sponsor=1"
        -      }
        -    },
        -    "node_modules/grunt-eslint/node_modules/color-convert": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        -      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "color-name": "~1.1.4"
        -      },
        -      "engines": {
        -        "node": ">=7.0.0"
        -      }
        -    },
        -    "node_modules/grunt-eslint/node_modules/has-flag": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        -      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/grunt-eslint/node_modules/supports-color": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        -      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        -      "dev": true,
        -      "dependencies": {
        -        "has-flag": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/grunt-known-options": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz",
        -      "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/grunt-legacy-log": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz",
        -      "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==",
        -      "dev": true,
        -      "dependencies": {
        -        "colors": "~1.1.2",
        -        "grunt-legacy-log-utils": "~2.1.0",
        -        "hooker": "~0.2.3",
        -        "lodash": "~4.17.19"
        -      },
        -      "engines": {
        -        "node": ">= 0.10.0"
        -      }
        -    },
        -    "node_modules/grunt-legacy-log-utils": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz",
        -      "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==",
        -      "dev": true,
        -      "dependencies": {
        -        "chalk": "~4.1.0",
        -        "lodash": "~4.17.19"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      }
        -    },
        -    "node_modules/grunt-legacy-log-utils/node_modules/ansi-styles": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        -      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
        -      "dev": true,
        -      "dependencies": {
        -        "color-convert": "^2.0.1"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
        -      }
        -    },
        -    "node_modules/grunt-legacy-log-utils/node_modules/chalk": {
        -      "version": "4.1.2",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        -      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^4.1.0",
        -        "supports-color": "^7.1.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/chalk?sponsor=1"
        -      }
        -    },
        -    "node_modules/grunt-legacy-log-utils/node_modules/color-convert": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        -      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "color-name": "~1.1.4"
        -      },
        -      "engines": {
        -        "node": ">=7.0.0"
        -      }
        -    },
        -    "node_modules/grunt-legacy-log-utils/node_modules/has-flag": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        -      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/grunt-legacy-log-utils/node_modules/supports-color": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        -      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        -      "dev": true,
        -      "dependencies": {
        -        "has-flag": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/grunt-legacy-util": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz",
        -      "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==",
        -      "dev": true,
        -      "dependencies": {
        -        "async": "~3.2.0",
        -        "exit": "~0.1.2",
        -        "getobject": "~1.0.0",
        -        "hooker": "~0.2.3",
        -        "lodash": "~4.17.21",
        -        "underscore.string": "~3.3.5",
        -        "which": "~2.0.2"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      }
        -    },
        -    "node_modules/grunt-legacy-util/node_modules/async": {
        -      "version": "3.2.3",
        -      "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
        -      "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
        -      "dev": true
        -    },
        -    "node_modules/grunt-legacy-util/node_modules/which": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
        -      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
        -      "dev": true,
        -      "dependencies": {
        -        "isexe": "^2.0.0"
        -      },
        -      "bin": {
        -        "node-which": "bin/node-which"
        -      },
        -      "engines": {
        -        "node": ">= 8"
        -      }
        -    },
        -    "node_modules/grunt-minjson": {
        -      "version": "0.4.0",
        -      "resolved": "https://registry.npmjs.org/grunt-minjson/-/grunt-minjson-0.4.0.tgz",
        -      "integrity": "sha1-I5PO+bADVrur6pB9Zc33fHYoCkQ=",
        -      "dev": true,
        -      "dependencies": {
        -        "maxmin": "^2.1.0"
        -      },
        -      "engines": {
        -        "node": ">= 0.8.0"
        -      }
        -    },
        -    "node_modules/grunt-mocha-test": {
        -      "version": "0.13.3",
        -      "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.13.3.tgz",
        -      "integrity": "sha512-zQGEsi3d+ViPPi7/4jcj78afKKAKiAA5n61pknQYi25Ugik+aNOuRmiOkmb8mN2CeG8YxT+YdT1H1Q7B/eNkoQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "hooker": "^0.2.3",
        -        "mkdirp": "^0.5.0"
        -      },
        -      "engines": {
        -        "node": ">= 0.10.4"
        -      },
        -      "peerDependencies": {
        -        "mocha": ">=1.20.0"
        -      }
        -    },
        -    "node_modules/grunt-newer": {
        -      "version": "1.3.0",
        -      "resolved": "https://registry.npmjs.org/grunt-newer/-/grunt-newer-1.3.0.tgz",
        -      "integrity": "sha1-g8y3od2ny9irI7BZAk6+YUrS80I=",
        -      "dev": true,
        -      "dependencies": {
        -        "async": "^1.5.2",
        -        "rimraf": "^2.5.2"
        -      },
        -      "engines": {
        -        "node": ">= 0.8.0"
        -      },
        -      "peerDependencies": {
        -        "grunt": ">=0.4.1"
        -      }
        -    },
        -    "node_modules/grunt-newer/node_modules/async": {
        -      "version": "1.5.2",
        -      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
        -      "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
        -      "dev": true
        -    },
        -    "node_modules/grunt-simple-nyc": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/grunt-simple-nyc/-/grunt-simple-nyc-3.0.1.tgz",
        -      "integrity": "sha512-/YLY+jNI6gBuVO3xu07zwvDN+orTAFS50W00yb/2ncvc2PFO4pR+oU7TyiHhe8a6O3KuQDHsyCE0iE+rqJagQg==",
        -      "dev": true,
        -      "dependencies": {
        -        "lodash": "^4.17.15",
        -        "nyc": "^14.1.0",
        -        "simple-cli": "^5.0.3"
        -      }
        -    },
        -    "node_modules/grunt/node_modules/iconv-lite": {
        -      "version": "0.6.3",
        -      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
        -      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
        -      "dev": true,
        -      "dependencies": {
        -        "safer-buffer": ">= 2.1.2 < 3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/grunt/node_modules/js-yaml": {
        -      "version": "3.14.1",
        -      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
        -      "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
        -      "dev": true,
        -      "dependencies": {
        -        "argparse": "^1.0.7",
        -        "esprima": "^4.0.0"
        -      },
        -      "bin": {
        -        "js-yaml": "bin/js-yaml.js"
        -      }
        -    },
        -    "node_modules/gzip-size": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz",
        -      "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=",
        -      "dev": true,
        -      "dependencies": {
        -        "duplexer": "^0.1.1"
        -      },
        -      "engines": {
        -        "node": ">=0.12.0"
        -      }
        -    },
        -    "node_modules/hard-rejection": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
        -      "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/has": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
        -      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
        -      "dev": true,
        -      "dependencies": {
        -        "function-bind": "^1.1.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.4.0"
        -      }
        -    },
        -    "node_modules/has-ansi": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
        -      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-regex": "^2.0.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/has-flag": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
        -      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/has-property-descriptors": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
        -      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
        -      "dev": true,
        -      "dependencies": {
        -        "es-define-property": "^1.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        -      }
        -    },
        -    "node_modules/has-proto": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
        -      "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.4"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        -      }
        -    },
        -    "node_modules/has-symbols": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
        -      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.4"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        -      }
        -    },
        -    "node_modules/has-yarn": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz",
        -      "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==",
        -      "dev": true,
        -      "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/hash-base": {
        -      "version": "3.0.4",
        -      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
        -      "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
        -      "dev": true,
        -      "dependencies": {
        -        "inherits": "^2.0.1",
        -        "safe-buffer": "^5.0.1"
        -      },
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/hash.js": {
        -      "version": "1.1.7",
        -      "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
        -      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
        -      "dev": true,
        -      "dependencies": {
        -        "inherits": "^2.0.3",
        -        "minimalistic-assert": "^1.0.1"
        -      }
        -    },
        -    "node_modules/hasha": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz",
        -      "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=",
        -      "dev": true,
        -      "dependencies": {
        -        "is-stream": "^1.0.1"
        -      },
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/hasown": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
        -      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "function-bind": "^1.1.2"
        -      },
        -      "engines": {
        -        "node": ">= 0.4"
        -      }
        -    },
        -    "node_modules/hawk": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz",
        -      "integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=",
        -      "deprecated": "This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.",
        -      "dev": true,
        -      "optional": true,
        -      "dependencies": {
        -        "boom": "0.4.x",
        -        "cryptiles": "0.2.x",
        -        "hoek": "0.9.x",
        -        "sntp": "0.2.x"
        -      },
        -      "engines": {
        -        "node": ">=0.8.0"
        -      }
        -    },
        -    "node_modules/he": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
        -      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
        -      "dev": true,
        -      "bin": {
        -        "he": "bin/he"
        -      }
        -    },
        -    "node_modules/hmac-drbg": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
        -      "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
        -      "dev": true,
        -      "dependencies": {
        -        "hash.js": "^1.0.3",
        -        "minimalistic-assert": "^1.0.0",
        -        "minimalistic-crypto-utils": "^1.0.1"
        -      }
        -    },
        -    "node_modules/hoek": {
        -      "version": "0.9.1",
        -      "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz",
        -      "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=",
        -      "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).",
        -      "dev": true,
        -      "optional": true,
        -      "engines": {
        -        "node": ">=0.8.0"
        -      }
        -    },
        -    "node_modules/homedir-polyfill": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
        -      "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
        -      "dev": true,
        -      "dependencies": {
        -        "parse-passwd": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/hooker": {
        -      "version": "0.2.3",
        -      "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz",
        -      "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=",
        -      "dev": true,
        -      "engines": {
        -        "node": "*"
        -      }
        -    },
        -    "node_modules/hosted-git-info": {
        -      "version": "2.8.9",
        -      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
        -      "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
        -      "dev": true
        -    },
        -    "node_modules/html-entities": {
        -      "version": "1.3.1",
        -      "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz",
        -      "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==",
        -      "dev": true
        -    },
        -    "node_modules/html-escaper": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
        -      "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
        -      "dev": true
        -    },
        -    "node_modules/htmlescape": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz",
        -      "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10"
        -      }
        -    },
        -    "node_modules/http-cache-semantics": {
        -      "version": "4.1.1",
        -      "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
        -      "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
        -      "dev": true
        -    },
        -    "node_modules/http-errors": {
        -      "version": "1.6.3",
        -      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
        -      "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
        -      "dev": true,
        -      "dependencies": {
        -        "depd": "~1.1.2",
        -        "inherits": "2.0.3",
        -        "setprototypeof": "1.1.0",
        -        "statuses": ">= 1.4.0 < 2"
        -      },
        -      "engines": {
        -        "node": ">= 0.6"
        -      }
        -    },
        -    "node_modules/http-parser-js": {
        -      "version": "0.4.13",
        -      "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz",
        -      "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=",
        -      "dev": true
        -    },
        -    "node_modules/http-signature": {
        -      "version": "0.10.1",
        -      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz",
        -      "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=",
        -      "dev": true,
        -      "optional": true,
        -      "dependencies": {
        -        "asn1": "0.1.11",
        -        "assert-plus": "^0.1.5",
        -        "ctype": "0.5.3"
        -      },
        -      "engines": {
        -        "node": ">=0.8"
        -      }
        -    },
        -    "node_modules/http2-wrapper": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
        -      "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
        -      "dev": true,
        -      "dependencies": {
        -        "quick-lru": "^5.1.1",
        -        "resolve-alpn": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10.19.0"
        -      }
        -    },
        -    "node_modules/http2-wrapper/node_modules/quick-lru": {
        -      "version": "5.1.1",
        -      "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
        -      "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/https-browserify": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
        -      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
        -      "dev": true
        -    },
        -    "node_modules/https-proxy-agent": {
        -      "version": "5.0.1",
        -      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
        -      "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
        -      "dev": true,
        -      "dependencies": {
        -        "agent-base": "6",
        -        "debug": "4"
        -      },
        -      "engines": {
        -        "node": ">= 6"
        -      }
        -    },
        -    "node_modules/https-proxy-agent/node_modules/debug": {
        -      "version": "4.3.4",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
        -      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "ms": "2.1.2"
        -      },
        -      "engines": {
        -        "node": ">=6.0"
        -      },
        -      "peerDependenciesMeta": {
        -        "supports-color": {
        -          "optional": true
        -        }
        -      }
        -    },
        -    "node_modules/https-proxy-agent/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        -      "dev": true
        -    },
        -    "node_modules/human-signals": {
        -      "version": "4.3.1",
        -      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
        -      "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=14.18.0"
        -      }
        -    },
        -    "node_modules/husky": {
        -      "version": "4.3.8",
        -      "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz",
        -      "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==",
        -      "dev": true,
        -      "hasInstallScript": true,
        -      "dependencies": {
        -        "chalk": "^4.0.0",
        -        "ci-info": "^2.0.0",
        -        "compare-versions": "^3.6.0",
        -        "cosmiconfig": "^7.0.0",
        -        "find-versions": "^4.0.0",
        -        "opencollective-postinstall": "^2.0.2",
        -        "pkg-dir": "^5.0.0",
        -        "please-upgrade-node": "^3.2.0",
        -        "slash": "^3.0.0",
        -        "which-pm-runs": "^1.0.0"
        -      },
        -      "bin": {
        -        "husky-run": "bin/run.js",
        -        "husky-upgrade": "lib/upgrader/bin.js"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "type": "opencollective",
        -        "url": "https://opencollective.com/husky"
        -      }
        -    },
        -    "node_modules/husky/node_modules/ansi-styles": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        -      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
        -      "dev": true,
        -      "dependencies": {
        -        "color-convert": "^2.0.1"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
        -      }
        -    },
        -    "node_modules/husky/node_modules/chalk": {
        -      "version": "4.1.2",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        -      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^4.1.0",
        -        "supports-color": "^7.1.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/chalk?sponsor=1"
        -      }
        -    },
        -    "node_modules/husky/node_modules/color-convert": {
        +    "node_modules/ccount": {
               "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        -      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "color-name": "~1.1.4"
        -      },
        -      "engines": {
        -        "node": ">=7.0.0"
        -      }
        -    },
        -    "node_modules/husky/node_modules/cosmiconfig": {
        -      "version": "7.0.1",
        -      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
        -      "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "@types/parse-json": "^4.0.0",
        -        "import-fresh": "^3.2.1",
        -        "parse-json": "^5.0.0",
        -        "path-type": "^4.0.0",
        -        "yaml": "^1.10.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      }
        -    },
        -    "node_modules/husky/node_modules/find-up": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
        -      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
        -      "dev": true,
        -      "dependencies": {
        -        "locate-path": "^6.0.0",
        -        "path-exists": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/husky/node_modules/has-flag": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        -      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/husky/node_modules/locate-path": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
        -      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
        -      "dev": true,
        -      "dependencies": {
        -        "p-locate": "^5.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/husky/node_modules/p-limit": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
        -      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "yocto-queue": "^0.1.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/husky/node_modules/p-locate": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
        -      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
        -      "dev": true,
        -      "dependencies": {
        -        "p-limit": "^3.0.2"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/husky/node_modules/parse-json": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
        -      "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
        -      "dev": true,
        -      "dependencies": {
        -        "@babel/code-frame": "^7.0.0",
        -        "error-ex": "^1.3.1",
        -        "json-parse-even-better-errors": "^2.3.0",
        -        "lines-and-columns": "^1.1.6"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/husky/node_modules/pkg-dir": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
        -      "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==",
        -      "dev": true,
        -      "dependencies": {
        -        "find-up": "^5.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        +      "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
        +      "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
        +      "dev": true,
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/husky/node_modules/supports-color": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        -      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        +    "node_modules/chai": {
        +      "version": "5.1.2",
        +      "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
        +      "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
               "dev": true,
               "dependencies": {
        -        "has-flag": "^4.0.0"
        +        "assertion-error": "^2.0.1",
        +        "check-error": "^2.1.1",
        +        "deep-eql": "^5.0.1",
        +        "loupe": "^3.1.0",
        +        "pathval": "^2.0.0"
               },
               "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/i18next": {
        -      "version": "19.0.2",
        -      "resolved": "https://registry.npmjs.org/i18next/-/i18next-19.0.2.tgz",
        -      "integrity": "sha512-fBa43Ann2udP1CQAz3IQpOZ1dGAkmi3mMfzisOhH17igneSRbvZ7P2RNbL+L1iRYKMufBmVwnC7G3gqcyviZ9g==",
        -      "dev": true,
        -      "dependencies": {
        -        "@babel/runtime": "^7.3.1"
        +        "node": ">=12"
               }
             },
        -    "node_modules/i18next-browser-languagedetector": {
        -      "version": "4.0.1",
        -      "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.0.1.tgz",
        -      "integrity": "sha512-RxSoX6mB8cab0CTIQ+klCS764vYRj+Jk621cnFVsINvcdlb/cdi3vQFyrPwmnowB7ReUadjHovgZX+RPIzHVQQ==",
        +    "node_modules/character-entities": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
        +      "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/runtime": "^7.5.5"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/i18next-browser-languagedetector/node_modules/@babel/runtime": {
        -      "version": "7.7.7",
        -      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz",
        -      "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==",
        +    "node_modules/character-entities-html4": {
        +      "version": "2.1.0",
        +      "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
        +      "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
               "dev": true,
        -      "dependencies": {
        -        "regenerator-runtime": "^0.13.2"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/iconv-lite": {
        -      "version": "0.4.24",
        -      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
        -      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
        +    "node_modules/character-entities-legacy": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
        +      "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
               "dev": true,
        -      "dependencies": {
        -        "safer-buffer": ">= 2.1.2 < 3"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/ieee754": {
        -      "version": "1.1.13",
        -      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
        -      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
        +    "node_modules/chardet": {
        +      "version": "0.7.0",
        +      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
        +      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
               "dev": true
             },
        -    "node_modules/ignore": {
        -      "version": "5.2.4",
        -      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
        -      "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
        +    "node_modules/check-error": {
        +      "version": "2.1.1",
        +      "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
        +      "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
               "dev": true,
               "engines": {
        -        "node": ">= 4"
        +        "node": ">= 16"
               }
             },
        -    "node_modules/ignore-walk": {
        -      "version": "6.0.3",
        -      "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.3.tgz",
        -      "integrity": "sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==",
        +    "node_modules/cheerio": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
        +      "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
               "dev": true,
               "dependencies": {
        -        "minimatch": "^9.0.0"
        +        "cheerio-select": "^2.1.0",
        +        "dom-serializer": "^2.0.0",
        +        "domhandler": "^5.0.3",
        +        "domutils": "^3.1.0",
        +        "encoding-sniffer": "^0.2.0",
        +        "htmlparser2": "^9.1.0",
        +        "parse5": "^7.1.2",
        +        "parse5-htmlparser2-tree-adapter": "^7.0.0",
        +        "parse5-parser-stream": "^7.1.2",
        +        "undici": "^6.19.5",
        +        "whatwg-mimetype": "^4.0.0"
               },
               "engines": {
        -        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
        +        "node": ">=18.17"
        +      },
        +      "funding": {
        +        "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
               }
             },
        -    "node_modules/ignore-walk/node_modules/brace-expansion": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
        -      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
        +    "node_modules/cheerio-select": {
        +      "version": "2.1.0",
        +      "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
        +      "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
               "dev": true,
               "dependencies": {
        -        "balanced-match": "^1.0.0"
        +        "boolbase": "^1.0.0",
        +        "css-select": "^5.1.0",
        +        "css-what": "^6.1.0",
        +        "domelementtype": "^2.3.0",
        +        "domhandler": "^5.0.3",
        +        "domutils": "^3.0.1"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/fb55"
               }
             },
        -    "node_modules/ignore-walk/node_modules/minimatch": {
        -      "version": "9.0.3",
        -      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
        -      "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
        +    "node_modules/cheerio/node_modules/entities": {
        +      "version": "4.5.0",
        +      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
        +      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
               "dev": true,
        -      "dependencies": {
        -        "brace-expansion": "^2.0.1"
        -      },
               "engines": {
        -        "node": ">=16 || 14 >=14.17"
        +        "node": ">=0.12"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/isaacs"
        +        "url": "https://github.com/fb55/entities?sponsor=1"
               }
             },
        -    "node_modules/import-fresh": {
        -      "version": "3.3.0",
        -      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
        -      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
        +    "node_modules/cheerio/node_modules/parse5": {
        +      "version": "7.2.1",
        +      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
        +      "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
               "dev": true,
               "dependencies": {
        -        "parent-module": "^1.0.0",
        -        "resolve-from": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=6"
        +        "entities": "^4.5.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/import-fresh/node_modules/resolve-from": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
        -      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/import-lazy": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
        -      "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        +        "url": "https://github.com/inikulin/parse5?sponsor=1"
               }
             },
        -    "node_modules/import-local": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
        -      "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==",
        +    "node_modules/chokidar": {
        +      "version": "3.5.3",
        +      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
        +      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "individual",
        +          "url": "https://paulmillr.com/funding/"
        +        }
        +      ],
               "dependencies": {
        -        "pkg-dir": "^4.2.0",
        -        "resolve-cwd": "^3.0.0"
        -      },
        -      "bin": {
        -        "import-local-fixture": "fixtures/cli.js"
        +        "anymatch": "~3.1.2",
        +        "braces": "~3.0.2",
        +        "glob-parent": "~5.1.2",
        +        "is-binary-path": "~2.1.0",
        +        "is-glob": "~4.0.1",
        +        "normalize-path": "~3.0.0",
        +        "readdirp": "~3.6.0"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">= 8.10.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "optionalDependencies": {
        +        "fsevents": "~2.3.2"
               }
             },
        -    "node_modules/import-local/node_modules/pkg-dir": {
        -      "version": "4.2.0",
        -      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
        -      "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
        +    "node_modules/chokidar/node_modules/glob-parent": {
        +      "version": "5.1.2",
        +      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
        +      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
               "dev": true,
               "dependencies": {
        -        "find-up": "^4.0.0"
        +        "is-glob": "^4.0.1"
               },
               "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/imurmurhash": {
        -      "version": "0.1.4",
        -      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
        -      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.8.19"
        +        "node": ">= 6"
               }
             },
        -    "node_modules/indent-string": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
        -      "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
        +    "node_modules/chromium-bidi": {
        +      "version": "0.6.3",
        +      "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz",
        +      "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==",
               "dev": true,
        +      "optional": true,
        +      "peer": true,
               "dependencies": {
        -        "repeating": "^2.0.0"
        +        "mitt": "3.0.1",
        +        "urlpattern-polyfill": "10.0.0",
        +        "zod": "3.23.8"
               },
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "peerDependencies": {
        +        "devtools-protocol": "*"
               }
             },
        -    "node_modules/inflight": {
        -      "version": "1.0.6",
        -      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
        -      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
        +    "node_modules/chromium-bidi/node_modules/zod": {
        +      "version": "3.23.8",
        +      "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
        +      "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
               "dev": true,
        -      "dependencies": {
        -        "once": "^1.3.0",
        -        "wrappy": "1"
        +      "optional": true,
        +      "peer": true,
        +      "funding": {
        +        "url": "https://github.com/sponsors/colinhacks"
               }
             },
        -    "node_modules/inherits": {
        -      "version": "2.0.3",
        -      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
        -      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
        -      "dev": true
        -    },
        -    "node_modules/ini": {
        -      "version": "1.3.7",
        -      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz",
        -      "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==",
        +    "node_modules/ci-info": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
        +      "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
               "dev": true
             },
        -    "node_modules/inline-source-map": {
        -      "version": "0.6.2",
        -      "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz",
        -      "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=",
        +    "node_modules/cli-cursor": {
        +      "version": "3.1.0",
        +      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
        +      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
               "dev": true,
               "dependencies": {
        -        "source-map": "~0.5.3"
        -      }
        -    },
        -    "node_modules/inline-source-map/node_modules/source-map": {
        -      "version": "0.5.7",
        -      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
        -      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
        -      "dev": true,
        +        "restore-cursor": "^3.1.0"
        +      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/inquirer": {
        -      "version": "7.3.3",
        -      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
        -      "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
        +    "node_modules/cli-width": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
        +      "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
               "dev": true,
        -      "dependencies": {
        -        "ansi-escapes": "^4.2.1",
        -        "chalk": "^4.1.0",
        -        "cli-cursor": "^3.1.0",
        -        "cli-width": "^3.0.0",
        -        "external-editor": "^3.0.3",
        -        "figures": "^3.0.0",
        -        "lodash": "^4.17.19",
        -        "mute-stream": "0.0.8",
        -        "run-async": "^2.4.0",
        -        "rxjs": "^6.6.0",
        -        "string-width": "^4.1.0",
        -        "strip-ansi": "^6.0.0",
        -        "through": "^2.3.6"
        -      },
               "engines": {
        -        "node": ">=8.0.0"
        +        "node": ">= 10"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt": {
        -      "version": "0.2.0",
        -      "resolved": "https://registry.npmjs.org/inquirer-autosubmit-prompt/-/inquirer-autosubmit-prompt-0.2.0.tgz",
        -      "integrity": "sha512-mzNrusCk5L6kSzlN0Ioddn8yzrhYNLli+Sn2ZxMuLechMYAzakiFCIULxsxlQb5YKzthLGfrFACcWoAvM7p04Q==",
        +    "node_modules/cliui": {
        +      "version": "6.0.0",
        +      "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
        +      "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^2.4.1",
        -        "inquirer": "^6.2.1",
        -        "rxjs": "^6.3.3"
        +        "string-width": "^4.2.0",
        +        "strip-ansi": "^6.0.0",
        +        "wrap-ansi": "^6.2.0"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/ansi-escapes": {
        -      "version": "3.2.0",
        -      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
        -      "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
        +    "node_modules/cliui/node_modules/ansi-regex": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
               "dev": true,
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/ansi-regex": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
        -      "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4"
        -      }
        +    "node_modules/cliui/node_modules/emoji-regex": {
        +      "version": "8.0.0",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        +      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        +      "dev": true
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/cli-cursor": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
        -      "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==",
        +    "node_modules/cliui/node_modules/is-fullwidth-code-point": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        +      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
               "dev": true,
        -      "dependencies": {
        -        "restore-cursor": "^2.0.0"
        -      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/cli-width": {
        -      "version": "2.2.1",
        -      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
        -      "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
        -      "dev": true
        -    },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/figures": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
        -      "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==",
        +    "node_modules/cliui/node_modules/string-width": {
        +      "version": "4.2.2",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
        +      "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
               "dev": true,
               "dependencies": {
        -        "escape-string-regexp": "^1.0.5"
        +        "emoji-regex": "^8.0.0",
        +        "is-fullwidth-code-point": "^3.0.0",
        +        "strip-ansi": "^6.0.0"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/inquirer": {
        -      "version": "6.5.2",
        -      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
        -      "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
        +    "node_modules/cliui/node_modules/strip-ansi": {
        +      "version": "6.0.0",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
        +      "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
               "dev": true,
               "dependencies": {
        -        "ansi-escapes": "^3.2.0",
        -        "chalk": "^2.4.2",
        -        "cli-cursor": "^2.1.0",
        -        "cli-width": "^2.0.0",
        -        "external-editor": "^3.0.3",
        -        "figures": "^2.0.0",
        -        "lodash": "^4.17.12",
        -        "mute-stream": "0.0.7",
        -        "run-async": "^2.2.0",
        -        "rxjs": "^6.4.0",
        -        "string-width": "^2.1.0",
        -        "strip-ansi": "^5.1.0",
        -        "through": "^2.3.6"
        +        "ansi-regex": "^5.0.0"
               },
               "engines": {
        -        "node": ">=6.0.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/is-fullwidth-code-point": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
        -      "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
        +    "node_modules/color-name": {
        +      "version": "1.1.4",
        +      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
        +      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
        +      "dev": true
        +    },
        +    "node_modules/colorette": {
        +      "version": "2.0.20",
        +      "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
        +      "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
        +      "dev": true
        +    },
        +    "node_modules/colorjs.io": {
        +      "version": "0.5.2",
        +      "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
        +      "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="
        +    },
        +    "node_modules/comma-separated-tokens": {
        +      "version": "2.0.3",
        +      "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
        +      "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
               "dev": true,
        -      "engines": {
        -        "node": ">=4"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/mimic-fn": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
        -      "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
        +    "node_modules/commander": {
        +      "version": "12.1.0",
        +      "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
        +      "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
               "dev": true,
               "engines": {
        -        "node": ">=4"
        +        "node": ">=18"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/mute-stream": {
        -      "version": "0.0.7",
        -      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
        -      "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==",
        +    "node_modules/commondir": {
        +      "version": "1.0.1",
        +      "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
        +      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
               "dev": true
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/onetime": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
        -      "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==",
        +    "node_modules/compare-versions": {
        +      "version": "3.6.0",
        +      "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
        +      "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==",
        +      "dev": true
        +    },
        +    "node_modules/compress-commons": {
        +      "version": "6.0.2",
        +      "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
        +      "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==",
               "dev": true,
               "dependencies": {
        -        "mimic-fn": "^1.0.0"
        +        "crc-32": "^1.2.0",
        +        "crc32-stream": "^6.0.0",
        +        "is-stream": "^2.0.1",
        +        "normalize-path": "^3.0.0",
        +        "readable-stream": "^4.0.0"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/restore-cursor": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
        -      "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==",
        +    "node_modules/compress-commons/node_modules/buffer": {
        +      "version": "6.0.3",
        +      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
        +      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
        +        }
        +      ],
               "dependencies": {
        -        "onetime": "^2.0.0",
        -        "signal-exit": "^3.0.2"
        -      },
        -      "engines": {
        -        "node": ">=4"
        +        "base64-js": "^1.3.1",
        +        "ieee754": "^1.2.1"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/rxjs": {
        -      "version": "6.6.7",
        -      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
        -      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
        +    "node_modules/compress-commons/node_modules/events": {
        +      "version": "3.3.0",
        +      "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
        +      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
               "dev": true,
        -      "dependencies": {
        -        "tslib": "^1.9.0"
        -      },
               "engines": {
        -        "npm": ">=2.0.0"
        +        "node": ">=0.8.x"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/string-width": {
        -      "version": "2.1.1",
        -      "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
        -      "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
        +    "node_modules/compress-commons/node_modules/is-stream": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
        +      "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
               "dev": true,
        -      "dependencies": {
        -        "is-fullwidth-code-point": "^2.0.0",
        -        "strip-ansi": "^4.0.0"
        -      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/string-width/node_modules/strip-ansi": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
        -      "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
        +    "node_modules/compress-commons/node_modules/readable-stream": {
        +      "version": "4.7.0",
        +      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
        +      "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
               "dev": true,
               "dependencies": {
        -        "ansi-regex": "^3.0.0"
        +        "abort-controller": "^3.0.0",
        +        "buffer": "^6.0.3",
        +        "events": "^3.3.0",
        +        "process": "^0.11.10",
        +        "string_decoder": "^1.3.0"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/strip-ansi": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
        -      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
        +    "node_modules/compress-commons/node_modules/safe-buffer": {
        +      "version": "5.2.1",
        +      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
        +      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
        +      "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
        +        }
        +      ]
        +    },
        +    "node_modules/compress-commons/node_modules/string_decoder": {
        +      "version": "1.3.0",
        +      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
        +      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
               "dev": true,
               "dependencies": {
        -        "ansi-regex": "^4.1.0"
        -      },
        -      "engines": {
        -        "node": ">=6"
        +        "safe-buffer": "~5.2.0"
               }
             },
        -    "node_modules/inquirer-autosubmit-prompt/node_modules/strip-ansi/node_modules/ansi-regex": {
        -      "version": "4.1.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
        -      "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
        +    "node_modules/concat-map": {
        +      "version": "0.0.1",
        +      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
        +      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
        +      "dev": true
        +    },
        +    "node_modules/concurrently": {
        +      "version": "8.2.2",
        +      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz",
        +      "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==",
               "dev": true,
        +      "dependencies": {
        +        "chalk": "^4.1.2",
        +        "date-fns": "^2.30.0",
        +        "lodash": "^4.17.21",
        +        "rxjs": "^7.8.1",
        +        "shell-quote": "^1.8.1",
        +        "spawn-command": "0.0.2",
        +        "supports-color": "^8.1.1",
        +        "tree-kill": "^1.2.2",
        +        "yargs": "^17.7.2"
        +      },
        +      "bin": {
        +        "conc": "dist/bin/concurrently.js",
        +        "concurrently": "dist/bin/concurrently.js"
        +      },
               "engines": {
        -        "node": ">=6"
        +        "node": "^14.13.0 || >=16.0.0"
        +      },
        +      "funding": {
        +        "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
               }
             },
        -    "node_modules/inquirer/node_modules/ansi-regex": {
        +    "node_modules/concurrently/node_modules/ansi-regex": {
               "version": "5.0.1",
               "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
               "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
        @@ -8880,7 +4014,7 @@
                 "node": ">=8"
               }
             },
        -    "node_modules/inquirer/node_modules/ansi-styles": {
        +    "node_modules/concurrently/node_modules/ansi-styles": {
               "version": "4.3.0",
               "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
               "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
        @@ -8895,7 +4029,7 @@
                 "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/inquirer/node_modules/chalk": {
        +    "node_modules/concurrently/node_modules/chalk": {
               "version": "4.1.2",
               "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
               "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
        @@ -8911,7 +4045,33 @@
                 "url": "https://github.com/chalk/chalk?sponsor=1"
               }
             },
        -    "node_modules/inquirer/node_modules/color-convert": {
        +    "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": {
        +      "version": "7.2.0",
        +      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        +      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        +      "dev": true,
        +      "dependencies": {
        +        "has-flag": "^4.0.0"
        +      },
        +      "engines": {
        +        "node": ">=8"
        +      }
        +    },
        +    "node_modules/concurrently/node_modules/cliui": {
        +      "version": "8.0.1",
        +      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
        +      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
        +      "dev": true,
        +      "dependencies": {
        +        "string-width": "^4.2.0",
        +        "strip-ansi": "^6.0.1",
        +        "wrap-ansi": "^7.0.0"
        +      },
        +      "engines": {
        +        "node": ">=12"
        +      }
        +    },
        +    "node_modules/concurrently/node_modules/color-convert": {
               "version": "2.0.1",
               "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
               "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
        @@ -8923,28 +4083,29 @@
                 "node": ">=7.0.0"
               }
             },
        -    "node_modules/inquirer/node_modules/emoji-regex": {
        -      "version": "8.0.0",
        -      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        -      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        -      "dev": true
        -    },
        -    "node_modules/inquirer/node_modules/figures": {
        -      "version": "3.2.0",
        -      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
        -      "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
        +    "node_modules/concurrently/node_modules/date-fns": {
        +      "version": "2.30.0",
        +      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
        +      "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
               "dev": true,
               "dependencies": {
        -        "escape-string-regexp": "^1.0.5"
        +        "@babel/runtime": "^7.21.0"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=0.11"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/date-fns"
               }
             },
        -    "node_modules/inquirer/node_modules/has-flag": {
        +    "node_modules/concurrently/node_modules/emoji-regex": {
        +      "version": "8.0.0",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        +      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        +      "dev": true
        +    },
        +    "node_modules/concurrently/node_modules/has-flag": {
               "version": "4.0.0",
               "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
               "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        @@ -8953,7 +4114,7 @@
                 "node": ">=8"
               }
             },
        -    "node_modules/inquirer/node_modules/is-fullwidth-code-point": {
        +    "node_modules/concurrently/node_modules/is-fullwidth-code-point": {
               "version": "3.0.0",
               "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
               "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
        @@ -8962,19 +4123,7 @@
                 "node": ">=8"
               }
             },
        -    "node_modules/inquirer/node_modules/rxjs": {
        -      "version": "6.6.7",
        -      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
        -      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "tslib": "^1.9.0"
        -      },
        -      "engines": {
        -        "npm": ">=2.0.0"
        -      }
        -    },
        -    "node_modules/inquirer/node_modules/string-width": {
        +    "node_modules/concurrently/node_modules/string-width": {
               "version": "4.2.3",
               "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
               "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
        @@ -8988,7 +4137,7 @@
                 "node": ">=8"
               }
             },
        -    "node_modules/inquirer/node_modules/strip-ansi": {
        +    "node_modules/concurrently/node_modules/strip-ansi": {
               "version": "6.0.1",
               "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
               "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
        @@ -9000,1811 +4149,1922 @@
                 "node": ">=8"
               }
             },
        -    "node_modules/inquirer/node_modules/supports-color": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        -      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        +    "node_modules/concurrently/node_modules/supports-color": {
        +      "version": "8.1.1",
        +      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
        +      "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
               "dev": true,
               "dependencies": {
                 "has-flag": "^4.0.0"
               },
               "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/insert-module-globals": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz",
        -      "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==",
        -      "dev": true,
        -      "dependencies": {
        -        "acorn-node": "^1.5.2",
        -        "combine-source-map": "^0.8.0",
        -        "concat-stream": "^1.6.1",
        -        "is-buffer": "^1.1.0",
        -        "JSONStream": "^1.0.3",
        -        "path-is-absolute": "^1.0.1",
        -        "process": "~0.11.0",
        -        "through2": "^2.0.0",
        -        "undeclared-identifiers": "^1.1.2",
        -        "xtend": "^4.0.0"
        +        "node": ">=10"
               },
        -      "bin": {
        -        "insert-module-globals": "bin/cmd.js"
        -      }
        -    },
        -    "node_modules/insert-module-globals/node_modules/concat-stream": {
        -      "version": "1.6.2",
        -      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
        -      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
        -      "dev": true,
        -      "engines": [
        -        "node >= 0.8"
        -      ],
        -      "dependencies": {
        -        "buffer-from": "^1.0.0",
        -        "inherits": "^2.0.3",
        -        "readable-stream": "^2.2.2",
        -        "typedarray": "^0.0.6"
        +      "funding": {
        +        "url": "https://github.com/chalk/supports-color?sponsor=1"
               }
             },
        -    "node_modules/interpret": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
        -      "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=",
        -      "dev": true
        -    },
        -    "node_modules/invariant": {
        -      "version": "2.2.4",
        -      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
        -      "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
        +    "node_modules/concurrently/node_modules/wrap-ansi": {
        +      "version": "7.0.0",
        +      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
        +      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
               "dev": true,
               "dependencies": {
        -        "loose-envify": "^1.0.0"
        +        "ansi-styles": "^4.0.0",
        +        "string-width": "^4.1.0",
        +        "strip-ansi": "^6.0.0"
        +      },
        +      "engines": {
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
               }
             },
        -    "node_modules/ipaddr.js": {
        -      "version": "1.9.1",
        -      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
        -      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
        +    "node_modules/concurrently/node_modules/y18n": {
        +      "version": "5.0.8",
        +      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
        +      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.10"
        +        "node": ">=10"
               }
             },
        -    "node_modules/is-absolute": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
        -      "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
        +    "node_modules/concurrently/node_modules/yargs": {
        +      "version": "17.7.2",
        +      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
        +      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
               "dev": true,
               "dependencies": {
        -        "is-relative": "^1.0.0",
        -        "is-windows": "^1.0.1"
        +        "cliui": "^8.0.1",
        +        "escalade": "^3.1.1",
        +        "get-caller-file": "^2.0.5",
        +        "require-directory": "^2.1.1",
        +        "string-width": "^4.2.3",
        +        "y18n": "^5.0.5",
        +        "yargs-parser": "^21.1.1"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=12"
               }
             },
        -    "node_modules/is-arrayish": {
        -      "version": "0.2.1",
        -      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
        -      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
        -      "dev": true
        -    },
        -    "node_modules/is-binary-path": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
        -      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
        +    "node_modules/concurrently/node_modules/yargs-parser": {
        +      "version": "21.1.1",
        +      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
        +      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
               "dev": true,
        -      "dependencies": {
        -        "binary-extensions": "^2.0.0"
        -      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=12"
               }
             },
        -    "node_modules/is-buffer": {
        -      "version": "1.1.6",
        -      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
        -      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
        +    "node_modules/core-util-is": {
        +      "version": "1.0.2",
        +      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
        +      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
               "dev": true
             },
        -    "node_modules/is-builtin-module": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
        -      "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
        +    "node_modules/crc-32": {
        +      "version": "1.2.2",
        +      "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
        +      "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
               "dev": true,
        -      "dependencies": {
        -        "builtin-modules": "^1.0.0"
        +      "bin": {
        +        "crc32": "bin/crc32.njs"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=0.8"
               }
             },
        -    "node_modules/is-ci": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
        -      "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
        +    "node_modules/crc32-stream": {
        +      "version": "6.0.0",
        +      "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz",
        +      "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==",
               "dev": true,
               "dependencies": {
        -        "ci-info": "^3.2.0"
        +        "crc-32": "^1.2.0",
        +        "readable-stream": "^4.0.0"
               },
        -      "bin": {
        -        "is-ci": "bin.js"
        +      "engines": {
        +        "node": ">= 14"
               }
             },
        -    "node_modules/is-ci/node_modules/ci-info": {
        -      "version": "3.8.0",
        -      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
        -      "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==",
        +    "node_modules/crc32-stream/node_modules/buffer": {
        +      "version": "6.0.3",
        +      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
        +      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
               "dev": true,
               "funding": [
                 {
                   "type": "github",
        -          "url": "https://github.com/sponsors/sibiraj-s"
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
                 }
               ],
        +      "dependencies": {
        +        "base64-js": "^1.3.1",
        +        "ieee754": "^1.2.1"
        +      }
        +    },
        +    "node_modules/crc32-stream/node_modules/events": {
        +      "version": "3.3.0",
        +      "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
        +      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
        +      "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=0.8.x"
               }
             },
        -    "node_modules/is-core-module": {
        -      "version": "2.9.0",
        -      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
        -      "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
        +    "node_modules/crc32-stream/node_modules/readable-stream": {
        +      "version": "4.7.0",
        +      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
        +      "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
               "dev": true,
               "dependencies": {
        -        "has": "^1.0.3"
        +        "abort-controller": "^3.0.0",
        +        "buffer": "^6.0.3",
        +        "events": "^3.3.0",
        +        "process": "^0.11.10",
        +        "string_decoder": "^1.3.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        +      "engines": {
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
               }
             },
        -    "node_modules/is-docker": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz",
        -      "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==",
        +    "node_modules/crc32-stream/node_modules/safe-buffer": {
        +      "version": "5.2.1",
        +      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
        +      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
               "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
        +        }
        +      ]
             },
        -    "node_modules/is-extglob": {
        -      "version": "2.1.1",
        -      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
        -      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
        +    "node_modules/crc32-stream/node_modules/string_decoder": {
        +      "version": "1.3.0",
        +      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
        +      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "dependencies": {
        +        "safe-buffer": "~5.2.0"
               }
             },
        -    "node_modules/is-finite": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
        -      "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
        +    "node_modules/cross-spawn": {
        +      "version": "7.0.6",
        +      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
        +      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
               "dev": true,
               "dependencies": {
        -        "number-is-nan": "^1.0.0"
        +        "path-key": "^3.1.0",
        +        "shebang-command": "^2.0.0",
        +        "which": "^2.0.1"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">= 8"
               }
             },
        -    "node_modules/is-fullwidth-code-point": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
        -      "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
        +    "node_modules/cross-spawn/node_modules/which": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
        +      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
               "dev": true,
               "dependencies": {
        -        "number-is-nan": "^1.0.0"
        +        "isexe": "^2.0.0"
        +      },
        +      "bin": {
        +        "node-which": "bin/node-which"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">= 8"
               }
             },
        -    "node_modules/is-glob": {
        -      "version": "4.0.3",
        -      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
        -      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
        +    "node_modules/css-select": {
        +      "version": "5.1.0",
        +      "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
        +      "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
               "dev": true,
               "dependencies": {
        -        "is-extglob": "^2.1.1"
        +        "boolbase": "^1.0.0",
        +        "css-what": "^6.1.0",
        +        "domhandler": "^5.0.2",
        +        "domutils": "^3.0.1",
        +        "nth-check": "^2.0.1"
               },
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "funding": {
        +        "url": "https://github.com/sponsors/fb55"
               }
             },
        -    "node_modules/is-inside-container": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
        -      "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
        +    "node_modules/css-shorthand-properties": {
        +      "version": "1.1.2",
        +      "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.2.tgz",
        +      "integrity": "sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==",
        +      "dev": true
        +    },
        +    "node_modules/css-value": {
        +      "version": "0.0.1",
        +      "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz",
        +      "integrity": "sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==",
        +      "dev": true
        +    },
        +    "node_modules/css-what": {
        +      "version": "6.1.0",
        +      "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
        +      "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
               "dev": true,
        -      "dependencies": {
        -        "is-docker": "^3.0.0"
        -      },
        -      "bin": {
        -        "is-inside-container": "cli.js"
        -      },
               "engines": {
        -        "node": ">=14.16"
        +        "node": ">= 6"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/sponsors/fb55"
               }
             },
        -    "node_modules/is-inside-container/node_modules/is-docker": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
        -      "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
        +    "node_modules/data-uri-to-buffer": {
        +      "version": "6.0.2",
        +      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
        +      "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
               "dev": true,
        -      "bin": {
        -        "is-docker": "cli.js"
        -      },
               "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/is-installed-globally": {
        -      "version": "0.4.0",
        -      "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz",
        -      "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==",
        +    "node_modules/dayjs": {
        +      "version": "1.11.13",
        +      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
        +      "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
        +      "dev": true
        +    },
        +    "node_modules/de-indent": {
        +      "version": "1.0.2",
        +      "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
        +      "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
        +      "dev": true,
        +      "optional": true
        +    },
        +    "node_modules/decamelize": {
        +      "version": "1.2.0",
        +      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
        +      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
               "dev": true,
        -      "dependencies": {
        -        "global-dirs": "^3.0.0",
        -        "is-path-inside": "^3.0.2"
        -      },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=0.10.0"
        +      }
        +    },
        +    "node_modules/decode-named-character-reference": {
        +      "version": "1.0.2",
        +      "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
        +      "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
        +      "dev": true,
        +      "dependencies": {
        +        "character-entities": "^2.0.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/is-installed-globally/node_modules/is-path-inside": {
        -      "version": "3.0.3",
        -      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
        -      "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
        +    "node_modules/deep-eql": {
        +      "version": "5.0.2",
        +      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
        +      "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=6"
               }
             },
        -    "node_modules/is-interactive": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
        -      "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
        +    "node_modules/deep-is": {
        +      "version": "0.1.4",
        +      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
        +      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
        +      "dev": true
        +    },
        +    "node_modules/deepmerge": {
        +      "version": "4.3.1",
        +      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
        +      "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
               "dev": true,
               "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/is-name-taken": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/is-name-taken/-/is-name-taken-2.0.0.tgz",
        -      "integrity": "sha512-W+FUWF5g7ONVJTx3rldZeVizmPzrMMUdscpSQ96vyYerx+4b2NcqaujLJJDWruGzE0FjzGZO9RFIipOGxx/WIw==",
        +    "node_modules/deepmerge-ts": {
        +      "version": "7.1.3",
        +      "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz",
        +      "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=16.0.0"
        +      }
        +    },
        +    "node_modules/degenerator": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
        +      "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
               "dev": true,
               "dependencies": {
        -        "all-package-names": "^2.0.2",
        -        "package-name-conflict": "^1.0.3",
        -        "validate-npm-package-name": "^3.0.0"
        +        "ast-types": "^0.13.4",
        +        "escodegen": "^2.1.0",
        +        "esprima": "^4.0.1"
        +      },
        +      "engines": {
        +        "node": ">= 14"
               }
             },
        -    "node_modules/is-npm": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz",
        -      "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==",
        +    "node_modules/dequal": {
        +      "version": "2.0.3",
        +      "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
        +      "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
               "dev": true,
               "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">=6"
               }
             },
        -    "node_modules/is-number": {
        -      "version": "7.0.0",
        -      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
        -      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
        +    "node_modules/devtools-protocol": {
        +      "version": "0.0.1312386",
        +      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz",
        +      "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==",
        +      "dev": true,
        +      "optional": true,
        +      "peer": true
        +    },
        +    "node_modules/didyoumean": {
        +      "version": "1.2.1",
        +      "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.1.tgz",
        +      "integrity": "sha1-6S7f2tplN9SE1zwBcv0eugxJdv8=",
        +      "dev": true
        +    },
        +    "node_modules/diff": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
        +      "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
               "dev": true,
               "engines": {
        -        "node": ">=0.12.0"
        +        "node": ">=0.3.1"
               }
             },
        -    "node_modules/is-number-like": {
        -      "version": "1.0.8",
        -      "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz",
        -      "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==",
        +    "node_modules/doctrine": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
        +      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
               "dev": true,
               "dependencies": {
        -        "lodash.isfinite": "^3.3.2"
        +        "esutils": "^2.0.2"
        +      },
        +      "engines": {
        +        "node": ">=6.0.0"
               }
             },
        -    "node_modules/is-obj": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
        -      "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
        +    "node_modules/doctrine-temporary-fork": {
        +      "version": "2.1.0",
        +      "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz",
        +      "integrity": "sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA==",
               "dev": true,
        +      "dependencies": {
        +        "esutils": "^2.0.2"
        +      },
               "engines": {
                 "node": ">=0.10.0"
               }
             },
        -    "node_modules/is-observable": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz",
        -      "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==",
        +    "node_modules/documentation": {
        +      "version": "14.0.3",
        +      "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.3.tgz",
        +      "integrity": "sha512-B7cAviVKN9Rw7Ofd+9grhVuxiHwly6Ieh+d/ceMw8UdBOv/irkuwnDEJP8tq0wgdLJDUVuIkovV+AX9mTrZFxg==",
               "dev": true,
               "dependencies": {
        -        "symbol-observable": "^1.1.0"
        +        "@babel/core": "^7.18.10",
        +        "@babel/generator": "^7.18.10",
        +        "@babel/parser": "^7.18.11",
        +        "@babel/traverse": "^7.18.11",
        +        "@babel/types": "^7.18.10",
        +        "chalk": "^5.0.1",
        +        "chokidar": "^3.5.3",
        +        "diff": "^5.1.0",
        +        "doctrine-temporary-fork": "2.1.0",
        +        "git-url-parse": "^13.1.0",
        +        "github-slugger": "1.4.0",
        +        "glob": "^8.0.3",
        +        "globals-docs": "^2.4.1",
        +        "highlight.js": "^11.6.0",
        +        "ini": "^3.0.0",
        +        "js-yaml": "^4.1.0",
        +        "konan": "^2.1.1",
        +        "lodash": "^4.17.21",
        +        "mdast-util-find-and-replace": "^2.2.1",
        +        "mdast-util-inject": "^1.1.0",
        +        "micromark-util-character": "^1.1.0",
        +        "parse-filepath": "^1.0.2",
        +        "pify": "^6.0.0",
        +        "read-pkg-up": "^9.1.0",
        +        "remark": "^14.0.2",
        +        "remark-gfm": "^3.0.1",
        +        "remark-html": "^15.0.1",
        +        "remark-reference-links": "^6.0.1",
        +        "remark-toc": "^8.0.1",
        +        "resolve": "^1.22.1",
        +        "strip-json-comments": "^5.0.0",
        +        "unist-builder": "^3.0.0",
        +        "unist-util-visit": "^4.1.0",
        +        "vfile": "^5.3.4",
        +        "vfile-reporter": "^7.0.4",
        +        "vfile-sort": "^3.0.0",
        +        "yargs": "^17.5.1"
        +      },
        +      "bin": {
        +        "documentation": "bin/documentation.js"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=14"
        +      },
        +      "optionalDependencies": {
        +        "@vue/compiler-sfc": "^3.2.37",
        +        "vue-template-compiler": "^2.7.8"
               }
             },
        -    "node_modules/is-observable/node_modules/symbol-observable": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
        -      "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
        +    "node_modules/documentation/node_modules/ansi-regex": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/is-path-cwd": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-3.0.0.tgz",
        -      "integrity": "sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==",
        +    "node_modules/documentation/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
        +      "dependencies": {
        +        "color-convert": "^2.0.1"
        +      },
               "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +        "node": ">=8"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
        +      }
        +    },
        +    "node_modules/documentation/node_modules/argparse": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
        +      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
        +      "dev": true
        +    },
        +    "node_modules/documentation/node_modules/brace-expansion": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
        +      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
        +      "dev": true,
        +      "dependencies": {
        +        "balanced-match": "^1.0.0"
               }
             },
        -    "node_modules/is-path-inside": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz",
        -      "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==",
        +    "node_modules/documentation/node_modules/chalk": {
        +      "version": "5.4.1",
        +      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
        +      "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
               "dev": true,
               "engines": {
        -        "node": ">=12"
        +        "node": "^12.17.0 || ^14.13 || >=16.0.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/chalk/chalk?sponsor=1"
               }
             },
        -    "node_modules/is-plain-obj": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
        -      "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
        +    "node_modules/documentation/node_modules/cliui": {
        +      "version": "8.0.1",
        +      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
        +      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
               "dev": true,
        +      "dependencies": {
        +        "string-width": "^4.2.0",
        +        "strip-ansi": "^6.0.1",
        +        "wrap-ansi": "^7.0.0"
        +      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=12"
               }
             },
        -    "node_modules/is-plain-object": {
        -      "version": "2.0.4",
        -      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
        -      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
        +    "node_modules/documentation/node_modules/color-convert": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
               "dependencies": {
        -        "isobject": "^3.0.1"
        +        "color-name": "~1.1.4"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/is-promise": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
        -      "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
        -      "dev": true
        -    },
        -    "node_modules/is-regexp": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
        -      "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=",
        +    "node_modules/documentation/node_modules/diff": {
        +      "version": "5.2.0",
        +      "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
        +      "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=0.3.1"
               }
             },
        -    "node_modules/is-relative": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
        -      "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
        -      "dev": true,
        -      "dependencies": {
        -        "is-unc-path": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        +    "node_modules/documentation/node_modules/emoji-regex": {
        +      "version": "8.0.0",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        +      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        +      "dev": true
             },
        -    "node_modules/is-scoped": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-3.0.0.tgz",
        -      "integrity": "sha512-ezxLUq30kiTvP0w/5n9tj4qTOKlrA07Oty1hwTQ+lcqw11x6uc8sp7VRb2OVGRzKfCHZ2A22T5Zsau/Q2Akb0g==",
        +    "node_modules/documentation/node_modules/glob": {
        +      "version": "8.1.0",
        +      "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
        +      "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
        +      "deprecated": "Glob versions prior to v9 are no longer supported",
               "dev": true,
               "dependencies": {
        -        "scoped-regex": "^3.0.0"
        +        "fs.realpath": "^1.0.0",
        +        "inflight": "^1.0.4",
        +        "inherits": "2",
        +        "minimatch": "^5.0.1",
        +        "once": "^1.3.0"
               },
               "engines": {
                 "node": ">=12"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/is-stream": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
        -      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
        +    "node_modules/documentation/node_modules/ini": {
        +      "version": "3.0.1",
        +      "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz",
        +      "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
               }
             },
        -    "node_modules/is-typedarray": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
        -      "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
        -      "dev": true
        +    "node_modules/documentation/node_modules/is-fullwidth-code-point": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        +      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=8"
        +      }
             },
        -    "node_modules/is-unc-path": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
        -      "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
        +    "node_modules/documentation/node_modules/js-yaml": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
        +      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
               "dev": true,
               "dependencies": {
        -        "unc-path-regex": "^0.1.2"
        +        "argparse": "^2.0.1"
               },
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "bin": {
        +        "js-yaml": "bin/js-yaml.js"
               }
             },
        -    "node_modules/is-unicode-supported": {
        -      "version": "0.1.0",
        -      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
        -      "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
        +    "node_modules/documentation/node_modules/minimatch": {
        +      "version": "5.1.6",
        +      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
        +      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
               "dev": true,
        +      "dependencies": {
        +        "brace-expansion": "^2.0.1"
        +      },
               "engines": {
                 "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/is-url-superb": {
        +    "node_modules/documentation/node_modules/pify": {
               "version": "6.1.0",
        -      "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-6.1.0.tgz",
        -      "integrity": "sha512-LXdhGlYqUPdvEyIhWPEEwYYK3yrUiPcBjmFGlZNv1u5GtIL5qQRf7ddDyPNAvsMFqdzS923FROpTQU97tLe3JQ==",
        +      "resolved": "https://registry.npmjs.org/pify/-/pify-6.1.0.tgz",
        +      "integrity": "sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==",
               "dev": true,
               "engines": {
        -        "node": ">=12"
        +        "node": ">=14.16"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/is-windows": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
        -      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/is-wsl": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
        -      "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/is-yarn-global": {
        -      "version": "0.4.1",
        -      "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz",
        -      "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==",
        +    "node_modules/documentation/node_modules/string-width": {
        +      "version": "4.2.3",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
        +      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
               "dev": true,
        +      "dependencies": {
        +        "emoji-regex": "^8.0.0",
        +        "is-fullwidth-code-point": "^3.0.0",
        +        "strip-ansi": "^6.0.1"
        +      },
               "engines": {
        -        "node": ">=12"
        +        "node": ">=8"
               }
             },
        -    "node_modules/isarray": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
        -      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
        -      "dev": true
        -    },
        -    "node_modules/isexe": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
        -      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
        -      "dev": true
        -    },
        -    "node_modules/isobject": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
        -      "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
        +    "node_modules/documentation/node_modules/strip-ansi": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        +      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
               "dev": true,
        +      "dependencies": {
        +        "ansi-regex": "^5.0.1"
        +      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/issue-regex": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/issue-regex/-/issue-regex-4.1.0.tgz",
        -      "integrity": "sha512-X3HBmm7+Th+l4/kMtqwcHHgELD0Lfl0Ina6S3+grr+mKmTxsrM84NAO1UuRPIxIbGLIl3TCEu45S1kdu21HYbQ==",
        +    "node_modules/documentation/node_modules/strip-json-comments": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz",
        +      "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==",
               "dev": true,
               "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +        "node": ">=14.16"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/istanbul-lib-coverage": {
        -      "version": "2.0.5",
        -      "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
        -      "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/istanbul-lib-hook": {
        -      "version": "2.0.7",
        -      "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz",
        -      "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==",
        +    "node_modules/documentation/node_modules/wrap-ansi": {
        +      "version": "7.0.0",
        +      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
        +      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
               "dev": true,
               "dependencies": {
        -        "append-transform": "^1.0.0"
        +        "ansi-styles": "^4.0.0",
        +        "string-width": "^4.1.0",
        +        "strip-ansi": "^6.0.0"
               },
               "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/istanbul-lib-instrument": {
        -      "version": "3.3.0",
        -      "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz",
        -      "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==",
        -      "dev": true,
        -      "dependencies": {
        -        "@babel/generator": "^7.4.0",
        -        "@babel/parser": "^7.4.3",
        -        "@babel/template": "^7.4.0",
        -        "@babel/traverse": "^7.4.3",
        -        "@babel/types": "^7.4.0",
        -        "istanbul-lib-coverage": "^2.0.5",
        -        "semver": "^6.0.0"
        +        "node": ">=10"
               },
        -      "engines": {
        -        "node": ">=6"
        +      "funding": {
        +        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
               }
             },
        -    "node_modules/istanbul-lib-instrument/node_modules/semver": {
        -      "version": "6.1.1",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz",
        -      "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==",
        +    "node_modules/documentation/node_modules/y18n": {
        +      "version": "5.0.8",
        +      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
        +      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
               "dev": true,
        -      "bin": {
        -        "semver": "bin/semver"
        +      "engines": {
        +        "node": ">=10"
               }
             },
        -    "node_modules/istanbul-lib-report": {
        -      "version": "2.0.8",
        -      "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz",
        -      "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==",
        +    "node_modules/documentation/node_modules/yargs": {
        +      "version": "17.7.2",
        +      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
        +      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
               "dev": true,
               "dependencies": {
        -        "istanbul-lib-coverage": "^2.0.5",
        -        "make-dir": "^2.1.0",
        -        "supports-color": "^6.1.0"
        +        "cliui": "^8.0.1",
        +        "escalade": "^3.1.1",
        +        "get-caller-file": "^2.0.5",
        +        "require-directory": "^2.1.1",
        +        "string-width": "^4.2.3",
        +        "y18n": "^5.0.5",
        +        "yargs-parser": "^21.1.1"
               },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=12"
               }
             },
        -    "node_modules/istanbul-lib-report/node_modules/make-dir": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
        -      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
        +    "node_modules/documentation/node_modules/yargs-parser": {
        +      "version": "21.1.1",
        +      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
        +      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
               "dev": true,
        -      "dependencies": {
        -        "pify": "^4.0.1",
        -        "semver": "^5.6.0"
        -      },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=12"
               }
             },
        -    "node_modules/istanbul-lib-report/node_modules/semver": {
        -      "version": "5.7.1",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
        -      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
        -      "dev": true,
        -      "bin": {
        -        "semver": "bin/semver"
        -      }
        +    "node_modules/dom-accessibility-api": {
        +      "version": "0.5.16",
        +      "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
        +      "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
        +      "dev": true
             },
        -    "node_modules/istanbul-lib-report/node_modules/supports-color": {
        -      "version": "6.1.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
        -      "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
        +    "node_modules/dom-serializer": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
        +      "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
               "dev": true,
               "dependencies": {
        -        "has-flag": "^3.0.0"
        +        "domelementtype": "^2.3.0",
        +        "domhandler": "^5.0.2",
        +        "entities": "^4.2.0"
               },
        -      "engines": {
        -        "node": ">=6"
        +      "funding": {
        +        "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
               }
             },
        -    "node_modules/istanbul-lib-source-maps": {
        -      "version": "3.0.6",
        -      "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz",
        -      "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==",
        +    "node_modules/dom-serializer/node_modules/entities": {
        +      "version": "4.5.0",
        +      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
        +      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
               "dev": true,
        -      "dependencies": {
        -        "debug": "^4.1.1",
        -        "istanbul-lib-coverage": "^2.0.5",
        -        "make-dir": "^2.1.0",
        -        "rimraf": "^2.6.3",
        -        "source-map": "^0.6.1"
        -      },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=0.12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/fb55/entities?sponsor=1"
               }
             },
        -    "node_modules/istanbul-lib-source-maps/node_modules/debug": {
        -      "version": "4.1.1",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
        -      "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
        -      "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
        +    "node_modules/domelementtype": {
        +      "version": "2.3.0",
        +      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
        +      "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
               "dev": true,
        -      "dependencies": {
        -        "ms": "^2.1.1"
        -      }
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/fb55"
        +        }
        +      ]
             },
        -    "node_modules/istanbul-lib-source-maps/node_modules/make-dir": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
        -      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
        +    "node_modules/domhandler": {
        +      "version": "5.0.3",
        +      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
        +      "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
               "dev": true,
               "dependencies": {
        -        "pify": "^4.0.1",
        -        "semver": "^5.6.0"
        +        "domelementtype": "^2.3.0"
               },
               "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/istanbul-lib-source-maps/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        -      "dev": true
        -    },
        -    "node_modules/istanbul-lib-source-maps/node_modules/semver": {
        -      "version": "5.7.1",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
        -      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
        -      "dev": true,
        -      "bin": {
        -        "semver": "bin/semver"
        +        "node": ">= 4"
        +      },
        +      "funding": {
        +        "url": "https://github.com/fb55/domhandler?sponsor=1"
               }
             },
        -    "node_modules/istanbul-reports": {
        -      "version": "2.2.7",
        -      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz",
        -      "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==",
        +    "node_modules/domutils": {
        +      "version": "3.2.2",
        +      "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
        +      "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
               "dev": true,
               "dependencies": {
        -        "html-escaper": "^2.0.0"
        +        "dom-serializer": "^2.0.0",
        +        "domelementtype": "^2.3.0",
        +        "domhandler": "^5.0.3"
               },
        -      "engines": {
        -        "node": ">=6"
        +      "funding": {
        +        "url": "https://github.com/fb55/domutils?sponsor=1"
               }
             },
        -    "node_modules/jest-get-type": {
        -      "version": "21.2.0",
        -      "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-21.2.0.tgz",
        -      "integrity": "sha512-y2fFw3C+D0yjNSDp7ab1kcd6NUYfy3waPTlD8yWkAtiocJdBRQqNoRqVfMNxgj+IjT0V5cBIHJO0z9vuSSZ43Q==",
        +    "node_modules/eastasianwidth": {
        +      "version": "0.2.0",
        +      "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
        +      "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
               "dev": true
             },
        -    "node_modules/jest-validate": {
        -      "version": "21.2.1",
        -      "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-21.2.1.tgz",
        -      "integrity": "sha512-k4HLI1rZQjlU+EC682RlQ6oZvLrE5SCh3brseQc24vbZTxzT/k/3urar5QMCVgjadmSO7lECeGdc6YxnM3yEGg==",
        +    "node_modules/edge-paths": {
        +      "version": "3.0.5",
        +      "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz",
        +      "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^2.0.1",
        -        "jest-get-type": "^21.2.0",
        -        "leven": "^2.1.0",
        -        "pretty-format": "^21.2.1"
        +        "@types/which": "^2.0.1",
        +        "which": "^2.0.2"
        +      },
        +      "engines": {
        +        "node": ">=14.0.0"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/shirshak55"
               }
             },
        -    "node_modules/js-sdsl": {
        -      "version": "4.1.4",
        -      "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz",
        -      "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==",
        -      "dev": true
        -    },
        -    "node_modules/js-tokens": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
        -      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
        -      "dev": true
        -    },
        -    "node_modules/js-yaml": {
        -      "version": "3.13.1",
        -      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
        -      "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
        +    "node_modules/edge-paths/node_modules/which": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
        +      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
               "dev": true,
               "dependencies": {
        -        "argparse": "^1.0.7",
        -        "esprima": "^4.0.0"
        +        "isexe": "^2.0.0"
               },
               "bin": {
        -        "js-yaml": "bin/js-yaml.js"
        +        "node-which": "bin/node-which"
        +      },
        +      "engines": {
        +        "node": ">= 8"
               }
             },
        -    "node_modules/jsesc": {
        -      "version": "2.5.2",
        -      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
        -      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
        +    "node_modules/edgedriver": {
        +      "version": "6.1.1",
        +      "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-6.1.1.tgz",
        +      "integrity": "sha512-/dM/PoBf22Xg3yypMWkmRQrBKEnSyNaZ7wHGCT9+qqT14izwtFT+QvdR89rjNkMfXwW+bSFoqOfbcvM+2Cyc7w==",
               "dev": true,
        +      "hasInstallScript": true,
        +      "dependencies": {
        +        "@wdio/logger": "^9.1.3",
        +        "@zip.js/zip.js": "^2.7.53",
        +        "decamelize": "^6.0.0",
        +        "edge-paths": "^3.0.5",
        +        "fast-xml-parser": "^4.5.0",
        +        "http-proxy-agent": "^7.0.2",
        +        "https-proxy-agent": "^7.0.5",
        +        "node-fetch": "^3.3.2",
        +        "which": "^5.0.0"
        +      },
               "bin": {
        -        "jsesc": "bin/jsesc"
        +        "edgedriver": "bin/edgedriver.js"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=18.0.0"
               }
             },
        -    "node_modules/json-buffer": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
        -      "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
        -      "dev": true
        -    },
        -    "node_modules/json-fixer": {
        -      "version": "1.6.5",
        -      "resolved": "https://registry.npmjs.org/json-fixer/-/json-fixer-1.6.5.tgz",
        -      "integrity": "sha512-ewOhCI/b7Wx0DtO7ZhDp4SW5sjvp5dBWoeGnjta7mXPrvopvcE6TYGIqo+XREhzr/hKz7Bf3e2C0TSuoGFxAYA==",
        +    "node_modules/edgedriver/node_modules/agent-base": {
        +      "version": "7.1.3",
        +      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
        +      "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
               "dev": true,
        -      "dependencies": {
        -        "@babel/runtime": "^7.11.2",
        -        "chalk": "^4.1.0",
        -        "pegjs": "^0.10.0"
        -      },
               "engines": {
        -        "node": ">=10"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/json-fixer/node_modules/@babel/runtime": {
        -      "version": "7.11.2",
        -      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
        -      "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
        +    "node_modules/edgedriver/node_modules/data-uri-to-buffer": {
        +      "version": "4.0.1",
        +      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
        +      "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
               "dev": true,
        -      "dependencies": {
        -        "regenerator-runtime": "^0.13.4"
        +      "engines": {
        +        "node": ">= 12"
               }
             },
        -    "node_modules/json-fixer/node_modules/ansi-styles": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        -      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
        +    "node_modules/edgedriver/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
               "dependencies": {
        -        "color-convert": "^2.0.1"
        +        "ms": "^2.1.3"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=6.0"
               },
        -      "funding": {
        -        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/json-fixer/node_modules/chalk": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
        -      "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
        +    "node_modules/edgedriver/node_modules/decamelize": {
        +      "version": "6.0.0",
        +      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz",
        +      "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==",
               "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^4.1.0",
        -        "supports-color": "^7.1.0"
        -      },
               "engines": {
        -        "node": ">=10"
        +        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
               },
               "funding": {
        -        "url": "https://github.com/chalk/chalk?sponsor=1"
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/json-fixer/node_modules/color-convert": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        -      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
        +    "node_modules/edgedriver/node_modules/https-proxy-agent": {
        +      "version": "7.0.6",
        +      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
        +      "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
               "dev": true,
               "dependencies": {
        -        "color-name": "~1.1.4"
        +        "agent-base": "^7.1.2",
        +        "debug": "4"
               },
               "engines": {
        -        "node": ">=7.0.0"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/json-fixer/node_modules/has-flag": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        -      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        +    "node_modules/edgedriver/node_modules/isexe": {
        +      "version": "3.1.1",
        +      "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
        +      "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=16"
               }
             },
        -    "node_modules/json-fixer/node_modules/regenerator-runtime": {
        -      "version": "0.13.7",
        -      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
        -      "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
        +    "node_modules/edgedriver/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
               "dev": true
             },
        -    "node_modules/json-fixer/node_modules/supports-color": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        -      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        +    "node_modules/edgedriver/node_modules/node-fetch": {
        +      "version": "3.3.2",
        +      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
        +      "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
               "dev": true,
               "dependencies": {
        -        "has-flag": "^4.0.0"
        +        "data-uri-to-buffer": "^4.0.0",
        +        "fetch-blob": "^3.1.4",
        +        "formdata-polyfill": "^4.0.10"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/node-fetch"
               }
             },
        -    "node_modules/json-parse-better-errors": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
        -      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
        -      "dev": true
        +    "node_modules/edgedriver/node_modules/which": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz",
        +      "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==",
        +      "dev": true,
        +      "dependencies": {
        +        "isexe": "^3.1.1"
        +      },
        +      "bin": {
        +        "node-which": "bin/which.js"
        +      },
        +      "engines": {
        +        "node": "^18.17.0 || >=20.5.0"
        +      }
             },
        -    "node_modules/json-parse-even-better-errors": {
        -      "version": "2.3.1",
        -      "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
        -      "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
        +    "node_modules/electron-to-chromium": {
        +      "version": "1.5.84",
        +      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.84.tgz",
        +      "integrity": "sha512-I+DQ8xgafao9Ha6y0qjHHvpZ9OfyA1qKlkHkjywxzniORU2awxyz7f/iVJcULmrF2yrM3nHQf+iDjJtbbexd/g==",
               "dev": true
             },
        -    "node_modules/json-schema-traverse": {
        -      "version": "0.4.1",
        -      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
        -      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
        -      "dev": true
        +    "node_modules/encoding-sniffer": {
        +      "version": "0.2.0",
        +      "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
        +      "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
        +      "dev": true,
        +      "dependencies": {
        +        "iconv-lite": "^0.6.3",
        +        "whatwg-encoding": "^3.1.1"
        +      },
        +      "funding": {
        +        "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
        +      }
             },
        -    "node_modules/json-stable-stringify": {
        -      "version": "0.0.1",
        -      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz",
        -      "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=",
        +    "node_modules/encoding-sniffer/node_modules/iconv-lite": {
        +      "version": "0.6.3",
        +      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
        +      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
               "dev": true,
               "dependencies": {
        -        "jsonify": "~0.0.0"
        +        "safer-buffer": ">= 2.1.2 < 3.0.0"
        +      },
        +      "engines": {
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/json-stable-stringify-without-jsonify": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
        -      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
        -      "dev": true
        +    "node_modules/end-of-stream": {
        +      "version": "1.4.1",
        +      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
        +      "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
        +      "dev": true,
        +      "dependencies": {
        +        "once": "^1.4.0"
        +      }
             },
        -    "node_modules/json-stringify-safe": {
        -      "version": "5.0.1",
        -      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
        -      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
        +    "node_modules/environment": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
        +      "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=18"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
        +      }
        +    },
        +    "node_modules/error-ex": {
        +      "version": "1.3.1",
        +      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
        +      "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
        +      "dev": true,
        +      "dependencies": {
        +        "is-arrayish": "^0.2.1"
        +      }
        +    },
        +    "node_modules/es-module-lexer": {
        +      "version": "1.6.0",
        +      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
        +      "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
               "dev": true
             },
        -    "node_modules/json5": {
        -      "version": "2.2.3",
        -      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
        -      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
        +    "node_modules/esbuild": {
        +      "version": "0.21.5",
        +      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
        +      "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
               "dev": true,
        +      "hasInstallScript": true,
               "bin": {
        -        "json5": "lib/cli.js"
        +        "esbuild": "bin/esbuild"
               },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=12"
        +      },
        +      "optionalDependencies": {
        +        "@esbuild/aix-ppc64": "0.21.5",
        +        "@esbuild/android-arm": "0.21.5",
        +        "@esbuild/android-arm64": "0.21.5",
        +        "@esbuild/android-x64": "0.21.5",
        +        "@esbuild/darwin-arm64": "0.21.5",
        +        "@esbuild/darwin-x64": "0.21.5",
        +        "@esbuild/freebsd-arm64": "0.21.5",
        +        "@esbuild/freebsd-x64": "0.21.5",
        +        "@esbuild/linux-arm": "0.21.5",
        +        "@esbuild/linux-arm64": "0.21.5",
        +        "@esbuild/linux-ia32": "0.21.5",
        +        "@esbuild/linux-loong64": "0.21.5",
        +        "@esbuild/linux-mips64el": "0.21.5",
        +        "@esbuild/linux-ppc64": "0.21.5",
        +        "@esbuild/linux-riscv64": "0.21.5",
        +        "@esbuild/linux-s390x": "0.21.5",
        +        "@esbuild/linux-x64": "0.21.5",
        +        "@esbuild/netbsd-x64": "0.21.5",
        +        "@esbuild/openbsd-x64": "0.21.5",
        +        "@esbuild/sunos-x64": "0.21.5",
        +        "@esbuild/win32-arm64": "0.21.5",
        +        "@esbuild/win32-ia32": "0.21.5",
        +        "@esbuild/win32-x64": "0.21.5"
               }
             },
        -    "node_modules/jsonify": {
        -      "version": "0.0.0",
        -      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
        -      "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
        +    "node_modules/escalade": {
        +      "version": "3.2.0",
        +      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
        +      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
               "dev": true,
               "engines": {
        -        "node": "*"
        +        "node": ">=6"
               }
             },
        -    "node_modules/jsonparse": {
        -      "version": "1.3.1",
        -      "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
        -      "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
        +    "node_modules/escape-string-regexp": {
        +      "version": "1.0.5",
        +      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
        +      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
               "dev": true,
        -      "engines": [
        -        "node >= 0.2.0"
        -      ]
        +      "engines": {
        +        "node": ">=0.8.0"
        +      }
             },
        -    "node_modules/JSONStream": {
        -      "version": "1.3.5",
        -      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
        -      "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
        +    "node_modules/escodegen": {
        +      "version": "2.1.0",
        +      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
        +      "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
               "dev": true,
               "dependencies": {
        -        "jsonparse": "^1.2.0",
        -        "through": ">=2.2.7 <3"
        +        "esprima": "^4.0.1",
        +        "estraverse": "^5.2.0",
        +        "esutils": "^2.0.2"
               },
               "bin": {
        -        "JSONStream": "bin.js"
        +        "escodegen": "bin/escodegen.js",
        +        "esgenerate": "bin/esgenerate.js"
               },
               "engines": {
        -        "node": "*"
        -      }
        -    },
        -    "node_modules/key-list": {
        -      "version": "0.1.4",
        -      "resolved": "https://registry.npmjs.org/key-list/-/key-list-0.1.4.tgz",
        -      "integrity": "sha512-DMGLZAmEoKRUHPlc772EW0i92P/WY12/oWYc2pQZb5MVGOSjYmF0BEQXbOLjbou1+/PqZ+CivwfyjaUwmyl4CQ==",
        -      "dev": true
        -    },
        -    "node_modules/keyv": {
        -      "version": "4.5.2",
        -      "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz",
        -      "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==",
        -      "dev": true,
        -      "dependencies": {
        -        "json-buffer": "3.0.1"
        +        "node": ">=6.0"
        +      },
        +      "optionalDependencies": {
        +        "source-map": "~0.6.1"
               }
             },
        -    "node_modules/kind-of": {
        -      "version": "6.0.3",
        -      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
        -      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
        +    "node_modules/escodegen/node_modules/estraverse": {
        +      "version": "5.3.0",
        +      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
        +      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=4.0"
               }
             },
        -    "node_modules/labeled-stream-splicer": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz",
        -      "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==",
        +    "node_modules/eslint": {
        +      "version": "8.57.1",
        +      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
        +      "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
        +      "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
               "dev": true,
               "dependencies": {
        -        "inherits": "^2.0.1",
        -        "stream-splicer": "^2.0.0"
        +        "@eslint-community/eslint-utils": "^4.2.0",
        +        "@eslint-community/regexpp": "^4.6.1",
        +        "@eslint/eslintrc": "^2.1.4",
        +        "@eslint/js": "8.57.1",
        +        "@humanwhocodes/config-array": "^0.13.0",
        +        "@humanwhocodes/module-importer": "^1.0.1",
        +        "@nodelib/fs.walk": "^1.2.8",
        +        "@ungap/structured-clone": "^1.2.0",
        +        "ajv": "^6.12.4",
        +        "chalk": "^4.0.0",
        +        "cross-spawn": "^7.0.2",
        +        "debug": "^4.3.2",
        +        "doctrine": "^3.0.0",
        +        "escape-string-regexp": "^4.0.0",
        +        "eslint-scope": "^7.2.2",
        +        "eslint-visitor-keys": "^3.4.3",
        +        "espree": "^9.6.1",
        +        "esquery": "^1.4.2",
        +        "esutils": "^2.0.2",
        +        "fast-deep-equal": "^3.1.3",
        +        "file-entry-cache": "^6.0.1",
        +        "find-up": "^5.0.0",
        +        "glob-parent": "^6.0.2",
        +        "globals": "^13.19.0",
        +        "graphemer": "^1.4.0",
        +        "ignore": "^5.2.0",
        +        "imurmurhash": "^0.1.4",
        +        "is-glob": "^4.0.0",
        +        "is-path-inside": "^3.0.3",
        +        "js-yaml": "^4.1.0",
        +        "json-stable-stringify-without-jsonify": "^1.0.1",
        +        "levn": "^0.4.1",
        +        "lodash.merge": "^4.6.2",
        +        "minimatch": "^3.1.2",
        +        "natural-compare": "^1.4.0",
        +        "optionator": "^0.9.3",
        +        "strip-ansi": "^6.0.1",
        +        "text-table": "^0.2.0"
        +      },
        +      "bin": {
        +        "eslint": "bin/eslint.js"
        +      },
        +      "engines": {
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        +      },
        +      "funding": {
        +        "url": "https://opencollective.com/eslint"
               }
             },
        -    "node_modules/latest-version": {
        -      "version": "7.0.0",
        -      "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz",
        -      "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==",
        +    "node_modules/eslint-scope": {
        +      "version": "7.2.2",
        +      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
        +      "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
               "dev": true,
               "dependencies": {
        -        "package-json": "^8.1.0"
        +        "esrecurse": "^4.3.0",
        +        "estraverse": "^5.2.0"
               },
               "engines": {
        -        "node": ">=14.16"
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://opencollective.com/eslint"
               }
             },
        -    "node_modules/leven": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
        -      "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=",
        +    "node_modules/eslint-scope/node_modules/estraverse": {
        +      "version": "5.3.0",
        +      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
        +      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=4.0"
               }
             },
        -    "node_modules/levenary": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz",
        -      "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==",
        +    "node_modules/eslint-visitor-keys": {
        +      "version": "3.4.3",
        +      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
        +      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
               "dev": true,
        -      "dependencies": {
        -        "leven": "^3.1.0"
        -      },
               "engines": {
        -        "node": ">= 6"
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        +      },
        +      "funding": {
        +        "url": "https://opencollective.com/eslint"
               }
             },
        -    "node_modules/levenary/node_modules/leven": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
        -      "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
        +    "node_modules/eslint/node_modules/ansi-regex": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
               "dev": true,
               "engines": {
        -        "node": ">=6"
        +        "node": ">=8"
               }
             },
        -    "node_modules/levn": {
        -      "version": "0.4.1",
        -      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
        -      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
        +    "node_modules/eslint/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
               "dependencies": {
        -        "prelude-ls": "^1.2.1",
        -        "type-check": "~0.4.0"
        +        "color-convert": "^2.0.1"
               },
               "engines": {
        -        "node": ">= 0.8.0"
        +        "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/libtess": {
        -      "version": "1.2.2",
        -      "resolved": "https://registry.npmjs.org/libtess/-/libtess-1.2.2.tgz",
        -      "integrity": "sha1-Fz61KhpXoCOP5I8F0SJFB0SX+Jg=",
        +    "node_modules/eslint/node_modules/argparse": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
        +      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
               "dev": true
             },
        -    "node_modules/liftup": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz",
        -      "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==",
        +    "node_modules/eslint/node_modules/chalk": {
        +      "version": "4.1.2",
        +      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        +      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
               "dev": true,
               "dependencies": {
        -        "extend": "^3.0.2",
        -        "findup-sync": "^4.0.0",
        -        "fined": "^1.2.0",
        -        "flagged-respawn": "^1.0.1",
        -        "is-plain-object": "^2.0.4",
        -        "object.map": "^1.0.1",
        -        "rechoir": "^0.7.0",
        -        "resolve": "^1.19.0"
        +        "ansi-styles": "^4.1.0",
        +        "supports-color": "^7.1.0"
               },
               "engines": {
                 "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/chalk?sponsor=1"
               }
             },
        -    "node_modules/liftup/node_modules/findup-sync": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz",
        -      "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==",
        +    "node_modules/eslint/node_modules/color-convert": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
               "dependencies": {
        -        "detect-file": "^1.0.0",
        -        "is-glob": "^4.0.0",
        -        "micromatch": "^4.0.2",
        -        "resolve-dir": "^1.0.1"
        +        "color-name": "~1.1.4"
               },
               "engines": {
        -        "node": ">= 8"
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/liftup/node_modules/rechoir": {
        -      "version": "0.7.1",
        -      "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
        -      "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
        +    "node_modules/eslint/node_modules/debug": {
        +      "version": "4.3.4",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
        +      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
               "dev": true,
               "dependencies": {
        -        "resolve": "^1.9.0"
        +        "ms": "2.1.2"
               },
               "engines": {
        -        "node": ">= 0.10"
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/liftup/node_modules/resolve": {
        -      "version": "1.22.0",
        -      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
        -      "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
        +    "node_modules/eslint/node_modules/escape-string-regexp": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
        +      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
               "dev": true,
        -      "dependencies": {
        -        "is-core-module": "^2.8.1",
        -        "path-parse": "^1.0.7",
        -        "supports-preserve-symlinks-flag": "^1.0.0"
        -      },
        -      "bin": {
        -        "resolve": "bin/resolve"
        +      "engines": {
        +        "node": ">=10"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        -      }
        -    },
        -    "node_modules/lines-and-columns": {
        -      "version": "1.1.6",
        -      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
        -      "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
        -      "dev": true
        -    },
        -    "node_modules/linkify-it": {
        -      "version": "1.2.4",
        -      "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-1.2.4.tgz",
        -      "integrity": "sha1-B3NSbDF8j9E71TTuHRgP+Iq/iBo=",
        -      "dev": true,
        -      "dependencies": {
        -        "uc.micro": "^1.0.1"
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/lint-staged": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-4.3.0.tgz",
        -      "integrity": "sha512-C/Zxslg0VRbsxwmCu977iIs+QyrmW2cyRCPUV5NDFYOH/jtRFHH8ch7ua2fH0voI/nVC3Tpg7DykfgMZySliKw==",
        +    "node_modules/eslint/node_modules/find-up": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
        +      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
               "dev": true,
               "dependencies": {
        -        "app-root-path": "^2.0.0",
        -        "chalk": "^2.1.0",
        -        "commander": "^2.11.0",
        -        "cosmiconfig": "^1.1.0",
        -        "execa": "^0.8.0",
        -        "is-glob": "^4.0.0",
        -        "jest-validate": "^21.1.0",
        -        "listr": "^0.12.0",
        -        "lodash": "^4.17.4",
        -        "log-symbols": "^2.0.0",
        -        "minimatch": "^3.0.0",
        -        "npm-which": "^3.0.1",
        -        "p-map": "^1.1.1",
        -        "staged-git-files": "0.0.4",
        -        "stringify-object": "^3.2.0"
        -      },
        -      "bin": {
        -        "lint-staged": "index.js"
        +        "locate-path": "^6.0.0",
        +        "path-exists": "^4.0.0"
               },
               "engines": {
        -        "node": ">=4.2.0"
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/lint-staged/node_modules/commander": {
        -      "version": "2.13.0",
        -      "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
        -      "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==",
        -      "dev": true
        -    },
        -    "node_modules/lint-staged/node_modules/execa": {
        -      "version": "0.8.0",
        -      "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz",
        -      "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=",
        +    "node_modules/eslint/node_modules/globals": {
        +      "version": "13.24.0",
        +      "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
        +      "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
               "dev": true,
               "dependencies": {
        -        "cross-spawn": "^5.0.1",
        -        "get-stream": "^3.0.0",
        -        "is-stream": "^1.1.0",
        -        "npm-run-path": "^2.0.0",
        -        "p-finally": "^1.0.0",
        -        "signal-exit": "^3.0.0",
        -        "strip-eof": "^1.0.0"
        +        "type-fest": "^0.20.2"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/listr": {
        -      "version": "0.12.0",
        -      "resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz",
        -      "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=",
        +    "node_modules/eslint/node_modules/has-flag": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        +      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
               "dev": true,
        -      "dependencies": {
        -        "chalk": "^1.1.3",
        -        "cli-truncate": "^0.2.1",
        -        "figures": "^1.7.0",
        -        "indent-string": "^2.1.0",
        -        "is-promise": "^2.1.0",
        -        "is-stream": "^1.1.0",
        -        "listr-silent-renderer": "^1.1.1",
        -        "listr-update-renderer": "^0.2.0",
        -        "listr-verbose-renderer": "^0.4.0",
        -        "log-symbols": "^1.0.2",
        -        "log-update": "^1.0.2",
        -        "ora": "^0.2.3",
        -        "p-map": "^1.1.1",
        -        "rxjs": "^5.0.0-beta.11",
        -        "stream-to-observable": "^0.1.0",
        -        "strip-ansi": "^3.0.1"
        -      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
               }
             },
        -    "node_modules/listr-input": {
        -      "version": "0.2.1",
        -      "resolved": "https://registry.npmjs.org/listr-input/-/listr-input-0.2.1.tgz",
        -      "integrity": "sha512-oa8iVG870qJq+OuuMK3DjGqFcwsK1SDu+kULp9kEq09TY231aideIZenr3lFOQdASpAr6asuyJBbX62/a3IIhg==",
        +    "node_modules/eslint/node_modules/is-path-inside": {
        +      "version": "3.0.3",
        +      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
        +      "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
               "dev": true,
        -      "dependencies": {
        -        "inquirer": "^7.0.0",
        -        "inquirer-autosubmit-prompt": "^0.2.0",
        -        "rxjs": "^6.5.3",
        -        "through": "^2.3.8"
        -      },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=8"
               }
             },
        -    "node_modules/listr-input/node_modules/rxjs": {
        -      "version": "6.6.7",
        -      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
        -      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
        +    "node_modules/eslint/node_modules/js-yaml": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
        +      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
               "dev": true,
               "dependencies": {
        -        "tslib": "^1.9.0"
        +        "argparse": "^2.0.1"
               },
        -      "engines": {
        -        "npm": ">=2.0.0"
        +      "bin": {
        +        "js-yaml": "bin/js-yaml.js"
               }
             },
        -    "node_modules/listr-silent-renderer": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz",
        -      "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=",
        +    "node_modules/eslint/node_modules/locate-path": {
        +      "version": "6.0.0",
        +      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
        +      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
               "dev": true,
        +      "dependencies": {
        +        "p-locate": "^5.0.0"
        +      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/listr-update-renderer": {
        -      "version": "0.2.0",
        -      "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz",
        -      "integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=",
        +    "node_modules/eslint/node_modules/minimatch": {
        +      "version": "3.1.2",
        +      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
        +      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^1.1.3",
        -        "cli-truncate": "^0.2.1",
        -        "elegant-spinner": "^1.0.1",
        -        "figures": "^1.7.0",
        -        "indent-string": "^3.0.0",
        -        "log-symbols": "^1.0.2",
        -        "log-update": "^1.0.2",
        -        "strip-ansi": "^3.0.1"
        +        "brace-expansion": "^1.1.7"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": "*"
               }
             },
        -    "node_modules/listr-update-renderer/node_modules/ansi-styles": {
        -      "version": "2.2.1",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
        -      "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
        +    "node_modules/eslint/node_modules/ms": {
        +      "version": "2.1.2",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        +      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        +      "dev": true
        +    },
        +    "node_modules/eslint/node_modules/p-limit": {
        +      "version": "3.1.0",
        +      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
        +      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
               "dev": true,
        +      "dependencies": {
        +        "yocto-queue": "^0.1.0"
        +      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/listr-update-renderer/node_modules/chalk": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
        -      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
        +    "node_modules/eslint/node_modules/p-locate": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
        +      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
               "dev": true,
               "dependencies": {
        -        "ansi-styles": "^2.2.1",
        -        "escape-string-regexp": "^1.0.2",
        -        "has-ansi": "^2.0.0",
        -        "strip-ansi": "^3.0.0",
        -        "supports-color": "^2.0.0"
        +        "p-limit": "^3.0.2"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/listr-update-renderer/node_modules/indent-string": {
        -      "version": "3.2.0",
        -      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
        -      "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=",
        +    "node_modules/eslint/node_modules/strip-ansi": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        +      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
               "dev": true,
        +      "dependencies": {
        +        "ansi-regex": "^5.0.1"
        +      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
               }
             },
        -    "node_modules/listr-update-renderer/node_modules/log-symbols": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
        -      "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=",
        +    "node_modules/eslint/node_modules/supports-color": {
        +      "version": "7.2.0",
        +      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        +      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^1.0.0"
        +        "has-flag": "^4.0.0"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/listr-update-renderer/node_modules/supports-color": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
        -      "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
        +    "node_modules/eslint/node_modules/type-fest": {
        +      "version": "0.20.2",
        +      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
        +      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
               "dev": true,
               "engines": {
        -        "node": ">=0.8.0"
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/listr-verbose-renderer": {
        -      "version": "0.4.1",
        -      "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz",
        -      "integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=",
        +    "node_modules/espree": {
        +      "version": "9.6.1",
        +      "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
        +      "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^1.1.3",
        -        "cli-cursor": "^1.0.2",
        -        "date-fns": "^1.27.2",
        -        "figures": "^1.7.0"
        +        "acorn": "^8.9.0",
        +        "acorn-jsx": "^5.3.2",
        +        "eslint-visitor-keys": "^3.4.1"
               },
               "engines": {
        -        "node": ">=4"
        -      }
        -    },
        -    "node_modules/listr-verbose-renderer/node_modules/ansi-styles": {
        -      "version": "2.2.1",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
        -      "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        +      },
        +      "funding": {
        +        "url": "https://opencollective.com/eslint"
               }
             },
        -    "node_modules/listr-verbose-renderer/node_modules/chalk": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
        -      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
        +    "node_modules/esprima": {
        +      "version": "4.0.1",
        +      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
        +      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
               "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^2.2.1",
        -        "escape-string-regexp": "^1.0.2",
        -        "has-ansi": "^2.0.0",
        -        "strip-ansi": "^3.0.0",
        -        "supports-color": "^2.0.0"
        +      "bin": {
        +        "esparse": "bin/esparse.js",
        +        "esvalidate": "bin/esvalidate.js"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=4"
               }
             },
        -    "node_modules/listr-verbose-renderer/node_modules/cli-cursor": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
        -      "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
        +    "node_modules/esquery": {
        +      "version": "1.6.0",
        +      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
        +      "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
               "dev": true,
               "dependencies": {
        -        "restore-cursor": "^1.0.1"
        +        "estraverse": "^5.1.0"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=0.10"
               }
             },
        -    "node_modules/listr-verbose-renderer/node_modules/onetime": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
        -      "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
        +    "node_modules/esquery/node_modules/estraverse": {
        +      "version": "5.3.0",
        +      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
        +      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=4.0"
               }
             },
        -    "node_modules/listr-verbose-renderer/node_modules/restore-cursor": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
        -      "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
        +    "node_modules/esrecurse": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
        +      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
               "dev": true,
               "dependencies": {
        -        "exit-hook": "^1.0.0",
        -        "onetime": "^1.0.0"
        +        "estraverse": "^5.2.0"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=4.0"
               }
             },
        -    "node_modules/listr-verbose-renderer/node_modules/supports-color": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
        -      "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
        +    "node_modules/esrecurse/node_modules/estraverse": {
        +      "version": "5.3.0",
        +      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
        +      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
               "dev": true,
               "engines": {
        -        "node": ">=0.8.0"
        +        "node": ">=4.0"
               }
             },
        -    "node_modules/listr/node_modules/ansi-styles": {
        -      "version": "2.2.1",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
        -      "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        +    "node_modules/estree-walker": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
        +      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
        +      "dev": true
             },
        -    "node_modules/listr/node_modules/chalk": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
        -      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
        +    "node_modules/esutils": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
        +      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
               "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^2.2.1",
        -        "escape-string-regexp": "^1.0.2",
        -        "has-ansi": "^2.0.0",
        -        "strip-ansi": "^3.0.0",
        -        "supports-color": "^2.0.0"
        -      },
               "engines": {
                 "node": ">=0.10.0"
               }
             },
        -    "node_modules/listr/node_modules/log-symbols": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
        -      "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=",
        +    "node_modules/event-target-shim": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
        +      "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
               "dev": true,
        -      "dependencies": {
        -        "chalk": "^1.0.0"
        -      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=6"
               }
             },
        -    "node_modules/listr/node_modules/supports-color": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
        -      "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
        +    "node_modules/eventemitter3": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
        +      "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
        +      "dev": true
        +    },
        +    "node_modules/expect-type": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz",
        +      "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==",
               "dev": true,
               "engines": {
        -        "node": ">=0.8.0"
        +        "node": ">=12.0.0"
               }
             },
        -    "node_modules/livereload-js": {
        -      "version": "2.3.0",
        -      "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz",
        -      "integrity": "sha512-j1R0/FeGa64Y+NmqfZhyoVRzcFlOZ8sNlKzHjh4VvLULFACZhn68XrX5DFg2FhMvSMJmROuFxRSa560ECWKBMg==",
        +    "node_modules/extend": {
        +      "version": "3.0.2",
        +      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
        +      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
               "dev": true
             },
        -    "node_modules/locate-path": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
        -      "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
        +    "node_modules/external-editor": {
        +      "version": "3.1.0",
        +      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
        +      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
               "dev": true,
               "dependencies": {
        -        "p-locate": "^3.0.0",
        -        "path-exists": "^3.0.0"
        +        "chardet": "^0.7.0",
        +        "iconv-lite": "^0.4.24",
        +        "tmp": "^0.0.33"
               },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=4"
               }
             },
        -    "node_modules/locate-path/node_modules/path-exists": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
        -      "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
        +    "node_modules/extract-zip": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
        +      "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
               "dev": true,
        +      "dependencies": {
        +        "debug": "^4.1.1",
        +        "get-stream": "^5.1.0",
        +        "yauzl": "^2.10.0"
        +      },
        +      "bin": {
        +        "extract-zip": "cli.js"
        +      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">= 10.17.0"
        +      },
        +      "optionalDependencies": {
        +        "@types/yauzl": "^2.9.1"
               }
             },
        -    "node_modules/lodash": {
        -      "version": "4.17.21",
        -      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
        -      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
        -      "dev": true
        -    },
        -    "node_modules/lodash.flattendeep": {
        +    "node_modules/extract-zip/node_modules/debug": {
               "version": "4.4.0",
        -      "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
        -      "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
        -      "dev": true
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
        +      "dev": true,
        +      "dependencies": {
        +        "ms": "^2.1.3"
        +      },
        +      "engines": {
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
        +      }
             },
        -    "node_modules/lodash.isequal": {
        -      "version": "4.5.0",
        -      "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
        -      "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
        +    "node_modules/extract-zip/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
               "dev": true
             },
        -    "node_modules/lodash.isfinite": {
        -      "version": "3.3.2",
        -      "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz",
        -      "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==",
        +    "node_modules/fast-deep-equal": {
        +      "version": "3.1.3",
        +      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
        +      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
               "dev": true
             },
        -    "node_modules/lodash.memoize": {
        -      "version": "3.0.4",
        -      "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
        -      "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=",
        +    "node_modules/fast-fifo": {
        +      "version": "1.3.2",
        +      "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
        +      "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
               "dev": true
             },
        -    "node_modules/lodash.merge": {
        -      "version": "4.6.2",
        -      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
        -      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
        +    "node_modules/fast-json-stable-stringify": {
        +      "version": "2.1.0",
        +      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
        +      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
               "dev": true
             },
        -    "node_modules/lodash.zip": {
        -      "version": "4.2.0",
        -      "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz",
        -      "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==",
        +    "node_modules/fast-levenshtein": {
        +      "version": "2.0.6",
        +      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
        +      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
               "dev": true
             },
        -    "node_modules/log-symbols": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.1.0.tgz",
        -      "integrity": "sha512-zLeLrzMA1A2vRF1e/0Mo+LNINzi6jzBylHj5WqvQ/WK/5WCZt8si9SyN4p9llr/HRYvVR1AoXHRHl4WTHyQAzQ==",
        +    "node_modules/fast-xml-parser": {
        +      "version": "4.5.1",
        +      "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.1.tgz",
        +      "integrity": "sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/NaturalIntelligence"
        +        },
        +        {
        +          "type": "paypal",
        +          "url": "https://paypal.me/naturalintelligence"
        +        }
        +      ],
               "dependencies": {
        -        "chalk": "^2.0.1"
        +        "strnum": "^1.0.5"
               },
        -      "engines": {
        -        "node": ">=4"
        +      "bin": {
        +        "fxparser": "src/cli/cli.js"
               }
             },
        -    "node_modules/log-update": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz",
        -      "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=",
        +    "node_modules/fastq": {
        +      "version": "1.13.0",
        +      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
        +      "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
               "dev": true,
               "dependencies": {
        -        "ansi-escapes": "^1.0.0",
        -        "cli-cursor": "^1.0.2"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        +        "reusify": "^1.0.4"
               }
             },
        -    "node_modules/log-update/node_modules/ansi-escapes": {
        -      "version": "1.4.0",
        -      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
        -      "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
        +    "node_modules/fd-slicer": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
        +      "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "dependencies": {
        +        "pend": "~1.2.0"
               }
             },
        -    "node_modules/log-update/node_modules/cli-cursor": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
        -      "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
        +    "node_modules/fetch-blob": {
        +      "version": "3.2.0",
        +      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
        +      "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/jimmywarting"
        +        },
        +        {
        +          "type": "paypal",
        +          "url": "https://paypal.me/jimmywarting"
        +        }
        +      ],
               "dependencies": {
        -        "restore-cursor": "^1.0.1"
        +        "node-domexception": "^1.0.0",
        +        "web-streams-polyfill": "^3.0.3"
               },
               "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/log-update/node_modules/onetime": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
        -      "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        +        "node": "^12.20 || >= 14.13"
               }
             },
        -    "node_modules/log-update/node_modules/restore-cursor": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
        -      "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
        +    "node_modules/file-entry-cache": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
        +      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
               "dev": true,
               "dependencies": {
        -        "exit-hook": "^1.0.0",
        -        "onetime": "^1.0.0"
        +        "flat-cache": "^3.0.4"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": "^10.12.0 || >=12.0.0"
               }
             },
        -    "node_modules/loose-envify": {
        -      "version": "1.4.0",
        -      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
        -      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
        +    "node_modules/file-saver": {
        +      "version": "1.3.8",
        +      "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz",
        +      "integrity": "sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg=="
        +    },
        +    "node_modules/fill-range": {
        +      "version": "7.1.1",
        +      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
        +      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
               "dev": true,
               "dependencies": {
        -        "js-tokens": "^3.0.0 || ^4.0.0"
        +        "to-regex-range": "^5.0.1"
               },
        -      "bin": {
        -        "loose-envify": "cli.js"
        -      }
        -    },
        -    "node_modules/lowercase-keys": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
        -      "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
        -      "dev": true,
               "engines": {
                 "node": ">=8"
               }
             },
        -    "node_modules/lru-cache": {
        -      "version": "4.1.5",
        -      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
        -      "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
        +    "node_modules/find-up": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
        +      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
               "dev": true,
               "dependencies": {
        -        "pseudomap": "^1.0.2",
        -        "yallist": "^2.1.2"
        +        "locate-path": "^5.0.0",
        +        "path-exists": "^4.0.0"
        +      },
        +      "engines": {
        +        "node": ">=8"
               }
             },
        -    "node_modules/make-iterator": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz",
        -      "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==",
        +    "node_modules/find-up/node_modules/locate-path": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
        +      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
               "dev": true,
               "dependencies": {
        -        "kind-of": "^6.0.2"
        +        "p-locate": "^4.1.0"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/map-cache": {
        -      "version": "0.2.2",
        -      "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
        -      "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
        +    "node_modules/find-up/node_modules/p-locate": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
        +      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
               "dev": true,
        +      "dependencies": {
        +        "p-limit": "^2.2.0"
        +      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/map-obj": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
        -      "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
        +    "node_modules/find-versions": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz",
        +      "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==",
               "dev": true,
        +      "dependencies": {
        +        "semver-regex": "^3.1.2"
        +      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=10"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/markdown-it": {
        -      "version": "4.4.0",
        -      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-4.4.0.tgz",
        -      "integrity": "sha1-PfNz2+pYepp/7z5WMRtokI91xBQ=",
        +    "node_modules/flat-cache": {
        +      "version": "3.0.4",
        +      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
        +      "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
               "dev": true,
               "dependencies": {
        -        "argparse": "~1.0.2",
        -        "entities": "~1.1.1",
        -        "linkify-it": "~1.2.0",
        -        "mdurl": "~1.0.0",
        -        "uc.micro": "^1.0.0"
        +        "flatted": "^3.1.0",
        +        "rimraf": "^3.0.2"
               },
        -      "bin": {
        -        "markdown-it": "bin/markdown-it.js"
        +      "engines": {
        +        "node": "^10.12.0 || >=12.0.0"
               }
             },
        -    "node_modules/marked": {
        -      "version": "4.0.10",
        -      "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz",
        -      "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==",
        +    "node_modules/flat-cache/node_modules/rimraf": {
        +      "version": "3.0.2",
        +      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
        +      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
               "dev": true,
        +      "dependencies": {
        +        "glob": "^7.1.3"
        +      },
               "bin": {
        -        "marked": "bin/marked.js"
        +        "rimraf": "bin.js"
               },
        -      "engines": {
        -        "node": ">= 12"
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/maxmin": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-2.1.0.tgz",
        -      "integrity": "sha1-TTsiCQPZXu5+t6x/qGTnLcCaMWY=",
        +    "node_modules/flatted": {
        +      "version": "3.2.7",
        +      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
        +      "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
        +      "dev": true
        +    },
        +    "node_modules/formdata-polyfill": {
        +      "version": "4.0.10",
        +      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
        +      "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^1.0.0",
        -        "figures": "^1.0.1",
        -        "gzip-size": "^3.0.0",
        -        "pretty-bytes": "^3.0.0"
        +        "fetch-blob": "^3.1.2"
               },
               "engines": {
        -        "node": ">=0.12"
        +        "node": ">=12.20.0"
               }
             },
        -    "node_modules/maxmin/node_modules/ansi-styles": {
        -      "version": "2.2.1",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
        -      "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
        +    "node_modules/fs.realpath": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
        +      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
        +      "dev": true
        +    },
        +    "node_modules/fsevents": {
        +      "version": "2.3.3",
        +      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
        +      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
               "dev": true,
        +      "hasInstallScript": true,
        +      "optional": true,
        +      "os": [
        +        "darwin"
        +      ],
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
               }
             },
        -    "node_modules/maxmin/node_modules/chalk": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
        -      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
        +    "node_modules/function-bind": {
        +      "version": "1.1.2",
        +      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
        +      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
               "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^2.2.1",
        -        "escape-string-regexp": "^1.0.2",
        -        "has-ansi": "^2.0.0",
        -        "strip-ansi": "^3.0.0",
        -        "supports-color": "^2.0.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "funding": {
        +        "url": "https://github.com/sponsors/ljharb"
               }
             },
        -    "node_modules/maxmin/node_modules/pretty-bytes": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz",
        -      "integrity": "sha1-J9AAjXeAY6C0gRuzXHnxvV1fvM8=",
        +    "node_modules/geckodriver": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-5.0.0.tgz",
        +      "integrity": "sha512-vn7TtQ3b9VMJtVXsyWtQQl1fyBVFhQy7UvJF96kPuuJ0or5THH496AD3eUyaDD11+EqCxH9t6V+EP9soZQk4YQ==",
               "dev": true,
        +      "hasInstallScript": true,
               "dependencies": {
        -        "number-is-nan": "^1.0.0"
        +        "@wdio/logger": "^9.1.3",
        +        "@zip.js/zip.js": "^2.7.53",
        +        "decamelize": "^6.0.0",
        +        "http-proxy-agent": "^7.0.2",
        +        "https-proxy-agent": "^7.0.5",
        +        "node-fetch": "^3.3.2",
        +        "tar-fs": "^3.0.6",
        +        "which": "^5.0.0"
        +      },
        +      "bin": {
        +        "geckodriver": "bin/geckodriver.js"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=18.0.0"
               }
             },
        -    "node_modules/maxmin/node_modules/supports-color": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
        -      "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
        +    "node_modules/geckodriver/node_modules/agent-base": {
        +      "version": "7.1.3",
        +      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
        +      "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
               "dev": true,
               "engines": {
        -        "node": ">=0.8.0"
        -      }
        -    },
        -    "node_modules/md5.js": {
        -      "version": "1.3.5",
        -      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
        -      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
        -      "dev": true,
        -      "dependencies": {
        -        "hash-base": "^3.0.0",
        -        "inherits": "^2.0.1",
        -        "safe-buffer": "^5.1.2"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/md5.js/node_modules/safe-buffer": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
        -      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
        -      "dev": true
        -    },
        -    "node_modules/mdn-links": {
        -      "version": "0.1.0",
        -      "resolved": "https://registry.npmjs.org/mdn-links/-/mdn-links-0.1.0.tgz",
        -      "integrity": "sha1-4kyDuXy0xYhsw58veAcF+/4nOqU=",
        -      "dev": true
        -    },
        -    "node_modules/mdurl": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
        -      "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
        -      "dev": true
        -    },
        -    "node_modules/media-typer": {
        -      "version": "0.3.0",
        -      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
        -      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
        +    "node_modules/geckodriver/node_modules/data-uri-to-buffer": {
        +      "version": "4.0.1",
        +      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
        +      "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.6"
        +        "node": ">= 12"
               }
             },
        -    "node_modules/meow": {
        -      "version": "12.0.1",
        -      "resolved": "https://registry.npmjs.org/meow/-/meow-12.0.1.tgz",
        -      "integrity": "sha512-/QOqMALNoKQcJAOOdIXjNLtfcCdLXbMFyB1fOOPdm6RzfBTlsuodOCTBDjVbeUSmgDQb8UI2oONqYGtq1PKKKA==",
        +    "node_modules/geckodriver/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
               "dependencies": {
        -        "@types/minimist": "^1.2.2",
        -        "camelcase-keys": "^8.0.2",
        -        "decamelize": "^6.0.0",
        -        "decamelize-keys": "^2.0.1",
        -        "hard-rejection": "^2.1.0",
        -        "minimist-options": "4.1.0",
        -        "normalize-package-data": "^5.0.0",
        -        "read-pkg-up": "^9.1.0",
        -        "redent": "^4.0.0",
        -        "trim-newlines": "^5.0.0",
        -        "type-fest": "^3.9.0",
        -        "yargs-parser": "^21.1.1"
        +        "ms": "^2.1.3"
               },
               "engines": {
        -        "node": ">=16.10"
        +        "node": ">=6.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/meow/node_modules/decamelize": {
        +    "node_modules/geckodriver/node_modules/decamelize": {
               "version": "6.0.0",
               "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz",
               "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==",
        @@ -10816,607 +6076,627 @@
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/meow/node_modules/hosted-git-info": {
        -      "version": "6.1.1",
        -      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
        -      "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
        +    "node_modules/geckodriver/node_modules/https-proxy-agent": {
        +      "version": "7.0.6",
        +      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
        +      "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
               "dev": true,
               "dependencies": {
        -        "lru-cache": "^7.5.1"
        +        "agent-base": "^7.1.2",
        +        "debug": "4"
               },
               "engines": {
        -        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/meow/node_modules/lru-cache": {
        -      "version": "7.18.3",
        -      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
        -      "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
        +    "node_modules/geckodriver/node_modules/isexe": {
        +      "version": "3.1.1",
        +      "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
        +      "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
               "dev": true,
               "engines": {
        -        "node": ">=12"
        +        "node": ">=16"
               }
             },
        -    "node_modules/meow/node_modules/normalize-package-data": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz",
        -      "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==",
        +    "node_modules/geckodriver/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        +      "dev": true
        +    },
        +    "node_modules/geckodriver/node_modules/node-fetch": {
        +      "version": "3.3.2",
        +      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
        +      "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
               "dev": true,
               "dependencies": {
        -        "hosted-git-info": "^6.0.0",
        -        "is-core-module": "^2.8.1",
        -        "semver": "^7.3.5",
        -        "validate-npm-package-license": "^3.0.4"
        +        "data-uri-to-buffer": "^4.0.0",
        +        "fetch-blob": "^3.1.4",
        +        "formdata-polyfill": "^4.0.10"
               },
               "engines": {
        -        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
        +        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/node-fetch"
               }
             },
        -    "node_modules/meow/node_modules/semver": {
        -      "version": "7.5.4",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
        -      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
        +    "node_modules/geckodriver/node_modules/which": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz",
        +      "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==",
               "dev": true,
               "dependencies": {
        -        "lru-cache": "^6.0.0"
        +        "isexe": "^3.1.1"
               },
               "bin": {
        -        "semver": "bin/semver.js"
        +        "node-which": "bin/which.js"
               },
               "engines": {
        -        "node": ">=10"
        +        "node": "^18.17.0 || >=20.5.0"
               }
             },
        -    "node_modules/meow/node_modules/semver/node_modules/lru-cache": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
        -      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
        +    "node_modules/gensync": {
        +      "version": "1.0.0-beta.2",
        +      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
        +      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
               "dev": true,
        -      "dependencies": {
        -        "yallist": "^4.0.0"
        -      },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=6.9.0"
               }
             },
        -    "node_modules/meow/node_modules/type-fest": {
        -      "version": "3.13.0",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.0.tgz",
        -      "integrity": "sha512-Gur3yQGM9qiLNs0KPP7LPgeRbio2QTt4xXouobMCarR0/wyW3F+F/+OWwshg3NG0Adon7uQfSZBpB46NfhoF1A==",
        +    "node_modules/get-caller-file": {
        +      "version": "2.0.5",
        +      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
        +      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
               "dev": true,
               "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": "6.* || 8.* || >= 10.*"
               }
             },
        -    "node_modules/meow/node_modules/yallist": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
        -      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
        -      "dev": true
        -    },
        -    "node_modules/meow/node_modules/yargs-parser": {
        -      "version": "21.1.1",
        -      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
        -      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
        +    "node_modules/get-east-asian-width": {
        +      "version": "1.3.0",
        +      "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
        +      "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==",
               "dev": true,
               "engines": {
        -        "node": ">=12"
        +        "node": ">=18"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/merge-descriptors": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
        -      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
        +    "node_modules/get-port": {
        +      "version": "7.1.0",
        +      "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz",
        +      "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==",
               "dev": true,
        +      "engines": {
        +        "node": ">=16"
        +      },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/merge-source-map": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
        -      "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
        +    "node_modules/get-stream": {
        +      "version": "5.2.0",
        +      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
        +      "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
               "dev": true,
               "dependencies": {
        -        "source-map": "^0.6.1"
        -      }
        -    },
        -    "node_modules/merge-stream": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
        -      "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
        -      "dev": true
        -    },
        -    "node_modules/merge2": {
        -      "version": "1.4.1",
        -      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
        -      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
        -      "dev": true,
        +        "pump": "^3.0.0"
        +      },
               "engines": {
        -        "node": ">= 8"
        +        "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/methods": {
        -      "version": "1.1.2",
        -      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
        -      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
        +    "node_modules/get-uri": {
        +      "version": "6.0.4",
        +      "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz",
        +      "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==",
               "dev": true,
        +      "dependencies": {
        +        "basic-ftp": "^5.0.2",
        +        "data-uri-to-buffer": "^6.0.2",
        +        "debug": "^4.3.4"
        +      },
               "engines": {
        -        "node": ">= 0.6"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/micromatch": {
        -      "version": "4.0.5",
        -      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
        -      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
        +    "node_modules/get-uri/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
               "dependencies": {
        -        "braces": "^3.0.2",
        -        "picomatch": "^2.3.1"
        +        "ms": "^2.1.3"
               },
               "engines": {
        -        "node": ">=8.6"
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/miller-rabin": {
        -      "version": "4.0.1",
        -      "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
        -      "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
        +    "node_modules/get-uri/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        +      "dev": true
        +    },
        +    "node_modules/gifenc": {
        +      "version": "1.0.3",
        +      "resolved": "https://registry.npmjs.org/gifenc/-/gifenc-1.0.3.tgz",
        +      "integrity": "sha512-xdr6AdrfGBcfzncONUOlXMBuc5wJDtOueE3c5rdG0oNgtINLD+f2iFZltrBRZYzACRbKr+mSVU/x98zv2u3jmw=="
        +    },
        +    "node_modules/git-up": {
        +      "version": "7.0.0",
        +      "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz",
        +      "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==",
               "dev": true,
               "dependencies": {
        -        "bn.js": "^4.0.0",
        -        "brorand": "^1.0.1"
        -      },
        -      "bin": {
        -        "miller-rabin": "bin/miller-rabin"
        +        "is-ssh": "^1.4.0",
        +        "parse-url": "^8.1.0"
               }
             },
        -    "node_modules/mime": {
        -      "version": "1.6.0",
        -      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
        -      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
        +    "node_modules/git-url-parse": {
        +      "version": "13.1.1",
        +      "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.1.tgz",
        +      "integrity": "sha512-PCFJyeSSdtnbfhSNRw9Wk96dDCNx+sogTe4YNXeXSJxt7xz5hvXekuRn9JX7m+Mf4OscCu8h+mtAl3+h5Fo8lQ==",
               "dev": true,
        -      "bin": {
        -        "mime": "cli.js"
        -      },
        -      "engines": {
        -        "node": ">=4"
        +      "dependencies": {
        +        "git-up": "^7.0.0"
               }
             },
        -    "node_modules/mime-db": {
        -      "version": "1.52.0",
        -      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
        -      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.6"
        -      }
        +    "node_modules/github-slugger": {
        +      "version": "1.4.0",
        +      "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz",
        +      "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==",
        +      "dev": true
             },
        -    "node_modules/mime-types": {
        -      "version": "2.1.35",
        -      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
        -      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
        +    "node_modules/glob": {
        +      "version": "7.1.6",
        +      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
        +      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
               "dev": true,
               "dependencies": {
        -        "mime-db": "1.52.0"
        +        "fs.realpath": "^1.0.0",
        +        "inflight": "^1.0.4",
        +        "inherits": "2",
        +        "minimatch": "^3.0.4",
        +        "once": "^1.3.0",
        +        "path-is-absolute": "^1.0.0"
               },
               "engines": {
        -        "node": ">= 0.6"
        +        "node": "*"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/mimic-fn": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
        -      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
        +    "node_modules/glob-parent": {
        +      "version": "6.0.2",
        +      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
        +      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
               "dev": true,
        +      "dependencies": {
        +        "is-glob": "^4.0.3"
        +      },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=10.13.0"
               }
             },
        -    "node_modules/mimic-response": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
        -      "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
        +    "node_modules/globals": {
        +      "version": "11.7.0",
        +      "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz",
        +      "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==",
               "dev": true,
               "engines": {
                 "node": ">=4"
               }
             },
        -    "node_modules/min-indent": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
        -      "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4"
        -      }
        +    "node_modules/globals-docs": {
        +      "version": "2.4.1",
        +      "resolved": "https://registry.npmjs.org/globals-docs/-/globals-docs-2.4.1.tgz",
        +      "integrity": "sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==",
        +      "dev": true
             },
        -    "node_modules/minimalistic-assert": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
        -      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
        +    "node_modules/graceful-fs": {
        +      "version": "4.2.11",
        +      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
        +      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
               "dev": true
             },
        -    "node_modules/minimalistic-crypto-utils": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
        -      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
        +    "node_modules/grapheme-splitter": {
        +      "version": "1.0.4",
        +      "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
        +      "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
               "dev": true
             },
        -    "node_modules/minimatch": {
        -      "version": "3.0.8",
        -      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz",
        -      "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==",
        +    "node_modules/graphemer": {
        +      "version": "1.4.0",
        +      "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
        +      "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
        +      "dev": true
        +    },
        +    "node_modules/graphql": {
        +      "version": "16.10.0",
        +      "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz",
        +      "integrity": "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==",
               "dev": true,
        -      "dependencies": {
        -        "brace-expansion": "^1.1.7"
        -      },
               "engines": {
        -        "node": "*"
        +        "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
               }
             },
        -    "node_modules/minimist": {
        -      "version": "1.2.6",
        -      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
        -      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
        -      "dev": true
        -    },
        -    "node_modules/minimist-options": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
        -      "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
        +    "node_modules/hasown": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
        +      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
               "dev": true,
               "dependencies": {
        -        "arrify": "^1.0.1",
        -        "is-plain-obj": "^1.1.0",
        -        "kind-of": "^6.0.3"
        +        "function-bind": "^1.1.2"
               },
               "engines": {
        -        "node": ">= 6"
        +        "node": ">= 0.4"
               }
             },
        -    "node_modules/mkdirp": {
        -      "version": "0.5.4",
        -      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
        -      "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
        -      "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
        +    "node_modules/hast-util-from-parse5": {
        +      "version": "7.1.2",
        +      "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz",
        +      "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==",
               "dev": true,
               "dependencies": {
        -        "minimist": "^1.2.5"
        -      },
        -      "bin": {
        -        "mkdirp": "bin/cmd.js"
        -      }
        -    },
        -    "node_modules/mkdirp-classic": {
        -      "version": "0.5.3",
        -      "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
        -      "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
        -      "dev": true
        -    },
        -    "node_modules/mocha": {
        -      "version": "10.2.0",
        -      "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
        -      "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
        -      "dev": true,
        -      "dependencies": {
        -        "ansi-colors": "4.1.1",
        -        "browser-stdout": "1.3.1",
        -        "chokidar": "3.5.3",
        -        "debug": "4.3.4",
        -        "diff": "5.0.0",
        -        "escape-string-regexp": "4.0.0",
        -        "find-up": "5.0.0",
        -        "glob": "7.2.0",
        -        "he": "1.2.0",
        -        "js-yaml": "4.1.0",
        -        "log-symbols": "4.1.0",
        -        "minimatch": "5.0.1",
        -        "ms": "2.1.3",
        -        "nanoid": "3.3.3",
        -        "serialize-javascript": "6.0.0",
        -        "strip-json-comments": "3.1.1",
        -        "supports-color": "8.1.1",
        -        "workerpool": "6.2.1",
        -        "yargs": "16.2.0",
        -        "yargs-parser": "20.2.4",
        -        "yargs-unparser": "2.0.0"
        -      },
        -      "bin": {
        -        "_mocha": "bin/_mocha",
        -        "mocha": "bin/mocha.js"
        -      },
        -      "engines": {
        -        "node": ">= 14.0.0"
        +        "@types/hast": "^2.0.0",
        +        "@types/unist": "^2.0.0",
        +        "hastscript": "^7.0.0",
        +        "property-information": "^6.0.0",
        +        "vfile": "^5.0.0",
        +        "vfile-location": "^4.0.0",
        +        "web-namespaces": "^2.0.0"
               },
               "funding": {
                 "type": "opencollective",
        -        "url": "https://opencollective.com/mochajs"
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/mocha/node_modules/ansi-regex": {
        -      "version": "5.0.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        -      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
        +    "node_modules/hast-util-parse-selector": {
        +      "version": "3.1.1",
        +      "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz",
        +      "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==",
               "dev": true,
        -      "engines": {
        -        "node": ">=8"
        +      "dependencies": {
        +        "@types/hast": "^2.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/mocha/node_modules/ansi-styles": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        -      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
        +    "node_modules/hast-util-raw": {
        +      "version": "7.2.3",
        +      "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz",
        +      "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==",
               "dev": true,
               "dependencies": {
        -        "color-convert": "^2.0.1"
        -      },
        -      "engines": {
        -        "node": ">=8"
        +        "@types/hast": "^2.0.0",
        +        "@types/parse5": "^6.0.0",
        +        "hast-util-from-parse5": "^7.0.0",
        +        "hast-util-to-parse5": "^7.0.0",
        +        "html-void-elements": "^2.0.0",
        +        "parse5": "^6.0.0",
        +        "unist-util-position": "^4.0.0",
        +        "unist-util-visit": "^4.0.0",
        +        "vfile": "^5.0.0",
        +        "web-namespaces": "^2.0.0",
        +        "zwitch": "^2.0.0"
               },
               "funding": {
        -        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/mocha/node_modules/argparse": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
        -      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
        -      "dev": true
        -    },
        -    "node_modules/mocha/node_modules/chalk": {
        -      "version": "4.1.2",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        -      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
        +    "node_modules/hast-util-sanitize": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.1.0.tgz",
        +      "integrity": "sha512-Hd9tU0ltknMGRDv+d6Ro/4XKzBqQnP/EZrpiTbpFYfXv/uOhWeKc+2uajcbEvAEH98VZd7eII2PiXm13RihnLw==",
               "dev": true,
               "dependencies": {
        -        "ansi-styles": "^4.1.0",
        -        "supports-color": "^7.1.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        +        "@types/hast": "^2.0.0"
               },
               "funding": {
        -        "url": "https://github.com/chalk/chalk?sponsor=1"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/mocha/node_modules/chalk/node_modules/supports-color": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        -      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        +    "node_modules/hast-util-to-html": {
        +      "version": "8.0.4",
        +      "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz",
        +      "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==",
               "dev": true,
               "dependencies": {
        -        "has-flag": "^4.0.0"
        +        "@types/hast": "^2.0.0",
        +        "@types/unist": "^2.0.0",
        +        "ccount": "^2.0.0",
        +        "comma-separated-tokens": "^2.0.0",
        +        "hast-util-raw": "^7.0.0",
        +        "hast-util-whitespace": "^2.0.0",
        +        "html-void-elements": "^2.0.0",
        +        "property-information": "^6.0.0",
        +        "space-separated-tokens": "^2.0.0",
        +        "stringify-entities": "^4.0.0",
        +        "zwitch": "^2.0.4"
               },
        -      "engines": {
        -        "node": ">=8"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/mocha/node_modules/cliui": {
        -      "version": "7.0.4",
        -      "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
        -      "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
        +    "node_modules/hast-util-to-parse5": {
        +      "version": "7.1.0",
        +      "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz",
        +      "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==",
               "dev": true,
               "dependencies": {
        -        "string-width": "^4.2.0",
        -        "strip-ansi": "^6.0.0",
        -        "wrap-ansi": "^7.0.0"
        +        "@types/hast": "^2.0.0",
        +        "comma-separated-tokens": "^2.0.0",
        +        "property-information": "^6.0.0",
        +        "space-separated-tokens": "^2.0.0",
        +        "web-namespaces": "^2.0.0",
        +        "zwitch": "^2.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/mocha/node_modules/color-convert": {
        +    "node_modules/hast-util-whitespace": {
               "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        -      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
        +      "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz",
        +      "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==",
               "dev": true,
        -      "dependencies": {
        -        "color-name": "~1.1.4"
        -      },
        -      "engines": {
        -        "node": ">=7.0.0"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/mocha/node_modules/debug": {
        -      "version": "4.3.4",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
        -      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
        +    "node_modules/hastscript": {
        +      "version": "7.2.0",
        +      "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz",
        +      "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==",
               "dev": true,
               "dependencies": {
        -        "ms": "2.1.2"
        -      },
        -      "engines": {
        -        "node": ">=6.0"
        +        "@types/hast": "^2.0.0",
        +        "comma-separated-tokens": "^2.0.0",
        +        "hast-util-parse-selector": "^3.0.0",
        +        "property-information": "^6.0.0",
        +        "space-separated-tokens": "^2.0.0"
               },
        -      "peerDependenciesMeta": {
        -        "supports-color": {
        -          "optional": true
        -        }
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/mocha/node_modules/debug/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        -      "dev": true
        +    "node_modules/he": {
        +      "version": "1.2.0",
        +      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
        +      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
        +      "dev": true,
        +      "optional": true,
        +      "bin": {
        +        "he": "bin/he"
        +      }
             },
        -    "node_modules/mocha/node_modules/emoji-regex": {
        -      "version": "8.0.0",
        -      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        -      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        +    "node_modules/headers-polyfill": {
        +      "version": "4.0.3",
        +      "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz",
        +      "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==",
               "dev": true
             },
        -    "node_modules/mocha/node_modules/escape-string-regexp": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
        -      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
        +    "node_modules/highlight.js": {
        +      "version": "11.11.1",
        +      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
        +      "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
               "dev": true,
               "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">=12.0.0"
               }
             },
        -    "node_modules/mocha/node_modules/find-up": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
        -      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
        +    "node_modules/html-void-elements": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
        +      "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==",
               "dev": true,
        -      "dependencies": {
        -        "locate-path": "^6.0.0",
        -        "path-exists": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/mocha/node_modules/glob": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
        -      "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
        +    "node_modules/htmlfy": {
        +      "version": "0.5.1",
        +      "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.5.1.tgz",
        +      "integrity": "sha512-nb66M9g0zKrvmR3kk/WOM+5tOT3DzO1yJ4yEJXsz2zfZ3gXiCTrlGvbc4lQzTZyylJj7at+XSVDxFvAVH6J6tQ==",
        +      "dev": true
        +    },
        +    "node_modules/htmlparser2": {
        +      "version": "9.1.0",
        +      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
        +      "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
               "dev": true,
        +      "funding": [
        +        "https://github.com/fb55/htmlparser2?sponsor=1",
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/fb55"
        +        }
        +      ],
               "dependencies": {
        -        "fs.realpath": "^1.0.0",
        -        "inflight": "^1.0.4",
        -        "inherits": "2",
        -        "minimatch": "^3.0.4",
        -        "once": "^1.3.0",
        -        "path-is-absolute": "^1.0.0"
        -      },
        +        "domelementtype": "^2.3.0",
        +        "domhandler": "^5.0.3",
        +        "domutils": "^3.1.0",
        +        "entities": "^4.5.0"
        +      }
        +    },
        +    "node_modules/htmlparser2/node_modules/entities": {
        +      "version": "4.5.0",
        +      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
        +      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
        +      "dev": true,
               "engines": {
        -        "node": "*"
        +        "node": ">=0.12"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/isaacs"
        +        "url": "https://github.com/fb55/entities?sponsor=1"
               }
             },
        -    "node_modules/mocha/node_modules/glob/node_modules/minimatch": {
        -      "version": "3.1.2",
        -      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
        -      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
        +    "node_modules/http-proxy-agent": {
        +      "version": "7.0.2",
        +      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
        +      "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
               "dev": true,
               "dependencies": {
        -        "brace-expansion": "^1.1.7"
        +        "agent-base": "^7.1.0",
        +        "debug": "^4.3.4"
               },
               "engines": {
        -        "node": "*"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/mocha/node_modules/has-flag": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        -      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        +    "node_modules/http-proxy-agent/node_modules/agent-base": {
        +      "version": "7.1.3",
        +      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
        +      "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/mocha/node_modules/is-fullwidth-code-point": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        -      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
        +    "node_modules/http-proxy-agent/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
        +      "dependencies": {
        +        "ms": "^2.1.3"
        +      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/mocha/node_modules/js-yaml": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
        -      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
        +    "node_modules/http-proxy-agent/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        +      "dev": true
        +    },
        +    "node_modules/husky": {
        +      "version": "4.3.8",
        +      "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz",
        +      "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==",
               "dev": true,
        +      "hasInstallScript": true,
               "dependencies": {
        -        "argparse": "^2.0.1"
        +        "chalk": "^4.0.0",
        +        "ci-info": "^2.0.0",
        +        "compare-versions": "^3.6.0",
        +        "cosmiconfig": "^7.0.0",
        +        "find-versions": "^4.0.0",
        +        "opencollective-postinstall": "^2.0.2",
        +        "pkg-dir": "^5.0.0",
        +        "please-upgrade-node": "^3.2.0",
        +        "slash": "^3.0.0",
        +        "which-pm-runs": "^1.0.0"
               },
               "bin": {
        -        "js-yaml": "bin/js-yaml.js"
        -      }
        -    },
        -    "node_modules/mocha/node_modules/locate-path": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
        -      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
        -      "dev": true,
        -      "dependencies": {
        -        "p-locate": "^5.0.0"
        +        "husky-run": "bin/run.js",
        +        "husky-upgrade": "lib/upgrader/bin.js"
               },
               "engines": {
                 "node": ">=10"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/husky"
               }
             },
        -    "node_modules/mocha/node_modules/log-symbols": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
        -      "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
        +    "node_modules/husky/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^4.1.0",
        -        "is-unicode-supported": "^0.1.0"
        +        "color-convert": "^2.0.1"
               },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=8"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/mocha/node_modules/minimatch": {
        -      "version": "5.0.1",
        -      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
        -      "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
        +    "node_modules/husky/node_modules/chalk": {
        +      "version": "4.1.2",
        +      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        +      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
               "dev": true,
               "dependencies": {
        -        "brace-expansion": "^2.0.1"
        +        "ansi-styles": "^4.1.0",
        +        "supports-color": "^7.1.0"
               },
               "engines": {
                 "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/chalk?sponsor=1"
               }
             },
        -    "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": {
        +    "node_modules/husky/node_modules/color-convert": {
               "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
        -      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
               "dependencies": {
        -        "balanced-match": "^1.0.0"
        +        "color-name": "~1.1.4"
        +      },
        +      "engines": {
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/mocha/node_modules/ms": {
        -      "version": "2.1.3",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        -      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        -      "dev": true
        -    },
        -    "node_modules/mocha/node_modules/p-limit": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
        -      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
        +    "node_modules/husky/node_modules/cosmiconfig": {
        +      "version": "7.0.1",
        +      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
        +      "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
               "dev": true,
               "dependencies": {
        -        "yocto-queue": "^0.1.0"
        +        "@types/parse-json": "^4.0.0",
        +        "import-fresh": "^3.2.1",
        +        "parse-json": "^5.0.0",
        +        "path-type": "^4.0.0",
        +        "yaml": "^1.10.0"
               },
               "engines": {
                 "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/mocha/node_modules/p-locate": {
        +    "node_modules/husky/node_modules/find-up": {
               "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
        -      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
        +      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
        +      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
               "dev": true,
               "dependencies": {
        -        "p-limit": "^3.0.2"
        +        "locate-path": "^6.0.0",
        +        "path-exists": "^4.0.0"
               },
               "engines": {
                 "node": ">=10"
        @@ -11425,1078 +6705,985 @@
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/mocha/node_modules/string-width": {
        -      "version": "4.2.3",
        -      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
        -      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
        +    "node_modules/husky/node_modules/has-flag": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        +      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
               "dev": true,
        -      "dependencies": {
        -        "emoji-regex": "^8.0.0",
        -        "is-fullwidth-code-point": "^3.0.0",
        -        "strip-ansi": "^6.0.1"
        -      },
               "engines": {
                 "node": ">=8"
               }
             },
        -    "node_modules/mocha/node_modules/strip-ansi": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        -      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
        +    "node_modules/husky/node_modules/locate-path": {
        +      "version": "6.0.0",
        +      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
        +      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
               "dev": true,
               "dependencies": {
        -        "ansi-regex": "^5.0.1"
        +        "p-locate": "^5.0.0"
               },
               "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/mocha/node_modules/strip-json-comments": {
        -      "version": "3.1.1",
        -      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
        -      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        +        "node": ">=10"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/mocha/node_modules/supports-color": {
        -      "version": "8.1.1",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
        -      "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
        +    "node_modules/husky/node_modules/p-limit": {
        +      "version": "3.1.0",
        +      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
        +      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
               "dev": true,
               "dependencies": {
        -        "has-flag": "^4.0.0"
        +        "yocto-queue": "^0.1.0"
               },
               "engines": {
                 "node": ">=10"
               },
               "funding": {
        -        "url": "https://github.com/chalk/supports-color?sponsor=1"
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/mocha/node_modules/wrap-ansi": {
        -      "version": "7.0.0",
        -      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
        -      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
        +    "node_modules/husky/node_modules/p-locate": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
        +      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
               "dev": true,
               "dependencies": {
        -        "ansi-styles": "^4.0.0",
        -        "string-width": "^4.1.0",
        -        "strip-ansi": "^6.0.0"
        +        "p-limit": "^3.0.2"
               },
               "engines": {
                 "node": ">=10"
               },
               "funding": {
        -        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/mocha/node_modules/y18n": {
        -      "version": "5.0.8",
        -      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
        -      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
        +    "node_modules/husky/node_modules/parse-json": {
        +      "version": "5.2.0",
        +      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
        +      "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
               "dev": true,
        +      "dependencies": {
        +        "@babel/code-frame": "^7.0.0",
        +        "error-ex": "^1.3.1",
        +        "json-parse-even-better-errors": "^2.3.0",
        +        "lines-and-columns": "^1.1.6"
        +      },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/mocha/node_modules/yargs": {
        -      "version": "16.2.0",
        -      "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
        -      "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
        +    "node_modules/husky/node_modules/pkg-dir": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
        +      "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==",
               "dev": true,
               "dependencies": {
        -        "cliui": "^7.0.2",
        -        "escalade": "^3.1.1",
        -        "get-caller-file": "^2.0.5",
        -        "require-directory": "^2.1.1",
        -        "string-width": "^4.2.0",
        -        "y18n": "^5.0.5",
        -        "yargs-parser": "^20.2.2"
        +        "find-up": "^5.0.0"
               },
               "engines": {
                 "node": ">=10"
               }
             },
        -    "node_modules/mocha/node_modules/yargs-parser": {
        -      "version": "20.2.4",
        -      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
        -      "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
        +    "node_modules/husky/node_modules/supports-color": {
        +      "version": "7.2.0",
        +      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        +      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
               "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      }
        -    },
        -    "node_modules/module-deps": {
        -      "version": "6.2.2",
        -      "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.2.tgz",
        -      "integrity": "sha512-a9y6yDv5u5I4A+IPHTnqFxcaKr4p50/zxTjcQJaX2ws9tN/W6J6YXnEKhqRyPhl494dkcxx951onSKVezmI+3w==",
        -      "dev": true,
        -      "dependencies": {
        -        "browser-resolve": "^1.7.0",
        -        "cached-path-relative": "^1.0.2",
        -        "concat-stream": "~1.6.0",
        -        "defined": "^1.0.0",
        -        "detective": "^5.2.0",
        -        "duplexer2": "^0.1.2",
        -        "inherits": "^2.0.1",
        -        "JSONStream": "^1.0.3",
        -        "parents": "^1.0.0",
        -        "readable-stream": "^2.0.2",
        -        "resolve": "^1.4.0",
        -        "stream-combiner2": "^1.1.1",
        -        "subarg": "^1.0.0",
        -        "through2": "^2.0.0",
        -        "xtend": "^4.0.0"
        -      },
        -      "bin": {
        -        "module-deps": "bin/cmd.js"
        +      "dependencies": {
        +        "has-flag": "^4.0.0"
               },
               "engines": {
        -        "node": ">= 0.8.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/morgan": {
        -      "version": "1.10.0",
        -      "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
        -      "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
        +    "node_modules/i18next": {
        +      "version": "19.0.2",
        +      "resolved": "https://registry.npmjs.org/i18next/-/i18next-19.0.2.tgz",
        +      "integrity": "sha512-fBa43Ann2udP1CQAz3IQpOZ1dGAkmi3mMfzisOhH17igneSRbvZ7P2RNbL+L1iRYKMufBmVwnC7G3gqcyviZ9g==",
               "dev": true,
               "dependencies": {
        -        "basic-auth": "~2.0.1",
        -        "debug": "2.6.9",
        -        "depd": "~2.0.0",
        -        "on-finished": "~2.3.0",
        -        "on-headers": "~1.0.2"
        -      },
        -      "engines": {
        -        "node": ">= 0.8.0"
        +        "@babel/runtime": "^7.3.1"
               }
             },
        -    "node_modules/morgan/node_modules/depd": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
        -      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
        +    "node_modules/i18next-browser-languagedetector": {
        +      "version": "4.0.1",
        +      "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.0.1.tgz",
        +      "integrity": "sha512-RxSoX6mB8cab0CTIQ+klCS764vYRj+Jk621cnFVsINvcdlb/cdi3vQFyrPwmnowB7ReUadjHovgZX+RPIzHVQQ==",
               "dev": true,
        -      "engines": {
        -        "node": ">= 0.8"
        +      "dependencies": {
        +        "@babel/runtime": "^7.5.5"
               }
             },
        -    "node_modules/ms": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
        -      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
        -      "dev": true
        -    },
        -    "node_modules/mute-stream": {
        -      "version": "0.0.8",
        -      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
        -      "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
        -      "dev": true
        -    },
        -    "node_modules/nanoid": {
        -      "version": "3.3.3",
        -      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
        -      "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
        +    "node_modules/iconv-lite": {
        +      "version": "0.4.24",
        +      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
        +      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
               "dev": true,
        -      "bin": {
        -        "nanoid": "bin/nanoid.cjs"
        +      "dependencies": {
        +        "safer-buffer": ">= 2.1.2 < 3"
               },
               "engines": {
        -        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/natural-compare": {
        -      "version": "1.4.0",
        -      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
        -      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
        -      "dev": true
        +    "node_modules/ieee754": {
        +      "version": "1.2.1",
        +      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
        +      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
        +      "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
        +        }
        +      ]
             },
        -    "node_modules/negotiator": {
        -      "version": "0.6.3",
        -      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
        -      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
        +    "node_modules/ignore": {
        +      "version": "5.2.4",
        +      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
        +      "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.6"
        +        "node": ">= 4"
               }
             },
        -    "node_modules/nested-error-stacks": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz",
        -      "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==",
        +    "node_modules/immediate": {
        +      "version": "3.0.6",
        +      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
        +      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
               "dev": true
             },
        -    "node_modules/new-github-release-url": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/new-github-release-url/-/new-github-release-url-2.0.0.tgz",
        -      "integrity": "sha512-NHDDGYudnvRutt/VhKFlX26IotXe1w0cmkDm6JGquh5bz/bDTw0LufSmH/GxTjEdpHEO+bVKFTwdrcGa/9XlKQ==",
        +    "node_modules/import-fresh": {
        +      "version": "3.3.0",
        +      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
        +      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
               "dev": true,
               "dependencies": {
        -        "type-fest": "^2.5.1"
        -      },
        -      "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +        "parent-module": "^1.0.0",
        +        "resolve-from": "^4.0.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/new-github-release-url/node_modules/type-fest": {
        -      "version": "2.19.0",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
        -      "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
        -      "dev": true,
               "engines": {
        -        "node": ">=12.20"
        +        "node": ">=6"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/node-fetch": {
        -      "version": "2.6.7",
        -      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
        -      "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "whatwg-url": "^5.0.0"
        -      },
        -      "engines": {
        -        "node": "4.x || >=6.0.0"
        -      },
        -      "peerDependencies": {
        -        "encoding": "^0.1.0"
        -      },
        -      "peerDependenciesMeta": {
        -        "encoding": {
        -          "optional": true
        -        }
        -      }
        -    },
        -    "node_modules/node-http2": {
        -      "version": "4.0.1",
        -      "resolved": "https://registry.npmjs.org/node-http2/-/node-http2-4.0.1.tgz",
        -      "integrity": "sha512-AP21BjQsOAMTCJCCkdXUUMa1o7/Qx+yAWHnHZbCf8RhZ+hKMjB9rUkAtnfayk/yGj1qapZ5eBHZJBpk1dqdNlw==",
        -      "dev": true,
        -      "dependencies": {
        -        "assert": "1.4.1",
        -        "events": "1.1.1",
        -        "https-browserify": "0.0.1",
        -        "setimmediate": "^1.0.5",
        -        "stream-browserify": "2.0.1",
        -        "timers-browserify": "2.0.2",
        -        "url": "^0.11.0",
        -        "websocket-stream": "^5.0.1"
        -      },
        -      "engines": {
        -        "node": ">=0.12.0"
        -      }
        -    },
        -    "node_modules/node-http2/node_modules/assert": {
        -      "version": "1.4.1",
        -      "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
        -      "integrity": "sha512-N+aAxov+CKVS3JuhDIQFr24XvZvwE96Wlhk9dytTg/GmwWoghdOvR8dspx8MVz71O+Y0pA3UPqHF68D6iy8UvQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "util": "0.10.3"
        -      }
        -    },
        -    "node_modules/node-http2/node_modules/https-browserify": {
        -      "version": "0.0.1",
        -      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.1.tgz",
        -      "integrity": "sha512-EjDQFbgJr1vDD/175UJeSX3ncQ3+RUnCL5NkthQGHvF4VNHlzTy8ifJfTqz47qiPRqaFH58+CbuG3x51WuB1XQ==",
        -      "dev": true
        -    },
        -    "node_modules/node-http2/node_modules/inherits": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
        -      "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==",
        -      "dev": true
        -    },
        -    "node_modules/node-http2/node_modules/stream-browserify": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
        -      "integrity": "sha512-nmQnY9D9TlnfQIkYJCCWxvCcQODilFRZIw14gCMYQVXOiY4E1Ze1VMxB+6y3qdXHpTordULo2qWloHmNcNAQYw==",
        -      "dev": true,
        -      "dependencies": {
        -        "inherits": "~2.0.1",
        -        "readable-stream": "^2.0.2"
        -      }
        -    },
        -    "node_modules/node-http2/node_modules/timers-browserify": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.2.tgz",
        -      "integrity": "sha512-O7UB405+hxP2OWqlBdlUMxZVEdsi8NOWL2c730Cs6zeO1l1AkxygvTm6yC4nTw84iGbFcqxbIkkrdNKzq/3Fvg==",
        -      "dev": true,
        -      "dependencies": {
        -        "setimmediate": "^1.0.4"
        -      },
        +    "node_modules/import-fresh/node_modules/resolve-from": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
        +      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
        +      "dev": true,
               "engines": {
        -        "node": ">=0.6.0"
        +        "node": ">=4"
               }
             },
        -    "node_modules/node-http2/node_modules/util": {
        -      "version": "0.10.3",
        -      "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
        -      "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==",
        +    "node_modules/import-meta-resolve": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
        +      "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==",
               "dev": true,
        -      "dependencies": {
        -        "inherits": "2.0.1"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/node-modules-regexp": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz",
        -      "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=",
        +    "node_modules/imurmurhash": {
        +      "version": "0.1.4",
        +      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
        +      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/node-uuid": {
        -      "version": "1.4.8",
        -      "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
        -      "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=",
        -      "deprecated": "Use uuid module instead",
        -      "dev": true,
        -      "bin": {
        -        "uuid": "bin/uuid"
        +        "node": ">=0.8.19"
               }
             },
        -    "node_modules/nopt": {
        -      "version": "3.0.6",
        -      "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
        -      "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
        +    "node_modules/inflight": {
        +      "version": "1.0.6",
        +      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
        +      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
               "dev": true,
               "dependencies": {
        -        "abbrev": "1"
        -      },
        -      "bin": {
        -        "nopt": "bin/nopt.js"
        +        "once": "^1.3.0",
        +        "wrappy": "1"
               }
             },
        -    "node_modules/normalize-package-data": {
        -      "version": "2.4.0",
        -      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
        -      "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
        +    "node_modules/inherits": {
        +      "version": "2.0.3",
        +      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
        +      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
        +      "dev": true
        +    },
        +    "node_modules/inquirer": {
        +      "version": "7.3.3",
        +      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
        +      "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
               "dev": true,
               "dependencies": {
        -        "hosted-git-info": "^2.1.4",
        -        "is-builtin-module": "^1.0.0",
        -        "semver": "2 || 3 || 4 || 5",
        -        "validate-npm-package-license": "^3.0.1"
        +        "ansi-escapes": "^4.2.1",
        +        "chalk": "^4.1.0",
        +        "cli-cursor": "^3.1.0",
        +        "cli-width": "^3.0.0",
        +        "external-editor": "^3.0.3",
        +        "figures": "^3.0.0",
        +        "lodash": "^4.17.19",
        +        "mute-stream": "0.0.8",
        +        "run-async": "^2.4.0",
        +        "rxjs": "^6.6.0",
        +        "string-width": "^4.1.0",
        +        "strip-ansi": "^6.0.0",
        +        "through": "^2.3.6"
        +      },
        +      "engines": {
        +        "node": ">=8.0.0"
               }
             },
        -    "node_modules/normalize-path": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
        -      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
        +    "node_modules/inquirer/node_modules/ansi-regex": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/normalize-url": {
        -      "version": "6.1.0",
        -      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
        -      "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
        +    "node_modules/inquirer/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
        +      "dependencies": {
        +        "color-convert": "^2.0.1"
        +      },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=8"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/np": {
        -      "version": "8.0.4",
        -      "resolved": "https://registry.npmjs.org/np/-/np-8.0.4.tgz",
        -      "integrity": "sha512-a4s1yESHcIwsrk/oaTekfbhb1R/2z2yyfVLX6Atl54w/9+QR01qeYyK3vMWgJ0UY+kYsGzQXausgvUX0pkmIMg==",
        +    "node_modules/inquirer/node_modules/chalk": {
        +      "version": "4.1.2",
        +      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        +      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^5.2.0",
        -        "cosmiconfig": "^8.1.3",
        -        "del": "^7.0.0",
        -        "escape-goat": "^4.0.0",
        -        "escape-string-regexp": "^5.0.0",
        -        "execa": "^7.1.1",
        -        "exit-hook": "^3.2.0",
        -        "github-url-from-git": "^1.5.0",
        -        "has-yarn": "^3.0.0",
        -        "hosted-git-info": "^6.1.1",
        -        "ignore-walk": "^6.0.3",
        -        "import-local": "^3.1.0",
        -        "inquirer": "^9.2.6",
        -        "is-installed-globally": "^0.4.0",
        -        "is-interactive": "^2.0.0",
        -        "is-scoped": "^3.0.0",
        -        "issue-regex": "^4.1.0",
        -        "listr": "^0.14.3",
        -        "listr-input": "^0.2.1",
        -        "log-symbols": "^5.1.0",
        -        "meow": "^12.0.1",
        -        "new-github-release-url": "^2.0.0",
        -        "npm-name": "^7.1.0",
        -        "onetime": "^6.0.0",
        -        "open": "^9.1.0",
        -        "ow": "^1.1.1",
        -        "p-memoize": "^7.1.1",
        -        "p-timeout": "^6.1.1",
        -        "path-exists": "^5.0.0",
        -        "pkg-dir": "^7.0.0",
        -        "read-pkg-up": "^9.1.0",
        -        "rxjs": "^7.8.1",
        -        "semver": "^7.5.1",
        -        "symbol-observable": "^4.0.0",
        -        "terminal-link": "^3.0.0",
        -        "update-notifier": "^6.0.2"
        -      },
        -      "bin": {
        -        "np": "source/cli.js"
        +        "ansi-styles": "^4.1.0",
        +        "supports-color": "^7.1.0"
               },
               "engines": {
        -        "git": ">=2.11.0",
        -        "node": ">=16.6.0",
        -        "npm": ">=7.19.0",
        -        "yarn": ">=1.7.0"
        +        "node": ">=10"
               },
               "funding": {
        -        "url": "https://github.com/sindresorhus/np?sponsor=1"
        +        "url": "https://github.com/chalk/chalk?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/ansi-escapes": {
        -      "version": "3.2.0",
        -      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
        -      "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
        +    "node_modules/inquirer/node_modules/color-convert": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
        +      "dependencies": {
        +        "color-name": "~1.1.4"
        +      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/np/node_modules/ansi-regex": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
        -      "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=4"
        -      }
        +    "node_modules/inquirer/node_modules/emoji-regex": {
        +      "version": "8.0.0",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        +      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        +      "dev": true
             },
        -    "node_modules/np/node_modules/ansi-styles": {
        -      "version": "4.3.0",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        -      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
        +    "node_modules/inquirer/node_modules/figures": {
        +      "version": "3.2.0",
        +      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
        +      "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
               "dev": true,
               "dependencies": {
        -        "color-convert": "^2.0.1"
        +        "escape-string-regexp": "^1.0.5"
               },
               "engines": {
                 "node": ">=8"
               },
               "funding": {
        -        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/argparse": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
        -      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
        -      "dev": true
        +    "node_modules/inquirer/node_modules/has-flag": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        +      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=8"
        +      }
             },
        -    "node_modules/np/node_modules/chalk": {
        -      "version": "5.3.0",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
        -      "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
        +    "node_modules/inquirer/node_modules/is-fullwidth-code-point": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        +      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
               "dev": true,
               "engines": {
        -        "node": "^12.17.0 || ^14.13 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/chalk?sponsor=1"
        +        "node": ">=8"
               }
             },
        -    "node_modules/np/node_modules/cli-cursor": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
        -      "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==",
        +    "node_modules/inquirer/node_modules/rxjs": {
        +      "version": "6.6.7",
        +      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
        +      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
               "dev": true,
               "dependencies": {
        -        "restore-cursor": "^2.0.0"
        +        "tslib": "^1.9.0"
               },
               "engines": {
        -        "node": ">=4"
        +        "npm": ">=2.0.0"
               }
             },
        -    "node_modules/np/node_modules/cli-spinners": {
        -      "version": "2.9.0",
        -      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz",
        -      "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==",
        +    "node_modules/inquirer/node_modules/string-width": {
        +      "version": "4.2.3",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
        +      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
               "dev": true,
        -      "engines": {
        -        "node": ">=6"
        +      "dependencies": {
        +        "emoji-regex": "^8.0.0",
        +        "is-fullwidth-code-point": "^3.0.0",
        +        "strip-ansi": "^6.0.1"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "engines": {
        +        "node": ">=8"
               }
             },
        -    "node_modules/np/node_modules/cli-width": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.0.0.tgz",
        -      "integrity": "sha512-ZksGS2xpa/bYkNzN3BAw1wEjsLV/ZKOf/CCrJ/QOBsxx6fOARIkwTutxp1XIOIohi6HKmOFjMoK/XaqDVUpEEw==",
        +    "node_modules/inquirer/node_modules/strip-ansi": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        +      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
               "dev": true,
        +      "dependencies": {
        +        "ansi-regex": "^5.0.1"
        +      },
               "engines": {
        -        "node": ">= 12"
        +        "node": ">=8"
               }
             },
        -    "node_modules/np/node_modules/color-convert": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        -      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
        +    "node_modules/inquirer/node_modules/supports-color": {
        +      "version": "7.2.0",
        +      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        +      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
               "dev": true,
               "dependencies": {
        -        "color-name": "~1.1.4"
        +        "has-flag": "^4.0.0"
               },
               "engines": {
        -        "node": ">=7.0.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/np/node_modules/cosmiconfig": {
        -      "version": "8.2.0",
        -      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz",
        -      "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==",
        +    "node_modules/ip-address": {
        +      "version": "9.0.5",
        +      "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
        +      "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
               "dev": true,
               "dependencies": {
        -        "import-fresh": "^3.2.1",
        -        "js-yaml": "^4.1.0",
        -        "parse-json": "^5.0.0",
        -        "path-type": "^4.0.0"
        +        "jsbn": "1.1.0",
        +        "sprintf-js": "^1.1.3"
               },
               "engines": {
        -        "node": ">=14"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/d-fischer"
        +        "node": ">= 12"
               }
             },
        -    "node_modules/np/node_modules/emoji-regex": {
        -      "version": "8.0.0",
        -      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        -      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        +    "node_modules/ip-address/node_modules/sprintf-js": {
        +      "version": "1.1.3",
        +      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
        +      "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
               "dev": true
             },
        -    "node_modules/np/node_modules/escape-string-regexp": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
        -      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
        +    "node_modules/is-absolute": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
        +      "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
               "dev": true,
        +      "dependencies": {
        +        "is-relative": "^1.0.0",
        +        "is-windows": "^1.0.1"
        +      },
               "engines": {
        -        "node": ">=12"
        +        "node": ">=0.10.0"
        +      }
        +    },
        +    "node_modules/is-arrayish": {
        +      "version": "0.2.1",
        +      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
        +      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
        +      "dev": true
        +    },
        +    "node_modules/is-binary-path": {
        +      "version": "2.1.0",
        +      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
        +      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
        +      "dev": true,
        +      "dependencies": {
        +        "binary-extensions": "^2.0.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "engines": {
        +        "node": ">=8"
               }
             },
        -    "node_modules/np/node_modules/exit-hook": {
        -      "version": "3.2.0",
        -      "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-3.2.0.tgz",
        -      "integrity": "sha512-aIQN7Q04HGAV/I5BszisuHTZHXNoC23WtLkxdCLuYZMdWviRD0TMIt2bnUBi9MrHaF/hH8b3gwG9iaAUHKnJGA==",
        +    "node_modules/is-core-module": {
        +      "version": "2.16.1",
        +      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
        +      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
               "dev": true,
        +      "dependencies": {
        +        "hasown": "^2.0.2"
        +      },
               "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +        "node": ">= 0.4"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/sponsors/ljharb"
               }
             },
        -    "node_modules/np/node_modules/find-up": {
        -      "version": "6.3.0",
        -      "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz",
        -      "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==",
        +    "node_modules/is-docker": {
        +      "version": "2.2.1",
        +      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
        +      "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
               "dev": true,
        -      "dependencies": {
        -        "locate-path": "^7.1.0",
        -        "path-exists": "^5.0.0"
        +      "bin": {
        +        "is-docker": "cli.js"
               },
               "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +        "node": ">=8"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/has-flag": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        -      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        +    "node_modules/is-extglob": {
        +      "version": "2.1.1",
        +      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
        +      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/np/node_modules/hosted-git-info": {
        -      "version": "6.1.1",
        -      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz",
        -      "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==",
        +    "node_modules/is-glob": {
        +      "version": "4.0.3",
        +      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
        +      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
               "dev": true,
               "dependencies": {
        -        "lru-cache": "^7.5.1"
        +        "is-extglob": "^2.1.1"
               },
               "engines": {
        -        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/np/node_modules/indent-string": {
        -      "version": "3.2.0",
        -      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
        -      "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==",
        +    "node_modules/is-module": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
        +      "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
        +      "dev": true
        +    },
        +    "node_modules/is-node-process": {
        +      "version": "1.2.0",
        +      "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz",
        +      "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==",
        +      "dev": true
        +    },
        +    "node_modules/is-number": {
        +      "version": "7.0.0",
        +      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
        +      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
               "dev": true,
               "engines": {
        -        "node": ">=4"
        +        "node": ">=0.12.0"
               }
             },
        -    "node_modules/np/node_modules/inquirer": {
        -      "version": "9.2.7",
        -      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.7.tgz",
        -      "integrity": "sha512-Bf52lnfvNxGPJPltiNO2tLBp3zC339KNlGMqOkW+dsvNikBhcVDK5kqU2lVX2FTPzuXUFX5WJDlsw//w3ZwoTw==",
        +    "node_modules/is-reference": {
        +      "version": "1.2.1",
        +      "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
        +      "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
               "dev": true,
               "dependencies": {
        -        "ansi-escapes": "^4.3.2",
        -        "chalk": "^5.2.0",
        -        "cli-cursor": "^3.1.0",
        -        "cli-width": "^4.0.0",
        -        "external-editor": "^3.0.3",
        -        "figures": "^5.0.0",
        -        "lodash": "^4.17.21",
        -        "mute-stream": "1.0.0",
        -        "ora": "^5.4.1",
        -        "run-async": "^3.0.0",
        -        "rxjs": "^7.8.1",
        -        "string-width": "^4.2.3",
        -        "strip-ansi": "^6.0.1",
        -        "through": "^2.3.6",
        -        "wrap-ansi": "^6.0.1"
        -      },
        -      "engines": {
        -        "node": ">=14.18.0"
        +        "@types/estree": "*"
               }
             },
        -    "node_modules/np/node_modules/inquirer/node_modules/ansi-escapes": {
        -      "version": "4.3.2",
        -      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
        -      "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
        +    "node_modules/is-relative": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
        +      "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
               "dev": true,
               "dependencies": {
        -        "type-fest": "^0.21.3"
        -      },
        -      "engines": {
        -        "node": ">=8"
        +        "is-unc-path": "^1.0.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/np/node_modules/inquirer/node_modules/ansi-regex": {
        -      "version": "5.0.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        -      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
        -      "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/np/node_modules/inquirer/node_modules/cli-cursor": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
        -      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
        +    "node_modules/is-ssh": {
        +      "version": "1.4.0",
        +      "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz",
        +      "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==",
               "dev": true,
               "dependencies": {
        -        "restore-cursor": "^3.1.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        +        "protocols": "^2.0.1"
               }
             },
        -    "node_modules/np/node_modules/inquirer/node_modules/figures": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz",
        -      "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==",
        +    "node_modules/is-unc-path": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
        +      "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
               "dev": true,
               "dependencies": {
        -        "escape-string-regexp": "^5.0.0",
        -        "is-unicode-supported": "^1.2.0"
        +        "unc-path-regex": "^0.1.2"
               },
               "engines": {
        -        "node": ">=14"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/np/node_modules/inquirer/node_modules/is-fullwidth-code-point": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        -      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
        +    "node_modules/is-windows": {
        +      "version": "1.0.2",
        +      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
        +      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/np/node_modules/inquirer/node_modules/is-unicode-supported": {
        -      "version": "1.3.0",
        -      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
        -      "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
        +    "node_modules/isarray": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
        +      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
        +      "dev": true
        +    },
        +    "node_modules/isexe": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
        +      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
        +      "dev": true
        +    },
        +    "node_modules/jackspeak": {
        +      "version": "3.4.3",
        +      "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
        +      "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
               "dev": true,
        -      "engines": {
        -        "node": ">=12"
        +      "dependencies": {
        +        "@isaacs/cliui": "^8.0.2"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/sponsors/isaacs"
        +      },
        +      "optionalDependencies": {
        +        "@pkgjs/parseargs": "^0.11.0"
               }
             },
        -    "node_modules/np/node_modules/inquirer/node_modules/mimic-fn": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
        -      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=6"
        -      }
        +    "node_modules/js-tokens": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
        +      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
        +      "dev": true
             },
        -    "node_modules/np/node_modules/inquirer/node_modules/onetime": {
        -      "version": "5.1.2",
        -      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
        -      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
        +    "node_modules/jsbn": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
        +      "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
        +      "dev": true
        +    },
        +    "node_modules/jsesc": {
        +      "version": "3.1.0",
        +      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
        +      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
               "dev": true,
        -      "dependencies": {
        -        "mimic-fn": "^2.1.0"
        +      "bin": {
        +        "jsesc": "bin/jsesc"
               },
               "engines": {
                 "node": ">=6"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/inquirer/node_modules/restore-cursor": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
        -      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
        +    "node_modules/json-fixer": {
        +      "version": "1.6.5",
        +      "resolved": "https://registry.npmjs.org/json-fixer/-/json-fixer-1.6.5.tgz",
        +      "integrity": "sha512-ewOhCI/b7Wx0DtO7ZhDp4SW5sjvp5dBWoeGnjta7mXPrvopvcE6TYGIqo+XREhzr/hKz7Bf3e2C0TSuoGFxAYA==",
               "dev": true,
               "dependencies": {
        -        "onetime": "^5.1.0",
        -        "signal-exit": "^3.0.2"
        +        "@babel/runtime": "^7.11.2",
        +        "chalk": "^4.1.0",
        +        "pegjs": "^0.10.0"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=10"
               }
             },
        -    "node_modules/np/node_modules/inquirer/node_modules/string-width": {
        -      "version": "4.2.3",
        -      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
        -      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
        +    "node_modules/json-fixer/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
               "dependencies": {
        -        "emoji-regex": "^8.0.0",
        -        "is-fullwidth-code-point": "^3.0.0",
        -        "strip-ansi": "^6.0.1"
        +        "color-convert": "^2.0.1"
               },
               "engines": {
                 "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/inquirer/node_modules/strip-ansi": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        -      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
        +    "node_modules/json-fixer/node_modules/chalk": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
        +      "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
               "dev": true,
               "dependencies": {
        -        "ansi-regex": "^5.0.1"
        +        "ansi-styles": "^4.1.0",
        +        "supports-color": "^7.1.0"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/chalk?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/inquirer/node_modules/wrap-ansi": {
        -      "version": "6.2.0",
        -      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
        -      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
        +    "node_modules/json-fixer/node_modules/color-convert": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
               "dependencies": {
        -        "ansi-styles": "^4.0.0",
        -        "string-width": "^4.1.0",
        -        "strip-ansi": "^6.0.0"
        +        "color-name": "~1.1.4"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/np/node_modules/is-fullwidth-code-point": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
        -      "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
        +    "node_modules/json-fixer/node_modules/has-flag": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        +      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
               "dev": true,
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
               }
             },
        -    "node_modules/np/node_modules/is-wsl": {
        -      "version": "2.2.0",
        -      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
        -      "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
        +    "node_modules/json-fixer/node_modules/supports-color": {
        +      "version": "7.2.0",
        +      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        +      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
               "dev": true,
               "dependencies": {
        -        "is-docker": "^2.0.0"
        +        "has-flag": "^4.0.0"
               },
               "engines": {
                 "node": ">=8"
               }
             },
        -    "node_modules/np/node_modules/js-yaml": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
        -      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
        +    "node_modules/json-parse-even-better-errors": {
        +      "version": "2.3.1",
        +      "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
        +      "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
        +      "dev": true
        +    },
        +    "node_modules/json-schema-traverse": {
        +      "version": "0.4.1",
        +      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
        +      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
        +      "dev": true
        +    },
        +    "node_modules/json-stable-stringify-without-jsonify": {
        +      "version": "1.0.1",
        +      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
        +      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
        +      "dev": true
        +    },
        +    "node_modules/json5": {
        +      "version": "2.2.3",
        +      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
        +      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
               "dev": true,
        -      "dependencies": {
        -        "argparse": "^2.0.1"
        -      },
               "bin": {
        -        "js-yaml": "bin/js-yaml.js"
        +        "json5": "lib/cli.js"
        +      },
        +      "engines": {
        +        "node": ">=6"
               }
             },
        -    "node_modules/np/node_modules/listr": {
        -      "version": "0.14.3",
        -      "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz",
        -      "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==",
        +    "node_modules/jszip": {
        +      "version": "3.10.1",
        +      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
        +      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
               "dev": true,
               "dependencies": {
        -        "@samverschueren/stream-to-observable": "^0.3.0",
        -        "is-observable": "^1.1.0",
        -        "is-promise": "^2.1.0",
        -        "is-stream": "^1.1.0",
        -        "listr-silent-renderer": "^1.1.1",
        -        "listr-update-renderer": "^0.5.0",
        -        "listr-verbose-renderer": "^0.5.0",
        -        "p-map": "^2.0.0",
        -        "rxjs": "^6.3.3"
        -      },
        -      "engines": {
        -        "node": ">=6"
        +        "lie": "~3.3.0",
        +        "pako": "~1.0.2",
        +        "readable-stream": "~2.3.6",
        +        "setimmediate": "^1.0.5"
               }
             },
        -    "node_modules/np/node_modules/listr-update-renderer": {
        -      "version": "0.5.0",
        -      "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz",
        -      "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==",
        +    "node_modules/jszip/node_modules/pako": {
        +      "version": "1.0.11",
        +      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
        +      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
        +      "dev": true
        +    },
        +    "node_modules/jszip/node_modules/process-nextick-args": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
        +      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
        +      "dev": true
        +    },
        +    "node_modules/jszip/node_modules/readable-stream": {
        +      "version": "2.3.8",
        +      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
        +      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^1.1.3",
        -        "cli-truncate": "^0.2.1",
        -        "elegant-spinner": "^1.0.1",
        -        "figures": "^1.7.0",
        -        "indent-string": "^3.0.0",
        -        "log-symbols": "^1.0.2",
        -        "log-update": "^2.3.0",
        -        "strip-ansi": "^3.0.1"
        -      },
        -      "engines": {
        -        "node": ">=6"
        -      },
        -      "peerDependencies": {
        -        "listr": "^0.14.2"
        +        "core-util-is": "~1.0.0",
        +        "inherits": "~2.0.3",
        +        "isarray": "~1.0.0",
        +        "process-nextick-args": "~2.0.0",
        +        "safe-buffer": "~5.1.1",
        +        "string_decoder": "~1.1.1",
        +        "util-deprecate": "~1.0.1"
               }
             },
        -    "node_modules/np/node_modules/listr-update-renderer/node_modules/ansi-styles": {
        -      "version": "2.2.1",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
        -      "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
        +    "node_modules/jszip/node_modules/string_decoder": {
        +      "version": "1.1.1",
        +      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
        +      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "dependencies": {
        +        "safe-buffer": "~5.1.0"
               }
             },
        -    "node_modules/np/node_modules/listr-update-renderer/node_modules/chalk": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
        -      "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
        +    "node_modules/kleur": {
        +      "version": "4.1.5",
        +      "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
        +      "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
               "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^2.2.1",
        -        "escape-string-regexp": "^1.0.2",
        -        "has-ansi": "^2.0.0",
        -        "strip-ansi": "^3.0.0",
        -        "supports-color": "^2.0.0"
        -      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=6"
               }
             },
        -    "node_modules/np/node_modules/listr-update-renderer/node_modules/escape-string-regexp": {
        -      "version": "1.0.5",
        -      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
        -      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
        +    "node_modules/konan": {
        +      "version": "2.1.1",
        +      "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz",
        +      "integrity": "sha512-7ZhYV84UzJ0PR/RJnnsMZcAbn+kLasJhVNWsu8ZyVEJYRpGA5XESQ9d/7zOa08U0Ou4cmB++hMNY/3OSV9KIbg==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.8.0"
        +      "dependencies": {
        +        "@babel/parser": "^7.10.5",
        +        "@babel/traverse": "^7.10.5"
               }
             },
        -    "node_modules/np/node_modules/listr-update-renderer/node_modules/log-symbols": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
        -      "integrity": "sha512-mmPrW0Fh2fxOzdBbFv4g1m6pR72haFLPJ2G5SJEELf1y+iaQrDG6cWCPjy54RHYbZAt7X+ls690Kw62AdWXBzQ==",
        +    "node_modules/lazystream": {
        +      "version": "1.0.1",
        +      "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
        +      "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^1.0.0"
        +        "readable-stream": "^2.0.5"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">= 0.6.3"
               }
             },
        -    "node_modules/np/node_modules/listr-update-renderer/node_modules/supports-color": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
        -      "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
        +    "node_modules/levn": {
        +      "version": "0.4.1",
        +      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
        +      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
               "dev": true,
        +      "dependencies": {
        +        "prelude-ls": "^1.2.1",
        +        "type-check": "~0.4.0"
        +      },
               "engines": {
        -        "node": ">=0.8.0"
        +        "node": ">= 0.8.0"
               }
             },
        -    "node_modules/np/node_modules/listr-verbose-renderer": {
        -      "version": "0.5.0",
        -      "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz",
        -      "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==",
        +    "node_modules/libtess": {
        +      "version": "1.2.2",
        +      "resolved": "https://registry.npmjs.org/libtess/-/libtess-1.2.2.tgz",
        +      "integrity": "sha1-Fz61KhpXoCOP5I8F0SJFB0SX+Jg="
        +    },
        +    "node_modules/lie": {
        +      "version": "3.3.0",
        +      "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
        +      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^2.4.1",
        -        "cli-cursor": "^2.1.0",
        -        "date-fns": "^1.27.2",
        -        "figures": "^2.0.0"
        -      },
        -      "engines": {
        -        "node": ">=4"
        +        "immediate": "~3.0.5"
               }
             },
        -    "node_modules/np/node_modules/listr-verbose-renderer/node_modules/ansi-styles": {
        -      "version": "3.2.1",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
        -      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
        +    "node_modules/lilconfig": {
        +      "version": "3.1.3",
        +      "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
        +      "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
               "dev": true,
        -      "dependencies": {
        -        "color-convert": "^1.9.0"
        -      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=14"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/antonk52"
               }
             },
        -    "node_modules/np/node_modules/listr-verbose-renderer/node_modules/chalk": {
        -      "version": "2.4.2",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
        -      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
        +    "node_modules/lines-and-columns": {
        +      "version": "1.1.6",
        +      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
        +      "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
        +      "dev": true
        +    },
        +    "node_modules/lint-staged": {
        +      "version": "15.4.1",
        +      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.4.1.tgz",
        +      "integrity": "sha512-P8yJuVRyLrm5KxCtFx+gjI5Bil+wO7wnTl7C3bXhvtTaAFGirzeB24++D0wGoUwxrUKecNiehemgCob9YL39NA==",
               "dev": true,
               "dependencies": {
        -        "ansi-styles": "^3.2.1",
        -        "escape-string-regexp": "^1.0.5",
        -        "supports-color": "^5.3.0"
        +        "chalk": "~5.4.1",
        +        "commander": "~12.1.0",
        +        "debug": "~4.4.0",
        +        "execa": "~8.0.1",
        +        "lilconfig": "~3.1.3",
        +        "listr2": "~8.2.5",
        +        "micromatch": "~4.0.8",
        +        "pidtree": "~0.6.0",
        +        "string-argv": "~0.3.2",
        +        "yaml": "~2.6.1"
        +      },
        +      "bin": {
        +        "lint-staged": "bin/lint-staged.js"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=18.12.0"
        +      },
        +      "funding": {
        +        "url": "https://opencollective.com/lint-staged"
               }
             },
        -    "node_modules/np/node_modules/listr-verbose-renderer/node_modules/color-convert": {
        -      "version": "1.9.3",
        -      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
        -      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
        +    "node_modules/lint-staged/node_modules/chalk": {
        +      "version": "5.4.1",
        +      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
        +      "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
               "dev": true,
        -      "dependencies": {
        -        "color-name": "1.1.3"
        +      "engines": {
        +        "node": "^12.17.0 || ^14.13 || >=16.0.0"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/chalk?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/listr-verbose-renderer/node_modules/color-name": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
        -      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
        -      "dev": true
        -    },
        -    "node_modules/np/node_modules/listr-verbose-renderer/node_modules/escape-string-regexp": {
        -      "version": "1.0.5",
        -      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
        -      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
        +    "node_modules/lint-staged/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
        +      "dependencies": {
        +        "ms": "^2.1.3"
        +      },
               "engines": {
        -        "node": ">=0.8.0"
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/np/node_modules/listr-verbose-renderer/node_modules/figures": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
        -      "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==",
        +    "node_modules/lint-staged/node_modules/execa": {
        +      "version": "8.0.1",
        +      "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
        +      "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
               "dev": true,
               "dependencies": {
        -        "escape-string-regexp": "^1.0.5"
        +        "cross-spawn": "^7.0.3",
        +        "get-stream": "^8.0.1",
        +        "human-signals": "^5.0.0",
        +        "is-stream": "^3.0.0",
        +        "merge-stream": "^2.0.0",
        +        "npm-run-path": "^5.1.0",
        +        "onetime": "^6.0.0",
        +        "signal-exit": "^4.1.0",
        +        "strip-final-newline": "^3.0.0"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=16.17"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sindresorhus/execa?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/listr-verbose-renderer/node_modules/has-flag": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
        -      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
        +    "node_modules/lint-staged/node_modules/get-stream": {
        +      "version": "8.0.1",
        +      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
        +      "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
               "dev": true,
               "engines": {
        -        "node": ">=4"
        +        "node": ">=16"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/listr-verbose-renderer/node_modules/supports-color": {
        -      "version": "5.5.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
        -      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
        +    "node_modules/lint-staged/node_modules/human-signals": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
        +      "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
               "dev": true,
        -      "dependencies": {
        -        "has-flag": "^3.0.0"
        -      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=16.17.0"
               }
             },
        -    "node_modules/np/node_modules/listr/node_modules/rxjs": {
        -      "version": "6.6.7",
        -      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
        -      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
        +    "node_modules/lint-staged/node_modules/is-stream": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
        +      "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
               "dev": true,
        -      "dependencies": {
        -        "tslib": "^1.9.0"
        -      },
               "engines": {
        -        "npm": ">=2.0.0"
        +        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/locate-path": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
        -      "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
        +    "node_modules/lint-staged/node_modules/mimic-fn": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
        +      "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
               "dev": true,
        -      "dependencies": {
        -        "p-locate": "^6.0.0"
        -      },
               "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +        "node": ">=12"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/log-symbols": {
        -      "version": "5.1.0",
        -      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz",
        -      "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==",
        +    "node_modules/lint-staged/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        +      "dev": true
        +    },
        +    "node_modules/lint-staged/node_modules/onetime": {
        +      "version": "6.0.0",
        +      "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
        +      "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^5.0.0",
        -        "is-unicode-supported": "^1.1.0"
        +        "mimic-fn": "^4.0.0"
               },
               "engines": {
                 "node": ">=12"
        @@ -12505,78 +7692,97 @@
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/log-symbols/node_modules/is-unicode-supported": {
        -      "version": "1.3.0",
        -      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
        -      "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
        +    "node_modules/lint-staged/node_modules/signal-exit": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
        +      "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
               "dev": true,
               "engines": {
        -        "node": ">=12"
        +        "node": ">=14"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/np/node_modules/log-update": {
        -      "version": "2.3.0",
        -      "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz",
        -      "integrity": "sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg==",
        +    "node_modules/lint-staged/node_modules/yaml": {
        +      "version": "2.6.1",
        +      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
        +      "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
               "dev": true,
        -      "dependencies": {
        -        "ansi-escapes": "^3.0.0",
        -        "cli-cursor": "^2.0.0",
        -        "wrap-ansi": "^3.0.1"
        +      "bin": {
        +        "yaml": "bin.mjs"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/np/node_modules/lru-cache": {
        -      "version": "7.18.3",
        -      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
        -      "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
        +    "node_modules/listr2": {
        +      "version": "8.2.5",
        +      "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz",
        +      "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==",
               "dev": true,
        +      "dependencies": {
        +        "cli-truncate": "^4.0.0",
        +        "colorette": "^2.0.20",
        +        "eventemitter3": "^5.0.1",
        +        "log-update": "^6.1.0",
        +        "rfdc": "^1.4.1",
        +        "wrap-ansi": "^9.0.0"
        +      },
               "engines": {
        -        "node": ">=12"
        +        "node": ">=18.0.0"
               }
             },
        -    "node_modules/np/node_modules/mimic-fn": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
        -      "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
        +    "node_modules/listr2/node_modules/ansi-regex": {
        +      "version": "6.1.0",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
        +      "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
               "dev": true,
               "engines": {
        -        "node": ">=4"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/mute-stream": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
        -      "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==",
        +    "node_modules/listr2/node_modules/ansi-styles": {
        +      "version": "6.2.1",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
        +      "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
               "dev": true,
               "engines": {
        -        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/onetime": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
        -      "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
        +    "node_modules/listr2/node_modules/cli-truncate": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
        +      "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
               "dev": true,
               "dependencies": {
        -        "mimic-fn": "^4.0.0"
        +        "slice-ansi": "^5.0.0",
        +        "string-width": "^7.0.0"
               },
               "engines": {
        -        "node": ">=12"
        +        "node": ">=18"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/onetime/node_modules/mimic-fn": {
        +    "node_modules/listr2/node_modules/emoji-regex": {
        +      "version": "10.4.0",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
        +      "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
        +      "dev": true
        +    },
        +    "node_modules/listr2/node_modules/is-fullwidth-code-point": {
               "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
        -      "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
        +      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
        +      "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
               "dev": true,
               "engines": {
                 "node": ">=12"
        @@ -12585,1484 +7791,1881 @@
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/open": {
        -      "version": "9.1.0",
        -      "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
        -      "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
        +    "node_modules/listr2/node_modules/slice-ansi": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
        +      "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
               "dev": true,
               "dependencies": {
        -        "default-browser": "^4.0.0",
        -        "define-lazy-prop": "^3.0.0",
        -        "is-inside-container": "^1.0.0",
        -        "is-wsl": "^2.2.0"
        +        "ansi-styles": "^6.0.0",
        +        "is-fullwidth-code-point": "^4.0.0"
               },
               "engines": {
        -        "node": ">=14.16"
        +        "node": ">=12"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/chalk/slice-ansi?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/ora": {
        -      "version": "5.4.1",
        -      "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
        -      "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
        +    "node_modules/listr2/node_modules/string-width": {
        +      "version": "7.2.0",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
        +      "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
               "dev": true,
               "dependencies": {
        -        "bl": "^4.1.0",
        -        "chalk": "^4.1.0",
        -        "cli-cursor": "^3.1.0",
        -        "cli-spinners": "^2.5.0",
        -        "is-interactive": "^1.0.0",
        -        "is-unicode-supported": "^0.1.0",
        -        "log-symbols": "^4.1.0",
        -        "strip-ansi": "^6.0.0",
        -        "wcwidth": "^1.0.1"
        +        "emoji-regex": "^10.3.0",
        +        "get-east-asian-width": "^1.0.0",
        +        "strip-ansi": "^7.1.0"
               },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=18"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/ora/node_modules/ansi-regex": {
        -      "version": "5.0.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        -      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
        +    "node_modules/listr2/node_modules/strip-ansi": {
        +      "version": "7.1.0",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
        +      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
               "dev": true,
        +      "dependencies": {
        +        "ansi-regex": "^6.0.1"
        +      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/ora/node_modules/chalk": {
        -      "version": "4.1.2",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        -      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
        +    "node_modules/listr2/node_modules/wrap-ansi": {
        +      "version": "9.0.0",
        +      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
        +      "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
               "dev": true,
               "dependencies": {
        -        "ansi-styles": "^4.1.0",
        -        "supports-color": "^7.1.0"
        +        "ansi-styles": "^6.2.1",
        +        "string-width": "^7.0.0",
        +        "strip-ansi": "^7.1.0"
               },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=18"
               },
               "funding": {
        -        "url": "https://github.com/chalk/chalk?sponsor=1"
        +        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/ora/node_modules/cli-cursor": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
        -      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
        +    "node_modules/locate-app": {
        +      "version": "2.5.0",
        +      "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.5.0.tgz",
        +      "integrity": "sha512-xIqbzPMBYArJRmPGUZD9CzV9wOqmVtQnaAn3wrj3s6WYW0bQvPI7x+sPYUGmDTYMHefVK//zc6HEYZ1qnxIK+Q==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "individual",
        +          "url": "https://buymeacoffee.com/hejny"
        +        },
        +        {
        +          "type": "github",
        +          "url": "https://github.com/hejny/locate-app/blob/main/README.md#%EF%B8%8F-contributing"
        +        }
        +      ],
               "dependencies": {
        -        "restore-cursor": "^3.1.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        +        "@promptbook/utils": "0.69.5",
        +        "type-fest": "4.26.0",
        +        "userhome": "1.0.1"
               }
             },
        -    "node_modules/np/node_modules/ora/node_modules/is-interactive": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
        -      "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
        +    "node_modules/locate-app/node_modules/type-fest": {
        +      "version": "4.26.0",
        +      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.0.tgz",
        +      "integrity": "sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw==",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=16"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/ora/node_modules/log-symbols": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
        -      "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
        +    "node_modules/lodash": {
        +      "version": "4.17.21",
        +      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
        +      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
        +      "dev": true
        +    },
        +    "node_modules/lodash.clonedeep": {
        +      "version": "4.5.0",
        +      "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
        +      "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
        +      "dev": true
        +    },
        +    "node_modules/lodash.merge": {
        +      "version": "4.6.2",
        +      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
        +      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
        +      "dev": true
        +    },
        +    "node_modules/lodash.zip": {
        +      "version": "4.2.0",
        +      "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz",
        +      "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==",
        +      "dev": true
        +    },
        +    "node_modules/log-update": {
        +      "version": "6.1.0",
        +      "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
        +      "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^4.1.0",
        -        "is-unicode-supported": "^0.1.0"
        +        "ansi-escapes": "^7.0.0",
        +        "cli-cursor": "^5.0.0",
        +        "slice-ansi": "^7.1.0",
        +        "strip-ansi": "^7.1.0",
        +        "wrap-ansi": "^9.0.0"
               },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=18"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/ora/node_modules/mimic-fn": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
        -      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/np/node_modules/ora/node_modules/onetime": {
        -      "version": "5.1.2",
        -      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
        -      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
        +    "node_modules/log-update/node_modules/ansi-escapes": {
        +      "version": "7.0.0",
        +      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
        +      "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
               "dev": true,
               "dependencies": {
        -        "mimic-fn": "^2.1.0"
        +        "environment": "^1.0.0"
               },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=18"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/ora/node_modules/restore-cursor": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
        -      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
        +    "node_modules/log-update/node_modules/ansi-regex": {
        +      "version": "6.1.0",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
        +      "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
               "dev": true,
        -      "dependencies": {
        -        "onetime": "^5.1.0",
        -        "signal-exit": "^3.0.2"
        -      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/ora/node_modules/strip-ansi": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        -      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
        +    "node_modules/log-update/node_modules/ansi-styles": {
        +      "version": "6.2.1",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
        +      "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
               "dev": true,
        -      "dependencies": {
        -        "ansi-regex": "^5.0.1"
        -      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/p-limit": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
        -      "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
        +    "node_modules/log-update/node_modules/cli-cursor": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
        +      "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
               "dev": true,
               "dependencies": {
        -        "yocto-queue": "^1.0.0"
        +        "restore-cursor": "^5.0.0"
               },
               "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +        "node": ">=18"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/p-locate": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
        -      "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
        +    "node_modules/log-update/node_modules/emoji-regex": {
        +      "version": "10.4.0",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
        +      "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
        +      "dev": true
        +    },
        +    "node_modules/log-update/node_modules/onetime": {
        +      "version": "7.0.0",
        +      "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
        +      "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
               "dev": true,
               "dependencies": {
        -        "p-limit": "^4.0.0"
        +        "mimic-function": "^5.0.0"
               },
               "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +        "node": ">=18"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/p-map": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
        -      "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/np/node_modules/parse-json": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
        -      "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
        +    "node_modules/log-update/node_modules/restore-cursor": {
        +      "version": "5.1.0",
        +      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
        +      "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
               "dev": true,
               "dependencies": {
        -        "@babel/code-frame": "^7.0.0",
        -        "error-ex": "^1.3.1",
        -        "json-parse-even-better-errors": "^2.3.0",
        -        "lines-and-columns": "^1.1.6"
        +        "onetime": "^7.0.0",
        +        "signal-exit": "^4.1.0"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=18"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/path-exists": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
        -      "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
        +    "node_modules/log-update/node_modules/signal-exit": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
        +      "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
               "dev": true,
               "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        +        "node": ">=14"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        -    "node_modules/np/node_modules/pkg-dir": {
        -      "version": "7.0.0",
        -      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz",
        -      "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==",
        +    "node_modules/log-update/node_modules/string-width": {
        +      "version": "7.2.0",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
        +      "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
               "dev": true,
               "dependencies": {
        -        "find-up": "^6.3.0"
        +        "emoji-regex": "^10.3.0",
        +        "get-east-asian-width": "^1.0.0",
        +        "strip-ansi": "^7.1.0"
               },
               "engines": {
        -        "node": ">=14.16"
        +        "node": ">=18"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/restore-cursor": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
        -      "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==",
        +    "node_modules/log-update/node_modules/strip-ansi": {
        +      "version": "7.1.0",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
        +      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
               "dev": true,
               "dependencies": {
        -        "onetime": "^2.0.0",
        -        "signal-exit": "^3.0.2"
        +        "ansi-regex": "^6.0.1"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/restore-cursor/node_modules/onetime": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
        -      "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==",
        +    "node_modules/log-update/node_modules/wrap-ansi": {
        +      "version": "9.0.0",
        +      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
        +      "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
               "dev": true,
               "dependencies": {
        -        "mimic-fn": "^1.0.0"
        +        "ansi-styles": "^6.2.1",
        +        "string-width": "^7.0.0",
        +        "strip-ansi": "^7.1.0"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=18"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
               }
             },
        -    "node_modules/np/node_modules/run-async": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz",
        -      "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==",
        +    "node_modules/loglevel": {
        +      "version": "1.9.2",
        +      "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
        +      "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
               "dev": true,
               "engines": {
        -        "node": ">=0.12.0"
        +        "node": ">= 0.6.0"
        +      },
        +      "funding": {
        +        "type": "tidelift",
        +        "url": "https://tidelift.com/funding/github/npm/loglevel"
               }
             },
        -    "node_modules/np/node_modules/rxjs": {
        -      "version": "7.8.1",
        -      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
        -      "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
        +    "node_modules/loglevel-plugin-prefix": {
        +      "version": "0.8.4",
        +      "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz",
        +      "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==",
        +      "dev": true
        +    },
        +    "node_modules/longest-streak": {
        +      "version": "3.1.0",
        +      "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
        +      "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
               "dev": true,
        -      "dependencies": {
        -        "tslib": "^2.1.0"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/np/node_modules/rxjs/node_modules/tslib": {
        -      "version": "2.6.0",
        -      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
        -      "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==",
        +    "node_modules/loupe": {
        +      "version": "3.1.2",
        +      "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
        +      "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
               "dev": true
             },
        -    "node_modules/np/node_modules/semver": {
        -      "version": "7.5.4",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
        -      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
        +    "node_modules/lz-string": {
        +      "version": "1.5.0",
        +      "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
        +      "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
               "dev": true,
        -      "dependencies": {
        -        "lru-cache": "^6.0.0"
        -      },
               "bin": {
        -        "semver": "bin/semver.js"
        -      },
        -      "engines": {
        -        "node": ">=10"
        +        "lz-string": "bin/bin.js"
               }
             },
        -    "node_modules/np/node_modules/semver/node_modules/lru-cache": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
        -      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
        +    "node_modules/magic-string": {
        +      "version": "0.30.17",
        +      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
        +      "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
               "dev": true,
               "dependencies": {
        -        "yallist": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        +        "@jridgewell/sourcemap-codec": "^1.5.0"
               }
             },
        -    "node_modules/np/node_modules/string-width": {
        -      "version": "2.1.1",
        -      "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
        -      "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
        +    "node_modules/map-cache": {
        +      "version": "0.2.2",
        +      "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
        +      "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
               "dev": true,
        -      "dependencies": {
        -        "is-fullwidth-code-point": "^2.0.0",
        -        "strip-ansi": "^4.0.0"
        -      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/np/node_modules/string-width/node_modules/strip-ansi": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
        -      "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
        +    "node_modules/markdown-table": {
        +      "version": "3.0.4",
        +      "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
        +      "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
               "dev": true,
        -      "dependencies": {
        -        "ansi-regex": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=4"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/np/node_modules/supports-color": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        -      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        +    "node_modules/mdast-util-definitions": {
        +      "version": "5.1.2",
        +      "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",
        +      "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==",
               "dev": true,
               "dependencies": {
        -        "has-flag": "^4.0.0"
        +        "@types/mdast": "^3.0.0",
        +        "@types/unist": "^2.0.0",
        +        "unist-util-visit": "^4.0.0"
               },
        -      "engines": {
        -        "node": ">=8"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/np/node_modules/symbol-observable": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
        -      "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==",
        +    "node_modules/mdast-util-find-and-replace": {
        +      "version": "2.2.2",
        +      "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz",
        +      "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.10"
        +      "dependencies": {
        +        "@types/mdast": "^3.0.0",
        +        "escape-string-regexp": "^5.0.0",
        +        "unist-util-is": "^5.0.0",
        +        "unist-util-visit-parents": "^5.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/np/node_modules/wrap-ansi": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz",
        -      "integrity": "sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==",
        +    "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
        +      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
               "dev": true,
        -      "dependencies": {
        -        "string-width": "^2.1.1",
        -        "strip-ansi": "^4.0.0"
        -      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/np/node_modules/wrap-ansi/node_modules/strip-ansi": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
        -      "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
        +    "node_modules/mdast-util-from-markdown": {
        +      "version": "1.3.1",
        +      "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz",
        +      "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==",
               "dev": true,
               "dependencies": {
        -        "ansi-regex": "^3.0.0"
        +        "@types/mdast": "^3.0.0",
        +        "@types/unist": "^2.0.0",
        +        "decode-named-character-reference": "^1.0.0",
        +        "mdast-util-to-string": "^3.1.0",
        +        "micromark": "^3.0.0",
        +        "micromark-util-decode-numeric-character-reference": "^1.0.0",
        +        "micromark-util-decode-string": "^1.0.0",
        +        "micromark-util-normalize-identifier": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0",
        +        "unist-util-stringify-position": "^3.0.0",
        +        "uvu": "^0.5.0"
               },
        -      "engines": {
        -        "node": ">=4"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/np/node_modules/yallist": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
        -      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
        -      "dev": true
        -    },
        -    "node_modules/np/node_modules/yocto-queue": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
        -      "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
        +    "node_modules/mdast-util-from-markdown/node_modules/mdast-util-to-string": {
        +      "version": "3.2.0",
        +      "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz",
        +      "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==",
               "dev": true,
        -      "engines": {
        -        "node": ">=12.20"
        +      "dependencies": {
        +        "@types/mdast": "^3.0.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/npm-name": {
        -      "version": "7.1.0",
        -      "resolved": "https://registry.npmjs.org/npm-name/-/npm-name-7.1.0.tgz",
        -      "integrity": "sha512-0Sxf+7tQUOkQ9HuYVSdvq7gZNAOPp1ZJjHiKzpJhsQw9m1YjNfARC0SxWuuUWefChsbvu+DWrwWFfGQWLHmLjg==",
        +    "node_modules/mdast-util-gfm": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz",
        +      "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==",
               "dev": true,
               "dependencies": {
        -        "got": "^11.8.5",
        -        "is-name-taken": "^2.0.0",
        -        "is-scoped": "^3.0.0",
        -        "is-url-superb": "^6.1.0",
        -        "lodash.zip": "^4.2.0",
        -        "org-regex": "^1.0.0",
        -        "p-map": "^5.5.0",
        -        "registry-auth-token": "^4.2.2",
        -        "registry-url": "^6.0.1",
        -        "validate-npm-package-name": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=12"
        +        "mdast-util-from-markdown": "^1.0.0",
        +        "mdast-util-gfm-autolink-literal": "^1.0.0",
        +        "mdast-util-gfm-footnote": "^1.0.0",
        +        "mdast-util-gfm-strikethrough": "^1.0.0",
        +        "mdast-util-gfm-table": "^1.0.0",
        +        "mdast-util-gfm-task-list-item": "^1.0.0",
        +        "mdast-util-to-markdown": "^1.0.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/npm-name/node_modules/p-map": {
        -      "version": "5.5.0",
        -      "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz",
        -      "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==",
        +    "node_modules/mdast-util-gfm-autolink-literal": {
        +      "version": "1.0.3",
        +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz",
        +      "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==",
               "dev": true,
               "dependencies": {
        -        "aggregate-error": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=12"
        +        "@types/mdast": "^3.0.0",
        +        "ccount": "^2.0.0",
        +        "mdast-util-find-and-replace": "^2.0.0",
        +        "micromark-util-character": "^1.0.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/npm-path": {
        -      "version": "2.0.4",
        -      "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz",
        -      "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==",
        +    "node_modules/mdast-util-gfm-footnote": {
        +      "version": "1.0.2",
        +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz",
        +      "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==",
               "dev": true,
               "dependencies": {
        -        "which": "^1.2.10"
        -      },
        -      "bin": {
        -        "npm-path": "bin/npm-path"
        +        "@types/mdast": "^3.0.0",
        +        "mdast-util-to-markdown": "^1.3.0",
        +        "micromark-util-normalize-identifier": "^1.0.0"
               },
        -      "engines": {
        -        "node": ">=0.8"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/npm-run-path": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
        -      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
        +    "node_modules/mdast-util-gfm-strikethrough": {
        +      "version": "1.0.3",
        +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz",
        +      "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==",
               "dev": true,
               "dependencies": {
        -        "path-key": "^2.0.0"
        +        "@types/mdast": "^3.0.0",
        +        "mdast-util-to-markdown": "^1.3.0"
               },
        -      "engines": {
        -        "node": ">=4"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/npm-which": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz",
        -      "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=",
        +    "node_modules/mdast-util-gfm-table": {
        +      "version": "1.0.7",
        +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz",
        +      "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==",
               "dev": true,
               "dependencies": {
        -        "commander": "^2.9.0",
        -        "npm-path": "^2.0.2",
        -        "which": "^1.2.10"
        +        "@types/mdast": "^3.0.0",
        +        "markdown-table": "^3.0.0",
        +        "mdast-util-from-markdown": "^1.0.0",
        +        "mdast-util-to-markdown": "^1.3.0"
               },
        -      "bin": {
        -        "npm-which": "bin/npm-which.js"
        -      },
        -      "engines": {
        -        "node": ">=4.2.0"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/number-is-nan": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
        -      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
        +    "node_modules/mdast-util-gfm-task-list-item": {
        +      "version": "1.0.2",
        +      "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz",
        +      "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/nyc": {
        -      "version": "14.1.1",
        -      "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz",
        -      "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==",
        -      "dev": true,
        -      "dependencies": {
        -        "archy": "^1.0.0",
        -        "caching-transform": "^3.0.2",
        -        "convert-source-map": "^1.6.0",
        -        "cp-file": "^6.2.0",
        -        "find-cache-dir": "^2.1.0",
        -        "find-up": "^3.0.0",
        -        "foreground-child": "^1.5.6",
        -        "glob": "^7.1.3",
        -        "istanbul-lib-coverage": "^2.0.5",
        -        "istanbul-lib-hook": "^2.0.7",
        -        "istanbul-lib-instrument": "^3.3.0",
        -        "istanbul-lib-report": "^2.0.8",
        -        "istanbul-lib-source-maps": "^3.0.6",
        -        "istanbul-reports": "^2.2.4",
        -        "js-yaml": "^3.13.1",
        -        "make-dir": "^2.1.0",
        -        "merge-source-map": "^1.1.0",
        -        "resolve-from": "^4.0.0",
        -        "rimraf": "^2.6.3",
        -        "signal-exit": "^3.0.2",
        -        "spawn-wrap": "^1.4.2",
        -        "test-exclude": "^5.2.3",
        -        "uuid": "^3.3.2",
        -        "yargs": "^13.2.2",
        -        "yargs-parser": "^13.0.0"
        -      },
        -      "bin": {
        -        "nyc": "bin/nyc.js"
        +      "dependencies": {
        +        "@types/mdast": "^3.0.0",
        +        "mdast-util-to-markdown": "^1.3.0"
               },
        -      "engines": {
        -        "node": ">=6"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/nyc/node_modules/ansi-regex": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
        -      "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
        +    "node_modules/mdast-util-inject": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz",
        +      "integrity": "sha512-CcJ0mHa36QYumDKiZ2OIR+ClhfOM7zIzN+Wfy8tRZ1hpH9DKLCS+Mh4DyK5bCxzE9uxMWcbIpeNFWsg1zrj/2g==",
               "dev": true,
        -      "engines": {
        -        "node": ">=6"
        +      "dependencies": {
        +        "mdast-util-to-string": "^1.0.0"
               }
             },
        -    "node_modules/nyc/node_modules/cliui": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
        -      "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
        +    "node_modules/mdast-util-phrasing": {
        +      "version": "3.0.1",
        +      "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz",
        +      "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==",
               "dev": true,
               "dependencies": {
        -        "string-width": "^3.1.0",
        -        "strip-ansi": "^5.2.0",
        -        "wrap-ansi": "^5.1.0"
        +        "@types/mdast": "^3.0.0",
        +        "unist-util-is": "^5.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/nyc/node_modules/find-up": {
        -      "version": "3.0.0",
        -      "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
        +    "node_modules/mdast-util-to-hast": {
        +      "version": "12.3.0",
        +      "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz",
        +      "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==",
               "dev": true,
               "dependencies": {
        -        "locate-path": "^3.0.0"
        +        "@types/hast": "^2.0.0",
        +        "@types/mdast": "^3.0.0",
        +        "mdast-util-definitions": "^5.0.0",
        +        "micromark-util-sanitize-uri": "^1.1.0",
        +        "trim-lines": "^3.0.0",
        +        "unist-util-generated": "^2.0.0",
        +        "unist-util-position": "^4.0.0",
        +        "unist-util-visit": "^4.0.0"
               },
        -      "engines": {
        -        "node": ">=6"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/nyc/node_modules/is-fullwidth-code-point": {
        -      "version": "2.0.0",
        -      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
        +    "node_modules/mdast-util-to-markdown": {
        +      "version": "1.5.0",
        +      "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz",
        +      "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==",
               "dev": true,
        -      "engines": {
        -        "node": ">=4"
        +      "dependencies": {
        +        "@types/mdast": "^3.0.0",
        +        "@types/unist": "^2.0.0",
        +        "longest-streak": "^3.0.0",
        +        "mdast-util-phrasing": "^3.0.0",
        +        "mdast-util-to-string": "^3.0.0",
        +        "micromark-util-decode-string": "^1.0.0",
        +        "unist-util-visit": "^4.0.0",
        +        "zwitch": "^2.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/nyc/node_modules/make-dir": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
        -      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
        +    "node_modules/mdast-util-to-markdown/node_modules/mdast-util-to-string": {
        +      "version": "3.2.0",
        +      "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz",
        +      "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==",
               "dev": true,
               "dependencies": {
        -        "pify": "^4.0.1",
        -        "semver": "^5.6.0"
        +        "@types/mdast": "^3.0.0"
               },
        -      "engines": {
        -        "node": ">=6"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/nyc/node_modules/resolve-from": {
        -      "version": "4.0.0",
        -      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
        +    "node_modules/mdast-util-to-string": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz",
        +      "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==",
               "dev": true,
        -      "engines": {
        -        "node": ">=4"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/nyc/node_modules/semver": {
        -      "version": "5.7.1",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
        -      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
        +    "node_modules/mdast-util-toc": {
        +      "version": "6.1.1",
        +      "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-6.1.1.tgz",
        +      "integrity": "sha512-Er21728Kow8hehecK2GZtb7Ny3omcoPUVrmObiSUwmoRYVZaXLR751QROEFjR8W/vAQdHMLj49Lz20J55XaNpw==",
               "dev": true,
        -      "bin": {
        -        "semver": "bin/semver"
        +      "dependencies": {
        +        "@types/extend": "^3.0.0",
        +        "@types/mdast": "^3.0.0",
        +        "extend": "^3.0.0",
        +        "github-slugger": "^2.0.0",
        +        "mdast-util-to-string": "^3.1.0",
        +        "unist-util-is": "^5.0.0",
        +        "unist-util-visit": "^4.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/nyc/node_modules/string-width": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
        -      "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
        +    "node_modules/mdast-util-toc/node_modules/github-slugger": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
        +      "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
        +      "dev": true
        +    },
        +    "node_modules/mdast-util-toc/node_modules/mdast-util-to-string": {
        +      "version": "3.2.0",
        +      "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz",
        +      "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==",
               "dev": true,
               "dependencies": {
        -        "emoji-regex": "^7.0.1",
        -        "is-fullwidth-code-point": "^2.0.0",
        -        "strip-ansi": "^5.1.0"
        +        "@types/mdast": "^3.0.0"
               },
        -      "engines": {
        -        "node": ">=6"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/nyc/node_modules/strip-ansi": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
        -      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
        +    "node_modules/merge-stream": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
        +      "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
        +      "dev": true
        +    },
        +    "node_modules/micromark": {
        +      "version": "3.2.0",
        +      "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz",
        +      "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==",
        +      "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
        +      "dependencies": {
        +        "@types/debug": "^4.0.0",
        +        "debug": "^4.0.0",
        +        "decode-named-character-reference": "^1.0.0",
        +        "micromark-core-commonmark": "^1.0.1",
        +        "micromark-factory-space": "^1.0.0",
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-chunked": "^1.0.0",
        +        "micromark-util-combine-extensions": "^1.0.0",
        +        "micromark-util-decode-numeric-character-reference": "^1.0.0",
        +        "micromark-util-encode": "^1.0.0",
        +        "micromark-util-normalize-identifier": "^1.0.0",
        +        "micromark-util-resolve-all": "^1.0.0",
        +        "micromark-util-sanitize-uri": "^1.0.0",
        +        "micromark-util-subtokenize": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.1",
        +        "uvu": "^0.5.0"
        +      }
        +    },
        +    "node_modules/micromark-core-commonmark": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz",
        +      "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==",
        +      "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
        +      "dependencies": {
        +        "decode-named-character-reference": "^1.0.0",
        +        "micromark-factory-destination": "^1.0.0",
        +        "micromark-factory-label": "^1.0.0",
        +        "micromark-factory-space": "^1.0.0",
        +        "micromark-factory-title": "^1.0.0",
        +        "micromark-factory-whitespace": "^1.0.0",
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-chunked": "^1.0.0",
        +        "micromark-util-classify-character": "^1.0.0",
        +        "micromark-util-html-tag-name": "^1.0.0",
        +        "micromark-util-normalize-identifier": "^1.0.0",
        +        "micromark-util-resolve-all": "^1.0.0",
        +        "micromark-util-subtokenize": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.1",
        +        "uvu": "^0.5.0"
        +      }
        +    },
        +    "node_modules/micromark-extension-gfm": {
        +      "version": "2.0.3",
        +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz",
        +      "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==",
               "dev": true,
               "dependencies": {
        -        "ansi-regex": "^4.1.0"
        +        "micromark-extension-gfm-autolink-literal": "^1.0.0",
        +        "micromark-extension-gfm-footnote": "^1.0.0",
        +        "micromark-extension-gfm-strikethrough": "^1.0.0",
        +        "micromark-extension-gfm-table": "^1.0.0",
        +        "micromark-extension-gfm-tagfilter": "^1.0.0",
        +        "micromark-extension-gfm-task-list-item": "^1.0.0",
        +        "micromark-util-combine-extensions": "^1.0.0",
        +        "micromark-util-types": "^1.0.0"
               },
        -      "engines": {
        -        "node": ">=6"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/nyc/node_modules/uuid": {
        -      "version": "3.3.3",
        -      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
        -      "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
        -      "deprecated": "Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.",
        +    "node_modules/micromark-extension-gfm-autolink-literal": {
        +      "version": "1.0.5",
        +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz",
        +      "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==",
               "dev": true,
        -      "bin": {
        -        "uuid": "bin/uuid"
        +      "dependencies": {
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-sanitize-uri": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/nyc/node_modules/wrap-ansi": {
        -      "version": "5.1.0",
        -      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
        -      "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
        +    "node_modules/micromark-extension-gfm-footnote": {
        +      "version": "1.1.2",
        +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz",
        +      "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==",
               "dev": true,
               "dependencies": {
        -        "ansi-styles": "^3.2.0",
        -        "string-width": "^3.0.0",
        -        "strip-ansi": "^5.0.0"
        +        "micromark-core-commonmark": "^1.0.0",
        +        "micromark-factory-space": "^1.0.0",
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-normalize-identifier": "^1.0.0",
        +        "micromark-util-sanitize-uri": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0",
        +        "uvu": "^0.5.0"
               },
        -      "engines": {
        -        "node": ">=6"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/nyc/node_modules/yargs": {
        -      "version": "13.3.0",
        -      "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
        -      "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
        +    "node_modules/micromark-extension-gfm-strikethrough": {
        +      "version": "1.0.7",
        +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz",
        +      "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==",
               "dev": true,
               "dependencies": {
        -        "cliui": "^5.0.0",
        -        "find-up": "^3.0.0",
        -        "get-caller-file": "^2.0.1",
        -        "require-directory": "^2.1.1",
        -        "require-main-filename": "^2.0.0",
        -        "set-blocking": "^2.0.0",
        -        "string-width": "^3.0.0",
        -        "which-module": "^2.0.0",
        -        "y18n": "^4.0.0",
        -        "yargs-parser": "^13.1.1"
        +        "micromark-util-chunked": "^1.0.0",
        +        "micromark-util-classify-character": "^1.0.0",
        +        "micromark-util-resolve-all": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0",
        +        "uvu": "^0.5.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/oauth-sign": {
        -      "version": "0.3.0",
        -      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz",
        -      "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=",
        +    "node_modules/micromark-extension-gfm-table": {
        +      "version": "1.0.7",
        +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz",
        +      "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==",
               "dev": true,
        -      "optional": true,
        -      "engines": {
        -        "node": "*"
        +      "dependencies": {
        +        "micromark-factory-space": "^1.0.0",
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0",
        +        "uvu": "^0.5.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/object-assign": {
        -      "version": "4.1.1",
        -      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
        -      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
        +    "node_modules/micromark-extension-gfm-tagfilter": {
        +      "version": "1.0.2",
        +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz",
        +      "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "dependencies": {
        +        "micromark-util-types": "^1.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/object-inspect": {
        -      "version": "1.13.2",
        -      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
        -      "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
        +    "node_modules/micromark-extension-gfm-task-list-item": {
        +      "version": "1.0.5",
        +      "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz",
        +      "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==",
               "dev": true,
        -      "engines": {
        -        "node": ">= 0.4"
        +      "dependencies": {
        +        "micromark-factory-space": "^1.0.0",
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0",
        +        "uvu": "^0.5.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/object-keys": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
        -      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
        +    "node_modules/micromark-factory-destination": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz",
        +      "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==",
               "dev": true,
        -      "engines": {
        -        "node": ">= 0.4"
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
        +      "dependencies": {
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0"
               }
             },
        -    "node_modules/object.assign": {
        -      "version": "4.1.0",
        -      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
        -      "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
        +    "node_modules/micromark-factory-label": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz",
        +      "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "define-properties": "^1.1.2",
        -        "function-bind": "^1.1.1",
        -        "has-symbols": "^1.0.0",
        -        "object-keys": "^1.0.11"
        -      },
        -      "engines": {
        -        "node": ">= 0.4"
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0",
        +        "uvu": "^0.5.0"
               }
             },
        -    "node_modules/object.defaults": {
        +    "node_modules/micromark-factory-space": {
               "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz",
        -      "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=",
        +      "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz",
        +      "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "array-each": "^1.0.1",
        -        "array-slice": "^1.0.0",
        -        "for-own": "^1.0.0",
        -        "isobject": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-types": "^1.0.0"
               }
             },
        -    "node_modules/object.map": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz",
        -      "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=",
        +    "node_modules/micromark-factory-title": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz",
        +      "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "for-own": "^1.0.0",
        -        "make-iterator": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        +        "micromark-factory-space": "^1.0.0",
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0"
               }
             },
        -    "node_modules/object.pick": {
        -      "version": "1.3.0",
        -      "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
        -      "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
        +    "node_modules/micromark-factory-whitespace": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz",
        +      "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "isobject": "^3.0.1"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        +        "micromark-factory-space": "^1.0.0",
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0"
               }
             },
        -    "node_modules/omggif": {
        -      "version": "1.0.10",
        -      "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz",
        -      "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==",
        -      "dev": true
        +    "node_modules/micromark-util-character": {
        +      "version": "1.2.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz",
        +      "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==",
        +      "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
        +      "dependencies": {
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0"
        +      }
             },
        -    "node_modules/on-finished": {
        -      "version": "2.3.0",
        -      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
        -      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
        +    "node_modules/micromark-util-chunked": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz",
        +      "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "ee-first": "1.1.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.8"
        +        "micromark-util-symbol": "^1.0.0"
               }
             },
        -    "node_modules/on-headers": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
        -      "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
        +    "node_modules/micromark-util-classify-character": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz",
        +      "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==",
               "dev": true,
        -      "engines": {
        -        "node": ">= 0.8"
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
        +      "dependencies": {
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0"
               }
             },
        -    "node_modules/once": {
        -      "version": "1.4.0",
        -      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
        -      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
        +    "node_modules/micromark-util-combine-extensions": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz",
        +      "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "wrappy": "1"
        +        "micromark-util-chunked": "^1.0.0",
        +        "micromark-util-types": "^1.0.0"
               }
             },
        -    "node_modules/onetime": {
        -      "version": "5.1.2",
        -      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
        -      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
        +    "node_modules/micromark-util-decode-numeric-character-reference": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz",
        +      "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "mimic-fn": "^2.1.0"
        -      },
        -      "engines": {
        -        "node": ">=6"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "micromark-util-symbol": "^1.0.0"
               }
             },
        -    "node_modules/open": {
        -      "version": "7.0.3",
        -      "resolved": "https://registry.npmjs.org/open/-/open-7.0.3.tgz",
        -      "integrity": "sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA==",
        +    "node_modules/micromark-util-decode-string": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz",
        +      "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "is-docker": "^2.0.0",
        -        "is-wsl": "^2.1.1"
        -      },
        -      "engines": {
        -        "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "decode-named-character-reference": "^1.0.0",
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-decode-numeric-character-reference": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0"
               }
             },
        -    "node_modules/open/node_modules/is-wsl": {
        -      "version": "2.1.1",
        -      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz",
        -      "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==",
        +    "node_modules/micromark-util-encode": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz",
        +      "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==",
               "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ]
             },
        -    "node_modules/opencollective-postinstall": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz",
        -      "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==",
        +    "node_modules/micromark-util-html-tag-name": {
        +      "version": "1.2.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz",
        +      "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==",
               "dev": true,
        -      "bin": {
        -        "opencollective-postinstall": "index.js"
        -      }
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ]
             },
        -    "node_modules/opentype.js": {
        -      "version": "0.9.0",
        -      "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-0.9.0.tgz",
        -      "integrity": "sha1-wdmNoqGzZOPv2ugGI2QOnj71t5c=",
        +    "node_modules/micromark-util-normalize-identifier": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz",
        +      "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "string.prototype.codepointat": "^0.2.1",
        -        "tiny-inflate": "^1.0.2"
        -      },
        -      "bin": {
        -        "ot": "bin/ot"
        +        "micromark-util-symbol": "^1.0.0"
               }
             },
        -    "node_modules/opn": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/opn/-/opn-6.0.0.tgz",
        -      "integrity": "sha512-I9PKfIZC+e4RXZ/qr1RhgyCnGgYX0UEIlXgWnCOVACIvFgaC9rz6Won7xbdhoHrd8IIhV7YEpHjreNUNkqCGkQ==",
        -      "deprecated": "The package has been renamed to `open`",
        +    "node_modules/micromark-util-resolve-all": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz",
        +      "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "is-wsl": "^1.1.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        +        "micromark-util-types": "^1.0.0"
               }
             },
        -    "node_modules/opted": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/opted/-/opted-1.0.2.tgz",
        -      "integrity": "sha1-CU562dDA/CuzhLTYpQfieOrVV8k=",
        +    "node_modules/micromark-util-sanitize-uri": {
        +      "version": "1.2.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz",
        +      "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "lodash": "^4.17.4"
        -      },
        -      "engines": {
        -        "node": ">=4"
        +        "micromark-util-character": "^1.0.0",
        +        "micromark-util-encode": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0"
               }
             },
        -    "node_modules/optimist": {
        -      "version": "0.6.1",
        -      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
        -      "integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==",
        +    "node_modules/micromark-util-subtokenize": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz",
        +      "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ],
               "dependencies": {
        -        "minimist": "~0.0.1",
        -        "wordwrap": "~0.0.2"
        +        "micromark-util-chunked": "^1.0.0",
        +        "micromark-util-symbol": "^1.0.0",
        +        "micromark-util-types": "^1.0.0",
        +        "uvu": "^0.5.0"
               }
             },
        -    "node_modules/optimist/node_modules/minimist": {
        -      "version": "0.0.10",
        -      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
        -      "integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==",
        -      "dev": true
        +    "node_modules/micromark-util-symbol": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz",
        +      "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==",
        +      "dev": true,
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ]
             },
        -    "node_modules/optimist/node_modules/wordwrap": {
        -      "version": "0.0.3",
        -      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
        -      "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==",
        +    "node_modules/micromark-util-types": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz",
        +      "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.4.0"
        -      }
        +      "funding": [
        +        {
        +          "type": "GitHub Sponsors",
        +          "url": "https://github.com/sponsors/unifiedjs"
        +        },
        +        {
        +          "type": "OpenCollective",
        +          "url": "https://opencollective.com/unified"
        +        }
        +      ]
             },
        -    "node_modules/optionator": {
        -      "version": "0.9.1",
        -      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
        -      "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
        +    "node_modules/micromark/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
               "dependencies": {
        -        "deep-is": "^0.1.3",
        -        "fast-levenshtein": "^2.0.6",
        -        "levn": "^0.4.1",
        -        "prelude-ls": "^1.2.1",
        -        "type-check": "^0.4.0",
        -        "word-wrap": "^1.2.3"
        +        "ms": "^2.1.3"
               },
               "engines": {
        -        "node": ">= 0.8.0"
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/ora": {
        -      "version": "0.2.3",
        -      "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz",
        -      "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=",
        +    "node_modules/micromark/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        +      "dev": true
        +    },
        +    "node_modules/micromatch": {
        +      "version": "4.0.8",
        +      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
        +      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
               "dev": true,
               "dependencies": {
        -        "chalk": "^1.1.1",
        -        "cli-cursor": "^1.0.2",
        -        "cli-spinners": "^0.1.2",
        -        "object-assign": "^4.0.1"
        +        "braces": "^3.0.3",
        +        "picomatch": "^2.3.1"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8.6"
               }
             },
        -    "node_modules/ora/node_modules/ansi-styles": {
        -      "version": "2.2.1",
        -      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
        -      "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
        +    "node_modules/mimic-fn": {
        +      "version": "2.1.0",
        +      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
        +      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=6"
               }
             },
        -    "node_modules/ora/node_modules/chalk": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
        -      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
        +    "node_modules/mimic-function": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
        +      "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
               "dev": true,
        -      "dependencies": {
        -        "ansi-styles": "^2.2.1",
        -        "escape-string-regexp": "^1.0.2",
        -        "has-ansi": "^2.0.0",
        -        "strip-ansi": "^3.0.0",
        -        "supports-color": "^2.0.0"
        -      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=18"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/ora/node_modules/cli-cursor": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
        -      "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
        +    "node_modules/minimatch": {
        +      "version": "3.0.8",
        +      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz",
        +      "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==",
               "dev": true,
               "dependencies": {
        -        "restore-cursor": "^1.0.1"
        +        "brace-expansion": "^1.1.7"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": "*"
               }
             },
        -    "node_modules/ora/node_modules/onetime": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
        -      "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
        +    "node_modules/minipass": {
        +      "version": "7.1.2",
        +      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
        +      "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=16 || 14 >=14.17"
               }
             },
        -    "node_modules/ora/node_modules/restore-cursor": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
        -      "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
        +    "node_modules/mitt": {
        +      "version": "3.0.1",
        +      "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
        +      "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
               "dev": true,
        -      "dependencies": {
        -        "exit-hook": "^1.0.0",
        -        "onetime": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        +      "optional": true,
        +      "peer": true
             },
        -    "node_modules/ora/node_modules/supports-color": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
        -      "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
        +    "node_modules/mri": {
        +      "version": "1.2.0",
        +      "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
        +      "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
               "dev": true,
               "engines": {
        -        "node": ">=0.8.0"
        +        "node": ">=4"
               }
             },
        -    "node_modules/org-regex": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/org-regex/-/org-regex-1.0.0.tgz",
        -      "integrity": "sha512-7bqkxkEJwzJQUAlyYniqEZ3Ilzjh0yoa62c7gL6Ijxj5bEpPL+8IE1Z0PFj0ywjjXQcdrwR51g9MIcLezR0hKQ==",
        +    "node_modules/mrmime": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
        +      "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=10"
               }
             },
        -    "node_modules/os-browserify": {
        -      "version": "0.3.0",
        -      "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
        -      "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
        -      "dev": true
        -    },
        -    "node_modules/os-homedir": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
        -      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
        +    "node_modules/msw": {
        +      "version": "2.7.0",
        +      "resolved": "https://registry.npmjs.org/msw/-/msw-2.7.0.tgz",
        +      "integrity": "sha512-BIodwZ19RWfCbYTxWTUfTXc+sg4OwjCAgxU1ZsgmggX/7S3LdUifsbUPJs61j0rWb19CZRGY5if77duhc0uXzw==",
               "dev": true,
        +      "hasInstallScript": true,
        +      "dependencies": {
        +        "@bundled-es-modules/cookie": "^2.0.1",
        +        "@bundled-es-modules/statuses": "^1.0.1",
        +        "@bundled-es-modules/tough-cookie": "^0.1.6",
        +        "@inquirer/confirm": "^5.0.0",
        +        "@mswjs/interceptors": "^0.37.0",
        +        "@open-draft/deferred-promise": "^2.2.0",
        +        "@open-draft/until": "^2.1.0",
        +        "@types/cookie": "^0.6.0",
        +        "@types/statuses": "^2.0.4",
        +        "graphql": "^16.8.1",
        +        "headers-polyfill": "^4.0.2",
        +        "is-node-process": "^1.2.0",
        +        "outvariant": "^1.4.3",
        +        "path-to-regexp": "^6.3.0",
        +        "picocolors": "^1.1.1",
        +        "strict-event-emitter": "^0.5.1",
        +        "type-fest": "^4.26.1",
        +        "yargs": "^17.7.2"
        +      },
        +      "bin": {
        +        "msw": "cli/index.js"
        +      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=18"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/mswjs"
        +      },
        +      "peerDependencies": {
        +        "typescript": ">= 4.8.x"
        +      },
        +      "peerDependenciesMeta": {
        +        "typescript": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/os-tmpdir": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
        -      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
        +    "node_modules/msw/node_modules/ansi-regex": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/osenv": {
        -      "version": "0.1.5",
        -      "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
        -      "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
        -      "dev": true,
        -      "dependencies": {
        -        "os-homedir": "^1.0.0",
        -        "os-tmpdir": "^1.0.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/ow": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/ow/-/ow-1.1.1.tgz",
        -      "integrity": "sha512-sJBRCbS5vh1Jp9EOgwp1Ws3c16lJrUkJYlvWTYC03oyiYVwS/ns7lKRWow4w4XjDyTrA2pplQv4B2naWSR6yDA==",
        +    "node_modules/msw/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
               "dependencies": {
        -        "@sindresorhus/is": "^5.3.0",
        -        "callsites": "^4.0.0",
        -        "dot-prop": "^7.2.0",
        -        "lodash.isequal": "^4.5.0",
        -        "vali-date": "^1.0.0"
        +        "color-convert": "^2.0.1"
               },
               "engines": {
        -        "node": ">=14.16"
        +        "node": ">=8"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/ow/node_modules/@sindresorhus/is": {
        -      "version": "5.4.1",
        -      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.4.1.tgz",
        -      "integrity": "sha512-axlrvsHlHlFmKKMEg4VyvMzFr93JWJj4eIfXY1STVuO2fsImCa7ncaiG5gC8HKOX590AW5RtRsC41/B+OfrSqw==",
        +    "node_modules/msw/node_modules/cliui": {
        +      "version": "8.0.1",
        +      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
        +      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
               "dev": true,
        -      "engines": {
        -        "node": ">=14.16"
        +      "dependencies": {
        +        "string-width": "^4.2.0",
        +        "strip-ansi": "^6.0.1",
        +        "wrap-ansi": "^7.0.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sindresorhus/is?sponsor=1"
        +      "engines": {
        +        "node": ">=12"
               }
             },
        -    "node_modules/ow/node_modules/callsites": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.0.0.tgz",
        -      "integrity": "sha512-y3jRROutgpKdz5vzEhWM34TidDU8vkJppF8dszITeb1PQmSqV3DTxyV8G/lyO/DNvtE1YTedehmw9MPZsCBHxQ==",
        +    "node_modules/msw/node_modules/color-convert": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
        -      "engines": {
        -        "node": ">=12.20"
        +      "dependencies": {
        +        "color-name": "~1.1.4"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "engines": {
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/p-cancelable": {
        -      "version": "2.1.1",
        -      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
        -      "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
        +    "node_modules/msw/node_modules/emoji-regex": {
        +      "version": "8.0.0",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        +      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        +      "dev": true
        +    },
        +    "node_modules/msw/node_modules/is-fullwidth-code-point": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        +      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
               "dev": true,
               "engines": {
                 "node": ">=8"
               }
             },
        -    "node_modules/p-finally": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
        -      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
        +    "node_modules/msw/node_modules/path-to-regexp": {
        +      "version": "6.3.0",
        +      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
        +      "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
        +      "dev": true
        +    },
        +    "node_modules/msw/node_modules/string-width": {
        +      "version": "4.2.3",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
        +      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
               "dev": true,
        +      "dependencies": {
        +        "emoji-regex": "^8.0.0",
        +        "is-fullwidth-code-point": "^3.0.0",
        +        "strip-ansi": "^6.0.1"
        +      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
               }
             },
        -    "node_modules/p-limit": {
        -      "version": "2.3.0",
        -      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
        -      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
        +    "node_modules/msw/node_modules/strip-ansi": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        +      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
               "dev": true,
               "dependencies": {
        -        "p-try": "^2.0.0"
        +        "ansi-regex": "^5.0.1"
               },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=8"
        +      }
        +    },
        +    "node_modules/msw/node_modules/type-fest": {
        +      "version": "4.33.0",
        +      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.33.0.tgz",
        +      "integrity": "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=16"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/p-locate": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
        -      "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
        +    "node_modules/msw/node_modules/wrap-ansi": {
        +      "version": "7.0.0",
        +      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
        +      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
               "dev": true,
               "dependencies": {
        -        "p-limit": "^2.0.0"
        +        "ansi-styles": "^4.0.0",
        +        "string-width": "^4.1.0",
        +        "strip-ansi": "^6.0.0"
               },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
               }
             },
        -    "node_modules/p-lock": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/p-lock/-/p-lock-2.1.0.tgz",
        -      "integrity": "sha512-pi2yT8gNhVrV4LgsUvJWQy58TXH1HG2+NXDby9+UrsS/9fXb0FJH9aCxbdHJ0EAQ6XC7ggSP6GAzuR5puDArUQ==",
        -      "dev": true
        -    },
        -    "node_modules/p-map": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
        -      "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==",
        +    "node_modules/msw/node_modules/y18n": {
        +      "version": "5.0.8",
        +      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
        +      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
               "dev": true,
               "engines": {
        -        "node": ">=4"
        +        "node": ">=10"
               }
             },
        -    "node_modules/p-memoize": {
        -      "version": "7.1.1",
        -      "resolved": "https://registry.npmjs.org/p-memoize/-/p-memoize-7.1.1.tgz",
        -      "integrity": "sha512-DZ/bONJILHkQ721hSr/E9wMz5Am/OTJ9P6LhLFo2Tu+jL8044tgc9LwHO8g4PiaYePnlVVRAJcKmgy8J9MVFrA==",
        +    "node_modules/msw/node_modules/yargs": {
        +      "version": "17.7.2",
        +      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
        +      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
               "dev": true,
               "dependencies": {
        -        "mimic-fn": "^4.0.0",
        -        "type-fest": "^3.0.0"
        +        "cliui": "^8.0.1",
        +        "escalade": "^3.1.1",
        +        "get-caller-file": "^2.0.5",
        +        "require-directory": "^2.1.1",
        +        "string-width": "^4.2.3",
        +        "y18n": "^5.0.5",
        +        "yargs-parser": "^21.1.1"
               },
               "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sindresorhus/p-memoize?sponsor=1"
        +        "node": ">=12"
               }
             },
        -    "node_modules/p-memoize/node_modules/mimic-fn": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
        -      "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
        +    "node_modules/msw/node_modules/yargs-parser": {
        +      "version": "21.1.1",
        +      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
        +      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
               "dev": true,
               "engines": {
                 "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/p-memoize/node_modules/type-fest": {
        -      "version": "3.13.0",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.0.tgz",
        -      "integrity": "sha512-Gur3yQGM9qiLNs0KPP7LPgeRbio2QTt4xXouobMCarR0/wyW3F+F/+OWwshg3NG0Adon7uQfSZBpB46NfhoF1A==",
        +    "node_modules/mute-stream": {
        +      "version": "0.0.8",
        +      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
        +      "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
        +      "dev": true
        +    },
        +    "node_modules/natural-compare": {
        +      "version": "1.4.0",
        +      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
        +      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
        +      "dev": true
        +    },
        +    "node_modules/netmask": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
        +      "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
               "dev": true,
               "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">= 0.4.0"
               }
             },
        -    "node_modules/p-timeout": {
        -      "version": "6.1.2",
        -      "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.2.tgz",
        -      "integrity": "sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==",
        +    "node_modules/node-domexception": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
        +      "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/jimmywarting"
        +        },
        +        {
        +          "type": "github",
        +          "url": "https://paypal.me/jimmywarting"
        +        }
        +      ],
               "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">=10.5.0"
               }
             },
        -    "node_modules/p-try": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
        -      "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
        +    "node_modules/node-fetch": {
        +      "version": "2.6.7",
        +      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
        +      "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
               "dev": true,
        +      "dependencies": {
        +        "whatwg-url": "^5.0.0"
        +      },
               "engines": {
        -        "node": ">=6"
        +        "node": "4.x || >=6.0.0"
        +      },
        +      "peerDependencies": {
        +        "encoding": "^0.1.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "encoding": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/package-hash": {
        +    "node_modules/node-releases": {
        +      "version": "2.0.19",
        +      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
        +      "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
        +      "dev": true
        +    },
        +    "node_modules/normalize-path": {
               "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz",
        -      "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==",
        +      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
        +      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
               "dev": true,
        -      "dependencies": {
        -        "graceful-fs": "^4.1.15",
        -        "hasha": "^3.0.0",
        -        "lodash.flattendeep": "^4.4.0",
        -        "release-zalgo": "^1.0.0"
        -      },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/package-json": {
        -      "version": "8.1.1",
        -      "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz",
        -      "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==",
        +    "node_modules/npm-run-path": {
        +      "version": "5.3.0",
        +      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
        +      "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
               "dev": true,
               "dependencies": {
        -        "got": "^12.1.0",
        -        "registry-auth-token": "^5.0.1",
        -        "registry-url": "^6.0.0",
        -        "semver": "^7.3.7"
        +        "path-key": "^4.0.0"
               },
               "engines": {
        -        "node": ">=14.16"
        +        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/package-json/node_modules/@sindresorhus/is": {
        -      "version": "5.4.1",
        -      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.4.1.tgz",
        -      "integrity": "sha512-axlrvsHlHlFmKKMEg4VyvMzFr93JWJj4eIfXY1STVuO2fsImCa7ncaiG5gC8HKOX590AW5RtRsC41/B+OfrSqw==",
        +    "node_modules/npm-run-path/node_modules/path-key": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
        +      "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
               "dev": true,
               "engines": {
        -        "node": ">=14.16"
        +        "node": ">=12"
               },
               "funding": {
        -        "url": "https://github.com/sindresorhus/is?sponsor=1"
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/package-json/node_modules/@szmarczak/http-timer": {
        -      "version": "5.0.1",
        -      "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
        -      "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==",
        +    "node_modules/nth-check": {
        +      "version": "2.1.1",
        +      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
        +      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
               "dev": true,
               "dependencies": {
        -        "defer-to-connect": "^2.0.1"
        +        "boolbase": "^1.0.0"
               },
        -      "engines": {
        -        "node": ">=14.16"
        +      "funding": {
        +        "url": "https://github.com/fb55/nth-check?sponsor=1"
               }
             },
        -    "node_modules/package-json/node_modules/cacheable-lookup": {
        -      "version": "7.0.0",
        -      "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
        -      "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=14.16"
        -      }
        +    "node_modules/omggif": {
        +      "version": "1.0.10",
        +      "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz",
        +      "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="
             },
        -    "node_modules/package-json/node_modules/cacheable-request": {
        -      "version": "10.2.12",
        -      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.12.tgz",
        -      "integrity": "sha512-qtWGB5kn2OLjx47pYUkWicyOpK1vy9XZhq8yRTXOy+KAmjjESSRLx6SiExnnaGGUP1NM6/vmygMu0fGylNh9tw==",
        +    "node_modules/once": {
        +      "version": "1.4.0",
        +      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
        +      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
               "dev": true,
               "dependencies": {
        -        "@types/http-cache-semantics": "^4.0.1",
        -        "get-stream": "^6.0.1",
        -        "http-cache-semantics": "^4.1.1",
        -        "keyv": "^4.5.2",
        -        "mimic-response": "^4.0.0",
        -        "normalize-url": "^8.0.0",
        -        "responselike": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=14.16"
        +        "wrappy": "1"
               }
             },
        -    "node_modules/package-json/node_modules/get-stream": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
        -      "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
        +    "node_modules/onetime": {
        +      "version": "5.1.2",
        +      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
        +      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
               "dev": true,
        +      "dependencies": {
        +        "mimic-fn": "^2.1.0"
        +      },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=6"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/package-json/node_modules/got": {
        -      "version": "12.6.1",
        -      "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz",
        -      "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==",
        +    "node_modules/open": {
        +      "version": "8.4.2",
        +      "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
        +      "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
               "dev": true,
               "dependencies": {
        -        "@sindresorhus/is": "^5.2.0",
        -        "@szmarczak/http-timer": "^5.0.1",
        -        "cacheable-lookup": "^7.0.0",
        -        "cacheable-request": "^10.2.8",
        -        "decompress-response": "^6.0.0",
        -        "form-data-encoder": "^2.1.2",
        -        "get-stream": "^6.0.1",
        -        "http2-wrapper": "^2.1.10",
        -        "lowercase-keys": "^3.0.0",
        -        "p-cancelable": "^3.0.0",
        -        "responselike": "^3.0.0"
        +        "define-lazy-prop": "^2.0.0",
        +        "is-docker": "^2.1.1",
        +        "is-wsl": "^2.2.0"
               },
               "engines": {
        -        "node": ">=14.16"
        +        "node": ">=12"
               },
               "funding": {
        -        "url": "https://github.com/sindresorhus/got?sponsor=1"
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/package-json/node_modules/http2-wrapper": {
        +    "node_modules/open/node_modules/define-lazy-prop": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
        +      "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=8"
        +      }
        +    },
        +    "node_modules/open/node_modules/is-wsl": {
               "version": "2.2.0",
        -      "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz",
        -      "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==",
        +      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
        +      "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
               "dev": true,
               "dependencies": {
        -        "quick-lru": "^5.1.1",
        -        "resolve-alpn": "^1.2.0"
        +        "is-docker": "^2.0.0"
               },
               "engines": {
        -        "node": ">=10.19.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/package-json/node_modules/lowercase-keys": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
        -      "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==",
        +    "node_modules/opencollective-postinstall": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz",
        +      "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==",
               "dev": true,
        -      "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "bin": {
        +        "opencollective-postinstall": "index.js"
               }
             },
        -    "node_modules/package-json/node_modules/lru-cache": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
        -      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
        +    "node_modules/optionator": {
        +      "version": "0.9.4",
        +      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
        +      "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
               "dev": true,
               "dependencies": {
        -        "yallist": "^4.0.0"
        +        "deep-is": "^0.1.3",
        +        "fast-levenshtein": "^2.0.6",
        +        "levn": "^0.4.1",
        +        "prelude-ls": "^1.2.1",
        +        "type-check": "^0.4.0",
        +        "word-wrap": "^1.2.5"
               },
               "engines": {
        -        "node": ">=10"
        +        "node": ">= 0.8.0"
               }
             },
        -    "node_modules/package-json/node_modules/mimic-response": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
        -      "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==",
        +    "node_modules/os-tmpdir": {
        +      "version": "1.0.2",
        +      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
        +      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
               "dev": true,
               "engines": {
        -        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/package-json/node_modules/normalize-url": {
        -      "version": "8.0.0",
        -      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz",
        -      "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==",
        +    "node_modules/outvariant": {
        +      "version": "1.4.3",
        +      "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz",
        +      "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==",
        +      "dev": true
        +    },
        +    "node_modules/p-limit": {
        +      "version": "2.3.0",
        +      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
        +      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
               "dev": true,
        +      "dependencies": {
        +        "p-try": "^2.0.0"
        +      },
               "engines": {
        -        "node": ">=14.16"
        +        "node": ">=6"
               },
               "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/package-json/node_modules/p-cancelable": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
        -      "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
        +    "node_modules/p-try": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
        +      "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
               "dev": true,
               "engines": {
        -        "node": ">=12.20"
        +        "node": ">=6"
               }
             },
        -    "node_modules/package-json/node_modules/quick-lru": {
        -      "version": "5.1.1",
        -      "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
        -      "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
        +    "node_modules/pac-proxy-agent": {
        +      "version": "7.1.0",
        +      "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.1.0.tgz",
        +      "integrity": "sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==",
               "dev": true,
        -      "engines": {
        -        "node": ">=10"
        +      "dependencies": {
        +        "@tootallnate/quickjs-emscripten": "^0.23.0",
        +        "agent-base": "^7.1.2",
        +        "debug": "^4.3.4",
        +        "get-uri": "^6.0.1",
        +        "http-proxy-agent": "^7.0.0",
        +        "https-proxy-agent": "^7.0.6",
        +        "pac-resolver": "^7.0.1",
        +        "socks-proxy-agent": "^8.0.5"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "engines": {
        +        "node": ">= 14"
               }
             },
        -    "node_modules/package-json/node_modules/registry-auth-token": {
        -      "version": "5.0.2",
        -      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz",
        -      "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==",
        +    "node_modules/pac-proxy-agent/node_modules/agent-base": {
        +      "version": "7.1.3",
        +      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
        +      "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
               "dev": true,
        -      "dependencies": {
        -        "@pnpm/npm-conf": "^2.1.0"
        -      },
               "engines": {
        -        "node": ">=14"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/package-json/node_modules/responselike": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz",
        -      "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==",
        +    "node_modules/pac-proxy-agent/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
               "dependencies": {
        -        "lowercase-keys": "^3.0.0"
        +        "ms": "^2.1.3"
               },
               "engines": {
        -        "node": ">=14.16"
        +        "node": ">=6.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/package-json/node_modules/semver": {
        -      "version": "7.5.4",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
        -      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
        +    "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": {
        +      "version": "7.0.6",
        +      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
        +      "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
               "dev": true,
               "dependencies": {
        -        "lru-cache": "^6.0.0"
        +        "agent-base": "^7.1.2",
        +        "debug": "4"
               },
        -      "bin": {
        -        "semver": "bin/semver.js"
        +      "engines": {
        +        "node": ">= 14"
        +      }
        +    },
        +    "node_modules/pac-proxy-agent/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        +      "dev": true
        +    },
        +    "node_modules/pac-resolver": {
        +      "version": "7.0.1",
        +      "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
        +      "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
        +      "dev": true,
        +      "dependencies": {
        +        "degenerator": "^5.0.0",
        +        "netmask": "^2.0.2"
               },
               "engines": {
        -        "node": ">=10"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/package-json/node_modules/yallist": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
        -      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
        -      "dev": true
        -    },
        -    "node_modules/package-name-conflict": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/package-name-conflict/-/package-name-conflict-1.0.3.tgz",
        -      "integrity": "sha512-DPBNWSUWC0wPofXeNThao0uP4a93J7r90UyhagmJS0QcacTTkorZwXYsOop70phn1hKdcf/2e9lJIhazS8bx5A==",
        +    "node_modules/package-json-from-dist": {
        +      "version": "1.0.1",
        +      "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
        +      "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
               "dev": true
             },
             "node_modules/pako": {
        -      "version": "1.0.10",
        -      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz",
        -      "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==",
        -      "dev": true
        +      "version": "2.1.0",
        +      "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
        +      "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
             },
             "node_modules/parent-module": {
               "version": "1.0.1",
        @@ -14076,28 +9679,6 @@
                 "node": ">=6"
               }
             },
        -    "node_modules/parents": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
        -      "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=",
        -      "dev": true,
        -      "dependencies": {
        -        "path-platform": "~0.11.15"
        -      }
        -    },
        -    "node_modules/parse-asn1": {
        -      "version": "5.1.6",
        -      "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
        -      "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
        -      "dev": true,
        -      "dependencies": {
        -        "asn1.js": "^5.2.0",
        -        "browserify-aes": "^1.0.0",
        -        "evp_bytestokey": "^1.0.0",
        -        "pbkdf2": "^3.0.3",
        -        "safe-buffer": "^5.1.1"
        -      }
        -    },
             "node_modules/parse-filepath": {
               "version": "1.0.2",
               "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz",
        @@ -14112,50 +9693,102 @@
                 "node": ">=0.8"
               }
             },
        -    "node_modules/parse-json": {
        -      "version": "2.2.0",
        -      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
        -      "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
        +    "node_modules/parse-path": {
        +      "version": "7.0.0",
        +      "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz",
        +      "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==",
        +      "dev": true,
        +      "dependencies": {
        +        "protocols": "^2.0.0"
        +      }
        +    },
        +    "node_modules/parse-url": {
        +      "version": "8.1.0",
        +      "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz",
        +      "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==",
        +      "dev": true,
        +      "dependencies": {
        +        "parse-path": "^7.0.0"
        +      }
        +    },
        +    "node_modules/parse5": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
        +      "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
        +      "dev": true
        +    },
        +    "node_modules/parse5-htmlparser2-tree-adapter": {
        +      "version": "7.1.0",
        +      "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
        +      "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
               "dev": true,
               "dependencies": {
        -        "error-ex": "^1.2.0"
        +        "domhandler": "^5.0.3",
        +        "parse5": "^7.0.0"
               },
        +      "funding": {
        +        "url": "https://github.com/inikulin/parse5?sponsor=1"
        +      }
        +    },
        +    "node_modules/parse5-htmlparser2-tree-adapter/node_modules/entities": {
        +      "version": "4.5.0",
        +      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
        +      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
        +      "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=0.12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/fb55/entities?sponsor=1"
               }
             },
        -    "node_modules/parse-json-object": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/parse-json-object/-/parse-json-object-2.0.1.tgz",
        -      "integrity": "sha512-/oF7PUUBjCqHmMEE6xIQeX5ZokQ9+miudACzPt4KBU2qi6CxZYPdisPXx4ad7wpZJYi2ZpcW2PacLTU3De3ebw==",
        +    "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": {
        +      "version": "7.2.1",
        +      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
        +      "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
               "dev": true,
               "dependencies": {
        -        "types-json": "^1.2.0"
        +        "entities": "^4.5.0"
        +      },
        +      "funding": {
        +        "url": "https://github.com/inikulin/parse5?sponsor=1"
               }
             },
        -    "node_modules/parse-passwd": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
        -      "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
        +    "node_modules/parse5-parser-stream": {
        +      "version": "7.1.2",
        +      "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
        +      "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "dependencies": {
        +        "parse5": "^7.0.0"
        +      },
        +      "funding": {
        +        "url": "https://github.com/inikulin/parse5?sponsor=1"
               }
             },
        -    "node_modules/parseurl": {
        -      "version": "1.3.3",
        -      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
        -      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
        +    "node_modules/parse5-parser-stream/node_modules/entities": {
        +      "version": "4.5.0",
        +      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
        +      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.8"
        +        "node": ">=0.12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/fb55/entities?sponsor=1"
               }
             },
        -    "node_modules/path-browserify": {
        -      "version": "0.0.1",
        -      "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
        -      "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
        -      "dev": true
        +    "node_modules/parse5-parser-stream/node_modules/parse5": {
        +      "version": "7.2.1",
        +      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
        +      "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
        +      "dev": true,
        +      "dependencies": {
        +        "entities": "^4.5.0"
        +      },
        +      "funding": {
        +        "url": "https://github.com/inikulin/parse5?sponsor=1"
        +      }
             },
             "node_modules/path-exists": {
               "version": "4.0.0",
        @@ -14176,12 +9809,12 @@
               }
             },
             "node_modules/path-key": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
        -      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
        +      "version": "3.1.1",
        +      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
        +      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
               "dev": true,
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
               }
             },
             "node_modules/path-parse": {
        @@ -14190,15 +9823,6 @@
               "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
               "dev": true
             },
        -    "node_modules/path-platform": {
        -      "version": "0.11.15",
        -      "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz",
        -      "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.8.0"
        -      }
        -    },
             "node_modules/path-root": {
               "version": "0.1.1",
               "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz",
        @@ -14220,10 +9844,26 @@
                 "node": ">=0.10.0"
               }
             },
        -    "node_modules/path-to-regexp": {
        -      "version": "0.1.10",
        -      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
        -      "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
        +    "node_modules/path-scurry": {
        +      "version": "1.11.1",
        +      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
        +      "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
        +      "dev": true,
        +      "dependencies": {
        +        "lru-cache": "^10.2.0",
        +        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
        +      },
        +      "engines": {
        +        "node": ">=16 || 14 >=14.18"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
        +      }
        +    },
        +    "node_modules/path-scurry/node_modules/lru-cache": {
        +      "version": "10.4.3",
        +      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
        +      "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
               "dev": true
             },
             "node_modules/path-type": {
        @@ -14235,20 +9875,19 @@
                 "node": ">=8"
               }
             },
        -    "node_modules/pbkdf2": {
        -      "version": "3.0.17",
        -      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
        -      "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
        +    "node_modules/pathe": {
        +      "version": "1.1.2",
        +      "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
        +      "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
        +      "dev": true
        +    },
        +    "node_modules/pathval": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
        +      "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
               "dev": true,
        -      "dependencies": {
        -        "create-hash": "^1.1.2",
        -        "create-hmac": "^1.1.4",
        -        "ripemd160": "^2.0.1",
        -        "safe-buffer": "^5.0.1",
        -        "sha.js": "^2.4.8"
        -      },
               "engines": {
        -        "node": ">=0.12"
        +        "node": ">= 14.16"
               }
             },
             "node_modules/pegjs": {
        @@ -14269,6 +9908,12 @@
               "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
               "dev": true
             },
        +    "node_modules/picocolors": {
        +      "version": "1.1.1",
        +      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
        +      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
        +      "dev": true
        +    },
             "node_modules/picomatch": {
               "version": "2.3.1",
               "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
        @@ -14281,70 +9926,16 @@
                 "url": "https://github.com/sponsors/jonschlinkert"
               }
             },
        -    "node_modules/pify": {
        -      "version": "4.0.1",
        -      "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
        -      "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/pinkie": {
        -      "version": "2.0.4",
        -      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
        -      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/pinkie-promise": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
        -      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
        -      "dev": true,
        -      "dependencies": {
        -        "pinkie": "^2.0.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
        -    "node_modules/pirates": {
        -      "version": "4.0.1",
        -      "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz",
        -      "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==",
        -      "dev": true,
        -      "dependencies": {
        -        "node-modules-regexp": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": ">= 6"
        -      }
        -    },
        -    "node_modules/pkg-dir": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
        -      "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
        -      "dev": true,
        -      "dependencies": {
        -        "find-up": "^3.0.0"
        -      },
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/pkg-dir/node_modules/find-up": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
        -      "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
        +    "node_modules/pidtree": {
        +      "version": "0.6.0",
        +      "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
        +      "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
               "dev": true,
        -      "dependencies": {
        -        "locate-path": "^3.0.0"
        +      "bin": {
        +        "pidtree": "bin/pidtree.js"
               },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=0.10"
               }
             },
             "node_modules/please-upgrade-node": {
        @@ -14356,102 +9947,94 @@
                 "semver-compare": "^1.0.0"
               }
             },
        -    "node_modules/portscanner": {
        -      "version": "2.2.0",
        -      "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz",
        -      "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==",
        +    "node_modules/postcss": {
        +      "version": "8.5.1",
        +      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
        +      "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "opencollective",
        +          "url": "https://opencollective.com/postcss/"
        +        },
        +        {
        +          "type": "tidelift",
        +          "url": "https://tidelift.com/funding/github/npm/postcss"
        +        },
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/ai"
        +        }
        +      ],
               "dependencies": {
        -        "async": "^2.6.0",
        -        "is-number-like": "^1.0.3"
        +        "nanoid": "^3.3.8",
        +        "picocolors": "^1.1.1",
        +        "source-map-js": "^1.2.1"
               },
               "engines": {
        -        "node": ">=0.4",
        -        "npm": ">=1.0.0"
        -      }
        -    },
        -    "node_modules/prelude-ls": {
        -      "version": "1.2.1",
        -      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
        -      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.8.0"
        -      }
        -    },
        -    "node_modules/pretty-bytes": {
        -      "version": "5.6.0",
        -      "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
        -      "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=6"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/pretty-fast": {
        -      "version": "0.2.7",
        -      "resolved": "https://registry.npmjs.org/pretty-fast/-/pretty-fast-0.2.7.tgz",
        -      "integrity": "sha512-pc70Uq1tyO+/fuW30e4eF/r1uZospIVpy+jdXeb3hbh2WJMEyqCzvEUDNhXxTCy3GsG7u7rty2lm2zj+2wooJA==",
        -      "dev": true,
        -      "dependencies": {
        -        "acorn": "~8.2.4",
        -        "optimist": "~0.6.1",
        -        "source-map": "^0.5.7"
        -      },
        -      "bin": {
        -        "pretty-fast": "bin/pretty-fast"
        +        "node": "^10 || ^12 || >=14"
               }
             },
        -    "node_modules/pretty-fast/node_modules/acorn": {
        -      "version": "8.2.4",
        -      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.2.4.tgz",
        -      "integrity": "sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg==",
        +    "node_modules/postcss/node_modules/nanoid": {
        +      "version": "3.3.8",
        +      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
        +      "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/ai"
        +        }
        +      ],
               "bin": {
        -        "acorn": "bin/acorn"
        +        "nanoid": "bin/nanoid.cjs"
               },
               "engines": {
        -        "node": ">=0.4.0"
        +        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
               }
             },
        -    "node_modules/pretty-fast/node_modules/source-map": {
        -      "version": "0.5.7",
        -      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
        -      "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
        +    "node_modules/prelude-ls": {
        +      "version": "1.2.1",
        +      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
        +      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">= 0.8.0"
               }
             },
             "node_modules/pretty-format": {
        -      "version": "21.2.1",
        -      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-21.2.1.tgz",
        -      "integrity": "sha512-ZdWPGYAnYfcVP8yKA3zFjCn8s4/17TeYH28MXuC8vTp0o21eXjbFGcOAXZEaDaOFJjc3h2qa7HQNHNshhvoh2A==",
        +      "version": "27.5.1",
        +      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
        +      "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
               "dev": true,
               "dependencies": {
        -        "ansi-regex": "^3.0.0",
        -        "ansi-styles": "^3.2.0"
        +        "ansi-regex": "^5.0.1",
        +        "ansi-styles": "^5.0.0",
        +        "react-is": "^17.0.1"
        +      },
        +      "engines": {
        +        "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
               }
             },
             "node_modules/pretty-format/node_modules/ansi-regex": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
        -      "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
               "dev": true,
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
               }
             },
        -    "node_modules/private": {
        -      "version": "0.1.8",
        -      "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
        -      "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
        +    "node_modules/pretty-format/node_modules/ansi-styles": {
        +      "version": "5.2.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
        +      "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.6"
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
             "node_modules/process": {
        @@ -14478,71 +10061,105 @@
                 "node": ">=0.4.0"
               }
             },
        -    "node_modules/promise-map-series": {
        -      "version": "0.2.3",
        -      "resolved": "https://registry.npmjs.org/promise-map-series/-/promise-map-series-0.2.3.tgz",
        -      "integrity": "sha1-wtN3r8kyU/a9A9u3d1XriKsgqEc=",
        +    "node_modules/property-information": {
        +      "version": "6.5.0",
        +      "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
        +      "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
               "dev": true,
        -      "dependencies": {
        -        "rsvp": "^3.0.14"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/proto-list": {
        -      "version": "1.2.4",
        -      "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
        -      "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
        +    "node_modules/protocols": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz",
        +      "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==",
               "dev": true
             },
        -    "node_modules/proxy-addr": {
        -      "version": "2.0.7",
        -      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
        -      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
        +    "node_modules/proxy-agent": {
        +      "version": "6.5.0",
        +      "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
        +      "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
               "dev": true,
               "dependencies": {
        -        "forwarded": "0.2.0",
        -        "ipaddr.js": "1.9.1"
        +        "agent-base": "^7.1.2",
        +        "debug": "^4.3.4",
        +        "http-proxy-agent": "^7.0.1",
        +        "https-proxy-agent": "^7.0.6",
        +        "lru-cache": "^7.14.1",
        +        "pac-proxy-agent": "^7.1.0",
        +        "proxy-from-env": "^1.1.0",
        +        "socks-proxy-agent": "^8.0.5"
               },
               "engines": {
        -        "node": ">= 0.10"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/proxy-from-env": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
        -      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
        -      "dev": true
        -    },
        -    "node_modules/pseudomap": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
        -      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
        -      "dev": true
        +    "node_modules/proxy-agent/node_modules/agent-base": {
        +      "version": "7.1.3",
        +      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
        +      "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">= 14"
        +      }
             },
        -    "node_modules/psl": {
        -      "version": "1.9.0",
        -      "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
        -      "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
        +    "node_modules/proxy-agent/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
        -      "optional": true
        +      "dependencies": {
        +        "ms": "^2.1.3"
        +      },
        +      "engines": {
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
        +      }
             },
        -    "node_modules/public-encrypt": {
        -      "version": "4.0.3",
        -      "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
        -      "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
        +    "node_modules/proxy-agent/node_modules/https-proxy-agent": {
        +      "version": "7.0.6",
        +      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
        +      "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
               "dev": true,
               "dependencies": {
        -        "bn.js": "^4.1.0",
        -        "browserify-rsa": "^4.0.0",
        -        "create-hash": "^1.1.0",
        -        "parse-asn1": "^5.0.0",
        -        "randombytes": "^2.0.1",
        -        "safe-buffer": "^5.1.2"
        +        "agent-base": "^7.1.2",
        +        "debug": "4"
        +      },
        +      "engines": {
        +        "node": ">= 14"
               }
             },
        -    "node_modules/public-encrypt/node_modules/safe-buffer": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
        -      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
        +    "node_modules/proxy-agent/node_modules/lru-cache": {
        +      "version": "7.18.3",
        +      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
        +      "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=12"
        +      }
        +    },
        +    "node_modules/proxy-agent/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        +      "dev": true
        +    },
        +    "node_modules/proxy-from-env": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
        +      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
        +      "dev": true
        +    },
        +    "node_modules/psl": {
        +      "version": "1.9.0",
        +      "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
        +      "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
               "dev": true
             },
             "node_modules/pump": {
        @@ -14555,72 +10172,33 @@
                 "once": "^1.3.1"
               }
             },
        -    "node_modules/punycode": {
        -      "version": "1.4.1",
        -      "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
        -      "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
        -      "dev": true
        -    },
        -    "node_modules/pupa": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz",
        -      "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==",
        -      "dev": true,
        -      "dependencies": {
        -        "escape-goat": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=12.20"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/puppeteer": {
        -      "version": "18.2.1",
        -      "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-18.2.1.tgz",
        -      "integrity": "sha512-7+UhmYa7wxPh2oMRwA++k8UGVDxh3YdWFB52r9C3tM81T6BU7cuusUSxImz0GEYSOYUKk/YzIhkQ6+vc0gHbxQ==",
        -      "deprecated": "< 19.4.0 is no longer supported",
        -      "dev": true,
        -      "hasInstallScript": true,
        -      "dependencies": {
        -        "https-proxy-agent": "5.0.1",
        -        "progress": "2.0.3",
        -        "proxy-from-env": "1.1.0",
        -        "puppeteer-core": "18.2.1"
        -      },
        -      "engines": {
        -        "node": ">=14.1.0"
        -      }
        -    },
             "node_modules/puppeteer-core": {
        -      "version": "18.2.1",
        -      "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-18.2.1.tgz",
        -      "integrity": "sha512-MRtTAZfQTluz3U2oU/X2VqVWPcR1+94nbA2V6ZrSZRVEwLqZ8eclZ551qGFQD/vD2PYqHJwWOW/fpC721uznVw==",
        +      "version": "22.15.0",
        +      "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz",
        +      "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==",
               "dev": true,
        +      "optional": true,
        +      "peer": true,
               "dependencies": {
        -        "cross-fetch": "3.1.5",
        -        "debug": "4.3.4",
        -        "devtools-protocol": "0.0.1045489",
        -        "extract-zip": "2.0.1",
        -        "https-proxy-agent": "5.0.1",
        -        "proxy-from-env": "1.1.0",
        -        "rimraf": "3.0.2",
        -        "tar-fs": "2.1.1",
        -        "unbzip2-stream": "1.4.3",
        -        "ws": "8.9.0"
        +        "@puppeteer/browsers": "2.3.0",
        +        "chromium-bidi": "0.6.3",
        +        "debug": "^4.3.6",
        +        "devtools-protocol": "0.0.1312386",
        +        "ws": "^8.18.0"
               },
               "engines": {
        -        "node": ">=14.1.0"
        +        "node": ">=18"
               }
             },
             "node_modules/puppeteer-core/node_modules/debug": {
        -      "version": "4.3.4",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
        -      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
        +      "optional": true,
        +      "peer": true,
               "dependencies": {
        -        "ms": "2.1.2"
        +        "ms": "^2.1.3"
               },
               "engines": {
                 "node": ">=6.0"
        @@ -14632,37 +10210,26 @@
               }
             },
             "node_modules/puppeteer-core/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        -      "dev": true
        -    },
        -    "node_modules/puppeteer-core/node_modules/rimraf": {
        -      "version": "3.0.2",
        -      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
        -      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
               "dev": true,
        -      "dependencies": {
        -        "glob": "^7.1.3"
        -      },
        -      "bin": {
        -        "rimraf": "bin.js"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/isaacs"
        -      }
        +      "optional": true,
        +      "peer": true
             },
             "node_modules/puppeteer-core/node_modules/ws": {
        -      "version": "8.9.0",
        -      "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz",
        -      "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==",
        +      "version": "8.18.0",
        +      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
        +      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
               "dev": true,
        +      "optional": true,
        +      "peer": true,
               "engines": {
                 "node": ">=10.0.0"
               },
               "peerDependencies": {
                 "bufferutil": "^4.0.1",
        -        "utf-8-validate": "^5.0.2"
        +        "utf-8-validate": ">=5.0.2"
               },
               "peerDependenciesMeta": {
                 "bufferutil": {
        @@ -14673,46 +10240,17 @@
                 }
               }
             },
        -    "node_modules/qs": {
        -      "version": "6.13.0",
        -      "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
        -      "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
        -      "dev": true,
        -      "dependencies": {
        -        "side-channel": "^1.0.6"
        -      },
        -      "engines": {
        -        "node": ">=0.6"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        -      }
        -    },
        -    "node_modules/querystring": {
        -      "version": "0.2.0",
        -      "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
        -      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
        -      "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.4.x"
        -      }
        -    },
        -    "node_modules/querystring-es3": {
        -      "version": "0.2.1",
        -      "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
        -      "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.4.x"
        -      }
        +    "node_modules/query-selector-shadow-dom": {
        +      "version": "1.0.1",
        +      "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz",
        +      "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==",
        +      "dev": true
             },
             "node_modules/querystringify": {
               "version": "2.2.0",
               "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
               "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
        -      "dev": true,
        -      "optional": true
        +      "dev": true
             },
             "node_modules/queue-microtask": {
               "version": "1.2.3",
        @@ -14734,17 +10272,11 @@
                 }
               ]
             },
        -    "node_modules/quick-lru": {
        -      "version": "6.1.1",
        -      "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.1.tgz",
        -      "integrity": "sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        +    "node_modules/queue-tick": {
        +      "version": "1.0.1",
        +      "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
        +      "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
        +      "dev": true
             },
             "node_modules/randombytes": {
               "version": "2.1.0",
        @@ -14755,93 +10287,12 @@
                 "safe-buffer": "^5.1.0"
               }
             },
        -    "node_modules/randomfill": {
        -      "version": "1.0.4",
        -      "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
        -      "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
        -      "dev": true,
        -      "dependencies": {
        -        "randombytes": "^2.0.5",
        -        "safe-buffer": "^5.1.0"
        -      }
        -    },
        -    "node_modules/range-parser": {
        -      "version": "1.2.1",
        -      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
        -      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">= 0.6"
        -      }
        -    },
        -    "node_modules/raw-body": {
        -      "version": "1.1.7",
        -      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz",
        -      "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=",
        -      "dev": true,
        -      "dependencies": {
        -        "bytes": "1",
        -        "string_decoder": "0.10"
        -      },
        -      "engines": {
        -        "node": ">= 0.8.0"
        -      }
        -    },
        -    "node_modules/raw-body/node_modules/string_decoder": {
        -      "version": "0.10.31",
        -      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
        -      "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
        -      "dev": true
        -    },
        -    "node_modules/rc": {
        -      "version": "1.2.8",
        -      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
        -      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
        -      "dev": true,
        -      "dependencies": {
        -        "deep-extend": "^0.6.0",
        -        "ini": "~1.3.0",
        -        "minimist": "^1.2.0",
        -        "strip-json-comments": "~2.0.1"
        -      },
        -      "bin": {
        -        "rc": "cli.js"
        -      }
        -    },
        -    "node_modules/read-file-safe": {
        -      "version": "1.0.10",
        -      "resolved": "https://registry.npmjs.org/read-file-safe/-/read-file-safe-1.0.10.tgz",
        -      "integrity": "sha512-qW25fd2uMX3dV6Ui/R0jYK1MhTpjx8FO/VHaHTXzwWsGnkNwLRcqYfCXd9qDM+NZ273DPUvP2RaimYuLSu1K/g==",
        +    "node_modules/react-is": {
        +      "version": "17.0.2",
        +      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
        +      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
               "dev": true
             },
        -    "node_modules/read-json-safe": {
        -      "version": "1.0.5",
        -      "resolved": "https://registry.npmjs.org/read-json-safe/-/read-json-safe-1.0.5.tgz",
        -      "integrity": "sha512-SJyNY/U9+vW35FPus22Qvv1oilnR7PCfN2E70uKQEGaJS313A5/cz9Yhv7ZtWzZ+XIwrtEPxXf10BOyYemHehA==",
        -      "dev": true,
        -      "dependencies": {
        -        "parse-json-object": "^1.0.5",
        -        "read-file-safe": "^1.0.5"
        -      }
        -    },
        -    "node_modules/read-json-safe/node_modules/parse-json-object": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/parse-json-object/-/parse-json-object-1.1.0.tgz",
        -      "integrity": "sha512-4w5s6uJY1tW9REY8UwUOyaZKSKsrbQrMEzlV/Le/g5t4iMWuuyK83pZZ0OZimSOL9iyv2ORvRSgz71Ekd7iD3g==",
        -      "dev": true,
        -      "dependencies": {
        -        "types-json": "^1.0.6"
        -      }
        -    },
        -    "node_modules/read-only-stream": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
        -      "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=",
        -      "dev": true,
        -      "dependencies": {
        -        "readable-stream": "^2.0.2"
        -      }
        -    },
             "node_modules/read-pkg": {
               "version": "7.1.0",
               "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz",
        @@ -15076,200 +10527,161 @@
                 "util-deprecate": "~1.0.1"
               }
             },
        -    "node_modules/readdirp": {
        -      "version": "3.6.0",
        -      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
        -      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
        +    "node_modules/readdir-glob": {
        +      "version": "1.1.3",
        +      "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
        +      "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
               "dev": true,
               "dependencies": {
        -        "picomatch": "^2.2.1"
        -      },
        -      "engines": {
        -        "node": ">=8.10.0"
        +        "minimatch": "^5.1.0"
               }
             },
        -    "node_modules/redent": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz",
        -      "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==",
        +    "node_modules/readdir-glob/node_modules/brace-expansion": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
        +      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
               "dev": true,
               "dependencies": {
        -        "indent-string": "^5.0.0",
        -        "strip-indent": "^4.0.0"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "balanced-match": "^1.0.0"
               }
             },
        -    "node_modules/redent/node_modules/indent-string": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
        -      "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
        +    "node_modules/readdir-glob/node_modules/minimatch": {
        +      "version": "5.1.6",
        +      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
        +      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
               "dev": true,
        -      "engines": {
        -        "node": ">=12"
        +      "dependencies": {
        +        "brace-expansion": "^2.0.1"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "engines": {
        +        "node": ">=10"
               }
             },
        -    "node_modules/regenerate": {
        -      "version": "1.4.1",
        -      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz",
        -      "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==",
        -      "dev": true
        -    },
        -    "node_modules/regenerate-unicode-properties": {
        -      "version": "8.2.0",
        -      "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz",
        -      "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==",
        +    "node_modules/readdirp": {
        +      "version": "3.6.0",
        +      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
        +      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
               "dev": true,
               "dependencies": {
        -        "regenerate": "^1.4.0"
        +        "picomatch": "^2.2.1"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8.10.0"
               }
             },
             "node_modules/regenerator-runtime": {
        -      "version": "0.13.3",
        -      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
        -      "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==",
        +      "version": "0.14.1",
        +      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
        +      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
               "dev": true
             },
        -    "node_modules/regenerator-transform": {
        -      "version": "0.14.4",
        -      "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz",
        -      "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==",
        -      "dev": true,
        -      "dependencies": {
        -        "@babel/runtime": "^7.8.4",
        -        "private": "^0.1.8"
        -      }
        -    },
        -    "node_modules/regenerator-transform/node_modules/@babel/runtime": {
        -      "version": "7.10.2",
        -      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz",
        -      "integrity": "sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==",
        +    "node_modules/remark": {
        +      "version": "14.0.3",
        +      "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.3.tgz",
        +      "integrity": "sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==",
               "dev": true,
               "dependencies": {
        -        "regenerator-runtime": "^0.13.4"
        -      }
        -    },
        -    "node_modules/regenerator-transform/node_modules/regenerator-runtime": {
        -      "version": "0.13.5",
        -      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
        -      "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==",
        -      "dev": true
        -    },
        -    "node_modules/regexpp": {
        -      "version": "3.2.0",
        -      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
        -      "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        +        "@types/mdast": "^3.0.0",
        +        "remark-parse": "^10.0.0",
        +        "remark-stringify": "^10.0.0",
        +        "unified": "^10.0.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/mysticatea"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/regexpu-core": {
        -      "version": "4.7.0",
        -      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz",
        -      "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==",
        +    "node_modules/remark-gfm": {
        +      "version": "3.0.1",
        +      "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz",
        +      "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==",
               "dev": true,
               "dependencies": {
        -        "regenerate": "^1.4.0",
        -        "regenerate-unicode-properties": "^8.2.0",
        -        "regjsgen": "^0.5.1",
        -        "regjsparser": "^0.6.4",
        -        "unicode-match-property-ecmascript": "^1.0.4",
        -        "unicode-match-property-value-ecmascript": "^1.2.0"
        +        "@types/mdast": "^3.0.0",
        +        "mdast-util-gfm": "^2.0.0",
        +        "micromark-extension-gfm": "^2.0.0",
        +        "unified": "^10.0.0"
               },
        -      "engines": {
        -        "node": ">=4"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/registry-auth-token": {
        -      "version": "4.2.2",
        -      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz",
        -      "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==",
        +    "node_modules/remark-html": {
        +      "version": "15.0.2",
        +      "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-15.0.2.tgz",
        +      "integrity": "sha512-/CIOI7wzHJzsh48AiuIyIe1clxVkUtreul73zcCXLub0FmnevQE0UMFDQm7NUx8/3rl/4zCshlMfqBdWScQthw==",
               "dev": true,
               "dependencies": {
        -        "rc": "1.2.8"
        +        "@types/mdast": "^3.0.0",
        +        "hast-util-sanitize": "^4.0.0",
        +        "hast-util-to-html": "^8.0.0",
        +        "mdast-util-to-hast": "^12.0.0",
        +        "unified": "^10.0.0"
               },
        -      "engines": {
        -        "node": ">=6.0.0"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/registry-url": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz",
        -      "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==",
        +    "node_modules/remark-parse": {
        +      "version": "10.0.2",
        +      "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz",
        +      "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==",
               "dev": true,
               "dependencies": {
        -        "rc": "1.2.8"
        -      },
        -      "engines": {
        -        "node": ">=12"
        +        "@types/mdast": "^3.0.0",
        +        "mdast-util-from-markdown": "^1.0.0",
        +        "unified": "^10.0.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/regjsgen": {
        -      "version": "0.5.2",
        -      "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz",
        -      "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==",
        -      "dev": true
        -    },
        -    "node_modules/regjsparser": {
        -      "version": "0.6.4",
        -      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz",
        -      "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==",
        +    "node_modules/remark-reference-links": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-6.0.1.tgz",
        +      "integrity": "sha512-34wY2C6HXSuKVTRtyJJwefkUD8zBOZOSHFZ4aSTnU2F656gr9WeuQ2dL6IJDK3NPd2F6xKF2t4XXcQY9MygAXg==",
               "dev": true,
               "dependencies": {
        -        "jsesc": "~0.5.0"
        +        "@types/mdast": "^3.0.0",
        +        "unified": "^10.0.0",
        +        "unist-util-visit": "^4.0.0"
               },
        -      "bin": {
        -        "regjsparser": "bin/parser"
        -      }
        -    },
        -    "node_modules/regjsparser/node_modules/jsesc": {
        -      "version": "0.5.0",
        -      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
        -      "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
        -      "dev": true,
        -      "bin": {
        -        "jsesc": "bin/jsesc"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/release-zalgo": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",
        -      "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=",
        +    "node_modules/remark-stringify": {
        +      "version": "10.0.3",
        +      "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.3.tgz",
        +      "integrity": "sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==",
               "dev": true,
               "dependencies": {
        -        "es6-error": "^4.0.1"
        +        "@types/mdast": "^3.0.0",
        +        "mdast-util-to-markdown": "^1.0.0",
        +        "unified": "^10.0.0"
               },
        -      "engines": {
        -        "node": ">=4"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/repeating": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
        -      "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
        +    "node_modules/remark-toc": {
        +      "version": "8.0.1",
        +      "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-8.0.1.tgz",
        +      "integrity": "sha512-7he2VOm/cy13zilnOTZcyAoyoolV26ULlon6XyCFU+vG54Z/LWJnwphj/xKIDLOt66QmJUgTyUvLVHi2aAElyg==",
               "dev": true,
               "dependencies": {
        -        "is-finite": "^1.0.0"
        +        "@types/mdast": "^3.0.0",
        +        "mdast-util-toc": "^6.0.0",
        +        "unified": "^10.0.0"
               },
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
             "node_modules/require-directory": {
        @@ -15281,15 +10693,6 @@
                 "node": ">=0.10.0"
               }
             },
        -    "node_modules/require-from-string": {
        -      "version": "1.2.1",
        -      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz",
        -      "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        -      }
        -    },
             "node_modules/require-main-filename": {
               "version": "2.0.0",
               "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
        @@ -15300,69 +10703,42 @@
               "version": "1.0.0",
               "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
               "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
        -      "dev": true,
        -      "optional": true
        -    },
        -    "node_modules/resolve": {
        -      "version": "1.5.0",
        -      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz",
        -      "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==",
        -      "dev": true,
        -      "dependencies": {
        -        "path-parse": "^1.0.5"
        -      }
        -    },
        -    "node_modules/resolve-alpn": {
        -      "version": "1.2.1",
        -      "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
        -      "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
               "dev": true
             },
        -    "node_modules/resolve-cwd": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
        -      "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
        +    "node_modules/resolve": {
        +      "version": "1.22.10",
        +      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
        +      "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
               "dev": true,
               "dependencies": {
        -        "resolve-from": "^5.0.0"
        +        "is-core-module": "^2.16.0",
        +        "path-parse": "^1.0.7",
        +        "supports-preserve-symlinks-flag": "^1.0.0"
        +      },
        +      "bin": {
        +        "resolve": "bin/resolve"
               },
               "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/resolve-cwd/node_modules/resolve-from": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
        -      "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        +        "node": ">= 0.4"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/ljharb"
               }
             },
        -    "node_modules/resolve-dir": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
        -      "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
        +    "node_modules/resq": {
        +      "version": "1.11.0",
        +      "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz",
        +      "integrity": "sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==",
               "dev": true,
               "dependencies": {
        -        "expand-tilde": "^2.0.0",
        -        "global-modules": "^1.0.0"
        -      },
        -      "engines": {
        -        "node": ">=0.10.0"
        +        "fast-deep-equal": "^2.0.1"
               }
             },
        -    "node_modules/responselike": {
        +    "node_modules/resq/node_modules/fast-deep-equal": {
               "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
        -      "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
        -      "dev": true,
        -      "dependencies": {
        -        "lowercase-keys": "^2.0.0"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        +      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
        +      "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==",
        +      "dev": true
             },
             "node_modules/restore-cursor": {
               "version": "3.1.0",
        @@ -15387,581 +10763,438 @@
                 "node": ">=0.10.0"
               }
             },
        -    "node_modules/rimraf": {
        -      "version": "2.7.1",
        -      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
        -      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
        -      "dev": true,
        -      "dependencies": {
        -        "glob": "^7.1.3"
        -      },
        -      "bin": {
        -        "rimraf": "bin.js"
        -      }
        -    },
        -    "node_modules/ripemd160": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
        -      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
        -      "dev": true,
        -      "dependencies": {
        -        "hash-base": "^3.0.0",
        -        "inherits": "^2.0.1"
        -      }
        +    "node_modules/rfdc": {
        +      "version": "1.4.1",
        +      "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
        +      "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
        +      "dev": true
             },
        -    "node_modules/rsvp": {
        -      "version": "3.6.2",
        -      "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz",
        -      "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==",
        -      "dev": true,
        -      "engines": {
        -        "node": "0.12.* || 4.* || 6.* || >= 7.*"
        -      }
        +    "node_modules/rgb2hex": {
        +      "version": "0.2.5",
        +      "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.5.tgz",
        +      "integrity": "sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==",
        +      "dev": true
             },
        -    "node_modules/run-applescript": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
        -      "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==",
        +    "node_modules/rollup": {
        +      "version": "4.31.0",
        +      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.31.0.tgz",
        +      "integrity": "sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==",
               "dev": true,
               "dependencies": {
        -        "execa": "^5.0.0"
        +        "@types/estree": "1.0.6"
        +      },
        +      "bin": {
        +        "rollup": "dist/bin/rollup"
               },
               "engines": {
        -        "node": ">=12"
        +        "node": ">=18.0.0",
        +        "npm": ">=8.0.0"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "optionalDependencies": {
        +        "@rollup/rollup-android-arm-eabi": "4.31.0",
        +        "@rollup/rollup-android-arm64": "4.31.0",
        +        "@rollup/rollup-darwin-arm64": "4.31.0",
        +        "@rollup/rollup-darwin-x64": "4.31.0",
        +        "@rollup/rollup-freebsd-arm64": "4.31.0",
        +        "@rollup/rollup-freebsd-x64": "4.31.0",
        +        "@rollup/rollup-linux-arm-gnueabihf": "4.31.0",
        +        "@rollup/rollup-linux-arm-musleabihf": "4.31.0",
        +        "@rollup/rollup-linux-arm64-gnu": "4.31.0",
        +        "@rollup/rollup-linux-arm64-musl": "4.31.0",
        +        "@rollup/rollup-linux-loongarch64-gnu": "4.31.0",
        +        "@rollup/rollup-linux-powerpc64le-gnu": "4.31.0",
        +        "@rollup/rollup-linux-riscv64-gnu": "4.31.0",
        +        "@rollup/rollup-linux-s390x-gnu": "4.31.0",
        +        "@rollup/rollup-linux-x64-gnu": "4.31.0",
        +        "@rollup/rollup-linux-x64-musl": "4.31.0",
        +        "@rollup/rollup-win32-arm64-msvc": "4.31.0",
        +        "@rollup/rollup-win32-ia32-msvc": "4.31.0",
        +        "@rollup/rollup-win32-x64-msvc": "4.31.0",
        +        "fsevents": "~2.3.2"
               }
             },
        -    "node_modules/run-applescript/node_modules/cross-spawn": {
        -      "version": "7.0.3",
        -      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
        -      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
        +    "node_modules/rollup-plugin-string": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/rollup-plugin-string/-/rollup-plugin-string-3.0.0.tgz",
        +      "integrity": "sha512-vqyzgn9QefAgeKi+Y4A7jETeIAU1zQmS6VotH6bzm/zmUQEnYkpIGRaOBPY41oiWYV4JyBoGAaBjYMYuv+6wVw==",
               "dev": true,
               "dependencies": {
        -        "path-key": "^3.1.0",
        -        "shebang-command": "^2.0.0",
        -        "which": "^2.0.1"
        -      },
        -      "engines": {
        -        "node": ">= 8"
        +        "rollup-pluginutils": "^2.4.1"
               }
             },
        -    "node_modules/run-applescript/node_modules/execa": {
        -      "version": "5.1.1",
        -      "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
        -      "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
        +    "node_modules/rollup-plugin-visualizer": {
        +      "version": "5.14.0",
        +      "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.14.0.tgz",
        +      "integrity": "sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==",
               "dev": true,
               "dependencies": {
        -        "cross-spawn": "^7.0.3",
        -        "get-stream": "^6.0.0",
        -        "human-signals": "^2.1.0",
        -        "is-stream": "^2.0.0",
        -        "merge-stream": "^2.0.0",
        -        "npm-run-path": "^4.0.1",
        -        "onetime": "^5.1.2",
        -        "signal-exit": "^3.0.3",
        -        "strip-final-newline": "^2.0.0"
        +        "open": "^8.4.0",
        +        "picomatch": "^4.0.2",
        +        "source-map": "^0.7.4",
        +        "yargs": "^17.5.1"
               },
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sindresorhus/execa?sponsor=1"
        -      }
        -    },
        -    "node_modules/run-applescript/node_modules/get-stream": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
        -      "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        +      "bin": {
        +        "rollup-plugin-visualizer": "dist/bin/cli.js"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/run-applescript/node_modules/human-signals": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
        -      "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10.17.0"
        -      }
        -    },
        -    "node_modules/run-applescript/node_modules/is-stream": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
        -      "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
        -      "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=18"
               },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/run-applescript/node_modules/npm-run-path": {
        -      "version": "4.0.1",
        -      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
        -      "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
        -      "dev": true,
        -      "dependencies": {
        -        "path-key": "^3.0.0"
        +      "peerDependencies": {
        +        "rolldown": "1.x",
        +        "rollup": "2.x || 3.x || 4.x"
               },
        -      "engines": {
        -        "node": ">=8"
        +      "peerDependenciesMeta": {
        +        "rolldown": {
        +          "optional": true
        +        },
        +        "rollup": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/run-applescript/node_modules/path-key": {
        -      "version": "3.1.1",
        -      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
        -      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/ansi-regex": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
               "dev": true,
               "engines": {
                 "node": ">=8"
               }
             },
        -    "node_modules/run-applescript/node_modules/shebang-command": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
        -      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
               "dependencies": {
        -        "shebang-regex": "^3.0.0"
        +        "color-convert": "^2.0.1"
               },
               "engines": {
                 "node": ">=8"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/run-applescript/node_modules/shebang-regex": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
        -      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/run-applescript/node_modules/strip-final-newline": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
        -      "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=6"
        -      }
        -    },
        -    "node_modules/run-applescript/node_modules/which": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
        -      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/cliui": {
        +      "version": "8.0.1",
        +      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
        +      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
               "dev": true,
               "dependencies": {
        -        "isexe": "^2.0.0"
        -      },
        -      "bin": {
        -        "node-which": "bin/node-which"
        +        "string-width": "^4.2.0",
        +        "strip-ansi": "^6.0.1",
        +        "wrap-ansi": "^7.0.0"
               },
               "engines": {
        -        "node": ">= 8"
        -      }
        -    },
        -    "node_modules/run-async": {
        -      "version": "2.4.1",
        -      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
        -      "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.12.0"
        -      }
        -    },
        -    "node_modules/run-parallel": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
        -      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
        -      "dev": true,
        -      "funding": [
        -        {
        -          "type": "github",
        -          "url": "https://github.com/sponsors/feross"
        -        },
        -        {
        -          "type": "patreon",
        -          "url": "https://www.patreon.com/feross"
        -        },
        -        {
        -          "type": "consulting",
        -          "url": "https://feross.org/support"
        -        }
        -      ],
        -      "dependencies": {
        -        "queue-microtask": "^1.2.2"
        +        "node": ">=12"
               }
             },
        -    "node_modules/rxjs": {
        -      "version": "5.5.6",
        -      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.6.tgz",
        -      "integrity": "sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/color-convert": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
               "dependencies": {
        -        "symbol-observable": "1.0.1"
        +        "color-name": "~1.1.4"
               },
               "engines": {
        -        "npm": ">=2.0.0"
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/safe-buffer": {
        -      "version": "5.1.1",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
        -      "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
        -      "dev": true
        -    },
        -    "node_modules/safe-json-parse": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz",
        -      "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=",
        -      "dev": true
        -    },
        -    "node_modules/safer-buffer": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
        -      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/emoji-regex": {
        +      "version": "8.0.0",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        +      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
               "dev": true
             },
        -    "node_modules/scoped-regex": {
        +    "node_modules/rollup-plugin-visualizer/node_modules/is-fullwidth-code-point": {
               "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-3.0.0.tgz",
        -      "integrity": "sha512-yEsN6TuxZhZ1Tl9iB81frTNS292m0I/IG7+w8lTvfcJQP2x3vnpOoevjBoE3Np5A6KnZM2+RtVenihj9t6NiYg==",
        +      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        +      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
               "dev": true,
               "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/semver": {
        -      "version": "5.4.1",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
        -      "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
        -      "dev": true,
        -      "bin": {
        -        "semver": "bin/semver"
        +        "node": ">=8"
               }
             },
        -    "node_modules/semver-compare": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
        -      "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
        -      "dev": true
        -    },
        -    "node_modules/semver-diff": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz",
        -      "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/picomatch": {
        +      "version": "4.0.2",
        +      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
        +      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
               "dev": true,
        -      "dependencies": {
        -        "semver": "^7.3.5"
        -      },
               "engines": {
                 "node": ">=12"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/sponsors/jonschlinkert"
               }
             },
        -    "node_modules/semver-diff/node_modules/lru-cache": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
        -      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/source-map": {
        +      "version": "0.7.4",
        +      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
        +      "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
               "dev": true,
        -      "dependencies": {
        -        "yallist": "^4.0.0"
        -      },
               "engines": {
        -        "node": ">=10"
        +        "node": ">= 8"
               }
             },
        -    "node_modules/semver-diff/node_modules/semver": {
        -      "version": "7.5.4",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
        -      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/string-width": {
        +      "version": "4.2.3",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
        +      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
               "dev": true,
               "dependencies": {
        -        "lru-cache": "^6.0.0"
        -      },
        -      "bin": {
        -        "semver": "bin/semver.js"
        +        "emoji-regex": "^8.0.0",
        +        "is-fullwidth-code-point": "^3.0.0",
        +        "strip-ansi": "^6.0.1"
               },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=8"
               }
             },
        -    "node_modules/semver-diff/node_modules/yallist": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
        -      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
        -      "dev": true
        -    },
        -    "node_modules/semver-regex": {
        -      "version": "3.1.4",
        -      "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz",
        -      "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/strip-ansi": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        +      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
               "dev": true,
        +      "dependencies": {
        +        "ansi-regex": "^5.0.1"
        +      },
               "engines": {
                 "node": ">=8"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/send": {
        -      "version": "0.19.0",
        -      "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
        -      "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/wrap-ansi": {
        +      "version": "7.0.0",
        +      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
        +      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
               "dev": true,
               "dependencies": {
        -        "debug": "2.6.9",
        -        "depd": "2.0.0",
        -        "destroy": "1.2.0",
        -        "encodeurl": "~1.0.2",
        -        "escape-html": "~1.0.3",
        -        "etag": "~1.8.1",
        -        "fresh": "0.5.2",
        -        "http-errors": "2.0.0",
        -        "mime": "1.6.0",
        -        "ms": "2.1.3",
        -        "on-finished": "2.4.1",
        -        "range-parser": "~1.2.1",
        -        "statuses": "2.0.1"
        +        "ansi-styles": "^4.0.0",
        +        "string-width": "^4.1.0",
        +        "strip-ansi": "^6.0.0"
               },
               "engines": {
        -        "node": ">= 0.8.0"
        +        "node": ">=10"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
               }
             },
        -    "node_modules/send/node_modules/depd": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
        -      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/y18n": {
        +      "version": "5.0.8",
        +      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
        +      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.8"
        +        "node": ">=10"
               }
             },
        -    "node_modules/send/node_modules/http-errors": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
        -      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
        +    "node_modules/rollup-plugin-visualizer/node_modules/yargs": {
        +      "version": "17.7.2",
        +      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
        +      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
               "dev": true,
               "dependencies": {
        -        "depd": "2.0.0",
        -        "inherits": "2.0.4",
        -        "setprototypeof": "1.2.0",
        -        "statuses": "2.0.1",
        -        "toidentifier": "1.0.1"
        +        "cliui": "^8.0.1",
        +        "escalade": "^3.1.1",
        +        "get-caller-file": "^2.0.5",
        +        "require-directory": "^2.1.1",
        +        "string-width": "^4.2.3",
        +        "y18n": "^5.0.5",
        +        "yargs-parser": "^21.1.1"
               },
               "engines": {
        -        "node": ">= 0.8"
        +        "node": ">=12"
               }
             },
        -    "node_modules/send/node_modules/inherits": {
        -      "version": "2.0.4",
        -      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
        -      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
        -      "dev": true
        -    },
        -    "node_modules/send/node_modules/ms": {
        -      "version": "2.1.3",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        -      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        -      "dev": true
        +    "node_modules/rollup-plugin-visualizer/node_modules/yargs-parser": {
        +      "version": "21.1.1",
        +      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
        +      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=12"
        +      }
             },
        -    "node_modules/send/node_modules/on-finished": {
        -      "version": "2.4.1",
        -      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
        -      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
        +    "node_modules/rollup-pluginutils": {
        +      "version": "2.8.2",
        +      "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
        +      "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
               "dev": true,
               "dependencies": {
        -        "ee-first": "1.1.1"
        -      },
        -      "engines": {
        -        "node": ">= 0.8"
        +        "estree-walker": "^0.6.1"
               }
             },
        -    "node_modules/send/node_modules/setprototypeof": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
        -      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
        +    "node_modules/rollup-pluginutils/node_modules/estree-walker": {
        +      "version": "0.6.1",
        +      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
        +      "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
               "dev": true
             },
        -    "node_modules/send/node_modules/statuses": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
        -      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
        +    "node_modules/run-async": {
        +      "version": "2.4.1",
        +      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
        +      "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.8"
        +        "node": ">=0.12.0"
               }
             },
        -    "node_modules/serialize-javascript": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
        -      "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
        +    "node_modules/run-parallel": {
        +      "version": "1.2.0",
        +      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
        +      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
        +        }
        +      ],
               "dependencies": {
        -        "randombytes": "^2.1.0"
        +        "queue-microtask": "^1.2.2"
               }
             },
        -    "node_modules/serve-index": {
        -      "version": "1.9.1",
        -      "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
        -      "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==",
        +    "node_modules/rxjs": {
        +      "version": "7.8.1",
        +      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
        +      "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
               "dev": true,
               "dependencies": {
        -        "accepts": "~1.3.4",
        -        "batch": "0.6.1",
        -        "debug": "2.6.9",
        -        "escape-html": "~1.0.3",
        -        "http-errors": "~1.6.2",
        -        "mime-types": "~2.1.17",
        -        "parseurl": "~1.3.2"
        -      },
        -      "engines": {
        -        "node": ">= 0.8.0"
        +        "tslib": "^2.1.0"
               }
             },
        -    "node_modules/serve-static": {
        -      "version": "1.16.2",
        -      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
        -      "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
        +    "node_modules/rxjs/node_modules/tslib": {
        +      "version": "2.8.1",
        +      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
        +      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
        +      "dev": true
        +    },
        +    "node_modules/sade": {
        +      "version": "1.8.1",
        +      "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
        +      "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
               "dev": true,
               "dependencies": {
        -        "encodeurl": "~2.0.0",
        -        "escape-html": "~1.0.3",
        -        "parseurl": "~1.3.3",
        -        "send": "0.19.0"
        +        "mri": "^1.1.0"
               },
               "engines": {
        -        "node": ">= 0.8.0"
        +        "node": ">=6"
               }
             },
        -    "node_modules/serve-static/node_modules/encodeurl": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
        -      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
        +    "node_modules/safaridriver": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-1.0.0.tgz",
        +      "integrity": "sha512-J92IFbskyo7OYB3Dt4aTdyhag1GlInrfbPCmMteb7aBK7PwlnGz1HI0+oyNN97j7pV9DqUAVoVgkNRMrfY47mQ==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.8"
        +        "node": ">=18.0.0"
               }
             },
        -    "node_modules/set-blocking": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
        -      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
        +    "node_modules/safe-buffer": {
        +      "version": "5.1.1",
        +      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
        +      "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
               "dev": true
             },
        -    "node_modules/set-function-length": {
        -      "version": "1.2.2",
        -      "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
        -      "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
        -      "dev": true,
        -      "dependencies": {
        -        "define-data-property": "^1.1.4",
        -        "es-errors": "^1.3.0",
        -        "function-bind": "^1.1.2",
        -        "get-intrinsic": "^1.2.4",
        -        "gopd": "^1.0.1",
        -        "has-property-descriptors": "^1.0.2"
        -      },
        -      "engines": {
        -        "node": ">= 0.4"
        -      }
        -    },
        -    "node_modules/setimmediate": {
        -      "version": "1.0.5",
        -      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
        -      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
        +    "node_modules/safer-buffer": {
        +      "version": "2.1.2",
        +      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
        +      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
               "dev": true
             },
        -    "node_modules/setprototypeof": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
        -      "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
        +    "node_modules/semver-compare": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
        +      "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
               "dev": true
             },
        -    "node_modules/sha.js": {
        -      "version": "2.4.11",
        -      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
        -      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
        +    "node_modules/semver-regex": {
        +      "version": "3.1.4",
        +      "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz",
        +      "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==",
               "dev": true,
        -      "dependencies": {
        -        "inherits": "^2.0.1",
        -        "safe-buffer": "^5.0.1"
        +      "engines": {
        +        "node": ">=8"
               },
        -      "bin": {
        -        "sha.js": "bin.js"
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/shasum": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz",
        -      "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=",
        +    "node_modules/serialize-error": {
        +      "version": "11.0.3",
        +      "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz",
        +      "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==",
               "dev": true,
               "dependencies": {
        -        "json-stable-stringify": "~0.0.0",
        -        "sha.js": "~2.4.4"
        +        "type-fest": "^2.12.2"
        +      },
        +      "engines": {
        +        "node": ">=14.16"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/shasum-object": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz",
        -      "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==",
        +    "node_modules/serialize-error/node_modules/type-fest": {
        +      "version": "2.19.0",
        +      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
        +      "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
               "dev": true,
        -      "dependencies": {
        -        "fast-safe-stringify": "^2.0.7"
        +      "engines": {
        +        "node": ">=12.20"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        +    "node_modules/set-blocking": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
        +      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
        +      "dev": true
        +    },
        +    "node_modules/setimmediate": {
        +      "version": "1.0.5",
        +      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
        +      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
        +      "dev": true
        +    },
             "node_modules/shebang-command": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
        -      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
        +      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
               "dev": true,
               "dependencies": {
        -        "shebang-regex": "^1.0.0"
        +        "shebang-regex": "^3.0.0"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
             "node_modules/shebang-regex": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
        -      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
        +      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
             "node_modules/shell-quote": {
        -      "version": "1.7.3",
        -      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
        -      "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
        -      "dev": true
        -    },
        -    "node_modules/side-channel": {
        -      "version": "1.0.6",
        -      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
        -      "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
        +      "version": "1.8.2",
        +      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz",
        +      "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==",
               "dev": true,
        -      "dependencies": {
        -        "call-bind": "^1.0.7",
        -        "es-errors": "^1.3.0",
        -        "get-intrinsic": "^1.2.4",
        -        "object-inspect": "^1.13.1"
        -      },
               "engines": {
                 "node": ">= 0.4"
               },
        @@ -15969,119 +11202,144 @@
                 "url": "https://github.com/sponsors/ljharb"
               }
             },
        +    "node_modules/siginfo": {
        +      "version": "2.0.0",
        +      "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
        +      "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
        +      "dev": true
        +    },
             "node_modules/signal-exit": {
               "version": "3.0.7",
               "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
               "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
               "dev": true
             },
        -    "node_modules/simple-cli": {
        -      "version": "5.0.5",
        -      "resolved": "https://registry.npmjs.org/simple-cli/-/simple-cli-5.0.5.tgz",
        -      "integrity": "sha512-Er2FhsIayL/sktxg6fOCdNQJBTXhlf/fswNFsdmks88xsHzQ/IXGwxYgSSKeXBq4yqn83/iD4Sg8yjagwysUgw==",
        +    "node_modules/sirv": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz",
        +      "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==",
               "dev": true,
               "dependencies": {
        -        "async": "^3.1.0",
        -        "chalk": "^2.4.2",
        -        "cross-spawn": "^7.0.0",
        -        "key-list": "^0.1.4",
        -        "lodash": "^4.17.15",
        -        "opted": "^1.0.0"
        +        "@polka/url": "^1.0.0-next.24",
        +        "mrmime": "^2.0.0",
        +        "totalist": "^3.0.0"
        +      },
        +      "engines": {
        +        "node": ">=18"
               }
             },
        -    "node_modules/simple-cli/node_modules/async": {
        -      "version": "3.2.4",
        -      "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
        -      "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
        -      "dev": true
        +    "node_modules/slash": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
        +      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=8"
        +      }
             },
        -    "node_modules/simple-cli/node_modules/cross-spawn": {
        -      "version": "7.0.1",
        -      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz",
        -      "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==",
        +    "node_modules/slice-ansi": {
        +      "version": "7.1.0",
        +      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
        +      "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
               "dev": true,
               "dependencies": {
        -        "path-key": "^3.1.0",
        -        "shebang-command": "^2.0.0",
        -        "which": "^2.0.1"
        +        "ansi-styles": "^6.2.1",
        +        "is-fullwidth-code-point": "^5.0.0"
               },
               "engines": {
        -        "node": ">= 8"
        +        "node": ">=18"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/slice-ansi?sponsor=1"
               }
             },
        -    "node_modules/simple-cli/node_modules/path-key": {
        -      "version": "3.1.1",
        -      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
        -      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
        +    "node_modules/slice-ansi/node_modules/ansi-styles": {
        +      "version": "6.2.1",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
        +      "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/simple-cli/node_modules/shebang-command": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
        -      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
        +    "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
        +      "version": "5.0.0",
        +      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
        +      "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
               "dev": true,
               "dependencies": {
        -        "shebang-regex": "^3.0.0"
        +        "get-east-asian-width": "^1.0.0"
               },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=18"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/simple-cli/node_modules/shebang-regex": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
        -      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
        +    "node_modules/smart-buffer": {
        +      "version": "4.2.0",
        +      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
        +      "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": ">= 6.0.0",
        +        "npm": ">= 3.0.0"
               }
             },
        -    "node_modules/simple-cli/node_modules/which": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
        -      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
        +    "node_modules/smob": {
        +      "version": "1.5.0",
        +      "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz",
        +      "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==",
        +      "dev": true
        +    },
        +    "node_modules/socks": {
        +      "version": "2.8.3",
        +      "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
        +      "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
               "dev": true,
               "dependencies": {
        -        "isexe": "^2.0.0"
        -      },
        -      "bin": {
        -        "node-which": "bin/node-which"
        +        "ip-address": "^9.0.5",
        +        "smart-buffer": "^4.2.0"
               },
               "engines": {
        -        "node": ">= 8"
        +        "node": ">= 10.0.0",
        +        "npm": ">= 3.0.0"
               }
             },
        -    "node_modules/simple-concat": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
        -      "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=",
        -      "dev": true
        -    },
        -    "node_modules/simple-git": {
        -      "version": "3.16.1",
        -      "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.16.1.tgz",
        -      "integrity": "sha512-xzRxMKiy1zEYeHGXgAzvuXffDS0xgsq07Oi4LWEEcVH29vLpcZ2tyQRWyK0NLLlCVaKysZeem5tC1qHEOxsKwA==",
        +    "node_modules/socks-proxy-agent": {
        +      "version": "8.0.5",
        +      "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
        +      "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
               "dev": true,
               "dependencies": {
        -        "@kwsites/file-exists": "^1.1.1",
        -        "@kwsites/promise-deferred": "^1.1.1",
        -        "debug": "^4.3.4"
        +        "agent-base": "^7.1.2",
        +        "debug": "^4.3.4",
        +        "socks": "^2.8.3"
               },
        -      "funding": {
        -        "type": "github",
        -        "url": "https://github.com/steveukx/git-js?sponsor=1"
        +      "engines": {
        +        "node": ">= 14"
               }
             },
        -    "node_modules/simple-git/node_modules/debug": {
        -      "version": "4.3.4",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
        -      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
        +    "node_modules/socks-proxy-agent/node_modules/agent-base": {
        +      "version": "7.1.3",
        +      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
        +      "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">= 14"
        +      }
        +    },
        +    "node_modules/socks-proxy-agent/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
               "dependencies": {
        -        "ms": "2.1.2"
        +        "ms": "^2.1.3"
               },
               "engines": {
                 "node": ">=6.0"
        @@ -16092,35 +11350,12 @@
                 }
               }
             },
        -    "node_modules/simple-git/node_modules/ms": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
        -      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
        +    "node_modules/socks-proxy-agent/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
               "dev": true
             },
        -    "node_modules/slash": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
        -      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
        -    "node_modules/sntp": {
        -      "version": "0.2.4",
        -      "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz",
        -      "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=",
        -      "deprecated": "This module moved to @hapi/sntp. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.",
        -      "dev": true,
        -      "optional": true,
        -      "dependencies": {
        -        "hoek": "0.9.x"
        -      },
        -      "engines": {
        -        "node": ">=0.8.0"
        -      }
        -    },
             "node_modules/source-map": {
               "version": "0.6.1",
               "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
        @@ -16130,30 +11365,57 @@
                 "node": ">=0.10.0"
               }
             },
        +    "node_modules/source-map-js": {
        +      "version": "1.2.1",
        +      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
        +      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=0.10.0"
        +      }
        +    },
             "node_modules/source-map-support": {
        -      "version": "0.5.16",
        -      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
        -      "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
        +      "version": "0.5.21",
        +      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
        +      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
               "dev": true,
               "dependencies": {
                 "buffer-from": "^1.0.0",
                 "source-map": "^0.6.0"
               }
             },
        -    "node_modules/spawn-wrap": {
        -      "version": "1.4.3",
        -      "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz",
        -      "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==",
        +    "node_modules/space-separated-tokens": {
        +      "version": "2.0.2",
        +      "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
        +      "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
               "dev": true,
        -      "dependencies": {
        -        "foreground-child": "^1.5.6",
        -        "mkdirp": "^0.5.0",
        -        "os-homedir": "^1.0.1",
        -        "rimraf": "^2.6.2",
        -        "signal-exit": "^3.0.2",
        -        "which": "^1.3.0"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        +    "node_modules/spacetrim": {
        +      "version": "0.11.59",
        +      "resolved": "https://registry.npmjs.org/spacetrim/-/spacetrim-0.11.59.tgz",
        +      "integrity": "sha512-lLYsktklSRKprreOm7NXReW8YiX2VBjbgmXYEziOoGf/qsJqAEACaDvoTtUOycwjpaSh+bT8eu0KrJn7UNxiCg==",
        +      "dev": true,
        +      "funding": [
        +        {
        +          "type": "individual",
        +          "url": "https://buymeacoffee.com/hejny"
        +        },
        +        {
        +          "type": "github",
        +          "url": "https://github.com/hejny/spacetrim/blob/main/README.md#%EF%B8%8F-contributing"
        +        }
        +      ]
        +    },
        +    "node_modules/spawn-command": {
        +      "version": "0.0.2",
        +      "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
        +      "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
        +      "dev": true
        +    },
             "node_modules/spdx-correct": {
               "version": "3.2.0",
               "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
        @@ -16186,188 +11448,150 @@
               "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==",
               "dev": true
             },
        -    "node_modules/sprintf-js": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
        -      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
        -      "dev": true
        -    },
        -    "node_modules/staged-git-files": {
        -      "version": "0.0.4",
        -      "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-0.0.4.tgz",
        -      "integrity": "sha1-15fhtVHKemOd7AI33G60u5vhfTU=",
        -      "dev": true
        -    },
        -    "node_modules/statuses": {
        -      "version": "1.5.0",
        -      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
        -      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
        +    "node_modules/split2": {
        +      "version": "4.2.0",
        +      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
        +      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.6"
        -      }
        -    },
        -    "node_modules/stream-browserify": {
        -      "version": "2.0.2",
        -      "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
        -      "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
        -      "dev": true,
        -      "dependencies": {
        -        "inherits": "~2.0.1",
        -        "readable-stream": "^2.0.2"
        +        "node": ">= 10.x"
               }
             },
        -    "node_modules/stream-combiner2": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
        -      "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=",
        -      "dev": true,
        -      "dependencies": {
        -        "duplexer2": "~0.1.0",
        -        "readable-stream": "^2.0.2"
        -      }
        +    "node_modules/stackback": {
        +      "version": "0.0.2",
        +      "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
        +      "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
        +      "dev": true
             },
        -    "node_modules/stream-http": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.0.tgz",
        -      "integrity": "sha512-cuB6RgO7BqC4FBYzmnvhob5Do3wIdIsXAgGycHJnW+981gHqoYcYz9lqjJrk8WXRddbwPuqPYRl+bag6mYv4lw==",
        -      "dev": true,
        -      "dependencies": {
        -        "builtin-status-codes": "^3.0.0",
        -        "inherits": "^2.0.1",
        -        "readable-stream": "^3.0.6",
        -        "xtend": "^4.0.0"
        -      }
        +    "node_modules/std-env": {
        +      "version": "3.8.0",
        +      "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz",
        +      "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==",
        +      "dev": true
             },
        -    "node_modules/stream-http/node_modules/readable-stream": {
        -      "version": "3.4.0",
        -      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
        -      "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
        +    "node_modules/streamx": {
        +      "version": "2.21.1",
        +      "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.1.tgz",
        +      "integrity": "sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==",
               "dev": true,
               "dependencies": {
        -        "inherits": "^2.0.3",
        -        "string_decoder": "^1.1.1",
        -        "util-deprecate": "^1.0.1"
        +        "fast-fifo": "^1.3.2",
        +        "queue-tick": "^1.0.1",
        +        "text-decoder": "^1.1.0"
               },
        -      "engines": {
        -        "node": ">= 6"
        +      "optionalDependencies": {
        +        "bare-events": "^2.2.0"
               }
             },
        -    "node_modules/stream-http/node_modules/safe-buffer": {
        -      "version": "5.2.0",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
        -      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
        +    "node_modules/strict-event-emitter": {
        +      "version": "0.5.1",
        +      "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz",
        +      "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==",
               "dev": true
             },
        -    "node_modules/stream-http/node_modules/string_decoder": {
        -      "version": "1.3.0",
        -      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
        -      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
        +    "node_modules/string_decoder": {
        +      "version": "1.0.3",
        +      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
        +      "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
               "dev": true,
               "dependencies": {
        -        "safe-buffer": "~5.2.0"
        +        "safe-buffer": "~5.1.0"
               }
             },
        -    "node_modules/stream-shift": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
        -      "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
        -      "dev": true
        -    },
        -    "node_modules/stream-splicer": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz",
        -      "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==",
        +    "node_modules/string-argv": {
        +      "version": "0.3.2",
        +      "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
        +      "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
               "dev": true,
        -      "dependencies": {
        -        "inherits": "^2.0.1",
        -        "readable-stream": "^2.0.2"
        +      "engines": {
        +        "node": ">=0.6.19"
               }
             },
        -    "node_modules/stream-to-observable": {
        -      "version": "0.1.0",
        -      "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz",
        -      "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=",
        +    "node_modules/string-width-cjs": {
        +      "name": "string-width",
        +      "version": "4.2.3",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
        +      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
               "dev": true,
        +      "dependencies": {
        +        "emoji-regex": "^8.0.0",
        +        "is-fullwidth-code-point": "^3.0.0",
        +        "strip-ansi": "^6.0.1"
        +      },
               "engines": {
        -        "node": ">=0.12.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/string_decoder": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
        -      "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
        +    "node_modules/string-width-cjs/node_modules/ansi-regex": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
               "dev": true,
        -      "dependencies": {
        -        "safe-buffer": "~5.1.0"
        +      "engines": {
        +        "node": ">=8"
               }
             },
        -    "node_modules/string-template": {
        -      "version": "0.2.1",
        -      "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz",
        -      "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=",
        +    "node_modules/string-width-cjs/node_modules/emoji-regex": {
        +      "version": "8.0.0",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        +      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
               "dev": true
             },
        -    "node_modules/string-width": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
        -      "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
        +    "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        +      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
               "dev": true,
        -      "dependencies": {
        -        "code-point-at": "^1.0.0",
        -        "is-fullwidth-code-point": "^1.0.0",
        -        "strip-ansi": "^3.0.0"
        -      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/string.prototype.codepointat": {
        -      "version": "0.2.1",
        -      "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
        -      "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==",
        -      "dev": true
        -    },
        -    "node_modules/stringify-object": {
        -      "version": "3.2.1",
        -      "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.1.tgz",
        -      "integrity": "sha512-jPcQYw/52HUPP8uOE4kkjxl5bB9LfHkKCTptIk3qw7ozP5XMIMlHMLjt00GGSwW6DJAf/njY5EU6Vpwl4LlBKQ==",
        +    "node_modules/string-width-cjs/node_modules/strip-ansi": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        +      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
               "dev": true,
               "dependencies": {
        -        "get-own-enumerable-property-symbols": "^2.0.1",
        -        "is-obj": "^1.0.1",
        -        "is-regexp": "^1.0.0"
        +        "ansi-regex": "^5.0.1"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=8"
               }
             },
        -    "node_modules/stringstream": {
        -      "version": "0.0.6",
        -      "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
        -      "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==",
        +    "node_modules/stringify-entities": {
        +      "version": "4.0.4",
        +      "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
        +      "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
               "dev": true,
        -      "optional": true
        +      "dependencies": {
        +        "character-entities-html4": "^2.0.0",
        +        "character-entities-legacy": "^3.0.0"
        +      },
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
        +      }
             },
        -    "node_modules/strip-ansi": {
        -      "version": "3.0.1",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
        -      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
        +    "node_modules/strip-ansi-cjs": {
        +      "name": "strip-ansi",
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        +      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
               "dev": true,
               "dependencies": {
        -        "ansi-regex": "^2.0.0"
        +        "ansi-regex": "^5.0.1"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/strip-eof": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
        -      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
        +    "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
             "node_modules/strip-final-newline": {
        @@ -16382,221 +11606,247 @@
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/strip-indent": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz",
        -      "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==",
        +    "node_modules/strnum": {
        +      "version": "1.0.5",
        +      "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
        +      "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==",
        +      "dev": true
        +    },
        +    "node_modules/supports-preserve-symlinks-flag": {
        +      "version": "1.0.0",
        +      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
        +      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
               "dev": true,
        -      "dependencies": {
        -        "min-indent": "^1.0.1"
        -      },
               "engines": {
        -        "node": ">=12"
        +        "node": ">= 0.4"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/sponsors/ljharb"
               }
             },
        -    "node_modules/strip-json-comments": {
        -      "version": "2.0.1",
        -      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
        -      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
        +    "node_modules/tar-fs": {
        +      "version": "3.0.8",
        +      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
        +      "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
               "dev": true,
        -      "engines": {
        -        "node": ">=0.10.0"
        +      "dependencies": {
        +        "pump": "^3.0.0",
        +        "tar-stream": "^3.1.5"
        +      },
        +      "optionalDependencies": {
        +        "bare-fs": "^4.0.1",
        +        "bare-path": "^3.0.0"
               }
             },
        -    "node_modules/subarg": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
        -      "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=",
        +    "node_modules/tar-stream": {
        +      "version": "3.1.7",
        +      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
        +      "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
               "dev": true,
               "dependencies": {
        -        "minimist": "^1.1.0"
        +        "b4a": "^1.6.4",
        +        "fast-fifo": "^1.2.0",
        +        "streamx": "^2.15.0"
               }
             },
        -    "node_modules/supports-color": {
        -      "version": "5.5.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
        -      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
        +    "node_modules/terser": {
        +      "version": "5.37.0",
        +      "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
        +      "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
               "dev": true,
               "dependencies": {
        -        "has-flag": "^3.0.0"
        +        "@jridgewell/source-map": "^0.3.3",
        +        "acorn": "^8.8.2",
        +        "commander": "^2.20.0",
        +        "source-map-support": "~0.5.20"
        +      },
        +      "bin": {
        +        "terser": "bin/terser"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=10"
               }
             },
        -    "node_modules/supports-hyperlinks": {
        -      "version": "2.3.0",
        -      "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz",
        -      "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==",
        +    "node_modules/terser/node_modules/commander": {
        +      "version": "2.20.3",
        +      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
        +      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
        +      "dev": true
        +    },
        +    "node_modules/text-decoder": {
        +      "version": "1.2.3",
        +      "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
        +      "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
               "dev": true,
               "dependencies": {
        -        "has-flag": "^4.0.0",
        -        "supports-color": "^7.0.0"
        -      },
        -      "engines": {
        -        "node": ">=8"
        +        "b4a": "^1.6.4"
               }
             },
        -    "node_modules/supports-hyperlinks/node_modules/has-flag": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        -      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
        +    "node_modules/text-table": {
        +      "version": "0.2.0",
        +      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
        +      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
        +      "dev": true
        +    },
        +    "node_modules/through": {
        +      "version": "2.3.8",
        +      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
        +      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
        +      "dev": true
        +    },
        +    "node_modules/tinybench": {
        +      "version": "2.9.0",
        +      "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
        +      "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
        +      "dev": true
        +    },
        +    "node_modules/tinyexec": {
        +      "version": "0.3.2",
        +      "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
        +      "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
        +      "dev": true
        +    },
        +    "node_modules/tinypool": {
        +      "version": "1.0.2",
        +      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
        +      "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==",
               "dev": true,
               "engines": {
        -        "node": ">=8"
        +        "node": "^18.0.0 || >=20.0.0"
               }
             },
        -    "node_modules/supports-hyperlinks/node_modules/supports-color": {
        -      "version": "7.2.0",
        -      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        -      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
        +    "node_modules/tinyrainbow": {
        +      "version": "1.2.0",
        +      "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
        +      "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
               "dev": true,
        -      "dependencies": {
        -        "has-flag": "^4.0.0"
        -      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=14.0.0"
               }
             },
        -    "node_modules/supports-preserve-symlinks-flag": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
        -      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
        +    "node_modules/tinyspy": {
        +      "version": "3.0.2",
        +      "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
        +      "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.4"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/ljharb"
        +        "node": ">=14.0.0"
               }
             },
        -    "node_modules/symbol-observable": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
        -      "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=",
        +    "node_modules/tmp": {
        +      "version": "0.0.33",
        +      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
        +      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
               "dev": true,
        +      "dependencies": {
        +        "os-tmpdir": "~1.0.2"
        +      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=0.6.0"
               }
             },
        -    "node_modules/syntax-error": {
        -      "version": "1.4.0",
        -      "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz",
        -      "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==",
        +    "node_modules/to-regex-range": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
        +      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
               "dev": true,
               "dependencies": {
        -        "acorn-node": "^1.2.0"
        +        "is-number": "^7.0.0"
        +      },
        +      "engines": {
        +        "node": ">=8.0"
               }
             },
        -    "node_modules/tar-fs": {
        -      "version": "2.1.1",
        -      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
        -      "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
        +    "node_modules/totalist": {
        +      "version": "3.0.1",
        +      "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
        +      "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
               "dev": true,
        -      "dependencies": {
        -        "chownr": "^1.1.1",
        -        "mkdirp-classic": "^0.5.2",
        -        "pump": "^3.0.0",
        -        "tar-stream": "^2.1.4"
        +      "engines": {
        +        "node": ">=6"
               }
             },
        -    "node_modules/tar-stream": {
        -      "version": "2.2.0",
        -      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
        -      "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
        +    "node_modules/tough-cookie": {
        +      "version": "4.1.4",
        +      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
        +      "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
               "dev": true,
               "dependencies": {
        -        "bl": "^4.0.3",
        -        "end-of-stream": "^1.4.1",
        -        "fs-constants": "^1.0.0",
        -        "inherits": "^2.0.3",
        -        "readable-stream": "^3.1.1"
        +        "psl": "^1.1.33",
        +        "punycode": "^2.1.1",
        +        "universalify": "^0.2.0",
        +        "url-parse": "^1.5.3"
               },
               "engines": {
                 "node": ">=6"
               }
             },
        -    "node_modules/tar-stream/node_modules/readable-stream": {
        -      "version": "3.6.0",
        -      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
        -      "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
        +    "node_modules/tough-cookie/node_modules/punycode": {
        +      "version": "2.3.0",
        +      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
        +      "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
               "dev": true,
        -      "dependencies": {
        -        "inherits": "^2.0.3",
        -        "string_decoder": "^1.1.1",
        -        "util-deprecate": "^1.0.1"
        -      },
               "engines": {
        -        "node": ">= 6"
        +        "node": ">=6"
               }
             },
        -    "node_modules/tar-stream/node_modules/safe-buffer": {
        -      "version": "5.2.1",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
        -      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
        +    "node_modules/tr46": {
        +      "version": "0.0.3",
        +      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
        +      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
        +      "dev": true
        +    },
        +    "node_modules/tree-kill": {
        +      "version": "1.2.2",
        +      "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
        +      "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
               "dev": true,
        -      "funding": [
        -        {
        -          "type": "github",
        -          "url": "https://github.com/sponsors/feross"
        -        },
        -        {
        -          "type": "patreon",
        -          "url": "https://www.patreon.com/feross"
        -        },
        -        {
        -          "type": "consulting",
        -          "url": "https://feross.org/support"
        -        }
        -      ]
        +      "bin": {
        +        "tree-kill": "cli.js"
        +      }
             },
        -    "node_modules/tar-stream/node_modules/string_decoder": {
        -      "version": "1.3.0",
        -      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
        -      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
        +    "node_modules/trim-lines": {
        +      "version": "3.0.1",
        +      "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
        +      "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
               "dev": true,
        -      "dependencies": {
        -        "safe-buffer": "~5.2.0"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/terminal-link": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-3.0.0.tgz",
        -      "integrity": "sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg==",
        +    "node_modules/trough": {
        +      "version": "2.2.0",
        +      "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
        +      "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
               "dev": true,
        -      "dependencies": {
        -        "ansi-escapes": "^5.0.0",
        -        "supports-hyperlinks": "^2.2.0"
        -      },
        -      "engines": {
        -        "node": ">=12"
        -      },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/terminal-link/node_modules/ansi-escapes": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz",
        -      "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==",
        +    "node_modules/tslib": {
        +      "version": "1.9.3",
        +      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
        +      "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
        +      "dev": true
        +    },
        +    "node_modules/type-check": {
        +      "version": "0.4.0",
        +      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
        +      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
               "dev": true,
               "dependencies": {
        -        "type-fest": "^1.0.2"
        +        "prelude-ls": "^1.2.1"
               },
               "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">= 0.8.0"
               }
             },
        -    "node_modules/terminal-link/node_modules/type-fest": {
        -      "version": "1.4.0",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
        -      "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
        +    "node_modules/type-fest": {
        +      "version": "0.21.3",
        +      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
        +      "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
               "dev": true,
               "engines": {
                 "node": ">=10"
        @@ -16605,828 +11855,1018 @@
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/test-exclude": {
        -      "version": "5.2.3",
        -      "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz",
        -      "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==",
        +    "node_modules/unbzip2-stream": {
        +      "version": "1.4.3",
        +      "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
        +      "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
               "dev": true,
               "dependencies": {
        -        "glob": "^7.1.3",
        -        "minimatch": "^3.0.4",
        -        "read-pkg-up": "^4.0.0",
        -        "require-main-filename": "^2.0.0"
        -      },
        -      "engines": {
        -        "node": ">=6"
        +        "buffer": "^5.2.1",
        +        "through": "^2.3.8"
               }
             },
        -    "node_modules/test-exclude/node_modules/find-up": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
        -      "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
        +    "node_modules/unc-path-regex": {
        +      "version": "0.1.2",
        +      "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
        +      "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=",
               "dev": true,
        -      "dependencies": {
        -        "locate-path": "^3.0.0"
        -      },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/test-exclude/node_modules/load-json-file": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
        -      "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
        +    "node_modules/undici": {
        +      "version": "6.21.1",
        +      "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
        +      "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
               "dev": true,
        -      "dependencies": {
        -        "graceful-fs": "^4.1.2",
        -        "parse-json": "^4.0.0",
        -        "pify": "^3.0.0",
        -        "strip-bom": "^3.0.0"
        -      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=18.17"
               }
             },
        -    "node_modules/test-exclude/node_modules/parse-json": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
        -      "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
        -      "dev": true,
        -      "dependencies": {
        -        "error-ex": "^1.3.1",
        -        "json-parse-better-errors": "^1.0.1"
        -      },
        -      "engines": {
        -        "node": ">=4"
        -      }
        +    "node_modules/undici-types": {
        +      "version": "6.20.0",
        +      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
        +      "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
        +      "dev": true
             },
        -    "node_modules/test-exclude/node_modules/path-type": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
        -      "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
        +    "node_modules/unified": {
        +      "version": "10.1.2",
        +      "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz",
        +      "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==",
               "dev": true,
               "dependencies": {
        -        "pify": "^3.0.0"
        +        "@types/unist": "^2.0.0",
        +        "bail": "^2.0.0",
        +        "extend": "^3.0.0",
        +        "is-buffer": "^2.0.0",
        +        "is-plain-obj": "^4.0.0",
        +        "trough": "^2.0.0",
        +        "vfile": "^5.0.0"
               },
        -      "engines": {
        -        "node": ">=4"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/test-exclude/node_modules/pify": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
        -      "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
        +    "node_modules/unified/node_modules/is-buffer": {
        +      "version": "2.0.5",
        +      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
        +      "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
        +        }
        +      ],
               "engines": {
                 "node": ">=4"
               }
             },
        -    "node_modules/test-exclude/node_modules/read-pkg": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
        -      "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
        +    "node_modules/unified/node_modules/is-plain-obj": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
        +      "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
               "dev": true,
        -      "dependencies": {
        -        "load-json-file": "^4.0.0",
        -        "normalize-package-data": "^2.3.2",
        -        "path-type": "^3.0.0"
        -      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/test-exclude/node_modules/read-pkg-up": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz",
        -      "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==",
        +    "node_modules/unist-builder": {
        +      "version": "3.0.1",
        +      "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.1.tgz",
        +      "integrity": "sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==",
               "dev": true,
               "dependencies": {
        -        "find-up": "^3.0.0",
        -        "read-pkg": "^3.0.0"
        +        "@types/unist": "^2.0.0"
               },
        -      "engines": {
        -        "node": ">=6"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/test-exclude/node_modules/strip-bom": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
        -      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
        +    "node_modules/unist-util-generated": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz",
        +      "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==",
               "dev": true,
        -      "engines": {
        -        "node": ">=4"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/text-table": {
        -      "version": "0.2.0",
        -      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
        -      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
        -      "dev": true
        -    },
        -    "node_modules/through": {
        -      "version": "2.3.8",
        -      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
        -      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
        -      "dev": true
        -    },
        -    "node_modules/through2": {
        -      "version": "2.0.3",
        -      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
        -      "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
        +    "node_modules/unist-util-is": {
        +      "version": "5.2.1",
        +      "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz",
        +      "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==",
               "dev": true,
               "dependencies": {
        -        "readable-stream": "^2.1.5",
        -        "xtend": "~4.0.1"
        +        "@types/unist": "^2.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/timers-browserify": {
        -      "version": "1.4.2",
        -      "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
        -      "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=",
        +    "node_modules/unist-util-position": {
        +      "version": "4.0.4",
        +      "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz",
        +      "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==",
               "dev": true,
               "dependencies": {
        -        "process": "~0.11.0"
        +        "@types/unist": "^2.0.0"
               },
        -      "engines": {
        -        "node": ">=0.6.0"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/tiny-inflate": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.2.tgz",
        -      "integrity": "sha1-k9nez/yIBb1X6uQxDwt0Xptvs6c=",
        -      "dev": true
        -    },
        -    "node_modules/tiny-lr": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz",
        -      "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==",
        +    "node_modules/unist-util-stringify-position": {
        +      "version": "3.0.3",
        +      "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz",
        +      "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==",
               "dev": true,
               "dependencies": {
        -        "body": "^5.1.0",
        -        "debug": "^3.1.0",
        -        "faye-websocket": "~0.10.0",
        -        "livereload-js": "^2.3.0",
        -        "object-assign": "^4.1.0",
        -        "qs": "^6.4.0"
        +        "@types/unist": "^2.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/tiny-lr/node_modules/debug": {
        -      "version": "3.1.0",
        -      "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
        -      "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
        +    "node_modules/unist-util-visit": {
        +      "version": "4.1.2",
        +      "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz",
        +      "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==",
               "dev": true,
               "dependencies": {
        -        "ms": "2.0.0"
        -      }
        -    },
        -    "node_modules/titleize": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
        -      "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12"
        +        "@types/unist": "^2.0.0",
        +        "unist-util-is": "^5.0.0",
        +        "unist-util-visit-parents": "^5.1.1"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/tmp": {
        -      "version": "0.0.33",
        -      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
        -      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
        +    "node_modules/unist-util-visit-parents": {
        +      "version": "5.1.3",
        +      "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz",
        +      "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==",
               "dev": true,
               "dependencies": {
        -        "os-tmpdir": "~1.0.2"
        +        "@types/unist": "^2.0.0",
        +        "unist-util-is": "^5.0.0"
               },
        -      "engines": {
        -        "node": ">=0.6.0"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/to-fast-properties": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
        -      "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
        +    "node_modules/universalify": {
        +      "version": "0.2.0",
        +      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
        +      "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
               "dev": true,
               "engines": {
        -        "node": ">=4"
        +        "node": ">= 4.0.0"
               }
             },
        -    "node_modules/to-regex-range": {
        -      "version": "5.0.1",
        -      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
        -      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
        +    "node_modules/update-browserslist-db": {
        +      "version": "1.1.2",
        +      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
        +      "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "opencollective",
        +          "url": "https://opencollective.com/browserslist"
        +        },
        +        {
        +          "type": "tidelift",
        +          "url": "https://tidelift.com/funding/github/npm/browserslist"
        +        },
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/ai"
        +        }
        +      ],
               "dependencies": {
        -        "is-number": "^7.0.0"
        +        "escalade": "^3.2.0",
        +        "picocolors": "^1.1.1"
               },
        -      "engines": {
        -        "node": ">=8.0"
        -      }
        -    },
        -    "node_modules/toidentifier": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
        -      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.6"
        +      "bin": {
        +        "update-browserslist-db": "cli.js"
        +      },
        +      "peerDependencies": {
        +        "browserslist": ">= 4.21.0"
               }
             },
        -    "node_modules/tough-cookie": {
        -      "version": "4.1.3",
        -      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
        -      "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
        +    "node_modules/uri-js": {
        +      "version": "4.4.1",
        +      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
        +      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
               "dev": true,
        -      "optional": true,
               "dependencies": {
        -        "psl": "^1.1.33",
        -        "punycode": "^2.1.1",
        -        "universalify": "^0.2.0",
        -        "url-parse": "^1.5.3"
        -      },
        -      "engines": {
        -        "node": ">=6"
        +        "punycode": "^2.1.0"
               }
             },
        -    "node_modules/tough-cookie/node_modules/punycode": {
        -      "version": "2.3.0",
        -      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
        -      "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
        +    "node_modules/uri-js/node_modules/punycode": {
        +      "version": "2.3.1",
        +      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
        +      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
               "dev": true,
        -      "optional": true,
               "engines": {
                 "node": ">=6"
               }
             },
        -    "node_modules/tr46": {
        -      "version": "0.0.3",
        -      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
        -      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
        -      "dev": true
        -    },
        -    "node_modules/trim-newlines": {
        -      "version": "5.0.0",
        -      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-5.0.0.tgz",
        -      "integrity": "sha512-kstfs+hgwmdsOadN3KgA+C68wPJwnZq4DN6WMDCvZapDWEF34W2TyPKN2v2+BJnZgIz5QOfxFeldLyYvdgRAwg==",
        +    "node_modules/url-parse": {
        +      "version": "1.5.10",
        +      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
        +      "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
               "dev": true,
        -      "engines": {
        -        "node": ">=14.16"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +      "dependencies": {
        +        "querystringify": "^2.1.1",
        +        "requires-port": "^1.0.0"
               }
             },
        -    "node_modules/tslib": {
        -      "version": "1.9.3",
        -      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
        -      "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
        -      "dev": true
        -    },
        -    "node_modules/tty-browserify": {
        -      "version": "0.0.1",
        -      "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
        -      "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==",
        +    "node_modules/urlpattern-polyfill": {
        +      "version": "10.0.0",
        +      "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz",
        +      "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==",
               "dev": true
             },
        -    "node_modules/tunnel-agent": {
        -      "version": "0.4.3",
        -      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
        -      "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=",
        +    "node_modules/userhome": {
        +      "version": "1.0.1",
        +      "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.1.tgz",
        +      "integrity": "sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA==",
               "dev": true,
        -      "optional": true,
               "engines": {
        -        "node": "*"
        +        "node": ">= 0.8.0"
               }
             },
        -    "node_modules/type-check": {
        -      "version": "0.4.0",
        -      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
        -      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
        +    "node_modules/util-deprecate": {
        +      "version": "1.0.2",
        +      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
        +      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
        +      "dev": true
        +    },
        +    "node_modules/uvu": {
        +      "version": "0.5.6",
        +      "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
        +      "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==",
               "dev": true,
               "dependencies": {
        -        "prelude-ls": "^1.2.1"
        +        "dequal": "^2.0.0",
        +        "diff": "^5.0.0",
        +        "kleur": "^4.0.3",
        +        "sade": "^1.7.3"
        +      },
        +      "bin": {
        +        "uvu": "bin.js"
               },
               "engines": {
        -        "node": ">= 0.8.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/type-detect": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz",
        -      "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=",
        +    "node_modules/validate-npm-package-license": {
        +      "version": "3.0.4",
        +      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
        +      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
               "dev": true,
        -      "engines": {
        -        "node": "*"
        +      "dependencies": {
        +        "spdx-correct": "^3.0.0",
        +        "spdx-expression-parse": "^3.0.0"
               }
             },
        -    "node_modules/type-fest": {
        -      "version": "0.21.3",
        -      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
        -      "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
        +    "node_modules/vfile": {
        +      "version": "5.3.7",
        +      "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz",
        +      "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==",
               "dev": true,
        -      "engines": {
        -        "node": ">=10"
        +      "dependencies": {
        +        "@types/unist": "^2.0.0",
        +        "is-buffer": "^2.0.0",
        +        "unist-util-stringify-position": "^3.0.0",
        +        "vfile-message": "^3.0.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/type-is": {
        -      "version": "1.6.18",
        -      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
        -      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
        +    "node_modules/vfile-location": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz",
        +      "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==",
               "dev": true,
               "dependencies": {
        -        "media-typer": "0.3.0",
        -        "mime-types": "~2.1.24"
        +        "@types/unist": "^2.0.0",
        +        "vfile": "^5.0.0"
               },
        -      "engines": {
        -        "node": ">= 0.6"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/typedarray": {
        -      "version": "0.0.6",
        -      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
        -      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
        -      "dev": true
        -    },
        -    "node_modules/typedarray-to-buffer": {
        -      "version": "3.1.5",
        -      "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
        -      "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
        +    "node_modules/vfile-message": {
        +      "version": "3.1.4",
        +      "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz",
        +      "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==",
               "dev": true,
               "dependencies": {
        -        "is-typedarray": "^1.0.0"
        +        "@types/unist": "^2.0.0",
        +        "unist-util-stringify-position": "^3.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/types-eslintrc": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/types-eslintrc/-/types-eslintrc-1.0.3.tgz",
        -      "integrity": "sha512-zKTR6aKHEudQpl+JoZjS3qh0B5IzSpQK/BCpYBECujcnKtqL87DJJ1sJKe5B8k/y8/UJ5sukq42QDvlaJyCO2w==",
        +    "node_modules/vfile-reporter": {
        +      "version": "7.0.5",
        +      "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-7.0.5.tgz",
        +      "integrity": "sha512-NdWWXkv6gcd7AZMvDomlQbK3MqFWL1RlGzMn++/O2TI+68+nqxCPTvLugdOtfSzXmjh+xUyhp07HhlrbJjT+mw==",
               "dev": true,
               "dependencies": {
        -        "types-json": "^1.2.2"
        +        "@types/supports-color": "^8.0.0",
        +        "string-width": "^5.0.0",
        +        "supports-color": "^9.0.0",
        +        "unist-util-stringify-position": "^3.0.0",
        +        "vfile": "^5.0.0",
        +        "vfile-message": "^3.0.0",
        +        "vfile-sort": "^3.0.0",
        +        "vfile-statistics": "^2.0.0"
        +      },
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/types-json": {
        -      "version": "1.2.2",
        -      "resolved": "https://registry.npmjs.org/types-json/-/types-json-1.2.2.tgz",
        -      "integrity": "sha512-VfVLISHypS7ayIHvhacOESOTib4Sm4mAhnsgR8fzQdGp89YoBwMqvGmqENjtYehUQzgclT+7NafpEXkK/MHKwA==",
        -      "dev": true
        -    },
        -    "node_modules/types-pkg-json": {
        -      "version": "1.2.1",
        -      "resolved": "https://registry.npmjs.org/types-pkg-json/-/types-pkg-json-1.2.1.tgz",
        -      "integrity": "sha512-Wj75lCkPwfj1BhmaJxMPpTQj9YGpihjs3WICigt1IjTAswr7zPXP0iJYPZjU0Rw/IriODhMJjAImkCIxt9KeuQ==",
        +    "node_modules/vfile-reporter/node_modules/ansi-regex": {
        +      "version": "6.1.0",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
        +      "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
               "dev": true,
        -      "dependencies": {
        -        "types-eslintrc": "^1.0.3",
        -        "types-json": "^1.2.2"
        +      "engines": {
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
               }
             },
        -    "node_modules/uc.micro": {
        -      "version": "1.0.3",
        -      "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.3.tgz",
        -      "integrity": "sha1-ftUNXg+an7ClczeSWfKndFjVAZI=",
        -      "dev": true
        -    },
        -    "node_modules/ultron": {
        -      "version": "1.1.1",
        -      "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
        -      "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
        +    "node_modules/vfile-reporter/node_modules/emoji-regex": {
        +      "version": "9.2.2",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
        +      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
               "dev": true
             },
        -    "node_modules/umd": {
        -      "version": "3.0.3",
        -      "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz",
        -      "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==",
        +    "node_modules/vfile-reporter/node_modules/string-width": {
        +      "version": "5.1.2",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
        +      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
               "dev": true,
        -      "bin": {
        -        "umd": "bin/cli.js"
        +      "dependencies": {
        +        "eastasianwidth": "^0.2.0",
        +        "emoji-regex": "^9.2.2",
        +        "strip-ansi": "^7.0.1"
        +      },
        +      "engines": {
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/unbzip2-stream": {
        -      "version": "1.4.3",
        -      "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
        -      "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
        +    "node_modules/vfile-reporter/node_modules/strip-ansi": {
        +      "version": "7.1.0",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
        +      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
               "dev": true,
               "dependencies": {
        -        "buffer": "^5.2.1",
        -        "through": "^2.3.8"
        +        "ansi-regex": "^6.0.1"
        +      },
        +      "engines": {
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
               }
             },
        -    "node_modules/unc-path-regex": {
        -      "version": "0.1.2",
        -      "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
        -      "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=",
        +    "node_modules/vfile-reporter/node_modules/supports-color": {
        +      "version": "9.4.0",
        +      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz",
        +      "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/chalk/supports-color?sponsor=1"
               }
             },
        -    "node_modules/undeclared-identifiers": {
        -      "version": "1.1.3",
        -      "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz",
        -      "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==",
        +    "node_modules/vfile-sort": {
        +      "version": "3.0.1",
        +      "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-3.0.1.tgz",
        +      "integrity": "sha512-1os1733XY6y0D5x0ugqSeaVJm9lYgj0j5qdcZQFyxlZOSy1jYarL77lLyb5gK4Wqr1d5OxmuyflSO3zKyFnTFw==",
               "dev": true,
               "dependencies": {
        -        "acorn-node": "^1.3.0",
        -        "dash-ast": "^1.0.0",
        -        "get-assigned-identifiers": "^1.2.0",
        -        "simple-concat": "^1.0.0",
        -        "xtend": "^4.0.1"
        +        "vfile": "^5.0.0",
        +        "vfile-message": "^3.0.0"
               },
        -      "bin": {
        -        "undeclared-identifiers": "bin.js"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/underscore.string": {
        -      "version": "3.3.6",
        -      "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz",
        -      "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==",
        +    "node_modules/vfile-statistics": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-2.0.1.tgz",
        +      "integrity": "sha512-W6dkECZmP32EG/l+dp2jCLdYzmnDBIw6jwiLZSER81oR5AHRcVqL+k3Z+pfH1R73le6ayDkJRMk0sutj1bMVeg==",
               "dev": true,
               "dependencies": {
        -        "sprintf-js": "^1.1.1",
        -        "util-deprecate": "^1.0.2"
        +        "vfile": "^5.0.0",
        +        "vfile-message": "^3.0.0"
               },
        -      "engines": {
        -        "node": "*"
        +      "funding": {
        +        "type": "opencollective",
        +        "url": "https://opencollective.com/unified"
               }
             },
        -    "node_modules/underscore.string/node_modules/sprintf-js": {
        -      "version": "1.1.2",
        -      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
        -      "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
        -      "dev": true
        -    },
        -    "node_modules/unicode-canonical-property-names-ecmascript": {
        -      "version": "1.0.4",
        -      "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
        -      "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==",
        +    "node_modules/vfile/node_modules/is-buffer": {
        +      "version": "2.0.5",
        +      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
        +      "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
               "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
        +        }
        +      ],
               "engines": {
                 "node": ">=4"
               }
             },
        -    "node_modules/unicode-match-property-ecmascript": {
        -      "version": "1.0.4",
        -      "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
        -      "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
        +    "node_modules/vite": {
        +      "version": "5.4.14",
        +      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
        +      "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
               "dev": true,
               "dependencies": {
        -        "unicode-canonical-property-names-ecmascript": "^1.0.4",
        -        "unicode-property-aliases-ecmascript": "^1.0.4"
        +        "esbuild": "^0.21.3",
        +        "postcss": "^8.4.43",
        +        "rollup": "^4.20.0"
        +      },
        +      "bin": {
        +        "vite": "bin/vite.js"
               },
               "engines": {
        -        "node": ">=4"
        +        "node": "^18.0.0 || >=20.0.0"
        +      },
        +      "funding": {
        +        "url": "https://github.com/vitejs/vite?sponsor=1"
        +      },
        +      "optionalDependencies": {
        +        "fsevents": "~2.3.3"
        +      },
        +      "peerDependencies": {
        +        "@types/node": "^18.0.0 || >=20.0.0",
        +        "less": "*",
        +        "lightningcss": "^1.21.0",
        +        "sass": "*",
        +        "sass-embedded": "*",
        +        "stylus": "*",
        +        "sugarss": "*",
        +        "terser": "^5.4.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "@types/node": {
        +          "optional": true
        +        },
        +        "less": {
        +          "optional": true
        +        },
        +        "lightningcss": {
        +          "optional": true
        +        },
        +        "sass": {
        +          "optional": true
        +        },
        +        "sass-embedded": {
        +          "optional": true
        +        },
        +        "stylus": {
        +          "optional": true
        +        },
        +        "sugarss": {
        +          "optional": true
        +        },
        +        "terser": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/unicode-match-property-value-ecmascript": {
        -      "version": "1.2.0",
        -      "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz",
        -      "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==",
        +    "node_modules/vite-node": {
        +      "version": "2.1.8",
        +      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz",
        +      "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==",
               "dev": true,
        +      "dependencies": {
        +        "cac": "^6.7.14",
        +        "debug": "^4.3.7",
        +        "es-module-lexer": "^1.5.4",
        +        "pathe": "^1.1.2",
        +        "vite": "^5.0.0"
        +      },
        +      "bin": {
        +        "vite-node": "vite-node.mjs"
        +      },
               "engines": {
        -        "node": ">=4"
        +        "node": "^18.0.0 || >=20.0.0"
        +      },
        +      "funding": {
        +        "url": "https://opencollective.com/vitest"
               }
             },
        -    "node_modules/unicode-property-aliases-ecmascript": {
        -      "version": "1.1.0",
        -      "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz",
        -      "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==",
        +    "node_modules/vite-node/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
        +      "dependencies": {
        +        "ms": "^2.1.3"
        +      },
               "engines": {
        -        "node": ">=4"
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/unique-string": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz",
        -      "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==",
        +    "node_modules/vite-node/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        +      "dev": true
        +    },
        +    "node_modules/vite-plugin-string": {
        +      "version": "1.2.3",
        +      "resolved": "https://registry.npmjs.org/vite-plugin-string/-/vite-plugin-string-1.2.3.tgz",
        +      "integrity": "sha512-zw2jL0c4B6CAvxO8PshX04494jTcqJjNH2kW1AugBH+fImRY0evdNtVgmeS1i1VFdua/OFhL7fMqIPh0uF21/Q==",
               "dev": true,
               "dependencies": {
        -        "crypto-random-string": "^4.0.0"
        +        "@rollup/pluginutils": ">=4.1.0"
        +      },
        +      "peerDependencies": {
        +        "vite": ">=2"
        +      }
        +    },
        +    "node_modules/vitest": {
        +      "version": "2.1.8",
        +      "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz",
        +      "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==",
        +      "dev": true,
        +      "dependencies": {
        +        "@vitest/expect": "2.1.8",
        +        "@vitest/mocker": "2.1.8",
        +        "@vitest/pretty-format": "^2.1.8",
        +        "@vitest/runner": "2.1.8",
        +        "@vitest/snapshot": "2.1.8",
        +        "@vitest/spy": "2.1.8",
        +        "@vitest/utils": "2.1.8",
        +        "chai": "^5.1.2",
        +        "debug": "^4.3.7",
        +        "expect-type": "^1.1.0",
        +        "magic-string": "^0.30.12",
        +        "pathe": "^1.1.2",
        +        "std-env": "^3.8.0",
        +        "tinybench": "^2.9.0",
        +        "tinyexec": "^0.3.1",
        +        "tinypool": "^1.0.1",
        +        "tinyrainbow": "^1.2.0",
        +        "vite": "^5.0.0",
        +        "vite-node": "2.1.8",
        +        "why-is-node-running": "^2.3.0"
        +      },
        +      "bin": {
        +        "vitest": "vitest.mjs"
               },
               "engines": {
        -        "node": ">=12"
        +        "node": "^18.0.0 || >=20.0.0"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://opencollective.com/vitest"
        +      },
        +      "peerDependencies": {
        +        "@edge-runtime/vm": "*",
        +        "@types/node": "^18.0.0 || >=20.0.0",
        +        "@vitest/browser": "2.1.8",
        +        "@vitest/ui": "2.1.8",
        +        "happy-dom": "*",
        +        "jsdom": "*"
        +      },
        +      "peerDependenciesMeta": {
        +        "@edge-runtime/vm": {
        +          "optional": true
        +        },
        +        "@types/node": {
        +          "optional": true
        +        },
        +        "@vitest/browser": {
        +          "optional": true
        +        },
        +        "@vitest/ui": {
        +          "optional": true
        +        },
        +        "happy-dom": {
        +          "optional": true
        +        },
        +        "jsdom": {
        +          "optional": true
        +        }
               }
        -    },
        -    "node_modules/universalify": {
        -      "version": "0.2.0",
        -      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
        -      "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
        +    },
        +    "node_modules/vitest/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
        -      "optional": true,
        +      "dependencies": {
        +        "ms": "^2.1.3"
        +      },
               "engines": {
        -        "node": ">= 4.0.0"
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/unpipe": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
        -      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
        +    "node_modules/vitest/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        +      "dev": true
        +    },
        +    "node_modules/vue-template-compiler": {
        +      "version": "2.7.16",
        +      "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
        +      "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==",
               "dev": true,
        -      "engines": {
        -        "node": ">= 0.8"
        +      "optional": true,
        +      "dependencies": {
        +        "de-indent": "^1.0.2",
        +        "he": "^1.2.0"
               }
             },
        -    "node_modules/untildify": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
        -      "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
        +    "node_modules/wait-port": {
        +      "version": "1.1.0",
        +      "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.1.0.tgz",
        +      "integrity": "sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==",
               "dev": true,
        +      "dependencies": {
        +        "chalk": "^4.1.2",
        +        "commander": "^9.3.0",
        +        "debug": "^4.3.4"
        +      },
        +      "bin": {
        +        "wait-port": "bin/wait-port.js"
        +      },
               "engines": {
        -        "node": ">=8"
        +        "node": ">=10"
               }
             },
        -    "node_modules/update-notifier": {
        -      "version": "6.0.2",
        -      "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz",
        -      "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==",
        +    "node_modules/wait-port/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
               "dependencies": {
        -        "boxen": "^7.0.0",
        -        "chalk": "^5.0.1",
        -        "configstore": "^6.0.0",
        -        "has-yarn": "^3.0.0",
        -        "import-lazy": "^4.0.0",
        -        "is-ci": "^3.0.1",
        -        "is-installed-globally": "^0.4.0",
        -        "is-npm": "^6.0.0",
        -        "is-yarn-global": "^0.4.0",
        -        "latest-version": "^7.0.0",
        -        "pupa": "^3.1.0",
        -        "semver": "^7.3.7",
        -        "semver-diff": "^4.0.0",
        -        "xdg-basedir": "^5.1.0"
        +        "color-convert": "^2.0.1"
               },
               "engines": {
        -        "node": ">=14.16"
        +        "node": ">=8"
               },
               "funding": {
        -        "url": "https://github.com/yeoman/update-notifier?sponsor=1"
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/update-notifier/node_modules/chalk": {
        -      "version": "5.3.0",
        -      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
        -      "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
        +    "node_modules/wait-port/node_modules/chalk": {
        +      "version": "4.1.2",
        +      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
        +      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
               "dev": true,
        +      "dependencies": {
        +        "ansi-styles": "^4.1.0",
        +        "supports-color": "^7.1.0"
        +      },
               "engines": {
        -        "node": "^12.17.0 || ^14.13 || >=16.0.0"
        +        "node": ">=10"
               },
               "funding": {
                 "url": "https://github.com/chalk/chalk?sponsor=1"
               }
             },
        -    "node_modules/update-notifier/node_modules/lru-cache": {
        -      "version": "6.0.0",
        -      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
        -      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
        +    "node_modules/wait-port/node_modules/color-convert": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
               "dependencies": {
        -        "yallist": "^4.0.0"
        +        "color-name": "~1.1.4"
               },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/update-notifier/node_modules/semver": {
        -      "version": "7.5.4",
        -      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
        -      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
        +    "node_modules/wait-port/node_modules/commander": {
        +      "version": "9.5.0",
        +      "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
        +      "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
        +      "dev": true,
        +      "engines": {
        +        "node": "^12.20.0 || >=14"
        +      }
        +    },
        +    "node_modules/wait-port/node_modules/debug": {
        +      "version": "4.4.0",
        +      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
        +      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
               "dev": true,
               "dependencies": {
        -        "lru-cache": "^6.0.0"
        -      },
        -      "bin": {
        -        "semver": "bin/semver.js"
        +        "ms": "^2.1.3"
               },
               "engines": {
        -        "node": ">=10"
        +        "node": ">=6.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "supports-color": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/update-notifier/node_modules/yallist": {
        +    "node_modules/wait-port/node_modules/has-flag": {
               "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
        -      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
        -      "dev": true
        -    },
        -    "node_modules/uri-js": {
        -      "version": "4.4.1",
        -      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
        -      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
        +      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
        +      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
               "dev": true,
        -      "dependencies": {
        -        "punycode": "^2.1.0"
        +      "engines": {
        +        "node": ">=8"
               }
             },
        -    "node_modules/uri-js/node_modules/punycode": {
        -      "version": "2.1.1",
        -      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
        -      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
        +    "node_modules/wait-port/node_modules/ms": {
        +      "version": "2.1.3",
        +      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
        +      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
        +      "dev": true
        +    },
        +    "node_modules/wait-port/node_modules/supports-color": {
        +      "version": "7.2.0",
        +      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
        +      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
               "dev": true,
        +      "dependencies": {
        +        "has-flag": "^4.0.0"
        +      },
               "engines": {
        -        "node": ">=6"
        +        "node": ">=8"
               }
             },
        -    "node_modules/uri-path": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz",
        -      "integrity": "sha512-8pMuAn4KacYdGMkFaoQARicp4HSw24/DHOVKWqVRJ8LhhAwPPFpdGvdL9184JVmUwe7vz7Z9n6IqI6t5n2ELdg==",
        +    "node_modules/web-namespaces": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
        +      "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
               "dev": true,
        -      "engines": {
        -        "node": ">= 0.10"
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             },
        -    "node_modules/url": {
        -      "version": "0.11.0",
        -      "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
        -      "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
        +    "node_modules/web-streams-polyfill": {
        +      "version": "3.3.3",
        +      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
        +      "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
               "dev": true,
        -      "dependencies": {
        -        "punycode": "1.3.2",
        -        "querystring": "0.2.0"
        +      "engines": {
        +        "node": ">= 8"
               }
             },
        -    "node_modules/url-parse": {
        -      "version": "1.5.10",
        -      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
        -      "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
        +    "node_modules/webdriver": {
        +      "version": "9.6.0",
        +      "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.6.0.tgz",
        +      "integrity": "sha512-7rZfKL00ityTEOjKmQizkMQigcUrGlpwl7k2jJVpFAMlWqGHZt9dGw7M3pVH1rBN/NDBkv0i9wQgWoQgzyggkw==",
               "dev": true,
        -      "optional": true,
               "dependencies": {
        -        "querystringify": "^2.1.1",
        -        "requires-port": "^1.0.0"
        +        "@types/node": "^20.1.0",
        +        "@types/ws": "^8.5.3",
        +        "@wdio/config": "9.5.0",
        +        "@wdio/logger": "9.4.4",
        +        "@wdio/protocols": "9.4.4",
        +        "@wdio/types": "9.5.0",
        +        "@wdio/utils": "9.5.0",
        +        "deepmerge-ts": "^7.0.3",
        +        "undici": "^6.20.1",
        +        "ws": "^8.8.0"
        +      },
        +      "engines": {
        +        "node": ">=18.20.0"
               }
             },
        -    "node_modules/url/node_modules/punycode": {
        -      "version": "1.3.2",
        -      "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
        -      "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
        -      "dev": true
        -    },
        -    "node_modules/util": {
        -      "version": "0.10.4",
        -      "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
        -      "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
        +    "node_modules/webdriver/node_modules/@types/node": {
        +      "version": "20.17.14",
        +      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.14.tgz",
        +      "integrity": "sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==",
               "dev": true,
               "dependencies": {
        -        "inherits": "2.0.3"
        +        "undici-types": "~6.19.2"
               }
             },
        -    "node_modules/util-deprecate": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
        -      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
        +    "node_modules/webdriver/node_modules/undici-types": {
        +      "version": "6.19.8",
        +      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
        +      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
               "dev": true
             },
        -    "node_modules/utils-merge": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
        -      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
        +    "node_modules/webdriver/node_modules/ws": {
        +      "version": "8.18.0",
        +      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
        +      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.4.0"
        -      }
        -    },
        -    "node_modules/v8flags": {
        -      "version": "3.2.0",
        -      "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz",
        -      "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==",
        -      "dev": true,
        -      "dependencies": {
        -        "homedir-polyfill": "^1.0.1"
        +        "node": ">=10.0.0"
               },
        -      "engines": {
        -        "node": ">= 0.10"
        +      "peerDependencies": {
        +        "bufferutil": "^4.0.1",
        +        "utf-8-validate": ">=5.0.2"
        +      },
        +      "peerDependenciesMeta": {
        +        "bufferutil": {
        +          "optional": true
        +        },
        +        "utf-8-validate": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/vali-date": {
        -      "version": "1.0.0",
        -      "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",
        -      "integrity": "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==",
        -      "dev": true,
        +    "node_modules/webdriverio": {
        +      "version": "9.6.0",
        +      "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.6.0.tgz",
        +      "integrity": "sha512-nMTPIq1WldQ72nQF86hapMZy0sxpgx8mHJtf+zwYpbuaaD35LLriWcduD2uvwkMSZmBvCHnNCYUfvtFHNhKKrw==",
        +      "dev": true,
        +      "dependencies": {
        +        "@types/node": "^20.11.30",
        +        "@types/sinonjs__fake-timers": "^8.1.5",
        +        "@wdio/config": "9.5.0",
        +        "@wdio/logger": "9.4.4",
        +        "@wdio/protocols": "9.4.4",
        +        "@wdio/repl": "9.4.4",
        +        "@wdio/types": "9.5.0",
        +        "@wdio/utils": "9.5.0",
        +        "archiver": "^7.0.1",
        +        "aria-query": "^5.3.0",
        +        "cheerio": "^1.0.0-rc.12",
        +        "css-shorthand-properties": "^1.1.1",
        +        "css-value": "^0.0.1",
        +        "grapheme-splitter": "^1.0.4",
        +        "htmlfy": "^0.5.0",
        +        "import-meta-resolve": "^4.0.0",
        +        "is-plain-obj": "^4.1.0",
        +        "jszip": "^3.10.1",
        +        "lodash.clonedeep": "^4.5.0",
        +        "lodash.zip": "^4.2.0",
        +        "minimatch": "^9.0.3",
        +        "query-selector-shadow-dom": "^1.0.1",
        +        "resq": "^1.11.0",
        +        "rgb2hex": "0.2.5",
        +        "serialize-error": "^11.0.3",
        +        "urlpattern-polyfill": "^10.0.0",
        +        "webdriver": "9.6.0"
        +      },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=18.20.0"
        +      },
        +      "peerDependencies": {
        +        "puppeteer-core": "^22.3.0"
        +      },
        +      "peerDependenciesMeta": {
        +        "puppeteer-core": {
        +          "optional": true
        +        }
               }
             },
        -    "node_modules/validate-npm-package-license": {
        -      "version": "3.0.4",
        -      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
        -      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
        +    "node_modules/webdriverio/node_modules/@types/node": {
        +      "version": "20.17.14",
        +      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.14.tgz",
        +      "integrity": "sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==",
               "dev": true,
               "dependencies": {
        -        "spdx-correct": "^3.0.0",
        -        "spdx-expression-parse": "^3.0.0"
        +        "undici-types": "~6.19.2"
               }
             },
        -    "node_modules/validate-npm-package-name": {
        -      "version": "3.0.0",
        -      "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
        -      "integrity": "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==",
        +    "node_modules/webdriverio/node_modules/brace-expansion": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
        +      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
               "dev": true,
               "dependencies": {
        -        "builtins": "^1.0.3"
        +        "balanced-match": "^1.0.0"
               }
             },
        -    "node_modules/vary": {
        -      "version": "1.1.2",
        -      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
        -      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
        +    "node_modules/webdriverio/node_modules/is-plain-obj": {
        +      "version": "4.1.0",
        +      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
        +      "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
               "dev": true,
               "engines": {
        -        "node": ">= 0.8"
        +        "node": ">=12"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/vm-browserify": {
        -      "version": "1.1.2",
        -      "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
        -      "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
        -      "dev": true
        -    },
        -    "node_modules/wcwidth": {
        -      "version": "1.0.1",
        -      "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
        -      "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
        +    "node_modules/webdriverio/node_modules/minimatch": {
        +      "version": "9.0.5",
        +      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
        +      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
               "dev": true,
               "dependencies": {
        -        "defaults": "^1.0.3"
        +        "brace-expansion": "^2.0.1"
        +      },
        +      "engines": {
        +        "node": ">=16 || 14 >=14.17"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/isaacs"
               }
             },
        +    "node_modules/webdriverio/node_modules/undici-types": {
        +      "version": "6.19.8",
        +      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
        +      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
        +      "dev": true
        +    },
             "node_modules/webidl-conversions": {
               "version": "3.0.1",
               "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
               "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
               "dev": true
             },
        -    "node_modules/websocket-driver": {
        -      "version": "0.7.0",
        -      "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz",
        -      "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=",
        +    "node_modules/whatwg-encoding": {
        +      "version": "3.1.1",
        +      "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
        +      "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
               "dev": true,
               "dependencies": {
        -        "http-parser-js": ">=0.4.0",
        -        "websocket-extensions": ">=0.1.1"
        +        "iconv-lite": "0.6.3"
               },
               "engines": {
        -        "node": ">=0.8.0"
        +        "node": ">=18"
               }
             },
        -    "node_modules/websocket-extensions": {
        -      "version": "0.1.4",
        -      "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
        -      "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
        +    "node_modules/whatwg-encoding/node_modules/iconv-lite": {
        +      "version": "0.6.3",
        +      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
        +      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
               "dev": true,
        +      "dependencies": {
        +        "safer-buffer": ">= 2.1.2 < 3.0.0"
        +      },
               "engines": {
        -        "node": ">=0.8.0"
        +        "node": ">=0.10.0"
               }
             },
        -    "node_modules/websocket-stream": {
        -      "version": "5.5.2",
        -      "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.5.2.tgz",
        -      "integrity": "sha512-8z49MKIHbGk3C4HtuHWDtYX8mYej1wWabjthC/RupM9ngeukU4IWoM46dgth1UOS/T4/IqgEdCDJuMe2039OQQ==",
        +    "node_modules/whatwg-mimetype": {
        +      "version": "4.0.0",
        +      "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
        +      "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
               "dev": true,
        -      "dependencies": {
        -        "duplexify": "^3.5.1",
        -        "inherits": "^2.0.1",
        -        "readable-stream": "^2.3.3",
        -        "safe-buffer": "^5.1.2",
        -        "ws": "^3.2.0",
        -        "xtend": "^4.0.0"
        +      "engines": {
        +        "node": ">=18"
               }
             },
        -    "node_modules/websocket-stream/node_modules/safe-buffer": {
        -      "version": "5.2.1",
        -      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
        -      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
        -      "dev": true,
        -      "funding": [
        -        {
        -          "type": "github",
        -          "url": "https://github.com/sponsors/feross"
        -        },
        -        {
        -          "type": "patreon",
        -          "url": "https://www.patreon.com/feross"
        -        },
        -        {
        -          "type": "consulting",
        -          "url": "https://feross.org/support"
        -        }
        -      ]
        -    },
        -    "node_modules/whatwg-fetch": {
        -      "version": "2.0.4",
        -      "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
        -      "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==",
        -      "dev": true
        -    },
             "node_modules/whatwg-url": {
               "version": "5.0.0",
               "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
        @@ -17437,18 +12877,6 @@
                 "webidl-conversions": "^3.0.0"
               }
             },
        -    "node_modules/which": {
        -      "version": "1.3.1",
        -      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
        -      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "isexe": "^2.0.0"
        -      },
        -      "bin": {
        -        "which": "bin/which"
        -      }
        -    },
             "node_modules/which-module": {
               "version": "2.0.0",
               "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
        @@ -17461,95 +12889,135 @@
               "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=",
               "dev": true
             },
        -    "node_modules/widest-line": {
        -      "version": "4.0.1",
        -      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
        -      "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==",
        +    "node_modules/why-is-node-running": {
        +      "version": "2.3.0",
        +      "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
        +      "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
        +      "dev": true,
        +      "dependencies": {
        +        "siginfo": "^2.0.0",
        +        "stackback": "0.0.2"
        +      },
        +      "bin": {
        +        "why-is-node-running": "cli.js"
        +      },
        +      "engines": {
        +        "node": ">=8"
        +      }
        +    },
        +    "node_modules/word-wrap": {
        +      "version": "1.2.5",
        +      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
        +      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=0.10.0"
        +      }
        +    },
        +    "node_modules/wrap-ansi": {
        +      "version": "6.2.0",
        +      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
        +      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
               "dev": true,
               "dependencies": {
        -        "string-width": "^5.0.1"
        +        "ansi-styles": "^4.0.0",
        +        "string-width": "^4.1.0",
        +        "strip-ansi": "^6.0.0"
               },
               "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "node": ">=8"
               }
             },
        -    "node_modules/widest-line/node_modules/ansi-regex": {
        -      "version": "6.0.1",
        -      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
        -      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
        +    "node_modules/wrap-ansi-cjs": {
        +      "name": "wrap-ansi",
        +      "version": "7.0.0",
        +      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
        +      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
               "dev": true,
        +      "dependencies": {
        +        "ansi-styles": "^4.0.0",
        +        "string-width": "^4.1.0",
        +        "strip-ansi": "^6.0.0"
        +      },
               "engines": {
        -        "node": ">=12"
        +        "node": ">=10"
               },
               "funding": {
        -        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
        +        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
               }
             },
        -    "node_modules/widest-line/node_modules/emoji-regex": {
        -      "version": "9.2.2",
        -      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
        -      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
        -      "dev": true
        +    "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
        +      "version": "5.0.1",
        +      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
        +      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=8"
        +      }
             },
        -    "node_modules/widest-line/node_modules/string-width": {
        -      "version": "5.1.2",
        -      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
        -      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
        +    "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
        +      "version": "4.3.0",
        +      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
        +      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
               "dev": true,
               "dependencies": {
        -        "eastasianwidth": "^0.2.0",
        -        "emoji-regex": "^9.2.2",
        -        "strip-ansi": "^7.0.1"
        +        "color-convert": "^2.0.1"
               },
               "engines": {
        -        "node": ">=12"
        +        "node": ">=8"
               },
               "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        +        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
               }
             },
        -    "node_modules/widest-line/node_modules/strip-ansi": {
        -      "version": "7.1.0",
        -      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
        -      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
        +    "node_modules/wrap-ansi-cjs/node_modules/color-convert": {
        +      "version": "2.0.1",
        +      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
        +      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
               "dev": true,
               "dependencies": {
        -        "ansi-regex": "^6.0.1"
        +        "color-name": "~1.1.4"
               },
               "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
        +        "node": ">=7.0.0"
               }
             },
        -    "node_modules/word-wrap": {
        -      "version": "1.2.4",
        -      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
        -      "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
        +    "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
        +      "version": "8.0.0",
        +      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
        +      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
        +      "dev": true
        +    },
        +    "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": {
        +      "version": "3.0.0",
        +      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
        +      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
               "dev": true,
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": ">=8"
               }
             },
        -    "node_modules/workerpool": {
        -      "version": "6.2.1",
        -      "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
        -      "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
        -      "dev": true
        +    "node_modules/wrap-ansi-cjs/node_modules/string-width": {
        +      "version": "4.2.3",
        +      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
        +      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
        +      "dev": true,
        +      "dependencies": {
        +        "emoji-regex": "^8.0.0",
        +        "is-fullwidth-code-point": "^3.0.0",
        +        "strip-ansi": "^6.0.1"
        +      },
        +      "engines": {
        +        "node": ">=8"
        +      }
             },
        -    "node_modules/wrap-ansi": {
        -      "version": "6.2.0",
        -      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
        -      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
        +    "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
        +      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
               "dev": true,
               "dependencies": {
        -        "ansi-styles": "^4.0.0",
        -        "string-width": "^4.1.0",
        -        "strip-ansi": "^6.0.0"
        +        "ansi-regex": "^5.0.1"
               },
               "engines": {
                 "node": ">=8"
        @@ -17638,61 +13106,12 @@
               "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
               "dev": true
             },
        -    "node_modules/write-file-atomic": {
        -      "version": "2.4.3",
        -      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
        -      "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
        -      "dev": true,
        -      "dependencies": {
        -        "graceful-fs": "^4.1.11",
        -        "imurmurhash": "^0.1.4",
        -        "signal-exit": "^3.0.2"
        -      }
        -    },
        -    "node_modules/ws": {
        -      "version": "3.3.3",
        -      "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
        -      "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
        -      "dev": true,
        -      "dependencies": {
        -        "async-limiter": "~1.0.0",
        -        "safe-buffer": "~5.1.0",
        -        "ultron": "~1.1.0"
        -      }
        -    },
        -    "node_modules/xdg-basedir": {
        -      "version": "5.1.0",
        -      "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz",
        -      "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=12"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/xtend": {
        -      "version": "4.0.1",
        -      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
        -      "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=0.4"
        -      }
        -    },
             "node_modules/y18n": {
               "version": "4.0.1",
               "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
               "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
               "dev": true
             },
        -    "node_modules/yallist": {
        -      "version": "2.1.2",
        -      "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
        -      "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
        -      "dev": true
        -    },
             "node_modules/yaml": {
               "version": "1.10.2",
               "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
        @@ -17724,64 +13143,6 @@
                 "node": ">=8"
               }
             },
        -    "node_modules/yargs-parser": {
        -      "version": "13.1.2",
        -      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
        -      "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
        -      "dev": true,
        -      "dependencies": {
        -        "camelcase": "^5.0.0",
        -        "decamelize": "^1.2.0"
        -      }
        -    },
        -    "node_modules/yargs-unparser": {
        -      "version": "2.0.0",
        -      "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
        -      "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
        -      "dev": true,
        -      "dependencies": {
        -        "camelcase": "^6.0.0",
        -        "decamelize": "^4.0.0",
        -        "flat": "^5.0.2",
        -        "is-plain-obj": "^2.1.0"
        -      },
        -      "engines": {
        -        "node": ">=10"
        -      }
        -    },
        -    "node_modules/yargs-unparser/node_modules/camelcase": {
        -      "version": "6.3.0",
        -      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
        -      "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/yargs-unparser/node_modules/decamelize": {
        -      "version": "4.0.0",
        -      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
        -      "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=10"
        -      },
        -      "funding": {
        -        "url": "https://github.com/sponsors/sindresorhus"
        -      }
        -    },
        -    "node_modules/yargs-unparser/node_modules/is-plain-obj": {
        -      "version": "2.1.0",
        -      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
        -      "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
        -      "dev": true,
        -      "engines": {
        -        "node": ">=8"
        -      }
        -    },
             "node_modules/yargs/node_modules/ansi-regex": {
               "version": "5.0.0",
               "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
        @@ -17855,6 +13216,15 @@
                 "fd-slicer": "~1.1.0"
               }
             },
        +    "node_modules/yauzl/node_modules/buffer-crc32": {
        +      "version": "0.2.13",
        +      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
        +      "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
        +      "dev": true,
        +      "engines": {
        +        "node": "*"
        +      }
        +    },
             "node_modules/yocto-queue": {
               "version": "0.1.0",
               "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
        @@ -17867,79 +13237,127 @@
                 "url": "https://github.com/sponsors/sindresorhus"
               }
             },
        -    "node_modules/yui": {
        -      "version": "3.18.1",
        -      "resolved": "https://registry.npmjs.org/yui/-/yui-3.18.1.tgz",
        -      "integrity": "sha1-4AAmnsCntvvHQcu4/L0OZRF7AUw=",
        +    "node_modules/yoctocolors-cjs": {
        +      "version": "2.1.2",
        +      "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz",
        +      "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=18"
        +      },
        +      "funding": {
        +        "url": "https://github.com/sponsors/sindresorhus"
        +      }
        +    },
        +    "node_modules/zip-stream": {
        +      "version": "6.0.1",
        +      "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
        +      "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==",
               "dev": true,
               "dependencies": {
        -        "request": "~2.40.0"
        +        "archiver-utils": "^5.0.0",
        +        "compress-commons": "^6.0.2",
        +        "readable-stream": "^4.0.0"
               },
               "engines": {
        -        "node": ">=0.8.0"
        +        "node": ">= 14"
               }
             },
        -    "node_modules/yui/node_modules/mime-types": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz",
        -      "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=",
        +    "node_modules/zip-stream/node_modules/buffer": {
        +      "version": "6.0.3",
        +      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
        +      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
               "dev": true,
        -      "engines": {
        -        "node": ">= 0.8.0"
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
        +        }
        +      ],
        +      "dependencies": {
        +        "base64-js": "^1.3.1",
        +        "ieee754": "^1.2.1"
               }
             },
        -    "node_modules/yui/node_modules/qs": {
        -      "version": "1.0.2",
        -      "resolved": "https://registry.npmjs.org/qs/-/qs-1.0.2.tgz",
        -      "integrity": "sha1-UKk+K1r2aRwxvOpdrnjubqGQN2g=",
        -      "dev": true
        +    "node_modules/zip-stream/node_modules/events": {
        +      "version": "3.3.0",
        +      "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
        +      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
        +      "dev": true,
        +      "engines": {
        +        "node": ">=0.8.x"
        +      }
             },
        -    "node_modules/yui/node_modules/request": {
        -      "version": "2.40.0",
        -      "resolved": "https://registry.npmjs.org/request/-/request-2.40.0.tgz",
        -      "integrity": "sha1-TdZw9pbx5uhC5mtLXoOTAaub62c=",
        -      "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
        +    "node_modules/zip-stream/node_modules/readable-stream": {
        +      "version": "4.7.0",
        +      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
        +      "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
               "dev": true,
        -      "engines": [
        -        "node >= 0.8.0"
        -      ],
               "dependencies": {
        -        "forever-agent": "~0.5.0",
        -        "json-stringify-safe": "~5.0.0",
        -        "mime-types": "~1.0.1",
        -        "node-uuid": "~1.4.0",
        -        "qs": "~1.0.0"
        -      },
        -      "optionalDependencies": {
        -        "aws-sign2": "~0.5.0",
        -        "form-data": "~0.1.0",
        -        "hawk": "1.1.1",
        -        "http-signature": "~0.10.0",
        -        "oauth-sign": "~0.3.0",
        -        "stringstream": "~0.0.4",
        -        "tough-cookie": ">=0.12.0",
        -        "tunnel-agent": "~0.4.0"
        -      }
        -    },
        -    "node_modules/yuidocjs": {
        -      "version": "0.10.2",
        -      "resolved": "https://registry.npmjs.org/yuidocjs/-/yuidocjs-0.10.2.tgz",
        -      "integrity": "sha1-M5JJZ85hkCTNcO9pTiZ9L5iPc/Y=",
        -      "dev": true,
        -      "dependencies": {
        -        "express": "^4.13.1",
        -        "graceful-fs": "^4.1.2",
        -        "markdown-it": "^4.3.0",
        -        "mdn-links": "^0.1.0",
        -        "minimatch": "^3.0.2",
        -        "rimraf": "^2.4.1",
        -        "yui": "^3.18.1"
        -      },
        -      "bin": {
        -        "yuidoc": "lib/cli.js"
        +        "abort-controller": "^3.0.0",
        +        "buffer": "^6.0.3",
        +        "events": "^3.3.0",
        +        "process": "^0.11.10",
        +        "string_decoder": "^1.3.0"
               },
               "engines": {
        -        "node": ">=0.10.0"
        +        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
        +      }
        +    },
        +    "node_modules/zip-stream/node_modules/safe-buffer": {
        +      "version": "5.2.1",
        +      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
        +      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
        +      "dev": true,
        +      "funding": [
        +        {
        +          "type": "github",
        +          "url": "https://github.com/sponsors/feross"
        +        },
        +        {
        +          "type": "patreon",
        +          "url": "https://www.patreon.com/feross"
        +        },
        +        {
        +          "type": "consulting",
        +          "url": "https://feross.org/support"
        +        }
        +      ]
        +    },
        +    "node_modules/zip-stream/node_modules/string_decoder": {
        +      "version": "1.3.0",
        +      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
        +      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
        +      "dev": true,
        +      "dependencies": {
        +        "safe-buffer": "~5.2.0"
        +      }
        +    },
        +    "node_modules/zod": {
        +      "version": "3.24.1",
        +      "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
        +      "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
        +      "dev": true,
        +      "funding": {
        +        "url": "https://github.com/sponsors/colinhacks"
        +      }
        +    },
        +    "node_modules/zwitch": {
        +      "version": "2.0.4",
        +      "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
        +      "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
        +      "dev": true,
        +      "funding": {
        +        "type": "github",
        +        "url": "https://github.com/sponsors/wooorm"
               }
             }
           }
        diff --git a/package.json b/package.json
        index 3de1f9e9a0..978e7d8703 100644
        --- a/package.json
        +++ b/package.json
        @@ -2,78 +2,64 @@
           "name": "p5",
           "repository": "processing/p5.js",
           "scripts": {
        -    "build": "grunt build",
        -    "dev": "grunt yui browserify:dev connect:yui watch:quick",
        -    "docs": "grunt yui",
        -    "docs:dev": "grunt yui:dev",
        -    "test": "grunt",
        -    "lint": "grunt lint",
        -    "lint:fix": "grunt lint-fix"
        +    "build": "rollup -c",
        +    "dev": "vite preview/",
        +    "dev:global": "concurrently -n build,server \"rollup -c -w\" \"npx vite preview/global/\"",
        +    "docs": "documentation build ./src/**/*.js ./src/**/**/*.js -o ./docs/data.json && node ./utils/convert.js",
        +    "bench": "vitest bench",
        +    "bench:report": "vitest bench --reporter=verbose",
        +    "test": "vitest",
        +    "lint": "eslint .",
        +    "lint:fix": "eslint --fix ."
           },
           "lint-staged": {
        -    "ignore": [
        -      "test/js/**/*.js"
        -    ],
             "Gruntfile.js": "eslint",
             "docs/preprocessor.js": "eslint",
             "utils/**/*.js": "eslint",
             "tasks/**/*.js": "eslint",
        -    "test/**/*.js": "eslint",
             "src/**/*.js": [
               "eslint",
        -      "node --require @babel/register ./utils/sample-linter.js"
        +      "node ./utils/sample-linter.mjs"
             ]
           },
        -  "version": "1.11.3",
        -  "devDependencies": {
        -    "@babel/core": "^7.7.7",
        -    "@babel/preset-env": "^7.10.2",
        -    "@babel/register": "^7.7.7",
        -    "all-contributors-cli": "^6.19.0",
        -    "babel-plugin-i18next-extract": "^0.5.0",
        -    "babel-plugin-istanbul": "^5.2.0",
        -    "babelify": "^10.0.0",
        -    "brfs-babel": "^2.0.0",
        -    "browserify": "^16.5.0",
        -    "chai": "^3.5.0",
        -    "connect-modrewrite": "^0.10.1",
        -    "core-js": "^3.6.5",
        -    "derequire": "^2.0.0",
        -    "es6-promise": "^4.2.8",
        -    "eslint": "^8.23.1",
        -    "fetch-jsonp": "^1.1.3",
        +  "version": "2.0.0-beta.1",
        +  "dependencies": {
        +    "@davepagurek/bezier-path": "^0.0.2",
        +    "acorn": "^8.12.1",
        +    "acorn-walk": "^8.3.4",
        +    "colorjs.io": "^0.5.2",
             "file-saver": "^1.3.8",
             "gifenc": "^1.0.3",
        -    "grunt": "^1.6.1",
        -    "grunt-cli": "^1.4.3",
        -    "grunt-contrib-clean": "^2.0.1",
        -    "grunt-contrib-connect": "^3.0.0",
        -    "grunt-contrib-uglify": "^5.2.2",
        -    "grunt-contrib-watch": "^1.1.0",
        -    "grunt-contrib-yuidoc": "1.0.0",
        -    "grunt-eslint": "^24.0.0",
        -    "grunt-minjson": "^0.4.0",
        -    "grunt-mocha-test": "^0.13.3",
        -    "grunt-newer": "^1.3.0",
        -    "grunt-simple-nyc": "^3.0.1",
        -    "html-entities": "^1.3.1",
        +    "libtess": "^1.2.2",
        +    "omggif": "^1.0.10",
        +    "pako": "^2.1.0"
        +  },
        +  "devDependencies": {
        +    "@rollup/plugin-alias": "^5.1.1",
        +    "@rollup/plugin-commonjs": "^25.0.7",
        +    "@rollup/plugin-json": "^6.1.0",
        +    "@rollup/plugin-node-resolve": "^15.2.3",
        +    "@rollup/plugin-replace": "^5.0.7",
        +    "@rollup/plugin-terser": "^0.4.4",
        +    "@vitest/browser": "^2.1.5",
        +    "all-contributors-cli": "^6.19.0",
        +    "concurrently": "^8.2.2",
        +    "dayjs": "^1.11.10",
        +    "documentation": "^14.0.3",
        +    "eslint": "^8.54.0",
             "husky": "^4.2.3",
             "i18next": "^19.0.2",
             "i18next-browser-languagedetector": "^4.0.1",
        -    "libtess": "^1.2.2",
        -    "lint-staged": "^4.3.0",
        -    "marked": "^4.0.10",
        -    "mocha": "^10.2.0",
        -    "np": "^8.0.4",
        -    "omggif": "^1.0.10",
        -    "open": "^7.0.3",
        -    "opentype.js": "^0.9.0",
        -    "pretty-fast": "^0.2.7",
        -    "promise-map-series": "^0.2.3",
        -    "puppeteer": "^18.2.1",
        -    "regenerator-runtime": "^0.13.3",
        -    "simple-git": "^3.16.1",
        -    "whatwg-fetch": "^2.0.4"
        +    "lint-staged": "^15.1.0",
        +    "msw": "^2.6.3",
        +    "rollup": "^4.9.6",
        +    "rollup-plugin-string": "^3.0.0",
        +    "rollup-plugin-visualizer": "^5.12.0",
        +    "vite": "^5.0.2",
        +    "vite-plugin-string": "^1.2.2",
        +    "vitest": "^2.1.5",
        +    "webdriverio": "^9.0.7",
        +    "zod": "^3.23.8"
           },
           "license": "LGPL-2.1",
           "main": "./lib/p5.min.js",
        @@ -89,59 +75,18 @@
           "bugs": {
             "url": "https://github.com/processing/p5.js/issues"
           },
        -  "homepage": "https://github.com/processing/p5.js#readme",
        +  "homepage": "https://p5js.org",
           "directories": {
             "doc": "docs",
             "test": "test"
           },
        -  "babel": {
        -    "presets": [
        -      [
        -        "@babel/preset-env",
        -        {
        -          "useBuiltIns": "usage",
        -          "corejs": 3
        -        }
        -      ]
        -    ],
        -    "plugins": [
        -      [
        -        "i18next-extract",
        -        {
        -          "locales": [
        -            "en",
        -            "es"
        -          ],
        -          "outputPath": "translations/{{locale}}/{{ns}}.json",
        -          "tFunctionNames": [
        -            "translator"
        -          ]
        -        }
        -      ]
        -    ],
        -    "env": {
        -      "test": {
        -        "plugins": [
        -          [
        -            "istanbul",
        -            {
        -              "include": [
        -                "src/**/*.js"
        -              ]
        -            }
        -          ]
        -        ]
        -      }
        -    }
        -  },
        -  "browserslist": [
        -    "last 2 versions",
        -    "not dead"
        -  ],
           "author": "",
           "husky": {
        -    "hooks": {
        -      "pre-commit": "lint-staged"
        -    }
        +    "hooks": {}
        +  },
        +  "msw": {
        +    "workerDirectory": [
        +      "test"
        +    ]
           }
         }
        diff --git a/preview/global/index.html b/preview/global/index.html
        new file mode 100644
        index 0000000000..7c8fa7d0d4
        --- /dev/null
        +++ b/preview/global/index.html
        @@ -0,0 +1,21 @@
        +<!DOCTYPE html>
        +<html>
        +<head>
        +  <title>P5 test</title>
        +  <meta http-equiv="pragma" content="no-cache" />
        +  <meta http-equiv="cache-control" content="no-cache" />
        +  <meta charset="utf-8">
        +
        +  <style>
        +  body{
        +    margin:0;
        +    overflow: hidden;
        +  }
        +  </style>
        +
        +  <script src="./p5.js"></script>
        +</head>
        +<body>
        +<script src="./sketch.js"></script>
        +</body>
        +</html>
        \ No newline at end of file
        diff --git a/preview/global/sketch.js b/preview/global/sketch.js
        new file mode 100644
        index 0000000000..4789f83f36
        --- /dev/null
        +++ b/preview/global/sketch.js
        @@ -0,0 +1,55 @@
        +const vertSrc = `#version 300 es
        + precision mediump float;
        + uniform mat4 uModelViewMatrix;
        + uniform mat4 uProjectionMatrix;
        +
        + in vec3 aPosition;
        + in vec2 aOffset;
        +
        + void main(){
        +   vec4 positionVec4 = vec4(aPosition.xyz, 1.0);
        +   positionVec4.xy += aOffset;
        +   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        + }
        +`;
        +
        +const fragSrc = `#version 300 es
        + precision mediump float;
        + out vec4 outColor;
        + void main(){
        +   outColor = vec4(0.0, 1.0, 1.0, 1.0);
        + }
        +`;
        +
        +let myShader;
        +function setup(){
        +  createCanvas(100, 100, WEBGL);
        +
        +  // Create and use the custom shader.
        +  myShader = createShader(vertSrc, fragSrc);
        +
        +  describe('A wobbly, cyan circle on a gray background.');
        +}
        +
        +function draw(){
        +  // Set the styles
        +  background(125);
        +  noStroke();
        +  shader(myShader);
        +
        +  // Draw the circle.
        +  beginShape();
        +  for (let i = 0; i < 30; i++){
        +    const x = 40 * cos(i/30 * TWO_PI);
        +    const y = 40 * sin(i/30 * TWO_PI);
        +
        +    // Apply some noise to the coordinates.
        +    const xOff = 10 * noise(x + millis()/1000) - 5;
        +    const yOff = 10 * noise(y + millis()/1000) - 5;
        +
        +    // Apply these noise values to the following vertex.
        +    vertexProperty('aOffset', [xOff, yOff]);
        +    vertex(x, y);
        +  }
        +  endShape(CLOSE);
        +}
        diff --git a/preview/global/vite.config.mjs b/preview/global/vite.config.mjs
        new file mode 100644
        index 0000000000..e322f16aa4
        --- /dev/null
        +++ b/preview/global/vite.config.mjs
        @@ -0,0 +1,32 @@
        +import { defineConfig } from 'vitest/config';
        +import vitePluginString from 'vite-plugin-string';
        +import path from 'node:path';
        +
        +const libPath = path.resolve(__dirname, '../../lib');
        +
        +export default defineConfig({
        +  root: '.',
        +  publicDir: libPath,
        +  plugins: [
        +    vitePluginString({
        +      include: [
        +        'src/webgl/shaders/**/*'
        +      ]
        +    }),
        +    {
        +      name: 'reload',
        +      configureServer(server) {
        +        const { ws, watcher } = server;
        +        const buildLibPath = path.resolve(libPath, './p5.rollup.js');
        +        watcher.add(buildLibPath);
        +        watcher.on('change', file => {
        +          if(file === buildLibPath){
        +            ws.send({
        +              type: 'full-reload'
        +            });
        +          }
        +        });
        +      }
        +    }
        +  ]
        +});
        diff --git a/preview/index.html b/preview/index.html
        new file mode 100644
        index 0000000000..d7cba1e642
        --- /dev/null
        +++ b/preview/index.html
        @@ -0,0 +1,38 @@
        +<!DOCTYPE html>
        +<html>
        +
        +<head>
        +  <title>P5 test</title>
        +  <meta http-equiv="pragma" content="no-cache" />
        +  <meta http-equiv="cache-control" content="no-cache" />
        +  <meta charset="utf-8">
        +
        +  <style>
        +    body {
        +      margin: 0;
        +      overflow: hidden;
        +    }
        +  </style>
        +</head>
        +
        +<body>
        +  <script type="module">
        +    import p5 from '../src/app.js';
        +
        +    const sketch = function (p) {
        +      p.setup = async function () {
        +        p.createCanvas(400, 400, p.WEBGL);
        +      };
        +
        +      p.draw = function () {
        +        p.background(200);
        +        p.strokeWeight(10)
        +        p.line(-100, -100, 100, 100)
        +      };
        +    };
        +
        +    new p5(sketch);
        +  </script>
        +</body>
        +
        +</html>
        \ No newline at end of file
        diff --git a/preview/vite.config.mjs b/preview/vite.config.mjs
        new file mode 100644
        index 0000000000..3f528f6925
        --- /dev/null
        +++ b/preview/vite.config.mjs
        @@ -0,0 +1,14 @@
        +import { defineConfig } from 'vitest/config';
        +import vitePluginString from 'vite-plugin-string';
        +
        +export default defineConfig({
        +  root: './',
        +  appType: 'mpa',
        +  plugins: [
        +    vitePluginString({
        +      include: [
        +        'src/webgl/shaders/**/*'
        +      ]
        +    })
        +  ]
        +});
        diff --git a/rfc_p5js_2.md b/rfc_p5js_2.md
        new file mode 100644
        index 0000000000..fb9bcf2baa
        --- /dev/null
        +++ b/rfc_p5js_2.md
        @@ -0,0 +1,437 @@
        +# p5.js 2.0
        +
        +## Introduction
        +This is a RFC document for a proposal of p5.js 2.0.
        +
        +---
        +
        +- [Build and test system](#build-and-test-system)
        +- [Refactors](#refactors)
        +- [Modular](#modular)
        +- [Libraries](#libraries)
        +- [Renderers](#renderers)
        +- [Async](#async)
        +- [Algorithm Changes](#algorithm-changes)
        +- [Reference](#reference)
        +- [Typography](#typography)
        +- [Misc](#misc)
        +
        +---
        +
        +## Motivation
        +p5.js 1.0.0 was released on February 29, 2020, while it may not seem to be that long ago (a bit less than four years ago at the time of writing) in the very fast moving landscape of JavaScript it is a great amount of time. In this time, the JS landscape has advanced a lot with new paradigms and expectations of what a JS library should be. In the time between the 1.0 release till now, p5.js has not been standing still either, with major progress and focuses on accessibility features, bug fixes, updates & enhancements to existing features, and more, contributed by a great number of contributors consistently over the past several years.
        +
        +However by being within a semver 1.0 release means that we have to consider full backwards compatibility of the library when reviewing any proposals around bug fixes, feature enhancements, and potentially adding new features. At the same time we have continously put off updating several aspects of the library, including things such as tooling, algorithms, and newer APIs, because of either lack of maturity in the JavaScript ecosystem or the overall scope of changes. With the view that p5.js will be getting a new website and simply just because this is long overdue, we propose this RFC to start the process of overhauling many aspects of p5.js and bring about a 2.0 version release.
        +
        +## Goals and non-goals
        +This project will continue to adhere to p5.js being an accessible and inclusive creative coding library first and foremost, all the proposals outlined below will continue to aim towards increasing access of p5.js. On a technical level, the larger goals of this project are:
        +* Update p5.js to use more modern JavaScript conventions, both in terms of its internal implementation and the interface it exposes to the user.
        +* Enable the use of p5.js through ES modules semantically.
        +* Keeping backwards compatibility with current 1.x version of p5.js as much as reasonable.
        +
        +A few non-goals of p5.js 2.0 are:
        +* Do everything possible: p5.js will have a powerful and easy to use addon library system and so should not include every feature possible.
        +
        +## Organization
        +Some exploratory work has been done and can be explored [here](https://github.com/limzykenneth/p5.js/tree/test-exploration) with future work to continue from it if feasible. The goal is to complete all necessary work listed and agreed upon below by end of March 2024 with a full release in April 2024. Public issues and a project tracker will be used to build up a clear roadmap towards an April 2024 release.
        +
        +The first stage of this process is for the whole community of p5.js to discuss and review the proposals below and to put forward their own proposals for p5.js 2.0. Exploratory prototyping can be done at this stage using the above linked exploratory version as a starting point. There may be a few rounds of synchronous calls available for community to discuss proposals in real time, the result of which will continue to be tracked here. This document will be constantly updated at this stage as proposals are hashed out, added, or removed throughout the discussions.
        +
        +The second stage will focus on implementation work. Proposals should be hashed out and accepted at this point to start implementation, continuing from the exploratory branch as the working branch and using feature branches for implementation and submitting PR back to the working branch.
        +
        +The third and final stage will be the prerelease process. Several prerelease (Release Candidate, RC) versions will be released in the run up to the final release in April 2024. Implementation work in the second stage should be completed at this point and features will be frozen at this point onward, only bug fixes should be worked on at this stage.
        +
        +After stage 3, p5.js 2.0 will be released!
        +
        +During this process, p5.js 1.0 will still continue to be worked on and we will review bug reports, fixing them, and create releases as necessary. However we will not review feature enchancements or new feature requests for p5.js 1.0, these should be filed towards a proposal for p5.js 2.0 instead.
        +
        +## Proposals and Solutions
        +See the below for the full list of current proposals under considerations. There may be overlaps between different proposals, which may be assigned to the same contributor for implementation in the second stage. For the purpose of derisking the use of bundled external dependencies, wherever possible, external dependencies should be avoided with preference to original implementation or inlining implementations of external dependencies, if external dependencies are to be used for practical reasons, they need to be fully vetted in stage 1.
        +
        +---
        +
        +### Build and test system
        +The build system for p5.js will be updated to use [Rollup](https://rollupjs.org/), development server will use [Vite](https://vitejs.dev/), and the test runner will use [Vitest](https://vitest.dev/).
        +
        +* Rollup
        +  * Significantly improves the build speed of the library with the core library taking just a few seconds to build.
        +  * Have more semantic support of ESM.
        +  * Simpler config and overall a more modern set of tools.
        +* Vite
        +  * Also Vite has a library mode and uses Rollup under the hood for production build, it lacks the flexibility to support complex library build where multiple inputs need to correspond to multiple outputs.
        +  * Provides a very good development server that can be used to speed up library development. An `index.html` file is added to the repo for using this development server.
        +* Vitest
        +  * Uses the same idea behind Vite's development server to run tests which speeds up tests running significantly.
        +  * Supports headless browsers, parallel tests running, granular test running in watch mode, and more.
        +  * While many tests already works, the overall tests will need to have a pass through to correct everything, update to use latest ESM syntax library will be using, and integrate any new test methods where necessary.
        +  * All tests will run in headless browsers and not test should be run in Node.js as it is not the intended environment for p5.js to run in.
        +
        +#### To do
        +In conjuction with refactoring and modularization, the build need to be updated as necessary.
        +
        +The Vite development server can benefit from a more comprehensive `index.html` that includes common visual regression cases that contributors can check while they work on the code base.
        +
        +The majority of pending work will be to update all tests to work with Vitest. The priority being all the existing unit tests. Visual tests may be added after.
        +
        +---
        +
        +### Refactors
        +The overall codebase will undergo extensive refactoring, with the goal of using more semantic JavaScript syntax (modules, classes, newer APIs). The use of side effect imports in the codebase will also be reduced to a minimum if complete removal is not possible.
        +
        +#### API Consistency
        +The overall public API of p5.js should be checked for consistency to ensure a uniform API is provided to the sketch authors where possible. An example for this is `beginShape()` by default does not close the path without `endShape(CLOSE)` being provided whereas `beginContour()` does. The behavior between the two should be made consistent, either both default to closing the path or both default to not close the path. Breaking changes are allowed for these cases.
        +
        +#### Deprecated code
        +Code marked as deprecated in the code base should be removed. Any older behavior if agreed to be kept around for the time being but will be removed in the future should be marked as deprecated with deprecation message printed in the console when used, this should be avoided as much as possible however.
        +
        +---
        +
        +### Modular
        +p5.js will have a core that contains only the absolute essential functionalities, while all other code will be separately import-able. Take as example the core only build of p5.js and the math module as a separate module not included in core, two versions of each will be built from the source code: IIFE and ESM. Rollup is setup to create build for both with IIFE being the preferred format for most users using `<script>` tags and ESM the preferred format for users using their own bundlers. (CommonJS or AMD format will not be supported)
        +
        +#### IIFE
        +Immediately Invoked Function Expression (IIFE) is a common format libraries meant to be included with regular `<script>` tag will come in. It prevents excessive global namespace pollution and is also used by p5.js 1.0. Rollup have several output formats and libraries meant to be included with regular `<script>` tag will either be using `iife` or `umd`, the later of which combines IIFE, CommonJS, and RequireJS/AMD module syntax in one. p5.js 2.0 will mainly use Rollup's `iife` format as IIFE allows for initialization through just side effects while for UMD, an export name must be set which is not compatible with the IIFE usage we want.
        +
        +```html
        +<script src="./lib/p5.js"></script>
        +<script src="./lib/p5.math.js"></script>
        +<script>
        +function setup(){
        +  createCanvas(400, 400);
        +  console.log(ceil(2.1));
        +}
        +
        +function draw(){
        +  background(200);
        +  circle(200, 200, 100);
        +}
        +</script>
        +```
        +
        +In the above example, both `p5.js` and `p5.math.js` are built with the `iife` format. This usage is similar if not identical to the usage of addon libraries currently (deliberately so). The `math` module here is a bundled module taken from all the source located in `src/math` folder. Each file in that folder can be independenly built into the `iife` format with Rollup if desired and the whole module can be included in the final `p5.js` bundle if desired as well.
        +
        +```js
        +// src/math/index.js
        +import calculation from './calculation.js';
        +import noise from './noise.js';
        +import random from './random.js';
        +import trigonometry from './trigonometry.js';
        +import math from './math.js';
        +import vector from './p5.Vector.js';
        +
        +export default function(p5, fn){
        +  p5.registerAddon(calculation);
        +  p5.registerAddon(noise);
        +  p5.registerAddon(random);
        +  p5.registerAddon(trigonometry);
        +  p5.registerAddon(math);
        +  p5.registerAddon(vector);
        +}
        +```
        +```js
        +// src/math/calculation.js (redacted)
        +function calculation(p5, fn){
        +  fn.abs = Math.abs;
        +}
        +
        +export default calculation;
        +
        +if(typeof p5 !== 'undefined'){
        +  calculation(p5, p5.prototype);
        +}
        +```
        +```js
        +// src/app.js (redacted)
        +// math (include if to be bundled as part of p5.js)
        +import './math/calculation';
        +import './math/math';
        +import './math/noise';
        +import './math/p5.Vector';
        +import './math/random';
        +import './math/trigonometry';
        +```
        +
        +Please see relevant examples in the exploration fork for implementation.
        +
        +#### ESM
        +ESM or ES Module is the current standard in JavaScript for working with modular JavaScript code. ESM are now very widely supported with all major browsers natively supporting it, all modern build tools supports or are even built around it, and Node.js have native support for it as well. p5.js 1.0's code is already written with ESM and transpiled into a UMD module. As part of the refactor mentioned in a previos section, the syntax of the internal use of ESM will be updated to match semantic usage. The main goal will be to limit cross dependencies between modules and minimize the use of side effects imports.
        +
        +```js
        +import p5 from 'p5';
        +import math from 'p5/math';
        +
        +p5.registerAddon(math);
        +
        +// The same instance mode syntax
        +const sketch = (p => {
        +  p.setup = () => {
        +    p.createCanvas(400, 400);
        +    console.log(p.ceil(2.1));
        +  };
        +
        +  p.draw = () => {
        +    p.background(200);
        +    p.circle(200, 200, 100);
        +  };
        +});
        +
        +new p5(sketch);
        +```
        +
        +The above example assumes the user is using Node.js module resolution and have installed `p5` through NPM. However, distributable ESM modules are built and will be published via CDN as well. To use this, the first two lines will instead be:
        +
        +```js
        +import p5 from './js/p5.esm.js';
        +import math from './js/p5.math.esm.js';
        +```
        +
        +Across both formats above, one thing that was not elaborated on is the static method `p5.registerAddon`. This will be discussed in the Libraries section below.
        +
        +---
        +
        +### Libraries
        +Existing libraries should have a level of compatibility or require minimal updates to work with p5.js 2.0.
        +
        +A new method of authoring libraries will be introduced that is more ergonomic. This will be through a factory function that exposes reasonable interfaces for completing the following tasks as necessary:
        +* Attaching methods and properties to prototype
        +* Lifecycle hooks
        +* Extending internal functionalities (eg. adding a new renderer)
        +
        +As reference, Day.js provide plugin interface in the following way:
        +```js
        +export default (option, dayjsClass, dayjsFactory) => {
        +  // extend dayjs()
        +  // e.g. add dayjs().isSameOrBefore()
        +  dayjsClass.prototype.isSameOrBefore = function(arguments) {}
        +
        +  // extend dayjs
        +  // e.g. add dayjs.utc()
        +  dayjsFactory.utc = arguments => {}
        +
        +  // overriding existing API
        +  // e.g. extend dayjs().format()
        +  const oldFormat = dayjsClass.prototype.format
        +  dayjsClass.prototype.format = function(arguments) {
        +    // original format result
        +    const result = oldFormat.bind(this)(arguments)
        +    // return modified result
        +  }
        +}
        +```
        +And is used with:
        +```js
        +dayjs.extend(myPlugin);
        +```
        +
        +While jQuery provides the following interface:
        +```js
        +$.fn.greenify = function() {
        +  this.css('color', 'green');
        +};
        +
        +$('a').greenify();
        +```
        +`fn` above is just an alias to `prototype` which in essense makes jQuery's plugin system identical to what p5.js does.
        +
        +p5.js plugins have some properties that are not present in the Day.js or jQuery use case. With Day.js, plugins are expected to be explicitly provided through `dayjs.extend()` while p5.js addons should have the expectations of being available immediately upon inclusion/load. jQuery plugin don't need to content with lifecycle hooks or other non-class instance related features. A p5.js addon should also have the flexibility of being imported as a ES module or included through a script tag, ie. there should be a ES module version and a UMD version ideally.
        +
        +The proposed interface that a p5.js 2.0 plugin can have is as the following:
        +```js
        +(function(p5){
        +  p5.registerAddon((p5, fn, lifecycles) => {
        +    // `fn` being the prototype
        +    fn.myMethod = function(){
        +      // Perform some tasks
        +    };
        +
        +    // Instead of requiring register preload,
        +    // async/await is preferred instead.
        +    fn.loadMyData = async function(){
        +      // Load some data asynchronously
        +    };
        +
        +    lifecycles.presetup = function(){
        +      // Run actions before `setup()` runs
        +    };
        +
        +    lifecycles.postdraw = function(){
        +      // Run actions after `draw()` runs
        +    };
        +  });
        +})(p5);
        +```
        +
        +---
        +
        +### Renderers
        +p5.js 1.0 is bundled with two renderers: 2D and WebGL. They corresponds to the HTML Canvas `2d` and `webgl` context respectively. However, there had been requests over the years to add additional renderers such as an SVG renderer or a renderer with scene graph capabilities. As the web evolve, we are also seeing new a possible standard renderer being developed, ie. [WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API).
        +
        +As currently implemented, adding a new renderer to p5.js is not an easy task which involves many parts that expects to behave differently depending on whether the current sketch is in 2D or WebGL mode.
        +
        +```js
        +createCanvas(400, 400, WEBGL);
        +```
        +
        +The constant value to determine whether a canvas is in 2D or WEBGL mode is also not easily extendable by addon libraries.
        +
        +With p5.js 2.0, the renderer system is redesigned and tweaked with a few key points.
        +1. `p5.Renderer` class which both `p5.Renderer2D` and `p5.RendererGL` classes inherit from will now act more like an abstract class that it is meant to be.
        +    * The `p5.Renderer` class will determine a set of basic properties and methods any renderer class inheriting from it should implement, while extra functionalities can still be implemented on top. (eg. all renderers should implement the `ellipse()` method but the WebGL renderer will also implement a `sphere()` method that 2D renderers don't need).
        +    * The `p5.Renderer` class should never be instantiated directly.
        +2. The core will not have implicit knowledge of what renderers are available. Previously the two modes supported (`P2D` and `WEBGL`) are harded coded into functions like `createCanvas()` making creating a new rendering mode difficult without also modifying core functionalities.
        +    * A list of renderers should be kept under the `p5.renderers` object with value being the class object of the renderer (that inherits from `p5.Renderer` class).
        +```js
        +p5.renderers = {
        +  [constants.P2D]: Renderer2D,
        +  [constants.WEBGL]: RendererGL
        +};
        +```
        +
        +For an addon library to create a new renderer to work with p5, it will need to first create a class that inherits and implements the `p5.Renderer` abstract class, then register the renderer under the `p5.renderers` object.
        +
        +```js
        +(function(p5){
        +  class MyRenderer extends p5.Renderer {
        +    ellipse(x, y, w, h) {
        +      // ...
        +    }
        +
        +    // ...
        +  }
        +
        +  p5.registerAddon((p5, fn, lifecycles) => {
        +    p5.renderers.myRenderer = MyRenderer;
        +  });
        +})(p5);
        +```
        +
        +When a sketch author wants to use the addon provided renderer above, they can use the following code when creating a canvas.
        +
        +```js
        +function setup(){
        +  createCanvas(400, 400, 'myRenderer');
        +}
        +```
        +
        +For usage that are more similar to p5.js' own renderers, constants registration can be exposed to addon library authors as well. In this case, it is recommended to make the constant value a `Symbol` matching behavior in the core library itself.
        +
        +#### Core question
        +Part of the motivation for this proposal is to enable a leaner build of the library for users who only use the 2D renderer but not the WebGL renderer and vice versa. If someone uses only the 2D canvas, there is no need for most if not all of the WebGL components to be included.
        +
        +This creates a question, should p5.js still be bundled with both renderers for distribution? What about new renderers in the future? There are a few options for this:
        +* Bundling both 2D and WebGL renderers, bundling also any new renderers if they are included in the library directly.
        +* Bundle only 2D renderer. Log a warning message if the sketch author tries to create a WebGL canvas without loading in the WebGL renderer as an addon.
        +* Bundle no renderer. All and any renderers should be included as addons.
        +
        +The third options is probably too extreme and probably should not be considered. Either of the first two options are open for discussions.
        +
        +---
        +
        +### Async
        +Data loading functions will all be updated to be fully async using promises (with `async`/`await`) while keeping the callback syntax if necessary. `setup()` will be awaited in the runtime and if the user define it as `async` functions, the promisified data loading functions can be awaited in them. With an async `setup()` function, the `draw()` loop will only start after the `setup()` function has resolved.
        +
        +`draw()` can be defined as an `async` function as well, although this is not recommended because of the possible timing conflict it may have with `requestAnimationFrame`.
        +
        +The async `setup()` function eliminates the need to have the `preload()` function. As such the data loading codebase will be refactored to remove `preload()` related code, while documentation around data loading should be updated accordingly.
        +
        +---
        +
        +### Algorithm changes
        +#### RNG
        +Seeded random number generator (RNG) will use xorshift128+. The current algorithm for RNG used in p5.js is an version of a Linear Congruential Generator (LCG).
        +
        +The main reasoning for this change is for performance. xorshift128+ performs better than LCG and it is the internal implementation of most of the current major browsers for `Math.random()`. The random property of xorshift128+ is similar if not better than LCG.
        +
        +This will be a breaking change as existing seeded RNG will not give the same random number sequence once the algoritm has switched to use xorshift128+. Consideration can be made to create a compatibility addon library that patch seeded RNG to use the old LCG RNG if necessary.
        +
        +Example implementation of a xorshift128+ backed RNG with compatible API to p5.js can be found [here](https://github.com/limzykenneth/js-xorshift128p).
        +
        +#### Noise
        +`noise()` function will use [Simplex noise](https://en.wikipedia.org/wiki/Simplex_noise) instead of the current [Perlin noise](https://en.wikipedia.org/wiki/Perlin_noise). Simplex noise has better performance than Perlin noise and no noticeable directional artifacts.
        +
        +Until January 8, 2022, Simplex noise is protected by a US Patent, making adopting it problematic in certain cases. The patent has since expired, opening the way for its implementation in p5.js. This will be a breaking change as existing seeded `noise()` output will not give the same output after the algorithm has switched to use Simplex noise. Consideration can be made to create a compatibility addon library that patch seeded `noise()` to use Perlin noise if necessary.
        +
        +`noise()` will also accept higher dimensional input with the new implementation through accepting any number of numerical arguments passed to it (currently only accept either 2 or 3 arguments).
        +
        +#### Color
        +A new color module will be implemented. The requirements of this new color module are:
        +* Full compatibility with CSS color space support.
        +* Possibility for addon libraries to introduce additional color spaces.
        +* Accurate gamut mapping.
        +* API as close to existing implementation as possible.
        +
        +#### Math
        +A new math module will be implemented. The requirements of this new math module are:
        +* Unified vector and matrices support with compatible API with ml5.js.
        +* Explore reasonable performance optimizations through the use of specific algorithms, GPU implementation, or others.
        +* Possibility for addon libraries to provide own implementation (so that any external libraries can be made compatible without being implemented internally by p5.js).
        +
        +---
        +
        +### Reference
        +The inline reference of p5.js while following a largely compatible syntax with JSDoc, under the hood is actually compiled with YUIDoc into a JSON file that the website can consume to render the actual reference page. While YUIDoc serves its purpose since the first reference documentation of p5.js, it is a tool that is no longer being worked on for over 6 years. In these 6 years, many issues and limitations means that p5.js is tied in some ways to how YUIDoc expects documentation to be authored. Several issues with setting up a development environment has also previously been tied to YUIDoc getting outdated with the overall JS ecosystem.
        +
        +While YUIDoc has a very similar syntax with JSDoc, there are subtle differences. For p5.js 2.0, we will move inline reference authoring to use the JSDoc syntax and generation/compilation to use [Documentation.js](https://github.com/documentationjs/documentation).
        +
        +#### Syntax
        +JSDoc syntax has for the most part settled into being a de facto standard for documentation authoring in the JavaScript ecosystem, in fact the current inline reference of p5.js is actually full compatible with JSDoc, in that it successfully compiles with JSDoc with no errors. This does not mean documentation generated through JSDoc will have the correct structure, only that it is valid syntax.
        +
        +Depending on the next step and the structure of what the website might expect from the reference data file, there will likely need to be minor changes to the reference throughout the codebase.
        +
        +#### Compilation
        +For compililng the inline reference into data file that can be used by the website to generate the reference pages, instead of using JSDoc, Documentation.js will be used. The reason to prefer Documentation.js over JSDoc in mainly in terms of build tool ergonomics. JSDoc is designed as a site generator with its ability to generate JSON output more of a debugging functionality instead of an intended feature; Documentation.js however supports generating JSON data files as its primary output.
        +
        +Documentation.js does not have as active a maintenance history as JSDoc which is a potential downside. However, since it fully supports JSDoc syntax and JSDoc syntax having widespread ubiquity, in the future if need be the transition to another tool (even JSDoc) should not require the same effort as transitioning from YUIDoc to JSDoc since the inline reference will stay the same still.
        +
        +#### Typing
        +A benefit fully adopting JSDoc syntax is that it enables direct generation of Typescript type declarations (and type checks if desired) from the JSDoc definitions. This is similar to how the source code of [SvelteKit](https://github.com/sveltejs/kit) is authored using just JavaScript while keeping type generation and type checks.
        +
        +This feature however is a nice to have. p5.js will aim to follow typing rules as much as possible but will not impose it as a strict requirement for contributors. Type declaration generation will also not be brought into the repo itself (including type checks) which also means types won't be published officially.
        +
        +---
        +
        +### Typography
        +The typography module can benefit from an overall refactor or even rewrite. The aim should be to leverage native browser/CSS capabilities as much as possible and limit the use of external library (ie. opentype.js) to only things not achievable otherwise.
        +
        +---
        +
        +### Misc
        +These are a collection of minor modifications that don't necessarily have enough scope to be a full proposal on its own.
        +* Remove some internal aliases to Web API. Their API references can be kept.
        +* Retire `p5.Table` for more semantic JavaScript data structure.
        +* Turn all constants that don't rely on underlying value to use `Symbol`.
        +* Remove support for JSONP in `loadJSON` function.
        +    * This feature adds complexity and bypasses security feature.
        +    * If a server was able to serve JSONP, it should be able to serve CORS resources instead.
        +    * If required, JSONP functionality can be readded through an addon but it should not be part of p5.js core.
        +* Regression fixes
        +  * FES should not be using `eval()` partly because it is not compatible with Rollup's bundling
        +
        +---
        +
        +## Future
        +The new architecture design should aim for as much pluggability as possible, enabling additional features to be changed, added, or extended through addon libraries. Some of the possible future features that may be implemented are listed below along with reasons they are and are not targeted for immediate implementation.
        +* [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas)
        +    * Provides a possibility of rendering `p5.Graphics` in web worker, potentially offloading frequent drawing of many graphics into multi-threaded like environment.
        +    * Considered using `creatElement('canvas')` then `transferControlToOffscreen()` (and other methods) to move a `p5.Graphics` canvas to the web worker. The offscreen canvas can then be drawn onto the on screen canvas with `drawImage()`.
        +    * WebGL context not currently supported in Safari.
        +    * Firefox currently does not support drawing offscreen canvas onto on screen canvas. Alternative of `transferToImageBitmap()` is too slow to achieve real time performance.
        +
        +### p5.js 1.0
        +The 1.x version of p5.js will continue to receive bug and critical fixes for another 12 months after the release of p5.js 2.0. No new features or feature updates will be accepted to 1.x version of p5.js and new releases will be created only when as necessary. Any existing code or addon libraries should aim to migrate to support p5.js 2.0 where possible.
        +
        +## Conclusion
        +All the above ideas for p5.js 2.0 would not be possible without all the people who has worked on p5.js over the years, all the contributors who provided so much insight and ideas that greatly inspired this project, the many different open source projects and their contributors that directly or indirectly enabled p5.js to be the project that it is, and many many more.
        +
        +We welcome you to participate in this process of realizing p5.js 2.0 and to continue contributing to p5.js in the future as well!
        +
        +## References
        +* https://github.com/AndreasMadsen/xorshift
        +* https://learn.jquery.com/plugins/basic-plugin-creation/
        +* https://day.js.org/docs/en/plugin/plugin
        +* https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
        +* https://colorjs.io/docs/gamut-mapping
        +* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
        +* https://rollupjs.org/configuration-options/
        +* https://vitejs.dev/guide/
        +* https://vitest.dev/guide/browser.html
        diff --git a/rollup.config.mjs b/rollup.config.mjs
        new file mode 100644
        index 0000000000..f49cc6d0ae
        --- /dev/null
        +++ b/rollup.config.mjs
        @@ -0,0 +1,151 @@
        +import { nodeResolve } from '@rollup/plugin-node-resolve';
        +import json from '@rollup/plugin-json';
        +import { string } from 'rollup-plugin-string';
        +import commonjs from '@rollup/plugin-commonjs';
        +import terser from '@rollup/plugin-terser';
        +import pkg from './package.json' with { type: 'json' };
        +import dayjs from 'dayjs';
        +import { visualizer } from 'rollup-plugin-visualizer';
        +import replace from '@rollup/plugin-replace';
        +import alias from '@rollup/plugin-alias';
        +
        +const plugins = [
        +  commonjs(),
        +  nodeResolve(),
        +  json(),
        +  string({
        +    include: 'src/webgl/shaders/**/*'
        +  }),
        +  replace({
        +    values: {
        +      'VERSION_WILL_BE_REPLACED_BY_BUILD': pkg.version
        +    },
        +    preventAssignment: true
        +  })
        +];
        +const banner = `/*! p5.js v${pkg.version} ${dayjs().format('MMMM D, YYYY')} */`;
        +const bundleSize = (name, sourcemap) => {
        +  return visualizer({
        +    filename: `analyzer/${name}.html`,
        +    gzipSize: true,
        +    brotliSize: true,
        +    sourcemap
        +  });
        +};
        +
        +const modules = ['math'];
        +const generateModuleBuild = () => {
        +  return modules.map((module) => {
        +    return {
        +      input: `src/${module}/index.js`,
        +      output: [
        +        {
        +          file: `./lib/p5.${module}.js`,
        +          format: 'iife',
        +          plugins: [
        +            bundleSize(`p5.${module}.js`)
        +          ]
        +        },
        +        {
        +          file: `./lib/p5.${module}.min.js`,
        +          format: 'iife',
        +          sourcemap: 'hidden',
        +          plugins: [
        +            terser({
        +              compress: {
        +                global_defs: {
        +                  IS_MINIFIED: true
        +                }
        +              },
        +              format: {
        +                comments: false
        +              }
        +            }),
        +            bundleSize(`p5.${module}.min.js`)
        +          ]
        +        },
        +        {
        +          file: `./lib/p5.${module}.esm.js`,
        +          format: 'esm',
        +          plugins: [
        +            bundleSize(`p5.${module}.esm.js`)
        +          ]
        +        }
        +      ],
        +      external: ['../core/main'],
        +      plugins: [
        +        ...plugins
        +      ]
        +    }
        +  });
        +};
        +
        +export default [
        +  {
        +    input: 'src/app.js',
        +    output: [
        +      {
        +        file: './lib/p5.js',
        +        format: 'iife',
        +        name: 'p5',
        +        banner,
        +        plugins: [
        +          bundleSize('p5.js')
        +        ]
        +      },
        +      {
        +        file: './lib/p5.esm.js',
        +        format: 'esm',
        +        banner,
        +        plugins: [
        +          bundleSize('p5.esm.js')
        +        ]
        +      }
        +    ],
        +    treeshake: {
        +      preset: 'smallest'
        +    },
        +    plugins: [
        +      ...plugins
        +    ]
        +  },
        +  //// Minified build ////
        +  {
        +    input: 'src/app.js',
        +    output: [
        +      {
        +        file: './lib/p5.min.js',
        +        format: 'iife',
        +        name: 'p5',
        +        banner,
        +        sourcemap: 'hidden',
        +        plugins: [
        +          terser({
        +            compress: {
        +              global_defs: {
        +                IS_MINIFIED: true
        +              }
        +            },
        +            format: {
        +              comments: false
        +            }
        +          }),
        +          bundleSize('p5.min.js', true)
        +        ]
        +      }
        +    ],
        +    treeshake: {
        +      preset: 'smallest'
        +    },
        +    plugins: [
        +      alias({
        +        entries: [
        +          { find: './core/friendly_errors', replacement: './core/noop' }
        +        ]
        +      }),
        +      ...plugins
        +    ]
        +  }
        +  // NOTE: comment to NOT build standalone math module
        +  // ...generateModuleBuild()
        +];
        diff --git a/src/accessibility/color_namer.js b/src/accessibility/color_namer.js
        index 4509280e5b..bfe1c96a58 100644
        --- a/src/accessibility/color_namer.js
        +++ b/src/accessibility/color_namer.js
        @@ -5,711 +5,716 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
         import color_conversion from '../color/color_conversion';
         
        -//stores the original hsb values
        -let originalHSB;
        +function colorNamer(p5, fn){
        +  //stores the original hsb values
        +  let originalHSB;
         
        -//stores values for color name exceptions
        -const colorExceptions = [
        -  {
        -    h: 0,
        -    s: 0,
        -    b: 0.8275,
        -    name: 'gray'
        -  },
        -  {
        -    h: 0,
        -    s: 0,
        -    b: 0.8627,
        -    name: 'gray'
        -  },
        -  {
        -    h: 0,
        -    s: 0,
        -    b: 0.7529,
        -    name: 'gray'
        -  },
        -  {
        -    h: 0.0167,
        -    s: 0.1176,
        -    b: 1,
        -    name: 'light pink'
        -  }
        -];
        -
        -//stores values for color names
        -const colorLookUp = [
        -  {
        -    h: 0,
        -    s: 0,
        -    b: 0,
        -    name: 'black'
        -  },
        -  {
        -    h: 0,
        -    s: 0,
        -    b: 0.5,
        -    name: 'gray'
        -  },
        -  {
        -    h: 0,
        -    s: 0,
        -    b: 1,
        -    name: 'white'
        -  },
        -  {
        -    h: 0,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'dark maroon'
        -  },
        -  {
        -    h: 0,
        -    s: 0.5,
        -    b: 1,
        -    name: 'salmon pink'
        -  },
        -  {
        -    h: 0,
        -    s: 1,
        -    b: 0,
        -    name: 'black'
        -  },
        -  {
        -    h: 0,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark red'
        -  },
        -  {
        -    h: 0,
        -    s: 1,
        -    b: 1,
        -    name: 'red'
        -  },
        -  {
        -    h: 5,
        -    s: 0,
        -    b: 1,
        -    name: 'very light peach'
        -  },
        -  {
        -    h: 5,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'brown'
        -  },
        -  {
        -    h: 5,
        -    s: 0.5,
        -    b: 1,
        -    name: 'peach'
        -  },
        -  {
        -    h: 5,
        -    s: 1,
        -    b: 0.5,
        -    name: 'brick red'
        -  },
        -  {
        -    h: 5,
        -    s: 1,
        -    b: 1,
        -    name: 'crimson'
        -  },
        -  {
        -    h: 10,
        -    s: 0,
        -    b: 1,
        -    name: 'light peach'
        -  },
        -  {
        -    h: 10,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'brown'
        -  },
        -  {
        -    h: 10,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light orange'
        -  },
        -  {
        -    h: 10,
        -    s: 1,
        -    b: 0.5,
        -    name: 'brown'
        -  },
        -  {
        -    h: 10,
        -    s: 1,
        -    b: 1,
        -    name: 'orange'
        -  },
        -  {
        -    h: 15,
        -    s: 0,
        -    b: 1,
        -    name: 'very light yellow'
        -  },
        -  {
        -    h: 15,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'olive green'
        -  },
        -  {
        -    h: 15,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light yellow'
        -  },
        -  {
        -    h: 15,
        -    s: 1,
        -    b: 0,
        -    name: 'dark olive green'
        -  },
        -  {
        -    h: 15,
        -    s: 1,
        -    b: 0.5,
        -    name: 'olive green'
        -  },
        -  {
        -    h: 15,
        -    s: 1,
        -    b: 1,
        -    name: 'yellow'
        -  },
        -  {
        -    h: 20,
        -    s: 0,
        -    b: 1,
        -    name: 'very light yellow'
        -  },
        -  {
        -    h: 20,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'olive green'
        -  },
        -  {
        -    h: 20,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light yellow green'
        -  },
        -  {
        -    h: 20,
        -    s: 1,
        -    b: 0,
        -    name: 'dark olive green'
        -  },
        -  {
        -    h: 20,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark yellow green'
        -  },
        -  {
        -    h: 20,
        -    s: 1,
        -    b: 1,
        -    name: 'yellow green'
        -  },
        -  {
        -    h: 25,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'dark yellow green'
        -  },
        -  {
        -    h: 25,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light green'
        -  },
        -  {
        -    h: 25,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark green'
        -  },
        -  {
        -    h: 25,
        -    s: 1,
        -    b: 1,
        -    name: 'green'
        -  },
        -  {
        -    h: 30,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light green'
        -  },
        -  {
        -    h: 30,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark green'
        -  },
        -  {
        -    h: 30,
        -    s: 1,
        -    b: 1,
        -    name: 'green'
        -  },
        -  {
        -    h: 35,
        -    s: 0,
        -    b: 0.5,
        -    name: 'light green'
        -  },
        -  {
        -    h: 35,
        -    s: 0,
        -    b: 1,
        -    name: 'very light green'
        -  },
        -  {
        -    h: 35,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'dark green'
        -  },
        -  {
        -    h: 35,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light green'
        -  },
        -  {
        -    h: 35,
        -    s: 1,
        -    b: 0,
        -    name: 'very dark green'
        -  },
        -  {
        -    h: 35,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark green'
        -  },
        -  {
        -    h: 35,
        -    s: 1,
        -    b: 1,
        -    name: 'green'
        -  },
        -  {
        -    h: 40,
        -    s: 0,
        -    b: 1,
        -    name: 'very light green'
        -  },
        -  {
        -    h: 40,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'dark green'
        -  },
        -  {
        -    h: 40,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light green'
        -  },
        -  {
        -    h: 40,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark green'
        -  },
        -  {
        -    h: 40,
        -    s: 1,
        -    b: 1,
        -    name: 'green'
        -  },
        -  {
        -    h: 45,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light turquoise'
        -  },
        -  {
        -    h: 45,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark turquoise'
        -  },
        -  {
        -    h: 45,
        -    s: 1,
        -    b: 1,
        -    name: 'turquoise'
        -  },
        -  {
        -    h: 50,
        -    s: 0,
        -    b: 1,
        -    name: 'light sky blue'
        -  },
        -  {
        -    h: 50,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'dark cyan'
        -  },
        -  {
        -    h: 50,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light cyan'
        -  },
        -  {
        -    h: 50,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark cyan'
        -  },
        -  {
        -    h: 50,
        -    s: 1,
        -    b: 1,
        -    name: 'cyan'
        -  },
        -  {
        -    h: 55,
        -    s: 0,
        -    b: 1,
        -    name: 'light sky blue'
        -  },
        -  {
        -    h: 55,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light sky blue'
        -  },
        -  {
        -    h: 55,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark blue'
        -  },
        -  {
        -    h: 55,
        -    s: 1,
        -    b: 1,
        -    name: 'sky blue'
        -  },
        -  {
        -    h: 60,
        -    s: 0,
        -    b: 0.5,
        -    name: 'gray'
        -  },
        -  {
        -    h: 60,
        -    s: 0,
        -    b: 1,
        -    name: 'very light blue'
        -  },
        -  {
        -    h: 60,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'blue'
        -  },
        -  {
        -    h: 60,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light blue'
        -  },
        -  {
        -    h: 60,
        -    s: 1,
        -    b: 0.5,
        -    name: 'navy blue'
        -  },
        -  {
        -    h: 60,
        -    s: 1,
        -    b: 1,
        -    name: 'blue'
        -  },
        -  {
        -    h: 65,
        -    s: 0,
        -    b: 1,
        -    name: 'lavender'
        -  },
        -  {
        -    h: 65,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'navy blue'
        -  },
        -  {
        -    h: 65,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light purple'
        -  },
        -  {
        -    h: 65,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark navy blue'
        -  },
        -  {
        -    h: 65,
        -    s: 1,
        -    b: 1,
        -    name: 'blue'
        -  },
        -  {
        -    h: 70,
        -    s: 0,
        -    b: 1,
        -    name: 'lavender'
        -  },
        -  {
        -    h: 70,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'navy blue'
        -  },
        -  {
        -    h: 70,
        -    s: 0.5,
        -    b: 1,
        -    name: 'lavender blue'
        -  },
        -  {
        -    h: 70,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark navy blue'
        -  },
        -  {
        -    h: 70,
        -    s: 1,
        -    b: 1,
        -    name: 'blue'
        -  },
        -  {
        -    h: 75,
        -    s: 0.5,
        -    b: 1,
        -    name: 'lavender'
        -  },
        -  {
        -    h: 75,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark purple'
        -  },
        -  {
        -    h: 75,
        -    s: 1,
        -    b: 1,
        -    name: 'purple'
        -  },
        -  {
        -    h: 80,
        -    s: 0.5,
        -    b: 1,
        -    name: 'pinkish purple'
        -  },
        -  {
        -    h: 80,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark purple'
        -  },
        -  {
        -    h: 80,
        -    s: 1,
        -    b: 1,
        -    name: 'purple'
        -  },
        -  {
        -    h: 85,
        -    s: 0,
        -    b: 1,
        -    name: 'light pink'
        -  },
        -  {
        -    h: 85,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'purple'
        -  },
        -  {
        -    h: 85,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light fuchsia'
        -  },
        -  {
        -    h: 85,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark fuchsia'
        -  },
        -  {
        -    h: 85,
        -    s: 1,
        -    b: 1,
        -    name: 'fuchsia'
        -  },
        -  {
        -    h: 90,
        -    s: 0.5,
        -    b: 0.5,
        -    name: 'dark fuchsia'
        -  },
        -  {
        -    h: 90,
        -    s: 0.5,
        -    b: 1,
        -    name: 'hot pink'
        -  },
        -  {
        -    h: 90,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark fuchsia'
        -  },
        -  {
        -    h: 90,
        -    s: 1,
        -    b: 1,
        -    name: 'fuchsia'
        -  },
        -  {
        -    h: 95,
        -    s: 0,
        -    b: 1,
        -    name: 'pink'
        -  },
        -  {
        -    h: 95,
        -    s: 0.5,
        -    b: 1,
        -    name: 'light pink'
        -  },
        -  {
        -    h: 95,
        -    s: 1,
        -    b: 0.5,
        -    name: 'dark magenta'
        -  },
        -  {
        -    h: 95,
        -    s: 1,
        -    b: 1,
        -    name: 'magenta'
        -  }
        -];
        +  //stores values for color name exceptions
        +  const colorExceptions = [
        +    {
        +      h: 0,
        +      s: 0,
        +      b: 0.8275,
        +      name: 'gray'
        +    },
        +    {
        +      h: 0,
        +      s: 0,
        +      b: 0.8627,
        +      name: 'gray'
        +    },
        +    {
        +      h: 0,
        +      s: 0,
        +      b: 0.7529,
        +      name: 'gray'
        +    },
        +    {
        +      h: 0.0167,
        +      s: 0.1176,
        +      b: 1,
        +      name: 'light pink'
        +    }
        +  ];
         
        -//returns text with color name
        -function _calculateColor(hsb) {
        -  let colortext;
        -  //round hue
        -  if (hsb[0] !== 0) {
        -    hsb[0] = Math.round(hsb[0] * 100);
        -    let hue = hsb[0].toString().split('');
        -    const last = hue.length - 1;
        -    hue[last] = parseInt(hue[last]);
        -    //if last digit of hue is < 2.5 make it 0
        -    if (hue[last] < 2.5) {
        -      hue[last] = 0;
        -      //if last digit of hue is >= 2.5 and less than 7.5 make it 5
        -    } else if (hue[last] >= 2.5 && hue[last] < 7.5) {
        -      hue[last] = 5;
        +  //stores values for color names
        +  const colorLookUp = [
        +    {
        +      h: 0,
        +      s: 0,
        +      b: 0,
        +      name: 'black'
        +    },
        +    {
        +      h: 0,
        +      s: 0,
        +      b: 0.5,
        +      name: 'gray'
        +    },
        +    {
        +      h: 0,
        +      s: 0,
        +      b: 1,
        +      name: 'white'
        +    },
        +    {
        +      h: 0,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'dark maroon'
        +    },
        +    {
        +      h: 0,
        +      s: 0.5,
        +      b: 1,
        +      name: 'salmon pink'
        +    },
        +    {
        +      h: 0,
        +      s: 1,
        +      b: 0,
        +      name: 'black'
        +    },
        +    {
        +      h: 0,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark red'
        +    },
        +    {
        +      h: 0,
        +      s: 1,
        +      b: 1,
        +      name: 'red'
        +    },
        +    {
        +      h: 5,
        +      s: 0,
        +      b: 1,
        +      name: 'very light peach'
        +    },
        +    {
        +      h: 5,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'brown'
        +    },
        +    {
        +      h: 5,
        +      s: 0.5,
        +      b: 1,
        +      name: 'peach'
        +    },
        +    {
        +      h: 5,
        +      s: 1,
        +      b: 0.5,
        +      name: 'brick red'
        +    },
        +    {
        +      h: 5,
        +      s: 1,
        +      b: 1,
        +      name: 'crimson'
        +    },
        +    {
        +      h: 10,
        +      s: 0,
        +      b: 1,
        +      name: 'light peach'
        +    },
        +    {
        +      h: 10,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'brown'
        +    },
        +    {
        +      h: 10,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light orange'
        +    },
        +    {
        +      h: 10,
        +      s: 1,
        +      b: 0.5,
        +      name: 'brown'
        +    },
        +    {
        +      h: 10,
        +      s: 1,
        +      b: 1,
        +      name: 'orange'
        +    },
        +    {
        +      h: 15,
        +      s: 0,
        +      b: 1,
        +      name: 'very light yellow'
        +    },
        +    {
        +      h: 15,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'olive green'
        +    },
        +    {
        +      h: 15,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light yellow'
        +    },
        +    {
        +      h: 15,
        +      s: 1,
        +      b: 0,
        +      name: 'dark olive green'
        +    },
        +    {
        +      h: 15,
        +      s: 1,
        +      b: 0.5,
        +      name: 'olive green'
        +    },
        +    {
        +      h: 15,
        +      s: 1,
        +      b: 1,
        +      name: 'yellow'
        +    },
        +    {
        +      h: 20,
        +      s: 0,
        +      b: 1,
        +      name: 'very light yellow'
        +    },
        +    {
        +      h: 20,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'olive green'
        +    },
        +    {
        +      h: 20,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light yellow green'
        +    },
        +    {
        +      h: 20,
        +      s: 1,
        +      b: 0,
        +      name: 'dark olive green'
        +    },
        +    {
        +      h: 20,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark yellow green'
        +    },
        +    {
        +      h: 20,
        +      s: 1,
        +      b: 1,
        +      name: 'yellow green'
        +    },
        +    {
        +      h: 25,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'dark yellow green'
        +    },
        +    {
        +      h: 25,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light green'
        +    },
        +    {
        +      h: 25,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark green'
        +    },
        +    {
        +      h: 25,
        +      s: 1,
        +      b: 1,
        +      name: 'green'
        +    },
        +    {
        +      h: 30,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light green'
        +    },
        +    {
        +      h: 30,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark green'
        +    },
        +    {
        +      h: 30,
        +      s: 1,
        +      b: 1,
        +      name: 'green'
        +    },
        +    {
        +      h: 35,
        +      s: 0,
        +      b: 0.5,
        +      name: 'light green'
        +    },
        +    {
        +      h: 35,
        +      s: 0,
        +      b: 1,
        +      name: 'very light green'
        +    },
        +    {
        +      h: 35,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'dark green'
        +    },
        +    {
        +      h: 35,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light green'
        +    },
        +    {
        +      h: 35,
        +      s: 1,
        +      b: 0,
        +      name: 'very dark green'
        +    },
        +    {
        +      h: 35,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark green'
        +    },
        +    {
        +      h: 35,
        +      s: 1,
        +      b: 1,
        +      name: 'green'
        +    },
        +    {
        +      h: 40,
        +      s: 0,
        +      b: 1,
        +      name: 'very light green'
        +    },
        +    {
        +      h: 40,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'dark green'
        +    },
        +    {
        +      h: 40,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light green'
        +    },
        +    {
        +      h: 40,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark green'
        +    },
        +    {
        +      h: 40,
        +      s: 1,
        +      b: 1,
        +      name: 'green'
        +    },
        +    {
        +      h: 45,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light turquoise'
        +    },
        +    {
        +      h: 45,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark turquoise'
        +    },
        +    {
        +      h: 45,
        +      s: 1,
        +      b: 1,
        +      name: 'turquoise'
        +    },
        +    {
        +      h: 50,
        +      s: 0,
        +      b: 1,
        +      name: 'light sky blue'
        +    },
        +    {
        +      h: 50,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'dark cyan'
        +    },
        +    {
        +      h: 50,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light cyan'
        +    },
        +    {
        +      h: 50,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark cyan'
        +    },
        +    {
        +      h: 50,
        +      s: 1,
        +      b: 1,
        +      name: 'cyan'
        +    },
        +    {
        +      h: 55,
        +      s: 0,
        +      b: 1,
        +      name: 'light sky blue'
        +    },
        +    {
        +      h: 55,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light sky blue'
        +    },
        +    {
        +      h: 55,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark blue'
        +    },
        +    {
        +      h: 55,
        +      s: 1,
        +      b: 1,
        +      name: 'sky blue'
        +    },
        +    {
        +      h: 60,
        +      s: 0,
        +      b: 0.5,
        +      name: 'gray'
        +    },
        +    {
        +      h: 60,
        +      s: 0,
        +      b: 1,
        +      name: 'very light blue'
        +    },
        +    {
        +      h: 60,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'blue'
        +    },
        +    {
        +      h: 60,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light blue'
        +    },
        +    {
        +      h: 60,
        +      s: 1,
        +      b: 0.5,
        +      name: 'navy blue'
        +    },
        +    {
        +      h: 60,
        +      s: 1,
        +      b: 1,
        +      name: 'blue'
        +    },
        +    {
        +      h: 65,
        +      s: 0,
        +      b: 1,
        +      name: 'lavender'
        +    },
        +    {
        +      h: 65,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'navy blue'
        +    },
        +    {
        +      h: 65,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light purple'
        +    },
        +    {
        +      h: 65,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark navy blue'
        +    },
        +    {
        +      h: 65,
        +      s: 1,
        +      b: 1,
        +      name: 'blue'
        +    },
        +    {
        +      h: 70,
        +      s: 0,
        +      b: 1,
        +      name: 'lavender'
        +    },
        +    {
        +      h: 70,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'navy blue'
        +    },
        +    {
        +      h: 70,
        +      s: 0.5,
        +      b: 1,
        +      name: 'lavender blue'
        +    },
        +    {
        +      h: 70,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark navy blue'
        +    },
        +    {
        +      h: 70,
        +      s: 1,
        +      b: 1,
        +      name: 'blue'
        +    },
        +    {
        +      h: 75,
        +      s: 0.5,
        +      b: 1,
        +      name: 'lavender'
        +    },
        +    {
        +      h: 75,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark purple'
        +    },
        +    {
        +      h: 75,
        +      s: 1,
        +      b: 1,
        +      name: 'purple'
        +    },
        +    {
        +      h: 80,
        +      s: 0.5,
        +      b: 1,
        +      name: 'pinkish purple'
        +    },
        +    {
        +      h: 80,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark purple'
        +    },
        +    {
        +      h: 80,
        +      s: 1,
        +      b: 1,
        +      name: 'purple'
        +    },
        +    {
        +      h: 85,
        +      s: 0,
        +      b: 1,
        +      name: 'light pink'
        +    },
        +    {
        +      h: 85,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'purple'
        +    },
        +    {
        +      h: 85,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light fuchsia'
        +    },
        +    {
        +      h: 85,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark fuchsia'
        +    },
        +    {
        +      h: 85,
        +      s: 1,
        +      b: 1,
        +      name: 'fuchsia'
        +    },
        +    {
        +      h: 90,
        +      s: 0.5,
        +      b: 0.5,
        +      name: 'dark fuchsia'
        +    },
        +    {
        +      h: 90,
        +      s: 0.5,
        +      b: 1,
        +      name: 'hot pink'
        +    },
        +    {
        +      h: 90,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark fuchsia'
        +    },
        +    {
        +      h: 90,
        +      s: 1,
        +      b: 1,
        +      name: 'fuchsia'
        +    },
        +    {
        +      h: 95,
        +      s: 0,
        +      b: 1,
        +      name: 'pink'
        +    },
        +    {
        +      h: 95,
        +      s: 0.5,
        +      b: 1,
        +      name: 'light pink'
        +    },
        +    {
        +      h: 95,
        +      s: 1,
        +      b: 0.5,
        +      name: 'dark magenta'
        +    },
        +    {
        +      h: 95,
        +      s: 1,
        +      b: 1,
        +      name: 'magenta'
             }
        -    //if hue only has two digits
        -    if (hue.length === 2) {
        -      hue[0] = parseInt(hue[0]);
        -      //if last is greater than 7.5
        -      if (hue[last] >= 7.5) {
        -        //add one to the tens
        +  ];
        +
        +  //returns text with color name
        +  function _calculateColor(hsb) {
        +    let colortext;
        +    //round hue
        +    if (hsb[0] !== 0) {
        +      hsb[0] = Math.round(hsb[0] * 100);
        +      let hue = hsb[0].toString().split('');
        +      const last = hue.length - 1;
        +      hue[last] = parseInt(hue[last]);
        +      //if last digit of hue is < 2.5 make it 0
        +      if (hue[last] < 2.5) {
                 hue[last] = 0;
        -        hue[0] = hue[0] + 1;
        +        //if last digit of hue is >= 2.5 and less than 7.5 make it 5
        +      } else if (hue[last] >= 2.5 && hue[last] < 7.5) {
        +        hue[last] = 5;
               }
        -      hsb[0] = hue[0] * 10 + hue[1];
        -    } else {
        -      if (hue[last] >= 7.5) {
        -        hsb[0] = 10;
        +      //if hue only has two digits
        +      if (hue.length === 2) {
        +        hue[0] = parseInt(hue[0]);
        +        //if last is greater than 7.5
        +        if (hue[last] >= 7.5) {
        +          //add one to the tens
        +          hue[last] = 0;
        +          hue[0] = hue[0] + 1;
        +        }
        +        hsb[0] = hue[0] * 10 + hue[1];
               } else {
        -        hsb[0] = hue[last];
        +        if (hue[last] >= 7.5) {
        +          hsb[0] = 10;
        +        } else {
        +          hsb[0] = hue[last];
        +        }
               }
             }
        -  }
        -  //map brightness from 0 to 1
        -  hsb[2] = hsb[2] / 255;
        -  //round saturation and brightness
        -  for (let i = hsb.length - 1; i >= 1; i--) {
        -    if (hsb[i] <= 0.25) {
        -      hsb[i] = 0;
        -    } else if (hsb[i] > 0.25 && hsb[i] < 0.75) {
        -      hsb[i] = 0.5;
        -    } else {
        -      hsb[i] = 1;
        -    }
        -  }
        -  //after rounding, if the values are hue 0, saturation 0 and brightness 1
        -  //look at color exceptions which includes several tones from white to gray
        -  if (hsb[0] === 0 && hsb[1] === 0 && hsb[2] === 1) {
        -    //round original hsb values
        -    for (let i = 2; i >= 0; i--) {
        -      originalHSB[i] = Math.round(originalHSB[i] * 10000) / 10000;
        -    }
        -    //compare with the values in the colorExceptions array
        -    for (let e = 0; e < colorExceptions.length; e++) {
        -      if (
        -        colorExceptions[e].h === originalHSB[0] &&
        -        colorExceptions[e].s === originalHSB[1] &&
        -        colorExceptions[e].b === originalHSB[2]
        -      ) {
        -        colortext = colorExceptions[e].name;
        -        break;
        +    //map brightness from 0 to 1
        +    hsb[2] = hsb[2] / 255;
        +    //round saturation and brightness
        +    for (let i = hsb.length - 1; i >= 1; i--) {
        +      if (hsb[i] <= 0.25) {
        +        hsb[i] = 0;
        +      } else if (hsb[i] > 0.25 && hsb[i] < 0.75) {
        +        hsb[i] = 0.5;
               } else {
        -        //if there is no match return white
        -        colortext = 'white';
        +        hsb[i] = 1;
               }
             }
        -  } else {
        -    //otherwise, compare with values in colorLookUp
        -    for (let i = 0; i < colorLookUp.length; i++) {
        -      if (
        -        colorLookUp[i].h === hsb[0] &&
        -        colorLookUp[i].s === hsb[1] &&
        -        colorLookUp[i].b === hsb[2]
        -      ) {
        -        colortext = colorLookUp[i].name;
        -        break;
        +    //after rounding, if the values are hue 0, saturation 0 and brightness 1
        +    //look at color exceptions which includes several tones from white to gray
        +    if (hsb[0] === 0 && hsb[1] === 0 && hsb[2] === 1) {
        +      //round original hsb values
        +      for (let i = 2; i >= 0; i--) {
        +        originalHSB[i] = Math.round(originalHSB[i] * 10000) / 10000;
        +      }
        +      //compare with the values in the colorExceptions array
        +      for (let e = 0; e < colorExceptions.length; e++) {
        +        if (
        +          colorExceptions[e].h === originalHSB[0] &&
        +          colorExceptions[e].s === originalHSB[1] &&
        +          colorExceptions[e].b === originalHSB[2]
        +        ) {
        +          colortext = colorExceptions[e].name;
        +          break;
        +        } else {
        +          //if there is no match return white
        +          colortext = 'white';
        +        }
        +      }
        +    } else {
        +      //otherwise, compare with values in colorLookUp
        +      for (let i = 0; i < colorLookUp.length; i++) {
        +        if (
        +          colorLookUp[i].h === hsb[0] &&
        +          colorLookUp[i].s === hsb[1] &&
        +          colorLookUp[i].b === hsb[2]
        +        ) {
        +          colortext = colorLookUp[i].name;
        +          break;
        +        }
               }
             }
        +    return colortext;
           }
        -  return colortext;
        +
        +  //gets rgba and returs a color name
        +  fn._rgbColorName = function(arg) {
        +    //conversts rgba to hsb
        +    let hsb = color_conversion._rgbaToHSBA(arg);
        +    //stores hsb in global variable
        +    originalHSB = hsb;
        +    //calculate color name
        +    return _calculateColor([hsb[0], hsb[1], hsb[2]]);
        +  };
         }
         
        -//gets rgba and returs a color name
        -p5.prototype._rgbColorName = function(arg) {
        -  //conversts rgba to hsb
        -  let hsb = color_conversion._rgbaToHSBA(arg);
        -  //stores hsb in global variable
        -  originalHSB = hsb;
        -  //calculate color name
        -  return _calculateColor([hsb[0], hsb[1], hsb[2]]);
        -};
        +export default colorNamer;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  colorNamer(p5, p5.prototype);
        +}
        diff --git a/src/accessibility/describe.js b/src/accessibility/describe.js
        index 563e891958..79cebe605e 100644
        --- a/src/accessibility/describe.js
        +++ b/src/accessibility/describe.js
        @@ -5,497 +5,501 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        -const descContainer = '_Description'; //Fallback container
        -const fallbackDescId = '_fallbackDesc'; //Fallback description
        -const fallbackTableId = '_fallbackTable'; //Fallback Table
        -const fallbackTableElId = '_fte_'; //Fallback Table Element
        -const labelContainer = '_Label'; //Label container
        -const labelDescId = '_labelDesc'; //Label description
        -const labelTableId = '_labelTable'; //Label Table
        -const labelTableElId = '_lte_'; //Label Table Element
        +function describe(p5, fn){
        +  const descContainer = '_Description'; //Fallback container
        +  const fallbackDescId = '_fallbackDesc'; //Fallback description
        +  const fallbackTableId = '_fallbackTable'; //Fallback Table
        +  const fallbackTableElId = '_fte_'; //Fallback Table Element
        +  const labelContainer = '_Label'; //Label container
        +  const labelDescId = '_labelDesc'; //Label description
        +  const labelTableId = '_labelTable'; //Label Table
        +  const labelTableElId = '_lte_'; //Label Table Element
         
        -/**
        - * Creates a screen reader-accessible description of the canvas.
        - *
        - * The first parameter, `text`, is the description of the canvas.
        - *
        - * The second parameter, `display`, is optional. It determines how the
        - * description is displayed. If `LABEL` is passed, as in
        - * `describe('A description.', LABEL)`, the description will be visible in
        - * a div element next to the canvas. If `FALLBACK` is passed, as in
        - * `describe('A description.', FALLBACK)`, the description will only be
        - * visible to screen readers. This is the default mode.
        - *
        - * Read
        - * <a href="https://p5js.org/tutorials/writing-accessible-canvas-descriptions/">Writing accessible canvas descriptions</a>
        - * to learn more about making sketches accessible.
        - *
        - * @method describe
        - * @param  {String} text        description of the canvas.
        - * @param  {Constant} [display] either LABEL or FALLBACK.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   background('pink');
        - *
        - *   // Draw a heart.
        - *   fill('red');
        - *   noStroke();
        - *   circle(67, 67, 20);
        - *   circle(83, 67, 20);
        - *   triangle(91, 73, 75, 95, 59, 73);
        - *
        - *   // Add a general description of the canvas.
        - *   describe('A pink square with a red heart in the bottom-right corner.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   background('pink');
        - *
        - *   // Draw a heart.
        - *   fill('red');
        - *   noStroke();
        - *   circle(67, 67, 20);
        - *   circle(83, 67, 20);
        - *   triangle(91, 73, 75, 95, 59, 73);
        - *
        - *   // Add a general description of the canvas
        - *   // and display it for debugging.
        - *   describe('A pink square with a red heart in the bottom-right corner.', LABEL);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function draw() {
        - *   background(200);
        - *
        - *   // The expression
        - *   // frameCount % 100
        - *   // causes x to increase from 0
        - *   // to 99, then restart from 0.
        - *   let x = frameCount % 100;
        - *
        - *   // Draw the circle.
        - *   fill(0, 255, 0);
        - *   circle(x, 50, 40);
        - *
        - *   // Add a general description of the canvas.
        - *   describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function draw() {
        - *   background(200);
        - *
        - *   // The expression
        - *   // frameCount % 100
        - *   // causes x to increase from 0
        - *   // to 99, then restart from 0.
        - *   let x = frameCount % 100;
        - *
        - *   // Draw the circle.
        - *   fill(0, 255, 0);
        - *   circle(x, 50, 40);
        - *
        - *   // Add a general description of the canvas
        - *   // and display it for debugging.
        - *   describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`, LABEL);
        - * }
        - * </code>
        - * </div>
        - */
        -
        -p5.prototype.describe = function(text, display) {
        -  p5._validateParameters('describe', arguments);
        -  if (typeof text !== 'string') {
        -    return;
        -  }
        -  const cnvId = this.canvas.id;
        -  //calls function that adds punctuation for better screen reading
        -  text = _descriptionText(text);
        -  //if there is no dummyDOM
        -  if (!this.dummyDOM) {
        -    this.dummyDOM = document.getElementById(cnvId).parentNode;
        -  }
        -  if (!this.descriptions) {
        -    this.descriptions = {};
        -  }
        -  //check if html structure for description is ready
        -  if (this.descriptions.fallback) {
        -    //check if text is different from current description
        -    if (this.descriptions.fallback.innerHTML !== text) {
        -      //update description
        -      this.descriptions.fallback.innerHTML = text;
        +  /**
        +   * Creates a screen reader-accessible description of the canvas.
        +   *
        +   * The first parameter, `text`, is the description of the canvas.
        +   *
        +   * The second parameter, `display`, is optional. It determines how the
        +   * description is displayed. If `LABEL` is passed, as in
        +   * `describe('A description.', LABEL)`, the description will be visible in
        +   * a div element next to the canvas. If `FALLBACK` is passed, as in
        +   * `describe('A description.', FALLBACK)`, the description will only be
        +   * visible to screen readers. This is the default mode.
        +   *
        +   * Read
        +   * <a href="/learn/accessible-labels.html">Writing accessible canvas descriptions</a>
        +   * to learn more about making sketches accessible.
        +   *
        +   * @method describe
        +   * @param  {String} text        description of the canvas.
        +   * @param  {(FALLBACK|LABEL)} [display] either LABEL or FALLBACK.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background('pink');
        +   *
        +   *   // Draw a heart.
        +   *   fill('red');
        +   *   noStroke();
        +   *   circle(67, 67, 20);
        +   *   circle(83, 67, 20);
        +   *   triangle(91, 73, 75, 95, 59, 73);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe('A pink square with a red heart in the bottom-right corner.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background('pink');
        +   *
        +   *   // Draw a heart.
        +   *   fill('red');
        +   *   noStroke();
        +   *   circle(67, 67, 20);
        +   *   circle(83, 67, 20);
        +   *   triangle(91, 73, 75, 95, 59, 73);
        +   *
        +   *   // Add a general description of the canvas
        +   *   // and display it for debugging.
        +   *   describe('A pink square with a red heart in the bottom-right corner.', LABEL);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // The expression
        +   *   // frameCount % 100
        +   *   // causes x to increase from 0
        +   *   // to 99, then restart from 0.
        +   *   let x = frameCount % 100;
        +   *
        +   *   // Draw the circle.
        +   *   fill(0, 255, 0);
        +   *   circle(x, 50, 40);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // The expression
        +   *   // frameCount % 100
        +   *   // causes x to increase from 0
        +   *   // to 99, then restart from 0.
        +   *   let x = frameCount % 100;
        +   *
        +   *   // Draw the circle.
        +   *   fill(0, 255, 0);
        +   *   circle(x, 50, 40);
        +   *
        +   *   // Add a general description of the canvas
        +   *   // and display it for debugging.
        +   *   describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`, LABEL);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.describe = function(text, display) {
        +    // p5._validateParameters('describe', arguments);
        +    if (typeof text !== 'string') {
        +      return;
             }
        -  } else {
        -    //create fallback html structure
        -    this._describeHTML('fallback', text);
        -  }
        -  //if display is LABEL
        -  if (display === this.LABEL) {
        -    //check if html structure for label is ready
        -    if (this.descriptions.label) {
        -      //check if text is different from current label
        -      if (this.descriptions.label.innerHTML !== text) {
        -        //update label description
        -        this.descriptions.label.innerHTML = text;
        +    const cnvId = this.canvas.id;
        +    //calls function that adds punctuation for better screen reading
        +    text = _descriptionText(text);
        +    //if there is no dummyDOM
        +    if (!this.dummyDOM) {
        +      this.dummyDOM = document.getElementById(cnvId).parentNode;
        +    }
        +    if (!this.descriptions) {
        +      this.descriptions = {};
        +    }
        +    //check if html structure for description is ready
        +    if (this.descriptions.fallback) {
        +      //check if text is different from current description
        +      if (this.descriptions.fallback.innerHTML !== text) {
        +        //update description
        +        this.descriptions.fallback.innerHTML = text;
               }
             } else {
        -      //create label html structure
        -      this._describeHTML('label', text);
        +      //create fallback html structure
        +      this._describeHTML('fallback', text);
             }
        -  }
        -};
        +    //if display is LABEL
        +    if (display === this.LABEL) {
        +      //check if html structure for label is ready
        +      if (this.descriptions.label) {
        +        //check if text is different from current label
        +        if (this.descriptions.label.innerHTML !== text) {
        +          //update label description
        +          this.descriptions.label.innerHTML = text;
        +        }
        +      } else {
        +        //create label html structure
        +        this._describeHTML('label', text);
        +      }
        +    }
        +  };
         
        -/**
        - * Creates a screen reader-accessible description of elements in the canvas.
        - *
        - * Elements are shapes or groups of shapes that create meaning together. For
        - * example, a few overlapping circles could make an "eye" element.
        - *
        - * The first parameter, `name`, is the name of the element.
        - *
        - * The second parameter, `text`, is the description of the element.
        - *
        - * The third parameter, `display`, is optional. It determines how the
        - * description is displayed. If `LABEL` is passed, as in
        - * `describe('A description.', LABEL)`, the description will be visible in
        - * a div element next to the canvas. Using `LABEL` creates unhelpful
        - * duplicates for screen readers. Only use `LABEL` during development. If
        - * `FALLBACK` is passed, as in `describe('A description.', FALLBACK)`, the
        - * description will only be visible to screen readers. This is the default
        - * mode.
        - *
        - * Read
        - * <a href="https://p5js.org/tutorials/writing-accessible-canvas-descriptions/">Writing accessible canvas descriptions</a>
        - * to learn more about making sketches accessible.
        - *
        - * @method describeElement
        - * @param  {String} name        name of the element.
        - * @param  {String} text        description of the element.
        - * @param  {Constant} [display] either LABEL or FALLBACK.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   background('pink');
        - *
        - *   // Describe the first element
        - *   // and draw it.
        - *   describeElement('Circle', 'A yellow circle in the top-left corner.');
        - *   noStroke();
        - *   fill('yellow');
        - *   circle(25, 25, 40);
        - *
        - *   // Describe the second element
        - *   // and draw it.
        - *   describeElement('Heart', 'A red heart in the bottom-right corner.');
        - *   fill('red');
        - *   circle(66.6, 66.6, 20);
        - *   circle(83.2, 66.6, 20);
        - *   triangle(91.2, 72.6, 75, 95, 58.6, 72.6);
        - *
        - *   // Add a general description of the canvas.
        - *   describe('A red heart and yellow circle over a pink background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   background('pink');
        - *
        - *   // Describe the first element
        - *   // and draw it. Display the
        - *   // description for debugging.
        - *   describeElement('Circle', 'A yellow circle in the top-left corner.', LABEL);
        - *   noStroke();
        - *   fill('yellow');
        - *   circle(25, 25, 40);
        - *
        - *   // Describe the second element
        - *   // and draw it. Display the
        - *   // description for debugging.
        - *   describeElement('Heart', 'A red heart in the bottom-right corner.', LABEL);
        - *   fill('red');
        - *   circle(66.6, 66.6, 20);
        - *   circle(83.2, 66.6, 20);
        - *   triangle(91.2, 72.6, 75, 95, 58.6, 72.6);
        - *
        - *   // Add a general description of the canvas.
        - *   describe('A red heart and yellow circle over a pink background.');
        - * }
        - * </code>
        - * </div>
        - */
        +  /**
        +   * Creates a screen reader-accessible description of elements in the canvas.
        +   *
        +   * Elements are shapes or groups of shapes that create meaning together. For
        +   * example, a few overlapping circles could make an "eye" element.
        +   *
        +   * The first parameter, `name`, is the name of the element.
        +   *
        +   * The second parameter, `text`, is the description of the element.
        +   *
        +   * The third parameter, `display`, is optional. It determines how the
        +   * description is displayed. If `LABEL` is passed, as in
        +   * `describe('A description.', LABEL)`, the description will be visible in
        +   * a div element next to the canvas. Using `LABEL` creates unhelpful
        +   * duplicates for screen readers. Only use `LABEL` during development. If
        +   * `FALLBACK` is passed, as in `describe('A description.', FALLBACK)`, the
        +   * description will only be visible to screen readers. This is the default
        +   * mode.
        +   *
        +   * Read
        +   * <a href="/learn/accessible-labels.html">Writing accessible canvas descriptions</a>
        +   * to learn more about making sketches accessible.
        +   *
        +   * @method describeElement
        +   * @param  {String} name        name of the element.
        +   * @param  {String} text        description of the element.
        +   * @param  {(FALLBACK|LABEL)} [display] either LABEL or FALLBACK.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background('pink');
        +   *
        +   *   // Describe the first element
        +   *   // and draw it.
        +   *   describeElement('Circle', 'A yellow circle in the top-left corner.');
        +   *   noStroke();
        +   *   fill('yellow');
        +   *   circle(25, 25, 40);
        +   *
        +   *   // Describe the second element
        +   *   // and draw it.
        +   *   describeElement('Heart', 'A red heart in the bottom-right corner.');
        +   *   fill('red');
        +   *   circle(66.6, 66.6, 20);
        +   *   circle(83.2, 66.6, 20);
        +   *   triangle(91.2, 72.6, 75, 95, 58.6, 72.6);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe('A red heart and yellow circle over a pink background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background('pink');
        +   *
        +   *   // Describe the first element
        +   *   // and draw it. Display the
        +   *   // description for debugging.
        +   *   describeElement('Circle', 'A yellow circle in the top-left corner.', LABEL);
        +   *   noStroke();
        +   *   fill('yellow');
        +   *   circle(25, 25, 40);
        +   *
        +   *   // Describe the second element
        +   *   // and draw it. Display the
        +   *   // description for debugging.
        +   *   describeElement('Heart', 'A red heart in the bottom-right corner.', LABEL);
        +   *   fill('red');
        +   *   circle(66.6, 66.6, 20);
        +   *   circle(83.2, 66.6, 20);
        +   *   triangle(91.2, 72.6, 75, 95, 58.6, 72.6);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe('A red heart and yellow circle over a pink background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -p5.prototype.describeElement = function(name, text, display) {
        -  p5._validateParameters('describeElement', arguments);
        -  if (typeof text !== 'string' || typeof name !== 'string') {
        -    return;
        -  }
        -  const cnvId = this.canvas.id;
        -  //calls function that adds punctuation for better screen reading
        -  text = _descriptionText(text);
        -  //calls function that adds punctuation for better screen reading
        -  let elementName = _elementName(name);
        -  //remove any special characters from name to use it as html id
        -  name = name.replace(/[^a-zA-Z0-9]/g, '');
        +  fn.describeElement = function(name, text, display) {
        +    // p5._validateParameters('describeElement', arguments);
        +    if (typeof text !== 'string' || typeof name !== 'string') {
        +      return;
        +    }
        +    const cnvId = this.canvas.id;
        +    //calls function that adds punctuation for better screen reading
        +    text = _descriptionText(text);
        +    //calls function that adds punctuation for better screen reading
        +    let elementName = _elementName(name);
        +    //remove any special characters from name to use it as html id
        +    name = name.replace(/[^a-zA-Z0-9]/g, '');
         
        -  //store element description
        -  let inner = `<th scope="row">${elementName}</th><td>${text}</td>`;
        -  //if there is no dummyDOM
        -  if (!this.dummyDOM) {
        -    this.dummyDOM = document.getElementById(cnvId).parentNode;
        -  }
        -  if (!this.descriptions) {
        -    this.descriptions = { fallbackElements: {} };
        -  } else if (!this.descriptions.fallbackElements) {
        -    this.descriptions.fallbackElements = {};
        -  }
        -  //check if html structure for element description is ready
        -  if (this.descriptions.fallbackElements[name]) {
        -    //if current element description is not the same as inner
        -    if (this.descriptions.fallbackElements[name].innerHTML !== inner) {
        -      //update element description
        -      this.descriptions.fallbackElements[name].innerHTML = inner;
        +    //store element description
        +    let inner = `<th scope="row">${elementName}</th><td>${text}</td>`;
        +    //if there is no dummyDOM
        +    if (!this.dummyDOM) {
        +      this.dummyDOM = document.getElementById(cnvId).parentNode;
             }
        -  } else {
        -    //create fallback html structure
        -    this._describeElementHTML('fallback', name, inner);
        -  }
        -  //if display is LABEL
        -  if (display === this.LABEL) {
        -    if (!this.descriptions.labelElements) {
        -      this.descriptions.labelElements = {};
        +    if (!this.descriptions) {
        +      this.descriptions = { fallbackElements: {} };
        +    } else if (!this.descriptions.fallbackElements) {
        +      this.descriptions.fallbackElements = {};
             }
        -    //if html structure for label element description is ready
        -    if (this.descriptions.labelElements[name]) {
        -      //if label element description is different
        -      if (this.descriptions.labelElements[name].innerHTML !== inner) {
        -        //update label element description
        -        this.descriptions.labelElements[name].innerHTML = inner;
        +    //check if html structure for element description is ready
        +    if (this.descriptions.fallbackElements[name]) {
        +      //if current element description is not the same as inner
        +      if (this.descriptions.fallbackElements[name].innerHTML !== inner) {
        +        //update element description
        +        this.descriptions.fallbackElements[name].innerHTML = inner;
               }
             } else {
        -      //create label element html structure
        -      this._describeElementHTML('label', name, inner);
        +      //create fallback html structure
        +      this._describeElementHTML('fallback', name, inner);
             }
        -  }
        -};
        +    //if display is LABEL
        +    if (display === this.LABEL) {
        +      if (!this.descriptions.labelElements) {
        +        this.descriptions.labelElements = {};
        +      }
        +      //if html structure for label element description is ready
        +      if (this.descriptions.labelElements[name]) {
        +        //if label element description is different
        +        if (this.descriptions.labelElements[name].innerHTML !== inner) {
        +          //update label element description
        +          this.descriptions.labelElements[name].innerHTML = inner;
        +        }
        +      } else {
        +        //create label element html structure
        +        this._describeElementHTML('label', name, inner);
        +      }
        +    }
        +  };
         
        -/*
        - *
        - * Helper functions for describe() and describeElement().
        - *
        - */
        +  /*
        +   *
        +   * Helper functions for describe() and describeElement().
        +   *
        +   */
         
        -// check that text is not LABEL or FALLBACK and ensure text ends with punctuation mark
        -function _descriptionText(text) {
        -  if (text === 'label' || text === 'fallback') {
        -    throw new Error('description should not be LABEL or FALLBACK');
        -  }
        -  //if string does not end with '.'
        -  if (
        -    !text.endsWith('.') &&
        -    !text.endsWith(';') &&
        -    !text.endsWith(',') &&
        -    !text.endsWith('?') &&
        -    !text.endsWith('!')
        -  ) {
        -    //add '.' to the end of string
        -    text = text + '.';
        +  // check that text is not LABEL or FALLBACK and ensure text ends with punctuation mark
        +  function _descriptionText(text) {
        +    if (text === 'label' || text === 'fallback') {
        +      throw new Error('description should not be LABEL or FALLBACK');
        +    }
        +    //if string does not end with '.'
        +    if (
        +      !text.endsWith('.') &&
        +      !text.endsWith(';') &&
        +      !text.endsWith(',') &&
        +      !text.endsWith('?') &&
        +      !text.endsWith('!')
        +    ) {
        +      //add '.' to the end of string
        +      text = text + '.';
        +    }
        +    return text;
           }
        -  return text;
        -}
         
        -/*
        - * Helper functions for describe()
        - */
        +  /*
        +   * Helper functions for describe()
        +   */
         
        -//creates HTML structure for canvas descriptions
        -p5.prototype._describeHTML = function(type, text) {
        -  const cnvId = this.canvas.id;
        -  if (type === 'fallback') {
        -    //if there is no description container
        -    if (!this.dummyDOM.querySelector(`#${cnvId + descContainer}`)) {
        -      //if there are no accessible outputs (see textOutput() and gridOutput())
        -      let html = `<div id="${cnvId}${descContainer}" role="region" aria-label="Canvas Description"><p id="${cnvId}${fallbackDescId}"></p></div>`;
        -      if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutput`)) {
        -        //create description container + <p> for fallback description
        -        this.dummyDOM.querySelector(`#${cnvId}`).innerHTML = html;
        +  //creates HTML structure for canvas descriptions
        +  fn._describeHTML = function(type, text) {
        +    const cnvId = this.canvas.id;
        +    if (type === 'fallback') {
        +      //if there is no description container
        +      if (!this.dummyDOM.querySelector(`#${cnvId + descContainer}`)) {
        +        //if there are no accessible outputs (see textOutput() and gridOutput())
        +        let html = `<div id="${cnvId}${descContainer}" role="region" aria-label="Canvas Description"><p id="${cnvId}${fallbackDescId}"></p></div>`;
        +        if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutput`)) {
        +          //create description container + <p> for fallback description
        +          this.dummyDOM.querySelector(`#${cnvId}`).innerHTML = html;
        +        } else {
        +          //create description container + <p> for fallback description before outputs
        +          this.dummyDOM
        +            .querySelector(`#${cnvId}accessibleOutput`)
        +            .insertAdjacentHTML('beforebegin', html);
        +        }
               } else {
        -        //create description container + <p> for fallback description before outputs
        +        //if describeElement() has already created the container and added a table of elements
        +        //create fallback description <p> before the table
                 this.dummyDOM
        -          .querySelector(`#${cnvId}accessibleOutput`)
        -          .insertAdjacentHTML('beforebegin', html);
        +          .querySelector('#' + cnvId + fallbackTableId)
        +          .insertAdjacentHTML(
        +            'beforebegin',
        +            `<p id="${cnvId + fallbackDescId}"></p>`
        +          );
               }
        -    } else {
        -      //if describeElement() has already created the container and added a table of elements
        -      //create fallback description <p> before the table
        -      this.dummyDOM
        -        .querySelector('#' + cnvId + fallbackTableId)
        -        .insertAdjacentHTML(
        -          'beforebegin',
        -          `<p id="${cnvId + fallbackDescId}"></p>`
        -        );
        -    }
        -    //if the container for the description exists
        -    this.descriptions.fallback = this.dummyDOM.querySelector(
        -      `#${cnvId}${fallbackDescId}`
        -    );
        -    this.descriptions.fallback.innerHTML = text;
        -    return;
        -  } else if (type === 'label') {
        -    //if there is no label container
        -    if (!this.dummyDOM.querySelector(`#${cnvId + labelContainer}`)) {
        -      let html = `<div id="${cnvId}${labelContainer}" class="p5Label"><p id="${cnvId}${labelDescId}"></p></div>`;
        -      //if there are no accessible outputs (see textOutput() and gridOutput())
        -      if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutputLabel`)) {
        -        //create label container + <p> for label description
        -        this.dummyDOM
        -          .querySelector('#' + cnvId)
        -          .insertAdjacentHTML('afterend', html);
        -      } else {
        -        //create label container + <p> for label description before outputs
        +      //if the container for the description exists
        +      this.descriptions.fallback = this.dummyDOM.querySelector(
        +        `#${cnvId}${fallbackDescId}`
        +      );
        +      this.descriptions.fallback.innerHTML = text;
        +      return;
        +    } else if (type === 'label') {
        +      //if there is no label container
        +      if (!this.dummyDOM.querySelector(`#${cnvId + labelContainer}`)) {
        +        let html = `<div id="${cnvId}${labelContainer}" class="p5Label"><p id="${cnvId}${labelDescId}"></p></div>`;
        +        //if there are no accessible outputs (see textOutput() and gridOutput())
        +        if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutputLabel`)) {
        +          //create label container + <p> for label description
        +          this.dummyDOM
        +            .querySelector('#' + cnvId)
        +            .insertAdjacentHTML('afterend', html);
        +        } else {
        +          //create label container + <p> for label description before outputs
        +          this.dummyDOM
        +            .querySelector(`#${cnvId}accessibleOutputLabel`)
        +            .insertAdjacentHTML('beforebegin', html);
        +        }
        +      } else if (this.dummyDOM.querySelector(`#${cnvId + labelTableId}`)) {
        +        //if describeElement() has already created the container and added a table of elements
        +        //create label description <p> before the table
                 this.dummyDOM
        -          .querySelector(`#${cnvId}accessibleOutputLabel`)
        -          .insertAdjacentHTML('beforebegin', html);
        +          .querySelector(`#${cnvId + labelTableId}`)
        +          .insertAdjacentHTML(
        +            'beforebegin',
        +            `<p id="${cnvId}${labelDescId}"></p>`
        +          );
               }
        -    } else if (this.dummyDOM.querySelector(`#${cnvId + labelTableId}`)) {
        -      //if describeElement() has already created the container and added a table of elements
        -      //create label description <p> before the table
        -      this.dummyDOM
        -        .querySelector(`#${cnvId + labelTableId}`)
        -        .insertAdjacentHTML(
        -          'beforebegin',
        -          `<p id="${cnvId}${labelDescId}"></p>`
        -        );
        +      this.descriptions.label = this.dummyDOM.querySelector(
        +        '#' + cnvId + labelDescId
        +      );
        +      this.descriptions.label.innerHTML = text;
        +      return;
             }
        -    this.descriptions.label = this.dummyDOM.querySelector(
        -      '#' + cnvId + labelDescId
        -    );
        -    this.descriptions.label.innerHTML = text;
        -    return;
        -  }
        -};
        +  };
         
        -/*
        - * Helper functions for describeElement().
        - */
        +  /*
        +   * Helper functions for describeElement().
        +   */
         
        -//check that name is not LABEL or FALLBACK and ensure text ends with colon
        -function _elementName(name) {
        -  if (name === 'label' || name === 'fallback') {
        -    throw new Error('element name should not be LABEL or FALLBACK');
        -  }
        -  //check if last character of string n is '.', ';', or ','
        -  if (name.endsWith('.') || name.endsWith(';') || name.endsWith(',')) {
        -    //replace last character with ':'
        -    name = name.replace(/.$/, ':');
        -  } else if (!name.endsWith(':')) {
        -    //if string n does not end with ':'
        -    //add ':'' at the end of string
        -    name = name + ':';
        +  //check that name is not LABEL or FALLBACK and ensure text ends with colon
        +  function _elementName(name) {
        +    if (name === 'label' || name === 'fallback') {
        +      throw new Error('element name should not be LABEL or FALLBACK');
        +    }
        +    //check if last character of string n is '.', ';', or ','
        +    if (name.endsWith('.') || name.endsWith(';') || name.endsWith(',')) {
        +      //replace last character with ':'
        +      name = name.replace(/.$/, ':');
        +    } else if (!name.endsWith(':')) {
        +      //if string n does not end with ':'
        +      //add ':'' at the end of string
        +      name = name + ':';
        +    }
        +    return name;
           }
        -  return name;
        -}
         
        -//creates HTML structure for element descriptions
        -p5.prototype._describeElementHTML = function(type, name, text) {
        -  const cnvId = this.canvas.id;
        -  if (type === 'fallback') {
        -    //if there is no description container
        -    if (!this.dummyDOM.querySelector(`#${cnvId + descContainer}`)) {
        -      //if there are no accessible outputs (see textOutput() and gridOutput())
        -      let html = `<div id="${cnvId}${descContainer}" role="region" aria-label="Canvas Description"><table id="${cnvId}${fallbackTableId}"><caption>Canvas elements and their descriptions</caption></table></div>`;
        -      if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutput`)) {
        -        //create container + table for element descriptions
        -        this.dummyDOM.querySelector('#' + cnvId).innerHTML = html;
        -      } else {
        -        //create container + table for element descriptions before outputs
        +  //creates HTML structure for element descriptions
        +  fn._describeElementHTML = function(type, name, text) {
        +    const cnvId = this.canvas.id;
        +    if (type === 'fallback') {
        +      //if there is no description container
        +      if (!this.dummyDOM.querySelector(`#${cnvId + descContainer}`)) {
        +        //if there are no accessible outputs (see textOutput() and gridOutput())
        +        let html = `<div id="${cnvId}${descContainer}" role="region" aria-label="Canvas Description"><table id="${cnvId}${fallbackTableId}"><caption>Canvas elements and their descriptions</caption></table></div>`;
        +        if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutput`)) {
        +          //create container + table for element descriptions
        +          this.dummyDOM.querySelector('#' + cnvId).innerHTML = html;
        +        } else {
        +          //create container + table for element descriptions before outputs
        +          this.dummyDOM
        +            .querySelector(`#${cnvId}accessibleOutput`)
        +            .insertAdjacentHTML('beforebegin', html);
        +        }
        +      } else if (!this.dummyDOM.querySelector('#' + cnvId + fallbackTableId)) {
        +        //if describe() has already created the container and added a description
        +        //and there is no table create fallback table for element description after
        +        //fallback description
                 this.dummyDOM
        -          .querySelector(`#${cnvId}accessibleOutput`)
        -          .insertAdjacentHTML('beforebegin', html);
        +          .querySelector('#' + cnvId + fallbackDescId)
        +          .insertAdjacentHTML(
        +            'afterend',
        +            `<table id="${cnvId}${fallbackTableId}"><caption>Canvas elements and their descriptions</caption></table>`
        +          );
               }
        -    } else if (!this.dummyDOM.querySelector('#' + cnvId + fallbackTableId)) {
        -      //if describe() has already created the container and added a description
        -      //and there is no table create fallback table for element description after
        -      //fallback description
        +      //create a table row for the element
        +      let tableRow = document.createElement('tr');
        +      tableRow.id = cnvId + fallbackTableElId + name;
               this.dummyDOM
        -        .querySelector('#' + cnvId + fallbackDescId)
        -        .insertAdjacentHTML(
        -          'afterend',
        -          `<table id="${cnvId}${fallbackTableId}"><caption>Canvas elements and their descriptions</caption></table>`
        -        );
        -    }
        -    //create a table row for the element
        -    let tableRow = document.createElement('tr');
        -    tableRow.id = cnvId + fallbackTableElId + name;
        -    this.dummyDOM
        -      .querySelector('#' + cnvId + fallbackTableId)
        -      .appendChild(tableRow);
        -    //update element description
        -    this.descriptions.fallbackElements[name] = this.dummyDOM.querySelector(
        -      `#${cnvId}${fallbackTableElId}${name}`
        -    );
        -    this.descriptions.fallbackElements[name].innerHTML = text;
        -    return;
        -  } else if (type === 'label') {
        -    //If display is LABEL creates a div adjacent to the canvas element with
        -    //a table, a row header cell with the name of the elements,
        -    //and adds the description of the element in adjacent cell.
        -    //if there is no label description container
        -    if (!this.dummyDOM.querySelector(`#${cnvId + labelContainer}`)) {
        -      //if there are no accessible outputs (see textOutput() and gridOutput())
        -      let html = `<div id="${cnvId}${labelContainer}" class="p5Label"><table id="${cnvId}${labelTableId}"></table></div>`;
        -      if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutputLabel`)) {
        -        //create container + table for element descriptions
        -        this.dummyDOM
        -          .querySelector('#' + cnvId)
        -          .insertAdjacentHTML('afterend', html);
        -      } else {
        -        //create container + table for element descriptions before outputs
        +        .querySelector('#' + cnvId + fallbackTableId)
        +        .appendChild(tableRow);
        +      //update element description
        +      this.descriptions.fallbackElements[name] = this.dummyDOM.querySelector(
        +        `#${cnvId}${fallbackTableElId}${name}`
        +      );
        +      this.descriptions.fallbackElements[name].innerHTML = text;
        +      return;
        +    } else if (type === 'label') {
        +      //If display is LABEL creates a div adjacent to the canvas element with
        +      //a table, a row header cell with the name of the elements,
        +      //and adds the description of the element in adjacent cell.
        +      //if there is no label description container
        +      if (!this.dummyDOM.querySelector(`#${cnvId + labelContainer}`)) {
        +        //if there are no accessible outputs (see textOutput() and gridOutput())
        +        let html = `<div id="${cnvId}${labelContainer}" class="p5Label"><table id="${cnvId}${labelTableId}"></table></div>`;
        +        if (!this.dummyDOM.querySelector(`#${cnvId}accessibleOutputLabel`)) {
        +          //create container + table for element descriptions
        +          this.dummyDOM
        +            .querySelector('#' + cnvId)
        +            .insertAdjacentHTML('afterend', html);
        +        } else {
        +          //create container + table for element descriptions before outputs
        +          this.dummyDOM
        +            .querySelector(`#${cnvId}accessibleOutputLabel`)
        +            .insertAdjacentHTML('beforebegin', html);
        +        }
        +      } else if (!this.dummyDOM.querySelector(`#${cnvId + labelTableId}`)) {
        +        //if describe() has already created the label container and added a description
        +        //and there is no table create label table for element description after
        +        //label description
                 this.dummyDOM
        -          .querySelector(`#${cnvId}accessibleOutputLabel`)
        -          .insertAdjacentHTML('beforebegin', html);
        +          .querySelector('#' + cnvId + labelDescId)
        +          .insertAdjacentHTML(
        +            'afterend',
        +            `<table id="${cnvId + labelTableId}"></table>`
        +          );
               }
        -    } else if (!this.dummyDOM.querySelector(`#${cnvId + labelTableId}`)) {
        -      //if describe() has already created the label container and added a description
        -      //and there is no table create label table for element description after
        -      //label description
        +      //create a table row for the element label description
        +      let tableRow = document.createElement('tr');
        +      tableRow.id = cnvId + labelTableElId + name;
               this.dummyDOM
        -        .querySelector('#' + cnvId + labelDescId)
        -        .insertAdjacentHTML(
        -          'afterend',
        -          `<table id="${cnvId + labelTableId}"></table>`
        -        );
        +        .querySelector('#' + cnvId + labelTableId)
        +        .appendChild(tableRow);
        +      //update element label description
        +      this.descriptions.labelElements[name] = this.dummyDOM.querySelector(
        +        `#${cnvId}${labelTableElId}${name}`
        +      );
        +      this.descriptions.labelElements[name].innerHTML = text;
             }
        -    //create a table row for the element label description
        -    let tableRow = document.createElement('tr');
        -    tableRow.id = cnvId + labelTableElId + name;
        -    this.dummyDOM
        -      .querySelector('#' + cnvId + labelTableId)
        -      .appendChild(tableRow);
        -    //update element label description
        -    this.descriptions.labelElements[name] = this.dummyDOM.querySelector(
        -      `#${cnvId}${labelTableElId}${name}`
        -    );
        -    this.descriptions.labelElements[name].innerHTML = text;
        -  }
        -};
        +  };
        +}
        +
        +export default describe;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  describe(p5, p5.prototype);
        +}
        diff --git a/src/accessibility/gridOutput.js b/src/accessibility/gridOutput.js
        index 5b52a23eb8..a7e5513a41 100644
        --- a/src/accessibility/gridOutput.js
        +++ b/src/accessibility/gridOutput.js
        @@ -4,151 +4,156 @@
          * @for p5
          * @requires core
          */
        -import p5 from '../core/main';
         
        -//the functions in this file support updating the grid output
        +function gridOutput(p5, fn){
        +  //the functions in this file support updating the grid output
         
        -//updates gridOutput
        -p5.prototype._updateGridOutput = function(idT) {
        -  //if html structure is not there yet
        -  if (!this.dummyDOM.querySelector(`#${idT}_summary`)) {
        -    return;
        -  }
        -  let current = this._accessibleOutputs[idT];
        -  //create shape details list
        -  let innerShapeDetails = _gridShapeDetails(idT, this.ingredients.shapes);
        -  //create summary
        -  let innerSummary = _gridSummary(
        -    innerShapeDetails.numShapes,
        -    this.ingredients.colors.background,
        -    this.width,
        -    this.height
        -  );
        -  //create grid map
        -  let innerMap = _gridMap(idT, this.ingredients.shapes);
        -  //if it is different from current summary
        -  if (innerSummary !== current.summary.innerHTML) {
        -    //update
        -    current.summary.innerHTML = innerSummary;
        -  }
        -  //if it is different from current map
        -  if (innerMap !== current.map.innerHTML) {
        -    //update
        -    current.map.innerHTML = innerMap;
        -  }
        -  //if it is different from current shape details
        -  if (innerShapeDetails.details !== current.shapeDetails.innerHTML) {
        -    //update
        -    current.shapeDetails.innerHTML = innerShapeDetails.details;
        -  }
        -  this._accessibleOutputs[idT] = current;
        -};
        -
        -//creates spatial grid that maps the location of shapes
        -function _gridMap(idT, ingredients) {
        -  let shapeNumber = 0;
        -  let table = '';
        -  //create an array of arrays 10*10 of empty cells
        -  let cells = Array.from(Array(10), () => Array(10));
        -  for (let x in ingredients) {
        -    for (let y in ingredients[x]) {
        -      let fill;
        -      if (x !== 'line') {
        -        fill = `<a href="#${idT}shape${shapeNumber}">${
        -          ingredients[x][y].color
        -        } ${x}</a>`;
        -      } else {
        -        fill = `<a href="#${idT}shape${shapeNumber}">${
        -          ingredients[x][y].color
        -        } ${x} midpoint</a>`;
        -      }
        +  //updates gridOutput
        +  fn._updateGridOutput = function(idT) {
        +    //if html structure is not there yet
        +    if (!this.dummyDOM.querySelector(`#${idT}_summary`)) {
        +      return;
        +    }
        +    let current = this._accessibleOutputs[idT];
        +    //create shape details list
        +    let innerShapeDetails = _gridShapeDetails(idT, this.ingredients.shapes);
        +    //create summary
        +    let innerSummary = _gridSummary(
        +      innerShapeDetails.numShapes,
        +      this.ingredients.colors.background,
        +      this.width,
        +      this.height
        +    );
        +    //create grid map
        +    let innerMap = _gridMap(idT, this.ingredients.shapes);
        +    //if it is different from current summary
        +    if (innerSummary !== current.summary.innerHTML) {
        +      //update
        +      current.summary.innerHTML = innerSummary;
        +    }
        +    //if it is different from current map
        +    if (innerMap !== current.map.innerHTML) {
        +      //update
        +      current.map.innerHTML = innerMap;
        +    }
        +    //if it is different from current shape details
        +    if (innerShapeDetails.details !== current.shapeDetails.innerHTML) {
        +      //update
        +      current.shapeDetails.innerHTML = innerShapeDetails.details;
        +    }
        +    this._accessibleOutputs[idT] = current;
        +  };
         
        -      // Check if shape is in canvas, skip if not
        -      if(
        -        ingredients[x][y].loc.locY < cells.length &&
        -        ingredients[x][y].loc.locX < cells[ingredients[x][y].loc.locY].length
        -      ){
        -        //if empty cell of location of shape is undefined
        -        if (!cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX]) {
        -          //fill it with shape info
        -          cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] = fill;
        -          //if a shape is already in that location
        +  //creates spatial grid that maps the location of shapes
        +  function _gridMap(idT, ingredients) {
        +    let shapeNumber = 0;
        +    let table = '';
        +    //create an array of arrays 10*10 of empty cells
        +    let cells = Array.from(Array(10), () => Array(10));
        +    for (let x in ingredients) {
        +      for (let y in ingredients[x]) {
        +        let fill;
        +        if (x !== 'line') {
        +          fill = `<a href="#${idT}shape${shapeNumber}">${
        +            ingredients[x][y].color
        +          } ${x}</a>`;
                 } else {
        -          //add it
        -          cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] =
        -            cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] +
        -            '  ' +
        -            fill;
        +          fill = `<a href="#${idT}shape${shapeNumber}">${
        +            ingredients[x][y].color
        +          } ${x} midpoint</a>`;
        +        }
        +
        +        // Check if shape is in canvas, skip if not
        +        if(
        +          ingredients[x][y].loc.locY < cells.length &&
        +          ingredients[x][y].loc.locX < cells[ingredients[x][y].loc.locY].length
        +        ){
        +          //if empty cell of location of shape is undefined
        +          if (!cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX]) {
        +            //fill it with shape info
        +            cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] = fill;
        +            //if a shape is already in that location
        +          } else {
        +            //add it
        +            cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] =
        +              cells[ingredients[x][y].loc.locY][ingredients[x][y].loc.locX] +
        +              '  ' +
        +              fill;
        +          }
        +          shapeNumber++;
                 }
        -        shapeNumber++;
               }
             }
        -  }
        -  //make table based on array
        -  for (let _r in cells) {
        -    let row = '<tr>';
        -    for (let c in cells[_r]) {
        -      row = row + '<td>';
        -      if (cells[_r][c] !== undefined) {
        -        row = row + cells[_r][c];
        +    //make table based on array
        +    for (let _r in cells) {
        +      let row = '<tr>';
        +      for (let c in cells[_r]) {
        +        row = row + '<td>';
        +        if (cells[_r][c] !== undefined) {
        +          row = row + cells[_r][c];
        +        }
        +        row = row + '</td>';
               }
        -      row = row + '</td>';
        +      table = table + row + '</tr>';
             }
        -    table = table + row + '</tr>';
        +    return table;
           }
        -  return table;
        -}
         
        -//creates grid summary
        -function _gridSummary(numShapes, background, width, height) {
        -  let text = `${background} canvas, ${width} by ${height} pixels, contains ${
        -    numShapes[0]
        -  }`;
        -  if (numShapes[0] === 1) {
        -    text = `${text} shape: ${numShapes[1]}`;
        -  } else {
        -    text = `${text} shapes: ${numShapes[1]}`;
        +  //creates grid summary
        +  function _gridSummary(numShapes, background, width, height) {
        +    let text = `${background} canvas, ${width} by ${height} pixels, contains ${
        +      numShapes[0]
        +    }`;
        +    if (numShapes[0] === 1) {
        +      text = `${text} shape: ${numShapes[1]}`;
        +    } else {
        +      text = `${text} shapes: ${numShapes[1]}`;
        +    }
        +    return text;
           }
        -  return text;
        -}
         
        -//creates list of shapes
        -function _gridShapeDetails(idT, ingredients) {
        -  let shapeDetails = '';
        -  let shapes = '';
        -  let totalShapes = 0;
        -  //goes trhough every shape type in ingredients
        -  for (let x in ingredients) {
        -    let shapeNum = 0;
        -    for (let y in ingredients[x]) {
        -      //it creates a line in a list
        -      let line = `<li id="${idT}shape${totalShapes}">${
        -        ingredients[x][y].color
        -      } ${x},`;
        -      if (x === 'line') {
        -        line =
        -          line +
        -          ` location = ${ingredients[x][y].pos}, length = ${
        -            ingredients[x][y].length
        -          } pixels`;
        -      } else {
        -        line = line + ` location = ${ingredients[x][y].pos}`;
        -        if (x !== 'point') {
        -          line = line + `, area = ${ingredients[x][y].area} %`;
        +  //creates list of shapes
        +  function _gridShapeDetails(idT, ingredients) {
        +    let shapeDetails = '';
        +    let shapes = '';
        +    let totalShapes = 0;
        +    //goes trhough every shape type in ingredients
        +    for (let x in ingredients) {
        +      let shapeNum = 0;
        +      for (let y in ingredients[x]) {
        +        //it creates a line in a list
        +        let line = `<li id="${idT}shape${totalShapes}">${
        +          ingredients[x][y].color
        +        } ${x},`;
        +        if (x === 'line') {
        +          line =
        +            line +
        +            ` location = ${ingredients[x][y].pos}, length = ${
        +              ingredients[x][y].length
        +            } pixels`;
        +        } else {
        +          line = line + ` location = ${ingredients[x][y].pos}`;
        +          if (x !== 'point') {
        +            line = line + `, area = ${ingredients[x][y].area} %`;
        +          }
        +          line = line + '</li>';
                 }
        -        line = line + '</li>';
        +        shapeDetails = shapeDetails + line;
        +        shapeNum++;
        +        totalShapes++;
        +      }
        +      if (shapeNum > 1) {
        +        shapes = `${shapes} ${shapeNum} ${x}s`;
        +      } else {
        +        shapes = `${shapes} ${shapeNum} ${x}`;
               }
        -      shapeDetails = shapeDetails + line;
        -      shapeNum++;
        -      totalShapes++;
        -    }
        -    if (shapeNum > 1) {
        -      shapes = `${shapes} ${shapeNum} ${x}s`;
        -    } else {
        -      shapes = `${shapes} ${shapeNum} ${x}`;
             }
        +    return { numShapes: [totalShapes, shapes], details: shapeDetails };
           }
        -  return { numShapes: [totalShapes, shapes], details: shapeDetails };
         }
         
        -export default p5;
        +export default gridOutput;
        +
        +if(typeof p5 !== 'undefined'){
        +  gridOutput(p5, p5.prototype);
        +}
        diff --git a/src/accessibility/index.js b/src/accessibility/index.js
        new file mode 100644
        index 0000000000..4469b4fdef
        --- /dev/null
        +++ b/src/accessibility/index.js
        @@ -0,0 +1,13 @@
        +import describe from './describe.js';
        +import gridOutput from './gridOutput.js';
        +import textOutput from './textOutput.js';
        +import outputs from './outputs.js';
        +import colorNamer from './color_namer.js';
        +
        +export default function(p5){
        +  p5.registerAddon(describe);
        +  p5.registerAddon(gridOutput);
        +  p5.registerAddon(textOutput);
        +  p5.registerAddon(outputs);
        +  p5.registerAddon(colorNamer);
        +}
        diff --git a/src/accessibility/outputs.js b/src/accessibility/outputs.js
        index 4cdb308c27..44b568478c 100644
        --- a/src/accessibility/outputs.js
        +++ b/src/accessibility/outputs.js
        @@ -5,684 +5,689 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        -
        -/**
        - * Creates a screen reader-accessible description of shapes on the canvas.
        - *
        - * `textOutput()` adds a general description, list of shapes, and
        - * table of shapes to the web page. The general description includes the
        - * canvas size, canvas color, and number of shapes. For example,
        - * `Your output is a, 100 by 100 pixels, gray canvas containing the following 2 shapes:`.
        - *
        - * A list of shapes follows the general description. The list describes the
        - * color, location, and area of each shape. For example,
        - * `a red circle at middle covering 3% of the canvas`. Each shape can be
        - * selected to get more details.
        - *
        - * `textOutput()` uses its table of shapes as a list. The table describes the
        - * shape, color, location, coordinates and area. For example,
        - * `red circle location = middle area = 3%`. This is different from
        - * <a href="#/p5/gridOutput">gridOutput()</a>, which uses its table as a grid.
        - *
        - * The `display` parameter is optional. It determines how the description is
        - * displayed. If `LABEL` is passed, as in `textOutput(LABEL)`, the description
        - * will be visible in a div element next to the canvas. Using `LABEL` creates
        - * unhelpful duplicates for screen readers. Only use `LABEL` during
        - * development. If `FALLBACK` is passed, as in `textOutput(FALLBACK)`, the
        - * description will only be visible to screen readers. This is the default
        - * mode.
        - *
        - * Read
        - * <a href="https://p5js.org/tutorials/writing-accessible-canvas-descriptions/">Writing accessible canvas descriptions</a>
        - * to learn more about making sketches accessible.
        - *
        - * @method textOutput
        - * @param  {Constant} [display] either FALLBACK or LABEL.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Add the text description.
        - *   textOutput();
        - *
        - *   // Draw a couple of shapes.
        - *   background(200);
        - *   fill(255, 0, 0);
        - *   circle(20, 20, 20);
        - *   fill(0, 0, 255);
        - *   square(50, 50, 50);
        - *
        - *   // Add a general description of the canvas.
        - *   describe('A red circle and a blue square on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Add the text description and
        - *   // display it for debugging.
        - *   textOutput(LABEL);
        - *
        - *   // Draw a couple of shapes.
        - *   background(200);
        - *   fill(255, 0, 0);
        - *   circle(20, 20, 20);
        - *   fill(0, 0, 255);
        - *   square(50, 50, 50);
        - *
        - *   // Add a general description of the canvas.
        - *   describe('A red circle and a blue square on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function draw() {
        - *   // Add the text description.
        - *   textOutput();
        - *
        - *   // Draw a moving circle.
        - *   background(200);
        - *   let x = frameCount * 0.1;
        - *   fill(255, 0, 0);
        - *   circle(x, 20, 20);
        - *   fill(0, 0, 255);
        - *   square(50, 50, 50);
        - *
        - *   // Add a general description of the canvas.
        - *   describe('A red circle moves from left to right above a blue square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function draw() {
        - *   // Add the text description and
        - *   // display it for debugging.
        - *   textOutput(LABEL);
        - *
        - *   // Draw a moving circle.
        - *   background(200);
        - *   let x = frameCount * 0.1;
        - *   fill(255, 0, 0);
        - *   circle(x, 20, 20);
        - *   fill(0, 0, 255);
        - *   square(50, 50, 50);
        - *
        - *   // Add a general description of the canvas.
        - *   describe('A red circle moves from left to right above a blue square.');
        - * }
        - * </code>
        - * </div>
        - */
        -
        -p5.prototype.textOutput = function(display) {
        -  p5._validateParameters('textOutput', arguments);
        -  //if textOutput is already true
        -  if (this._accessibleOutputs.text) {
        -    return;
        -  } else {
        -    //make textOutput true
        -    this._accessibleOutputs.text = true;
        -    //create output for fallback
        -    this._createOutput('textOutput', 'Fallback');
        -    if (display === this.LABEL) {
        -      //make textOutput label true
        -      this._accessibleOutputs.textLabel = true;
        -      //create output for label
        -      this._createOutput('textOutput', 'Label');
        +function outputs(p5, fn){
        +  /**
        +   * Creates a screen reader-accessible description of shapes on the canvas.
        +   *
        +   * `textOutput()` adds a general description, list of shapes, and
        +   * table of shapes to the web page. The general description includes the
        +   * canvas size, canvas color, and number of shapes. For example,
        +   * `Your output is a, 100 by 100 pixels, gray canvas containing the following 2 shapes:`.
        +   *
        +   * A list of shapes follows the general description. The list describes the
        +   * color, location, and area of each shape. For example,
        +   * `a red circle at middle covering 3% of the canvas`. Each shape can be
        +   * selected to get more details.
        +   *
        +   * `textOutput()` uses its table of shapes as a list. The table describes the
        +   * shape, color, location, coordinates and area. For example,
        +   * `red circle location = middle area = 3%`. This is different from
        +   * <a href="#/p5/gridOutput">gridOutput()</a>, which uses its table as a grid.
        +   *
        +   * The `display` parameter is optional. It determines how the description is
        +   * displayed. If `LABEL` is passed, as in `textOutput(LABEL)`, the description
        +   * will be visible in a div element next to the canvas. Using `LABEL` creates
        +   * unhelpful duplicates for screen readers. Only use `LABEL` during
        +   * development. If `FALLBACK` is passed, as in `textOutput(FALLBACK)`, the
        +   * description will only be visible to screen readers. This is the default
        +   * mode.
        +   *
        +   * Read
        +   * <a href="/learn/accessible-labels.html">Writing accessible canvas descriptions</a>
        +   * to learn more about making sketches accessible.
        +   *
        +   * @method textOutput
        +   * @param  {(FALLBACK|LABEL)} [display] either FALLBACK or LABEL.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Add the text description.
        +   *   textOutput();
        +   *
        +   *   // Draw a couple of shapes.
        +   *   background(200);
        +   *   fill(255, 0, 0);
        +   *   circle(20, 20, 20);
        +   *   fill(0, 0, 255);
        +   *   square(50, 50, 50);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe('A red circle and a blue square on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Add the text description and
        +   *   // display it for debugging.
        +   *   textOutput(LABEL);
        +   *
        +   *   // Draw a couple of shapes.
        +   *   background(200);
        +   *   fill(255, 0, 0);
        +   *   circle(20, 20, 20);
        +   *   fill(0, 0, 255);
        +   *   square(50, 50, 50);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe('A red circle and a blue square on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function draw() {
        +   *   // Add the text description.
        +   *   textOutput();
        +   *
        +   *   // Draw a moving circle.
        +   *   background(200);
        +   *   let x = frameCount * 0.1;
        +   *   fill(255, 0, 0);
        +   *   circle(x, 20, 20);
        +   *   fill(0, 0, 255);
        +   *   square(50, 50, 50);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe('A red circle moves from left to right above a blue square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function draw() {
        +   *   // Add the text description and
        +   *   // display it for debugging.
        +   *   textOutput(LABEL);
        +   *
        +   *   // Draw a moving circle.
        +   *   background(200);
        +   *   let x = frameCount * 0.1;
        +   *   fill(255, 0, 0);
        +   *   circle(x, 20, 20);
        +   *   fill(0, 0, 255);
        +   *   square(50, 50, 50);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe('A red circle moves from left to right above a blue square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.textOutput = function(display) {
        +    // p5._validateParameters('textOutput', arguments);
        +    //if textOutput is already true
        +    if (this._accessibleOutputs.text) {
        +      return;
        +    } else {
        +      //make textOutput true
        +      this._accessibleOutputs.text = true;
        +      //create output for fallback
        +      this._createOutput('textOutput', 'Fallback');
        +      if (display === this.LABEL) {
        +        //make textOutput label true
        +        this._accessibleOutputs.textLabel = true;
        +        //create output for label
        +        this._createOutput('textOutput', 'Label');
        +      }
             }
        -  }
        -};
        +  };
         
        -/**
        - * Creates a screen reader-accessible description of shapes on the canvas.
        - *
        - * `gridOutput()` adds a general description, table of shapes, and list of
        - * shapes to the web page. The general description includes the canvas size,
        - * canvas color, and number of shapes. For example,
        - * `gray canvas, 100 by 100 pixels, contains 2 shapes:  1 circle 1 square`.
        - *
        - * `gridOutput()` uses its table of shapes as a grid. Each shape in the grid
        - * is placed in a cell whose row and column correspond to the shape's location
        - * on the canvas. The grid cells describe the color and type of shape at that
        - * location. For example, `red circle`. These descriptions can be selected
        - * individually to get more details. This is different from
        - * <a href="#/p5/textOutput">textOutput()</a>, which uses its table as a list.
        - *
        - * A list of shapes follows the table. The list describes the color, type,
        - * location, and area of each shape. For example,
        - * `red circle, location = middle, area = 3 %`.
        - *
        - * The `display` parameter is optional. It determines how the description is
        - * displayed. If `LABEL` is passed, as in `gridOutput(LABEL)`, the description
        - * will be visible in a div element next to the canvas. Using `LABEL` creates
        - * unhelpful duplicates for screen readers. Only use `LABEL` during
        - * development. If `FALLBACK` is passed, as in `gridOutput(FALLBACK)`, the
        - * description will only be visible to screen readers. This is the default
        - * mode.
        - *
        - * Read
        - * <a href="https://p5js.org/tutorials/writing-accessible-canvas-descriptions/">Writing accessible canvas descriptions</a>
        - * to learn more about making sketches accessible.
        - *
        - * @method gridOutput
        - * @param  {Constant} [display] either FALLBACK or LABEL.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Add the grid description.
        - *   gridOutput();
        - *
        - *   // Draw a couple of shapes.
        - *   background(200);
        - *   fill(255, 0, 0);
        - *   circle(20, 20, 20);
        - *   fill(0, 0, 255);
        - *   square(50, 50, 50);
        - *
        - *   // Add a general description of the canvas.
        - *   describe('A red circle and a blue square on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Add the grid description and
        - *   // display it for debugging.
        - *   gridOutput(LABEL);
        - *
        - *   // Draw a couple of shapes.
        - *   background(200);
        - *   fill(255, 0, 0);
        - *   circle(20, 20, 20);
        - *   fill(0, 0, 255);
        - *   square(50, 50, 50);
        - *
        - *   // Add a general description of the canvas.
        - *   describe('A red circle and a blue square on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function draw() {
        - *   // Add the grid description.
        - *   gridOutput();
        - *
        - *   // Draw a moving circle.
        - *   background(200);
        - *   let x = frameCount * 0.1;
        - *   fill(255, 0, 0);
        - *   circle(x, 20, 20);
        - *   fill(0, 0, 255);
        - *   square(50, 50, 50);
        - *
        - *   // Add a general description of the canvas.
        - *   describe('A red circle moves from left to right above a blue square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function draw() {
        - *   // Add the grid description and
        - *   // display it for debugging.
        - *   gridOutput(LABEL);
        - *
        - *   // Draw a moving circle.
        - *   background(200);
        - *   let x = frameCount * 0.1;
        - *   fill(255, 0, 0);
        - *   circle(x, 20, 20);
        - *   fill(0, 0, 255);
        - *   square(50, 50, 50);
        - *
        - *   // Add a general description of the canvas.
        - *   describe('A red circle moves from left to right above a blue square.');
        - * }
        - * </code>
        - * </div>
        - */
        +  /**
        +   * Creates a screen reader-accessible description of shapes on the canvas.
        +   *
        +   * `gridOutput()` adds a general description, table of shapes, and list of
        +   * shapes to the web page. The general description includes the canvas size,
        +   * canvas color, and number of shapes. For example,
        +   * `gray canvas, 100 by 100 pixels, contains 2 shapes:  1 circle 1 square`.
        +   *
        +   * `gridOutput()` uses its table of shapes as a grid. Each shape in the grid
        +   * is placed in a cell whose row and column correspond to the shape's location
        +   * on the canvas. The grid cells describe the color and type of shape at that
        +   * location. For example, `red circle`. These descriptions can be selected
        +   * individually to get more details. This is different from
        +   * <a href="#/p5/textOutput">textOutput()</a>, which uses its table as a list.
        +   *
        +   * A list of shapes follows the table. The list describes the color, type,
        +   * location, and area of each shape. For example,
        +   * `red circle, location = middle, area = 3 %`.
        +   *
        +   * The `display` parameter is optional. It determines how the description is
        +   * displayed. If `LABEL` is passed, as in `gridOutput(LABEL)`, the description
        +   * will be visible in a div element next to the canvas. Using `LABEL` creates
        +   * unhelpful duplicates for screen readers. Only use `LABEL` during
        +   * development. If `FALLBACK` is passed, as in `gridOutput(FALLBACK)`, the
        +   * description will only be visible to screen readers. This is the default
        +   * mode.
        +   *
        +   * Read
        +   * <a href="/learn/accessible-labels.html">Writing accessible canvas descriptions</a>
        +   * to learn more about making sketches accessible.
        +   *
        +   * @method gridOutput
        +   * @param  {(FALLBACK|LABEL)} [display] either FALLBACK or LABEL.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Add the grid description.
        +   *   gridOutput();
        +   *
        +   *   // Draw a couple of shapes.
        +   *   background(200);
        +   *   fill(255, 0, 0);
        +   *   circle(20, 20, 20);
        +   *   fill(0, 0, 255);
        +   *   square(50, 50, 50);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe('A red circle and a blue square on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Add the grid description and
        +   *   // display it for debugging.
        +   *   gridOutput(LABEL);
        +   *
        +   *   // Draw a couple of shapes.
        +   *   background(200);
        +   *   fill(255, 0, 0);
        +   *   circle(20, 20, 20);
        +   *   fill(0, 0, 255);
        +   *   square(50, 50, 50);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe('A red circle and a blue square on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function draw() {
        +   *   // Add the grid description.
        +   *   gridOutput();
        +   *
        +   *   // Draw a moving circle.
        +   *   background(200);
        +   *   let x = frameCount * 0.1;
        +   *   fill(255, 0, 0);
        +   *   circle(x, 20, 20);
        +   *   fill(0, 0, 255);
        +   *   square(50, 50, 50);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe('A red circle moves from left to right above a blue square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function draw() {
        +   *   // Add the grid description and
        +   *   // display it for debugging.
        +   *   gridOutput(LABEL);
        +   *
        +   *   // Draw a moving circle.
        +   *   background(200);
        +   *   let x = frameCount * 0.1;
        +   *   fill(255, 0, 0);
        +   *   circle(x, 20, 20);
        +   *   fill(0, 0, 255);
        +   *   square(50, 50, 50);
        +   *
        +   *   // Add a general description of the canvas.
        +   *   describe('A red circle moves from left to right above a blue square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -p5.prototype.gridOutput = function(display) {
        -  p5._validateParameters('gridOutput', arguments);
        -  //if gridOutput is already true
        -  if (this._accessibleOutputs.grid) {
        -    return;
        -  } else {
        -    //make gridOutput true
        -    this._accessibleOutputs.grid = true;
        -    //create output for fallback
        -    this._createOutput('gridOutput', 'Fallback');
        -    if (display === this.LABEL) {
        -      //make gridOutput label true
        -      this._accessibleOutputs.gridLabel = true;
        -      //create output for label
        -      this._createOutput('gridOutput', 'Label');
        +  fn.gridOutput = function(display) {
        +    // p5._validateParameters('gridOutput', arguments);
        +    //if gridOutput is already true
        +    if (this._accessibleOutputs.grid) {
        +      return;
        +    } else {
        +      //make gridOutput true
        +      this._accessibleOutputs.grid = true;
        +      //create output for fallback
        +      this._createOutput('gridOutput', 'Fallback');
        +      if (display === this.LABEL) {
        +        //make gridOutput label true
        +        this._accessibleOutputs.gridLabel = true;
        +        //create output for label
        +        this._createOutput('gridOutput', 'Label');
        +      }
             }
        -  }
        -};
        +  };
         
        -//helper function returns true when accessible outputs are true
        -p5.prototype._addAccsOutput = function() {
        -  //if there are no accessible outputs create object with all false
        -  if (!this._accessibleOutputs) {
        -    this._accessibleOutputs = {
        -      text: false,
        -      grid: false,
        -      textLabel: false,
        -      gridLabel: false
        -    };
        -  }
        -  return this._accessibleOutputs.grid || this._accessibleOutputs.text;
        -};
        +  //helper function returns true when accessible outputs are true
        +  fn._addAccsOutput = function() {
        +    //if there are no accessible outputs create object with all false
        +    if (!this._accessibleOutputs) {
        +      this._accessibleOutputs = {
        +        text: false,
        +        grid: false,
        +        textLabel: false,
        +        gridLabel: false
        +      };
        +    }
        +    return this._accessibleOutputs.grid || this._accessibleOutputs.text;
        +  };
         
        -//helper function that creates html structure for accessible outputs
        -p5.prototype._createOutput = function(type, display) {
        -  let cnvId = this.canvas.id;
        -  //if there are no ingredients create object. this object stores data for the outputs
        -  if (!this.ingredients) {
        -    this.ingredients = {
        -      shapes: {},
        -      colors: { background: 'white', fill: 'white', stroke: 'black' },
        -      pShapes: '',
        -      pBackground: ''
        -    };
        -  }
        -  //if there is no dummyDOM create it
        -  if (!this.dummyDOM) {
        -    this.dummyDOM = document.getElementById(cnvId).parentNode;
        -  }
        -  let cIdT, container, inner;
        -  let query = '';
        -  if (display === 'Fallback') {
        -    cIdT = cnvId + type;
        -    container = cnvId + 'accessibleOutput';
        -    if (!this.dummyDOM.querySelector(`#${container}`)) {
        -      //if there is no canvas description (see describe() and describeElement())
        -      if (!this.dummyDOM.querySelector(`#${cnvId}_Description`)) {
        -        //create html structure inside of canvas
        -        this.dummyDOM.querySelector(
        -          `#${cnvId}`
        -        ).innerHTML = `<div id="${container}" role="region" aria-label="Canvas Outputs"></div>`;
        -      } else {
        -        //create html structure after canvas description container
        -        this.dummyDOM
        -          .querySelector(`#${cnvId}_Description`)
        -          .insertAdjacentHTML(
        -            'afterend',
        -            `<div id="${container}" role="region" aria-label="Canvas Outputs"></div>`
        -          );
        +  //helper function that creates html structure for accessible outputs
        +  fn._createOutput = function(type, display) {
        +    let cnvId = this.canvas.id;
        +    //if there are no ingredients create object. this object stores data for the outputs
        +    if (!this.ingredients) {
        +      this.ingredients = {
        +        shapes: {},
        +        colors: { background: 'white', fill: 'white', stroke: 'black' },
        +        pShapes: '',
        +        pBackground: ''
        +      };
        +    }
        +    //if there is no dummyDOM create it
        +    if (!this.dummyDOM) {
        +      this.dummyDOM = document.getElementById(cnvId).parentNode;
        +    }
        +    let cIdT, container, inner;
        +    let query = '';
        +    if (display === 'Fallback') {
        +      cIdT = cnvId + type;
        +      container = cnvId + 'accessibleOutput';
        +      if (!this.dummyDOM.querySelector(`#${container}`)) {
        +        //if there is no canvas description (see describe() and describeElement())
        +        if (!this.dummyDOM.querySelector(`#${cnvId}_Description`)) {
        +          //create html structure inside of canvas
        +          this.dummyDOM.querySelector(
        +            `#${cnvId}`
        +          ).innerHTML = `<div id="${container}" role="region" aria-label="Canvas Outputs"></div>`;
        +        } else {
        +          //create html structure after canvas description container
        +          this.dummyDOM
        +            .querySelector(`#${cnvId}_Description`)
        +            .insertAdjacentHTML(
        +              'afterend',
        +              `<div id="${container}" role="region" aria-label="Canvas Outputs"></div>`
        +            );
        +        }
        +      }
        +    } else if (display === 'Label') {
        +      query = display;
        +      cIdT = cnvId + type + display;
        +      container = cnvId + 'accessibleOutput' + display;
        +      if (!this.dummyDOM.querySelector(`#${container}`)) {
        +        //if there is no canvas description label (see describe() and describeElement())
        +        if (!this.dummyDOM.querySelector(`#${cnvId}_Label`)) {
        +          //create html structure adjacent to canvas
        +          this.dummyDOM
        +            .querySelector(`#${cnvId}`)
        +            .insertAdjacentHTML('afterend', `<div id="${container}"></div>`);
        +        } else {
        +          //create html structure after canvas label
        +          this.dummyDOM
        +            .querySelector(`#${cnvId}_Label`)
        +            .insertAdjacentHTML('afterend', `<div id="${container}"></div>`);
        +        }
               }
             }
        -  } else if (display === 'Label') {
        -    query = display;
        -    cIdT = cnvId + type + display;
        -    container = cnvId + 'accessibleOutput' + display;
        -    if (!this.dummyDOM.querySelector(`#${container}`)) {
        -      //if there is no canvas description label (see describe() and describeElement())
        -      if (!this.dummyDOM.querySelector(`#${cnvId}_Label`)) {
        -        //create html structure adjacent to canvas
        +    //create an object to store the latest output. this object is used in _updateTextOutput() and _updateGridOutput()
        +    this._accessibleOutputs[cIdT] = {};
        +    if (type === 'textOutput') {
        +      query = `#${cnvId}gridOutput${query}`; //query is used to check if gridOutput already exists
        +      inner = `<div id="${cIdT}">Text Output<div id="${cIdT}Summary" aria-label="text output summary"><p id="${cIdT}_summary"></p><ul id="${cIdT}_list"></ul></div><table id="${cIdT}_shapeDetails" summary="text output shape details"></table></div>`;
        +      //if gridOutput already exists
        +      if (this.dummyDOM.querySelector(query)) {
        +        //create textOutput before gridOutput
                 this.dummyDOM
        -          .querySelector(`#${cnvId}`)
        -          .insertAdjacentHTML('afterend', `<div id="${container}"></div>`);
        +          .querySelector(query)
        +          .insertAdjacentHTML('beforebegin', inner);
               } else {
        -        //create html structure after canvas label
        -        this.dummyDOM
        -          .querySelector(`#${cnvId}_Label`)
        -          .insertAdjacentHTML('afterend', `<div id="${container}"></div>`);
        +        //create output inside of container
        +        this.dummyDOM.querySelector(`#${container}`).innerHTML = inner;
               }
        +      //store output html elements
        +      this._accessibleOutputs[cIdT].list = this.dummyDOM.querySelector(
        +        `#${cIdT}_list`
        +      );
        +    } else if (type === 'gridOutput') {
        +      query = `#${cnvId}textOutput${query}`; //query is used to check if textOutput already exists
        +      inner = `<div id="${cIdT}">Grid Output<p id="${cIdT}_summary" aria-label="grid output summary"><table id="${cIdT}_map" summary="grid output content"></table><ul id="${cIdT}_shapeDetails" aria-label="grid output shape details"></ul></div>`;
        +      //if textOutput already exists
        +      if (this.dummyDOM.querySelector(query)) {
        +        //create gridOutput after textOutput
        +        this.dummyDOM.querySelector(query).insertAdjacentHTML('afterend', inner);
        +      } else {
        +        //create output inside of container
        +        this.dummyDOM.querySelector(`#${container}`).innerHTML = inner;
        +      }
        +      //store output html elements
        +      this._accessibleOutputs[cIdT].map = this.dummyDOM.querySelector(
        +        `#${cIdT}_map`
        +      );
             }
        -  }
        -  //create an object to store the latest output. this object is used in _updateTextOutput() and _updateGridOutput()
        -  this._accessibleOutputs[cIdT] = {};
        -  if (type === 'textOutput') {
        -    query = `#${cnvId}gridOutput${query}`; //query is used to check if gridOutput already exists
        -    inner = `<div id="${cIdT}">Text Output<div id="${cIdT}Summary" aria-label="text output summary"><p id="${cIdT}_summary"></p><ul id="${cIdT}_list"></ul></div><table id="${cIdT}_shapeDetails" summary="text output shape details"></table></div>`;
        -    //if gridOutput already exists
        -    if (this.dummyDOM.querySelector(query)) {
        -      //create textOutput before gridOutput
        -      this.dummyDOM
        -        .querySelector(query)
        -        .insertAdjacentHTML('beforebegin', inner);
        -    } else {
        -      //create output inside of container
        -      this.dummyDOM.querySelector(`#${container}`).innerHTML = inner;
        -    }
        -    //store output html elements
        -    this._accessibleOutputs[cIdT].list = this.dummyDOM.querySelector(
        -      `#${cIdT}_list`
        +    this._accessibleOutputs[cIdT].shapeDetails = this.dummyDOM.querySelector(
        +      `#${cIdT}_shapeDetails`
             );
        -  } else if (type === 'gridOutput') {
        -    query = `#${cnvId}textOutput${query}`; //query is used to check if textOutput already exists
        -    inner = `<div id="${cIdT}">Grid Output<p id="${cIdT}_summary" aria-label="grid output summary"><table id="${cIdT}_map" summary="grid output content"></table><ul id="${cIdT}_shapeDetails" aria-label="grid output shape details"></ul></div>`;
        -    //if textOutput already exists
        -    if (this.dummyDOM.querySelector(query)) {
        -      //create gridOutput after textOutput
        -      this.dummyDOM.querySelector(query).insertAdjacentHTML('afterend', inner);
        -    } else {
        -      //create output inside of container
        -      this.dummyDOM.querySelector(`#${container}`).innerHTML = inner;
        -    }
        -    //store output html elements
        -    this._accessibleOutputs[cIdT].map = this.dummyDOM.querySelector(
        -      `#${cIdT}_map`
        +    this._accessibleOutputs[cIdT].summary = this.dummyDOM.querySelector(
        +      `#${cIdT}_summary`
             );
        -  }
        -  this._accessibleOutputs[cIdT].shapeDetails = this.dummyDOM.querySelector(
        -    `#${cIdT}_shapeDetails`
        -  );
        -  this._accessibleOutputs[cIdT].summary = this.dummyDOM.querySelector(
        -    `#${cIdT}_summary`
        -  );
        -};
        +  };
        +
        +  //this function is called at the end of setup and draw if using
        +  //accessibleOutputs and calls update functions of outputs
        +  fn._updateAccsOutput = function() {
        +    let cnvId = this.canvas.id;
        +    //if the shapes are not the same as before
        +    if (
        +      JSON.stringify(this.ingredients.shapes) !== this.ingredients.pShapes ||
        +      this.ingredients.colors.background !== this.ingredients.pBackground
        +    ) {
        +      //save current shapes as string in pShapes
        +      this.ingredients.pShapes = JSON.stringify(this.ingredients.shapes);
        +      if (this._accessibleOutputs.text) {
        +        this._updateTextOutput(cnvId + 'textOutput');
        +      }
        +      if (this._accessibleOutputs.grid) {
        +        this._updateGridOutput(cnvId + 'gridOutput');
        +      }
        +      if (this._accessibleOutputs.textLabel) {
        +        this._updateTextOutput(cnvId + 'textOutputLabel');
        +      }
        +      if (this._accessibleOutputs.gridLabel) {
        +        this._updateGridOutput(cnvId + 'gridOutputLabel');
        +      }
        +    }
        +  };
         
        -//this function is called at the end of setup and draw if using
        -//accessibleOutputs and calls update functions of outputs
        -p5.prototype._updateAccsOutput = function() {
        -  let cnvId = this.canvas.id;
        -  //if the shapes are not the same as before
        -  if (
        -    JSON.stringify(this.ingredients.shapes) !== this.ingredients.pShapes ||
        -    this.ingredients.colors.background !== this.ingredients.pBackground
        -  ) {
        +  //helper function that resets all ingredients when background is called
        +  //and saves background color name
        +  fn._accsBackground = function(args) {
             //save current shapes as string in pShapes
             this.ingredients.pShapes = JSON.stringify(this.ingredients.shapes);
        -    if (this._accessibleOutputs.text) {
        -      this._updateTextOutput(cnvId + 'textOutput');
        -    }
        -    if (this._accessibleOutputs.grid) {
        -      this._updateGridOutput(cnvId + 'gridOutput');
        +    this.ingredients.pBackground = this.ingredients.colors.background;
        +    //empty shapes JSON
        +    this.ingredients.shapes = {};
        +    //update background different
        +    if (this.ingredients.colors.backgroundRGBA !== args) {
        +      this.ingredients.colors.backgroundRGBA = args;
        +      this.ingredients.colors.background = this._rgbColorName(args);
             }
        -    if (this._accessibleOutputs.textLabel) {
        -      this._updateTextOutput(cnvId + 'textOutputLabel');
        -    }
        -    if (this._accessibleOutputs.gridLabel) {
        -      this._updateGridOutput(cnvId + 'gridOutputLabel');
        -    }
        -  }
        -};
        -
        -//helper function that resets all ingredients when background is called
        -//and saves background color name
        -p5.prototype._accsBackground = function(args) {
        -  //save current shapes as string in pShapes
        -  this.ingredients.pShapes = JSON.stringify(this.ingredients.shapes);
        -  this.ingredients.pBackground = this.ingredients.colors.background;
        -  //empty shapes JSON
        -  this.ingredients.shapes = {};
        -  //update background different
        -  if (this.ingredients.colors.backgroundRGBA !== args) {
        -    this.ingredients.colors.backgroundRGBA = args;
        -    this.ingredients.colors.background = this._rgbColorName(args);
        -  }
        -};
        +  };
         
        -//helper function that gets fill and stroke of shapes
        -p5.prototype._accsCanvasColors = function(f, args) {
        -  if (f === 'fill') {
        -    //update fill different
        -    if (this.ingredients.colors.fillRGBA !== args) {
        -      this.ingredients.colors.fillRGBA = args;
        -      this.ingredients.colors.fill = this._rgbColorName(args);
        -    }
        -  } else if (f === 'stroke') {
        -    //update stroke if different
        -    if (this.ingredients.colors.strokeRGBA !== args) {
        -      this.ingredients.colors.strokeRGBA = args;
        -      this.ingredients.colors.stroke = this._rgbColorName(args);
        +  //helper function that gets fill and stroke of shapes
        +  fn._accsCanvasColors = function(f, args) {
        +    if (f === 'fill') {
        +      //update fill different
        +      if (this.ingredients.colors.fillRGBA !== args) {
        +        this.ingredients.colors.fillRGBA = args;
        +        this.ingredients.colors.fill = this._rgbColorName(args);
        +      }
        +    } else if (f === 'stroke') {
        +      //update stroke if different
        +      if (this.ingredients.colors.strokeRGBA !== args) {
        +        this.ingredients.colors.strokeRGBA = args;
        +        this.ingredients.colors.stroke = this._rgbColorName(args);
        +      }
             }
        -  }
        -};
        +  };
         
        -//builds ingredients.shapes used for building outputs
        -p5.prototype._accsOutput = function(f, args) {
        -  if (f === 'ellipse' && args[2] === args[3]) {
        -    f = 'circle';
        -  } else if (f === 'rectangle' && args[2] === args[3]) {
        -    f = 'square';
        -  }
        -  let include = {};
        -  let add = true;
        -  let middle = _getMiddle(f, args);
        -  if (f === 'line') {
        -    //make color stroke
        -    include.color = this.ingredients.colors.stroke;
        -    //get lenght
        -    include.length = Math.round(this.dist(args[0], args[1], args[2], args[3]));
        -    //get position of end points
        -    let p1 = this._getPos(args[0], [1]);
        -    let p2 = this._getPos(args[2], [3]);
        -    include.loc = _canvasLocator(middle, this.width, this.height);
        -    if (p1 === p2) {
        -      include.pos = `at ${p1}`;
        -    } else {
        -      include.pos = `from ${p1} to ${p2}`;
        +  //builds ingredients.shapes used for building outputs
        +  fn._accsOutput = function(f, args) {
        +    if (f === 'ellipse' && args[2] === args[3]) {
        +      f = 'circle';
        +    } else if (f === 'rectangle' && args[2] === args[3]) {
        +      f = 'square';
             }
        -  } else {
        -    if (f === 'point') {
        +    let include = {};
        +    let add = true;
        +    let middle = _getMiddle(f, args);
        +    if (f === 'line') {
               //make color stroke
               include.color = this.ingredients.colors.stroke;
        +      //get lenght
        +      include.length = Math.round(
        +        Math.hypot(args[2] - args[0], args[3] - args[1])
        +      );
        +      //get position of end points
        +      let p1 = this._getPos(args[0], [1]);
        +      let p2 = this._getPos(args[2], [3]);
        +      include.loc = _canvasLocator(middle, this.width, this.height);
        +      if (p1 === p2) {
        +        include.pos = `at ${p1}`;
        +      } else {
        +        include.pos = `from ${p1} to ${p2}`;
        +      }
             } else {
        -      //make color fill
        -      include.color = this.ingredients.colors.fill;
        -      //get area of shape
        -      include.area = this._getArea(f, args);
        +      if (f === 'point') {
        +        //make color stroke
        +        include.color = this.ingredients.colors.stroke;
        +      } else {
        +        //make color fill
        +        include.color = this.ingredients.colors.fill;
        +        //get area of shape
        +        include.area = this._getArea(f, args);
        +      }
        +      //get middle of shapes
        +      //calculate position using middle of shape
        +      include.pos = this._getPos(...middle);
        +      //calculate location using middle of shape
        +      include.loc = _canvasLocator(middle, this.width, this.height);
             }
        -    //get middle of shapes
        -    //calculate position using middle of shape
        -    include.pos = this._getPos(...middle);
        -    //calculate location using middle of shape
        -    include.loc = _canvasLocator(middle, this.width, this.height);
        -  }
        -  //if it is the first time this shape is created
        -  if (!this.ingredients.shapes[f]) {
        -    this.ingredients.shapes[f] = [include];
        -    //if other shapes of this type have been created
        -  } else if (this.ingredients.shapes[f] !== [include]) {
        -    //for every shape of this type
        -    for (let y in this.ingredients.shapes[f]) {
        -      //compare it with current shape and if it already exists make add false
        -      if (
        -        JSON.stringify(this.ingredients.shapes[f][y]) ===
        -        JSON.stringify(include)
        -      ) {
        -        add = false;
        +    //if it is the first time this shape is created
        +    if (!this.ingredients.shapes[f]) {
        +      this.ingredients.shapes[f] = [include];
        +      //if other shapes of this type have been created
        +    } else if (this.ingredients.shapes[f] !== [include]) {
        +      //for every shape of this type
        +      for (let y in this.ingredients.shapes[f]) {
        +        //compare it with current shape and if it already exists make add false
        +        if (
        +          JSON.stringify(this.ingredients.shapes[f][y]) ===
        +          JSON.stringify(include)
        +        ) {
        +          add = false;
        +        }
        +      }
        +      //add shape by pushing it to the end
        +      if (add === true) {
        +        this.ingredients.shapes[f].push(include);
               }
             }
        -    //add shape by pushing it to the end
        -    if (add === true) {
        -      this.ingredients.shapes[f].push(include);
        +  };
        +
        +  //gets middle point / centroid of shape
        +  function _getMiddle(f, args) {
        +    let x, y;
        +    if (
        +      f === 'rectangle' ||
        +      f === 'ellipse' ||
        +      f === 'arc' ||
        +      f === 'circle' ||
        +      f === 'square'
        +    ) {
        +      x = Math.round(args[0] + args[2] / 2);
        +      y = Math.round(args[1] + args[3] / 2);
        +    } else if (f === 'triangle') {
        +      x = (args[0] + args[2] + args[4]) / 3;
        +      y = (args[1] + args[3] + args[5]) / 3;
        +    } else if (f === 'quadrilateral') {
        +      x = (args[0] + args[2] + args[4] + args[6]) / 4;
        +      y = (args[1] + args[3] + args[5] + args[7]) / 4;
        +    } else if (f === 'line') {
        +      x = (args[0] + args[2]) / 2;
        +      y = (args[1] + args[3]) / 2;
        +    } else {
        +      x = args[0];
        +      y = args[1];
             }
        +    return [x, y];
           }
        -};
         
        -//gets middle point / centroid of shape
        -function _getMiddle(f, args) {
        -  let x, y;
        -  if (
        -    f === 'rectangle' ||
        -    f === 'ellipse' ||
        -    f === 'arc' ||
        -    f === 'circle' ||
        -    f === 'square'
        -  ) {
        -    x = Math.round(args[0] + args[2] / 2);
        -    y = Math.round(args[1] + args[3] / 2);
        -  } else if (f === 'triangle') {
        -    x = (args[0] + args[2] + args[4]) / 3;
        -    y = (args[1] + args[3] + args[5]) / 3;
        -  } else if (f === 'quadrilateral') {
        -    x = (args[0] + args[2] + args[4] + args[6]) / 4;
        -    y = (args[1] + args[3] + args[5] + args[7]) / 4;
        -  } else if (f === 'line') {
        -    x = (args[0] + args[2]) / 2;
        -    y = (args[1] + args[3]) / 2;
        -  } else {
        -    x = args[0];
        -    y = args[1];
        -  }
        -  return [x, y];
        -}
        -
        -//gets position of shape in the canvas
        -p5.prototype._getPos = function (x, y) {
        -  const untransformedPosition = new DOMPointReadOnly(x, y);
        -  const currentTransform = this._renderer.isP3D ?
        -    new DOMMatrix(this._renderer.uMVMatrix.mat4) :
        -    this.drawingContext.getTransform();
        -  const { x: transformedX, y: transformedY } = untransformedPosition
        -    .matrixTransform(currentTransform);
        -  const canvasWidth = this.width * this._pixelDensity;
        -  const canvasHeight = this.height * this._pixelDensity;
        -  if (transformedX < 0.4 * canvasWidth) {
        -    if (transformedY < 0.4 * canvasHeight) {
        -      return 'top left';
        -    } else if (transformedY > 0.6 * canvasHeight) {
        -      return 'bottom left';
        +  //gets position of shape in the canvas
        +  fn._getPos = function (x, y) {
        +    const untransformedPosition = new DOMPointReadOnly(x, y);
        +    const currentTransform = this._renderer.isP3D ?
        +      new DOMMatrix(this._renderer.calculateCombinedMatrix()) :
        +      this.drawingContext.getTransform();
        +    const { x: transformedX, y: transformedY } = untransformedPosition
        +      .matrixTransform(currentTransform);
        +    const canvasWidth = this.width * this._renderer._pixelDensity;
        +    const canvasHeight = this.height * this._renderer._pixelDensity;
        +    if (transformedX < 0.4 * canvasWidth) {
        +      if (transformedY < 0.4 * canvasHeight) {
        +        return 'top left';
        +      } else if (transformedY > 0.6 * canvasHeight) {
        +        return 'bottom left';
        +      } else {
        +        return 'mid left';
        +      }
        +    } else if (transformedX > 0.6 * canvasWidth) {
        +      if (transformedY < 0.4 * canvasHeight) {
        +        return 'top right';
        +      } else if (transformedY > 0.6 * canvasHeight) {
        +        return 'bottom right';
        +      } else {
        +        return 'mid right';
        +      }
             } else {
        -      return 'mid left';
        +      if (transformedY < 0.4 * canvasHeight) {
        +        return 'top middle';
        +      } else if (transformedY > 0.6 * canvasHeight) {
        +        return 'bottom middle';
        +      } else {
        +        return 'middle';
        +      }
             }
        -  } else if (transformedX > 0.6 * canvasWidth) {
        -    if (transformedY < 0.4 * canvasHeight) {
        -      return 'top right';
        -    } else if (transformedY > 0.6 * canvasHeight) {
        -      return 'bottom right';
        -    } else {
        -      return 'mid right';
        +  };
        +
        +  //locates shape in a 10*10 grid
        +  function _canvasLocator(args, canvasWidth, canvasHeight) {
        +    const noRows = 10;
        +    const noCols = 10;
        +    let locX = Math.floor(args[0] / canvasWidth * noRows);
        +    let locY = Math.floor(args[1] / canvasHeight * noCols);
        +    if (locX === noRows) {
        +      locX = locX - 1;
             }
        -  } else {
        -    if (transformedY < 0.4 * canvasHeight) {
        -      return 'top middle';
        -    } else if (transformedY > 0.6 * canvasHeight) {
        -      return 'bottom middle';
        -    } else {
        -      return 'middle';
        +    if (locY === noCols) {
        +      locY = locY - 1;
             }
        +    return {
        +      locX,
        +      locY
        +    };
           }
        -};
         
        -//locates shape in a 10*10 grid
        -function _canvasLocator(args, canvasWidth, canvasHeight) {
        -  const noRows = 10;
        -  const noCols = 10;
        -  let locX = Math.floor(args[0] / canvasWidth * noRows);
        -  let locY = Math.floor(args[1] / canvasHeight * noCols);
        -  if (locX === noRows) {
        -    locX = locX - 1;
        -  }
        -  if (locY === noCols) {
        -    locY = locY - 1;
        -  }
        -  return {
        -    locX,
        -    locY
        +  //calculates area of shape
        +  fn._getArea = function (objectType, shapeArgs) {
        +    let objectArea = 0;
        +    if (objectType === 'arc') {
        +      // area of full ellipse = PI * horizontal radius * vertical radius.
        +      // therefore, area of arc = difference bet. arc's start and end radians * horizontal radius * vertical radius.
        +      // the below expression is adjusted for negative values and differences in arc's start and end radians over PI*2
        +      const arcSizeInRadians =
        +        ((shapeArgs[5] - shapeArgs[4]) % (Math.PI * 2) + Math.PI * 2) %
        +        (Math.PI * 2);
        +      objectArea = arcSizeInRadians * shapeArgs[2] * shapeArgs[3] / 8;
        +      if (shapeArgs[6] === 'open' || shapeArgs[6] === 'chord') {
        +        // when the arc's mode is OPEN or CHORD, we need to account for the area of the triangle that is formed to close the arc
        +        // (Ax( By −  Cy) + Bx(Cy − Ay) + Cx(Ay − By ) )/2
        +        const Ax = shapeArgs[0];
        +        const Ay = shapeArgs[1];
        +        const Bx =
        +          shapeArgs[0] + shapeArgs[2] / 2 * Math.cos(shapeArgs[4]).toFixed(2);
        +        const By =
        +          shapeArgs[1] + shapeArgs[3] / 2 * Math.sin(shapeArgs[4]).toFixed(2);
        +        const Cx =
        +          shapeArgs[0] + shapeArgs[2] / 2 * Math.cos(shapeArgs[5]).toFixed(2);
        +        const Cy =
        +          shapeArgs[1] + shapeArgs[3] / 2 * Math.sin(shapeArgs[5]).toFixed(2);
        +        const areaOfExtraTriangle =
        +          Math.abs(Ax * (By - Cy) + Bx * (Cy - Ay) + Cx * (Ay - By)) / 2;
        +        if (arcSizeInRadians > Math.PI) {
        +          objectArea = objectArea + areaOfExtraTriangle;
        +        } else {
        +          objectArea = objectArea - areaOfExtraTriangle;
        +        }
        +      }
        +    } else if (objectType === 'ellipse' || objectType === 'circle') {
        +      objectArea = 3.14 * shapeArgs[2] / 2 * shapeArgs[3] / 2;
        +    } else if (objectType === 'line') {
        +      objectArea = 0;
        +    } else if (objectType === 'point') {
        +      objectArea = 0;
        +    } else if (objectType === 'quadrilateral') {
        +      // ((x4+x1)*(y4-y1)+(x1+x2)*(y1-y2)+(x2+x3)*(y2-y3)+(x3+x4)*(y3-y4))/2
        +      objectArea =
        +        Math.abs(
        +          (shapeArgs[6] + shapeArgs[0]) * (shapeArgs[7] - shapeArgs[1]) +
        +            (shapeArgs[0] + shapeArgs[2]) * (shapeArgs[1] - shapeArgs[3]) +
        +            (shapeArgs[2] + shapeArgs[4]) * (shapeArgs[3] - shapeArgs[5]) +
        +            (shapeArgs[4] + shapeArgs[6]) * (shapeArgs[5] - shapeArgs[7])
        +        ) / 2;
        +    } else if (objectType === 'rectangle' || objectType === 'square') {
        +      objectArea = shapeArgs[2] * shapeArgs[3];
        +    } else if (objectType === 'triangle') {
        +      objectArea =
        +        Math.abs(
        +          shapeArgs[0] * (shapeArgs[3] - shapeArgs[5]) +
        +            shapeArgs[2] * (shapeArgs[5] - shapeArgs[1]) +
        +            shapeArgs[4] * (shapeArgs[1] - shapeArgs[3])
        +        ) / 2;
        +      // (Ax( By −  Cy) + Bx(Cy − Ay) + Cx(Ay − By ))/2
        +    }
        +    //  Store the positions of the canvas corners
        +    const canvasWidth = this.width * this._renderer._pixelDensity;
        +    const canvasHeight = this.height * this._renderer._pixelDensity;
        +    const canvasCorners = [
        +      new DOMPoint(0, 0),
        +      new DOMPoint(canvasWidth, 0),
        +      new DOMPoint(canvasWidth, canvasHeight),
        +      new DOMPoint(0, canvasHeight)
        +    ];
        +    //  Apply the inverse of the current transformations to the canvas corners
        +    const currentTransform = this._renderer.isP3D ?
        +      new DOMMatrix(this._renderer.states.uMVMatrix.mat4) :
        +      this.drawingContext.getTransform();
        +    const invertedTransform = currentTransform.inverse();
        +    const tc = canvasCorners.map(
        +      corner => corner.matrixTransform(invertedTransform)
        +    );
        +    /*  Use same shoelace formula used for quad area (above) to calculate
        +    the area of the canvas with inverted transformation applied */
        +    const transformedCanvasArea = Math.abs(
        +      (tc[3].x + tc[0].x) * (tc[3].y - tc[0].y) +
        +      (tc[0].x + tc[1].x) * (tc[0].y - tc[1].y) +
        +      (tc[1].x + tc[2].x) * (tc[1].y - tc[2].y)+
        +      (tc[2].x + tc[3].x) * (tc[2].y - tc[3].y)
        +    ) / 2;
        +    /*  Compare area of shape (minus transformations) to area of canvas
        +    with inverted transformation applied.
        +    Return percentage  */
        +    const untransformedArea = Math.round(
        +      objectArea * 100 / (transformedCanvasArea)
        +    );
        +    return untransformedArea;
           };
         }
         
        -//calculates area of shape
        -p5.prototype._getArea = function (objectType, shapeArgs) {
        -  let objectArea = 0;
        -  if (objectType === 'arc') {
        -    // area of full ellipse = PI * horizontal radius * vertical radius.
        -    // therefore, area of arc = difference bet. arc's start and end radians * horizontal radius * vertical radius.
        -    // the below expression is adjusted for negative values and differences in arc's start and end radians over PI*2
        -    const arcSizeInRadians =
        -      ((shapeArgs[5] - shapeArgs[4]) % (Math.PI * 2) + Math.PI * 2) %
        -      (Math.PI * 2);
        -    objectArea = arcSizeInRadians * shapeArgs[2] * shapeArgs[3] / 8;
        -    if (shapeArgs[6] === 'open' || shapeArgs[6] === 'chord') {
        -      // when the arc's mode is OPEN or CHORD, we need to account for the area of the triangle that is formed to close the arc
        -      // (Ax( By −  Cy) + Bx(Cy − Ay) + Cx(Ay − By ) )/2
        -      const Ax = shapeArgs[0];
        -      const Ay = shapeArgs[1];
        -      const Bx =
        -        shapeArgs[0] + shapeArgs[2] / 2 * Math.cos(shapeArgs[4]).toFixed(2);
        -      const By =
        -        shapeArgs[1] + shapeArgs[3] / 2 * Math.sin(shapeArgs[4]).toFixed(2);
        -      const Cx =
        -        shapeArgs[0] + shapeArgs[2] / 2 * Math.cos(shapeArgs[5]).toFixed(2);
        -      const Cy =
        -        shapeArgs[1] + shapeArgs[3] / 2 * Math.sin(shapeArgs[5]).toFixed(2);
        -      const areaOfExtraTriangle =
        -        Math.abs(Ax * (By - Cy) + Bx * (Cy - Ay) + Cx * (Ay - By)) / 2;
        -      if (arcSizeInRadians > Math.PI) {
        -        objectArea = objectArea + areaOfExtraTriangle;
        -      } else {
        -        objectArea = objectArea - areaOfExtraTriangle;
        -      }
        -    }
        -  } else if (objectType === 'ellipse' || objectType === 'circle') {
        -    objectArea = 3.14 * shapeArgs[2] / 2 * shapeArgs[3] / 2;
        -  } else if (objectType === 'line') {
        -    objectArea = 0;
        -  } else if (objectType === 'point') {
        -    objectArea = 0;
        -  } else if (objectType === 'quadrilateral') {
        -    // ((x4+x1)*(y4-y1)+(x1+x2)*(y1-y2)+(x2+x3)*(y2-y3)+(x3+x4)*(y3-y4))/2
        -    objectArea =
        -      Math.abs(
        -        (shapeArgs[6] + shapeArgs[0]) * (shapeArgs[7] - shapeArgs[1]) +
        -          (shapeArgs[0] + shapeArgs[2]) * (shapeArgs[1] - shapeArgs[3]) +
        -          (shapeArgs[2] + shapeArgs[4]) * (shapeArgs[3] - shapeArgs[5]) +
        -          (shapeArgs[4] + shapeArgs[6]) * (shapeArgs[5] - shapeArgs[7])
        -      ) / 2;
        -  } else if (objectType === 'rectangle' || objectType === 'square') {
        -    objectArea = shapeArgs[2] * shapeArgs[3];
        -  } else if (objectType === 'triangle') {
        -    objectArea =
        -      Math.abs(
        -        shapeArgs[0] * (shapeArgs[3] - shapeArgs[5]) +
        -          shapeArgs[2] * (shapeArgs[5] - shapeArgs[1]) +
        -          shapeArgs[4] * (shapeArgs[1] - shapeArgs[3])
        -      ) / 2;
        -    // (Ax( By −  Cy) + Bx(Cy − Ay) + Cx(Ay − By ))/2
        -  }
        -  //  Store the positions of the canvas corners
        -  const canvasWidth = this.width * this._pixelDensity;
        -  const canvasHeight = this.height * this._pixelDensity;
        -  const canvasCorners = [
        -    new DOMPoint(0, 0),
        -    new DOMPoint(canvasWidth, 0),
        -    new DOMPoint(canvasWidth, canvasHeight),
        -    new DOMPoint(0, canvasHeight)
        -  ];
        -  //  Apply the inverse of the current transformations to the canvas corners
        -  const currentTransform = this._renderer.isP3D ?
        -    new DOMMatrix(this._renderer.uMVMatrix.mat4) :
        -    this.drawingContext.getTransform();
        -  const invertedTransform = currentTransform.inverse();
        -  const tc = canvasCorners.map(
        -    corner => corner.matrixTransform(invertedTransform)
        -  );
        -  /*  Use same shoelace formula used for quad area (above) to calculate
        -  the area of the canvas with inverted transformation applied */
        -  const transformedCanvasArea = Math.abs(
        -    (tc[3].x + tc[0].x) * (tc[3].y - tc[0].y) +
        -    (tc[0].x + tc[1].x) * (tc[0].y - tc[1].y) +
        -    (tc[1].x + tc[2].x) * (tc[1].y - tc[2].y)+
        -    (tc[2].x + tc[3].x) * (tc[2].y - tc[3].y)
        -  ) / 2;
        -  /*  Compare area of shape (minus transformations) to area of canvas
        -  with inverted transformation applied.
        -  Return percentage  */
        -  const untransformedArea = Math.round(
        -    objectArea * 100 / (transformedCanvasArea)
        -  );
        -  return untransformedArea;
        -};
        +export default outputs;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  outputs(p5, p5.prototype);
        +}
        diff --git a/src/accessibility/textOutput.js b/src/accessibility/textOutput.js
        index 5e37fd5781..59c55401e7 100644
        --- a/src/accessibility/textOutput.js
        +++ b/src/accessibility/textOutput.js
        @@ -4,118 +4,123 @@
          * @for p5
          * @requires core
          */
        -import p5 from '../core/main';
         
        -//the functions in this file support updating the text output
        +function textOutput(p5, fn){
        +  //the functions in this file support updating the text output
         
        -//updates textOutput
        -p5.prototype._updateTextOutput = function(idT) {
        -  //if html structure is not there yet
        -  if (!this.dummyDOM.querySelector(`#${idT}_summary`)) {
        -    return;
        -  }
        -  let current = this._accessibleOutputs[idT];
        -  //create shape list
        -  let innerList = _shapeList(idT, this.ingredients.shapes);
        -  //create output summary
        -  let innerSummary = _textSummary(
        -    innerList.numShapes,
        -    this.ingredients.colors.background,
        -    this.width,
        -    this.height
        -  );
        -  //create shape details
        -  let innerShapeDetails = _shapeDetails(idT, this.ingredients.shapes);
        -  //if it is different from current summary
        -  if (innerSummary !== current.summary.innerHTML) {
        -    //update
        -    current.summary.innerHTML = innerSummary;
        -  }
        -  //if it is different from current shape list
        -  if (innerList.listShapes !== current.list.innerHTML) {
        -    //update
        -    current.list.innerHTML = innerList.listShapes;
        -  }
        -  //if it is different from current shape details
        -  if (innerShapeDetails !== current.shapeDetails.innerHTML) {
        -    //update
        -    current.shapeDetails.innerHTML = innerShapeDetails;
        -  }
        -  this._accessibleOutputs[idT] = current;
        -};
        +  //updates textOutput
        +  fn._updateTextOutput = function(idT) {
        +    //if html structure is not there yet
        +    if (!this.dummyDOM.querySelector(`#${idT}_summary`)) {
        +      return;
        +    }
        +    let current = this._accessibleOutputs[idT];
        +    //create shape list
        +    let innerList = _shapeList(idT, this.ingredients.shapes);
        +    //create output summary
        +    let innerSummary = _textSummary(
        +      innerList.numShapes,
        +      this.ingredients.colors.background,
        +      this.width,
        +      this.height
        +    );
        +    //create shape details
        +    let innerShapeDetails = _shapeDetails(idT, this.ingredients.shapes);
        +    //if it is different from current summary
        +    if (innerSummary !== current.summary.innerHTML) {
        +      //update
        +      current.summary.innerHTML = innerSummary;
        +    }
        +    //if it is different from current shape list
        +    if (innerList.listShapes !== current.list.innerHTML) {
        +      //update
        +      current.list.innerHTML = innerList.listShapes;
        +    }
        +    //if it is different from current shape details
        +    if (innerShapeDetails !== current.shapeDetails.innerHTML) {
        +      //update
        +      current.shapeDetails.innerHTML = innerShapeDetails;
        +    }
        +    this._accessibleOutputs[idT] = current;
        +  };
         
        -//Builds textOutput summary
        -function _textSummary(numShapes, background, width, height) {
        -  let text = `Your output is a, ${width} by ${height} pixels, ${background} canvas containing the following`;
        -  if (numShapes === 1) {
        -    text = `${text} shape:`;
        -  } else {
        -    text = `${text} ${numShapes} shapes:`;
        +  //Builds textOutput summary
        +  function _textSummary(numShapes, background, width, height) {
        +    let text = `Your output is a, ${width} by ${height} pixels, ${background} canvas containing the following`;
        +    if (numShapes === 1) {
        +      text = `${text} shape:`;
        +    } else {
        +      text = `${text} ${numShapes} shapes:`;
        +    }
        +    return text;
           }
        -  return text;
        -}
         
        -//Builds textOutput table with shape details
        -function _shapeDetails(idT, ingredients) {
        -  let shapeDetails = '';
        -  let shapeNumber = 0;
        -  //goes trhough every shape type in ingredients
        -  for (let x in ingredients) {
        -    //and for every shape
        -    for (let y in ingredients[x]) {
        -      //it creates a table row
        -      let row = `<tr id="${idT}shape${shapeNumber}"><th>${
        -        ingredients[x][y].color
        -      } ${x}</th>`;
        -      if (x === 'line') {
        -        row =
        -          row +
        -          `<td>location = ${ingredients[x][y].pos}</td><td>length = ${
        -            ingredients[x][y].length
        -          } pixels</td></tr>`;
        -      } else {
        -        row = row + `<td>location = ${ingredients[x][y].pos}</td>`;
        -        if (x !== 'point') {
        -          row = row + `<td> area = ${ingredients[x][y].area}%</td>`;
        +  //Builds textOutput table with shape details
        +  function _shapeDetails(idT, ingredients) {
        +    let shapeDetails = '';
        +    let shapeNumber = 0;
        +    //goes trhough every shape type in ingredients
        +    for (let x in ingredients) {
        +      //and for every shape
        +      for (let y in ingredients[x]) {
        +        //it creates a table row
        +        let row = `<tr id="${idT}shape${shapeNumber}"><th>${
        +          ingredients[x][y].color
        +        } ${x}</th>`;
        +        if (x === 'line') {
        +          row =
        +            row +
        +            `<td>location = ${ingredients[x][y].pos}</td><td>length = ${
        +              ingredients[x][y].length
        +            } pixels</td></tr>`;
        +        } else {
        +          row = row + `<td>location = ${ingredients[x][y].pos}</td>`;
        +          if (x !== 'point') {
        +            row = row + `<td> area = ${ingredients[x][y].area}%</td>`;
        +          }
        +          row = row + '</tr>';
                 }
        -        row = row + '</tr>';
        +        shapeDetails = shapeDetails + row;
        +        shapeNumber++;
               }
        -      shapeDetails = shapeDetails + row;
        -      shapeNumber++;
             }
        +    return shapeDetails;
           }
        -  return shapeDetails;
        -}
         
        -//Builds textOutput shape list
        -function _shapeList(idT, ingredients) {
        -  let shapeList = '';
        -  let shapeNumber = 0;
        -  //goes trhough every shape type in ingredients
        -  for (let x in ingredients) {
        -    for (let y in ingredients[x]) {
        -      //it creates a line in a list
        -      let _line = `<li><a href="#${idT}shape${shapeNumber}">${
        -        ingredients[x][y].color
        -      } ${x}</a>`;
        -      if (x === 'line') {
        -        _line =
        -          _line +
        -          `, ${ingredients[x][y].pos}, ${
        -            ingredients[x][y].length
        -          } pixels long.</li>`;
        -      } else {
        -        _line = _line + `, at ${ingredients[x][y].pos}`;
        -        if (x !== 'point') {
        -          _line = _line + `, covering ${ingredients[x][y].area}% of the canvas`;
        +  //Builds textOutput shape list
        +  function _shapeList(idT, ingredients) {
        +    let shapeList = '';
        +    let shapeNumber = 0;
        +    //goes trhough every shape type in ingredients
        +    for (let x in ingredients) {
        +      for (let y in ingredients[x]) {
        +        //it creates a line in a list
        +        let _line = `<li><a href="#${idT}shape${shapeNumber}">${
        +          ingredients[x][y].color
        +        } ${x}</a>`;
        +        if (x === 'line') {
        +          _line =
        +            _line +
        +            `, ${ingredients[x][y].pos}, ${
        +              ingredients[x][y].length
        +            } pixels long.</li>`;
        +        } else {
        +          _line = _line + `, at ${ingredients[x][y].pos}`;
        +          if (x !== 'point') {
        +            _line = _line + `, covering ${ingredients[x][y].area}% of the canvas`;
        +          }
        +          _line = _line + '.</li>';
                 }
        -        _line = _line + '.</li>';
        +        shapeList = shapeList + _line;
        +        shapeNumber++;
               }
        -      shapeList = shapeList + _line;
        -      shapeNumber++;
             }
        +    return { numShapes: shapeNumber, listShapes: shapeList };
           }
        -  return { numShapes: shapeNumber, listShapes: shapeList };
         }
         
        -export default p5;
        +export default textOutput;
        +
        +if(typeof p5 !== 'undefined'){
        +  textOutput(p5, p5.prototype);
        +}
        diff --git a/src/app.js b/src/app.js
        index 672acc6142..84b74c99bc 100644
        --- a/src/app.js
        +++ b/src/app.js
        @@ -1,106 +1,63 @@
         // core
         import p5 from './core/main';
        -import './core/constants';
        -import './core/environment';
        -import './core/friendly_errors/stacktrace';
        -import './core/friendly_errors/validate_params';
        -import './core/friendly_errors/file_errors';
        -import './core/friendly_errors/fes_core';
        -import './core/friendly_errors/sketch_reader';
        -import './core/helpers';
        -import './core/legacy';
        -import './core/preload';
        -import './core/p5.Element';
        -import './core/p5.Graphics';
        -import './core/p5.Renderer';
        -import './core/p5.Renderer2D';
        -import './core/rendering';
        -import './core/shim';
        -import './core/structure';
        -import './core/transform';
        -import './core/shape/2d_primitives';
        -import './core/shape/attributes';
        -import './core/shape/curves';
        -import './core/shape/vertex';
        +import shape from './shape';
        +shape(p5);
        +
        +// shapes
        +import customShapes from './shape';
        +customShapes(p5);
        +
         //accessibility
        -import './accessibility/outputs';
        -import './accessibility/textOutput';
        -import './accessibility/gridOutput';
        -import './accessibility/color_namer';
        +import accessibility from './accessibility';
        +accessibility(p5);
        +
         // color
        -import './color/color_conversion';
        -import './color/creating_reading';
        -import './color/p5.Color';
        -import './color/setting';
        +import color from './color';
        +color(p5);
        +
        +// core
        +// currently, it only contains the test for parameter validation
        +import friendlyErrors from './core/friendly_errors';
        +friendlyErrors(p5);
         
         // data
        -import './data/p5.TypedDict';
        -import './data/local_storage.js';
        +import data from './data';
        +data(p5);
         
         // DOM
        -import './dom/dom';
        -
        -// accessibility
        -import './accessibility/describe';
        +import dom from './dom';
        +dom(p5);
         
         // events
        -import './events/acceleration';
        -import './events/keyboard';
        -import './events/mouse';
        -import './events/touch';
        +import events from './events';
        +events(p5);
         
         // image
        -import './image/filters';
        -import './image/image';
        -import './image/loading_displaying';
        -import './image/p5.Image';
        -import './image/pixels';
        +import image from './image';
        +image(p5);
         
         // io
        -import './io/files';
        -import './io/p5.Table';
        -import './io/p5.TableRow';
        -import './io/p5.XML';
        +import io from './io';
        +io(p5);
         
         // math
        -import './math/calculation';
        -import './math/math';
        -import './math/noise';
        -import './math/p5.Vector';
        -import './math/random';
        -import './math/trigonometry';
        -
        -// typography
        -import './typography/attributes';
        -import './typography/loading_displaying';
        -import './typography/p5.Font';
        +import math from './math';
        +math(p5);
         
         // utilities
        -import './utilities/array_functions';
        -import './utilities/conversion';
        -import './utilities/string_functions';
        -import './utilities/time_date';
        +import utilities from './utilities';
        +utilities(p5);
         
         // webgl
        -import './webgl/3d_primitives';
        -import './webgl/interaction';
        -import './webgl/light';
        -import './webgl/loading';
        -import './webgl/material';
        -import './webgl/p5.Camera';
        -import './webgl/p5.DataArray';
        -import './webgl/p5.Geometry';
        -import './webgl/p5.Matrix';
        -import './webgl/p5.Quat';
        -import './webgl/p5.RendererGL.Immediate';
        -import './webgl/p5.RendererGL';
        -import './webgl/p5.RendererGL.Retained';
        -import './webgl/p5.Framebuffer';
        -import './webgl/p5.Shader';
        -import './webgl/p5.RenderBuffer';
        -import './webgl/p5.Texture';
        -import './webgl/text';
        -
        -import './core/init';
        -
        -module.exports = p5;
        +import webgl from './webgl';
        +webgl(p5);
        +
        +// typography
        +import type from './type'
        +type(p5);
        +
        +import { waitForDocumentReady, waitingForTranslator, _globalInit } from './core/init';
        +Promise.all([waitForDocumentReady(), waitingForTranslator]).then(_globalInit);
        +
        +export default p5;
        +
        diff --git a/src/color/color_spaces/hsb.js b/src/color/color_spaces/hsb.js
        new file mode 100644
        index 0000000000..da1e051fd9
        --- /dev/null
        +++ b/src/color/color_spaces/hsb.js
        @@ -0,0 +1,142 @@
        +import { ColorSpace, sRGB } from 'colorjs.io/fn';
        +
        +export default new ColorSpace({
        +  id: 'hsb',
        +  name: 'HSB',
        +  coords: {
        +    h: {
        +      refRange: [0, 360],
        +      type: 'angle',
        +      name: 'Hue'
        +    },
        +    s: {
        +      range: [0, 100],
        +      name: 'Saturation'
        +    },
        +    b: {
        +      range: [0, 100],
        +      name: 'Brightness'
        +    }
        +  },
        +
        +  base: sRGB,
        +
        +  fromBase: rgb => {
        +    const val = Math.max(...rgb);
        +    const chroma = val - Math.min(...rgb);
        +
        +    let [red, green, blue] = rgb;
        +
        +    let hue, sat;
        +    if (chroma === 0) {
        +      // Return early if grayscale.
        +      hue = 0;
        +      sat = 0;
        +    } else {
        +      sat = chroma / val;
        +      if (red === val) {
        +        // Magenta to yellow.
        +        hue = (green - blue) / chroma;
        +      } else if (green === val) {
        +        // Yellow to cyan.
        +        hue = 2 + (blue - red) / chroma;
        +      } else if (blue === val) {
        +        // Cyan to magenta.
        +        hue = 4 + (red - green) / chroma;
        +      }
        +      if (hue < 0) {
        +        // Confine hue to the interval [0, 1).
        +        hue += 6;
        +      } else if (hue >= 6) {
        +        hue -= 6;
        +      }
        +    }
        +
        +    return [hue / 6 * 360, sat * 100, val * 100];
        +  },
        +  toBase,
        +
        +  formats: {
        +    default :{
        +      type: 'custom',
        +      serialize: (coords, alpha) => {
        +        const rgb = toBase(coords);
        +        let ret = `rgb(${
        +          Math.round(rgb[0] * 100 * 100) / 100
        +        }% ${
        +          Math.round(rgb[1] * 100 * 100) / 100
        +        }% ${
        +          Math.round(rgb[2] * 100 * 100) / 100
        +        }%`;
        +
        +        if (alpha < 1) {
        +          ret += ` / ${alpha}`;
        +        }
        +
        +        ret += ')';
        +
        +        return ret;
        +      }
        +    },
        +    'hsb': {
        +      coords: ['<number> | <angle>', '<percentage>', '<percentage>']
        +    },
        +    'hsba': {
        +      coords: ['<number> | <angle>', '<percentage>', '<percentage>'],
        +      commans: true,
        +      lastAlpha: true
        +    }
        +  }
        +});
        +
        +function toBase(hsb){
        +  const hue = hsb[0] / 360 * 6; // We will split hue into 6 sectors.
        +  const sat = hsb[1] / 100;
        +  const val = hsb[2] / 100;
        +
        +  let RGB = [];
        +
        +  if (sat === 0) {
        +    RGB = [val, val, val]; // Return early if grayscale.
        +  } else {
        +    const sector = Math.floor(hue);
        +    const tint1 = val * (1 - sat);
        +    const tint2 = val * (1 - sat * (hue - sector));
        +    const tint3 = val * (1 - sat * (1 + sector - hue));
        +    let red, green, blue;
        +    if (sector === 1) {
        +      // Yellow to green.
        +      red = tint2;
        +      green = val;
        +      blue = tint1;
        +    } else if (sector === 2) {
        +      // Green to cyan.
        +      red = tint1;
        +      green = val;
        +      blue = tint3;
        +    } else if (sector === 3) {
        +      // Cyan to blue.
        +      red = tint1;
        +      green = tint2;
        +      blue = val;
        +    } else if (sector === 4) {
        +      // Blue to magenta.
        +      red = tint3;
        +      green = tint1;
        +      blue = val;
        +    } else if (sector === 5) {
        +      // Magenta to red.
        +      red = val;
        +      green = tint1;
        +      blue = tint2;
        +    } else {
        +      // Red to yellow (sector could be 0 or 6).
        +      red = val;
        +      green = tint3;
        +      blue = tint1;
        +    }
        +    RGB = [red, green, blue];
        +  }
        +
        +  return RGB;
        +}
        diff --git a/src/color/creating_reading.js b/src/color/creating_reading.js
        index d918f09845..f848ba3a25 100644
        --- a/src/color/creating_reading.js
        +++ b/src/color/creating_reading.js
        @@ -6,1587 +6,1586 @@
          * @requires constants
          */
         
        -import p5 from '../core/main';
        -import * as constants from '../core/constants';
        -import './p5.Color';
        -import '../core/friendly_errors/validate_params';
        -import '../core/friendly_errors/file_errors';
        -import '../core/friendly_errors/fes_core';
        +import { Color } from './p5.Color';
         
        -/**
        - * Gets the alpha (transparency) value of a color.
        - *
        - * `alpha()` extracts the alpha value from a
        - * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        - * a CSS color string.
        - *
        - * @method alpha
        - * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        - *                                         color components, or CSS color string.
        - * @return {Number} the alpha value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color(0, 126, 255, 102);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'alphaValue' to 102.
        - *   let alphaValue = alpha(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(alphaValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light blue and the right one is charcoal gray.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a color array.
        - *   let c = [0, 126, 255, 102];
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'alphaValue' to 102.
        - *   let alphaValue = alpha(c);
        - *
        - *   // Draw the left rectangle.
        - *   fill(alphaValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light blue and the right one is charcoal gray.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a CSS color string.
        - *   let c = 'rgba(0, 126, 255, 0.4)';
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'alphaValue' to 102.
        - *   let alphaValue = alpha(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(alphaValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light blue and the right one is charcoal gray.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.alpha = function(c) {
        -  p5._validateParameters('alpha', arguments);
        -  return this.color(c)._getAlpha();
        -};
        +export const RGB = 'rgb';
        +export const RGBHDR = 'rgbhdr';
        +export const HSB = 'hsb';
        +export const HSL = 'hsl';
        +export const HWB = 'hwb';
         
        -/**
        - * Gets the blue value of a color.
        - *
        - * `blue()` extracts the blue value from a
        - * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        - * a CSS color string.
        - *
        - * By default, `blue()` returns a color's blue value in the range 0
        - * to 255. If the <a href="#/colorMode">colorMode()</a> is set to RGB, it
        - * returns the blue value in the given range.
        - *
        - * @method blue
        - * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        - *                                         color components, or CSS color string.
        - * @return {Number} the blue value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object using RGB values.
        - *   let c = color(175, 100, 220);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'blueValue' to 220.
        - *   let blueValue = blue(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(0, 0, blueValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is royal blue.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a color array.
        - *   let c = [175, 100, 220];
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'blueValue' to 220.
        - *   let blueValue = blue(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(0, 0, blueValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is royal blue.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a CSS color string.
        - *   let c = 'rgb(175, 100, 220)';
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'blueValue' to 220.
        - *   let blueValue = blue(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(0, 0, blueValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is royal blue.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use RGB color with values in the range 0-100.
        - *   colorMode(RGB, 100);
        - *
        - *   // Create a p5.Color object using RGB values.
        - *   let c = color(69, 39, 86);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'blueValue' to 86.
        - *   let blueValue = blue(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(0, 0, blueValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is royal blue.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.blue = function(c) {
        -  p5._validateParameters('blue', arguments);
        -  return this.color(c)._getBlue();
        -};
        +export const LAB = 'lab';
        +export const LCH = 'lch';
         
        -/**
        - * Gets the brightness value of a color.
        - *
        - * `brightness()` extracts the HSB brightness value from a
        - * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        - * a CSS color string.
        - *
        - * By default, `brightness()` returns a color's HSB brightness in the range 0
        - * to 100. If the <a href="#/colorMode">colorMode()</a> is set to HSB, it
        - * returns the brightness value in the given range.
        - *
        - * @method brightness
        - * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        - *                                         color components, or CSS color string.
        - * @return {Number} the brightness value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use HSB color.
        - *   colorMode(HSB);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color(0, 50, 100);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'brightValue' to 100.
        - *   let brightValue = brightness(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(brightValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use HSB color.
        - *   colorMode(HSB);
        - *
        - *   // Create a color array.
        - *   let c = [0, 50, 100];
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'brightValue' to 100.
        - *   let brightValue = brightness(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(brightValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use HSB color.
        - *   colorMode(HSB);
        - *
        - *   // Create a CSS color string.
        - *   let c = 'rgb(255, 128, 128)';
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'brightValue' to 100.
        - *   let brightValue = brightness(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(brightValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use HSB color with values in the range 0-255.
        - *   colorMode(HSB, 255);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color(0, 127, 255);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'brightValue' to 255.
        - *   let brightValue = brightness(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(brightValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.brightness = function(c) {
        -  p5._validateParameters('brightness', arguments);
        -  return this.color(c)._getBrightness();
        -};
        +export const OKLAB = 'oklab';
        +export const OKLCH = 'oklch';
         
        -/**
        - * Creates a <a href="#/p5/p5.Color">p5.Color</a> object.
        - *
        - * By default, the parameters are interpreted as RGB values. Calling
        - * `color(255, 204, 0)` will return a bright yellow color. The way these
        - * parameters are interpreted may be changed with the
        - * <a href="#/p5/colorMode">colorMode()</a> function.
        - *
        - * The version of `color()` with one parameter interprets the value one of two
        - * ways. If the parameter is a number, it's interpreted as a grayscale value.
        - * If the parameter is a string, it's interpreted as a CSS color string.
        - *
        - * The version of `color()` with two parameters interprets the first one as a
        - * grayscale value. The second parameter sets the alpha (transparency) value.
        - *
        - * The version of `color()` with three parameters interprets them as RGB, HSB,
        - * or HSL colors, depending on the current `colorMode()`.
        - *
        - * The version of `color()` with four parameters interprets them as RGBA, HSBA,
        - * or HSLA colors, depending on the current `colorMode()`. The last parameter
        - * sets the alpha (transparency) value.
        - *
        - * @method color
        - * @param  {Number} gray number specifying value between white and black.
        - * @param  {Number} [alpha] alpha value relative to current color range
        - *                                 (default is 0-255).
        - * @return {p5.Color} resulting color.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object using RGB values.
        - *   let c = color(255, 204, 0);
        - *
        - *   // Draw the square.
        - *   fill(c);
        - *   noStroke();
        - *   square(30, 20, 55);
        - *
        - *   describe('A yellow square on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object using RGB values.
        - *   let c1 = color(255, 204, 0);
        - *
        - *   // Draw the left circle.
        - *   fill(c1);
        - *   noStroke();
        - *   circle(25, 25, 80);
        - *
        - *   // Create a p5.Color object using a grayscale value.
        - *   let c2 = color(65);
        - *
        - *   // Draw the right circle.
        - *   fill(c2);
        - *   circle(75, 75, 80);
        - *
        - *   describe(
        - *     'Two circles on a gray canvas. The circle in the top-left corner is yellow and the one at the bottom-right is gray.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object using a named color.
        - *   let c = color('magenta');
        - *
        - *   // Draw the square.
        - *   fill(c);
        - *   noStroke();
        - *   square(20, 20, 60);
        - *
        - *   describe('A magenta square on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object using a hex color code.
        - *   let c1 = color('#0f0');
        - *
        - *   // Draw the left rectangle.
        - *   fill(c1);
        - *   noStroke();
        - *   rect(0, 10, 45, 80);
        - *
        - *   // Create a p5.Color object using a hex color code.
        - *   let c2 = color('#00ff00');
        - *
        - *   // Draw the right rectangle.
        - *   fill(c2);
        - *   rect(55, 10, 45, 80);
        - *
        - *   describe('Two bright green rectangles on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object using a RGB color string.
        - *   let c1 = color('rgb(0, 0, 255)');
        - *
        - *   // Draw the top-left square.
        - *   fill(c1);
        - *   square(10, 10, 35);
        - *
        - *   // Create a p5.Color object using a RGB color string.
        - *   let c2 = color('rgb(0%, 0%, 100%)');
        - *
        - *   // Draw the top-right square.
        - *   fill(c2);
        - *   square(55, 10, 35);
        - *
        - *   // Create a p5.Color object using a RGBA color string.
        - *   let c3 = color('rgba(0, 0, 255, 1)');
        - *
        - *   // Draw the bottom-left square.
        - *   fill(c3);
        - *   square(10, 55, 35);
        - *
        - *   // Create a p5.Color object using a RGBA color string.
        - *   let c4 = color('rgba(0%, 0%, 100%, 1)');
        - *
        - *   // Draw the bottom-right square.
        - *   fill(c4);
        - *   square(55, 55, 35);
        - *
        - *   describe('Four blue squares in the corners of a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object using a HSL color string.
        - *   let c1 = color('hsl(160, 100%, 50%)');
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c1);
        - *   rect(0, 10, 45, 80);
        - *
        - *   // Create a p5.Color object using a HSLA color string.
        - *   let c2 = color('hsla(160, 100%, 50%, 0.5)');
        - *
        - *   // Draw the right rectangle.
        - *   fill(c2);
        - *   rect(55, 10, 45, 80);
        - *
        - *   describe('Two sea green rectangles. A darker rectangle on the left and a brighter one on the right.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object using a HSB color string.
        - *   let c1 = color('hsb(160, 100%, 50%)');
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c1);
        - *   rect(0, 10, 45, 80);
        - *
        - *   // Create a p5.Color object using a HSBA color string.
        - *   let c2 = color('hsba(160, 100%, 50%, 0.5)');
        - *
        - *   // Draw the right rectangle.
        - *   fill(c2);
        - *   rect(55, 10, 45, 80);
        - *
        - *   describe('Two green rectangles. A darker rectangle on the left and a brighter one on the right.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object using RGB values.
        - *   let c1 = color(50, 55, 100);
        - *
        - *   // Draw the left rectangle.
        - *   fill(c1);
        - *   rect(0, 10, 45, 80);
        - *
        - *   // Switch the color mode to HSB.
        - *   colorMode(HSB, 100);
        - *
        - *   // Create a p5.Color object using HSB values.
        - *   let c2 = color(50, 55, 100);
        - *
        - *   // Draw the right rectangle.
        - *   fill(c2);
        - *   rect(55, 10, 45, 80);
        - *
        - *   describe('Two blue rectangles. A darker rectangle on the left and a brighter one on the right.');
        - * }
        - * </code>
        - * </div>
        - */
        +export const RGBA = 'rgba';
         
        -/**
        - * @method color
        - * @param  {Number}        v1      red or hue value relative to
        - *                                 the current color range.
        - * @param  {Number}        v2      green or saturation value
        - *                                 relative to the current color range.
        - * @param  {Number}        v3      blue or brightness value
        - *                                 relative to the current color range.
        - * @param  {Number}        [alpha]
        - * @return {p5.Color}
        - */
        +function creatingReading(p5, fn){
        +  fn.RGB = RGB;
        +  fn.RGBHDR = RGBHDR;
        +  fn.HSB = HSB;
        +  fn.HSL = HSL;
        +  fn.HWB = HWB;
         
        -/**
        - * @method color
        - * @param  {String}        value   a color string.
        - * @return {p5.Color}
        - */
        +  fn.LAB = LAB;
        +  fn.LCH = LCH;
         
        -/**
        - * @method color
        - * @param  {Number[]}      values  an array containing the red, green, blue,
        - *                                 and alpha components of the color.
        - * @return {p5.Color}
        - */
        +  fn.OKLAB = OKLAB;
        +  fn.OKLCH = OKLCH;
         
        -/**
        - * @method color
        - * @param  {p5.Color}     color
        - * @return {p5.Color}
        - */
        -p5.prototype.color = function(...args) {
        -  p5._validateParameters('color', args);
        -  if (args[0] instanceof p5.Color) {
        -    return args[0]; // Do nothing if argument is already a color object.
        -  }
        +  fn.RGBA = RGBA;
         
        -  const arg = Array.isArray(args[0]) ? args[0] : args;
        -  return new p5.Color(this, arg);
        -};
        +  // Add color states to renderer state machine
        +  p5.Renderer.states.colorMode = RGB;
        +  p5.Renderer.states.colorMaxes = {
        +    [RGB]: [255, 255, 255, 255],
        +    [RGBHDR]: [255, 255, 255, 255],
        +    [HSB]: [360, 100, 100, 1],
        +    [HSL]: [360, 100, 100, 1],
        +    [HWB]: [360, 100, 100, 1],
         
        -/**
        - * Gets the green value of a color.
        - *
        - * `green()` extracts the green value from a
        - * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        - * a CSS color string.
        - *
        - * By default, `green()` returns a color's green value in the range 0
        - * to 255. If the <a href="#/colorMode">colorMode()</a> is set to RGB, it
        - * returns the green value in the given range.
        - *
        - * @method green
        - * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        - *                                         color components, or CSS color string.
        - * @return {Number} the green value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color(175, 100, 220);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'greenValue' to 100.
        - *   let greenValue = green(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(0, greenValue, 0);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is dark green.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a color array.
        - *   let c = [175, 100, 220];
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'greenValue' to 100.
        - *   let greenValue = green(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(0, greenValue, 0);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is dark green.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a CSS color string.
        - *   let c = 'rgb(175, 100, 220)';
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'greenValue' to 100.
        - *   let greenValue = green(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(0, greenValue, 0);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is dark green.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use RGB color with values in the range 0-100.
        - *   colorMode(RGB, 100);
        - *
        - *   // Create a p5.Color object using RGB values.
        - *   let c = color(69, 39, 86);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'greenValue' to 39.
        - *   let greenValue = green(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(0, greenValue, 0);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is dark green.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.green = function(c) {
        -  p5._validateParameters('green', arguments);
        -  return this.color(c)._getGreen();
        -};
        +    [LAB]: [100, [-125, 125], [-125, 125], 1],
        +    [LCH]: [100, 150, 360, 1],
         
        -/**
        - * Gets the hue value of a color.
        - *
        - * `hue()` extracts the hue value from a
        - * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        - * a CSS color string.
        - *
        - * Hue describes a color's position on the color wheel. By default, `hue()`
        - * returns a color's HSL hue in the range 0 to 360. If the
        - * <a href="#/colorMode">colorMode()</a> is set to HSB or HSL, it returns the hue
        - * value in the given mode.
        - *
        - * @method hue
        - * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        - *                                         color components, or CSS color string.
        - * @return {Number} the hue value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use HSL color.
        - *   colorMode(HSL);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color(0, 50, 100);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 20, 35, 60);
        - *
        - *   // Set 'hueValue' to 0.
        - *   let hueValue = hue(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(hueValue);
        - *   rect(50, 20, 35, 60);
        - *
        - *   describe(
        - *     'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use HSL color.
        - *   colorMode(HSL);
        - *
        - *   // Create a color array.
        - *   let c = [0, 50, 100];
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 20, 35, 60);
        - *
        - *   // Set 'hueValue' to 0.
        - *   let hueValue = hue(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(hueValue);
        - *   rect(50, 20, 35, 60);
        - *
        - *   describe(
        - *     'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use HSL color.
        - *   colorMode(HSL);
        - *
        - *   // Create a CSS color string.
        - *   let c = 'rgb(255, 128, 128)';
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 20, 35, 60);
        - *
        - *   // Set 'hueValue' to 0.
        - *   let hueValue = hue(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(hueValue);
        - *   rect(50, 20, 35, 60);
        - *
        - *   describe(
        - *     'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.hue = function(c) {
        -  p5._validateParameters('hue', arguments);
        -  return this.color(c)._getHue();
        -};
        +    [OKLAB]: [100, [-125, 125], [-125, 125], 1],
        +    [OKLCH]: [100, 150, 360, 1],
        +    clone: function(){
        +      let ret = {};
        +      for(const key in this){
        +        if(typeof this[key] !== 'function'){
        +          ret[key] = structuredClone(this[key]);
        +        }else{
        +          ret[key] = this[key];
        +        }
        +      }
        +      return ret;
        +    }
        +  };
         
        -/**
        - * Blends two colors to find a third color between them.
        - *
        - * The `amt` parameter specifies the amount to interpolate between the two
        - * values. 0 is equal to the first color, 0.1 is very near the first color,
        - * 0.5 is halfway between the two colors, and so on. Negative numbers are set
        - * to 0. Numbers greater than 1 are set to 1. This differs from the behavior of
        - * <a href="#/p5/lerp">lerp</a>. It's necessary because numbers outside of the
        - * interval [0, 1] will produce strange and unexpected colors.
        - *
        - * The way that colors are interpolated depends on the current
        - * <a href="#/p5/colorMode">colorMode()</a>.
        - *
        - * @method lerpColor
        - * @param  {p5.Color} c1  interpolate from this color (any value created by the color() function).
        - * @param  {p5.Color} c2  interpolate to this color (any value created by the color() function).
        - * @param  {Number}   amt number between 0 and 1.
        - * @return {p5.Color}     interpolated color.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create p5.Color objects to interpolate between.
        - *   let from = color(218, 165, 32);
        - *   let to = color(72, 61, 139);
        - *
        - *   // Create intermediate colors.
        - *   let interA = lerpColor(from, to, 0.33);
        - *   let interB = lerpColor(from, to, 0.66);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(from);
        - *   rect(10, 20, 20, 60);
        - *
        - *   // Draw the left-center rectangle.
        - *   fill(interA);
        - *   rect(30, 20, 20, 60);
        - *
        - *   // Draw the right-center rectangle.
        - *   fill(interB);
        - *   rect(50, 20, 20, 60);
        - *
        - *   // Draw the right rectangle.
        - *   fill(to);
        - *   rect(70, 20, 20, 60);
        - *
        - *   describe(
        - *     'Four rectangles. From left to right, the rectangles are tan, brown, brownish purple, and purple.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.lerpColor = function(c1, c2, amt) {
        -  p5._validateParameters('lerpColor', arguments);
        +  /**
        +   * Creates a <a href="#/p5/p5.Color">p5.Color</a> object.
        +   *
        +   * By default, the parameters are interpreted as RGB values. Calling
        +   * `color(255, 204, 0)` will return a bright yellow color. The way these
        +   * parameters are interpreted may be changed with the
        +   * <a href="#/p5/colorMode">colorMode()</a> function.
        +   *
        +   * The version of `color()` with one parameter interprets the value one of two
        +   * ways. If the parameter is a number, it's interpreted as a grayscale value.
        +   * If the parameter is a string, it's interpreted as a CSS color string.
        +   *
        +   * The version of `color()` with two parameters interprets the first one as a
        +   * grayscale value. The second parameter sets the alpha (transparency) value.
        +   *
        +   * The version of `color()` with three parameters interprets them as RGB, HSB,
        +   * or HSL colors, depending on the current `colorMode()`.
        +   *
        +   * The version of `color()` with four parameters interprets them as RGBA, HSBA,
        +   * or HSLA colors, depending on the current `colorMode()`. The last parameter
        +   * sets the alpha (transparency) value.
        +   *
        +   * @method color
        +   * @param  {Number} gray number specifying value between white and black.
        +   * @param  {Number} [alpha] alpha value relative to current color range
        +   *                                 (default is 0-255).
        +   * @return {p5.Color} resulting color.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object using RGB values.
        +   *   let c = color(255, 204, 0);
        +   *
        +   *   // Draw the square.
        +   *   fill(c);
        +   *   noStroke();
        +   *   square(30, 20, 55);
        +   *
        +   *   describe('A yellow square on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object using RGB values.
        +   *   let c1 = color(255, 204, 0);
        +   *
        +   *   // Draw the left circle.
        +   *   fill(c1);
        +   *   noStroke();
        +   *   circle(25, 25, 80);
        +   *
        +   *   // Create a p5.Color object using a grayscale value.
        +   *   let c2 = color(65);
        +   *
        +   *   // Draw the right circle.
        +   *   fill(c2);
        +   *   circle(75, 75, 80);
        +   *
        +   *   describe(
        +   *     'Two circles on a gray canvas. The circle in the top-left corner is yellow and the one at the bottom-right is gray.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object using a named color.
        +   *   let c = color('magenta');
        +   *
        +   *   // Draw the square.
        +   *   fill(c);
        +   *   noStroke();
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A magenta square on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object using a hex color code.
        +   *   let c1 = color('#0f0');
        +   *
        +   *   // Draw the left rectangle.
        +   *   fill(c1);
        +   *   noStroke();
        +   *   rect(0, 10, 45, 80);
        +   *
        +   *   // Create a p5.Color object using a hex color code.
        +   *   let c2 = color('#00ff00');
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(c2);
        +   *   rect(55, 10, 45, 80);
        +   *
        +   *   describe('Two bright green rectangles on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object using a RGB color string.
        +   *   let c1 = color('rgb(0, 0, 255)');
        +   *
        +   *   // Draw the top-left square.
        +   *   fill(c1);
        +   *   square(10, 10, 35);
        +   *
        +   *   // Create a p5.Color object using a RGB color string.
        +   *   let c2 = color('rgb(0%, 0%, 100%)');
        +   *
        +   *   // Draw the top-right square.
        +   *   fill(c2);
        +   *   square(55, 10, 35);
        +   *
        +   *   // Create a p5.Color object using a RGBA color string.
        +   *   let c3 = color('rgba(0, 0, 255, 1)');
        +   *
        +   *   // Draw the bottom-left square.
        +   *   fill(c3);
        +   *   square(10, 55, 35);
        +   *
        +   *   // Create a p5.Color object using a RGBA color string.
        +   *   let c4 = color('rgba(0%, 0%, 100%, 1)');
        +   *
        +   *   // Draw the bottom-right square.
        +   *   fill(c4);
        +   *   square(55, 55, 35);
        +   *
        +   *   describe('Four blue squares in the corners of a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object using a HSL color string.
        +   *   let c1 = color('hsl(160, 100%, 50%)');
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c1);
        +   *   rect(0, 10, 45, 80);
        +   *
        +   *   // Create a p5.Color object using a HSLA color string.
        +   *   let c2 = color('hsla(160, 100%, 50%, 0.5)');
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(c2);
        +   *   rect(55, 10, 45, 80);
        +   *
        +   *   describe('Two sea green rectangles. A darker rectangle on the left and a brighter one on the right.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object using a HSB color string.
        +   *   let c1 = color('hsb(160, 100%, 50%)');
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c1);
        +   *   rect(0, 10, 45, 80);
        +   *
        +   *   // Create a p5.Color object using a HSBA color string.
        +   *   let c2 = color('hsba(160, 100%, 50%, 0.5)');
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(c2);
        +   *   rect(55, 10, 45, 80);
        +   *
        +   *   describe('Two green rectangles. A darker rectangle on the left and a brighter one on the right.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object using RGB values.
        +   *   let c1 = color(50, 55, 100);
        +   *
        +   *   // Draw the left rectangle.
        +   *   fill(c1);
        +   *   rect(0, 10, 45, 80);
        +   *
        +   *   // Switch the color mode to HSB.
        +   *   colorMode(HSB, 100);
        +   *
        +   *   // Create a p5.Color object using HSB values.
        +   *   let c2 = color(50, 55, 100);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(c2);
        +   *   rect(55, 10, 45, 80);
        +   *
        +   *   describe('Two blue rectangles. A darker rectangle on the left and a brighter one on the right.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -  if (!(c1 instanceof p5.Color)) {
        -    c1 = color(c1);
        -  }
        -  if (!(c2 instanceof p5.Color)) {
        -    c2 = color(c2);
        -  }
        +  /**
        +   * @method color
        +   * @param  {Number}        v1      red or hue value relative to
        +   *                                 the current color range.
        +   * @param  {Number}        v2      green or saturation value
        +   *                                 relative to the current color range.
        +   * @param  {Number}        v3      blue or brightness value
        +   *                                 relative to the current color range.
        +   * @param  {Number}        [alpha]
        +   * @return {p5.Color}
        +   */
         
        -  const mode = this._colorMode;
        -  const maxes = this._colorMaxes;
        -  let l0, l1, l2, l3;
        -  let fromArray, toArray;
        +  /**
        +   * @method color
        +   * @param  {String}        value   a color string.
        +   * @return {p5.Color}
        +   */
         
        -  if (mode === constants.RGB) {
        -    fromArray = c1.levels.map(level => level / 255);
        -    toArray = c2.levels.map(level => level / 255);
        -  } else if (mode === constants.HSB) {
        -    c1._getBrightness(); // Cache hsba so it definitely exists.
        -    c2._getBrightness();
        -    fromArray = c1.hsba;
        -    toArray = c2.hsba;
        -  } else if (mode === constants.HSL) {
        -    c1._getLightness(); // Cache hsla so it definitely exists.
        -    c2._getLightness();
        -    fromArray = c1.hsla;
        -    toArray = c2.hsla;
        -  } else {
        -    throw new Error(`${mode} cannot be used for interpolation.`);
        -  }
        +  /**
        +   * @method color
        +   * @param  {Number[]}      values  an array containing the red, green, blue,
        +   *                                 and alpha components of the color.
        +   * @return {p5.Color}
        +   */
         
        -  // Prevent extrapolation.
        -  amt = Math.max(Math.min(amt, 1), 0);
        +  /**
        +   * @method color
        +   * @param  {p5.Color}     color
        +   * @return {p5.Color}
        +   */
        +  fn.color = function(...args) {
        +    // p5._validateParameters('color', args);
        +    if (args[0] instanceof Color) {
        +      // TODO: perhaps change color mode to match instance mode?
        +      return args[0]; // Do nothing if argument is already a color object.
        +    }
         
        -  // Define lerp here itself if user isn't using math module.
        -  // Maintains the definition as found in math/calculation.js
        -  if (typeof this.lerp === 'undefined') {
        -    this.lerp = (start, stop, amt) => amt * (stop - start) + start;
        -  }
        +    const arg = Array.isArray(args[0]) ? args[0] : args;
        +    return new Color(
        +      arg,
        +      this._renderer.states.colorMode,
        +      this._renderer.states.colorMaxes[this._renderer.states.colorMode]
        +    );
        +  };
         
        -  // Perform interpolation.
        -  if (mode === constants.RGB) {
        -    l0 = this.lerp(fromArray[0], toArray[0], amt);
        -  }
        -  // l0 (hue) has to wrap around (and it's between 0 and 1)
        -  else {
        -    // find shortest path in the color wheel
        -    if (Math.abs(fromArray[0] - toArray[0]) > 0.5) {
        -      if (fromArray[0] > toArray[0]) {
        -        toArray[0] += 1;
        -      } else {
        -        fromArray[0] += 1;
        -      }
        -    }
        -    l0 = this.lerp(fromArray[0], toArray[0], amt);
        -    if (l0 >= 1) { l0 -= 1; }
        -  }
        -  l1 = this.lerp(fromArray[1], toArray[1], amt);
        -  l2 = this.lerp(fromArray[2], toArray[2], amt);
        -  l3 = this.lerp(fromArray[3], toArray[3], amt);
        +  /**
        +   * Gets the red value of a color.
        +   *
        +   * `red()` extracts the red value from a
        +   * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        +   * a CSS color string.
        +   *
        +   * By default, `red()` returns a color's red value in the range 0
        +   * to 255. If the <a href="#/colorMode">colorMode()</a> is set to RGB, it
        +   * returns the red value in the given range.
        +   *
        +   * @method red
        +   * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        +   *                                         color components, or CSS color string.
        +   * @return {Number} the red value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color(175, 100, 220);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'redValue' to 175.
        +   *   let redValue = red(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(redValue, 0, 0);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is red.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a color array.
        +   *   let c = [175, 100, 220];
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'redValue' to 175.
        +   *   let redValue = red(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(redValue, 0, 0);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is red.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a CSS color string.
        +   *   let c = 'rgb(175, 100, 220)';
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'redValue' to 175.
        +   *   let redValue = red(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(redValue, 0, 0);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is red.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use RGB color with values in the range 0-100.
        +   *   colorMode(RGB, 100);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color(69, 39, 86);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'redValue' to 69.
        +   *   let redValue = red(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(redValue, 0, 0);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is red.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.red = function(c) {
        +    // p5._validateParameters('red', arguments);
        +    // Get current red max
        +    return this.color(c)._getRed();
        +  };
         
        -  // Scale components.
        -  l0 *= maxes[mode][0];
        -  l1 *= maxes[mode][1];
        -  l2 *= maxes[mode][2];
        -  l3 *= maxes[mode][3];
        +  /**
        +   * Gets the green value of a color.
        +   *
        +   * `green()` extracts the green value from a
        +   * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        +   * a CSS color string.
        +   *
        +   * By default, `green()` returns a color's green value in the range 0
        +   * to 255. If the <a href="#/colorMode">colorMode()</a> is set to RGB, it
        +   * returns the green value in the given range.
        +   *
        +   * @method green
        +   * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        +   *                                         color components, or CSS color string.
        +   * @return {Number} the green value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color(175, 100, 220);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'greenValue' to 100.
        +   *   let greenValue = green(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(0, greenValue, 0);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is dark green.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a color array.
        +   *   let c = [175, 100, 220];
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'greenValue' to 100.
        +   *   let greenValue = green(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(0, greenValue, 0);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is dark green.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a CSS color string.
        +   *   let c = 'rgb(175, 100, 220)';
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'greenValue' to 100.
        +   *   let greenValue = green(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(0, greenValue, 0);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is dark green.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use RGB color with values in the range 0-100.
        +   *   colorMode(RGB, 100);
        +   *
        +   *   // Create a p5.Color object using RGB values.
        +   *   let c = color(69, 39, 86);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'greenValue' to 39.
        +   *   let greenValue = green(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(0, greenValue, 0);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is dark green.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.green = function(c) {
        +    // p5._validateParameters('green', arguments);
        +    // Get current green max
        +    return this.color(c)._getGreen();
        +  };
         
        -  return this.color(l0, l1, l2, l3);
        -};
        +  /**
        +   * Gets the blue value of a color.
        +   *
        +   * `blue()` extracts the blue value from a
        +   * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        +   * a CSS color string.
        +   *
        +   * By default, `blue()` returns a color's blue value in the range 0
        +   * to 255. If the <a href="#/colorMode">colorMode()</a> is set to RGB, it
        +   * returns the blue value in the given range.
        +   *
        +   * @method blue
        +   * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        +   *                                         color components, or CSS color string.
        +   * @return {Number} the blue value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object using RGB values.
        +   *   let c = color(175, 100, 220);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'blueValue' to 220.
        +   *   let blueValue = blue(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(0, 0, blueValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is royal blue.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a color array.
        +   *   let c = [175, 100, 220];
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'blueValue' to 220.
        +   *   let blueValue = blue(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(0, 0, blueValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is royal blue.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a CSS color string.
        +   *   let c = 'rgb(175, 100, 220)';
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'blueValue' to 220.
        +   *   let blueValue = blue(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(0, 0, blueValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is royal blue.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use RGB color with values in the range 0-100.
        +   *   colorMode(RGB, 100);
        +   *
        +   *   // Create a p5.Color object using RGB values.
        +   *   let c = color(69, 39, 86);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'blueValue' to 86.
        +   *   let blueValue = blue(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(0, 0, blueValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light purple and the right one is royal blue.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.blue = function(c) {
        +    // p5._validateParameters('blue', arguments);
        +    // Get current blue max
        +    return this.color(c)._getBlue();
        +  };
         
        -/**
        - * Blends multiple colors to find a color between them.
        - *
        - * The `amt` parameter specifies the amount to interpolate between the color
        - * stops which are colors at each `amt` value "location" with `amt` values
        - * that are between 2 color stops interpolating between them based on its relative
        - * distance to both.
        - *
        - * The way that colors are interpolated depends on the current
        - * <a href="#/colorMode">colorMode()</a>.
        - *
        - * @method paletteLerp
        - * @param  {[p5.Color, Number][]} colors_stops color stops to interpolate from
        - * @param  {Number} amt number to use to interpolate relative to color stops
        - * @return {p5.Color} interpolated color.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(400, 400);
        - * }
        - *
        - * function draw() {
        - *   // The background goes from white to red to green to blue fill
        - *   background(paletteLerp([
        - *     ['white', 0],
        - *     ['red', 0.05],
        - *     ['green', 0.25],
        - *     ['blue', 1]
        - *   ], millis() / 10000 % 1));
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.paletteLerp = function(color_stops, amt) {
        -  const first_color_stop = color_stops[0];
        -  if (amt < first_color_stop[1])
        -    return this.color(first_color_stop[0]);
        +  /**
        +   * Gets the alpha (transparency) value of a color.
        +   *
        +   * `alpha()` extracts the alpha value from a
        +   * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        +   * a CSS color string.
        +   *
        +   * @method alpha
        +   * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        +   *                                         color components, or CSS color string.
        +   * @return {Number} the alpha value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color(0, 126, 255, 102);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'alphaValue' to 102.
        +   *   let alphaValue = alpha(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(alphaValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light blue and the right one is charcoal gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a color array.
        +   *   let c = [0, 126, 255, 102];
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'alphaValue' to 102.
        +   *   let alphaValue = alpha(c);
        +   *
        +   *   // Draw the left rectangle.
        +   *   fill(alphaValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light blue and the right one is charcoal gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a CSS color string.
        +   *   let c = 'rgba(0, 126, 255, 0.4)';
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'alphaValue' to 102.
        +   *   let alphaValue = alpha(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(alphaValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is light blue and the right one is charcoal gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.alpha = function(c) {
        +    // p5._validateParameters('alpha', arguments);
        +    // Get current alpha max
        +    return this.color(c)._getAlpha();
        +  };
         
        -  for (let i = 1; i < color_stops.length; i++) {
        -    const color_stop = color_stops[i];
        -    if (amt < color_stop[1]) {
        -      const prev_color_stop = color_stops[i - 1];
        -      return this.lerpColor(
        -        this.color(prev_color_stop[0]),
        -        this.color(color_stop[0]),
        -        (amt - prev_color_stop[1]) / (color_stop[1] - prev_color_stop[1])
        -      );
        -    }
        -  }
        +  /**
        +   * Gets the hue value of a color.
        +   *
        +   * `hue()` extracts the hue value from a
        +   * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        +   * a CSS color string.
        +   *
        +   * Hue describes a color's position on the color wheel. By default, `hue()`
        +   * returns a color's HSL hue in the range 0 to 360. If the
        +   * <a href="#/colorMode">colorMode()</a> is set to HSB or HSL, it returns the hue
        +   * value in the given mode.
        +   *
        +   * @method hue
        +   * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        +   *                                         color components, or CSS color string.
        +   * @return {Number} the hue value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use HSL color.
        +   *   colorMode(HSL);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color(0, 50, 100);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 20, 35, 60);
        +   *
        +   *   // Set 'hueValue' to 0.
        +   *   let hueValue = hue(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(hueValue);
        +   *   rect(50, 20, 35, 60);
        +   *
        +   *   describe(
        +   *     'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use HSL color.
        +   *   colorMode(HSL);
        +   *
        +   *   // Create a color array.
        +   *   let c = [0, 50, 100];
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 20, 35, 60);
        +   *
        +   *   // Set 'hueValue' to 0.
        +   *   let hueValue = hue(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(hueValue);
        +   *   rect(50, 20, 35, 60);
        +   *
        +   *   describe(
        +   *     'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use HSL color.
        +   *   colorMode(HSL);
        +   *
        +   *   // Create a CSS color string.
        +   *   let c = 'rgb(255, 128, 128)';
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 20, 35, 60);
        +   *
        +   *   // Set 'hueValue' to 0.
        +   *   let hueValue = hue(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(hueValue);
        +   *   rect(50, 20, 35, 60);
        +   *
        +   *   describe(
        +   *     'Two rectangles. The rectangle on the left is salmon pink and the one on the right is black.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.hue = function(c) {
        +    // p5._validateParameters('hue', arguments);
        +    return this.color(c)._getHue();
        +  };
         
        -  return this.color(color_stops[color_stops.length - 1][0]);
        -};
        +  /**
        +   * Gets the saturation value of a color.
        +   *
        +   * `saturation()` extracts the saturation value from a
        +   * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        +   * a CSS color string.
        +   *
        +   * Saturation is scaled differently in HSB and HSL. By default, `saturation()`
        +   * returns a color's HSL saturation in the range 0 to 100. If the
        +   * <a href="#/colorMode">colorMode()</a> is set to HSB or HSL, it returns the
        +   * saturation value in the given mode.
        +   *
        +   * @method saturation
        +   * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        +   *                                         color components, or CSS color string.
        +   * @return {Number} the saturation value
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Use HSB color.
        +   *   colorMode(HSB);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color(0, 50, 100);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'satValue' to 50.
        +   *   let satValue = saturation(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(satValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is dark gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Use HSB color.
        +   *   colorMode(HSB);
        +   *
        +   *   // Create a color array.
        +   *   let c = [0, 50, 100];
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'satValue' to 100.
        +   *   let satValue = saturation(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(satValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Use HSB color.
        +   *   colorMode(HSB);
        +   *
        +   *   // Create a CSS color string.
        +   *   let c = 'rgb(255, 128, 128)';
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'satValue' to 100.
        +   *   let satValue = saturation(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(satValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Use HSL color.
        +   *   colorMode(HSL);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color(0, 100, 75);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'satValue' to 100.
        +   *   let satValue = saturation(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(satValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Use HSL color with values in the range 0-255.
        +   *   colorMode(HSL, 255);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color(0, 255, 191.5);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'satValue' to 255.
        +   *   let satValue = saturation(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(satValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.saturation = function(c) {
        +    // p5._validateParameters('saturation', arguments);
        +    return this.color(c)._getSaturation();
        +  };
         
        -/**
        - * Gets the lightness value of a color.
        - *
        - * `lightness()` extracts the HSL lightness value from a
        - * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        - * a CSS color string.
        - *
        - * By default, `lightness()` returns a color's HSL lightness in the range 0
        - * to 100. If the <a href="#/colorMode">colorMode()</a> is set to HSL, it
        - * returns the lightness value in the given range.
        - *
        - * @method lightness
        - * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        - *                                         color components, or CSS color string.
        - * @return {Number} the lightness value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Use HSL color.
        - *   colorMode(HSL);
        - *
        - *   // Create a p5.Color object using HSL values.
        - *   let c = color(0, 100, 75);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'lightValue' to 75.
        - *   let lightValue = lightness(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(lightValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Use HSL color.
        - *   colorMode(HSL);
        - *
        - *   // Create a color array.
        - *   let c = [0, 100, 75];
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'lightValue' to 75.
        - *   let lightValue = lightness(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(lightValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Use HSL color.
        - *   colorMode(HSL);
        - *
        - *   // Create a CSS color string.
        - *   let c = 'rgb(255, 128, 128)';
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'lightValue' to 75.
        - *   let lightValue = lightness(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(lightValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Use HSL color with values in the range 0-255.
        - *   colorMode(HSL, 255);
        - *
        - *   // Create a p5.Color object using HSL values.
        - *   let c = color(0, 255, 191.5);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'lightValue' to 191.5.
        - *   let lightValue = lightness(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(lightValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.lightness = function(c) {
        -  p5._validateParameters('lightness', arguments);
        -  return this.color(c)._getLightness();
        -};
        +  /**
        +   * Gets the brightness value of a color.
        +   *
        +   * `brightness()` extracts the HSB brightness value from a
        +   * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        +   * a CSS color string.
        +   *
        +   * By default, `brightness()` returns a color's HSB brightness in the range 0
        +   * to 100. If the <a href="#/colorMode">colorMode()</a> is set to HSB, it
        +   * returns the brightness value in the given range.
        +   *
        +   * @method brightness
        +   * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        +   *                                         color components, or CSS color string.
        +   * @return {Number} the brightness value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use HSB color.
        +   *   colorMode(HSB);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color(0, 50, 100);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'brightValue' to 100.
        +   *   let brightValue = brightness(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(brightValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use HSB color.
        +   *   colorMode(HSB);
        +   *
        +   *   // Create a color array.
        +   *   let c = [0, 50, 100];
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'brightValue' to 100.
        +   *   let brightValue = brightness(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(brightValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use HSB color.
        +   *   colorMode(HSB);
        +   *
        +   *   // Create a CSS color string.
        +   *   let c = 'rgb(255, 128, 128)';
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'brightValue' to 100.
        +   *   let brightValue = brightness(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(brightValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use HSB color with values in the range 0-255.
        +   *   colorMode(HSB, 255);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color(0, 127, 255);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'brightValue' to 255.
        +   *   let brightValue = brightness(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(brightValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.brightness = function(c) {
        +    // p5._validateParameters('brightness', arguments);
        +    return this.color(c)._getBrightness();
        +  };
         
        -/**
        - * Gets the red value of a color.
        - *
        - * `red()` extracts the red value from a
        - * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        - * a CSS color string.
        - *
        - * By default, `red()` returns a color's red value in the range 0
        - * to 255. If the <a href="#/colorMode">colorMode()</a> is set to RGB, it
        - * returns the red value in the given range.
        - *
        - * @method red
        - * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        - *                                         color components, or CSS color string.
        - * @return {Number} the red value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color(175, 100, 220);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'redValue' to 175.
        - *   let redValue = red(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(redValue, 0, 0);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is red.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a color array.
        - *   let c = [175, 100, 220];
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'redValue' to 175.
        - *   let redValue = red(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(redValue, 0, 0);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is red.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a CSS color string.
        - *   let c = 'rgb(175, 100, 220)';
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'redValue' to 175.
        - *   let redValue = red(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(redValue, 0, 0);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is red.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use RGB color with values in the range 0-100.
        - *   colorMode(RGB, 100);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color(69, 39, 86);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'redValue' to 69.
        - *   let redValue = red(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(redValue, 0, 0);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is light purple and the right one is red.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.red = function(c) {
        -  p5._validateParameters('red', arguments);
        -  return this.color(c)._getRed();
        -};
        +  /**
        +   * Gets the lightness value of a color.
        +   *
        +   * `lightness()` extracts the HSL lightness value from a
        +   * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        +   * a CSS color string.
        +   *
        +   * By default, `lightness()` returns a color's HSL lightness in the range 0
        +   * to 100. If the <a href="#/colorMode">colorMode()</a> is set to HSL, it
        +   * returns the lightness value in the given range.
        +   *
        +   * @method lightness
        +   * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        +   *                                         color components, or CSS color string.
        +   * @return {Number} the lightness value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Use HSL color.
        +   *   colorMode(HSL);
        +   *
        +   *   // Create a p5.Color object using HSL values.
        +   *   let c = color(0, 100, 75);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'lightValue' to 75.
        +   *   let lightValue = lightness(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(lightValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Use HSL color.
        +   *   colorMode(HSL);
        +   *
        +   *   // Create a color array.
        +   *   let c = [0, 100, 75];
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'lightValue' to 75.
        +   *   let lightValue = lightness(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(lightValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Use HSL color.
        +   *   colorMode(HSL);
        +   *
        +   *   // Create a CSS color string.
        +   *   let c = 'rgb(255, 128, 128)';
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'lightValue' to 75.
        +   *   let lightValue = lightness(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(lightValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Use HSL color with values in the range 0-255.
        +   *   colorMode(HSL, 255);
        +   *
        +   *   // Create a p5.Color object using HSL values.
        +   *   let c = color(0, 255, 191.5);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(c);
        +   *   rect(15, 15, 35, 70);
        +   *
        +   *   // Set 'lightValue' to 191.5.
        +   *   let lightValue = lightness(c);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(lightValue);
        +   *   rect(50, 15, 35, 70);
        +   *
        +   *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.lightness = function(c) {
        +    // p5._validateParameters('lightness', arguments);
        +    return this.color(c)._getLightness();
        +  };
         
        -/**
        - * Gets the saturation value of a color.
        - *
        - * `saturation()` extracts the saturation value from a
        - * <a href="#/p5.Color">p5.Color</a> object, an array of color components, or
        - * a CSS color string.
        - *
        - * Saturation is scaled differently in HSB and HSL. By default, `saturation()`
        - * returns a color's HSL saturation in the range 0 to 100. If the
        - * <a href="#/colorMode">colorMode()</a> is set to HSB or HSL, it returns the
        - * saturation value in the given mode.
        - *
        - * @method saturation
        - * @param {p5.Color|Number[]|String} color <a href="#/p5.Color">p5.Color</a> object, array of
        - *                                         color components, or CSS color string.
        - * @return {Number} the saturation value
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Use HSB color.
        - *   colorMode(HSB);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color(0, 50, 100);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'satValue' to 50.
        - *   let satValue = saturation(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(satValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is dark gray.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Use HSB color.
        - *   colorMode(HSB);
        - *
        - *   // Create a color array.
        - *   let c = [0, 50, 100];
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'satValue' to 100.
        - *   let satValue = saturation(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(satValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Use HSB color.
        - *   colorMode(HSB);
        - *
        - *   // Create a CSS color string.
        - *   let c = 'rgb(255, 128, 128)';
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'satValue' to 100.
        - *   let satValue = saturation(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(satValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is gray.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Use HSL color.
        - *   colorMode(HSL);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color(0, 100, 75);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'satValue' to 100.
        - *   let satValue = saturation(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(satValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Use HSL color with values in the range 0-255.
        - *   colorMode(HSL, 255);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color(0, 255, 191.5);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(c);
        - *   rect(15, 15, 35, 70);
        - *
        - *   // Set 'satValue' to 255.
        - *   let satValue = saturation(c);
        - *
        - *   // Draw the right rectangle.
        - *   fill(satValue);
        - *   rect(50, 15, 35, 70);
        - *
        - *   describe('Two rectangles. The left one is salmon pink and the right one is white.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.saturation = function(c) {
        -  p5._validateParameters('saturation', arguments);
        -  return this.color(c)._getSaturation();
        -};
        +  /**
        +   * Blends two colors to find a third color between them.
        +   *
        +   * The `amt` parameter specifies the amount to interpolate between the two
        +   * values. 0 is equal to the first color, 0.1 is very near the first color,
        +   * 0.5 is halfway between the two colors, and so on. Negative numbers are set
        +   * to 0. Numbers greater than 1 are set to 1. This differs from the behavior of
        +   * <a href="#/lerp">lerp</a>. It's necessary because numbers outside of the
        +   * interval [0, 1] will produce strange and unexpected colors.
        +   *
        +   * The way that colors are interpolated depends on the current
        +   * <a href="#/colorMode">colorMode()</a>.
        +   *
        +   * @method lerpColor
        +   * @param  {p5.Color} c1  interpolate from this color.
        +   * @param  {p5.Color} c2  interpolate to this color.
        +   * @param  {Number}   amt number between 0 and 1.
        +   * @return {p5.Color}     interpolated color.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create p5.Color objects to interpolate between.
        +   *   let from = color(218, 165, 32);
        +   *   let to = color(72, 61, 139);
        +   *
        +   *   // Create intermediate colors.
        +   *   let interA = lerpColor(from, to, 0.33);
        +   *   let interB = lerpColor(from, to, 0.66);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(from);
        +   *   rect(10, 20, 20, 60);
        +   *
        +   *   // Draw the left-center rectangle.
        +   *   fill(interA);
        +   *   rect(30, 20, 20, 60);
        +   *
        +   *   // Draw the right-center rectangle.
        +   *   fill(interB);
        +   *   rect(50, 20, 20, 60);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(to);
        +   *   rect(70, 20, 20, 60);
        +   *
        +   *   describe(
        +   *     'Four rectangles. From left to right, the rectangles are tan, brown, brownish purple, and purple.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.lerpColor = function(c1, c2, amt) {
        +    // p5._validateParameters('lerpColor', arguments);
        +    return c1.lerp(c2, amt, this._renderer.states.colorMode);
        +  };
        +
        +  /**
        +   * Blends multiple colors to find a color between them.
        +   *
        +   * The `amt` parameter specifies the amount to interpolate between the color
        +   * stops which are colors at each `amt` value "location" with `amt` values
        +   * that are between 2 color stops interpolating between them based on its relative
        +   * distance to both.
        +   *
        +   * The way that colors are interpolated depends on the current
        +   * <a href="#/colorMode">colorMode()</a>.
        +   *
        +   * @method paletteLerp
        +   * @param  {[p5.Color|String|Number|Number[], Number][]} colors_stops color stops to interpolate from
        +   * @param  {Number} amt number to use to interpolate relative to color stops
        +   * @return {p5.Color} interpolated color.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(400, 400);
        +   * }
        +   *
        +   * function draw() {
        +   *   // The background goes from white to red to green to blue fill
        +   *   background(paletteLerp([
        +   *     ['white', 0],
        +   *     ['red', 0.05],
        +   *     ['green', 0.25],
        +   *     ['blue', 1]
        +   *   ], millis() / 10000 % 1));
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.paletteLerp = function(color_stops, amt) {
        +    const first_color_stop = color_stops[0];
        +    if (amt < first_color_stop[1])
        +      return this.color(first_color_stop[0]);
        +
        +    for (let i = 1; i < color_stops.length; i++) {
        +      const color_stop = color_stops[i];
        +      if (amt < color_stop[1]) {
        +        const prev_color_stop = color_stops[i - 1];
        +        return this.lerpColor(
        +          this.color(prev_color_stop[0]),
        +          this.color(color_stop[0]),
        +          (amt - prev_color_stop[1]) / (color_stop[1] - prev_color_stop[1])
        +        );
        +      }
        +    }
        +
        +    return this.color(color_stops[color_stops.length - 1][0]);
        +  };
        +}
        +
        +export default creatingReading;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  creatingReading(p5, p5.prototype);
        +}
        diff --git a/src/color/index.js b/src/color/index.js
        new file mode 100644
        index 0000000000..bd0fb96f58
        --- /dev/null
        +++ b/src/color/index.js
        @@ -0,0 +1,9 @@
        +import creatingReading from './creating_reading.js';
        +import p5color from './p5.Color.js';
        +import setting from './setting.js';
        +
        +export default function(p5){
        +  p5.registerAddon(creatingReading);
        +  p5.registerAddon(p5color);
        +  p5.registerAddon(setting);
        +}
        diff --git a/src/color/p5.Color.culori.js b/src/color/p5.Color.culori.js
        new file mode 100644
        index 0000000000..0e184b13dc
        --- /dev/null
        +++ b/src/color/p5.Color.culori.js
        @@ -0,0 +1,66 @@
        +class Color {
        +  _color;
        +  maxes;
        +  mode;
        +  array;
        +
        +  static addColorMode(mode, definition){
        +
        +  }
        +
        +  constructor(vals){
        +
        +  }
        +
        +  toString(){
        +
        +  }
        +
        +  setRed(new_red){
        +
        +  }
        +
        +  setGreen(new_green){
        +
        +  }
        +
        +  setBlue(new_blue){
        +
        +  }
        +
        +  setAlpha(new_alpha){
        +
        +  }
        +
        +  _getRed(){
        +
        +  }
        +
        +  _getGreen(){
        +
        +  }
        +
        +  _getBlue(){
        +
        +  }
        +
        +  _getAlpha(){
        +
        +  }
        +
        +  _getHue(){
        +
        +  }
        +
        +  _getSaturation(){
        +
        +  }
        +
        +  _getBrightness(){
        +
        +  }
        +
        +  _getLightness(){
        +
        +  }
        +}
        diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js
        index 5dee81c61d..2a2a3824ac 100644
        --- a/src/color/p5.Color.js
        +++ b/src/color/p5.Color.js
        @@ -3,360 +3,200 @@
          * @submodule Creating & Reading
          * @for p5
          * @requires core
        - * @requires constants
          * @requires color_conversion
          */
         
        -import p5 from '../core/main';
        -import * as constants from '../core/constants';
        -import color_conversion from './color_conversion';
        +import { RGB, RGBHDR, HSL, HSB, HWB, LAB, LCH, OKLAB, OKLCH } from './creating_reading';
        +
        +import {
        +  ColorSpace,
        +  to,
        +  toGamut,
        +  serialize,
        +  parse,
        +  range,
        +
        +  sRGB,
        +  HSL as HSLSpace,
        +  HWB as HWBSpace,
        +
        +  Lab,
        +  LCH as LCHSpace,
        +
        +  OKLab,
        +  OKLCH as OKLCHSpace,
        +
        +  P3
        +} from 'colorjs.io/fn';
        +import HSBSpace from './color_spaces/hsb.js';
        +
        +const map = (n, start1, stop1, start2, stop2) =>
        +  ((n - start1) / (stop1 - start1) * (stop2 - start2) + start2);
        +
        +class Color {
        +  // Reference to underlying color object depending on implementation
        +  // Not meant to be used publicly unless the implementation is known for sure
        +  _color;
        +  // Color mode of the Color object, uses p5 color modes
        +  mode;
        +
        +  static colorMap = {};
        +  static #colorjsMaxes = {};
        +
        +  // Used to add additional color modes to p5.js
        +  // Uses underlying library's definition
        +  static addColorMode(mode, definition){
        +    ColorSpace.register(definition);
        +    Color.colorMap[mode] = definition.id;
        +    // Get colorjs maxes
        +    Color.#colorjsMaxes[mode] = Object.values(definition.coords).reduce((acc, v) => {
        +        acc.push(v.refRange || v.range);
        +        return acc;
        +      }, []);
        +    Color.#colorjsMaxes[mode].push([0, 1]);
        +  }
         
        -/**
        - * CSS named colors.
        - */
        -const namedColors = {
        -  aliceblue: '#f0f8ff',
        -  antiquewhite: '#faebd7',
        -  aqua: '#00ffff',
        -  aquamarine: '#7fffd4',
        -  azure: '#f0ffff',
        -  beige: '#f5f5dc',
        -  bisque: '#ffe4c4',
        -  black: '#000000',
        -  blanchedalmond: '#ffebcd',
        -  blue: '#0000ff',
        -  blueviolet: '#8a2be2',
        -  brown: '#a52a2a',
        -  burlywood: '#deb887',
        -  cadetblue: '#5f9ea0',
        -  chartreuse: '#7fff00',
        -  chocolate: '#d2691e',
        -  coral: '#ff7f50',
        -  cornflowerblue: '#6495ed',
        -  cornsilk: '#fff8dc',
        -  crimson: '#dc143c',
        -  cyan: '#00ffff',
        -  darkblue: '#00008b',
        -  darkcyan: '#008b8b',
        -  darkgoldenrod: '#b8860b',
        -  darkgray: '#a9a9a9',
        -  darkgreen: '#006400',
        -  darkgrey: '#a9a9a9',
        -  darkkhaki: '#bdb76b',
        -  darkmagenta: '#8b008b',
        -  darkolivegreen: '#556b2f',
        -  darkorange: '#ff8c00',
        -  darkorchid: '#9932cc',
        -  darkred: '#8b0000',
        -  darksalmon: '#e9967a',
        -  darkseagreen: '#8fbc8f',
        -  darkslateblue: '#483d8b',
        -  darkslategray: '#2f4f4f',
        -  darkslategrey: '#2f4f4f',
        -  darkturquoise: '#00ced1',
        -  darkviolet: '#9400d3',
        -  deeppink: '#ff1493',
        -  deepskyblue: '#00bfff',
        -  dimgray: '#696969',
        -  dimgrey: '#696969',
        -  dodgerblue: '#1e90ff',
        -  firebrick: '#b22222',
        -  floralwhite: '#fffaf0',
        -  forestgreen: '#228b22',
        -  fuchsia: '#ff00ff',
        -  gainsboro: '#dcdcdc',
        -  ghostwhite: '#f8f8ff',
        -  gold: '#ffd700',
        -  goldenrod: '#daa520',
        -  gray: '#808080',
        -  green: '#008000',
        -  greenyellow: '#adff2f',
        -  grey: '#808080',
        -  honeydew: '#f0fff0',
        -  hotpink: '#ff69b4',
        -  indianred: '#cd5c5c',
        -  indigo: '#4b0082',
        -  ivory: '#fffff0',
        -  khaki: '#f0e68c',
        -  lavender: '#e6e6fa',
        -  lavenderblush: '#fff0f5',
        -  lawngreen: '#7cfc00',
        -  lemonchiffon: '#fffacd',
        -  lightblue: '#add8e6',
        -  lightcoral: '#f08080',
        -  lightcyan: '#e0ffff',
        -  lightgoldenrodyellow: '#fafad2',
        -  lightgray: '#d3d3d3',
        -  lightgreen: '#90ee90',
        -  lightgrey: '#d3d3d3',
        -  lightpink: '#ffb6c1',
        -  lightsalmon: '#ffa07a',
        -  lightseagreen: '#20b2aa',
        -  lightskyblue: '#87cefa',
        -  lightslategray: '#778899',
        -  lightslategrey: '#778899',
        -  lightsteelblue: '#b0c4de',
        -  lightyellow: '#ffffe0',
        -  lime: '#00ff00',
        -  limegreen: '#32cd32',
        -  linen: '#faf0e6',
        -  magenta: '#ff00ff',
        -  maroon: '#800000',
        -  mediumaquamarine: '#66cdaa',
        -  mediumblue: '#0000cd',
        -  mediumorchid: '#ba55d3',
        -  mediumpurple: '#9370db',
        -  mediumseagreen: '#3cb371',
        -  mediumslateblue: '#7b68ee',
        -  mediumspringgreen: '#00fa9a',
        -  mediumturquoise: '#48d1cc',
        -  mediumvioletred: '#c71585',
        -  midnightblue: '#191970',
        -  mintcream: '#f5fffa',
        -  mistyrose: '#ffe4e1',
        -  moccasin: '#ffe4b5',
        -  navajowhite: '#ffdead',
        -  navy: '#000080',
        -  oldlace: '#fdf5e6',
        -  olive: '#808000',
        -  olivedrab: '#6b8e23',
        -  orange: '#ffa500',
        -  orangered: '#ff4500',
        -  orchid: '#da70d6',
        -  palegoldenrod: '#eee8aa',
        -  palegreen: '#98fb98',
        -  paleturquoise: '#afeeee',
        -  palevioletred: '#db7093',
        -  papayawhip: '#ffefd5',
        -  peachpuff: '#ffdab9',
        -  peru: '#cd853f',
        -  pink: '#ffc0cb',
        -  plum: '#dda0dd',
        -  powderblue: '#b0e0e6',
        -  purple: '#800080',
        -  rebeccapurple: '#663399',
        -  red: '#ff0000',
        -  rosybrown: '#bc8f8f',
        -  royalblue: '#4169e1',
        -  saddlebrown: '#8b4513',
        -  salmon: '#fa8072',
        -  sandybrown: '#f4a460',
        -  seagreen: '#2e8b57',
        -  seashell: '#fff5ee',
        -  sienna: '#a0522d',
        -  silver: '#c0c0c0',
        -  skyblue: '#87ceeb',
        -  slateblue: '#6a5acd',
        -  slategray: '#708090',
        -  slategrey: '#708090',
        -  snow: '#fffafa',
        -  springgreen: '#00ff7f',
        -  steelblue: '#4682b4',
        -  tan: '#d2b48c',
        -  teal: '#008080',
        -  thistle: '#d8bfd8',
        -  tomato: '#ff6347',
        -  turquoise: '#40e0d0',
        -  violet: '#ee82ee',
        -  wheat: '#f5deb3',
        -  white: '#ffffff',
        -  whitesmoke: '#f5f5f5',
        -  yellow: '#ffff00',
        -  yellowgreen: '#9acd32'
        -};
        +  constructor(vals, colorMode, colorMaxes) {
        +    // This changes with the color object
        +    this.mode = colorMode || RGB;
        +
        +    if(vals instanceof Color){
        +      // Received Color object to be used for color mode conversion
        +      const mode = colorMode ?
        +        Color.colorMap[colorMode] :
        +        Color.colorMap[vals.mode];
        +      this._color = to(vals._color, mode);
        +      this.mode = mode;
        +
        +    }else if (typeof vals === 'object' && !Array.isArray(vals) && vals !== null){
        +      // Received color.js object to be used internally
        +      const mode = colorMode ?
        +        Color.colorMap[colorMode] :
        +        vals.spaceId;
        +      this._color = to(vals, mode);
        +      this.mode = colorMode || Object.entries(Color.colorMap).find(([key, val]) => {
        +          return val === this._color.spaceId;
        +        });
        +
        +    } else if(typeof vals[0] === 'string') {
        +      // Received string
        +      try{
        +        this._color = parse(vals[0]);
        +        const [mode] = Object.entries(Color.colorMap).find(([key, val]) => {
        +          return val === this._color.spaceId;
        +        });
        +        this.mode = mode;
        +        this._color = to(this._color, this._color.spaceId);
        +      }catch(err){
        +        // TODO: Invalid color string
        +        throw new Error('Invalid color string');
        +      }
         
        -/**
        - * These regular expressions are used to build up the patterns for matching
        - * viable CSS color strings: fragmenting the regexes in this way increases the
        - * legibility and comprehensibility of the code.
        - *
        - * Note that RGB values of .9 are not parsed by IE, but are supported here for
        - * color string consistency.
        - */
        -const WHITESPACE = /\s*/; // Match zero or more whitespace characters.
        -const INTEGER = /(\d{1,3})/; // Match integers: 79, 255, etc.
        -const DECIMAL = /((?:\d+(?:\.\d+)?)|(?:\.\d+))/; // Match 129.6, 79, .9, etc.
        -const PERCENT = new RegExp(`${DECIMAL.source}%`); // Match 12.9%, 79%, .9%, etc.
        +    }else{
        +      // Received individual channel values
        +      let mappedVals;
        +
        +      if(colorMaxes){
        +        if(vals.length === 4){
        +          mappedVals = Color.mapColorRange(vals, this.mode, colorMaxes);
        +        }else if(vals.length === 3){
        +          mappedVals = Color.mapColorRange([vals[0], vals[1], vals[2]], this.mode, colorMaxes);
        +          mappedVals.push(1);
        +        }else if(vals.length === 2){
        +          mappedVals = Color.mapColorRange([vals[0], vals[0], vals[0], vals[1]], this.mode, colorMaxes);
        +        }else if(vals.length === 1){
        +          mappedVals = Color.mapColorRange([vals[0], vals[0], vals[0]], this.mode, colorMaxes);
        +          mappedVals.push(1);
        +        }else{
        +          throw new Error('Invalid color');
        +        }
        +      }else{
        +        mappedVals = vals;
        +      }
         
        -/**
        - * Full color string patterns. The capture groups are necessary.
        - */
        -const colorPatterns = {
        -  // Match colors in format #XXX, e.g. #416.
        -  HEX3: /^#([a-f0-9])([a-f0-9])([a-f0-9])$/i,
        -
        -  // Match colors in format #XXXX, e.g. #5123.
        -  HEX4: /^#([a-f0-9])([a-f0-9])([a-f0-9])([a-f0-9])$/i,
        -
        -  // Match colors in format #XXXXXX, e.g. #b4d455.
        -  HEX6: /^#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i,
        -
        -  // Match colors in format #XXXXXXXX, e.g. #b4d45535.
        -  HEX8: /^#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i,
        -
        -  // Match colors in format rgb(R, G, B), e.g. rgb(255, 0, 128).
        -  RGB: new RegExp(
        -    [
        -      '^rgb\\(',
        -      INTEGER.source,
        -      ',',
        -      INTEGER.source,
        -      ',',
        -      INTEGER.source,
        -      '\\)$'
        -    ].join(WHITESPACE.source),
        -    'i'
        -  ),
        -
        -  // Match colors in format rgb(R%, G%, B%), e.g. rgb(100%, 0%, 28.9%).
        -  RGB_PERCENT: new RegExp(
        -    [
        -      '^rgb\\(',
        -      PERCENT.source,
        -      ',',
        -      PERCENT.source,
        -      ',',
        -      PERCENT.source,
        -      '\\)$'
        -    ].join(WHITESPACE.source),
        -    'i'
        -  ),
        -
        -  // Match colors in format rgb(R, G, B, A), e.g. rgb(255, 0, 128, 0.25).
        -  RGBA: new RegExp(
        -    [
        -      '^rgba\\(',
        -      INTEGER.source,
        -      ',',
        -      INTEGER.source,
        -      ',',
        -      INTEGER.source,
        -      ',',
        -      DECIMAL.source,
        -      '\\)$'
        -    ].join(WHITESPACE.source),
        -    'i'
        -  ),
        -
        -  // Match colors in format rgb(R%, G%, B%, A), e.g. rgb(100%, 0%, 28.9%, 0.5).
        -  RGBA_PERCENT: new RegExp(
        -    [
        -      '^rgba\\(',
        -      PERCENT.source,
        -      ',',
        -      PERCENT.source,
        -      ',',
        -      PERCENT.source,
        -      ',',
        -      DECIMAL.source,
        -      '\\)$'
        -    ].join(WHITESPACE.source),
        -    'i'
        -  ),
        -
        -  // Match colors in format hsla(H, S%, L%), e.g. hsl(100, 40%, 28.9%).
        -  HSL: new RegExp(
        -    [
        -      '^hsl\\(',
        -      INTEGER.source,
        -      ',',
        -      PERCENT.source,
        -      ',',
        -      PERCENT.source,
        -      '\\)$'
        -    ].join(WHITESPACE.source),
        -    'i'
        -  ),
        -
        -  // Match colors in format hsla(H, S%, L%, A), e.g. hsla(100, 40%, 28.9%, 0.5).
        -  HSLA: new RegExp(
        -    [
        -      '^hsla\\(',
        -      INTEGER.source,
        -      ',',
        -      PERCENT.source,
        -      ',',
        -      PERCENT.source,
        -      ',',
        -      DECIMAL.source,
        -      '\\)$'
        -    ].join(WHITESPACE.source),
        -    'i'
        -  ),
        -
        -  // Match colors in format hsb(H, S%, B%), e.g. hsb(100, 40%, 28.9%).
        -  HSB: new RegExp(
        -    [
        -      '^hsb\\(',
        -      INTEGER.source,
        -      ',',
        -      PERCENT.source,
        -      ',',
        -      PERCENT.source,
        -      '\\)$'
        -    ].join(WHITESPACE.source),
        -    'i'
        -  ),
        -
        -  // Match colors in format hsba(H, S%, B%, A), e.g. hsba(100, 40%, 28.9%, 0.5).
        -  HSBA: new RegExp(
        -    [
        -      '^hsba\\(',
        -      INTEGER.source,
        -      ',',
        -      PERCENT.source,
        -      ',',
        -      PERCENT.source,
        -      ',',
        -      DECIMAL.source,
        -      '\\)$'
        -    ].join(WHITESPACE.source),
        -    'i'
        -  )
        -};
        +      const space = Color.colorMap[this.mode] || console.error('Invalid color mode');
        +      const coords = mappedVals.slice(0, 3);
         
        -/**
        - * A class to describe a color.
        - *
        - * Each `p5.Color` object stores the color mode
        - * and level maxes that were active during its construction. These values are
        - * used to interpret the arguments passed to the object's constructor. They
        - * also determine output formatting such as when
        - * <a href="#/p5/saturation">saturation()</a> is called.
        - *
        - * Color is stored internally as an array of ideal RGBA values in floating
        - * point form, normalized from 0 to 1. These values are used to calculate the
        - * closest screen colors, which are RGBA levels from 0 to 255. Screen colors
        - * are sent to the renderer.
        - *
        - * When different color representations are calculated, the results are cached
        - * for performance. These values are normalized, floating-point numbers.
        - *
        - * Note: <a href="#/p5/color">color()</a> is the recommended way to create an
        - * instance of this class.
        - *
        - * @class p5.Color
        - * @constructor
        - * @param {p5} [pInst]                      pointer to p5 instance.
        - *
        - * @param {Number[]|String} vals            an array containing the color values
        - *                                          for red, green, blue and alpha channel
        - *                                          or CSS color.
        - */
        -p5.Color = class Color {
        -  constructor(pInst, vals) {
        -    // Record color mode and maxes at time of construction.
        -    this._storeModeAndMaxes(pInst._colorMode, pInst._colorMaxes);
        -
        -    // Calculate normalized RGBA values.
        -    if (![constants.RGB, constants.HSL, constants.HSB].includes(this.mode)) {
        -      throw new Error(`${this.mode} is an invalid colorMode.`);
        -    } else {
        -      this._array = Color._parseInputs.apply(this, vals);
        +      const color = {
        +        space,
        +        coords,
        +        alpha: mappedVals[3]
        +      };
        +      this._color = to(color, space);
        +    }
        +  }
        +
        +  // Convert from p5 color range to color.js color range
        +  static mapColorRange(origin, mode, maxes){
        +    const p5Maxes = maxes.map((max) => {
        +      if(!Array.isArray(max)){
        +        return [0, max];
        +      }else{
        +        return max;
        +      }
        +    });
        +    const colorjsMaxes = Color.#colorjsMaxes[mode];
        +
        +    return origin.map((channel, i) => {
        +      const newval = map(channel, p5Maxes[i][0], p5Maxes[i][1], colorjsMaxes[i][0], colorjsMaxes[i][1]);
        +      return newval;
        +    });
        +  }
        +
        +  // Convert from color.js color range to p5 color range
        +  static unmapColorRange(origin, mode, maxes){
        +    const p5Maxes = maxes.map((max) => {
        +      if(!Array.isArray(max)){
        +        return [0, max];
        +      }else{
        +        return max;
        +      }
        +    });
        +    const colorjsMaxes = Color.#colorjsMaxes[mode];
        +
        +    return origin.map((channel, i) => {
        +      const newval = map(channel, colorjsMaxes[i][0], colorjsMaxes[i][1], p5Maxes[i][0], p5Maxes[i][1]);
        +      return newval;
        +    });
        +  }
        +
        +  // Will do conversion in-Gamut as out of Gamut conversion is only really useful for futher conversions
        +  #toColorMode(mode){
        +    return new Color(this._color, mode);
        +  }
        +
        +  // Get raw coordinates of underlying library, can differ between libraries
        +  get _array() {
        +    return [...this._color.coords, this._color.alpha];
        +  }
        +
        +  array(){
        +    return this._array;
        +  }
        +
        +  lerp(color, amt, mode){
        +    // Find the closest common ancestor color space
        +    let spaceIndex = -1;
        +    while(
        +      (
        +        spaceIndex+1 < this._color.space.path.length ||
        +        spaceIndex+1 < color._color.space.path.length
        +      ) &&
        +      this._color.space.path[spaceIndex+1] === color._color.space.path[spaceIndex+1]
        +    ){
        +      spaceIndex += 1;
             }
         
        -    // Expose closest screen color.
        -    this._calculateLevels();
        +    if (spaceIndex === -1) {
        +      // This probably will not occur in practice
        +      throw new Error('Cannot lerp colors. No common color space found');
        +    }
        +
        +    const obj = range(this._color, color._color, {
        +      space: this._color.space.path[spaceIndex].id
        +    })(amt);
        +
        +    return new Color(obj, mode || this.mode);
           }
         
           /**
        @@ -370,7 +210,6 @@ p5.Color = class Color {
            * `myColor.toString('#rrggbb')`, it will determine how the color string is
            * formatted. By default, color strings are formatted as `'rgba(r, g, b, a)'`.
            *
        -   * @method toString
            * @param {String} [format] how the color string will be formatted.
            * Leaving this empty formats the string as rgba(r, g, b, a).
            * '#rgb' '#rgba' '#rrggbb' and '#rrggbbaa' format as hexadecimal color codes.
        @@ -403,170 +242,10 @@ p5.Color = class Color {
            * </div>
            */
           toString(format) {
        -    const a = this.levels;
        -    const f = this._array;
        -    const alpha = f[3]; // String representation uses normalized alpha
        -
        -    switch (format) {
        -      case '#rrggbb':
        -        return '#'.concat(
        -          a[0] < 16 ? '0'.concat(a[0].toString(16)) : a[0].toString(16),
        -          a[1] < 16 ? '0'.concat(a[1].toString(16)) : a[1].toString(16),
        -          a[2] < 16 ? '0'.concat(a[2].toString(16)) : a[2].toString(16)
        -        );
        -
        -      case '#rrggbbaa':
        -        return '#'.concat(
        -          a[0] < 16 ? '0'.concat(a[0].toString(16)) : a[0].toString(16),
        -          a[1] < 16 ? '0'.concat(a[1].toString(16)) : a[1].toString(16),
        -          a[2] < 16 ? '0'.concat(a[2].toString(16)) : a[2].toString(16),
        -          a[3] < 16 ? '0'.concat(a[3].toString(16)) : a[3].toString(16)
        -        );
        -
        -      case '#rgb':
        -        return '#'.concat(
        -          Math.round(f[0] * 15).toString(16),
        -          Math.round(f[1] * 15).toString(16),
        -          Math.round(f[2] * 15).toString(16)
        -        );
        -
        -      case '#rgba':
        -        return '#'.concat(
        -          Math.round(f[0] * 15).toString(16),
        -          Math.round(f[1] * 15).toString(16),
        -          Math.round(f[2] * 15).toString(16),
        -          Math.round(f[3] * 15).toString(16)
        -        );
        -
        -      case 'rgb':
        -        return 'rgb('.concat(a[0], ', ', a[1], ', ', a[2], ')');
        -
        -      case 'rgb%':
        -        return 'rgb('.concat(
        -          (100 * f[0]).toPrecision(3),
        -          '%, ',
        -          (100 * f[1]).toPrecision(3),
        -          '%, ',
        -          (100 * f[2]).toPrecision(3),
        -          '%)'
        -        );
        -
        -      case 'rgba%':
        -        return 'rgba('.concat(
        -          (100 * f[0]).toPrecision(3),
        -          '%, ',
        -          (100 * f[1]).toPrecision(3),
        -          '%, ',
        -          (100 * f[2]).toPrecision(3),
        -          '%, ',
        -          (100 * f[3]).toPrecision(3),
        -          '%)'
        -        );
        -
        -      case 'hsb':
        -      case 'hsv':
        -        if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array);
        -        return 'hsb('.concat(
        -          this.hsba[0] * this.maxes[constants.HSB][0],
        -          ', ',
        -          this.hsba[1] * this.maxes[constants.HSB][1],
        -          ', ',
        -          this.hsba[2] * this.maxes[constants.HSB][2],
        -          ')'
        -        );
        -
        -      case 'hsb%':
        -      case 'hsv%':
        -        if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array);
        -        return 'hsb('.concat(
        -          (100 * this.hsba[0]).toPrecision(3),
        -          '%, ',
        -          (100 * this.hsba[1]).toPrecision(3),
        -          '%, ',
        -          (100 * this.hsba[2]).toPrecision(3),
        -          '%)'
        -        );
        -
        -      case 'hsba':
        -      case 'hsva':
        -        if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array);
        -        return 'hsba('.concat(
        -          this.hsba[0] * this.maxes[constants.HSB][0],
        -          ', ',
        -          this.hsba[1] * this.maxes[constants.HSB][1],
        -          ', ',
        -          this.hsba[2] * this.maxes[constants.HSB][2],
        -          ', ',
        -          alpha,
        -          ')'
        -        );
        -
        -      case 'hsba%':
        -      case 'hsva%':
        -        if (!this.hsba) this.hsba = color_conversion._rgbaToHSBA(this._array);
        -        return 'hsba('.concat(
        -          (100 * this.hsba[0]).toPrecision(3),
        -          '%, ',
        -          (100 * this.hsba[1]).toPrecision(3),
        -          '%, ',
        -          (100 * this.hsba[2]).toPrecision(3),
        -          '%, ',
        -          (100 * alpha).toPrecision(3),
        -          '%)'
        -        );
        -
        -      case 'hsl':
        -        if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array);
        -        return 'hsl('.concat(
        -          this.hsla[0] * this.maxes[constants.HSL][0],
        -          ', ',
        -          this.hsla[1] * this.maxes[constants.HSL][1],
        -          ', ',
        -          this.hsla[2] * this.maxes[constants.HSL][2],
        -          ')'
        -        );
        -
        -      case 'hsl%':
        -        if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array);
        -        return 'hsl('.concat(
        -          (100 * this.hsla[0]).toPrecision(3),
        -          '%, ',
        -          (100 * this.hsla[1]).toPrecision(3),
        -          '%, ',
        -          (100 * this.hsla[2]).toPrecision(3),
        -          '%)'
        -        );
        -
        -      case 'hsla':
        -        if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array);
        -        return 'hsla('.concat(
        -          this.hsla[0] * this.maxes[constants.HSL][0],
        -          ', ',
        -          this.hsla[1] * this.maxes[constants.HSL][1],
        -          ', ',
        -          this.hsla[2] * this.maxes[constants.HSL][2],
        -          ', ',
        -          alpha,
        -          ')'
        -        );
        -
        -      case 'hsla%':
        -        if (!this.hsla) this.hsla = color_conversion._rgbaToHSLA(this._array);
        -        return 'hsl('.concat(
        -          (100 * this.hsla[0]).toPrecision(3),
        -          '%, ',
        -          (100 * this.hsla[1]).toPrecision(3),
        -          '%, ',
        -          (100 * this.hsla[2]).toPrecision(3),
        -          '%, ',
        -          (100 * alpha).toPrecision(3),
        -          '%)'
        -        );
        -
        -      case 'rgba':
        -      default:
        -        return 'rgba('.concat(a[0], ',', a[1], ',', a[2], ',', alpha, ')');
        -    }
        +    // NOTE: memoize
        +    return serialize(this._color, {
        +      format
        +    });
           }
         
           /**
        @@ -575,7 +254,6 @@ p5.Color = class Color {
            * The range depends on the <a href="#/p5/colorMode">colorMode()</a>. In the
            * default RGB mode it's between 0 and 255.
            *
        -   * @method setRed
            * @param {Number} red the new red value.
            *
            * @example
        @@ -606,9 +284,23 @@ p5.Color = class Color {
            * </code>
            * </div>
            */
        -  setRed(new_red) {
        -    this._array[0] = new_red / this.maxes[constants.RGB][0];
        -    this._calculateLevels();
        +  setRed(new_red, max=[0, 1]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
        +    }
        +
        +    const colorjsMax = Color.#colorjsMaxes[RGB][0];
        +    const newval = map(new_red, max[0], max[1], colorjsMax[0], colorjsMax[1]);
        +
        +    if(this.mode === RGB || this.mode === RGBHDR){
        +      this._color.coords[0] = newval;
        +    }else{
        +      // Will do an imprecise conversion to 'srgb', not recommended
        +      const space = this._color.space.id;
        +      const representation = to(this._color, 'srgb');
        +      representation.coords[0] = newval;
        +      this._color = to(representation, space);
        +    }
           }
         
           /**
        @@ -617,7 +309,6 @@ p5.Color = class Color {
            * The range depends on the <a href="#/p5/colorMode">colorMode()</a>. In the
            * default RGB mode it's between 0 and 255.
            *
        -   * @method setGreen
            * @param {Number} green the new green value.
            *
            * @example
        @@ -648,9 +339,23 @@ p5.Color = class Color {
            * </code>
            * </div>
            **/
        -  setGreen(new_green) {
        -    this._array[1] = new_green / this.maxes[constants.RGB][1];
        -    this._calculateLevels();
        +  setGreen(new_green, max=[0, 1]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
        +    }
        +
        +    const colorjsMax = Color.#colorjsMaxes[RGB][1];
        +    const newval = map(new_green, max[0], max[1], colorjsMax[0], colorjsMax[1]);
        +
        +    if(this.mode === RGB || this.mode === RGBHDR){
        +      this._color.coords[1] = newval;
        +    }else{
        +      // Will do an imprecise conversion to 'srgb', not recommended
        +      const space = this._color.space.id;
        +      const representation = to(this._color, 'srgb');
        +      representation.coords[1] = newval;
        +      this._color = to(representation, space);
        +    }
           }
         
           /**
        @@ -659,7 +364,6 @@ p5.Color = class Color {
            * The range depends on the <a href="#/p5/colorMode">colorMode()</a>. In the
            * default RGB mode it's between 0 and 255.
            *
        -   * @method setBlue
            * @param {Number} blue the new blue value.
            *
            * @example
        @@ -690,9 +394,23 @@ p5.Color = class Color {
            * </code>
            * </div>
            **/
        -  setBlue(new_blue) {
        -    this._array[2] = new_blue / this.maxes[constants.RGB][2];
        -    this._calculateLevels();
        +  setBlue(new_blue, max=[0, 1]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
        +    }
        +
        +    const colorjsMax = Color.#colorjsMaxes[RGB][2];
        +    const newval = map(new_blue, max[0], max[1], colorjsMax[0], colorjsMax[1]);
        +
        +    if(this.mode === RGB || this.mode === RGBHDR){
        +      this._color.coords[2] = newval;
        +    }else{
        +      // Will do an imprecise conversion to 'srgb', not recommended
        +      const space = this._color.space.id;
        +      const representation = to(this._color, 'srgb');
        +      representation.coords[2] = newval;
        +      this._color = to(representation, space);
        +    }
           }
         
           /**
        @@ -702,7 +420,6 @@ p5.Color = class Color {
            * <a href="#/p5/colorMode">colorMode()</a>. In the default RGB mode it's
            * between 0 and 255.
            *
        -   * @method setAlpha
            * @param {Number} alpha the new alpha value.
            *
            * @example
        @@ -733,57 +450,96 @@ p5.Color = class Color {
            * </code>
            * </div>
            **/
        -  setAlpha(new_alpha) {
        -    this._array[3] = new_alpha / this.maxes[this.mode][3];
        -    this._calculateLevels();
        -  }
        -
        -  // calculates and stores the closest screen levels
        -  _calculateLevels() {
        -    const array = this._array;
        -    // (loop backwards for performance)
        -    const levels = (this.levels = new Array(array.length));
        -    for (let i = array.length - 1; i >= 0; --i) {
        -      levels[i] = Math.round(array[i] * 255);
        +  setAlpha(new_alpha, max=[0, 1]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
             }
         
        -    // Clear cached HSL/HSB values
        -    this.hsla = null;
        -    this.hsba = null;
        -  }
        +    const colorjsMax = Color.#colorjsMaxes[this.mode][3];
        +    const newval = map(new_alpha, max[0], max[1], colorjsMax[0], colorjsMax[1]);
         
        -  _getAlpha() {
        -    return this._array[3] * this.maxes[this.mode][3];
        +    this._color.alpha = newval;
           }
         
        -  // stores the color mode and maxes in this instance of Color
        -  // for later use (by _parseInputs())
        -  _storeModeAndMaxes(new_mode, new_maxes) {
        -    this.mode = new_mode;
        -    this.maxes = new_maxes;
        +  _getRGBA(maxes=[1, 1, 1, 1]) {
        +    // Get colorjs maxes
        +    const colorjsMaxes = Color.#colorjsMaxes[this.mode];
        +
        +    // Normalize everything to 0,1 or the provided range (map)
        +    let coords = structuredClone(to(this._color, 'srgb').coords);
        +    coords.push(this._color.alpha);
        +
        +    const rangeMaxes = maxes.map((v) => {
        +      if(!Array.isArray(v)){
        +        return [0, v];
        +      }else{
        +        return v
        +      }
        +    });
        +
        +    coords = coords.map((coord, i) => {
        +      return map(coord, colorjsMaxes[i][0], colorjsMaxes[i][1], rangeMaxes[i][0], rangeMaxes[i][1]);
        +    });
        +
        +    return coords;
           }
         
           _getMode() {
             return this.mode;
           }
         
        -  _getMaxes() {
        -    return this.maxes;
        +  _getRed(max=[0, 1]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
        +    }
        +
        +    if(this.mode === RGB || this.mode === RGBHDR){
        +      const colorjsMax = Color.#colorjsMaxes[this.mode][0];
        +      return map(this._color.coords[0], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }else{
        +      // Will do an imprecise conversion to 'srgb', not recommended
        +      const colorjsMax = Color.#colorjsMaxes[RGB][0];
        +      return map(to(this._color, 'srgb').coords[0], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }
           }
         
        -  _getBlue() {
        -    return this._array[2] * this.maxes[constants.RGB][2];
        +  _getGreen(max=[0, 1]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
        +    }
        +
        +    if(this.mode === RGB || this.mode === RGBHDR){
        +      const colorjsMax = Color.#colorjsMaxes[this.mode][1];
        +      return map(this._color.coords[1], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }else{
        +      // Will do an imprecise conversion to 'srgb', not recommended
        +      const colorjsMax = Color.#colorjsMaxes[RGB][1];
        +      return map(to(this._color, 'srgb').coords[1], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }
           }
         
        -  _getBrightness() {
        -    if (!this.hsba) {
        -      this.hsba = color_conversion._rgbaToHSBA(this._array);
        +  _getBlue(max=[0, 1]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
        +    }
        +
        +    if(this.mode === RGB || this.mode === RGBHDR){
        +      const colorjsMax = Color.#colorjsMaxes[this.mode][2];
        +      return map(this._color.coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }else{
        +      // Will do an imprecise conversion to 'srgb', not recommended
        +      const colorjsMax = Color.#colorjsMaxes[RGB][2];
        +      return map(to(this._color, 'srgb').coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
             }
        -    return this.hsba[2] * this.maxes[constants.HSB][2];
           }
         
        -  _getGreen() {
        -    return this._array[1] * this.maxes[constants.RGB][1];
        +  _getAlpha(max=[0, 1]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
        +    }
        +
        +    const colorjsMax = Color.#colorjsMaxes[this.mode][3];
        +    return map(this._color.alpha, colorjsMax[0], colorjsMax[1], max[0], max[1]);
           }
         
           /**
        @@ -792,29 +548,19 @@ p5.Color = class Color {
            * an HSB color object, but will default to the HSL-normalized saturation
            * otherwise.
            */
        -  _getHue() {
        -    if (this.mode === constants.HSB) {
        -      if (!this.hsba) {
        -        this.hsba = color_conversion._rgbaToHSBA(this._array);
        -      }
        -      return this.hsba[0] * this.maxes[constants.HSB][0];
        -    } else {
        -      if (!this.hsla) {
        -        this.hsla = color_conversion._rgbaToHSLA(this._array);
        -      }
        -      return this.hsla[0] * this.maxes[constants.HSL][0];
        +  _getHue(max=[0, 360]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
             }
        -  }
         
        -  _getLightness() {
        -    if (!this.hsla) {
        -      this.hsla = color_conversion._rgbaToHSLA(this._array);
        +    if(this.mode === HSB || this.mode === HSL){
        +      const colorjsMax = Color.#colorjsMaxes[this.mode][0];
        +      return map(this._color.coords[0], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }else{
        +      // Will do an imprecise conversion to 'HSL', not recommended
        +      const colorjsMax = Color.#colorjsMaxes[HSL][0];
        +      return map(to(this._color, 'hsl').coords[0], colorjsMax[0], colorjsMax[1], max[0], max[1]);
             }
        -    return this.hsla[2] * this.maxes[constants.HSL][2];
        -  }
        -
        -  _getRed() {
        -    return this._array[0] * this.maxes[constants.RGB][0];
           }
         
           /**
        @@ -822,253 +568,169 @@ p5.Color = class Color {
            * the HSB saturation when supplied with an HSB color object, but will default
            * to the HSL saturation otherwise.
            */
        -  _getSaturation() {
        -    if (this.mode === constants.HSB) {
        -      if (!this.hsba) {
        -        this.hsba = color_conversion._rgbaToHSBA(this._array);
        -      }
        -      return this.hsba[1] * this.maxes[constants.HSB][1];
        -    } else {
        -      if (!this.hsla) {
        -        this.hsla = color_conversion._rgbaToHSLA(this._array);
        -      }
        -      return this.hsla[1] * this.maxes[constants.HSL][1];
        +  _getSaturation(max=[0, 100]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
             }
        -  }
        -  /**
        - * For a number of different inputs, returns a color formatted as [r, g, b, a]
        - * arrays, with each component normalized between 0 and 1.
        - *
        - * @private
        - * @param {Array} [...args] An 'array-like' object that represents a list of
        - *                          arguments
        - * @return {Number[]}       a color formatted as [r, g, b, a]
        - *                          Example:
        - *                          input        ==> output
        - *                          g            ==> [g, g, g, 255]
        - *                          g,a          ==> [g, g, g, a]
        - *                          r, g, b      ==> [r, g, b, 255]
        - *                          r, g, b, a   ==> [r, g, b, a]
        - *                          [g]          ==> [g, g, g, 255]
        - *                          [g, a]       ==> [g, g, g, a]
        - *                          [r, g, b]    ==> [r, g, b, 255]
        - *                          [r, g, b, a] ==> [r, g, b, a]
        - * @example
        - * <div>
        - * <code>
        - * // todo
        - * //
        - * // describe('');
        - * </code>
        - * </div>
        - */
        -  static _parseInputs(r, g, b, a) {
        -    const numArgs = arguments.length;
        -    const mode = this.mode;
        -    const maxes = this.maxes[mode];
        -    let results = [];
        -    let i;
        -
        -    if (numArgs >= 3) {
        -      // Argument is a list of component values.
        -
        -      results[0] = r / maxes[0];
        -      results[1] = g / maxes[1];
        -      results[2] = b / maxes[2];
        -
        -      // Alpha may be undefined, so default it to 100%.
        -      if (typeof a === 'number') {
        -        results[3] = a / maxes[3];
        -      } else {
        -        results[3] = 1;
        -      }
         
        -      // Constrain components to the range [0,1].
        -      // (loop backwards for performance)
        -      for (i = results.length - 1; i >= 0; --i) {
        -        const result = results[i];
        -        if (result < 0) {
        -          results[i] = 0;
        -        } else if (result > 1) {
        -          results[i] = 1;
        -        }
        -      }
        +    if(this.mode === HSB || this.mode === HSL){
        +      const colorjsMax = Color.#colorjsMaxes[this.mode][1];
        +      return map(this._color.coords[1], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }else{
        +      // Will do an imprecise conversion to 'HSL', not recommended
        +      const colorjsMax = Color.#colorjsMaxes[HSL][1];
        +      return map(to(this._color, 'hsl').coords[1], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }
        +  }
         
        -      // Convert to RGBA and return.
        -      if (mode === constants.HSL) {
        -        return color_conversion._hslaToRGBA(results);
        -      } else if (mode === constants.HSB) {
        -        return color_conversion._hsbaToRGBA(results);
        -      } else {
        -        return results;
        -      }
        -    } else if (numArgs === 1 && typeof r === 'string') {
        -      const str = r.trim().toLowerCase();
        +  _getBrightness(max=[0, 100]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
        +    }
         
        -      // Return if string is a named colour.
        -      if (namedColors[str]) {
        -        return Color._parseInputs.call(this, namedColors[str]);
        -      }
        +    if(this.mode === HSB){
        +      const colorjsMax = Color.#colorjsMaxes[this.mode][2];
        +      return map(this._color.coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }else{
        +      // Will do an imprecise conversion to 'HSB', not recommended
        +      const colorjsMax = Color.#colorjsMaxes[HSB][2];
        +      return map(to(this._color, 'hsb').coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }
        +  }
         
        -      // Try RGBA pattern matching.
        -      if (colorPatterns.HEX3.test(str)) {
        -        // #rgb
        -        results = colorPatterns.HEX3.exec(str)
        -          .slice(1)
        -          .map(color => parseInt(color + color, 16) / 255);
        -        results[3] = 1;
        -        return results;
        -      } else if (colorPatterns.HEX6.test(str)) {
        -        // #rrggbb
        -        results = colorPatterns.HEX6.exec(str)
        -          .slice(1)
        -          .map(color => parseInt(color, 16) / 255);
        -        results[3] = 1;
        -        return results;
        -      } else if (colorPatterns.HEX4.test(str)) {
        -        // #rgba
        -        results = colorPatterns.HEX4.exec(str)
        -          .slice(1)
        -          .map(color => parseInt(color + color, 16) / 255);
        -        return results;
        -      } else if (colorPatterns.HEX8.test(str)) {
        -        // #rrggbbaa
        -        results = colorPatterns.HEX8.exec(str)
        -          .slice(1)
        -          .map(color => parseInt(color, 16) / 255);
        -        return results;
        -      } else if (colorPatterns.RGB.test(str)) {
        -        // rgb(R,G,B)
        -        results = colorPatterns.RGB.exec(str)
        -          .slice(1)
        -          .map(color => color / 255);
        -        results[3] = 1;
        -        return results;
        -      } else if (colorPatterns.RGB_PERCENT.test(str)) {
        -        // rgb(R%,G%,B%)
        -        results = colorPatterns.RGB_PERCENT.exec(str)
        -          .slice(1)
        -          .map(color => parseFloat(color) / 100);
        -        results[3] = 1;
        -        return results;
        -      } else if (colorPatterns.RGBA.test(str)) {
        -        // rgba(R,G,B,A)
        -        results = colorPatterns.RGBA.exec(str)
        -          .slice(1)
        -          .map((color, idx) => {
        -            if (idx === 3) {
        -              return parseFloat(color);
        -            }
        -            return color / 255;
        -          });
        -        return results;
        -      } else if (colorPatterns.RGBA_PERCENT.test(str)) {
        -        // rgba(R%,G%,B%,A%)
        -        results = colorPatterns.RGBA_PERCENT.exec(str)
        -          .slice(1)
        -          .map((color, idx) => {
        -            if (idx === 3) {
        -              return parseFloat(color);
        -            }
        -            return parseFloat(color) / 100;
        -          });
        -        return results;
        -      }
        +  _getLightness(max=[0, 100]) {
        +    if(!Array.isArray(max)){
        +      max = [0, max];
        +    }
         
        -      // Try HSLA pattern matching.
        -      if (colorPatterns.HSL.test(str)) {
        -        // hsl(H,S,L)
        -        results = colorPatterns.HSL.exec(str)
        -          .slice(1)
        -          .map((color, idx) => {
        -            if (idx === 0) {
        -              return parseInt(color, 10) / 360;
        -            }
        -            return parseInt(color, 10) / 100;
        -          });
        -        results[3] = 1;
        -      } else if (colorPatterns.HSLA.test(str)) {
        -        // hsla(H,S,L,A)
        -        results = colorPatterns.HSLA.exec(str)
        -          .slice(1)
        -          .map((color, idx) => {
        -            if (idx === 0) {
        -              return parseInt(color, 10) / 360;
        -            } else if (idx === 3) {
        -              return parseFloat(color);
        -            }
        -            return parseInt(color, 10) / 100;
        -          });
        -      }
        -      results = results.map(value => Math.max(Math.min(value, 1), 0));
        -      if (results.length) {
        -        return color_conversion._hslaToRGBA(results);
        -      }
        +    if(this.mode === HSL){
        +      const colorjsMax = Color.#colorjsMaxes[this.mode][2];
        +      return map(this._color.coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }else{
        +      // Will do an imprecise conversion to 'HSL', not recommended
        +      const colorjsMax = Color.#colorjsMaxes[HSL][2];
        +      return map(to(this._color, 'hsl').coords[2], colorjsMax[0], colorjsMax[1], max[0], max[1]);
        +    }
        +  }
        +}
         
        -      // Try HSBA pattern matching.
        -      if (colorPatterns.HSB.test(str)) {
        -        // hsb(H,S,B)
        -        results = colorPatterns.HSB.exec(str)
        -          .slice(1)
        -          .map((color, idx) => {
        -            if (idx === 0) {
        -              return parseInt(color, 10) / 360;
        -            }
        -            return parseInt(color, 10) / 100;
        -          });
        -        results[3] = 1;
        -      } else if (colorPatterns.HSBA.test(str)) {
        -        // hsba(H,S,B,A)
        -        results = colorPatterns.HSBA.exec(str)
        -          .slice(1)
        -          .map((color, idx) => {
        -            if (idx === 0) {
        -              return parseInt(color, 10) / 360;
        -            } else if (idx === 3) {
        -              return parseFloat(color);
        -            }
        -            return parseInt(color, 10) / 100;
        -          });
        +function color(p5, fn, lifecycles){
        +  /**
        +   * A class to describe a color.
        +   *
        +   * Each `p5.Color` object stores the color mode
        +   * and level maxes that were active during its construction. These values are
        +   * used to interpret the arguments passed to the object's constructor. They
        +   * also determine output formatting such as when
        +   * <a href="#/p5/saturation">saturation()</a> is called.
        +   *
        +   * Color is stored internally as an array of ideal RGBA values in floating
        +   * point form, normalized from 0 to 1. These values are used to calculate the
        +   * closest screen colors, which are RGBA levels from 0 to 255. Screen colors
        +   * are sent to the renderer.
        +   *
        +   * When different color representations are calculated, the results are cached
        +   * for performance. These values are normalized, floating-point numbers.
        +   *
        +   * Note: <a href="#/p5/color">color()</a> is the recommended way to create an
        +   * instance of this class.
        +   *
        +   * @class p5.Color
        +   * @param {p5} [pInst]                      pointer to p5 instance.
        +   *
        +   * @param {Number[]|String} vals            an array containing the color values
        +   *                                          for red, green, blue and alpha channel
        +   *                                          or CSS color.
        +   */
        +  p5.Color = Color;
        +
        +  // Register color modes and initialize Color maxes to what p5 has set for itself
        +  p5.Color.addColorMode(RGB, sRGB);
        +  p5.Color.addColorMode(RGBHDR, P3);
        +  p5.Color.addColorMode(HSB, HSBSpace);
        +  p5.Color.addColorMode(HSL, HSLSpace);
        +  p5.Color.addColorMode(HWB, HWBSpace);
        +  p5.Color.addColorMode(LAB, Lab);
        +  p5.Color.addColorMode(LCH, LCHSpace);
        +  p5.Color.addColorMode(OKLAB, OKLab);
        +  p5.Color.addColorMode(OKLCH, OKLCHSpace);
        +
        +  lifecycles.presetup = function(){
        +    const pInst = this;
        +
        +    // Decorate set methods
        +    const setMethods = ['Red', 'Green', 'Blue', 'Alpha'];
        +    for(let i in setMethods){
        +      const method = setMethods[i];
        +      const setCopy = p5.Color.prototype['set' + method];
        +      p5.Color.prototype['set' + method] = function(newval, max){
        +        max = max || pInst?._renderer?.states?.colorMaxes?.[RGB][i];
        +        return setCopy.call(this, newval, max);
               }
        +    }
         
        -      if (results.length) {
        -        // (loop backwards for performance)
        -        for (i = results.length - 1; i >= 0; --i) {
        -          results[i] = Math.max(Math.min(results[i], 1), 0);
        +    // Decorate get methods
        +    function decorateGet(channel, modes){
        +      const getCopy = p5.Color.prototype['_get' + channel];
        +      p5.Color.prototype['_get' + channel] = function(max){
        +        if(Object.keys(modes).includes(this.mode)){
        +          max = max || pInst?._renderer?.states?.colorMaxes?.[this.mode][modes[this.mode]];
        +        }else{
        +          const defaultMode = Object.keys(modes)[0];
        +          max = max || pInst?._renderer?.states?.colorMaxes?.[defaultMode][modes[defaultMode]];
                 }
         
        -        return color_conversion._hsbaToRGBA(results);
        -      }
        -
        -      // Input did not match any CSS color pattern: default to white.
        -      results = [1, 1, 1, 1];
        -    } else if ((numArgs === 1 || numArgs === 2) && typeof r === 'number') {
        -      // 'Grayscale' mode.
        -
        -      /**
        -       * For HSB and HSL, interpret the gray level as a brightness/lightness
        -       * value (they are equivalent when chroma is zero). For RGB, normalize the
        -       * gray level according to the blue maximum.
        -       */
        -      results[0] = r / maxes[2];
        -      results[1] = r / maxes[2];
        -      results[2] = r / maxes[2];
        -
        -      // Alpha may be undefined, so default it to 100%.
        -      if (typeof g === 'number') {
        -        results[3] = g / maxes[3];
        -      } else {
        -        results[3] = 1;
        +        return getCopy.call(this, max);
               }
        -
        -      // Constrain components to the range [0,1].
        -      results = results.map(value => Math.max(Math.min(value, 1), 0));
        -    } else {
        -      throw new Error(`${arguments}is not a valid color representation.`);
             }
         
        -    return results;
        -  }
        -};
        -
        -export default p5.Color;
        +    decorateGet('Red', {
        +      [RGB]: 0,
        +      [RGBHDR]: 0
        +    });
        +    decorateGet('Green', {
        +      [RGB]: 1,
        +      [RGBHDR]: 1
        +    });
        +    decorateGet('Blue', {
        +      [RGB]: 2,
        +      [RGBHDR]: 2
        +    });
        +    decorateGet('Alpha', {
        +      [RGB]: 3,
        +      [RGBHDR]: 3,
        +      [HSB]: 3,
        +      [HSL]: 3,
        +      [HWB]: 3,
        +      [LAB]: 3,
        +      [LCH]: 3,
        +      [OKLAB]: 3,
        +      [OKLCH]: 3
        +    });
        +
        +    decorateGet('Hue', {
        +      [HSL]: 0,
        +      [HSB]: 0,
        +      [HWB]: 0,
        +      [LCH]: 2,
        +      [OKLCH]: 2
        +    });
        +    decorateGet('Saturation', {
        +      [HSL]: 1,
        +      [HSB]: 1
        +    });
        +    decorateGet('Brightness', {
        +      [HSB]: 2
        +    });
        +    decorateGet('Lightness', {
        +      [HSL]: 2
        +    });
        +  };
        +}
        +
        +export default color;
        +export { Color }
        +
        +if(typeof p5 !== 'undefined'){
        +  color(p5, p5.prototype);
        +}
        diff --git a/src/color/setting.js b/src/color/setting.js
        index f659f24ee1..1a233ca9bb 100644
        --- a/src/color/setting.js
        +++ b/src/color/setting.js
        @@ -6,1713 +6,2198 @@
          * @requires constants
          */
         
        -import p5 from '../core/main';
         import * as constants from '../core/constants';
        -import './p5.Color';
        +import { RGB, RGBHDR, HSL, HSB, HWB, LAB, LCH, OKLAB, OKLCH } from './creating_reading';
         
        -/**
        - * Starts defining a shape that will mask any shapes drawn afterward.
        - *
        - * Any shapes drawn between `beginClip()` and
        - * <a href="#/p5/endClip">endClip()</a> will add to the mask shape. The mask
        - * will apply to anything drawn after <a href="#/p5/endClip">endClip()</a>.
        - *
        - * The parameter, `options`, is optional. If an object with an `invert`
        - * property is passed, as in `beginClip({ invert: true })`, it will be used to
        - * set the masking mode. `{ invert: true }` inverts the mask, creating holes
        - * in shapes that are masked. `invert` is `false` by default.
        - *
        - * Masks can be contained between the
        - * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions.
        - * Doing so allows unmasked shapes to be drawn after masked shapes.
        - *
        - * Masks can also be defined in a callback function that's passed to
        - * <a href="#/p5/clip">clip()</a>.
        - *
        - * @method beginClip
        - * @param {Object} [options] an object containing clip settings.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a mask.
        - *   beginClip();
        - *   triangle(15, 37, 30, 13, 43, 37);
        - *   circle(45, 45, 7);
        - *   endClip();
        - *
        - *   // Draw a backing shape.
        - *   square(5, 5, 45);
        - *
        - *   describe('A white triangle and circle on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an inverted mask.
        - *   beginClip({ invert: true });
        - *   triangle(15, 37, 30, 13, 43, 37);
        - *   circle(45, 45, 7);
        - *   endClip();
        - *
        - *   // Draw a backing shape.
        - *   square(5, 5, 45);
        - *
        - *   describe('A white square at the top-left corner of a gray square. The white square has a triangle and a circle cut out of it.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   noStroke();
        - *
        - *   // Draw a masked shape.
        - *   push();
        - *   // Create a mask.
        - *   beginClip();
        - *   triangle(15, 37, 30, 13, 43, 37);
        - *   circle(45, 45, 7);
        - *   endClip();
        - *
        - *   // Draw a backing shape.
        - *   square(5, 5, 45);
        - *   pop();
        - *
        - *   // Translate the origin to the center.
        - *   translate(50, 50);
        - *
        - *   // Draw an inverted masked shape.
        - *   push();
        - *   // Create an inverted mask.
        - *   beginClip({ invert: true });
        - *   triangle(15, 37, 30, 13, 43, 37);
        - *   circle(45, 45, 7);
        - *   endClip();
        - *
        - *   // Draw a backing shape.
        - *   square(5, 5, 45);
        - *   pop();
        - *
        - *   describe('In the top left, a white triangle and circle. In the bottom right, a white square with a triangle and circle cut out of it.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A silhouette of a rotating torus colored fuchsia.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create a mask.
        - *   beginClip();
        - *   push();
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   scale(0.5);
        - *   torus(30, 15);
        - *   pop();
        - *   endClip();
        - *
        - *   // Draw a backing shape.
        - *   noStroke();
        - *   fill('fuchsia');
        - *   plane(100);
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A silhouette of a rotating torus colored with a gradient from cyan to purple.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create a mask.
        - *   beginClip();
        - *   push();
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   scale(0.5);
        - *   torus(30, 15);
        - *   pop();
        - *   endClip();
        - *
        - *   // Draw a backing shape.
        - *   noStroke();
        - *   beginShape(QUAD_STRIP);
        - *   fill(0, 255, 255);
        - *   vertex(-width / 2, -height / 2);
        - *   vertex(width / 2, -height / 2);
        - *   fill(100, 0, 100);
        - *   vertex(-width / 2, height / 2);
        - *   vertex(width / 2, height / 2);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.beginClip = function(options = {}) {
        -  this._renderer.beginClip(options);
        -};
        +function setting(p5, fn){
        +  /**
        +   * Starts defining a shape that will mask any shapes drawn afterward.
        +   *
        +   * Any shapes drawn between `beginClip()` and
        +   * <a href="#/p5/endClip">endClip()</a> will add to the mask shape. The mask
        +   * will apply to anything drawn after <a href="#/p5/endClip">endClip()</a>.
        +   *
        +   * The parameter, `options`, is optional. If an object with an `invert`
        +   * property is passed, as in `beginClip({ invert: true })`, it will be used to
        +   * set the masking mode. `{ invert: true }` inverts the mask, creating holes
        +   * in shapes that are masked. `invert` is `false` by default.
        +   *
        +   * Masks can be contained between the
        +   * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions.
        +   * Doing so allows unmasked shapes to be drawn after masked shapes.
        +   *
        +   * Masks can also be defined in a callback function that's passed to
        +   * <a href="#/p5/clip">clip()</a>.
        +   *
        +   * @method beginClip
        +   * @param {Object} [options] an object containing clip settings.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a mask.
        +   *   beginClip();
        +   *   triangle(15, 37, 30, 13, 43, 37);
        +   *   circle(45, 45, 7);
        +   *   endClip();
        +   *
        +   *   // Draw a backing shape.
        +   *   square(5, 5, 45);
        +   *
        +   *   describe('A white triangle and circle on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an inverted mask.
        +   *   beginClip({ invert: true });
        +   *   triangle(15, 37, 30, 13, 43, 37);
        +   *   circle(45, 45, 7);
        +   *   endClip();
        +   *
        +   *   // Draw a backing shape.
        +   *   square(5, 5, 45);
        +   *
        +   *   describe('A white square at the top-left corner of a gray square. The white square has a triangle and a circle cut out of it.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   noStroke();
        +   *
        +   *   // Draw a masked shape.
        +   *   push();
        +   *   // Create a mask.
        +   *   beginClip();
        +   *   triangle(15, 37, 30, 13, 43, 37);
        +   *   circle(45, 45, 7);
        +   *   endClip();
        +   *
        +   *   // Draw a backing shape.
        +   *   square(5, 5, 45);
        +   *   pop();
        +   *
        +   *   // Translate the origin to the center.
        +   *   translate(50, 50);
        +   *
        +   *   // Draw an inverted masked shape.
        +   *   push();
        +   *   // Create an inverted mask.
        +   *   beginClip({ invert: true });
        +   *   triangle(15, 37, 30, 13, 43, 37);
        +   *   circle(45, 45, 7);
        +   *   endClip();
        +   *
        +   *   // Draw a backing shape.
        +   *   square(5, 5, 45);
        +   *   pop();
        +   *
        +   *   describe('In the top left, a white triangle and circle. In the bottom right, a white square with a triangle and circle cut out of it.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A silhouette of a rotating torus colored fuchsia.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create a mask.
        +   *   beginClip();
        +   *   push();
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *   scale(0.5);
        +   *   torus(30, 15);
        +   *   pop();
        +   *   endClip();
        +   *
        +   *   // Draw a backing shape.
        +   *   noStroke();
        +   *   fill('fuchsia');
        +   *   plane(100);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A silhouette of a rotating torus colored with a gradient from cyan to purple.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create a mask.
        +   *   beginClip();
        +   *   push();
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *   scale(0.5);
        +   *   torus(30, 15);
        +   *   pop();
        +   *   endClip();
        +   *
        +   *   // Draw a backing shape.
        +   *   noStroke();
        +   *   beginShape(QUAD_STRIP);
        +   *   fill(0, 255, 255);
        +   *   vertex(-width / 2, -height / 2);
        +   *   vertex(width / 2, -height / 2);
        +   *   fill(100, 0, 100);
        +   *   vertex(-width / 2, height / 2);
        +   *   vertex(width / 2, height / 2);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.beginClip = function(options = {}) {
        +    this._renderer.beginClip(options);
        +  };
         
        -/**
        - * Ends defining a mask that was started with
        - * <a href="#/p5/beginClip">beginClip()</a>.
        - *
        - * @method endClip
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a mask.
        - *   beginClip();
        - *   triangle(15, 37, 30, 13, 43, 37);
        - *   circle(45, 45, 7);
        - *   endClip();
        - *
        - *   // Draw a backing shape.
        - *   square(5, 5, 45);
        - *
        - *   describe('A white triangle and circle on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.endClip = function() {
        -  this._renderer.endClip();
        -};
        +  /**
        +   * Ends defining a mask that was started with
        +   * <a href="#/p5/beginClip">beginClip()</a>.
        +   *
        +   * @method endClip
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a mask.
        +   *   beginClip();
        +   *   triangle(15, 37, 30, 13, 43, 37);
        +   *   circle(45, 45, 7);
        +   *   endClip();
        +   *
        +   *   // Draw a backing shape.
        +   *   square(5, 5, 45);
        +   *
        +   *   describe('A white triangle and circle on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.endClip = function() {
        +    this._renderer.endClip();
        +  };
         
        -/**
        - * Defines a shape that will mask any shapes drawn afterward.
        - *
        - * The first parameter, `callback`, is a function that defines the mask.
        - * Any shapes drawn in  `callback` will add to the mask shape. The mask
        - * will apply to anything drawn after `clip()` is called.
        - *
        - * The second parameter, `options`, is optional. If an object with an `invert`
        - * property is passed, as in `beginClip({ invert: true })`, it will be used to
        - * set the masking mode. `{ invert: true }` inverts the mask, creating holes
        - * in shapes that are masked. `invert` is `false` by default.
        - *
        - * Masks can be contained between the
        - * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions.
        - * Doing so allows unmasked shapes to be drawn after masked shapes.
        - *
        - * Masks can also be defined with <a href="#/p5/beginClip">beginClip()</a>
        - * and <a href="#/p5/endClip">endClip()</a>.
        - *
        - * @method clip
        - * @param {Function} callback a function that draws the mask shape.
        - * @param {Object} [options] an object containing clip settings.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a mask.
        - *   clip(mask);
        - *
        - *   // Draw a backing shape.
        - *   square(5, 5, 45);
        - *
        - *   describe('A white triangle and circle on a gray background.');
        - * }
        - *
        - * // Declare a function that defines the mask.
        - * function mask() {
        - *   triangle(15, 37, 30, 13, 43, 37);
        - *   circle(45, 45, 7);
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an inverted mask.
        - *   clip(mask, { invert: true });
        - *
        - *   // Draw a backing shape.
        - *   square(5, 5, 45);
        - *
        - *   describe('A white square at the top-left corner of a gray square. The white square has a triangle and a circle cut out of it.');
        - * }
        - *
        - * // Declare a function that defines the mask.
        - * function mask() {
        - *   triangle(15, 37, 30, 13, 43, 37);
        - *   circle(45, 45, 7);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   noStroke();
        - *
        - *   // Draw a masked shape.
        - *   push();
        - *   // Create a mask.
        - *   clip(mask);
        - *
        - *   // Draw a backing shape.
        - *   square(5, 5, 45);
        - *   pop();
        - *
        - *   // Translate the origin to the center.
        - *   translate(50, 50);
        - *
        - *   // Draw an inverted masked shape.
        - *   push();
        - *   // Create an inverted mask.
        - *   clip(mask, { invert: true });
        - *
        - *   // Draw a backing shape.
        - *   square(5, 5, 45);
        - *   pop();
        - *
        - *   describe('In the top left, a white triangle and circle. In the bottom right, a white square with a triangle and circle cut out of it.');
        - * }
        - *
        - * // Declare a function that defines the mask.
        - * function mask() {
        - *   triangle(15, 37, 30, 13, 43, 37);
        - *   circle(45, 45, 7);
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A silhouette of a rotating torus colored fuchsia.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create a mask.
        - *   clip(mask);
        - *
        - *   // Draw a backing shape.
        - *   noStroke();
        - *   fill('fuchsia');
        - *   plane(100);
        - * }
        - *
        - * // Declare a function that defines the mask.
        - * function mask() {
        - *   push();
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   scale(0.5);
        - *   torus(30, 15);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A silhouette of a rotating torus colored with a gradient from cyan to purple.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create a mask.
        - *   clip(mask);
        - *
        - *   // Draw a backing shape.
        - *   noStroke();
        - *   beginShape(QUAD_STRIP);
        - *   fill(0, 255, 255);
        - *   vertex(-width / 2, -height / 2);
        - *   vertex(width / 2, -height / 2);
        - *   fill(100, 0, 100);
        - *   vertex(-width / 2, height / 2);
        - *   vertex(width / 2, height / 2);
        - *   endShape();
        - * }
        - *
        - * // Declare a function that defines the mask.
        - * function mask() {
        - *   push();
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   scale(0.5);
        - *   torus(30, 15);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.clip = function(callback, options) {
        -  this._renderer.beginClip(options);
        -  callback();
        -  this._renderer.endClip(options);
        -};
        +  /**
        +   * Defines a shape that will mask any shapes drawn afterward.
        +   *
        +   * The first parameter, `callback`, is a function that defines the mask.
        +   * Any shapes drawn in  `callback` will add to the mask shape. The mask
        +   * will apply to anything drawn after `clip()` is called.
        +   *
        +   * The second parameter, `options`, is optional. If an object with an `invert`
        +   * property is passed, as in `beginClip({ invert: true })`, it will be used to
        +   * set the masking mode. `{ invert: true }` inverts the mask, creating holes
        +   * in shapes that are masked. `invert` is `false` by default.
        +   *
        +   * Masks can be contained between the
        +   * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions.
        +   * Doing so allows unmasked shapes to be drawn after masked shapes.
        +   *
        +   * Masks can also be defined with <a href="#/p5/beginClip">beginClip()</a>
        +   * and <a href="#/p5/endClip">endClip()</a>.
        +   *
        +   * @method clip
        +   * @param {Function} callback a function that draws the mask shape.
        +   * @param {Object} [options] an object containing clip settings.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a mask.
        +   *   clip(mask);
        +   *
        +   *   // Draw a backing shape.
        +   *   square(5, 5, 45);
        +   *
        +   *   describe('A white triangle and circle on a gray background.');
        +   * }
        +   *
        +   * // Declare a function that defines the mask.
        +   * function mask() {
        +   *   triangle(15, 37, 30, 13, 43, 37);
        +   *   circle(45, 45, 7);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an inverted mask.
        +   *   clip(mask, { invert: true });
        +   *
        +   *   // Draw a backing shape.
        +   *   square(5, 5, 45);
        +   *
        +   *   describe('A white square at the top-left corner of a gray square. The white square has a triangle and a circle cut out of it.');
        +   * }
        +   *
        +   * // Declare a function that defines the mask.
        +   * function mask() {
        +   *   triangle(15, 37, 30, 13, 43, 37);
        +   *   circle(45, 45, 7);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   noStroke();
        +   *
        +   *   // Draw a masked shape.
        +   *   push();
        +   *   // Create a mask.
        +   *   clip(mask);
        +   *
        +   *   // Draw a backing shape.
        +   *   square(5, 5, 45);
        +   *   pop();
        +   *
        +   *   // Translate the origin to the center.
        +   *   translate(50, 50);
        +   *
        +   *   // Draw an inverted masked shape.
        +   *   push();
        +   *   // Create an inverted mask.
        +   *   clip(mask, { invert: true });
        +   *
        +   *   // Draw a backing shape.
        +   *   square(5, 5, 45);
        +   *   pop();
        +   *
        +   *   describe('In the top left, a white triangle and circle. In the bottom right, a white square with a triangle and circle cut out of it.');
        +   * }
        +   *
        +   * // Declare a function that defines the mask.
        +   * function mask() {
        +   *   triangle(15, 37, 30, 13, 43, 37);
        +   *   circle(45, 45, 7);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A silhouette of a rotating torus colored fuchsia.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create a mask.
        +   *   clip(mask);
        +   *
        +   *   // Draw a backing shape.
        +   *   noStroke();
        +   *   fill('fuchsia');
        +   *   plane(100);
        +   * }
        +   *
        +   * // Declare a function that defines the mask.
        +   * function mask() {
        +   *   push();
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *   scale(0.5);
        +   *   torus(30, 15);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A silhouette of a rotating torus colored with a gradient from cyan to purple.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create a mask.
        +   *   clip(mask);
        +   *
        +   *   // Draw a backing shape.
        +   *   noStroke();
        +   *   beginShape(QUAD_STRIP);
        +   *   fill(0, 255, 255);
        +   *   vertex(-width / 2, -height / 2);
        +   *   vertex(width / 2, -height / 2);
        +   *   fill(100, 0, 100);
        +   *   vertex(-width / 2, height / 2);
        +   *   vertex(width / 2, height / 2);
        +   *   endShape();
        +   * }
        +   *
        +   * // Declare a function that defines the mask.
        +   * function mask() {
        +   *   push();
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *   scale(0.5);
        +   *   torus(30, 15);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.clip = function(callback, options) {
        +    this._renderer.beginClip(options);
        +    callback();
        +    this._renderer.endClip(options);
        +  };
         
        -/**
        - * Sets the color used for the background of the canvas.
        - *
        - * By default, the background is transparent. `background()` is typically used
        - * within <a href="#/p5/draw">draw()</a> to clear the display window at the
        - * beginning of each frame. It can also be used inside
        - * <a href="#/p5/setup">setup()</a> to set the background on the first frame
        - * of animation.
        - *
        - * The version of `background()` with one parameter interprets the value one
        - * of four ways. If the parameter is a `Number`, it's interpreted as a grayscale
        - * value. If the parameter is a `String`, it's interpreted as a CSS color string.
        - * RGB, RGBA, HSL, HSLA, hex, and named color strings are supported. If the
        - * parameter is a <a href="#/p5.Color">p5.Color</a> object, it will be used as
        - * the background color. If the parameter is a
        - * <a href="#/p5.Image">p5.Image</a> object, it will be used as the background
        - * image.
        - *
        - * The version of `background()` with two parameters interprets the first one
        - * as a grayscale value. The second parameter sets the alpha (transparency)
        - * value.
        - *
        - * The version of `background()` with three parameters interprets them as RGB,
        - * HSB, or HSL colors, depending on the current
        - * <a href="#/p5/colorMode">colorMode()</a>. By default, colors are specified
        - * in RGB values. Calling `background(255, 204, 0)` sets the background a bright
        - * yellow color.
        - *
        - * @method background
        - * @param {p5.Color} color  any value created by the <a href="#/p5/color">color()</a> function
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // A grayscale value.
        - *   background(51);
        - *
        - *   describe('A canvas with a dark charcoal gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // A grayscale value and an alpha value.
        - *   background(51, 0.4);
        - *   describe('A canvas with a transparent gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // R, G & B values.
        - *   background(255, 204, 0);
        - *
        - *   describe('A canvas with a yellow background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use HSB color.
        - *   colorMode(HSB);
        - *
        - *   // H, S & B values.
        - *   background(255, 204, 100);
        - *
        - *   describe('A canvas with a royal blue background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // A CSS named color.
        - *   background('red');
        - *
        - *   describe('A canvas with a red background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Three-digit hex RGB notation.
        - *   background('#fae');
        - *
        - *   describe('A canvas with a pink background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Six-digit hex RGB notation.
        - *   background('#222222');
        - *
        - *   describe('A canvas with a black background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Integer RGB notation.
        - *   background('rgb(0, 255, 0)');
        - *
        - *   describe('A canvas with a bright green background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Integer RGBA notation.
        - *   background('rgba(0, 255, 0, 0.25)');
        - *
        - *   describe('A canvas with a transparent green background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Percentage RGB notation.
        - *   background('rgb(100%, 0%, 10%)');
        - *
        - *   describe('A canvas with a red background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Percentage RGBA notation.
        - *   background('rgba(100%, 0%, 100%, 0.5)');
        - *
        - *   describe('A canvas with a transparent purple background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // A p5.Color object.
        - *   let c = color(0, 0, 255);
        - *   background(c);
        - *
        - *   describe('A canvas with a blue background.');
        - * }
        - * </code>
        - * </div>
        - *
        - */
        +  /**
        +   * Sets the color used for the background of the canvas.
        +   *
        +   * By default, the background is transparent. `background()` is typically used
        +   * within <a href="#/p5/draw">draw()</a> to clear the display window at the
        +   * beginning of each frame. It can also be used inside
        +   * <a href="#/p5/setup">setup()</a> to set the background on the first frame
        +   * of animation.
        +   *
        +   * The version of `background()` with one parameter interprets the value one
        +   * of four ways. If the parameter is a `Number`, it's interpreted as a grayscale
        +   * value. If the parameter is a `String`, it's interpreted as a CSS color string.
        +   * RGB, RGBA, HSL, HSLA, hex, and named color strings are supported. If the
        +   * parameter is a <a href="#/p5.Color">p5.Color</a> object, it will be used as
        +   * the background color. If the parameter is a
        +   * <a href="#/p5.Image">p5.Image</a> object, it will be used as the background
        +   * image.
        +   *
        +   * The version of `background()` with two parameters interprets the first one
        +   * as a grayscale value. The second parameter sets the alpha (transparency)
        +   * value.
        +   *
        +   * The version of `background()` with three parameters interprets them as RGB,
        +   * HSB, or HSL colors, depending on the current
        +   * <a href="#/p5/colorMode">colorMode()</a>. By default, colors are specified
        +   * in RGB values. Calling `background(255, 204, 0)` sets the background a bright
        +   * yellow color.
        +   *
        +   * @method background
        +   * @param {p5.Color} color  any value created by the <a href="#/p5/color">color()</a> function
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // A grayscale value.
        +   *   background(51);
        +   *
        +   *   describe('A canvas with a dark charcoal gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // A grayscale value and an alpha value.
        +   *   background(51, 0.4);
        +   *   describe('A canvas with a transparent gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // R, G & B values.
        +   *   background(255, 204, 0);
        +   *
        +   *   describe('A canvas with a yellow background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use HSB color.
        +   *   colorMode(HSB);
        +   *
        +   *   // H, S & B values.
        +   *   background(255, 204, 100);
        +   *
        +   *   describe('A canvas with a royal blue background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // A CSS named color.
        +   *   background('red');
        +   *
        +   *   describe('A canvas with a red background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Three-digit hex RGB notation.
        +   *   background('#fae');
        +   *
        +   *   describe('A canvas with a pink background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Six-digit hex RGB notation.
        +   *   background('#222222');
        +   *
        +   *   describe('A canvas with a black background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Integer RGB notation.
        +   *   background('rgb(0, 255, 0)');
        +   *
        +   *   describe('A canvas with a bright green background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Integer RGBA notation.
        +   *   background('rgba(0, 255, 0, 0.25)');
        +   *
        +   *   describe('A canvas with a transparent green background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Percentage RGB notation.
        +   *   background('rgb(100%, 0%, 10%)');
        +   *
        +   *   describe('A canvas with a red background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Percentage RGBA notation.
        +   *   background('rgba(100%, 0%, 100%, 0.5)');
        +   *
        +   *   describe('A canvas with a transparent purple background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // A p5.Color object.
        +   *   let c = color(0, 0, 255);
        +   *   background(c);
        +   *
        +   *   describe('A canvas with a blue background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   */
         
        -/**
        - * @method background
        - * @param {String} colorstring color string, possible formats include: integer
        - *                         rgb() or rgba(), percentage rgb() or rgba(),
        - *                         3-digit hex, 6-digit hex.
        - * @param {Number} [a]         opacity of the background relative to current
        - *                             color range (default is 0-255).
        - * @chainable
        - */
        +  /**
        +   * @method background
        +   * @param {String} colorstring color string, possible formats include: integer
        +   *                         rgb() or rgba(), percentage rgb() or rgba(),
        +   *                         3-digit hex, 6-digit hex.
        +   * @param {Number} [a]         opacity of the background relative to current
        +   *                             color range (default is 0-255).
        +   * @chainable
        +   */
         
        -/**
        - * @method background
        - * @param {Number} gray   specifies a value between white and black.
        - * @param {Number} [a]
        - * @chainable
        - */
        +  /**
        +   * @method background
        +   * @param {Number} gray   specifies a value between white and black.
        +   * @param {Number} [a]
        +   * @chainable
        +   */
         
        -/**
        - * @method background
        - * @param {Number} v1     red value if color mode is RGB, or hue value if color mode is HSB.
        - * @param {Number} v2     green value if color mode is RGB, or saturation value if color mode is HSB.
        - * @param {Number} v3     blue value if color mode is RGB, or brightness value if color mode is HSB.
        - * @param  {Number} [a]
        - * @chainable
        - */
        +  /**
        +   * @method background
        +   * @param {Number} v1     red value if color mode is RGB, or hue value if color mode is HSB.
        +   * @param {Number} v2     green value if color mode is RGB, or saturation value if color mode is HSB.
        +   * @param {Number} v3     blue value if color mode is RGB, or brightness value if color mode is HSB.
        +   * @param  {Number} [a]
        +   * @chainable
        +   */
         
        -/**
        - * @method background
        - * @param  {Number[]}      values  an array containing the red, green, blue
        - *                                 and alpha components of the color.
        - * @chainable
        - */
        +  /**
        +   * @method background
        +   * @param  {Number[]}      values  an array containing the red, green, blue
        +   *                                 and alpha components of the color.
        +   * @chainable
        +   */
         
        -/**
        - * @method background
        - * @param {p5.Image} image     image created with <a href="#/p5/loadImage">loadImage()</a>
        - *                             or <a href="#/p5/createImage">createImage()</a>,
        - *                             to set as background.
        - *                             (must be same size as the sketch window).
        - * @param  {Number}  [a]
        - * @chainable
        - */
        -p5.prototype.background = function(...args) {
        -  this._renderer.background(...args);
        -  return this;
        -};
        +  /**
        +   * @method background
        +   * @param {p5.Image} image     image created with <a href="#/p5/loadImage">loadImage()</a>
        +   *                             or <a href="#/p5/createImage">createImage()</a>,
        +   *                             to set as background.
        +   *                             (must be same size as the sketch window).
        +   * @param  {Number}  [a]
        +   * @chainable
        +   */
        +  fn.background = function(...args) {
        +    this._renderer.background(...args);
        +    return this;
        +  };
         
        -/**
        - * Clears the pixels on the canvas.
        - *
        - * `clear()` makes every pixel 100% transparent. Calling `clear()` doesn't
        - * clear objects created by `createX()` functions such as
        - * <a href="#/p5/createGraphics">createGraphics()</a>,
        - * <a href="#/p5/createVideo">createVideo()</a>, and
        - * <a href="#/p5/createImg">createImg()</a>. These objects will remain
        - * unchanged after calling `clear()` and can be redrawn.
        - *
        - * In WebGL mode, this function can clear the screen to a specific color. It
        - * interprets four numeric parameters as normalized RGBA color values. It also
        - * clears the depth buffer. If you are not using the WebGL renderer, these
        - * parameters will have no effect.
        - *
        - * @method clear
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A gray square. White circles are drawn as the user moves the mouse. The circles disappear when the user presses the mouse.');
        - * }
        - *
        - * function draw() {
        - *   circle(mouseX, mouseY, 20);
        - * }
        - *
        - * function mousePressed() {
        - *   clear();
        - *   background(200);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let pg;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *   background(200);
        - *
        - *   pg = createGraphics(60, 60);
        - *   pg.background(200);
        - *   pg.noStroke();
        - *   pg.circle(pg.width / 2, pg.height / 2, 15);
        - *   image(pg, 20, 20);
        - *
        - *   describe('A white circle drawn on a gray square. The square gets smaller when the mouse is pressed.');
        - * }
        - *
        - * function mousePressed() {
        - *   clear();
        - *   image(pg, 20, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * @param {Number} [r] normalized red value.
        - * @param {Number} [g] normalized green value.
        - * @param {Number} [b] normalized blue value.
        - * @param {Number} [a] normalized alpha value.
        - */
        -p5.prototype.clear = function(...args) {
        -  const _r = args[0] || 0;
        -  const _g = args[1] || 0;
        -  const _b = args[2] || 0;
        -  const _a = args[3] || 0;
        +  /**
        +   * Clears the pixels on the canvas.
        +   *
        +   * `clear()` makes every pixel 100% transparent. Calling `clear()` doesn't
        +   * clear objects created by `createX()` functions such as
        +   * <a href="#/p5/createGraphics">createGraphics()</a>,
        +   * <a href="#/p5/createVideo">createVideo()</a>, and
        +   * <a href="#/p5/createImg">createImg()</a>. These objects will remain
        +   * unchanged after calling `clear()` and can be redrawn.
        +   *
        +   * In WebGL mode, this function can clear the screen to a specific color. It
        +   * interprets four numeric parameters as normalized RGBA color values. It also
        +   * clears the depth buffer. If you are not using the WebGL renderer, these
        +   * parameters will have no effect.
        +   *
        +   * @method clear
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A gray square. White circles are drawn as the user moves the mouse. The circles disappear when the user presses the mouse.');
        +   * }
        +   *
        +   * function draw() {
        +   *   circle(mouseX, mouseY, 20);
        +   * }
        +   *
        +   * function mousePressed() {
        +   *   clear();
        +   *   background(200);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let pg;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *   background(200);
        +   *
        +   *   pg = createGraphics(60, 60);
        +   *   pg.background(200);
        +   *   pg.noStroke();
        +   *   pg.circle(pg.width / 2, pg.height / 2, 15);
        +   *   image(pg, 20, 20);
        +   *
        +   *   describe('A white circle drawn on a gray square. The square gets smaller when the mouse is pressed.');
        +   * }
        +   *
        +   * function mousePressed() {
        +   *   clear();
        +   *   image(pg, 20, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @param {Number} [r] normalized red value.
        +   * @param {Number} [g] normalized green value.
        +   * @param {Number} [b] normalized blue value.
        +   * @param {Number} [a] normalized alpha value.
        +   */
        +  fn.clear = function(...args) {
        +    const _r = args[0] || 0;
        +    const _g = args[1] || 0;
        +    const _b = args[2] || 0;
        +    const _a = args[3] || 0;
         
        -  this._renderer.clear(_r, _g, _b, _a);
        -  return this;
        -};
        +    this._renderer.clear(_r, _g, _b, _a);
        +    return this;
        +  };
         
        -/**
        - * Changes the way color values are interpreted.
        - *
        - * By default, the `Number` parameters for <a href="#/p5/fill">fill()</a>,
        - * <a href="#/p5/stroke">stroke()</a>,
        - * <a href="#/p5/background">background()</a>, and
        - * <a href="#/p5/color">color()</a> are defined by values between 0 and 255
        - * using the RGB color model. This is equivalent to calling
        - * `colorMode(RGB, 255)`. Pure red is `color(255, 0, 0)` in this model.
        - *
        - * Calling `colorMode(RGB, 100)` sets colors to use RGB color values
        - * between 0 and 100. Pure red is `color(100, 0, 0)` in this model.
        - *
        - * Calling `colorMode(HSB)` or `colorMode(HSL)` changes to HSB or HSL system
        - * instead of RGB. Pure red is `color(0, 100, 100)` in HSB and
        - * `color(0, 100, 50)` in HSL.
        - *
        - * <a href="#/p5.Color">p5.Color</a> objects remember the mode that they were
        - * created in. Changing modes doesn't affect their appearance.
        - *
        - * @method colorMode
        - * @param {Constant} mode   either RGB, HSB or HSL, corresponding to
        - *                          Red/Green/Blue and Hue/Saturation/Brightness
        - *                          (or Lightness).
        - * @param {Number}  [max]  range for all values.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Fill with pure red.
        - *   fill(255, 0, 0);
        - *
        - *   circle(50, 50, 25);
        - *
        - *   describe('A gray square with a red circle at its center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use RGB color with values in the range 0-100.
        - *   colorMode(RGB, 100);
        - *
        - *   // Fill with pure red.
        - *   fill(100, 0, 0);
        - *
        - *   circle(50, 50, 25);
        - *
        - *   describe('A gray square with a red circle at its center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use HSB color.
        - *   colorMode(HSB);
        - *
        - *   // Fill with pure red.
        - *   fill(0, 100, 100);
        - *
        - *   circle(50, 50, 25);
        - *
        - *   describe('A gray square with a red circle at its center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use HSL color.
        - *   colorMode(HSL);
        - *
        - *   // Fill with pure red.
        - *   fill(0, 100, 50);
        - *
        - *   circle(50, 50, 25);
        - *
        - *   describe('A gray square with a red circle at its center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use RGB color with values in the range 0-100.
        - *   colorMode(RGB, 100);
        - *
        - *   for (let x = 0; x < 100; x += 1) {
        - *     for (let y = 0; y < 100; y += 1) {
        - *       stroke(x, y, 0);
        - *       point(x, y);
        - *     }
        - *   }
        - *
        - *   describe(
        - *     'A diagonal green to red gradient from bottom-left to top-right with shading transitioning to black at top-left corner.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use HSB color with values in the range 0-100.
        - *   colorMode(HSB, 100);
        - *
        - *   for (let x = 0; x < 100; x += 1) {
        - *     for (let y = 0; y < 100; y += 1) {
        - *       stroke(x, y, 100);
        - *       point(x, y);
        - *     }
        - *   }
        - *
        - *   describe('A rainbow gradient from left-to-right. Brightness transitions to white at the top.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.Color object.
        - *   let myColor = color(180, 175, 230);
        - *   background(myColor);
        - *
        - *   // Use RGB color with values in the range 0-1.
        - *   colorMode(RGB, 1);
        - *
        - *   // Get the red, green, and blue color components.
        - *   let redValue = red(myColor);
        - *   let greenValue = green(myColor);
        - *   let blueValue = blue(myColor);
        - *
        - *   // Round the color components for display.
        - *   redValue = round(redValue, 2);
        - *   greenValue = round(greenValue, 2);
        - *   blueValue = round(blueValue, 2);
        - *
        - *   // Display the color components.
        - *   text(`Red: ${redValue}`, 10, 10, 80, 80);
        - *   text(`Green: ${greenValue}`, 10, 40, 80, 80);
        - *   text(`Blue: ${blueValue}`, 10, 70, 80, 80);
        - *
        - *   describe('A purple canvas with the red, green, and blue decimal values of the color written on it.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(255);
        - *
        - *   // Use RGB color with alpha values in the range 0-1.
        - *   colorMode(RGB, 255, 255, 255, 1);
        - *
        - *   noFill();
        - *   strokeWeight(4);
        - *   stroke(255, 0, 10, 0.3);
        - *   circle(40, 40, 50);
        - *   circle(50, 60, 50);
        - *
        - *   describe('Two overlapping translucent pink circle outlines.');
        - * }
        - * </code>
        - * </div>
        - */
        +  /**
        +   * Changes the way color values are interpreted.
        +   *
        +   * By default, the `Number` parameters for <a href="#/p5/fill">fill()</a>,
        +   * <a href="#/p5/stroke">stroke()</a>,
        +   * <a href="#/p5/background">background()</a>, and
        +   * <a href="#/p5/color">color()</a> are defined by values between 0 and 255
        +   * using the RGB color model. This is equivalent to calling
        +   * `colorMode(RGB, 255)`. Pure red is `color(255, 0, 0)` in this model.
        +   *
        +   * Calling `colorMode(RGB, 100)` sets colors to use RGB color values
        +   * between 0 and 100. Pure red is `color(100, 0, 0)` in this model.
        +   *
        +   * Calling `colorMode(HSB)` or `colorMode(HSL)` changes to HSB or HSL system
        +   * instead of RGB. Pure red is `color(0, 100, 100)` in HSB and
        +   * `color(0, 100, 50)` in HSL.
        +   *
        +   * <a href="#/p5.Color">p5.Color</a> objects remember the mode that they were
        +   * created in. Changing modes doesn't affect their appearance.
        +   *
        +   * @method colorMode
        +   * @param {(RGB|HSB|HSL)} mode   either RGB, HSB or HSL, corresponding to
        +   *                          Red/Green/Blue and Hue/Saturation/Brightness
        +   *                          (or Lightness).
        +   * @param {Number}  [max]  range for all values.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Fill with pure red.
        +   *   fill(255, 0, 0);
        +   *
        +   *   circle(50, 50, 25);
        +   *
        +   *   describe('A gray square with a red circle at its center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use RGB color with values in the range 0-100.
        +   *   colorMode(RGB, 100);
        +   *
        +   *   // Fill with pure red.
        +   *   fill(100, 0, 0);
        +   *
        +   *   circle(50, 50, 25);
        +   *
        +   *   describe('A gray square with a red circle at its center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use HSB color.
        +   *   colorMode(HSB);
        +   *
        +   *   // Fill with pure red.
        +   *   fill(0, 100, 100);
        +   *
        +   *   circle(50, 50, 25);
        +   *
        +   *   describe('A gray square with a red circle at its center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use HSL color.
        +   *   colorMode(HSL);
        +   *
        +   *   // Fill with pure red.
        +   *   fill(0, 100, 50);
        +   *
        +   *   circle(50, 50, 25);
        +   *
        +   *   describe('A gray square with a red circle at its center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use RGB color with values in the range 0-100.
        +   *   colorMode(RGB, 100);
        +   *
        +   *   for (let x = 0; x < 100; x += 1) {
        +   *     for (let y = 0; y < 100; y += 1) {
        +   *       stroke(x, y, 0);
        +   *       point(x, y);
        +   *     }
        +   *   }
        +   *
        +   *   describe(
        +   *     'A diagonal green to red gradient from bottom-left to top-right with shading transitioning to black at top-left corner.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use HSB color with values in the range 0-100.
        +   *   colorMode(HSB, 100);
        +   *
        +   *   for (let x = 0; x < 100; x += 1) {
        +   *     for (let y = 0; y < 100; y += 1) {
        +   *       stroke(x, y, 100);
        +   *       point(x, y);
        +   *     }
        +   *   }
        +   *
        +   *   describe('A rainbow gradient from left-to-right. Brightness transitions to white at the top.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let myColor = color(180, 175, 230);
        +   *   background(myColor);
        +   *
        +   *   // Use RGB color with values in the range 0-1.
        +   *   colorMode(RGB, 1);
        +   *
        +   *   // Get the red, green, and blue color components.
        +   *   let redValue = red(myColor);
        +   *   let greenValue = green(myColor);
        +   *   let blueValue = blue(myColor);
        +   *
        +   *   // Round the color components for display.
        +   *   redValue = round(redValue, 2);
        +   *   greenValue = round(greenValue, 2);
        +   *   blueValue = round(blueValue, 2);
        +   *
        +   *   // Display the color components.
        +   *   text(`Red: ${redValue}`, 10, 10, 80, 80);
        +   *   text(`Green: ${greenValue}`, 10, 40, 80, 80);
        +   *   text(`Blue: ${blueValue}`, 10, 70, 80, 80);
        +   *
        +   *   describe('A purple canvas with the red, green, and blue decimal values of the color written on it.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(255);
        +   *
        +   *   // Use RGB color with alpha values in the range 0-1.
        +   *   colorMode(RGB, 255, 255, 255, 1);
        +   *
        +   *   noFill();
        +   *   strokeWeight(4);
        +   *   stroke(255, 0, 10, 0.3);
        +   *   circle(40, 40, 50);
        +   *   circle(50, 60, 50);
        +   *
        +   *   describe('Two overlapping translucent pink circle outlines.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -/**
        - * @method colorMode
        - * @param {Constant} mode
        - * @param {Number} max1     range for the red or hue depending on the
        - *                              current color mode.
        - * @param {Number} max2     range for the green or saturation depending
        - *                              on the current color mode.
        - * @param {Number} max3     range for the blue or brightness/lightness
        - *                              depending on the current color mode.
        - * @param {Number} [maxA]   range for the alpha.
        - * @chainable
        - */
        -p5.prototype.colorMode = function(mode, max1, max2, max3, maxA) {
        -  p5._validateParameters('colorMode', arguments);
        -  if (
        -    mode === constants.RGB ||
        -    mode === constants.HSB ||
        -    mode === constants.HSL
        -  ) {
        -    // Set color mode.
        -    this._colorMode = mode;
        +  /**
        +   * @method colorMode
        +   * @param {(RGB|HSB|HSL)} mode
        +   * @param {Number} max1     range for the red or hue depending on the
        +   *                              current color mode.
        +   * @param {Number} max2     range for the green or saturation depending
        +   *                              on the current color mode.
        +   * @param {Number} max3     range for the blue or brightness/lightness
        +   *                              depending on the current color mode.
        +   * @param {Number} [maxA]   range for the alpha.
        +   *
        +   * @return {String}      The current color mode.
        +   */
        +  fn.colorMode = function(mode, max1, max2, max3, maxA) {
        +    // p5._validateParameters('colorMode', arguments);
        +    if (
        +      [
        +        RGB,
        +        RGBHDR,
        +        HSB,
        +        HSL,
        +        HWB,
        +        LAB,
        +        LCH,
        +        OKLAB,
        +        OKLCH
        +      ].includes(mode)
        +    ) {
        +      // Set color mode.
        +      this._renderer.states.colorMode = mode;
         
        -    // Set color maxes.
        -    const maxes = this._colorMaxes[mode];
        -    if (arguments.length === 2) {
        -      maxes[0] = max1; // Red
        -      maxes[1] = max1; // Green
        -      maxes[2] = max1; // Blue
        -      maxes[3] = max1; // Alpha
        -    } else if (arguments.length === 4) {
        -      maxes[0] = max1; // Red
        -      maxes[1] = max2; // Green
        -      maxes[2] = max3; // Blue
        -    } else if (arguments.length === 5) {
        -      maxes[0] = max1; // Red
        -      maxes[1] = max2; // Green
        -      maxes[2] = max3; // Blue
        -      maxes[3] = maxA; // Alpha
        +      // Set color maxes.
        +      const maxes = this._renderer.states.colorMaxes[mode];
        +      if (arguments.length === 2) {
        +        maxes[0] = max1; // Red
        +        maxes[1] = max1; // Green
        +        maxes[2] = max1; // Blue
        +        maxes[3] = max1; // Alpha
        +      } else if (arguments.length === 4) {
        +        maxes[0] = max1; // Red
        +        maxes[1] = max2; // Green
        +        maxes[2] = max3; // Blue
        +      } else if (arguments.length === 5) {
        +        maxes[0] = max1; // Red
        +        maxes[1] = max2; // Green
        +        maxes[2] = max3; // Blue
        +        maxes[3] = maxA; // Alpha
        +      }
             }
        -  }
         
        -  return this;
        -};
        +    return this._renderer.states.colorMode;
        +  };
         
        -/**
        - * Sets the color used to fill shapes.
        - *
        - * Calling `fill(255, 165, 0)` or `fill('orange')` means all shapes drawn
        - * after the fill command will be filled with the color orange.
        - *
        - * The version of `fill()` with one parameter interprets the value one of
        - * three ways. If the parameter is a `Number`, it's interpreted as a grayscale
        - * value. If the parameter is a `String`, it's interpreted as a CSS color
        - * string. A <a href="#/p5.Color">p5.Color</a> object can also be provided to
        - * set the fill color.
        - *
        - * The version of `fill()` with three parameters interprets them as RGB, HSB,
        - * or HSL colors, depending on the current
        - * <a href="#/p5/colorMode">colorMode()</a>. The default color space is RGB,
        - * with each value in the range from 0 to 255.
        - *
        - * @method fill
        - * @param  {Number}        v1      red value if color mode is RGB or hue value if color mode is HSB.
        - * @param  {Number}        v2      green value if color mode is RGB or saturation value if color mode is HSB.
        - * @param  {Number}        v3      blue value if color mode is RGB or brightness value if color mode is HSB.
        - * @param  {Number}        [alpha] alpha value, controls transparency (0 - transparent, 255 - opaque).
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // A grayscale value.
        - *   fill(51);
        - *   square(20, 20, 60);
        - *
        - *   describe('A dark charcoal gray square with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // R, G & B values.
        - *   fill(255, 204, 0);
        - *   square(20, 20, 60);
        - *
        - *   describe('A yellow square with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(100);
        - *
        - *   // Use HSB color.
        - *   colorMode(HSB);
        - *
        - *   // H, S & B values.
        - *   fill(255, 204, 100);
        - *   square(20, 20, 60);
        - *
        - *   describe('A royal blue square with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // A CSS named color.
        - *   fill('red');
        - *   square(20, 20, 60);
        - *
        - *   describe('A red square with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Three-digit hex RGB notation.
        - *   fill('#fae');
        - *   square(20, 20, 60);
        - *
        - *   describe('A pink square with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Six-digit hex RGB notation.
        - *   fill('#A251FA');
        - *   square(20, 20, 60);
        - *
        - *   describe('A purple square with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Integer RGB notation.
        - *   fill('rgb(0, 255, 0)');
        - *   square(20, 20, 60);
        - *
        - *   describe('A bright green square with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Integer RGBA notation.
        - *   fill('rgba(0, 255, 0, 0.25)');
        - *   square(20, 20, 60);
        - *
        - *   describe('A soft green rectange with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Percentage RGB notation.
        - *   fill('rgb(100%, 0%, 10%)');
        - *   square(20, 20, 60);
        - *
        - *   describe('A red square with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Percentage RGBA notation.
        - *   fill('rgba(100%, 0%, 100%, 0.5)');
        - *   square(20, 20, 60);
        - *
        - *   describe('A dark fuchsia square with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // A p5.Color object.
        - *   let c = color(0, 0, 255);
        - *   fill(c);
        - *   square(20, 20, 60);
        - *
        - *   describe('A blue square with a black outline.');
        - * }
        - * </code>
        - * </div>
        - */
        +  /**
        +   * Sets the color used to fill shapes.
        +   *
        +   * Calling `fill(255, 165, 0)` or `fill('orange')` means all shapes drawn
        +   * after the fill command will be filled with the color orange.
        +   *
        +   * The version of `fill()` with one parameter interprets the value one of
        +   * three ways. If the parameter is a `Number`, it's interpreted as a grayscale
        +   * value. If the parameter is a `String`, it's interpreted as a CSS color
        +   * string. A <a href="#/p5.Color">p5.Color</a> object can also be provided to
        +   * set the fill color.
        +   *
        +   * The version of `fill()` with three parameters interprets them as RGB, HSB,
        +   * or HSL colors, depending on the current
        +   * <a href="#/p5/colorMode">colorMode()</a>. The default color space is RGB,
        +   * with each value in the range from 0 to 255.
        +   *
        +   * @method fill
        +   * @param  {Number}        v1      red value if color mode is RGB or hue value if color mode is HSB.
        +   * @param  {Number}        v2      green value if color mode is RGB or saturation value if color mode is HSB.
        +   * @param  {Number}        v3      blue value if color mode is RGB or brightness value if color mode is HSB.
        +   * @param  {Number}        [alpha] alpha value, controls transparency (0 - transparent, 255 - opaque).
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // A grayscale value.
        +   *   fill(51);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A dark charcoal gray square with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // R, G & B values.
        +   *   fill(255, 204, 0);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A yellow square with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(100);
        +   *
        +   *   // Use HSB color.
        +   *   colorMode(HSB);
        +   *
        +   *   // H, S & B values.
        +   *   fill(255, 204, 100);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A royal blue square with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // A CSS named color.
        +   *   fill('red');
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A red square with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Three-digit hex RGB notation.
        +   *   fill('#fae');
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A pink square with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Six-digit hex RGB notation.
        +   *   fill('#A251FA');
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A purple square with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Integer RGB notation.
        +   *   fill('rgb(0, 255, 0)');
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A bright green square with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Integer RGBA notation.
        +   *   fill('rgba(0, 255, 0, 0.25)');
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A soft green rectange with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Percentage RGB notation.
        +   *   fill('rgb(100%, 0%, 10%)');
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A red square with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Percentage RGBA notation.
        +   *   fill('rgba(100%, 0%, 100%, 0.5)');
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A dark fuchsia square with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // A p5.Color object.
        +   *   let c = color(0, 0, 255);
        +   *   fill(c);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A blue square with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -/**
        - * @method fill
        - * @param  {String}        value   a color string.
        - * @chainable
        - */
        +  /**
        +   * @method fill
        +   * @param  {String}        value   a color string.
        +   * @chainable
        +   */
         
        -/**
        - * @method fill
        - * @param  {Number}        gray   a grayscale value.
        - * @param  {Number}        [alpha]
        - * @chainable
        - */
        +  /**
        +   * @method fill
        +   * @param  {Number}        gray   a grayscale value.
        +   * @param  {Number}        [alpha]
        +   * @chainable
        +   */
         
        -/**
        - * @method fill
        - * @param  {Number[]}      values  an array containing the red, green, blue &
        - *                                 and alpha components of the color.
        - * @chainable
        - */
        +  /**
        +   * @method fill
        +   * @param  {Number[]}      values  an array containing the red, green, blue &
        +   *                                 and alpha components of the color.
        +   * @chainable
        +   */
         
        -/**
        - * @method fill
        - * @param  {p5.Color}      color   the fill color.
        - * @chainable
        - */
        -p5.prototype.fill = function(...args) {
        -  this._renderer._setProperty('_fillSet', true);
        -  this._renderer._setProperty('_doFill', true);
        -  this._renderer.fill(...args);
        -  return this;
        -};
        +  /**
        +   * @method fill
        +   * @param  {p5.Color}      color   the fill color.
        +   * @chainable
        +   */
        +  fn.fill = function(...args) {
        +    this._renderer.fill(...args);
        +    return this;
        +  };
         
        -/**
        - * Disables setting the fill color for shapes.
        - *
        - * Calling `noFill()` is the same as making the fill completely transparent,
        - * as in `fill(0, 0)`. If both <a href="#/p5/noStroke">noStroke()</a> and
        - * `noFill()` are called, nothing will be drawn to the screen.
        - *
        - * @method noFill
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw the top square.
        - *   square(32, 10, 35);
        - *
        - *   // Draw the bottom square.
        - *   noFill();
        - *   square(32, 55, 35);
        - *
        - *   describe('A white square on above an empty square. Both squares have black outlines.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div modernizr='webgl'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A purple cube wireframe spinning on a black canvas.');
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Style the box.
        - *   noFill();
        - *   stroke(100, 100, 240);
        - *
        - *   // Rotate the coordinates.
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Draw the box.
        - *   box(45);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noFill = function() {
        -  this._renderer._setProperty('_doFill', false);
        -  return this;
        -};
        +  /**
        +   * Disables setting the fill color for shapes.
        +   *
        +   * Calling `noFill()` is the same as making the fill completely transparent,
        +   * as in `fill(0, 0)`. If both <a href="#/p5/noStroke">noStroke()</a> and
        +   * `noFill()` are called, nothing will be drawn to the screen.
        +   *
        +   * @method noFill
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw the top square.
        +   *   square(32, 10, 35);
        +   *
        +   *   // Draw the bottom square.
        +   *   noFill();
        +   *   square(32, 55, 35);
        +   *
        +   *   describe('A white square on above an empty square. Both squares have black outlines.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A purple cube wireframe spinning on a black canvas.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Style the box.
        +   *   noFill();
        +   *   stroke(100, 100, 240);
        +   *
        +   *   // Rotate the coordinates.
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Draw the box.
        +   *   box(45);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noFill = function() {
        +    this._renderer.noFill();
        +    return this;
        +  };
         
        -/**
        - * Disables drawing points, lines, and the outlines of shapes.
        - *
        - * Calling `noStroke()` is the same as making the stroke completely transparent,
        - * as in `stroke(0, 0)`. If both `noStroke()` and
        - * <a href="#/p5/noFill">noFill()</a> are called, nothing will be drawn to the
        - * screen.
        - *
        - * @method noStroke
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   noStroke();
        - *   square(20, 20, 60);
        - *
        - *   describe('A white square with no outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div modernizr='webgl'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A pink cube with no edge outlines spinning on a black canvas.');
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Style the box.
        - *   noStroke();
        - *   fill(240, 150, 150);
        - *
        - *   // Rotate the coordinates.
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Draw the box.
        - *   box(45);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noStroke = function() {
        -  this._renderer._setProperty('_doStroke', false);
        -  return this;
        -};
        +  /**
        +   * Disables drawing points, lines, and the outlines of shapes.
        +   *
        +   * Calling `noStroke()` is the same as making the stroke completely transparent,
        +   * as in `stroke(0, 0)`. If both `noStroke()` and
        +   * <a href="#/p5/noFill">noFill()</a> are called, nothing will be drawn to the
        +   * screen.
        +   *
        +   * @method noStroke
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   noStroke();
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with no outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A pink cube with no edge outlines spinning on a black canvas.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Style the box.
        +   *   noStroke();
        +   *   fill(240, 150, 150);
        +   *
        +   *   // Rotate the coordinates.
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Draw the box.
        +   *   box(45);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noStroke = function() {
        +    this._renderer.states.strokeColor = null;
        +    return this;
        +  };
         
        -/**
        - * Sets the color used to draw points, lines, and the outlines of shapes.
        - *
        - * Calling `stroke(255, 165, 0)` or `stroke('orange')` means all shapes drawn
        - * after calling `stroke()` will be filled with the color orange. The way
        - * these parameters are interpreted may be changed with the
        - * <a href="#/p5/colorMode">colorMode()</a> function.
        - *
        - * The version of `stroke()` with one parameter interprets the value one of
        - * three ways. If the parameter is a `Number`, it's interpreted as a grayscale
        - * value. If the parameter is a `String`, it's interpreted as a CSS color
        - * string. A <a href="#/p5.Color">p5.Color</a> object can also be provided to
        - * set the stroke color.
        - *
        - * The version of `stroke()` with two parameters interprets the first one as a
        - * grayscale value. The second parameter sets the alpha (transparency) value.
        - *
        - * The version of `stroke()` with three parameters interprets them as RGB, HSB,
        - * or HSL colors, depending on the current `colorMode()`.
        - *
        - * The version of `stroke()` with four parameters interprets them as RGBA, HSBA,
        - * or HSLA colors, depending on the current `colorMode()`. The last parameter
        - * sets the alpha (transparency) value.
        - *
        - * @method stroke
        - * @param  {Number}        v1      red value if color mode is RGB or hue value if color mode is HSB.
        - * @param  {Number}        v2      green value if color mode is RGB or saturation value if color mode is HSB.
        - * @param  {Number}        v3      blue value if color mode is RGB or brightness value if color mode is HSB.
        - * @param  {Number}        [alpha] alpha value, controls transparency (0 - transparent, 255 - opaque).
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // A grayscale value.
        - *   strokeWeight(4);
        - *   stroke(51);
        - *   square(20, 20, 60);
        - *
        - *   describe('A white square with a dark charcoal gray outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // R, G & B values.
        - *   stroke(255, 204, 0);
        - *   strokeWeight(4);
        - *   square(20, 20, 60);
        - *
        - *   describe('A white square with a yellow outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use HSB color.
        - *   colorMode(HSB);
        - *
        - *   // H, S & B values.
        - *   strokeWeight(4);
        - *   stroke(255, 204, 100);
        - *   square(20, 20, 60);
        - *
        - *   describe('A white square with a royal blue outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // A CSS named color.
        - *   stroke('red');
        - *   strokeWeight(4);
        - *   square(20, 20, 60);
        - *
        - *   describe('A white square with a red outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Three-digit hex RGB notation.
        - *   stroke('#fae');
        - *   strokeWeight(4);
        - *   square(20, 20, 60);
        - *
        - *   describe('A white square with a pink outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Six-digit hex RGB notation.
        - *   stroke('#222222');
        - *   strokeWeight(4);
        - *   square(20, 20, 60);
        - *
        - *   describe('A white square with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Integer RGB notation.
        - *   stroke('rgb(0, 255, 0)');
        - *   strokeWeight(4);
        - *   square(20, 20, 60);
        - *
        - *   describe('A whiite square with a bright green outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Integer RGBA notation.
        - *   stroke('rgba(0, 255, 0, 0.25)');
        - *   strokeWeight(4);
        - *   square(20, 20, 60);
        - *
        - *   describe('A white square with a soft green outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Percentage RGB notation.
        - *   stroke('rgb(100%, 0%, 10%)');
        - *   strokeWeight(4);
        - *   square(20, 20, 60);
        - *
        - *   describe('A white square with a red outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Percentage RGBA notation.
        - *   stroke('rgba(100%, 0%, 100%, 0.5)');
        - *   strokeWeight(4);
        - *   square(20, 20, 60);
        - *
        - *   describe('A white square with a dark fuchsia outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // A p5.Color object.
        - *   stroke(color(0, 0, 255));
        - *   strokeWeight(4);
        - *   square(20, 20, 60);
        - *
        - *   describe('A white square with a blue outline.');
        - * }
        - * </code>
        - * </div>
        - */
        +  /**
        +   * Sets the color used to draw points, lines, and the outlines of shapes.
        +   *
        +   * Calling `stroke(255, 165, 0)` or `stroke('orange')` means all shapes drawn
        +   * after calling `stroke()` will be filled with the color orange. The way
        +   * these parameters are interpreted may be changed with the
        +   * <a href="#/p5/colorMode">colorMode()</a> function.
        +   *
        +   * The version of `stroke()` with one parameter interprets the value one of
        +   * three ways. If the parameter is a `Number`, it's interpreted as a grayscale
        +   * value. If the parameter is a `String`, it's interpreted as a CSS color
        +   * string. A <a href="#/p5.Color">p5.Color</a> object can also be provided to
        +   * set the stroke color.
        +   *
        +   * The version of `stroke()` with two parameters interprets the first one as a
        +   * grayscale value. The second parameter sets the alpha (transparency) value.
        +   *
        +   * The version of `stroke()` with three parameters interprets them as RGB, HSB,
        +   * or HSL colors, depending on the current `colorMode()`.
        +   *
        +   * The version of `stroke()` with four parameters interprets them as RGBA, HSBA,
        +   * or HSLA colors, depending on the current `colorMode()`. The last parameter
        +   * sets the alpha (transparency) value.
        +   *
        +   * @method stroke
        +   * @param  {Number}        v1      red value if color mode is RGB or hue value if color mode is HSB.
        +   * @param  {Number}        v2      green value if color mode is RGB or saturation value if color mode is HSB.
        +   * @param  {Number}        v3      blue value if color mode is RGB or brightness value if color mode is HSB.
        +   * @param  {Number}        [alpha] alpha value, controls transparency (0 - transparent, 255 - opaque).
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // A grayscale value.
        +   *   strokeWeight(4);
        +   *   stroke(51);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with a dark charcoal gray outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // R, G & B values.
        +   *   stroke(255, 204, 0);
        +   *   strokeWeight(4);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with a yellow outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use HSB color.
        +   *   colorMode(HSB);
        +   *
        +   *   // H, S & B values.
        +   *   strokeWeight(4);
        +   *   stroke(255, 204, 100);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with a royal blue outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // A CSS named color.
        +   *   stroke('red');
        +   *   strokeWeight(4);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with a red outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Three-digit hex RGB notation.
        +   *   stroke('#fae');
        +   *   strokeWeight(4);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with a pink outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Six-digit hex RGB notation.
        +   *   stroke('#222222');
        +   *   strokeWeight(4);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with a black outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Integer RGB notation.
        +   *   stroke('rgb(0, 255, 0)');
        +   *   strokeWeight(4);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with a bright green outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Integer RGBA notation.
        +   *   stroke('rgba(0, 255, 0, 0.25)');
        +   *   strokeWeight(4);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with a soft green outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Percentage RGB notation.
        +   *   stroke('rgb(100%, 0%, 10%)');
        +   *   strokeWeight(4);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with a red outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Percentage RGBA notation.
        +   *   stroke('rgba(100%, 0%, 100%, 0.5)');
        +   *   strokeWeight(4);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with a dark fuchsia outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // A p5.Color object.
        +   *   stroke(color(0, 0, 255));
        +   *   strokeWeight(4);
        +   *   square(20, 20, 60);
        +   *
        +   *   describe('A white square with a blue outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -/**
        - * @method stroke
        - * @param  {String}        value   a color string.
        - * @chainable
        - */
        +  /**
        +   * @method stroke
        +   * @param  {String}        value   a color string.
        +   * @chainable
        +   */
         
        -/**
        - * @method stroke
        - * @param  {Number}        gray   a grayscale value.
        - * @param  {Number}        [alpha]
        - * @chainable
        - */
        +  /**
        +   * @method stroke
        +   * @param  {Number}        gray   a grayscale value.
        +   * @param  {Number}        [alpha]
        +   * @chainable
        +   */
         
        -/**
        - * @method stroke
        - * @param  {Number[]}      values  an array containing the red, green, blue,
        - *                                 and alpha components of the color.
        - * @chainable
        - */
        +  /**
        +   * @method stroke
        +   * @param  {Number[]}      values  an array containing the red, green, blue,
        +   *                                 and alpha components of the color.
        +   * @chainable
        +   */
         
        -/**
        - * @method stroke
        - * @param  {p5.Color}      color   the stroke color.
        - * @chainable
        - */
        +  /**
        +   * @method stroke
        +   * @param  {p5.Color}      color   the stroke color.
        +   * @chainable
        +   */
        +  fn.stroke = function(...args) {
        +    this._renderer.stroke(...args);
        +    return this;
        +  };
         
        -p5.prototype.stroke = function(...args) {
        -  this._renderer._setProperty('_strokeSet', true);
        -  this._renderer._setProperty('_doStroke', true);
        -  this._renderer.stroke(...args);
        -  return this;
        -};
        +  /**
        +   * Starts using shapes to erase parts of the canvas.
        +   *
        +   * All drawing that follows `erase()` will subtract from the canvas, revealing
        +   * the web page underneath. The erased areas will become transparent, allowing
        +   * the content behind the canvas to show through. The
        +   * <a href="#/p5/fill">fill()</a>, <a href="#/p5/stroke">stroke()</a>, and
        +   * <a href="#/p5/blendMode">blendMode()</a> have no effect once `erase()` is
        +   * called.
        +   *
        +   * The `erase()` function has two optional parameters. The first parameter
        +   * sets the strength of erasing by the shape's interior. A value of 0 means
        +   * that no erasing will occur. A value of 255 means that the shape's interior
        +   * will fully erase the content underneath. The default value is 255
        +   * (full strength).
        +   *
        +   * The second parameter sets the strength of erasing by the shape's edge. A
        +   * value of 0 means that no erasing will occur. A value of 255 means that the
        +   * shape's edge will fully erase the content underneath. The default value is
        +   * 255 (full strength).
        +   *
        +   * To cancel the erasing effect, use the <a href="#/p5/noErase">noErase()</a>
        +   * function.
        +   *
        +   * `erase()` has no effect on drawing done with the
        +   * <a href="#/p5/image">image()</a> and
        +   * <a href="#/p5/background">background()</a> functions.
        +   *
        +   * @method erase
        +   * @param  {Number}   [strengthFill]      a number (0-255) for the strength of erasing under a shape's interior.
        +   *                                        Defaults to 255, which is full strength.
        +   * @param  {Number}   [strengthStroke]    a number (0-255) for the strength of erasing under a shape's edge.
        +   *                                        Defaults to 255, which is full strength.
        +   *
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(100, 100, 250);
        +   *
        +   *   // Draw a pink square.
        +   *   fill(250, 100, 100);
        +   *   square(20, 20, 60);
        +   *
        +   *   // Erase a circular area.
        +   *   erase();
        +   *   circle(25, 30, 30);
        +   *   noErase();
        +   *
        +   *   describe('A purple canvas with a pink square in the middle. A circle is erased from the top-left, leaving a hole.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(100, 100, 250);
        +   *
        +   *   // Draw a pink square.
        +   *   fill(250, 100, 100);
        +   *   square(20, 20, 60);
        +   *
        +   *   // Erase a circular area.
        +   *   strokeWeight(5);
        +   *   erase(150, 255);
        +   *   circle(25, 30, 30);
        +   *   noErase();
        +   *
        +   *   describe('A purple canvas with a pink square in the middle. A circle at the top-left partially erases its interior and a fully erases its outline.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.erase = function(opacityFill = 255, opacityStroke = 255) {
        +    this._renderer.erase(opacityFill, opacityStroke);
         
        -/**
        - * Starts using shapes to erase parts of the canvas.
        - *
        - * All drawing that follows `erase()` will subtract from the canvas, revealing
        - * the web page underneath. The erased areas will become transparent, allowing
        - * the content behind the canvas to show through. The
        - * <a href="#/p5/fill">fill()</a>, <a href="#/p5/stroke">stroke()</a>, and
        - * <a href="#/p5/blendMode">blendMode()</a> have no effect once `erase()` is
        - * called.
        - *
        - * The `erase()` function has two optional parameters. The first parameter
        - * sets the strength of erasing by the shape's interior. A value of 0 means
        - * that no erasing will occur. A value of 255 means that the shape's interior
        - * will fully erase the content underneath. The default value is 255
        - * (full strength).
        - *
        - * The second parameter sets the strength of erasing by the shape's edge. A
        - * value of 0 means that no erasing will occur. A value of 255 means that the
        - * shape's edge will fully erase the content underneath. The default value is
        - * 255 (full strength).
        - *
        - * To cancel the erasing effect, use the <a href="#/p5/noErase">noErase()</a>
        - * function.
        - *
        - * `erase()` has no effect on drawing done with the
        - * <a href="#/p5/image">image()</a> and
        - * <a href="#/p5/background">background()</a> functions.
        - *
        - * @method erase
        - * @param  {Number}   [strengthFill]      a number (0-255) for the strength of erasing under a shape's interior.
        - *                                        Defaults to 255, which is full strength.
        - * @param  {Number}   [strengthStroke]    a number (0-255) for the strength of erasing under a shape's edge.
        - *                                        Defaults to 255, which is full strength.
        - *
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(100, 100, 250);
        - *
        - *   // Draw a pink square.
        - *   fill(250, 100, 100);
        - *   square(20, 20, 60);
        - *
        - *   // Erase a circular area.
        - *   erase();
        - *   circle(25, 30, 30);
        - *   noErase();
        - *
        - *   describe('A purple canvas with a pink square in the middle. A circle is erased from the top-left, leaving a hole.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(100, 100, 250);
        - *
        - *   // Draw a pink square.
        - *   fill(250, 100, 100);
        - *   square(20, 20, 60);
        - *
        - *   // Erase a circular area.
        - *   strokeWeight(5);
        - *   erase(150, 255);
        - *   circle(25, 30, 30);
        - *   noErase();
        - *
        - *   describe('A purple canvas with a pink square in the middle. A circle at the top-left partially erases its interior and a fully erases its outline.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.erase = function(opacityFill = 255, opacityStroke = 255) {
        -  this._renderer.erase(opacityFill, opacityStroke);
        +    return this;
        +  };
         
        -  return this;
        -};
        +  /**
        +   * Ends erasing that was started with <a href="#/p5/erase">erase()</a>.
        +   *
        +   * The <a href="#/p5/fill">fill()</a>, <a href="#/p5/stroke">stroke()</a>, and
        +   * <a href="#/p5/blendMode">blendMode()</a> settings will return to what they
        +   * were prior to calling <a href="#/p5/erase">erase()</a>.
        +   *
        +   * @method noErase
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(235, 145, 15);
        +   *
        +   *   // Draw the left rectangle.
        +   *   noStroke();
        +   *   fill(30, 45, 220);
        +   *   rect(30, 10, 10, 80);
        +   *
        +   *   // Erase a circle.
        +   *   erase();
        +   *   circle(50, 50, 60);
        +   *   noErase();
        +   *
        +   *   // Draw the right rectangle.
        +   *   rect(70, 10, 10, 80);
        +   *
        +   *   describe('An orange canvas with two tall blue rectangles. A circular hole in the center erases the rectangle on the left but not the one on the right.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noErase = function() {
        +    this._renderer.noErase();
        +    return this;
        +  };
         
        -/**
        - * Ends erasing that was started with <a href="#/p5/erase">erase()</a>.
        - *
        - * The <a href="#/p5/fill">fill()</a>, <a href="#/p5/stroke">stroke()</a>, and
        - * <a href="#/p5/blendMode">blendMode()</a> settings will return to what they
        - * were prior to calling <a href="#/p5/erase">erase()</a>.
        - *
        - * @method noErase
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(235, 145, 15);
        - *
        - *   // Draw the left rectangle.
        - *   noStroke();
        - *   fill(30, 45, 220);
        - *   rect(30, 10, 10, 80);
        - *
        - *   // Erase a circle.
        - *   erase();
        - *   circle(50, 50, 60);
        - *   noErase();
        - *
        - *   // Draw the right rectangle.
        - *   rect(70, 10, 10, 80);
        - *
        - *   describe('An orange canvas with two tall blue rectangles. A circular hole in the center erases the rectangle on the left but not the one on the right.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noErase = function() {
        -  this._renderer.noErase();
        -  return this;
        -};
        +  /**
        +   * Sets the way colors blend when added to the canvas.
        +   *
        +   * By default, drawing with a solid color paints over the current pixel values
        +   * on the canvas. `blendMode()` offers many options for blending colors.
        +   *
        +   * Shapes, images, and text can be used as sources for drawing to the canvas.
        +   * A source pixel changes the color of the canvas pixel where it's drawn. The
        +   * final color results from blending the source pixel's color with the canvas
        +   * pixel's color. RGB color values from the source and canvas pixels are
        +   * compared, added, subtracted, multiplied, and divided to create different
        +   * effects. Red values with red values, greens with greens, and blues with
        +   * blues.
        +   *
        +   * The parameter, `mode`, sets the blend mode. For example, calling
        +   * `blendMode(ADD)` sets the blend mode to `ADD`. The following blend modes
        +   * are available in both 2D and WebGL mode:
        +   *
        +   * - `BLEND`: color values from the source overwrite the canvas. This is the default mode.
        +   * - `ADD`: color values from the source are added to values from the canvas.
        +   * - `DARKEST`: keeps the darkest color value.
        +   * - `LIGHTEST`: keeps the lightest color value.
        +   * - `EXCLUSION`: similar to `DIFFERENCE` but with less contrast.
        +   * - `MULTIPLY`: color values from the source are multiplied with values from the canvas. The result is always darker.
        +   * - `SCREEN`: all color values are inverted, then multiplied, then inverted again. The result is always lighter. (Opposite of `MULTIPLY`)
        +   * - `REPLACE`: the last source drawn completely replaces the rest of the canvas.
        +   * - `REMOVE`: overlapping pixels are removed by making them completely transparent.
        +   *
        +   * The following blend modes are only available in 2D mode:
        +   *
        +   * - `DIFFERENCE`: color values from the source are subtracted from the values from the canvas. If the difference is a negative number, it's made positive.
        +   * - `OVERLAY`: combines `MULTIPLY` and `SCREEN`. Dark values in the canvas get darker and light values get lighter.
        +   * - `HARD_LIGHT`: combines `MULTIPLY` and `SCREEN`. Dark values in the source get darker and light values get lighter.
        +   * - `SOFT_LIGHT`: a softer version of `HARD_LIGHT`.
        +   * - `DODGE`: lightens light tones and increases contrast. Divides the canvas color values by the inverted color values from the source.
        +   * - `BURN`: darkens dark tones and increases contrast. Divides the source color values by the inverted color values from the canvas, then inverts the result.
        +   *
        +   * The following blend modes are only available in WebGL mode:
        +   *
        +   * - `SUBTRACT`: RGB values from the source are subtracted from the values from the canvas. If the difference is a negative number, it's made positive. Alpha (transparency) values from the source and canvas are added.
        +   *
        +   * @method blendMode
        +   * @param  {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|REMOVE|SUBTRACT)} mode blend mode to set.
        +   *                either BLEND, DARKEST, LIGHTEST, DIFFERENCE, MULTIPLY,
        +   *                EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,
        +   *                SOFT_LIGHT, DODGE, BURN, ADD, REMOVE or SUBTRACT
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use the default blend mode.
        +   *   blendMode(BLEND);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A blue line and a red line form an X on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(ADD);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is faint magenta.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(DARKEST);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A blue line and a red line form an X on a gray background. The area where they overlap is black.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(LIGHTEST);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is faint magenta.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(EXCLUSION);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A yellow line and a cyan line form an X on a gray background. The area where they overlap is green.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(MULTIPLY);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A blue line and a red line form an X on a gray background. The area where they overlap is black.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(SCREEN);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is faint magenta.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(REPLACE);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A diagonal red line.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(REMOVE);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('The silhouette of an X is missing from a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(DIFFERENCE);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A yellow line and a cyan line form an X on a gray background. The area where they overlap is green.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(OVERLAY);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is bright magenta.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(HARD_LIGHT);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A blue line and a red line form an X on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(SOFT_LIGHT);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is violet.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(DODGE);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A faint blue line and a faint red line form an X on a gray background. The area where they overlap is faint violet.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(BURN);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A blue line and a red line form an X on a gray background. The area where they overlap is black.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the blend mode.
        +   *   blendMode(SUBTRACT);
        +   *
        +   *   // Style the lines.
        +   *   strokeWeight(30);
        +   *
        +   *   // Draw the blue line.
        +   *   stroke('blue');
        +   *   line(25, 25, 75, 75);
        +   *
        +   *   // Draw the red line.
        +   *   stroke('red');
        +   *   line(75, 25, 25, 75);
        +   *
        +   *   describe('A yellow line and a turquoise line form an X on a gray background. The area where they overlap is green.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.blendMode = function (mode) {
        +    // p5._validateParameters('blendMode', arguments);
        +    if (mode === constants.NORMAL) {
        +      // Warning added 3/26/19, can be deleted in future (1.0 release?)
        +      console.warn(
        +        'NORMAL has been deprecated for use in blendMode. defaulting to BLEND instead.'
        +      );
        +      mode = constants.BLEND;
        +    }
        +    this._renderer.blendMode(mode);
        +  };
        +}
        +
        +export default setting;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  setting(p5, p5.prototype);
        +}
        diff --git a/src/core/constants.js b/src/core/constants.js
        index bf28b50164..dd7cde820f 100644
        --- a/src/core/constants.js
        +++ b/src/core/constants.js
        @@ -11,16 +11,19 @@ const _PI = Math.PI;
          * @property {String} VERSION
          * @final
          */
        -export const VERSION =
        -  'VERSION_CONST_WILL_BE_REPLACED_BY_BROWSERIFY_BUILD_PROCESS';
        +export const VERSION = 'VERSION_WILL_BE_REPLACED_BY_BUILD';
         
         // GRAPHICS RENDERER
         /**
          * The default, two-dimensional renderer.
        - * @property {String} P2D
        + * @typedef {unique symbol} P2D
        + * @property {P2D} P2D
          * @final
          */
         export const P2D = 'p2d';
        +
        +export const P2DHDR = 'p2d-hdr';
        +
         /**
          * One of the two render modes in p5.js, used for computationally intensive tasks like 3D rendering and shaders.
          *
        @@ -38,7 +41,8 @@ export const P2D = 'p2d';
          *
          * To learn more about WEBGL mode, check out <a href="https://p5js.org/tutorials/#webgl">all the interactive WEBGL tutorials</a> in the "Tutorials" section of this website, or read the wiki article <a href="https://github.com/processing/p5.js/wiki/Getting-started-with-WebGL-in-p5">"Getting started with WebGL in p5"</a>.
          *
        - * @property {String} WEBGL
        + * @typedef {unique symbol} WEBGL
        + * @property {WEBGL} WEBGL
          * @final
          */
         export const WEBGL = 'webgl';
        @@ -46,39 +50,58 @@ export const WEBGL = 'webgl';
          * One of the two possible values of a WebGL canvas (either WEBGL or WEBGL2),
          * which can be used to determine what capabilities the rendering environment
          * has.
        - * @property {String} WEBGL2
        + * @typedef {unique symbol} WEBGL2
        + * @property {WEBGL2} WEBGL2
          * @final
          */
         export const WEBGL2 = 'webgl2';
         
         // ENVIRONMENT
         /**
        - * @property {String} ARROW
        + * @typedef {'default'} ARROW
        + * @property {ARROW} ARROW
          * @final
          */
         export const ARROW = 'default';
        +
        +/**
        + * @property {String} SIMPLE
        + * @final
        + */
        +export const SIMPLE = 'simple';
        +/**
        + * @property {String} FULL
        + * @final
        + */
        +export const FULL = 'full';
        +
         /**
        - * @property {String} CROSS
        + * @typedef {'crosshair'} CROSS
        + * @property {CROSS} CROSS
          * @final
          */
         export const CROSS = 'crosshair';
         /**
        - * @property {String} HAND
        + * @typedef {'pointer'} HAND
        + * @property {HAND} HAND
          * @final
          */
         export const HAND = 'pointer';
         /**
        - * @property {String} MOVE
        + * @typedef {'move'} MOVE
        + * @property {MOVE} MOVE
          * @final
          */
         export const MOVE = 'move';
         /**
        - * @property {String} TEXT
        + * @typedef {'text'} TEXT
        + * @property {TEXT} TEXT
          * @final
          */
         export const TEXT = 'text';
         /**
        - * @property {String} WAIT
        + * @typedef {'wait'} WAIT
        + * @property {WAIT} WAIT
          * @final
          */
         export const WAIT = 'wait';
        @@ -555,7 +578,8 @@ export const TWO_PI = _PI * 2;
          *
          * Note: `TWO_PI` radians equals 360˚.
          *
        - * @property {String} DEGREES
        + * @typedef {unique symbol} DEGREES
        + * @property {DEGREES} DEGREES
          * @final
          *
          * @example
        @@ -582,7 +606,7 @@ export const TWO_PI = _PI * 2;
          * </code>
          * </div>
          */
        -export const DEGREES = 'degrees';
        +// export const DEGREES = Symbol('degrees');
         
         /**
          * A `String` constant that's used to set the
        @@ -597,7 +621,8 @@ export const DEGREES = 'degrees';
          *
          * Note: `TWO_PI` radians equals 360˚.
          *
        - * @property {String} RADIANS
        + * @typedef {unique symbol} RADIANS
        + * @property {RADIANS} RADIANS
          * @final
          *
          * @example
        @@ -627,184 +652,221 @@ export const DEGREES = 'degrees';
          * </code>
          * </div>
          */
        -export const RADIANS = 'radians';
        +// export const RADIANS = Symbol('radians');
         export const DEG_TO_RAD = _PI / 180.0;
         export const RAD_TO_DEG = 180.0 / _PI;
         
         // SHAPE
         /**
        - * @property {String} CORNER
        + * @typedef {'corner'} CORNER
        + * @property {CORNER} CORNER
          * @final
          */
         export const CORNER = 'corner';
         /**
        - * @property {String} CORNERS
        + * @typedef {'corners'} CORNERS
        + * @property {CORNERS} CORNERS
          * @final
          */
         export const CORNERS = 'corners';
         /**
        - * @property {String} RADIUS
        + * @typedef {'radius'} RADIUS
        + * @property {RADIUS} RADIUS
          * @final
          */
         export const RADIUS = 'radius';
         /**
        - * @property {String} RIGHT
        + * @typedef {'right'} RIGHT
        + * @property {RIGHT} RIGHT
          * @final
          */
         export const RIGHT = 'right';
         /**
        - * @property {String} LEFT
        + * @typedef {'left'} LEFT
        + * @property {LEFT} LEFT
          * @final
          */
         export const LEFT = 'left';
         /**
        - * @property {String} CENTER
        + * @typedef {'center'} CENTER
        + * @property {CENTER} CENTER
          * @final
          */
         export const CENTER = 'center';
         /**
        - * @property {String} TOP
        + * @typedef {'top'} TOP
        + * @property {TOP} TOP
          * @final
          */
         export const TOP = 'top';
         /**
        - * @property {String} BOTTOM
        + * @typedef {'bottom'} BOTTOM
        + * @property {BOTTOM} BOTTOM
          * @final
          */
         export const BOTTOM = 'bottom';
         /**
        - * @property {String} BASELINE
        + * @typedef {'alphabetic'} BASELINE
        + * @property {BASELINE} BASELINE
          * @final
        - * @default alphabetic
          */
         export const BASELINE = 'alphabetic';
         /**
        - * @property {Number} POINTS
        + * @typedef {0x0000} POINTS
        + * @property {POINTS} POINTS
          * @final
        - * @default 0x0000
          */
         export const POINTS = 0x0000;
         /**
        - * @property {Number} LINES
        + * @typedef {0x0001} LINES
        + * @property {LINES} LINES
          * @final
        - * @default 0x0001
          */
         export const LINES = 0x0001;
         /**
        - * @property {Number} LINE_STRIP
        + * @property {0x0003} LINE_STRIP
        + * @property {LINE_STRIP} LINE_STRIP
          * @final
        - * @default 0x0003
          */
         export const LINE_STRIP = 0x0003;
         /**
        - * @property {Number} LINE_LOOP
        + * @typedef {0x0002} LINE_LOOP
        + * @property {LINE_LOOP} LINE_LOOP
          * @final
        - * @default 0x0002
          */
         export const LINE_LOOP = 0x0002;
         /**
        - * @property {Number} TRIANGLES
        + * @typedef {0x0004} TRIANGLES
        + * @property {TRIANGLES} TRIANGLES
          * @final
        - * @default 0x0004
          */
         export const TRIANGLES = 0x0004;
         /**
        - * @property {Number} TRIANGLE_FAN
        + * @typedef {0x0006} TRIANGLE_FAN
        + * @property {TRIANGLE_FAN} TRIANGLE_FAN
          * @final
        - * @default 0x0006
          */
         export const TRIANGLE_FAN = 0x0006;
         /**
        - * @property {Number} TRIANGLE_STRIP
        + * @typedef {0x0005} TRIANGLE_STRIP
        + * @property {TRIANGLE_STRIP} TRIANGLE_STRIP
          * @final
        - * @default 0x0005
          */
         export const TRIANGLE_STRIP = 0x0005;
         /**
        - * @property {String} QUADS
        + * @typedef {'quads'} QUADS
        + * @property {QUADS} QUADS
          * @final
          */
         export const QUADS = 'quads';
         /**
        - * @property {String} QUAD_STRIP
        + * @typedef {'quad_strip'} QUAD_STRIP
        + * @property {QUAD_STRIP} QUAD_STRIP
          * @final
        - * @default quad_strip
          */
         export const QUAD_STRIP = 'quad_strip';
         /**
        - * @property {String} TESS
        + * @typedef {'tess'} TESS
        + * @property {TESS} TESS
          * @final
        - * @default tess
          */
         export const TESS = 'tess';
         /**
        - * @property {String} CLOSE
        + * @typedef {0x0007} EMPTY_PATH
        + * @property {EMPTY_PATH} EMPTY_PATH
        + * @final
        + */
        +export const EMPTY_PATH = 0x0007;
        +/**
        + * @typedef {0x0008} PATH
        + * @property {PATH} PATH
        + * @final
        + */
        +export const PATH = 0x0008;
        +/**
        + * @typedef {'close'} CLOSE
        + * @property {CLOSE} CLOSE
          * @final
          */
         export const CLOSE = 'close';
         /**
        - * @property {String} OPEN
        + * @typedef {'open'} OPEN
        + * @property {OPEN} OPEN
          * @final
          */
         export const OPEN = 'open';
         /**
        - * @property {String} CHORD
        + * @typedef {'chord'} CHORD
        + * @property {CHORD} CHORD
          * @final
          */
         export const CHORD = 'chord';
         /**
        - * @property {String} PIE
        + * @typedef {'pie'} PIE
        + * @property {PIE} PIE
          * @final
          */
         export const PIE = 'pie';
         /**
        - * @property {String} PROJECT
        + * @typedef {'square'} PROJECT
        + * @property {PROJECT} PROJECT
          * @final
        - * @default square
          */
         export const PROJECT = 'square'; // PEND: careful this is counterintuitive
         /**
        - * @property {String} SQUARE
        + * @typedef {'butt'} SQUARE
        + * @property {SQUERE} SQUARE
          * @final
        - * @default butt
          */
         export const SQUARE = 'butt';
         /**
        - * @property {String} ROUND
        + * @typedef {'round'} ROUND
        + * @property {ROUND} ROUND
          * @final
          */
         export const ROUND = 'round';
         /**
        - * @property {String} BEVEL
        + * @typedef {'bevel'} BEVEL
        + * @property {BEVEL} BEVEL
          * @final
          */
         export const BEVEL = 'bevel';
         /**
        - * @property {String} MITER
        + * @typedef {'miter'} MITER
        + * @property {MITER} MITER
          * @final
          */
         export const MITER = 'miter';
         
         // COLOR
         /**
        - * @property {String} RGB
        + * @typedef {'rgb'} RGB
        + * @property {RGB} RGB
          * @final
          */
        -export const RGB = 'rgb';
        +// export const RGB = 'rgb';
         /**
          * HSB (hue, saturation, brightness) is a type of color model.
          * You can learn more about it at
          * <a href="https://learnui.design/blog/the-hsb-color-system-practicioners-primer.html">HSB</a>.
          *
        - * @property {String} HSB
        + * @typedef {'hsb'} HSB
        + * @property {HSB} HSB
          * @final
          */
        -export const HSB = 'hsb';
        +// export const HSB = 'hsb';
         /**
        - * @property {String} HSL
        + * @typedef {'hsl'} HSL
        + * @property {HSL} HSL
          * @final
          */
        -export const HSL = 'hsl';
        +// export const HSL = 'hsl';
        +/**
        + * @typedef {'rgba'} RGBA
        + * @property {RGBA} RGBA
        + * @final
        + */
        +// export const RGBA = 'rgba';
         
         // DOM EXTENSION
         /**
        @@ -812,244 +874,280 @@ export const HSL = 'hsl';
          * based on the current height and width of the element. Only one parameter can
          * be passed to the <a href="/reference/#/p5.Element/size">size</a> function as AUTO, at a time.
          *
        - * @property {String} AUTO
        + * @typedef {'auto'} AUTO
        + * @property {AUTO} AUTO
          * @final
          */
         export const AUTO = 'auto';
         
         /**
        - * @property {Number} ALT
        + * @typedef {18} ALT
        + * @property {ALT} ALT
          * @final
          */
         // INPUT
         export const ALT = 18;
         /**
        - * @property {Number} BACKSPACE
        + * @typedef {8} BACKSPACE
        + * @property {BACKSPACE} BACKSPACE
          * @final
          */
         export const BACKSPACE = 8;
         /**
        - * @property {Number} CONTROL
        + * @typedef {17} CONTROL
        + * @property {CONTROL} CONTROL
          * @final
          */
         export const CONTROL = 17;
         /**
        - * @property {Number} DELETE
        + * @typedef {46} DELETE
        + * @property {DELETE} DELETE
          * @final
          */
         export const DELETE = 46;
         /**
        - * @property {Number} DOWN_ARROW
        + * @typedef {40} DOWN_ARROW
        + * @property {DOWN_ARROW} DOWN_ARROW
          * @final
          */
         export const DOWN_ARROW = 40;
         /**
        - * @property {Number} ENTER
        + * @typedef {13} ENTER
        + * @property {ENTER} ENTER
          * @final
          */
         export const ENTER = 13;
         /**
        - * @property {Number} ESCAPE
        + * @typedef {27} ESCAPE
        + * @property {ESCAPE} ESCAPE
          * @final
          */
         export const ESCAPE = 27;
         /**
        - * @property {Number} LEFT_ARROW
        + * @typedef {37} LEFT_ARROW
        + * @property {LEFT_ARROW} LEFT_ARROW
          * @final
          */
         export const LEFT_ARROW = 37;
         /**
        - * @property {Number} OPTION
        + * @typedef {18} OPTION
        + * @property {OPTION} OPTION
          * @final
          */
         export const OPTION = 18;
         /**
        - * @property {Number} RETURN
        + * @typedef {13} RETURN
        + * @property {RETURN} RETURN
          * @final
          */
         export const RETURN = 13;
         /**
        - * @property {Number} RIGHT_ARROW
        + * @typedef {39} RIGHT_ARROW
        + * @property {RIGHT_ARROW} RIGHT_ARROW
          * @final
          */
         export const RIGHT_ARROW = 39;
         /**
        - * @property {Number} SHIFT
        + * @typedef {16} SHIFT
        + * @property {SHIFT} SHIFT
          * @final
          */
         export const SHIFT = 16;
         /**
        - * @property {Number} TAB
        + * @typedef {9} TAB
        + * @property {TAB} TAB
          * @final
          */
         export const TAB = 9;
         /**
        - * @property {Number} UP_ARROW
        + * @typedef {38} UP_ARROW
        + * @property {UP_ARROW} UP_ARROW
          * @final
          */
         export const UP_ARROW = 38;
         
         // RENDERING
         /**
        - * @property {String} BLEND
        + * @typedef {'source-over'} BLEND
        + * @property {BLEND} BLEND
          * @final
        - * @default source-over
          */
         export const BLEND = 'source-over';
         /**
        - * @property {String} REMOVE
        + * @typedef {'destination-out'} REMOVE
        + * @property {REMOVE} REMOVE
          * @final
        - * @default destination-out
          */
         export const REMOVE = 'destination-out';
         /**
        - * @property {String} ADD
        + * @typedef {'lighter'} ADD
        + * @property {ADD} ADD
          * @final
        - * @default lighter
          */
         export const ADD = 'lighter';
        -//ADD: 'add', //
        -//SUBTRACT: 'subtract', //
         /**
        - * @property {String} DARKEST
        + * @typedef {'darken'} DARKEST
        + * @property {DARKEST} DARKEST
          * @final
          */
         export const DARKEST = 'darken';
         /**
        - * @property {String} LIGHTEST
        + * @typedef {'lighten'} LIGHTEST
        + * @property {LIGHTEST} LIGHTEST
          * @final
        - * @default lighten
          */
         export const LIGHTEST = 'lighten';
         /**
        - * @property {String} DIFFERENCE
        + * @typedef {'difference'} DIFFERENCE
        + * @property {DIFFERENCE} DIFFERENCE
          * @final
          */
         export const DIFFERENCE = 'difference';
         /**
        - * @property {String} SUBTRACT
        + * @typedef {'subtract'} SUBTRACT
        + * @property {SUBTRACT} SUBTRACT
          * @final
          */
         export const SUBTRACT = 'subtract';
         /**
        - * @property {String} EXCLUSION
        + * @typedef {'exclusion'} EXCLUSION
        + * @property {EXCLUSION} EXCLUSION
          * @final
          */
         export const EXCLUSION = 'exclusion';
         /**
        - * @property {String} MULTIPLY
        + * @typedef {'multiply'} MULTIPLY
        + * @property {MULTIPLY} MULTIPLY
          * @final
          */
         export const MULTIPLY = 'multiply';
         /**
        - * @property {String} SCREEN
        + * @typedef {'screen'} SCREEN
        + * @property {SCREEN} SCREEN
          * @final
          */
         export const SCREEN = 'screen';
         /**
        - * @property {String} REPLACE
        + * @typedef {'copy'} REPLACE
        + * @property {REPLACE} REPLACE
          * @final
        - * @default copy
          */
         export const REPLACE = 'copy';
         /**
        - * @property {String} OVERLAY
        + * @typedef {'overlay'} OVERLAY
        + * @property {OVERLAY} OVERLAY
          * @final
          */
         export const OVERLAY = 'overlay';
         /**
        - * @property {String} HARD_LIGHT
        + * @typedef {'hard-light'} HARD_LIGHT
        + * @property {HARD_LIGHT} HARD_LIGHT
          * @final
          */
         export const HARD_LIGHT = 'hard-light';
         /**
        - * @property {String} SOFT_LIGHT
        + * @typedef {'soft-light'} SOFT_LIGHT
        + * @property {SOFT_LIGHT} SOFT_LIGHT
          * @final
          */
         export const SOFT_LIGHT = 'soft-light';
         /**
        - * @property {String} DODGE
        + * @typedef {'color-dodge'} DODGE
        + * @property {DODGE} DODGE
          * @final
        - * @default color-dodge
          */
         export const DODGE = 'color-dodge';
         /**
        - * @property {String} BURN
        + * @typedef {'color-burn'} BURN
        + * @property {BURN} BURN
          * @final
        - * @default color-burn
          */
         export const BURN = 'color-burn';
         
         // FILTERS
         /**
        - * @property {String} THRESHOLD
        + * @typedef {'threshold'} THRESHOLD
        + * @property {THRESHOLD} THRESHOLD
          * @final
          */
         export const THRESHOLD = 'threshold';
         /**
        - * @property {String} GRAY
        + * @typedef {'gray'} GRAY
        + * @property {GRAY} GRAY
          * @final
          */
         export const GRAY = 'gray';
         /**
        - * @property {String} OPAQUE
        + * @typedef {'opaque'} OPAQUE
        + * @property {OPAQUE} OPAQUE
          * @final
          */
         export const OPAQUE = 'opaque';
         /**
        - * @property {String} INVERT
        + * @typedef {'invert'} INVERT
        + * @property {INVERT} INVERT
          * @final
          */
         export const INVERT = 'invert';
         /**
        - * @property {String} POSTERIZE
        + * @typedef {'posterize'} POSTERIZE
        + * @property {POSTERIZE} POSTERIZE
          * @final
          */
         export const POSTERIZE = 'posterize';
         /**
        - * @property {String} DILATE
        + * @typedef {'dilate'} DILATE
        + * @property {DILATE} DILATE
          * @final
          */
         export const DILATE = 'dilate';
         /**
        - * @property {String} ERODE
        + * @typedef {'erode'} ERODE
        + * @property {ERODE} ERODE
          * @final
          */
         export const ERODE = 'erode';
         /**
        - * @property {String} BLUR
        + * @typedef {'blur'} BLUR
        + * @property {BLUR} BLUR
          * @final
          */
         export const BLUR = 'blur';
         
         // TYPOGRAPHY
         /**
        - * @property {String} NORMAL
        + * @typedef {'normal'} NORMAL
        + * @property {NORMAL} NORMAL
          * @final
          */
         export const NORMAL = 'normal';
         /**
        - * @property {String} ITALIC
        + * @typedef {'italic'} ITALIC
        + * @property {ITALIC} ITALIC
          * @final
          */
         export const ITALIC = 'italic';
         /**
        - * @property {String} BOLD
        + * @typedef {'bold'} BOLD
        + * @property {BOLD} BOLD
          * @final
          */
         export const BOLD = 'bold';
         /**
        - * @property {String} BOLDITALIC
        + * @typedef {'bold italic'} BOLDITALIC
        + * @property {BOLDITALIC} BOLDITALIC
          * @final
          */
         export const BOLDITALIC = 'bold italic';
         /**
        - * @property {String} CHAR
        + * @typedef {'CHAR'} CHAR
        + * @property {CHAR} CHAR
          * @final
          */
         export const CHAR = 'CHAR';
         /**
        - * @property {String} WORD
        + * @typedef {'WORD'} WORD
        + * @property {WORD} WORD
          * @final
          */
         export const WORD = 'WORD';
        @@ -1061,44 +1159,52 @@ export const _CTX_MIDDLE = 'middle';
         
         // VERTICES
         /**
        - * @property {String} LINEAR
        + * @typedef {'linear'} LINEAR
        + * @property {LINEAR} LINEAR
          * @final
          */
         export const LINEAR = 'linear';
         /**
        - * @property {String} QUADRATIC
        + * @typedef {'quadratic'} QUADRATIC
        + * @property {QUADRATIC} QUADRATIC
          * @final
          */
         export const QUADRATIC = 'quadratic';
         /**
        - * @property {String} BEZIER
        + * @typedef {'bezier'} BEZIER
        + * @property {BEZIER} BEZIER
          * @final
          */
         export const BEZIER = 'bezier';
         /**
        - * @property {String} CURVE
        + * @typedef {'curve'} CURVE
        + * @property {CURVE} CURVE
          * @final
          */
         export const CURVE = 'curve';
         
         // WEBGL DRAWMODES
         /**
        - * @property {String} STROKE
        + * @typedef {'stroke'} STROKE
        + * @property {STROKE} STROKE
          * @final
          */
         export const STROKE = 'stroke';
         /**
        - * @property {String} FILL
        + * @typedef {'fill'} FILL
        + * @property {FILL} FILL
          * @final
          */
         export const FILL = 'fill';
         /**
        - * @property {String} TEXTURE
        + * @typedef {'texture'} TEXTURE
        + * @property {TEXTURE} TEXTURE
          * @final
          */
         export const TEXTURE = 'texture';
         /**
        - * @property {String} IMMEDIATE
        + * @typedef {'immediate'} IMMEDIATE
        + * @property {IMMEDIATE} IMMEDIATE
          * @final
          */
         export const IMMEDIATE = 'immediate';
        @@ -1106,7 +1212,8 @@ export const IMMEDIATE = 'immediate';
         // WEBGL TEXTURE MODE
         // NORMAL already exists for typography
         /**
        - * @property {String} IMAGE
        + * @typedef {'image'} IMAGE
        + * @property {IMAGE} IMAGE
          * @final
          */
         export const IMAGE = 'image';
        @@ -1114,46 +1221,54 @@ export const IMAGE = 'image';
         // WEBGL TEXTURE WRAP AND FILTERING
         // LINEAR already exists above
         /**
        - * @property {String} NEAREST
        + * @typedef {'nearest'} NEAREST
        + * @property {NEAREST} NEAREST
          * @final
          */
         export const NEAREST = 'nearest';
         /**
        - * @property {String} REPEAT
        + * @typedef {'repeat'} REPEAT
        + * @property {REPEAT} REPEAT
          * @final
          */
         export const REPEAT = 'repeat';
         /**
        - * @property {String} CLAMP
        + * @typedef {'clamp'} CLAMP
        + * @property {CLAMP} CLAMP
          * @final
          */
         export const CLAMP = 'clamp';
         /**
        - * @property {String} MIRROR
        + * @typedef {'mirror'} MIRROR
        + * @property {MIRROR} MIRROR
          * @final
          */
         export const MIRROR = 'mirror';
         
         // WEBGL GEOMETRY SHADING
         /**
        - * @property {String} FLAT
        + * @typedef {'flat'} FLAT
        + * @property {FLAT} FLAT
          * @final
          */
         export const FLAT = 'flat';
         /**
        - * @property {String} SMOOTH
        + * @typedef {'smooth'} SMOOTH
        + * @property {SMOOTH} SMOOTH
          * @final
          */
         export const SMOOTH = 'smooth';
         
         // DEVICE-ORIENTATION
         /**
        - * @property {String} LANDSCAPE
        + * @typedef {'landscape'} LANDSCAPE
        + * @property {LANDSCAPE} LANDSCAPE
          * @final
          */
         export const LANDSCAPE = 'landscape';
         /**
        - * @property {String} PORTRAIT
        + * @typedef {'portrait'} PORTRAIT
        + * @property {PORTRAIT} PORTRAIT
          * @final
          */
         export const PORTRAIT = 'portrait';
        @@ -1163,66 +1278,98 @@ export const _DEFAULT_STROKE = '#000000';
         export const _DEFAULT_FILL = '#FFFFFF';
         
         /**
        - * @property {String} GRID
        + * @typedef {'grid'} GRID
        + * @property {GRID} GRID
          * @final
          */
         export const GRID = 'grid';
         
         /**
        - * @property {String} AXES
        + * @typedef {'axes'} AXES
        + * @property {AXES} AXES
          * @final
          */
         export const AXES = 'axes';
         
         /**
        - * @property {String} LABEL
        + * @typedef {'label'} LABEL
        + * @property {LABEL} LABEL
          * @final
          */
         export const LABEL = 'label';
         /**
        - * @property {String} FALLBACK
        + * @typedef {'fallback'} FALLBACK
        + * @property {FALLBACK} FALLBACK
          * @final
          */
         export const FALLBACK = 'fallback';
         
         /**
        - * @property {String} CONTAIN
        + * @typedef {'contain'} CONTAIN
        + * @property {CONTAIN} CONTAIN
          * @final
          */
         export const CONTAIN = 'contain';
         
         /**
        - * @property {String} COVER
        + * @typedef {'cover'} COVER
        + * @property {COVER} COVER
          * @final
          */
         export const COVER = 'cover';
         
         /**
        - * @property {String} UNSIGNED_BYTE
        + * @typedef {'unsigned-byte'} UNSIGNED_BYTE
        + * @property {UNSIGNED_BYTE} UNSIGNED_BYTE
          * @final
          */
         export const UNSIGNED_BYTE = 'unsigned-byte';
         
         /**
        - * @property {String} UNSIGNED_INT
        + * @typedef {'unsigned-int'} UNSIGNED_INT
        + * @property {UNSIGNED_INT} UNSIGNED_INT
          * @final
          */
         export const UNSIGNED_INT = 'unsigned-int';
         
         /**
        - * @property {String} FLOAT
        + * @typedef {'float'} FLOAT
        + * @property {FLOAT} FLOAT
          * @final
          */
         export const FLOAT = 'float';
         
         /**
        - * @property {String} HALF_FLOAT
        + * @typedef {'half-float'} HALF_FLOAT
        + * @property {HALF_FLOAT} HALF_FLOAT
          * @final
          */
         export const HALF_FLOAT = 'half-float';
         
         /**
        - * @property {String} RGBA
        + * The `splineProperty('ends')` mode where splines curve through
        + * their first and last points.
        + * @typedef {unique symbol} INCLUDE
        + * @property {INCLUDE} INCLUDE
        + * @final
        + */
        +export const INCLUDE = Symbol('include');
        +
        +/**
        + * The `splineProperty('ends')` mode where the first and last points in a spline
        + * affect the direction of the curve, but are not rendered.
        + * @typedef {unique symbol} EXCLUDE
        + * @property {EXCLUDE} EXCLUDE
        + * @final
        + */
        +export const EXCLUDE = Symbol('exclude');
        +
        +/**
        + * The `splineProperty('ends')` mode where the spline loops back to its first point.
        + * Only used internally.
        + * @typedef {unique symbol} JOIN
        + * @property {JOIN} JOIN
          * @final
        + * @private
          */
        -export const RGBA = 'rgba';
        +export const JOIN = Symbol('join');
        diff --git a/src/core/environment.js b/src/core/environment.js
        index 4d15491948..1cdb735644 100644
        --- a/src/core/environment.js
        +++ b/src/core/environment.js
        @@ -6,1252 +6,1410 @@
          * @requires constants
          */
         
        -import p5 from './main';
         import * as C from './constants';
        +import { Vector } from '../math/p5.Vector';
         
        -const standardCursors = [C.ARROW, C.CROSS, C.HAND, C.MOVE, C.TEXT, C.WAIT];
        +function environment(p5, fn){
        +  const standardCursors = [C.ARROW, C.CROSS, C.HAND, C.MOVE, C.TEXT, C.WAIT];
         
        -p5.prototype._frameRate = 0;
        -p5.prototype._lastFrameTime = window.performance.now();
        -p5.prototype._targetFrameRate = 60;
        +  fn._frameRate = 0;
        +  fn._lastFrameTime = window.performance.now();
        +  fn._targetFrameRate = 60;
         
        -const _windowPrint = window.print;
        -let windowPrintDisabled = false;
        +  const _windowPrint = window.print;
        +  let windowPrintDisabled = false;
         
        -/**
        - * Displays text in the web browser's console.
        - *
        - * `print()` is helpful for printing values while debugging. Each call to
        - * `print()` creates a new line of text.
        - *
        - * Note: Call `print('\n')` to print a blank line. Calling `print()` without
        - * an argument opens the browser's dialog for printing documents.
        - *
        - * @method print
        - * @param {Any} contents content to print to the console.
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Prints "hello, world" to the console.
        - *   print('hello, world');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   let name = 'ada';
        - *   // Prints "hello, ada" to the console.
        - *   print(`hello, ${name}`);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.print = function(...args) {
        -  if (!args.length) {
        -    if (!windowPrintDisabled) {
        -      _windowPrint();
        -      if (
        -        window.confirm(
        -          'You just tried to print the webpage. Do you want to prevent this from running again?'
        -        )
        -      ) {
        -        windowPrintDisabled = true;
        +  /**
        +   * Displays text in the web browser's console.
        +   *
        +   * `print()` is helpful for printing values while debugging. Each call to
        +   * `print()` creates a new line of text.
        +   *
        +   * Note: Call `print('\n')` to print a blank line. Calling `print()` without
        +   * an argument opens the browser's dialog for printing documents.
        +   *
        +   * @method print
        +   * @param {Any} contents content to print to the console.
        +   * @example
        +   * <div>
        +   * <code class="norender">
        +   * function setup() {
        +   *   // Prints "hello, world" to the console.
        +   *   print('hello, world');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code class="norender">
        +   * function setup() {
        +   *   let name = 'ada';
        +   *   // Prints "hello, ada" to the console.
        +   *   print(`hello, ${name}`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.print = function(...args) {
        +    if (!args.length) {
        +      if (!windowPrintDisabled) {
        +        _windowPrint();
        +        if (
        +          window.confirm(
        +            'You just tried to print the webpage. Do you want to prevent this from running again?'
        +          )
        +        ) {
        +          windowPrintDisabled = true;
        +        }
               }
        +    } else {
        +      console.log(...args);
             }
        -  } else {
        -    console.log(...args);
        -  }
        -};
        +  };
         
        -/**
        - * A `Number` variable that tracks the number of frames drawn since the sketch
        - * started.
        - *
        - * `frameCount`'s value is 0 inside <a href="#/p5/setup">setup()</a>. It
        - * increments by 1 each time the code in <a href="#/p5/draw">draw()</a>
        - * finishes executing.
        - *
        - * @property {Integer} frameCount
        - * @readOnly
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Display the value of
        - *   // frameCount.
        - *   textSize(30);
        - *   textAlign(CENTER, CENTER);
        - *   text(frameCount, 50, 50);
        - *
        - *   describe('The number 0 written in black in the middle of a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Set the frameRate to 30.
        - *   frameRate(30);
        - *
        - *   textSize(30);
        - *   textAlign(CENTER, CENTER);
        - *
        - *   describe('A number written in black in the middle of a gray square. Its value increases rapidly.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Display the value of
        - *   // frameCount.
        - *   text(frameCount, 50, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.frameCount = 0;
        +  /**
        +   * A `Number` variable that tracks the number of frames drawn since the sketch
        +   * started.
        +   *
        +   * `frameCount`'s value is 0 inside <a href="#/p5/setup">setup()</a>. It
        +   * increments by 1 each time the code in <a href="#/p5/draw">draw()</a>
        +   * finishes executing.
        +   *
        +   * @property {Integer} frameCount
        +   * @readOnly
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Display the value of
        +   *   // frameCount.
        +   *   textSize(30);
        +   *   textAlign(CENTER, CENTER);
        +   *   text(frameCount, 50, 50);
        +   *
        +   *   describe('The number 0 written in black in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Set the frameRate to 30.
        +   *   frameRate(30);
        +   *
        +   *   textSize(30);
        +   *   textAlign(CENTER, CENTER);
        +   *
        +   *   describe('A number written in black in the middle of a gray square. Its value increases rapidly.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Display the value of
        +   *   // frameCount.
        +   *   text(frameCount, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.frameCount = 0;
         
        -/**
        - * A `Number` variable that tracks the number of milliseconds it took to draw
        - * the last frame.
        - *
        - * `deltaTime` contains the amount of time it took
        - * <a href="#/p5/draw">draw()</a> to execute during the previous frame. It's
        - * useful for simulating physics.
        - *
        - * @property {Integer} deltaTime
        - * @readOnly
        - * @example
        - * <div>
        - * <code>
        - * let x = 0;
        - * let speed = 0.05;
        - *
        - * function setup()  {
        - *   createCanvas(100, 100);
        - *
        - *   // Set the frameRate to 30.
        - *   frameRate(30);
        - *
        - *   describe('A white circle moves from left to right on a gray background. It reappears on the left side when it reaches the right side.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Use deltaTime to calculate
        - *   // a change in position.
        - *   let deltaX = speed * deltaTime;
        - *
        - *   // Update the x variable.
        - *   x += deltaX;
        - *
        - *   // Reset x to 0 if it's
        - *   // greater than 100.
        - *   if (x > 100)  {
        - *     x = 0;
        - *   }
        - *
        - *   // Use x to set the circle's
        - *   // position.
        - *   circle(x, 50, 20);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.deltaTime = 0;
        +  /**
        +   * A `Number` variable that tracks the number of milliseconds it took to draw
        +   * the last frame.
        +   *
        +   * `deltaTime` contains the amount of time it took
        +   * <a href="#/p5/draw">draw()</a> to execute during the previous frame. It's
        +   * useful for simulating physics.
        +   *
        +   * @property {Integer} deltaTime
        +   * @readOnly
        +   * @example
        +   * <div>
        +   * <code>
        +   * let x = 0;
        +   * let speed = 0.05;
        +   *
        +   * function setup()  {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Set the frameRate to 30.
        +   *   frameRate(30);
        +   *
        +   *   describe('A white circle moves from left to right on a gray background. It reappears on the left side when it reaches the right side.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Use deltaTime to calculate
        +   *   // a change in position.
        +   *   let deltaX = speed * deltaTime;
        +   *
        +   *   // Update the x variable.
        +   *   x += deltaX;
        +   *
        +   *   // Reset x to 0 if it's
        +   *   // greater than 100.
        +   *   if (x > 100)  {
        +   *     x = 0;
        +   *   }
        +   *
        +   *   // Use x to set the circle's
        +   *   // position.
        +   *   circle(x, 50, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.deltaTime = 0;
         
        -/**
        - * A `Boolean` variable that's `true` if the browser is focused and `false` if
        - * not.
        - *
        - * Note: The browser window can only receive input if it's focused.
        - *
        - * @property {Boolean} focused
        - * @readOnly
        - * @example
        - * <div>
        - * <code>
        - * // Open this example in two separate browser
        - * // windows placed side-by-side to demonstrate.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A square changes color from green to red when the browser window is out of focus.');
        - * }
        - *
        - * function draw() {
        - *   // Change the background color
        - *   // when the browser window
        - *   // goes in/out of focus.
        - *   if (focused === true) {
        - *     background(0, 255, 0);
        - *   } else {
        - *     background(255, 0, 0);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.focused = document.hasFocus();
        +  /**
        +   * A `Boolean` variable that's `true` if the browser is focused and `false` if
        +   * not.
        +   *
        +   * Note: The browser window can only receive input if it's focused.
        +   *
        +   * @property {Boolean} focused
        +   * @readOnly
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Open this example in two separate browser
        +   * // windows placed side-by-side to demonstrate.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A square changes color from green to red when the browser window is out of focus.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Change the background color
        +   *   // when the browser window
        +   *   // goes in/out of focus.
        +   *   if (focused === true) {
        +   *     background(0, 255, 0);
        +   *   } else {
        +   *     background(255, 0, 0);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.focused = document.hasFocus();
         
        -/**
        - * Changes the cursor's appearance.
        - *
        - * The first parameter, `type`, sets the type of cursor to display. The
        - * built-in options are `ARROW`, `CROSS`, `HAND`, `MOVE`, `TEXT`, and `WAIT`.
        - * `cursor()` also recognizes standard CSS cursor properties passed as
        - * strings: `'help'`, `'wait'`, `'crosshair'`, `'not-allowed'`, `'zoom-in'`,
        - * and `'grab'`. If the path to an image is passed, as in
        - * `cursor('assets/target.png')`, then the image will be used as the cursor.
        - * Images must be in .cur, .gif, .jpg, .jpeg, or .png format and should be <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#icon_size_limits">at most 32 by 32 pixels large.</a>
        - *
        - * The parameters `x` and `y` are optional. If an image is used for the
        - * cursor, `x` and `y` set the location pointed to within the image. They are
        - * both 0 by default, so the cursor points to the image's top-left corner. `x`
        - * and `y` must be less than the image's width and height, respectively.
        - *
        - * @method cursor
        - * @param {String|Constant} type Built-in: either ARROW, CROSS, HAND, MOVE, TEXT, or WAIT.
        - *                               Native CSS properties: 'grab', 'progress', and so on.
        - *                               Path to cursor image.
        - * @param {Number}          [x]  horizontal active spot of the cursor.
        - * @param {Number}          [y]  vertical active spot of the cursor.
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A gray square. The cursor appears as crosshairs.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Set the cursor to crosshairs: +
        - *   cursor(CROSS);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A gray square divided into quadrants. The cursor image changes when the mouse moves to each quadrant.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Divide the canvas into quadrants.
        - *   line(50, 0, 50, 100);
        - *   line(0, 50, 100, 50);
        - *
        - *   // Change cursor based on mouse position.
        - *   if (mouseX < 50 && mouseY < 50) {
        - *     cursor(CROSS);
        - *   } else if (mouseX > 50 && mouseY < 50) {
        - *     cursor('progress');
        - *   } else if (mouseX > 50 && mouseY > 50) {
        - *     cursor('https://avatars0.githubusercontent.com/u/1617169?s=16');
        - *   } else {
        - *     cursor('grab');
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('An image of three purple curves follows the mouse. The image shifts when the mouse is pressed.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Change the cursor's active spot
        - *   // when the mouse is pressed.
        - *   if (mouseIsPressed === true) {
        - *     cursor('https://avatars0.githubusercontent.com/u/1617169?s=16', 8, 8);
        - *   } else {
        - *     cursor('https://avatars0.githubusercontent.com/u/1617169?s=16');
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.cursor = function(type, x, y) {
        -  let cursor = 'auto';
        -  const canvas = this._curElement.elt;
        -  if (standardCursors.includes(type)) {
        -    // Standard css cursor
        -    cursor = type;
        -  } else if (typeof type === 'string') {
        -    let coords = '';
        -    if (x && y && (typeof x === 'number' && typeof y === 'number')) {
        -      // Note that x and y values must be unit-less positive integers < 32
        -      // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
        -      coords = `${x} ${y}`;
        -    }
        -    if (
        -      type.substring(0, 7) === 'http://' ||
        -      type.substring(0, 8) === 'https://'
        -    ) {
        -      // Image (absolute url)
        -      cursor = `url(${type}) ${coords}, auto`;
        -    } else if (/\.(cur|jpg|jpeg|gif|png|CUR|JPG|JPEG|GIF|PNG)$/.test(type)) {
        -      // Image file (relative path) - Separated for performance reasons
        -      cursor = `url(${type}) ${coords}, auto`;
        -    } else {
        -      // Any valid string for the css cursor property
        +  /**
        +   * Changes the cursor's appearance.
        +   *
        +   * The first parameter, `type`, sets the type of cursor to display. The
        +   * built-in options are `ARROW`, `CROSS`, `HAND`, `MOVE`, `TEXT`, and `WAIT`.
        +   * `cursor()` also recognizes standard CSS cursor properties passed as
        +   * strings: `'help'`, `'wait'`, `'crosshair'`, `'not-allowed'`, `'zoom-in'`,
        +   * and `'grab'`. If the path to an image is passed, as in
        +   * `cursor('assets/target.png')`, then the image will be used as the cursor.
        +   * Images must be in .cur, .gif, .jpg, .jpeg, or .png format and should be <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#icon_size_limits">at most 32 by 32 pixels large.</a>
        +   *
        +   * The parameters `x` and `y` are optional. If an image is used for the
        +   * cursor, `x` and `y` set the location pointed to within the image. They are
        +   * both 0 by default, so the cursor points to the image's top-left corner. `x`
        +   * and `y` must be less than the image's width and height, respectively.
        +   *
        +   * @method cursor
        +   * @param {(ARROW|CROSS|HAND|MOVE|TEXT|WAIT|String)} type Built-in: either ARROW, CROSS, HAND, MOVE, TEXT, or WAIT.
        +   *                               Native CSS properties: 'grab', 'progress', and so on.
        +   *                               Path to cursor image.
        +   * @param {Number}          [x]  horizontal active spot of the cursor.
        +   * @param {Number}          [y]  vertical active spot of the cursor.
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A gray square. The cursor appears as crosshairs.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Set the cursor to crosshairs: +
        +   *   cursor(CROSS);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A gray square divided into quadrants. The cursor image changes when the mouse moves to each quadrant.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Divide the canvas into quadrants.
        +   *   line(50, 0, 50, 100);
        +   *   line(0, 50, 100, 50);
        +   *
        +   *   // Change cursor based on mouse position.
        +   *   if (mouseX < 50 && mouseY < 50) {
        +   *     cursor(CROSS);
        +   *   } else if (mouseX > 50 && mouseY < 50) {
        +   *     cursor('progress');
        +   *   } else if (mouseX > 50 && mouseY > 50) {
        +   *     cursor('https://avatars0.githubusercontent.com/u/1617169?s=16');
        +   *   } else {
        +   *     cursor('grab');
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('An image of three purple curves follows the mouse. The image shifts when the mouse is pressed.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Change the cursor's active spot
        +   *   // when the mouse is pressed.
        +   *   if (mouseIsPressed === true) {
        +   *     cursor('https://avatars0.githubusercontent.com/u/1617169?s=16', 8, 8);
        +   *   } else {
        +   *     cursor('https://avatars0.githubusercontent.com/u/1617169?s=16');
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.cursor = function(type, x, y) {
        +    let cursor = 'auto';
        +    const canvas = this._curElement.elt;
        +    if (standardCursors.includes(type)) {
        +      // Standard css cursor
               cursor = type;
        +    } else if (typeof type === 'string') {
        +      let coords = '';
        +      if (x && y && (typeof x === 'number' && typeof y === 'number')) {
        +        // Note that x and y values must be unit-less positive integers < 32
        +        // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
        +        coords = `${x} ${y}`;
        +      }
        +      if (
        +        type.substring(0, 7) === 'http://' ||
        +        type.substring(0, 8) === 'https://'
        +      ) {
        +        // Image (absolute url)
        +        cursor = `url(${type}) ${coords}, auto`;
        +      } else if (/\.(cur|jpg|jpeg|gif|png|CUR|JPG|JPEG|GIF|PNG)$/.test(type)) {
        +        // Image file (relative path) - Separated for performance reasons
        +        cursor = `url(${type}) ${coords}, auto`;
        +      } else {
        +        // Any valid string for the css cursor property
        +        cursor = type;
        +      }
             }
        -  }
        -  canvas.style.cursor = cursor;
        -};
        +    canvas.style.cursor = cursor;
        +  };
         
        -/**
        - * Sets the number of frames to draw per second.
        - *
        - * Calling `frameRate()` with one numeric argument, as in `frameRate(30)`,
        - * attempts to draw 30 frames per second (FPS). The target frame rate may not
        - * be achieved depending on the sketch's processing needs. Most computers
        - * default to a frame rate of 60 FPS. Frame rates of 24 FPS and above are
        - * fast enough for smooth animations.
        - *
        - * Calling `frameRate()` without an argument returns the current frame rate.
        - * The value returned is an approximation.
        - *
        - * @method frameRate
        - * @param  {Number} fps number of frames to draw per second.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white circle on a gray background. The circle moves from left to right in a loop. It slows down when the mouse is pressed.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Set the x variable based
        - *   // on the current frameCount.
        - *   let x = frameCount % 100;
        - *
        - *   // If the mouse is pressed,
        - *   // decrease the frame rate.
        - *   if (mouseIsPressed === true) {
        - *     frameRate(10);
        - *   } else {
        - *     frameRate(60);
        - *   }
        - *
        - *   // Use x to set the circle's
        - *   // position.
        - *   circle(x, 50, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A number written in black on a gray background. The number decreases when the mouse is pressed.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // If the mouse is pressed, do lots
        - *   // of math to slow down drawing.
        - *   if (mouseIsPressed === true) {
        - *     for (let i = 0; i < 1000000; i += 1) {
        - *       random();
        - *     }
        - *   }
        - *
        - *   // Get the current frame rate
        - *   // and display it.
        - *   let fps = frameRate();
        - *   text(fps, 50, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method frameRate
        - * @return {Number}       current frame rate.
        - */
        -p5.prototype.frameRate = function(fps) {
        -  p5._validateParameters('frameRate', arguments);
        -  if (typeof fps !== 'number' || fps < 0) {
        -    return this._frameRate;
        -  } else {
        -    this._setProperty('_targetFrameRate', fps);
        -    if (fps === 0) {
        -      this._setProperty('_frameRate', fps);
        +  /**
        +   * Sets the number of frames to draw per second.
        +   *
        +   * Calling `frameRate()` with one numeric argument, as in `frameRate(30)`,
        +   * attempts to draw 30 frames per second (FPS). The target frame rate may not
        +   * be achieved depending on the sketch's processing needs. Most computers
        +   * default to a frame rate of 60 FPS. Frame rates of 24 FPS and above are
        +   * fast enough for smooth animations.
        +   *
        +   * Calling `frameRate()` without an argument returns the current frame rate.
        +   * The value returned is an approximation.
        +   *
        +   * @method frameRate
        +   * @param  {Number} fps number of frames to draw per second.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white circle on a gray background. The circle moves from left to right in a loop. It slows down when the mouse is pressed.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Set the x variable based
        +   *   // on the current frameCount.
        +   *   let x = frameCount % 100;
        +   *
        +   *   // If the mouse is pressed,
        +   *   // decrease the frame rate.
        +   *   if (mouseIsPressed === true) {
        +   *     frameRate(10);
        +   *   } else {
        +   *     frameRate(60);
        +   *   }
        +   *
        +   *   // Use x to set the circle's
        +   *   // position.
        +   *   circle(x, 50, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A number written in black on a gray background. The number decreases when the mouse is pressed.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // If the mouse is pressed, do lots
        +   *   // of math to slow down drawing.
        +   *   if (mouseIsPressed === true) {
        +   *     for (let i = 0; i < 1000000; i += 1) {
        +   *       random();
        +   *     }
        +   *   }
        +   *
        +   *   // Get the current frame rate
        +   *   // and display it.
        +   *   let fps = frameRate();
        +   *   text(fps, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method frameRate
        +   * @return {Number}       current frame rate.
        +   */
        +  fn.frameRate = function(fps) {
        +    // p5._validateParameters('frameRate', arguments);
        +    if (typeof fps !== 'number' || fps < 0) {
        +      return this._frameRate;
        +    } else {
        +      this._targetFrameRate = fps;
        +      if (fps === 0) {
        +        this._frameRate = fps;
        +      }
        +      return this;
             }
        -    return this;
        -  }
        -};
        +  };
         
        -/**
        - * Returns the current framerate.
        - *
        - * @private
        - * @return {Number} current frameRate
        - */
        -p5.prototype.getFrameRate = function() {
        -  return this.frameRate();
        -};
        +  /**
        +   * Returns the current framerate.
        +   *
        +   * @private
        +   * @return {Number} current frameRate
        +   */
        +  fn.getFrameRate = function() {
        +    return this.frameRate();
        +  };
         
        -/**
        - * Specifies the number of frames to be displayed every second. For example,
        - * the function call frameRate(30) will attempt to refresh 30 times a second.
        - * If the processor is not fast enough to maintain the specified rate, the
        - * frame rate will not be achieved. Setting the frame rate within <a href="#/p5/setup">setup()</a> is
        - * recommended. The default rate is 60 frames per second.
        - *
        - * Calling `frameRate()` with no arguments returns the current frame rate.
        - *
        - * @private
        - * @param {Number} [fps] number of frames to be displayed every second
        - */
        -p5.prototype.setFrameRate = function(fps) {
        -  return this.frameRate(fps);
        -};
        +  /**
        +   * Specifies the number of frames to be displayed every second. For example,
        +   * the function call frameRate(30) will attempt to refresh 30 times a second.
        +   * If the processor is not fast enough to maintain the specified rate, the
        +   * frame rate will not be achieved. Setting the frame rate within <a href="#/p5/setup">setup()</a> is
        +   * recommended. The default rate is 60 frames per second.
        +   *
        +   * Calling `frameRate()` with no arguments returns the current frame rate.
        +   *
        +   * @private
        +   * @param {Number} [fps] number of frames to be displayed every second
        +   */
        +  fn.setFrameRate = function(fps) {
        +    return this.frameRate(fps);
        +  };
         
        -/**
        - * Returns the target frame rate.
        - *
        - * The value is either the system frame rate or the last value passed to
        - * <a href="#/p5/frameRate">frameRate()</a>.
        - *
        - * @method getTargetFrameRate
        - * @return {Number} _targetFrameRate
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('The number 20 written in black on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Set the frame rate to 20.
        - *   frameRate(20);
        - *
        - *   // Get the target frame rate and
        - *   // display it.
        - *   let fps = getTargetFrameRate();
        - *   text(fps, 43, 54);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.getTargetFrameRate = function() {
        -  return this._targetFrameRate;
        -};
        +  /**
        +   * Returns the target frame rate.
        +   *
        +   * The value is either the system frame rate or the last value passed to
        +   * <a href="#/p5/frameRate">frameRate()</a>.
        +   *
        +   * @method getTargetFrameRate
        +   * @return {Number} _targetFrameRate
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('The number 20 written in black on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Set the frame rate to 20.
        +   *   frameRate(20);
        +   *
        +   *   // Get the target frame rate and
        +   *   // display it.
        +   *   let fps = getTargetFrameRate();
        +   *   text(fps, 43, 54);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.getTargetFrameRate = function() {
        +    return this._targetFrameRate;
        +  };
         
        -/**
        - * Hides the cursor from view.
        - *
        - * @method noCursor
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Hide the cursor.
        - *   noCursor();
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   circle(mouseX, mouseY, 10);
        - *
        - *   describe('A white circle on a gray background. The circle follows the mouse as it moves. The cursor is hidden.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noCursor = function() {
        -  this._curElement.elt.style.cursor = 'none';
        -};
        +  /**
        +   * Hides the cursor from view.
        +   *
        +   * @method noCursor
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Hide the cursor.
        +   *   noCursor();
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   circle(mouseX, mouseY, 10);
        +   *
        +   *   describe('A white circle on a gray background. The circle follows the mouse as it moves. The cursor is hidden.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noCursor = function() {
        +    this._curElement.elt.style.cursor = 'none';
        +  };
         
        -/**
        - * A `String` variable with the WebGL version in use.
        - *
        - * `webglVersion`'s value equals one of the following string constants:
        - *
        - * - `WEBGL2` whose value is `'webgl2'`,
        - * - `WEBGL` whose value is `'webgl'`, or
        - * - `P2D` whose value is `'p2d'`. This is the default for 2D sketches.
        - *
        - * See <a href="#/p5/setAttributes">setAttributes()</a> for ways to set the
        - * WebGL version.
        - *
        - * @property {String} webglVersion
        - * @readOnly
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *
        - *   // Display the current WebGL version.
        - *   text(webglVersion, 42, 54);
        - *
        - *   describe('The text "p2d" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let font;
        - *
        - * function preload() {
        - *   // Load a font to use.
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   // Create a canvas using WEBGL mode.
        - *   createCanvas(100, 50, WEBGL);
        - *   background(200);
        - *
        - *   // Display the current WebGL version.
        - *   fill(0);
        - *   textFont(font);
        - *   text(webglVersion, -15, 5);
        - *
        - *   describe('The text "webgl2" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let font;
        - *
        - * function preload() {
        - *   // Load a font to use.
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   // Create a canvas using WEBGL mode.
        - *   createCanvas(100, 50, WEBGL);
        - *
        - *   // Set WebGL to version 1.
        - *   setAttributes({ version: 1 });
        - *
        - *   background(200);
        - *
        - *   // Display the current WebGL version.
        - *   fill(0);
        - *   textFont(font);
        - *   text(webglVersion, -14, 5);
        - *
        - *   describe('The text "webgl" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.webglVersion = C.P2D;
        +  /**
        +   * A `String` variable with the WebGL version in use.
        +   *
        +   * `webglVersion`'s value equals one of the following string constants:
        +   *
        +   * - `WEBGL2` whose value is `'webgl2'`,
        +   * - `WEBGL` whose value is `'webgl'`, or
        +   * - `P2D` whose value is `'p2d'`. This is the default for 2D sketches.
        +   *
        +   * See <a href="#/p5/setAttributes">setAttributes()</a> for ways to set the
        +   * WebGL version.
        +   *
        +   * @property {(WEBGL|WEBGL2)} webglVersion
        +   * @readOnly
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background(200);
        +   *
        +   *   // Display the current WebGL version.
        +   *   text(webglVersion, 42, 54);
        +   *
        +   *   describe('The text "p2d" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let font;
        +   *
        +   * function preload() {
        +   *   // Load a font to use.
        +   *   font = loadFont('assets/inconsolata.otf');
        +   * }
        +   *
        +   * function setup() {
        +   *   // Create a canvas using WEBGL mode.
        +   *   createCanvas(100, 50, WEBGL);
        +   *   background(200);
        +   *
        +   *   // Display the current WebGL version.
        +   *   fill(0);
        +   *   textFont(font);
        +   *   text(webglVersion, -15, 5);
        +   *
        +   *   describe('The text "webgl2" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let font;
        +   *
        +   * function preload() {
        +   *   // Load a font to use.
        +   *   font = loadFont('assets/inconsolata.otf');
        +   * }
        +   *
        +   * function setup() {
        +   *   // Create a canvas using WEBGL mode.
        +   *   createCanvas(100, 50, WEBGL);
        +   *
        +   *   // Set WebGL to version 1.
        +   *   setAttributes({ version: 1 });
        +   *
        +   *   background(200);
        +   *
        +   *   // Display the current WebGL version.
        +   *   fill(0);
        +   *   textFont(font);
        +   *   text(webglVersion, -14, 5);
        +   *
        +   *   describe('The text "webgl" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.webglVersion = C.P2D;
         
        -/**
        - * A `Number` variable that stores the width of the screen display.
        - *
        - * `displayWidth` is useful for running full-screen programs. Its value
        - * depends on the current <a href="#/p5/pixelDensity">pixelDensity()</a>.
        - *
        - * Note: The actual screen width can be computed as
        - * `displayWidth * pixelDensity()`.
        - *
        - * @property {Number} displayWidth
        - * @readOnly
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Set the canvas' width and height
        - *   // using the display's dimensions.
        - *   createCanvas(displayWidth, displayHeight);
        - *
        - *   background(200);
        - *
        - *   describe('A gray canvas that is the same size as the display.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @alt
        - * This example does not render anything.
        - */
        -p5.prototype.displayWidth = screen.width;
        +  /**
        +   * A `Number` variable that stores the width of the screen display.
        +   *
        +   * `displayWidth` is useful for running full-screen programs. Its value
        +   * depends on the current <a href="#/p5/pixelDensity">pixelDensity()</a>.
        +   *
        +   * Note: The actual screen width can be computed as
        +   * `displayWidth * pixelDensity()`.
        +   *
        +   * @property {Number} displayWidth
        +   * @readOnly
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Set the canvas' width and height
        +   *   // using the display's dimensions.
        +   *   createCanvas(displayWidth, displayHeight);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A gray canvas that is the same size as the display.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @alt
        +   * This example does not render anything.
        +   */
        +  fn.displayWidth = screen.width;
         
        -/**
        - * A `Number` variable that stores the height of the screen display.
        - *
        - * `displayHeight` is useful for running full-screen programs. Its value
        - * depends on the current <a href="#/p5/pixelDensity">pixelDensity()</a>.
        - *
        - * Note: The actual screen height can be computed as
        - * `displayHeight * pixelDensity()`.
        - *
        - * @property {Number} displayHeight
        - * @readOnly
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Set the canvas' width and height
        - *   // using the display's dimensions.
        - *   createCanvas(displayWidth, displayHeight);
        - *
        - *   background(200);
        - *
        - *   describe('A gray canvas that is the same size as the display.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @alt
        - * This example does not render anything.
        - */
        -p5.prototype.displayHeight = screen.height;
        +  /**
        +   * A `Number` variable that stores the height of the screen display.
        +   *
        +   * `displayHeight` is useful for running full-screen programs. Its value
        +   * depends on the current <a href="#/p5/pixelDensity">pixelDensity()</a>.
        +   *
        +   * Note: The actual screen height can be computed as
        +   * `displayHeight * pixelDensity()`.
        +   *
        +   * @property {Number} displayHeight
        +   * @readOnly
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Set the canvas' width and height
        +   *   // using the display's dimensions.
        +   *   createCanvas(displayWidth, displayHeight);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A gray canvas that is the same size as the display.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @alt
        +   * This example does not render anything.
        +   */
        +  fn.displayHeight = screen.height;
         
        -/**
        - * A `Number` variable that stores the width of the browser's viewport.
        - *
        - * The <a href="https://developer.mozilla.org/en-US/docs/Glossary/Layout_viewport" target="_blank">layout viewport</a>
        - * is the area within the browser that's available for drawing.
        - *
        - * @property {Number} windowWidth
        - * @readOnly
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Set the canvas' width and height
        - *   // using the browser's dimensions.
        - *   createCanvas(windowWidth, windowHeight);
        - *
        - *   background(200);
        - *
        - *   describe('A gray canvas that takes up the entire browser window.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @alt
        - * This example does not render anything.
        - */
        -p5.prototype.windowWidth = 0;
        +  /**
        +   * A `Number` variable that stores the width of the browser's viewport.
        +   *
        +   * The <a href="https://developer.mozilla.org/en-US/docs/Glossary/Layout_viewport" target="_blank">layout viewport</a>
        +   * is the area within the browser that's available for drawing.
        +   *
        +   * @property {Number} windowWidth
        +   * @readOnly
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Set the canvas' width and height
        +   *   // using the browser's dimensions.
        +   *   createCanvas(windowWidth, windowHeight);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A gray canvas that takes up the entire browser window.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @alt
        +   * This example does not render anything.
        +   */
        +  fn.windowWidth = 0;
         
        -/**
        - * A `Number` variable that stores the height of the browser's viewport.
        - *
        - * The <a href="https://developer.mozilla.org/en-US/docs/Glossary/Layout_viewport" target="_blank">layout viewport</a>
        - * is the area within the browser that's available for drawing.
        - *
        - * @property {Number} windowHeight
        - * @readOnly
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Set the canvas' width and height
        - *   // using the browser's dimensions.
        - *   createCanvas(windowWidth, windowHeight);
        - *
        - *   background(200);
        - *
        - *   describe('A gray canvas that takes up the entire browser window.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @alt
        - * This example does not render anything.
        - */
        -p5.prototype.windowHeight = 0;
        +  /**
        +   * A `Number` variable that stores the height of the browser's viewport.
        +   *
        +   * The <a href="https://developer.mozilla.org/en-US/docs/Glossary/Layout_viewport" target="_blank">layout viewport</a>
        +   * is the area within the browser that's available for drawing.
        +   *
        +   * @property {Number} windowHeight
        +   * @readOnly
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Set the canvas' width and height
        +   *   // using the browser's dimensions.
        +   *   createCanvas(windowWidth, windowHeight);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A gray canvas that takes up the entire browser window.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @alt
        +   * This example does not render anything.
        +   */
        +  fn.windowHeight = 0;
         
        -/**
        - * A function that's called when the browser window is resized.
        - *
        - * Code placed in the body of `windowResized()` will run when the
        - * browser window's size changes. It's a good place to call
        - * <a href="#/p5/resizeCanvas">resizeCanvas()</a> or make other
        - * adjustments to accommodate the new window size.
        - *
        - * The `event` parameter is optional. If added to the function declaration, it
        - * can be used for debugging or other purposes.
        - *
        - * @method windowResized
        - * @param {UIEvent} [event] optional resize Event.
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   createCanvas(windowWidth, windowHeight);
        - *
        - *   describe('A gray canvas with a white circle at its center. The canvas takes up the entire browser window. It changes size to match the browser window.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a circle at the center.
        - *   circle(width / 2, height / 2, 50);
        - * }
        - *
        - * // Resize the canvas when the
        - * // browser's size changes.
        - * function windowResized() {
        - *   resizeCanvas(windowWidth, windowHeight);
        - * }
        - * </code>
        - * </div>
        - * @alt
        - * This example does not render anything.
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   createCanvas(windowWidth, windowHeight);
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   describe('A gray canvas that takes up the entire browser window. It changes size to match the browser window.');
        - * }
        - *
        - * function windowResized(event) {
        - *   // Resize the canvas when the
        - *   // browser's size changes.
        - *   resizeCanvas(windowWidth, windowHeight);
        - *
        - *   // Print the resize event to the console for debugging.
        - *   print(event);
        - * }
        - * </code>
        - * </div>
        - * @alt
        - * This example does not render anything.
        - */
        -p5.prototype._onresize = function(e) {
        -  this._setProperty('windowWidth', getWindowWidth());
        -  this._setProperty('windowHeight', getWindowHeight());
        -  const context = this._isGlobal ? window : this;
        -  let executeDefault;
        -  if (typeof context.windowResized === 'function') {
        -    executeDefault = context.windowResized(e);
        -    if (executeDefault !== undefined && !executeDefault) {
        -      e.preventDefault();
        +  /**
        +   * A function that's called when the browser window is resized.
        +   *
        +   * Code placed in the body of `windowResized()` will run when the
        +   * browser window's size changes. It's a good place to call
        +   * <a href="#/p5/resizeCanvas">resizeCanvas()</a> or make other
        +   * adjustments to accommodate the new window size.
        +   *
        +   * The `event` parameter is optional. If added to the function declaration, it
        +   * can be used for debugging or other purposes.
        +   *
        +   * @method windowResized
        +   * @param {UIEvent} [event] optional resize Event.
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(windowWidth, windowHeight);
        +   *
        +   *   describe('A gray canvas with a white circle at its center. The canvas takes up the entire browser window. It changes size to match the browser window.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a circle at the center.
        +   *   circle(width / 2, height / 2, 50);
        +   * }
        +   *
        +   * // Resize the canvas when the
        +   * // browser's size changes.
        +   * function windowResized() {
        +   *   resizeCanvas(windowWidth, windowHeight);
        +   * }
        +   * </code>
        +   * </div>
        +   * @alt
        +   * This example does not render anything.
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(windowWidth, windowHeight);
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   describe('A gray canvas that takes up the entire browser window. It changes size to match the browser window.');
        +   * }
        +   *
        +   * function windowResized(event) {
        +   *   // Resize the canvas when the
        +   *   // browser's size changes.
        +   *   resizeCanvas(windowWidth, windowHeight);
        +   *
        +   *   // Print the resize event to the console for debugging.
        +   *   print(event);
        +   * }
        +   * </code>
        +   * </div>
        +   * @alt
        +   * This example does not render anything.
        +   */
        +  fn._onresize = function(e) {
        +    this.windowWidth = getWindowWidth();
        +    this.windowHeight = getWindowHeight();
        +    const context = this._isGlobal ? window : this;
        +    let executeDefault;
        +    if (typeof context.windowResized === 'function') {
        +      executeDefault = context.windowResized(e);
        +      if (executeDefault !== undefined && !executeDefault) {
        +        e.preventDefault();
        +      }
             }
        -  }
        -};
        +  };
         
        -function getWindowWidth() {
        -  return (
        -    window.innerWidth ||
        -    (document.documentElement && document.documentElement.clientWidth) ||
        -    (document.body && document.body.clientWidth) ||
        -    0
        -  );
        -}
        +  function getWindowWidth() {
        +    return (
        +      window.innerWidth ||
        +      (document.documentElement && document.documentElement.clientWidth) ||
        +      (document.body && document.body.clientWidth) ||
        +      0
        +    );
        +  }
         
        -function getWindowHeight() {
        -  return (
        -    window.innerHeight ||
        -    (document.documentElement && document.documentElement.clientHeight) ||
        -    (document.body && document.body.clientHeight) ||
        -    0
        -  );
        -}
        +  function getWindowHeight() {
        +    return (
        +      window.innerHeight ||
        +      (document.documentElement && document.documentElement.clientHeight) ||
        +      (document.body && document.body.clientHeight) ||
        +      0
        +    );
        +  }
         
        -/**
        - * Called upon each p5 instantiation instead of module import due to the
        - * possibility of the window being resized when no sketch is active.
        - */
        -p5.prototype._updateWindowSize = function() {
        -  this._setProperty('windowWidth', getWindowWidth());
        -  this._setProperty('windowHeight', getWindowHeight());
        -};
        +  /**
        +   * Called upon each p5 instantiation instead of module import due to the
        +   * possibility of the window being resized when no sketch is active.
        +   */
        +  fn._updateWindowSize = function() {
        +    this.windowWidth = getWindowWidth();
        +    this.windowHeight = getWindowHeight();
        +  };
         
        -/**
        - * A `Number` variable that stores the width of the canvas in pixels.
        - *
        - * `width`'s default value is 100. Calling
        - * <a href="#/p5/createCanvas">createCanvas()</a> or
        - * <a href="#/p5/resizeCanvas">resizeCanvas()</a> changes the value of
        - * `width`. Calling <a href="#/p5/noCanvas">noCanvas()</a> sets its value to
        - * 0.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *
        - *   // Display the canvas' width.
        - *   text(width, 42, 54);
        - *
        - *   describe('The number 100 written in black on a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(50, 100);
        - *
        - *   background(200);
        - *
        - *   // Display the canvas' width.
        - *   text(width, 21, 54);
        - *
        - *   describe('The number 50 written in black on a gray rectangle.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Display the canvas' width.
        - *   text(width, 42, 54);
        - *
        - *   describe('The number 100 written in black on a gray square. When the mouse is pressed, the square becomes a rectangle and the number becomes 50.');
        - * }
        - *
        - * // If the mouse is pressed, reisze
        - * // the canvas and display its new
        - * // width.
        - * function mousePressed() {
        - *   if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
        - *     resizeCanvas(50, 100);
        - *     background(200);
        - *     text(width, 21, 54);
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * @property {Number} width
        - * @readOnly
        - */
        -p5.prototype.width = 0;
        +  /**
        +   * A `Number` variable that stores the width of the canvas in pixels.
        +   *
        +   * `width`'s default value is 100. Calling
        +   * <a href="#/p5/createCanvas">createCanvas()</a> or
        +   * <a href="#/p5/resizeCanvas">resizeCanvas()</a> changes the value of
        +   * `width`. Calling <a href="#/p5/noCanvas">noCanvas()</a> sets its value to
        +   * 0.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background(200);
        +   *
        +   *   // Display the canvas' width.
        +   *   text(width, 42, 54);
        +   *
        +   *   describe('The number 100 written in black on a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(50, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Display the canvas' width.
        +   *   text(width, 21, 54);
        +   *
        +   *   describe('The number 50 written in black on a gray rectangle.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Display the canvas' width.
        +   *   text(width, 42, 54);
        +   *
        +   *   describe('The number 100 written in black on a gray square. When the mouse is pressed, the square becomes a rectangle and the number becomes 50.');
        +   * }
        +   *
        +   * // If the mouse is pressed, reisze
        +   * // the canvas and display its new
        +   * // width.
        +   * function mousePressed() {
        +   *   if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
        +   *     resizeCanvas(50, 100);
        +   *     background(200);
        +   *     text(width, 21, 54);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @property {Number} width
        +   * @readOnly
        +   */
        +  Object.defineProperty(fn, 'width', {
        +    get(){
        +      return this._renderer.width;
        +    }
        +  });
         
        -/**
        - * A `Number` variable that stores the height of the canvas in pixels.
        - *
        - * `height`'s default value is 100. Calling
        - * <a href="#/p5/createCanvas">createCanvas()</a> or
        - * <a href="#/p5/resizeCanvas">resizeCanvas()</a> changes the value of
        - * `height`. Calling <a href="#/p5/noCanvas">noCanvas()</a> sets its value to
        - * 0.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *
        - *   // Display the canvas' height.
        - *   text(height, 42, 54);
        - *
        - *   describe('The number 100 written in black on a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 50);
        - *
        - *   background(200);
        - *
        - *   // Display the canvas' height.
        - *   text(height, 42, 27);
        - *
        - *   describe('The number 50 written in black on a gray rectangle.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Display the canvas' height.
        - *   text(height, 42, 54);
        - *
        - *   describe('The number 100 written in black on a gray square. When the mouse is pressed, the square becomes a rectangle and the number becomes 50.');
        - * }
        - *
        - * // If the mouse is pressed, reisze
        - * // the canvas and display its new
        - * // height.
        - * function mousePressed() {
        - *   if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
        - *     resizeCanvas(100, 50);
        - *     background(200);
        - *     text(height, 42, 27);
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * @property {Number} height
        - * @readOnly
        - */
        -p5.prototype.height = 0;
        +  /**
        +   * A `Number` variable that stores the height of the canvas in pixels.
        +   *
        +   * `height`'s default value is 100. Calling
        +   * <a href="#/p5/createCanvas">createCanvas()</a> or
        +   * <a href="#/p5/resizeCanvas">resizeCanvas()</a> changes the value of
        +   * `height`. Calling <a href="#/p5/noCanvas">noCanvas()</a> sets its value to
        +   * 0.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background(200);
        +   *
        +   *   // Display the canvas' height.
        +   *   text(height, 42, 54);
        +   *
        +   *   describe('The number 100 written in black on a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 50);
        +   *
        +   *   background(200);
        +   *
        +   *   // Display the canvas' height.
        +   *   text(height, 42, 27);
        +   *
        +   *   describe('The number 50 written in black on a gray rectangle.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Display the canvas' height.
        +   *   text(height, 42, 54);
        +   *
        +   *   describe('The number 100 written in black on a gray square. When the mouse is pressed, the square becomes a rectangle and the number becomes 50.');
        +   * }
        +   *
        +   * // If the mouse is pressed, reisze
        +   * // the canvas and display its new
        +   * // height.
        +   * function mousePressed() {
        +   *   if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
        +   *     resizeCanvas(100, 50);
        +   *     background(200);
        +   *     text(height, 42, 27);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @property {Number} height
        +   * @readOnly
        +   */
        +  Object.defineProperty(fn, 'height', {
        +    get(){
        +      return this._renderer.height;
        +    }
        +  });
         
        -/**
        - * Toggles full-screen mode or returns the current mode.
        - *
        - * Calling `fullscreen(true)` makes the sketch full-screen. Calling
        - * `fullscreen(false)` makes the sketch its original size.
        - *
        - * Calling `fullscreen()` without an argument returns `true` if the sketch
        - * is in full-screen mode and `false` if not.
        - *
        - * Note: Due to browser restrictions, `fullscreen()` can only be called with
        - * user input such as a mouse press.
        - *
        - * @method fullscreen
        - * @param  {Boolean} [val] whether the sketch should be in fullscreen mode.
        - * @return {Boolean} current fullscreen state.
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *
        - *   describe('A gray canvas that switches between default and full-screen display when clicked.');
        - * }
        - *
        - * // If the mouse is pressed,
        - * // toggle full-screen mode.
        - * function mousePressed() {
        - *   if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
        - *     let fs = fullscreen();
        - *     fullscreen(!fs);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.fullscreen = function(val) {
        -  p5._validateParameters('fullscreen', arguments);
        -  // no arguments, return fullscreen or not
        -  if (typeof val === 'undefined') {
        -    return (
        -      document.fullscreenElement ||
        -      document.webkitFullscreenElement ||
        -      document.mozFullScreenElement ||
        -      document.msFullscreenElement
        -    );
        -  } else {
        -    // otherwise set to fullscreen or not
        -    if (val) {
        -      launchFullscreen(document.documentElement);
        +  /**
        +   * Toggles full-screen mode or returns the current mode.
        +   *
        +   * Calling `fullscreen(true)` makes the sketch full-screen. Calling
        +   * `fullscreen(false)` makes the sketch its original size.
        +   *
        +   * Calling `fullscreen()` without an argument returns `true` if the sketch
        +   * is in full-screen mode and `false` if not.
        +   *
        +   * Note: Due to browser restrictions, `fullscreen()` can only be called with
        +   * user input such as a mouse press.
        +   *
        +   * @method fullscreen
        +   * @param  {Boolean} [val] whether the sketch should be in fullscreen mode.
        +   * @return {Boolean} current fullscreen state.
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background(200);
        +   *
        +   *   describe('A gray canvas that switches between default and full-screen display when clicked.');
        +   * }
        +   *
        +   * // If the mouse is pressed,
        +   * // toggle full-screen mode.
        +   * function mousePressed() {
        +   *   if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
        +   *     let fs = fullscreen();
        +   *     fullscreen(!fs);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.fullscreen = function(val) {
        +    // p5._validateParameters('fullscreen', arguments);
        +    // no arguments, return fullscreen or not
        +    if (typeof val === 'undefined') {
        +      return (
        +        document.fullscreenElement ||
        +        document.webkitFullscreenElement ||
        +        document.mozFullScreenElement ||
        +        document.msFullscreenElement
        +      );
             } else {
        -      exitFullscreen();
        +      // otherwise set to fullscreen or not
        +      if (val) {
        +        launchFullscreen(document.documentElement);
        +      } else {
        +        exitFullscreen();
        +      }
             }
        -  }
        -};
        +  };
         
        -/**
        - * Sets the pixel density or returns the current density.
        - *
        - * Computer displays are grids of little lights called <em>pixels</em>. A
        - * display's <em>pixel density</em> describes how many pixels it packs into an
        - * area. Displays with smaller pixels have a higher pixel density and create
        - * sharper images.
        - *
        - * `pixelDensity()` sets the pixel scaling for high pixel density displays.
        - * By default, the pixel density is set to match the display's density.
        - * Calling `pixelDensity(1)` turn this off.
        - *
        - * Calling `pixelDensity()` without an argument returns the current pixel
        - * density.
        - *
        - * @method pixelDensity
        - * @param  {Number} [val] desired pixel density.
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Set the pixel density to 1.
        - *   pixelDensity(1);
        - *
        - *   // Create a canvas and draw
        - *   // a circle.
        - *   createCanvas(100, 100);
        - *   background(200);
        - *   circle(50, 50, 70);
        - *
        - *   describe('A fuzzy white circle on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Set the pixel density to 3.
        - *   pixelDensity(3);
        - *
        - *   // Create a canvas, paint the
        - *   // background, and draw a
        - *   // circle.
        - *   createCanvas(100, 100);
        - *   background(200);
        - *   circle(50, 50, 70);
        - *
        - *   describe('A sharp white circle on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method pixelDensity
        - * @returns {Number} current pixel density of the sketch.
        - */
        -p5.prototype.pixelDensity = function(val) {
        -  p5._validateParameters('pixelDensity', arguments);
        -  let returnValue;
        -  if (typeof val === 'number') {
        -    if (val !== this._pixelDensity) {
        -      this._pixelDensity = this._maxAllowedPixelDimensions = val;
        +  /**
        +   * Sets the pixel density or returns the current density.
        +   *
        +   * Computer displays are grids of little lights called <em>pixels</em>. A
        +   * display's <em>pixel density</em> describes how many pixels it packs into an
        +   * area. Displays with smaller pixels have a higher pixel density and create
        +   * sharper images.
        +   *
        +   * `pixelDensity()` sets the pixel scaling for high pixel density displays.
        +   * By default, the pixel density is set to match the display's density.
        +   * Calling `pixelDensity(1)` turn this off.
        +   *
        +   * Calling `pixelDensity()` without an argument returns the current pixel
        +   * density.
        +   *
        +   * @method pixelDensity
        +   * @param  {Number} [val] desired pixel density.
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Set the pixel density to 1.
        +   *   pixelDensity(1);
        +   *
        +   *   // Create a canvas and draw
        +   *   // a circle.
        +   *   createCanvas(100, 100);
        +   *   background(200);
        +   *   circle(50, 50, 70);
        +   *
        +   *   describe('A fuzzy white circle on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Set the pixel density to 3.
        +   *   pixelDensity(3);
        +   *
        +   *   // Create a canvas, paint the
        +   *   // background, and draw a
        +   *   // circle.
        +   *   createCanvas(100, 100);
        +   *   background(200);
        +   *   circle(50, 50, 70);
        +   *
        +   *   describe('A sharp white circle on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method pixelDensity
        +   * @returns {Number} current pixel density of the sketch.
        +   */
        +  fn.pixelDensity = function(val) {
        +    // p5._validateParameters('pixelDensity', arguments);
        +    let returnValue;
        +    if (typeof val === 'number') {
        +      if (val !== this._renderer._pixelDensity) {
        +        this._renderer._pixelDensity = val;
        +      }
        +      returnValue = this;
        +      this.resizeCanvas(this.width, this.height, true); // as a side effect, it will clear the canvas
        +    } else {
        +      returnValue = this._renderer._pixelDensity;
             }
        -    returnValue = this;
        -    this.resizeCanvas(this.width, this.height, true); // as a side effect, it will clear the canvas
        -  } else {
        -    returnValue = this._pixelDensity;
        -  }
        -  return returnValue;
        -};
        +    return returnValue;
        +  };
         
        -/**
        - * Returns the display's current pixel density.
        - *
        - * @method displayDensity
        - * @returns {Number} current pixel density of the display.
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Set the pixel density to 1.
        - *   pixelDensity(1);
        - *
        - *   // Create a canvas and draw
        - *   // a circle.
        - *   createCanvas(100, 100);
        - *   background(200);
        - *   circle(50, 50, 70);
        - *
        - *   describe('A fuzzy white circle drawn on a gray background. The circle becomes sharper when the mouse is pressed.');
        - * }
        - *
        - * function mousePressed() {
        - *   // Get the current display density.
        - *   let d = displayDensity();
        - *
        - *   // Use the display density to set
        - *   // the sketch's pixel density.
        - *   pixelDensity(d);
        - *
        - *   // Paint the background and
        - *   // draw a circle.
        - *   background(200);
        - *   circle(50, 50, 70);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.displayDensity = () => window.devicePixelRatio;
        +  /**
        +   * Returns the display's current pixel density.
        +   *
        +   * @method displayDensity
        +   * @returns {Number} current pixel density of the display.
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Set the pixel density to 1.
        +   *   pixelDensity(1);
        +   *
        +   *   // Create a canvas and draw
        +   *   // a circle.
        +   *   createCanvas(100, 100);
        +   *   background(200);
        +   *   circle(50, 50, 70);
        +   *
        +   *   describe('A fuzzy white circle drawn on a gray background. The circle becomes sharper when the mouse is pressed.');
        +   * }
        +   *
        +   * function mousePressed() {
        +   *   // Get the current display density.
        +   *   let d = displayDensity();
        +   *
        +   *   // Use the display density to set
        +   *   // the sketch's pixel density.
        +   *   pixelDensity(d);
        +   *
        +   *   // Paint the background and
        +   *   // draw a circle.
        +   *   background(200);
        +   *   circle(50, 50, 70);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.displayDensity = () => window.devicePixelRatio;
         
        -function launchFullscreen(element) {
        -  const enabled =
        -    document.fullscreenEnabled ||
        -    document.webkitFullscreenEnabled ||
        -    document.mozFullScreenEnabled ||
        -    document.msFullscreenEnabled;
        -  if (!enabled) {
        -    throw new Error('Fullscreen not enabled in this browser.');
        -  }
        -  if (element.requestFullscreen) {
        -    element.requestFullscreen();
        -  } else if (element.mozRequestFullScreen) {
        -    element.mozRequestFullScreen();
        -  } else if (element.webkitRequestFullscreen) {
        -    element.webkitRequestFullscreen();
        -  } else if (element.msRequestFullscreen) {
        -    element.msRequestFullscreen();
        +  function launchFullscreen(element) {
        +    const enabled =
        +      document.fullscreenEnabled ||
        +      document.webkitFullscreenEnabled ||
        +      document.mozFullScreenEnabled ||
        +      document.msFullscreenEnabled;
        +    if (!enabled) {
        +      throw new Error('Fullscreen not enabled in this browser.');
        +    }
        +    if (element.requestFullscreen) {
        +      element.requestFullscreen();
        +    } else if (element.mozRequestFullScreen) {
        +      element.mozRequestFullScreen();
        +    } else if (element.webkitRequestFullscreen) {
        +      element.webkitRequestFullscreen();
        +    } else if (element.msRequestFullscreen) {
        +      element.msRequestFullscreen();
        +    }
           }
        -}
         
        -function exitFullscreen() {
        -  if (document.exitFullscreen) {
        -    document.exitFullscreen();
        -  } else if (document.mozCancelFullScreen) {
        -    document.mozCancelFullScreen();
        -  } else if (document.webkitExitFullscreen) {
        -    document.webkitExitFullscreen();
        -  } else if (document.msExitFullscreen) {
        -    document.msExitFullscreen();
        +  function exitFullscreen() {
        +    if (document.exitFullscreen) {
        +      document.exitFullscreen();
        +    } else if (document.mozCancelFullScreen) {
        +      document.mozCancelFullScreen();
        +    } else if (document.webkitExitFullscreen) {
        +      document.webkitExitFullscreen();
        +    } else if (document.msExitFullscreen) {
        +      document.msExitFullscreen();
        +    }
           }
        -}
         
        -/**
        - * Returns the sketch's current
        - * <a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL" target="_blank">URL</a>
        - * as a `String`.
        - *
        - * @method getURL
        - * @return {String} url
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *
        - *   // Get the sketch's URL
        - *   // and display it.
        - *   let url = getURL();
        - *   textWrap(CHAR);
        - *   text(url, 0, 40, 100);
        - *
        - *   describe('The URL "https://p5js.org/reference/p5/getURL" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.getURL = () => location.href;
        +  /**
        +   * Returns the sketch's current
        +   * <a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL" target="_blank">URL</a>
        +   * as a `String`.
        +   *
        +   * @method getURL
        +   * @return {String} url
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background(200);
        +   *
        +   *   // Get the sketch's URL
        +   *   // and display it.
        +   *   let url = getURL();
        +   *   textWrap(CHAR);
        +   *   text(url, 0, 40, 100);
        +   *
        +   *   describe('The URL "https://p5js.org/reference/#/p5/getURL" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.getURL = () => location.href;
         
        -/**
        - * Returns the current
        - * <a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL#path_to_resource" target="_blank">URL</a>
        - * path as an `Array` of `String`s.
        - *
        - * For example, consider a sketch hosted at the URL
        - * `https://example.com/sketchbook`. Calling `getURLPath()` returns
        - * `['sketchbook']`. For a sketch hosted at the URL
        - * `https://example.com/sketchbook/monday`, `getURLPath()` returns
        - * `['sketchbook', 'monday']`.
        - *
        - * @method getURLPath
        - * @return {String[]} path components.
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *
        - *   // Get the sketch's URL path
        - *   // and display the first
        - *   // part.
        - *   let path = getURLPath();
        - *   text(path[0], 25, 54);
        - *
        - *   describe('The word "reference" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.getURLPath = () =>
        -  location.pathname.split('/').filter(v => v !== '');
        +  /**
        +   * Returns the current
        +   * <a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL#path_to_resource" target="_blank">URL</a>
        +   * path as an `Array` of `String`s.
        +   *
        +   * For example, consider a sketch hosted at the URL
        +   * `https://example.com/sketchbook`. Calling `getURLPath()` returns
        +   * `['sketchbook']`. For a sketch hosted at the URL
        +   * `https://example.com/sketchbook/monday`, `getURLPath()` returns
        +   * `['sketchbook', 'monday']`.
        +   *
        +   * @method getURLPath
        +   * @return {String[]} path components.
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background(200);
        +   *
        +   *   // Get the sketch's URL path
        +   *   // and display the first
        +   *   // part.
        +   *   let path = getURLPath();
        +   *   text(path[0], 25, 54);
        +   *
        +   *   describe('The word "reference" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.getURLPath = () =>
        +    location.pathname.split('/').filter(v => v !== '');
         
        -/**
        - * Returns the current
        - * <a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL#parameters" target="_blank">URL parameters</a>
        - * in an `Object`.
        - *
        - * For example, calling `getURLParams()` in a sketch hosted at the URL
        - * `http://p5js.org?year=2014&month=May&day=15` returns
        - * `{ year: 2014, month: 'May', day: 15 }`.
        - *
        - * @method getURLParams
        - * @return {Object} URL params
        - * @example
        - * <div class='norender notest'>
        - * <code>
        - * // Imagine this sketch is hosted at the following URL:
        - * // https://p5js.org?year=2014&month=May&day=15
        - *
        - * function setup() {
        - *   background(200);
        - *
        - *   // Get the sketch's URL
        - *   // parameters and display
        - *   // them.
        - *   let params = getURLParams();
        - *   text(params.day, 10, 20);
        - *   text(params.month, 10, 40);
        - *   text(params.year, 10, 60);
        - *
        - *   describe('The text "15", "May", and "2014" written in black on separate lines.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @alt
        - * This example does not render anything.
        - */
        -p5.prototype.getURLParams = function() {
        -  const re = /[?&]([^&=]+)(?:[&=])([^&=]+)/gim;
        -  let m;
        -  const v = {};
        -  while ((m = re.exec(location.search)) != null) {
        -    if (m.index === re.lastIndex) {
        -      re.lastIndex++;
        +  /**
        +   * Returns the current
        +   * <a href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL#parameters" target="_blank">URL parameters</a>
        +   * in an `Object`.
        +   *
        +   * For example, calling `getURLParams()` in a sketch hosted at the URL
        +   * `http://p5js.org?year=2014&month=May&day=15` returns
        +   * `{ year: 2014, month: 'May', day: 15 }`.
        +   *
        +   * @method getURLParams
        +   * @return {Object} URL params
        +   * @example
        +   * <div class='norender notest'>
        +   * <code>
        +   * // Imagine this sketch is hosted at the following URL:
        +   * // https://p5js.org?year=2014&month=May&day=15
        +   *
        +   * function setup() {
        +   *   background(200);
        +   *
        +   *   // Get the sketch's URL
        +   *   // parameters and display
        +   *   // them.
        +   *   let params = getURLParams();
        +   *   text(params.day, 10, 20);
        +   *   text(params.month, 10, 40);
        +   *   text(params.year, 10, 60);
        +   *
        +   *   describe('The text "15", "May", and "2014" written in black on separate lines.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @alt
        +   * This example does not render anything.
        +   */
        +  fn.getURLParams = function() {
        +    const re = /[?&]([^&=]+)(?:[&=])([^&=]+)/gim;
        +    let m;
        +    const v = {};
        +    while ((m = re.exec(location.search)) != null) {
        +      if (m.index === re.lastIndex) {
        +        re.lastIndex++;
        +      }
        +      v[m[1]] = m[2];
             }
        -    v[m[1]] = m[2];
        -  }
        -  return v;
        -};
        +    return v;
        +  };
         
        -export default p5;
        +  /**
        +   * Converts 3D world coordinates to 2D screen coordinates.
        +   *
        +   * This function takes a 3D vector and converts its coordinates
        +   * from the world space to screen space. This can be useful for placing
        +   * 2D elements in a 3D scene or for determining the screen position
        +   * of 3D objects.
        +   *
        +   * @method worldToScreen
        +   * @param {p5.Vector} worldPosition The 3D coordinates in the world space.
        +   * @return {p5.Vector} A vector containing the 2D screen coordinates.
        +   * @example
        +   * <div>
        +   * <code>
        +   *
        +   * function setup() {
        +   *   createCanvas(150, 150);
        +   *   let vertices = [
        +   *     createVector(-20, -20),
        +   *     createVector(20, -20),
        +   *     createVector(20, 20),
        +   *     createVector(-20, 20)
        +   *   ];
        +   *
        +   *   push();
        +   *   translate(75, 55);
        +   *   rotate(PI / 4);
        +   *
        +   *   // Convert world coordinates to screen coordinates
        +   *   let screenPos = vertices.map(v => worldToScreen(v));
        +   *   pop();
        +   *
        +   *   background(200);
        +   *
        +   *   stroke(0);
        +   *   fill(100, 150, 255, 100);
        +   *   beginShape();
        +   *   screenPos.forEach(pos => vertex(pos.x, pos.y));
        +   *   endShape(CLOSE);
        +   *
        +   *   screenPos.forEach((pos, i) => {
        +   *     fill(0);
        +   *     textSize(10);
        +   *     if (i === 0) {
        +   *       text(i + 1, pos.x + 3, pos.y - 7);
        +   *     } else if (i === 1) {
        +   *       text(i + 1, pos.x + 7, pos.y + 2);
        +   *     } else if (i === 2) {
        +   *       text(i + 1, pos.x - 2, pos.y + 12);
        +   *     } else if (i === 3) {
        +   *       text(i + 1, pos.x - 12, pos.y - 2);
        +   *     }
        +   *   });
        +   *
        +   *   fill(0);
        +   *   noStroke();
        +   *   textSize(10);
        +   *   let legendY = height - 35;
        +   *   screenPos.forEach((pos, i) => {
        +   *     text(`Vertex ${i + 1}: (${pos.x.toFixed(1)}, ${pos.y.toFixed(1)})`, 5, legendY + i * 10);
        +   *   });
        +   *
        +   *   describe('A rotating square is transformed and drawn using screen coordinates.');
        +   *
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let vertices;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *   vertices = [
        +   *     createVector(-25, -25, -25),
        +   *     createVector(25, -25, -25),
        +   *     createVector(25, 25, -25),
        +   *     createVector(-25, 25, -25),
        +   *     createVector(-25, -25, 25),
        +   *     createVector(25, -25, 25),
        +   *     createVector(25, 25, 25),
        +   *     createVector(-25, 25, 25)
        +   *   ];
        +   *
        +   *   describe('A rotating cube with points mapped to 2D screen space and displayed as ellipses.');
        +   *
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Animate rotation
        +   *   let rotationX = millis() / 1000;
        +   *   let rotationY = millis() / 1200;
        +   *
        +   *   push();
        +   *
        +   *   rotateX(rotationX);
        +   *   rotateY(rotationY);
        +   *
        +   *   // Convert world coordinates to screen coordinates
        +   *   let screenPos = vertices.map(v => worldToScreen(v));
        +   *
        +   *   pop();
        +   *
        +   *   screenPos.forEach((pos, i) => {
        +   *
        +   *     let screenX = pos.x - width / 2;
        +   *     let screenY = pos.y - height / 2;
        +   *     fill(0);
        +   *     noStroke();
        +   *     ellipse(screenX, screenY, 3, 3);
        +   *   });
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   */
        +  fn.worldToScreen = function(worldPosition) {
        +    const renderer = this._renderer;
        +    if (renderer.drawingContext instanceof CanvasRenderingContext2D) {
        +      // Handle 2D context
        +      const transformMatrix = new DOMMatrix()
        +        .scale(1 / renderer._pInst.pixelDensity())
        +        .multiply(renderer.drawingContext.getTransform());
        +      const screenCoordinates = transformMatrix.transformPoint(
        +        new DOMPoint(worldPosition.x, worldPosition.y)
        +      );
        +      return new p5.Vector(screenCoordinates.x, screenCoordinates.y);
        +    } else {
        +          // Handle WebGL context (3D)
        +          const modelViewMatrix = renderer.calculateCombinedMatrix();
        +          const cameraCoordinates = modelViewMatrix.multiplyPoint(worldPosition);
        +          const normalizedDeviceCoordinates =
        +            renderer.states.uPMatrix.multiplyAndNormalizePoint(cameraCoordinates);
        +          const screenX = (0.5 + 0.5 * normalizedDeviceCoordinates.x) * this.width;
        +          const screenY = (0.5 - 0.5 * normalizedDeviceCoordinates.y) * this.height;
        +          const screenZ = 0.5 + 0.5 * normalizedDeviceCoordinates.z;
        +          return new Vector(screenX, screenY, screenZ);
        +    }
        +  };
        +}
        +
        +export default environment;
        +
        +if(typeof p5 !== 'undefined'){
        +  environment(p5, p5.prototype);
        +}
        diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js
        index f78fd957ce..ca6f09493f 100644
        --- a/src/core/friendly_errors/fes_core.js
        +++ b/src/core/friendly_errors/fes_core.js
        @@ -22,1081 +22,1088 @@
          * sequence of each function, please look at the FES Reference + Dev Notes:
          * https://github.com/processing/p5.js/blob/main/contributor_docs/fes_reference_dev_notes.md
          */
        -import p5 from '../main';
         import { translator } from '../internationalization';
        -
        -// p5.js blue, p5.js orange, auto dark green; fallback p5.js darkened magenta
        -// See testColors below for all the color codes and names
        -const typeColors = ['#2D7BB6', '#EE9900', '#4DB200', '#C83C00'];
        -let misusedAtTopLevelCode = null;
        -let defineMisusedAtTopLevelCode = null;
        -
        -// the threshold for the maximum allowed levenshtein distance
        -// used in misspelling detection
        -const EDIT_DIST_THRESHOLD = 2;
        -
        -// to enable or disable styling (color, font-size, etc. ) for fes messages
        -const ENABLE_FES_STYLING = false;
        -
        -if (typeof IS_MINIFIED !== 'undefined') {
        -  p5._friendlyError =
        -    p5._checkForUserDefinedFunctions =
        -    p5._fesErrorMonitor =
        -    () => {};
        -} else {
        -  let doFriendlyWelcome = false; // TEMP until we get it all working LM
        -
        -  const errorTable = require('./browser_errors').default;
        -
        -  // -- Borrowed from jQuery 1.11.3 --
        -  const class2type = {};
        -  const toString = class2type.toString;
        -  const names = [
        -    'Boolean',
        -    'Number',
        -    'String',
        -    'Function',
        -    'Array',
        -    'Date',
        -    'RegExp',
        -    'Object',
        -    'Error'
        -  ];
        -  for (let n = 0; n < names.length; n++) {
        -    class2type[`[object ${names[n]}]`] = names[n].toLowerCase();
        -  }
        -  const getType = obj => {
        -    if (obj == null) {
        -      return `${obj}`;
        -    }
        -    return typeof obj === 'object' || typeof obj === 'function'
        -      ? class2type[toString.call(obj)] || 'object'
        -      : typeof obj;
        -  };
        -
        -  // -- End borrow --
        -
        -  // entry points into user-defined code
        -  const entryPoints = [
        -    'setup',
        -    'draw',
        -    'preload',
        -    'deviceMoved',
        -    'deviceTurned',
        -    'deviceShaken',
        -    'doubleClicked',
        -    'mousePressed',
        -    'mouseReleased',
        -    'mouseMoved',
        -    'mouseDragged',
        -    'mouseClicked',
        -    'mouseWheel',
        -    'touchStarted',
        -    'touchMoved',
        -    'touchEnded',
        -    'keyPressed',
        -    'keyReleased',
        -    'keyTyped',
        -    'windowResized'
        -  ];
        -
        -  const friendlyWelcome = () => {
        -    // p5.js brand - magenta: #ED225D
        -    //const astrixBgColor = 'transparent';
        -    //const astrixTxtColor = '#ED225D';
        -    //const welcomeBgColor = '#ED225D';
        -    //const welcomeTextColor = 'white';
        -    const welcomeMessage = translator('fes.pre', {
        -      message: translator('fes.welcome')
        -    });
        -    console.log(
        -      '    _ \n' +
        -        ' /\\| |/\\ \n' +
        -        " \\ ` ' /  \n" +
        -        ' / , . \\  \n' +
        -        ' \\/|_|\\/ ' +
        -        '\n\n' +
        -        welcomeMessage
        -    );
        -  };
        -
        -  /**
        -   * Takes a message and a p5 function func, and adds a link pointing to
        -   * the reference documentation of func at the end of the message
        -   *
        -   * @method mapToReference
        -   * @private
        -   * @param {String}  message   the words to be said
        -   * @param {String}  [func]    the name of function
        -   *
        -   * @returns {String}
        -   */
        -  const mapToReference = (message, func) => {
        -    let msgWithReference = '';
        -    if (func == null || func.substring(0, 4) === 'load') {
        -      msgWithReference = message;
        -    } else {
        -      const methodParts = func.split('.');
        -      const referenceSection =
        -        methodParts.length > 1 ? `${methodParts[0]}.${methodParts[1]}` : 'p5';
        -
        -      const funcName =
        -        methodParts.length === 1 ? func : methodParts.slice(2).join('/');
        -
        -      //Whenever func having p5.[Class] is encountered, we need to have the error link as mentioned below else different link
        -      funcName.startsWith('p5.')  ?
        -        msgWithReference = `${message} (http://p5js.org/reference/${referenceSection}.${funcName})` :
        -        msgWithReference = `${message} (http://p5js.org/reference/${referenceSection}/${funcName})`;
        -    }
        -    return msgWithReference;
        -  };
        -
        -  /**
        -   * Prints out a fancy, colorful message to the console log
        -   * Attaches Friendly Errors prefix [fes.pre] to the message.
        -   *
        -   * @method _report
        -   * @private
        -   * @param  {String}          message  Message to be printed
        -   * @param  {String}          [func]   Name of function
        -   * @param  {Number|String}   [color]  CSS color code
        -   *
        -   * @return console logs
        -   */
        -  p5._report = (message, func, color) => {
        -    // if p5._fesLogger is set ( i.e we are running tests ), use that
        -    // instead of console.log
        -    const log =
        -      p5._fesLogger == null ? console.log.bind(console) : p5._fesLogger;
        -
        -    if (doFriendlyWelcome) {
        -      friendlyWelcome();
        -      doFriendlyWelcome = false;
        +import errorTable from './browser_errors';
        +import * as contants from '../constants';
        +
        +function fesCore(p5, fn){
        +  // p5.js blue, p5.js orange, auto dark green; fallback p5.js darkened magenta
        +  // See testColors below for all the color codes and names
        +  const typeColors = ['#2D7BB6', '#EE9900', '#4DB200', '#C83C00'];
        +  let misusedAtTopLevelCode = null;
        +  let defineMisusedAtTopLevelCode = null;
        +
        +  // the threshold for the maximum allowed levenshtein distance
        +  // used in misspelling detection
        +  const EDIT_DIST_THRESHOLD = 2;
        +
        +  // to enable or disable styling (color, font-size, etc. ) for fes messages
        +  const ENABLE_FES_STYLING = false;
        +
        +  if (typeof IS_MINIFIED !== 'undefined') {
        +    p5._friendlyError =
        +      p5._checkForUserDefinedFunctions =
        +      p5._fesErrorMonitor =
        +      () => {};
        +  } else {
        +    let doFriendlyWelcome = false; // TEMP until we get it all working LM
        +
        +    // const errorTable = require('./browser_errors').default;
        +
        +    // -- Borrowed from jQuery 1.11.3 --
        +    const class2type = {};
        +    const toString = class2type.toString;
        +    const names = [
        +      'Boolean',
        +      'Number',
        +      'String',
        +      'Function',
        +      'Array',
        +      'Date',
        +      'RegExp',
        +      'Object',
        +      'Error'
        +    ];
        +    for (let n = 0; n < names.length; n++) {
        +      class2type[`[object ${names[n]}]`] = names[n].toLowerCase();
             }
        -    if ('undefined' === getType(color)) {
        -      color = '#B40033'; // dark magenta
        -    } else if (getType(color) === 'number') {
        -      // Type to color
        -      color = typeColors[color];
        -    }
        -
        -    // Add a link to the reference docs of func at the end of the message
        -    message = mapToReference(message, func);
        -    let style = [`color: ${color}`, 'font-family: Arial', 'font-size: larger'];
        -    const prefixedMsg = translator('fes.pre', { message });
        -
        -    if (ENABLE_FES_STYLING) {
        -      log('%c' + prefixedMsg, style.join(';'));
        -    } else {
        -      log(prefixedMsg);
        -    }
        -  };
        -  /**
        -   * This is a generic method that can be called from anywhere in the p5
        -   * library to alert users to a common error.
        -   *
        -   * @method _friendlyError
        -   * @private
        -   * @param  {String}         message   Message to be printed
        -   * @param  {String}         [func]    Name of the function linked to error
        -   * @param  {Number|String}  [color]   CSS color code
        -   */
        -  p5._friendlyError = function(message, func, color) {
        -    p5._report(message, func, color);
        -  };
        +    const getType = obj => {
        +      if (obj == null) {
        +        return `${obj}`;
        +      }
        +      return typeof obj === 'object' || typeof obj === 'function'
        +        ? class2type[toString.call(obj)] || 'object'
        +        : typeof obj;
        +    };
        +
        +    // -- End borrow --
        +
        +    // entry points into user-defined code
        +    const entryPoints = [
        +      'setup',
        +      'draw',
        +      'preload',
        +      'deviceMoved',
        +      'deviceTurned',
        +      'deviceShaken',
        +      'doubleClicked',
        +      'mousePressed',
        +      'mouseReleased',
        +      'mouseMoved',
        +      'mouseDragged',
        +      'mouseClicked',
        +      'mouseWheel',
        +      'touchStarted',
        +      'touchMoved',
        +      'touchEnded',
        +      'keyPressed',
        +      'keyReleased',
        +      'keyTyped',
        +      'windowResized'
        +    ];
        +
        +    const friendlyWelcome = () => {
        +      // p5.js brand - magenta: #ED225D
        +      //const astrixBgColor = 'transparent';
        +      //const astrixTxtColor = '#ED225D';
        +      //const welcomeBgColor = '#ED225D';
        +      //const welcomeTextColor = 'white';
        +      const welcomeMessage = translator('fes.pre', {
        +        message: translator('fes.welcome')
        +      });
        +      console.log(
        +        '    _ \n' +
        +          ' /\\| |/\\ \n' +
        +          " \\ ` ' /  \n" +
        +          ' / , . \\  \n' +
        +          ' \\/|_|\\/ ' +
        +          '\n\n' +
        +          welcomeMessage
        +      );
        +    };
        +
        +    /**
        +     * Takes a message and a p5 function func, and adds a link pointing to
        +     * the reference documentation of func at the end of the message
        +     *
        +     * @method mapToReference
        +     * @private
        +     * @param {String}  message   the words to be said
        +     * @param {String}  [func]    the name of function
        +     *
        +     * @returns {String}
        +     */
        +    const mapToReference = (message, func) => {
        +      let msgWithReference = '';
        +      if (func == null || func.substring(0, 4) === 'load') {
        +        msgWithReference = message;
        +      } else {
        +        const methodParts = func.split('.');
        +        const referenceSection =
        +          methodParts.length > 1 ? `${methodParts[0]}.${methodParts[1]}` : 'p5';
         
        -  /**
        -   * This is called internally if there is an error with autoplay. Generates
        -   * and prints a friendly error message [fes.autoplay].
        -   *
        -   * @method _friendlyAutoplayError
        -   * @private
        -   */
        -  p5._friendlyAutoplayError = function(src) {
        -    const message = translator('fes.autoplay', {
        -      src,
        -      url: 'https://developer.mozilla.org/docs/Web/Media/Autoplay_guide'
        -    });
        -    console.log(translator('fes.pre', { message }));
        -  };
        +        const funcName =
        +          methodParts.length === 1 ? func : methodParts.slice(2).join('/');
         
        -  /**
        -   * Measures dissimilarity between two strings by calculating
        -   * the Levenshtein distance.
        -   *
        -   * If the "distance" between them is small enough, it is
        -   * reasonable to think that one is the misspelled version of the other.
        -   *
        -   * Specifically, this uses the Wagner–Fischer algorithm.
        -   * @method computeEditDistance
        -   * @private
        -   * @param {String} w1 the first word
        -   * @param {String} w2 the second word
        -   *
        -   * @returns {Number} the "distance" between the two words, a smaller value
        -   *                   indicates that the words are similar
        -   */
        -  const computeEditDistance = (w1, w2) => {
        -    const l1 = w1.length,
        -      l2 = w2.length;
        -    if (l1 === 0) return w2;
        -    if (l2 === 0) return w1;
        -
        -    let prev = [];
        -    let cur = [];
        +        //Whenever func having p5.[Class] is encountered, we need to have the error link as mentioned below else different link
        +        funcName.startsWith('p5.')  ?
        +          msgWithReference = `${message} (http://p5js.org/reference/${referenceSection}.${funcName})` :
        +          msgWithReference = `${message} (http://p5js.org/reference/${referenceSection}/${funcName})`;
        +      }
        +      return msgWithReference;
        +    };
        +
        +    /**
        +     * Prints out a fancy, colorful message to the console log
        +     * Attaches Friendly Errors prefix [fes.pre] to the message.
        +     *
        +     * @method _report
        +     * @private
        +     * @param  {String}          message  Message to be printed
        +     * @param  {String}          [func]   Name of function
        +     * @param  {Number|String}   [color]  CSS color code
        +     *
        +     * @return console logs
        +     */
        +    p5._report = (message, func, color) => {
        +      // if p5._fesLogger is set ( i.e we are running tests ), use that
        +      // instead of console.log
        +      const log =
        +        p5._fesLogger == null ? console.log.bind(console) : p5._fesLogger;
        +
        +      if (doFriendlyWelcome) {
        +        friendlyWelcome();
        +        doFriendlyWelcome = false;
        +      }
        +      if ('undefined' === getType(color)) {
        +        color = '#B40033'; // dark magenta
        +      } else if (getType(color) === 'number') {
        +        // Type to color
        +        color = typeColors[color];
        +      }
         
        -    for (let j = 0; j < l2 + 1; j++) {
        -      cur[j] = j;
        -    }
        +      // Add a link to the reference docs of func at the end of the message
        +      message = mapToReference(message, func);
        +      let style = [`color: ${color}`, 'font-family: Arial', 'font-size: larger'];
        +      const prefixedMsg = translator('fes.pre', { message });
         
        -    prev = cur;
        +      if (ENABLE_FES_STYLING) {
        +        log('%c' + prefixedMsg, style.join(';'));
        +      } else {
        +        log(prefixedMsg);
        +      }
        +    };
        +    /**
        +     * This is a generic method that can be called from anywhere in the p5
        +     * library to alert users to a common error.
        +     *
        +     * @method _friendlyError
        +     * @private
        +     * @param  {String}         message   Message to be printed
        +     * @param  {String}         [func]    Name of the function linked to error
        +     * @param  {Number|String}  [color]   CSS color code
        +     */
        +    p5._friendlyError = function(message, func, color) {
        +      p5._report(message, func, color);
        +    };
        +
        +    /**
        +     * This is called internally if there is an error with autoplay. Generates
        +     * and prints a friendly error message [fes.autoplay].
        +     *
        +     * @method _friendlyAutoplayError
        +     * @private
        +     */
        +    p5._friendlyAutoplayError = function(src) {
        +      const message = translator('fes.autoplay', {
        +        src,
        +        url: 'https://developer.mozilla.org/docs/Web/Media/Autoplay_guide'
        +      });
        +      console.log(translator('fes.pre', { message }));
        +    };
        +
        +    /**
        +     * Measures dissimilarity between two strings by calculating
        +     * the Levenshtein distance.
        +     *
        +     * If the "distance" between them is small enough, it is
        +     * reasonable to think that one is the misspelled version of the other.
        +     *
        +     * Specifically, this uses the Wagner–Fischer algorithm.
        +     * @method computeEditDistance
        +     * @private
        +     * @param {String} w1 the first word
        +     * @param {String} w2 the second word
        +     *
        +     * @returns {Number} the "distance" between the two words, a smaller value
        +     *                   indicates that the words are similar
        +     */
        +    const computeEditDistance = (w1, w2) => {
        +      const l1 = w1.length,
        +        l2 = w2.length;
        +      if (l1 === 0) return w2;
        +      if (l2 === 0) return w1;
        +
        +      let prev = [];
        +      let cur = [];
         
        -    for (let i = 1; i < l1 + 1; i++) {
        -      cur = [];
               for (let j = 0; j < l2 + 1; j++) {
        -        if (j === 0) {
        -          cur[j] = i;
        -        } else {
        -          let a1 = w1[i - 1],
        -            a2 = w2[j - 1];
        -          let temp = 999999;
        -          let cost = a1.toLowerCase() === a2.toLowerCase() ? 0 : 1;
        -          temp = temp > cost + prev[j - 1] ? cost + prev[j - 1] : temp;
        -          temp = temp > 1 + cur[j - 1] ? 1 + cur[j - 1] : temp;
        -          temp = temp > 1 + prev[j] ? 1 + prev[j] : temp;
        -          cur[j] = temp;
        -        }
        +        cur[j] = j;
               }
        -      prev = cur;
        -    }
        -
        -    return cur[l2];
        -  };
         
        -  /**
        -   * Checks capitalization for user defined functions.
        -   *
        -   * Generates and prints a friendly error message using key:
        -   * "fes.checkUserDefinedFns".
        -   *
        -   * @method checkForUserDefinedFunctions
        -   * @private
        -   * @param {*} context   Current default context. Set to window in
        -   *                      "global mode" and to a p5 instance in "instance mode"
        -   */
        -  const checkForUserDefinedFunctions = context => {
        -    if (p5.disableFriendlyErrors) return;
        -
        -    // if using instance mode, this function would be called with the current
        -    // instance as context
        -    const instanceMode = context instanceof p5;
        -    context = instanceMode ? context : window;
        -    const fnNames = entryPoints;
        -
        -    const fxns = {};
        -    // lowercasename -> actualName mapping
        -    fnNames.forEach(symbol => {
        -      fxns[symbol.toLowerCase()] = symbol;
        -    });
        +      prev = cur;
         
        -    for (const prop of Object.keys(context)) {
        -      const lowercase = prop.toLowerCase();
        +      for (let i = 1; i < l1 + 1; i++) {
        +        cur = [];
        +        for (let j = 0; j < l2 + 1; j++) {
        +          if (j === 0) {
        +            cur[j] = i;
        +          } else {
        +            let a1 = w1[i - 1],
        +              a2 = w2[j - 1];
        +            let temp = 999999;
        +            let cost = a1.toLowerCase() === a2.toLowerCase() ? 0 : 1;
        +            temp = temp > cost + prev[j - 1] ? cost + prev[j - 1] : temp;
        +            temp = temp > 1 + cur[j - 1] ? 1 + cur[j - 1] : temp;
        +            temp = temp > 1 + prev[j] ? 1 + prev[j] : temp;
        +            cur[j] = temp;
        +          }
        +        }
        +        prev = cur;
        +      }
         
        -      // check if the lowercase property name has an entry in fxns, if the
        -      // actual name with correct capitalization doesnt exist in context,
        -      // and if the user-defined symbol is of the type function
        -      if (
        -        fxns[lowercase] &&
        -        !context[fxns[lowercase]] &&
        -        typeof context[prop] === 'function'
        -      ) {
        -        const msg = translator('fes.checkUserDefinedFns', {
        -          name: prop,
        -          actualName: fxns[lowercase]
        -        });
        +      return cur[l2];
        +    };
        +
        +    /**
        +     * Checks capitalization for user defined functions.
        +     *
        +     * Generates and prints a friendly error message using key:
        +     * "fes.checkUserDefinedFns".
        +     *
        +     * @method checkForUserDefinedFunctions
        +     * @private
        +     * @param {*} context   Current default context. Set to window in
        +     *                      "global mode" and to a p5 instance in "instance mode"
        +     */
        +    const checkForUserDefinedFunctions = context => {
        +      if (p5.disableFriendlyErrors) return;
        +
        +      // if using instance mode, this function would be called with the current
        +      // instance as context
        +      const instanceMode = context instanceof p5;
        +      context = instanceMode ? context : window;
        +      const fnNames = entryPoints;
        +
        +      const fxns = {};
        +      // lowercasename -> actualName mapping
        +      fnNames.forEach(symbol => {
        +        fxns[symbol.toLowerCase()] = symbol;
        +      });
         
        -        p5._friendlyError(msg, fxns[lowercase]);
        +      for (const prop of Object.keys(context)) {
        +        const lowercase = prop.toLowerCase();
        +
        +        // check if the lowercase property name has an entry in fxns, if the
        +        // actual name with correct capitalization doesnt exist in context,
        +        // and if the user-defined symbol is of the type function
        +        if (
        +          fxns[lowercase] &&
        +          !context[fxns[lowercase]] &&
        +          typeof context[prop] === 'function'
        +        ) {
        +          const msg = translator('fes.checkUserDefinedFns', {
        +            name: prop,
        +            actualName: fxns[lowercase]
        +          });
        +
        +          p5._friendlyError(msg, fxns[lowercase]);
        +        }
        +      }
        +    };
        +
        +    /**
        +     * Compares the symbol caught in the ReferenceError to everything in
        +     * misusedAtTopLevel ( all public p5 properties ).
        +     *
        +     * Generates and prints a friendly error message using key: "fes.misspelling".
        +     *
        +     * @method handleMisspelling
        +     * @private
        +     * @param {String} errSym   Symbol to whose spelling to check
        +     * @param {Error} error     ReferenceError object
        +     *
        +     * @returns {Boolean} tell whether error was likely due to typo
        +     */
        +    const handleMisspelling = (errSym, error) => {
        +      if (!misusedAtTopLevelCode) {
        +        defineMisusedAtTopLevelCode();
               }
        -    }
        -  };
         
        -  /**
        -   * Compares the symbol caught in the ReferenceError to everything in
        -   * misusedAtTopLevel ( all public p5 properties ).
        -   *
        -   * Generates and prints a friendly error message using key: "fes.misspelling".
        -   *
        -   * @method handleMisspelling
        -   * @private
        -   * @param {String} errSym   Symbol to whose spelling to check
        -   * @param {Error} error     ReferenceError object
        -   *
        -   * @returns {Boolean} tell whether error was likely due to typo
        -   */
        -  const handleMisspelling = (errSym, error) => {
        -    if (!misusedAtTopLevelCode) {
        -      defineMisusedAtTopLevelCode();
        -    }
        +      const distanceMap = {};
        +      let min = 999999;
        +      // compute the levenshtein distance for the symbol against all known
        +      // public p5 properties. Find the property with the minimum distance
        +      misusedAtTopLevelCode.forEach(symbol => {
        +        let dist = computeEditDistance(errSym, symbol.name);
        +        if (distanceMap[dist]) distanceMap[dist].push(symbol);
        +        else distanceMap[dist] = [symbol];
         
        -    const distanceMap = {};
        -    let min = 999999;
        -    // compute the levenshtein distance for the symbol against all known
        -    // public p5 properties. Find the property with the minimum distance
        -    misusedAtTopLevelCode.forEach(symbol => {
        -      let dist = computeEditDistance(errSym, symbol.name);
        -      if (distanceMap[dist]) distanceMap[dist].push(symbol);
        -      else distanceMap[dist] = [symbol];
        +        if (dist < min) min = dist;
        +      });
         
        -      if (dist < min) min = dist;
        -    });
        +      // if the closest match has more "distance" than the max allowed threshold
        +      if (min > Math.min(EDIT_DIST_THRESHOLD, errSym.length)) return false;
         
        -    // if the closest match has more "distance" than the max allowed threshold
        -    if (min > Math.min(EDIT_DIST_THRESHOLD, errSym.length)) return false;
        +      // Show a message only if the caught symbol and the matched property name
        +      // differ in their name ( either letter difference or difference of case )
        +      const matchedSymbols = distanceMap[min].filter(
        +        symbol => symbol.name !== errSym
        +      );
        +      if (matchedSymbols.length !== 0) {
        +        const parsed = p5._getErrorStackParser().parse(error);
        +        let locationObj;
        +        if (
        +          parsed &&
        +          parsed[0] &&
        +          parsed[0].fileName &&
        +          parsed[0].lineNumber &&
        +          parsed[0].columnNumber
        +        ) {
        +          locationObj = {
        +            location: `${parsed[0].fileName}:${parsed[0].lineNumber}:${
        +              parsed[0].columnNumber
        +            }`,
        +            file: parsed[0].fileName.split('/').slice(-1),
        +            line: parsed[0].lineNumber
        +          };
        +        }
         
        -    // Show a message only if the caught symbol and the matched property name
        -    // differ in their name ( either letter difference or difference of case )
        -    const matchedSymbols = distanceMap[min].filter(
        -      symbol => symbol.name !== errSym
        -    );
        -    if (matchedSymbols.length !== 0) {
        -      const parsed = p5._getErrorStackParser().parse(error);
        -      let locationObj;
        -      if (
        -        parsed &&
        -        parsed[0] &&
        -        parsed[0].fileName &&
        -        parsed[0].lineNumber &&
        -        parsed[0].columnNumber
        -      ) {
        -        locationObj = {
        -          location: `${parsed[0].fileName}:${parsed[0].lineNumber}:${
        -            parsed[0].columnNumber
        -          }`,
        -          file: parsed[0].fileName.split('/').slice(-1),
        -          line: parsed[0].lineNumber
        -        };
        -      }
        +        let msg;
        +        if (matchedSymbols.length === 1) {
        +          // To be used when there is only one closest match. The count parameter
        +          // allows i18n to pick between the keys "fes.misspelling" and
        +          // "fes.misspelling_plural"
        +          msg = translator('fes.misspelling', {
        +            name: errSym,
        +            actualName: matchedSymbols[0].name,
        +            type: matchedSymbols[0].type,
        +            location: locationObj ? translator('fes.location', locationObj) : '',
        +            count: matchedSymbols.length
        +          });
        +        } else {
        +          // To be used when there are multiple closest matches. Gives each
        +          // suggestion on its own line, the function name followed by a link to
        +          // reference documentation
        +          const suggestions = matchedSymbols
        +            .map(symbol => {
        +              const message =
        +                '▶️ ' + symbol.name + (symbol.type === 'function' ? '()' : '');
        +              return mapToReference(message, symbol.name);
        +            })
        +            .join('\n');
        +
        +          msg = translator('fes.misspelling', {
        +            name: errSym,
        +            suggestions,
        +            location: locationObj ? translator('fes.location', locationObj) : '',
        +            count: matchedSymbols.length
        +          });
        +        }
         
        -      let msg;
        -      if (matchedSymbols.length === 1) {
        -        // To be used when there is only one closest match. The count parameter
        -        // allows i18n to pick between the keys "fes.misspelling" and
        -        // "fes.misspelling_plural"
        -        msg = translator('fes.misspelling', {
        -          name: errSym,
        -          actualName: matchedSymbols[0].name,
        -          type: matchedSymbols[0].type,
        -          location: locationObj ? translator('fes.location', locationObj) : '',
        -          count: matchedSymbols.length
        +        // If there is only one closest match, tell _friendlyError to also add
        +        // a link to the reference documentation. In case of multiple matches,
        +        // this is already done in the suggestions variable, one link for each
        +        // suggestion.
        +        p5._friendlyError(
        +          msg,
        +          matchedSymbols.length === 1 ? matchedSymbols[0].name : undefined
        +        );
        +        return true;
        +      }
        +      return false;
        +    };
        +
        +    /**
        +     * Prints a friendly stacktrace for user-written functions for "global" errors
        +     *
        +     * Generates and prints a friendly error message using key:
        +     * "fes.globalErrors.stackTop", "fes.globalErrors.stackSubseq".
        +     *
        +     * @method printFriendlyStack
        +     * @private
        +     * @param {Array} friendlyStack
        +     */
        +    const printFriendlyStack = friendlyStack => {
        +      const log =
        +        p5._fesLogger && typeof p5._fesLogger === 'function'
        +          ? p5._fesLogger
        +          : console.log.bind(console);
        +      if (friendlyStack.length > 1) {
        +        let stacktraceMsg = '';
        +        friendlyStack.forEach((frame, idx) => {
        +          const location = `${frame.fileName}:${frame.lineNumber}:${
        +            frame.columnNumber
        +          }`;
        +          let frameMsg,
        +            translationObj = {
        +              func: frame.functionName,
        +              line: frame.lineNumber,
        +              location,
        +              file: frame.fileName.split('/').slice(-1)
        +            };
        +          if (idx === 0) {
        +            frameMsg = translator('fes.globalErrors.stackTop', translationObj);
        +          } else {
        +            frameMsg = translator('fes.globalErrors.stackSubseq', translationObj);
        +          }
        +          stacktraceMsg += frameMsg;
                 });
        -      } else {
        -        // To be used when there are multiple closest matches. Gives each
        -        // suggestion on its own line, the function name followed by a link to
        -        // reference documentation
        -        const suggestions = matchedSymbols
        -          .map(symbol => {
        -            const message =
        -              '▶️ ' + symbol.name + (symbol.type === 'function' ? '()' : '');
        -            return mapToReference(message, symbol.name);
        -          })
        -          .join('\n');
        +        log(stacktraceMsg);
        +      }
        +    };
        +
        +    /**
        +     * Takes a stacktrace array and filters out all frames that show internal p5
        +     * details.
        +     *
        +     * Generates and prints a friendly error message using key:
        +     * "fes.wrongPreload", "fes.libraryError".
        +     *
        +     * The processed stack is used to find whether the error happened internally
        +     * within the library, and if the error was due to a non-loadX() method
        +     * being used in preload.
        +     *
        +     * "Internally" here means that the exact location of the error (the top of
        +     * the stack) is a piece of code written in the p5.js library (which may or
        +     * may not have been called from the user's sketch).
        +     *
        +     * @method processStack
        +     * @private
        +     * @param {Error} error
        +     * @param {Array} stacktrace
        +     *
        +     * @returns {Array} An array with two elements, [isInternal, friendlyStack]
        +     *                 isInternal: a boolean value indicating whether the error
        +     *                             happened internally
        +     *                 friendlyStack: the filtered (simplified) stacktrace
        +     */
        +    const processStack = (error, stacktrace) => {
        +      // cannot process a stacktrace that doesn't exist
        +      if (!stacktrace) return [false, null];
        +
        +      stacktrace.forEach(frame => {
        +        frame.functionName = frame.functionName || '';
        +      });
         
        -        msg = translator('fes.misspelling', {
        -          name: errSym,
        -          suggestions,
        -          location: locationObj ? translator('fes.location', locationObj) : '',
        -          count: matchedSymbols.length
        -        });
        +      // isInternal - Did this error happen inside the library
        +      let isInternal = false;
        +      let p5FileName, friendlyStack, currentEntryPoint;
        +
        +      // Intentionally throw an error that we catch so that we can check the name
        +      // of the current file. Any errors we see from this file, we treat as
        +      // internal errors.
        +      try {
        +        throw new Error();
        +      } catch (testError) {
        +        const testStacktrace = p5._getErrorStackParser().parse(testError);
        +        p5FileName = testStacktrace[0].fileName;
               }
         
        -      // If there is only one closest match, tell _friendlyError to also add
        -      // a link to the reference documentation. In case of multiple matches,
        -      // this is already done in the suggestions variable, one link for each
        -      // suggestion.
        -      p5._friendlyError(
        -        msg,
        -        matchedSymbols.length === 1 ? matchedSymbols[0].name : undefined
        -      );
        -      return true;
        -    }
        -    return false;
        -  };
        +      for (let i = stacktrace.length - 1; i >= 0; i--) {
        +        let splitted = stacktrace[i].functionName.split('.');
        +        if (entryPoints.includes(splitted[splitted.length - 1])) {
        +          // remove everything below an entry point function (setup, draw, etc).
        +          // (it's usually the internal initialization calls)
        +          friendlyStack = stacktrace.slice(0, i + 1);
        +          currentEntryPoint = splitted[splitted.length - 1];
        +          // We call the error "internal" if the source of the error was a
        +          // function from within the p5.js library file, but called from the
        +          // user's code directly. We only need to check the topmost frame in
        +          // the stack trace since any function internal to p5 should pass this
        +          // check, not just public p5 functions.
        +          if (stacktrace[0].fileName === p5FileName) {
        +            isInternal = true;
        +            break;
        +          }
        +          break;
        +        }
        +      }
         
        -  /**
        -   * Prints a friendly stacktrace for user-written functions for "global" errors
        -   *
        -   * Generates and prints a friendly error message using key:
        -   * "fes.globalErrors.stackTop", "fes.globalErrors.stackSubseq".
        -   *
        -   * @method printFriendlyStack
        -   * @private
        -   * @param {Array} friendlyStack
        -   */
        -  const printFriendlyStack = friendlyStack => {
        -    const log =
        -      p5._fesLogger && typeof p5._fesLogger === 'function'
        -        ? p5._fesLogger
        -        : console.log.bind(console);
        -    if (friendlyStack.length > 1) {
        -      let stacktraceMsg = '';
        -      friendlyStack.forEach((frame, idx) => {
        -        const location = `${frame.fileName}:${frame.lineNumber}:${
        -          frame.columnNumber
        -        }`;
        -        let frameMsg,
        -          translationObj = {
        -            func: frame.functionName,
        -            line: frame.lineNumber,
        -            location,
        -            file: frame.fileName.split('/').slice(-1)
        +      // in some cases ( errors in promises, callbacks, etc), no entry-point
        +      // function may be found in the stacktrace. In that case just use the
        +      // entire stacktrace for friendlyStack
        +      if (!friendlyStack) friendlyStack = stacktrace;
        +
        +      if (isInternal) {
        +        // the frameIndex property is added before the filter, so frameIndex
        +        // corresponds to the index of a frame in the original stacktrace.
        +        // Then we filter out all frames which belong to the file that contains
        +        // the p5 library
        +        friendlyStack = friendlyStack
        +          .map((frame, index) => {
        +            frame.frameIndex = index;
        +            return frame;
        +          })
        +          .filter(frame => frame.fileName !== p5FileName);
        +
        +        // a weird case, if for some reason we can't identify the function called
        +        // from user's code
        +        if (friendlyStack.length === 0) return [true, null];
        +
        +        // get the function just above the topmost frame in the friendlyStack.
        +        // i.e the name of the library function called from user's code
        +        const func = stacktrace[friendlyStack[0].frameIndex - 2].functionName
        +          .split('.')
        +          .slice(-1)[0];
        +
        +        // Try and get the location (line no.) from the top element of the stack
        +        let locationObj;
        +        if (
        +          friendlyStack[0].fileName &&
        +          friendlyStack[0].lineNumber &&
        +          friendlyStack[0].columnNumber
        +        ) {
        +          locationObj = {
        +            location: `${friendlyStack[0].fileName}:${
        +              friendlyStack[0].lineNumber
        +            }:${friendlyStack[0].columnNumber}`,
        +            file: friendlyStack[0].fileName.split('/').slice(-1),
        +            line: friendlyStack[0].lineNumber
                   };
        -        if (idx === 0) {
        -          frameMsg = translator('fes.globalErrors.stackTop', translationObj);
        +
        +          // if already handled by another part of the FES, don't handle again
        +          if (p5._fesLogCache[locationObj.location]) return [true, null];
        +        }
        +
        +        // Check if the error is due to a non loadX method being used incorrectly
        +        // in preload
        +        if (
        +          currentEntryPoint === 'preload' &&
        +          fn._preloadMethods[func] == null
        +        ) {
        +          p5._friendlyError(
        +            translator('fes.wrongPreload', {
        +              func,
        +              location: locationObj
        +                ? translator('fes.location', locationObj)
        +                : '',
        +              error: error.message
        +            }),
        +            'preload'
        +          );
                 } else {
        -          frameMsg = translator('fes.globalErrors.stackSubseq', translationObj);
        +          // Library error
        +          p5._friendlyError(
        +            translator('fes.libraryError', {
        +              func,
        +              location: locationObj
        +                ? translator('fes.location', locationObj)
        +                : '',
        +              error: error.message
        +            }),
        +            func
        +          );
                 }
        -        stacktraceMsg += frameMsg;
        -      });
        -      log(stacktraceMsg);
        -    }
        -  };
         
        -  /**
        -   * Takes a stacktrace array and filters out all frames that show internal p5
        -   * details.
        -   *
        -   * Generates and prints a friendly error message using key:
        -   * "fes.wrongPreload", "fes.libraryError".
        -   *
        -   * The processed stack is used to find whether the error happened internally
        -   * within the library, and if the error was due to a non-loadX() method
        -   * being used in preload.
        -   *
        -   * "Internally" here means that the exact location of the error (the top of
        -   * the stack) is a piece of code written in the p5.js library (which may or
        -   * may not have been called from the user's sketch).
        -   *
        -   * @method processStack
        -   * @private
        -   * @param {Error} error
        -   * @param {Array} stacktrace
        -   *
        -   * @returns {Array} An array with two elements, [isInternal, friendlyStack]
        -   *                 isInternal: a boolean value indicating whether the error
        -   *                             happened internally
        -   *                 friendlyStack: the filtered (simplified) stacktrace
        -   */
        -  const processStack = (error, stacktrace) => {
        -    // cannot process a stacktrace that doesn't exist
        -    if (!stacktrace) return [false, null];
        +        // Finally, if it's an internal error, print the friendlyStack
        +        // ( fesErrorMonitor won't handle this error )
        +        if (friendlyStack && friendlyStack.length) {
        +          printFriendlyStack(friendlyStack);
        +        }
        +      }
        +      return [isInternal, friendlyStack];
        +    };
        +
        +    /**
        +     * Handles "global" errors that the browser catches.
        +     *
        +     * Called when an error event happens and detects the type of error.
        +     *
        +     * Generates and prints a friendly error message using key:
        +     * "fes.globalErrors.syntax.[*]", "fes.globalErrors.reference.[*]",
        +     * "fes.globalErrors.type.[*]".
        +     *
        +     * @method fesErrorMonitor
        +     * @private
        +     * @param {*} e  Event object to extract error details from
        +     */
        +    const fesErrorMonitor = e => {
        +      if (p5.disableFriendlyErrors) return;
        +      // Try to get the error object from e
        +      let error;
        +      if (e instanceof Error) {
        +        error = e;
        +      } else if (e instanceof ErrorEvent) {
        +        error = e.error;
        +      } else if (e instanceof PromiseRejectionEvent) {
        +        error = e.reason;
        +        if (!(error instanceof Error)) return;
        +      }
        +      if (!error) return;
         
        -    stacktrace.forEach(frame => {
        -      frame.functionName = frame.functionName || '';
        -    });
        +      let stacktrace = p5._getErrorStackParser().parse(error);
        +      // process the stacktrace from the browser and simplify it to give
        +      // friendlyStack.
        +      let [isInternal, friendlyStack] = processStack(error, stacktrace);
         
        -    // isInternal - Did this error happen inside the library
        -    let isInternal = false;
        -    let p5FileName, friendlyStack, currentEntryPoint;
        -
        -    // Intentionally throw an error that we catch so that we can check the name
        -    // of the current file. Any errors we see from this file, we treat as
        -    // internal errors.
        -    try {
        -      throw new Error();
        -    } catch (testError) {
        -      const testStacktrace = p5._getErrorStackParser().parse(testError);
        -      p5FileName = testStacktrace[0].fileName;
        -    }
        +      // if this is an internal library error, the type of the error is not relevant,
        +      // only the user code that lead to it is.
        +      if (isInternal) {
        +        return;
        +      }
         
        -    for (let i = stacktrace.length - 1; i >= 0; i--) {
        -      let splitted = stacktrace[i].functionName.split('.');
        -      if (entryPoints.includes(splitted[splitted.length - 1])) {
        -        // remove everything below an entry point function (setup, draw, etc).
        -        // (it's usually the internal initialization calls)
        -        friendlyStack = stacktrace.slice(0, i + 1);
        -        currentEntryPoint = splitted[splitted.length - 1];
        -        // We call the error "internal" if the source of the error was a
        -        // function from within the p5.js library file, but called from the
        -        // user's code directly. We only need to check the topmost frame in
        -        // the stack trace since any function internal to p5 should pass this
        -        // check, not just public p5 functions.
        -        if (stacktrace[0].fileName === p5FileName) {
        -          isInternal = true;
        +      const errList = errorTable[error.name];
        +      if (!errList) return; // this type of error can't be handled yet
        +      let matchedError;
        +      for (const obj of errList) {
        +        let string = obj.msg;
        +        // capture the primary symbol mentioned in the error
        +        string = string.replace(new RegExp('{{}}', 'g'), '([a-zA-Z0-9_]+)');
        +        string = string.replace(new RegExp('{{.}}', 'g'), '(.+)');
        +        string = string.replace(new RegExp('{}', 'g'), '(?:[a-zA-Z0-9_]+)');
        +        let matched = error.message.match(string);
        +
        +        if (matched) {
        +          matchedError = Object.assign({}, obj);
        +          matchedError.match = matched;
                   break;
                 }
        -        break;
               }
        -    }
        -
        -    // in some cases ( errors in promises, callbacks, etc), no entry-point
        -    // function may be found in the stacktrace. In that case just use the
        -    // entire stacktrace for friendlyStack
        -    if (!friendlyStack) friendlyStack = stacktrace;
        -
        -    if (isInternal) {
        -      // the frameIndex property is added before the filter, so frameIndex
        -      // corresponds to the index of a frame in the original stacktrace.
        -      // Then we filter out all frames which belong to the file that contains
        -      // the p5 library
        -      friendlyStack = friendlyStack
        -        .map((frame, index) => {
        -          frame.frameIndex = index;
        -          return frame;
        -        })
        -        .filter(frame => frame.fileName !== p5FileName);
         
        -      // a weird case, if for some reason we can't identify the function called
        -      // from user's code
        -      if (friendlyStack.length === 0) return [true, null];
        +      if (!matchedError) return;
         
        -      // get the function just above the topmost frame in the friendlyStack.
        -      // i.e the name of the library function called from user's code
        -      const func = stacktrace[friendlyStack[0].frameIndex - 1].functionName
        -        .split('.')
        -        .slice(-1)[0];
        -
        -      // Try and get the location (line no.) from the top element of the stack
        +      // Try and get the location from the top element of the stack
               let locationObj;
               if (
        -        friendlyStack[0].fileName &&
        -        friendlyStack[0].lineNumber &&
        -        friendlyStack[0].columnNumber
        +        stacktrace &&
        +        stacktrace[0].fileName &&
        +        stacktrace[0].lineNumber &&
        +        stacktrace[0].columnNumber
               ) {
                 locationObj = {
        -          location: `${friendlyStack[0].fileName}:${
        -            friendlyStack[0].lineNumber
        -          }:${friendlyStack[0].columnNumber}`,
        -          file: friendlyStack[0].fileName.split('/').slice(-1),
        +          location: `${stacktrace[0].fileName}:${stacktrace[0].lineNumber}:${
        +            stacktrace[0].columnNumber
        +          }`,
        +          file: stacktrace[0].fileName.split('/').slice(-1),
                   line: friendlyStack[0].lineNumber
                 };
        -
        -        // if already handled by another part of the FES, don't handle again
        -        if (p5._fesLogCache[locationObj.location]) return [true, null];
        -      }
        -
        -      // Check if the error is due to a non loadX method being used incorrectly
        -      // in preload
        -      if (
        -        currentEntryPoint === 'preload' &&
        -        p5.prototype._preloadMethods[func] == null
        -      ) {
        -        p5._friendlyError(
        -          translator('fes.wrongPreload', {
        -            func,
        -            location: locationObj
        -              ? translator('fes.location', locationObj)
        -              : '',
        -            error: error.message
        -          }),
        -          'preload'
        -        );
        -      } else {
        -        // Library error
        -        p5._friendlyError(
        -          translator('fes.libraryError', {
        -            func,
        -            location: locationObj
        -              ? translator('fes.location', locationObj)
        -              : '',
        -            error: error.message
        -          }),
        -          func
        -        );
        -      }
        -
        -      // Finally, if it's an internal error, print the friendlyStack
        -      // ( fesErrorMonitor won't handle this error )
        -      if (friendlyStack && friendlyStack.length) {
        -        printFriendlyStack(friendlyStack);
        -      }
        -    }
        -    return [isInternal, friendlyStack];
        -  };
        -
        -  /**
        -   * Handles "global" errors that the browser catches.
        -   *
        -   * Called when an error event happens and detects the type of error.
        -   *
        -   * Generates and prints a friendly error message using key:
        -   * "fes.globalErrors.syntax.[*]", "fes.globalErrors.reference.[*]",
        -   * "fes.globalErrors.type.[*]".
        -   *
        -   * @method fesErrorMonitor
        -   * @private
        -   * @param {*} e  Event object to extract error details from
        -   */
        -  const fesErrorMonitor = e => {
        -    if (p5.disableFriendlyErrors) return;
        -    // Try to get the error object from e
        -    let error;
        -    if (e instanceof Error) {
        -      error = e;
        -    } else if (e instanceof ErrorEvent) {
        -      error = e.error;
        -    } else if (e instanceof PromiseRejectionEvent) {
        -      error = e.reason;
        -      if (!(error instanceof Error)) return;
        -    }
        -    if (!error) return;
        -
        -    let stacktrace = p5._getErrorStackParser().parse(error);
        -    // process the stacktrace from the browser and simplify it to give
        -    // friendlyStack.
        -    let [isInternal, friendlyStack] = processStack(error, stacktrace);
        -
        -    // if this is an internal library error, the type of the error is not relevant,
        -    // only the user code that lead to it is.
        -    if (isInternal) {
        -      return;
        -    }
        -
        -    const errList = errorTable[error.name];
        -    if (!errList) return; // this type of error can't be handled yet
        -    let matchedError;
        -    for (const obj of errList) {
        -      let string = obj.msg;
        -      // capture the primary symbol mentioned in the error
        -      string = string.replace(new RegExp('{{}}', 'g'), '([a-zA-Z0-9_]+)');
        -      string = string.replace(new RegExp('{{.}}', 'g'), '(.+)');
        -      string = string.replace(new RegExp('{}', 'g'), '(?:[a-zA-Z0-9_]+)');
        -      let matched = error.message.match(string);
        -
        -      if (matched) {
        -        matchedError = Object.assign({}, obj);
        -        matchedError.match = matched;
        -        break;
               }
        -    }
         
        -    if (!matchedError) return;
        -
        -    // Try and get the location from the top element of the stack
        -    let locationObj;
        -    if (
        -      stacktrace &&
        -      stacktrace[0].fileName &&
        -      stacktrace[0].lineNumber &&
        -      stacktrace[0].columnNumber
        -    ) {
        -      locationObj = {
        -        location: `${stacktrace[0].fileName}:${stacktrace[0].lineNumber}:${
        -          stacktrace[0].columnNumber
        -        }`,
        -        file: stacktrace[0].fileName.split('/').slice(-1),
        -        line: friendlyStack[0].lineNumber
        -      };
        -    }
        -
        -    switch (error.name) {
        -      case 'SyntaxError': {
        -        // We can't really do much with syntax errors other than try to use
        -        // a simpler framing of the error message. The stack isn't available
        -        // for syntax errors
        -        switch (matchedError.type) {
        -          case 'INVALIDTOKEN': {
        -            //Error if there is an invalid or unexpected token that doesn't belong at this position in the code
        -            //let x = “not a string”; -> string not in proper quotes
        -            let url =
        -              'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Illegal_character#What_went_wrong';
        -            p5._friendlyError(
        -              translator('fes.globalErrors.syntax.invalidToken', {
        -                url
        -              })
        -            );
        -            break;
        -          }
        -          case 'UNEXPECTEDTOKEN': {
        -            //Error if a specific language construct(, { ; etc) was expected, but something else was provided
        -            //for (let i = 0; i < 5,; ++i) -> a comma after i<5 instead of a semicolon
        -            let url =
        -              'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Unexpected_token#What_went_wrong';
        -            p5._friendlyError(
        -              translator('fes.globalErrors.syntax.unexpectedToken', {
        -                url
        -              })
        -            );
        -            break;
        -          }
        -          case 'REDECLAREDVARIABLE': {
        -            //Error if a variable is redeclared by the user. Example=>
        -            //let a = 10;
        -            //let a = 100;
        -            let errSym = matchedError.match[1];
        -            let url =
        -              'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Redeclared_parameter#what_went_wrong';
        -            p5._friendlyError(
        -              translator('fes.globalErrors.syntax.redeclaredVariable', {
        -                symbol: errSym,
        -                url
        -              })
        -            );
        -            break;
        -          }
        -          case 'MISSINGINITIALIZER': {
        -            //Error if a const variable is not initialized during declaration
        -            //Example => const a;
        -            let url =
        -              'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Missing_initializer_in_const#what_went_wrong';
        -            p5._friendlyError(
        -              translator('fes.globalErrors.syntax.missingInitializer', {
        -                url
        -              })
        -            );
        -            break;
        -          }
        -          case 'BADRETURNORYIELD': {
        -            //Error when a return statement is misplaced(usually outside of a function)
        -            // const a = function(){
        -            //  .....
        -            //  }
        -            //  return; -> misplaced return statement
        -            let url =
        -              'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Bad_return_or_yield#what_went_wrong';
        -            p5._friendlyError(
        -              translator('fes.globalErrors.syntax.badReturnOrYield', {
        -                url
        -              })
        -            );
        -            break;
        +      switch (error.name) {
        +        case 'SyntaxError': {
        +          // We can't really do much with syntax errors other than try to use
        +          // a simpler framing of the error message. The stack isn't available
        +          // for syntax errors
        +          switch (matchedError.type) {
        +            case 'INVALIDTOKEN': {
        +              //Error if there is an invalid or unexpected token that doesn't belong at this position in the code
        +              //let x = “not a string”; -> string not in proper quotes
        +              let url =
        +                'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Illegal_character#What_went_wrong';
        +              p5._friendlyError(
        +                translator('fes.globalErrors.syntax.invalidToken', {
        +                  url
        +                })
        +              );
        +              break;
        +            }
        +            case 'UNEXPECTEDTOKEN': {
        +              //Error if a specific language construct(, { ; etc) was expected, but something else was provided
        +              //for (let i = 0; i < 5,; ++i) -> a comma after i<5 instead of a semicolon
        +              let url =
        +                'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Unexpected_token#What_went_wrong';
        +              p5._friendlyError(
        +                translator('fes.globalErrors.syntax.unexpectedToken', {
        +                  url
        +                })
        +              );
        +              break;
        +            }
        +            case 'REDECLAREDVARIABLE': {
        +              //Error if a variable is redeclared by the user. Example=>
        +              //let a = 10;
        +              //let a = 100;
        +              let errSym = matchedError.match[1];
        +              let url =
        +                'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Redeclared_parameter#what_went_wrong';
        +              p5._friendlyError(
        +                translator('fes.globalErrors.syntax.redeclaredVariable', {
        +                  symbol: errSym,
        +                  url
        +                })
        +              );
        +              break;
        +            }
        +            case 'MISSINGINITIALIZER': {
        +              //Error if a const variable is not initialized during declaration
        +              //Example => const a;
        +              let url =
        +                'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Missing_initializer_in_const#what_went_wrong';
        +              p5._friendlyError(
        +                translator('fes.globalErrors.syntax.missingInitializer', {
        +                  url
        +                })
        +              );
        +              break;
        +            }
        +            case 'BADRETURNORYIELD': {
        +              //Error when a return statement is misplaced(usually outside of a function)
        +              // const a = function(){
        +              //  .....
        +              //  }
        +              //  return; -> misplaced return statement
        +              let url =
        +                'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Bad_return_or_yield#what_went_wrong';
        +              p5._friendlyError(
        +                translator('fes.globalErrors.syntax.badReturnOrYield', {
        +                  url
        +                })
        +              );
        +              break;
        +            }
                   }
        +          break;
                 }
        -        break;
        -      }
        -      case 'ReferenceError': {
        -        switch (matchedError.type) {
        -          case 'NOTDEFINED': {
        -            //Error if there is a non-existent variable referenced somewhere
        -            //let a = 10;
        -            //console.log(x);
        -            let errSym = matchedError.match[1];
        -
        -            if (errSym && handleMisspelling(errSym, error)) {
        +        case 'ReferenceError': {
        +          switch (matchedError.type) {
        +            case 'NOTDEFINED': {
        +              //Error if there is a non-existent variable referenced somewhere
        +              //let a = 10;
        +              //console.log(x);
        +              let errSym = matchedError.match[1];
        +
        +              if (errSym && handleMisspelling(errSym, error)) {
        +                break;
        +              }
        +
        +              // if the flow gets this far, this is likely not a misspelling
        +              // of a p5 property/function
        +              let url = 'https://p5js.org/examples/data-variable-scope.html';
        +              p5._friendlyError(
        +                translator('fes.globalErrors.reference.notDefined', {
        +                  url,
        +                  symbol: errSym,
        +                  location: locationObj
        +                    ? translator('fes.location', locationObj)
        +                    : ''
        +                })
        +              );
        +
        +              if (friendlyStack) printFriendlyStack(friendlyStack);
                       break;
                     }
        +            case 'CANNOTACCESS': {
        +              //Error if a lexical variable was accessed before it was initialized
        +              //console.log(a); -> variable accessed before it was initialized
        +              //let a=100;
        +              let errSym = matchedError.match[1];
        +              let url =
        +                'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_lexical_declaration_before_init#what_went_wrong';
        +              p5._friendlyError(
        +                translator('fes.globalErrors.reference.cannotAccess', {
        +                  url,
        +                  symbol: errSym,
        +                  location: locationObj
        +                    ? translator('fes.location', locationObj)
        +                    : ''
        +                })
        +              );
         
        -            // if the flow gets this far, this is likely not a misspelling
        -            // of a p5 property/function
        -            let url = 'https://p5js.org/tutorials/variables-and-change/';
        -            p5._friendlyError(
        -              translator('fes.globalErrors.reference.notDefined', {
        -                url,
        -                symbol: errSym,
        -                location: locationObj
        -                  ? translator('fes.location', locationObj)
        -                  : ''
        -              })
        -            );
        -
        -            if (friendlyStack) printFriendlyStack(friendlyStack);
        -            break;
        +              if (friendlyStack) printFriendlyStack(friendlyStack);
        +              break;
        +            }
                   }
        -          case 'CANNOTACCESS': {
        -            //Error if a lexical variable was accessed before it was initialized
        -            //console.log(a); -> variable accessed before it was initialized
        -            //let a=100;
        -            let errSym = matchedError.match[1];
        -            let url =
        -              'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_lexical_declaration_before_init#what_went_wrong';
        -            p5._friendlyError(
        -              translator('fes.globalErrors.reference.cannotAccess', {
        +          break;
        +        }
        +
        +        case 'TypeError': {
        +          switch (matchedError.type) {
        +            case 'NOTFUNC': {
        +              //Error when some code expects you to provide a function, but that didn't happen
        +              //let a = document.getElementByID('foo'); -> getElementById instead of getElementByID
        +              let errSym = matchedError.match[1];
        +              let splitSym = errSym.split('.');
        +              let url =
        +                'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_a_function#What_went_wrong';
        +
        +              // if errSym is aa.bb.cc , symbol would be cc and obj would aa.bb
        +              let translationObj = {
                         url,
        -                symbol: errSym,
        +                symbol: splitSym[splitSym.length - 1],
        +                obj: splitSym.slice(0, splitSym.length - 1).join('.'),
                         location: locationObj
                           ? translator('fes.location', locationObj)
                           : ''
        -              })
        -            );
        -
        -            if (friendlyStack) printFriendlyStack(friendlyStack);
        -            break;
        -          }
        -        }
        -        break;
        -      }
        -
        -      case 'TypeError': {
        -        switch (matchedError.type) {
        -          case 'NOTFUNC': {
        -            //Error when some code expects you to provide a function, but that didn't happen
        -            //let a = document.getElementByID('foo'); -> getElementById instead of getElementByID
        -            let errSym = matchedError.match[1];
        -            let splitSym = errSym.split('.');
        -            let url =
        -              'https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_a_function#What_went_wrong';
        -
        -            // if errSym is aa.bb.cc , symbol would be cc and obj would aa.bb
        -            let translationObj = {
        -              url,
        -              symbol: splitSym[splitSym.length - 1],
        -              obj: splitSym.slice(0, splitSym.length - 1).join('.'),
        -              location: locationObj
        -                ? translator('fes.location', locationObj)
        -                : ''
        -            };
        -
        -            // There are two cases to handle here. When the function is called
        -            // as a property of an object and when it's called independently.
        -            // Both have different explanations.
        -            if (splitSym.length > 1) {
        +              };
        +
        +              // There are two cases to handle here. When the function is called
        +              // as a property of an object and when it's called independently.
        +              // Both have different explanations.
        +              if (splitSym.length > 1) {
        +                p5._friendlyError(
        +                  translator('fes.globalErrors.type.notfuncObj', translationObj)
        +                );
        +              } else {
        +                p5._friendlyError(
        +                  translator('fes.globalErrors.type.notfunc', translationObj)
        +                );
        +              }
        +
        +              if (friendlyStack) printFriendlyStack(friendlyStack);
        +              break;
        +            }
        +            case 'READNULL': {
        +              //Error if a property of null is accessed
        +              //let a = null;
        +              //console.log(a.property); -> a is null
        +              let errSym = matchedError.match[1];
        +              let url =
        +                'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_property#what_went_wrong';
        +              /*let url2 =
        +                'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null';*/
                       p5._friendlyError(
        -                translator('fes.globalErrors.type.notfuncObj', translationObj)
        +                translator('fes.globalErrors.type.readFromNull', {
        +                  url,
        +                  symbol: errSym,
        +                  location: locationObj
        +                    ? translator('fes.location', locationObj)
        +                    : ''
        +                })
                       );
        -            } else {
        +
        +              if (friendlyStack) printFriendlyStack(friendlyStack);
        +              break;
        +            }
        +            case 'READUDEFINED': {
        +              //Error if a property of undefined is accessed
        +              //let a; -> default value of a is undefined
        +              //console.log(a.property); -> a is undefined
        +              let errSym = matchedError.match[1];
        +              let url =
        +                'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_property#what_went_wrong';
        +              /*let url2 =
        +                'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined#description';*/
                       p5._friendlyError(
        -                translator('fes.globalErrors.type.notfunc', translationObj)
        +                translator('fes.globalErrors.type.readFromUndefined', {
        +                  url,
        +                  symbol: errSym,
        +                  location: locationObj
        +                    ? translator('fes.location', locationObj)
        +                    : ''
        +                })
                       );
        +
        +              if (friendlyStack) printFriendlyStack(friendlyStack);
        +              break;
                     }
        +            case 'CONSTASSIGN': {
        +              //Error when a const variable is reassigned a value
        +              //const a = 100;
        +              //a=10;
        +              let url =
        +                'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_const_assignment#what_went_wrong';
        +              p5._friendlyError(
        +                translator('fes.globalErrors.type.constAssign', {
        +                  url,
        +                  location: locationObj
        +                    ? translator('fes.location', locationObj)
        +                    : ''
        +                })
        +              );
         
        -            if (friendlyStack) printFriendlyStack(friendlyStack);
        -            break;
        +              if (friendlyStack) printFriendlyStack(friendlyStack);
        +              break;
        +            }
                   }
        -          case 'READNULL': {
        -            //Error if a property of null is accessed
        -            //let a = null;
        -            //console.log(a.property); -> a is null
        -            let errSym = matchedError.match[1];
        -            let url =
        -              'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_property#what_went_wrong';
        -            /*let url2 =
        -              'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null';*/
        -            p5._friendlyError(
        -              translator('fes.globalErrors.type.readFromNull', {
        -                url,
        -                symbol: errSym,
        -                location: locationObj
        -                  ? translator('fes.location', locationObj)
        -                  : ''
        -              })
        -            );
        +        }
        +      }
        +    };
        +
        +    p5._fesErrorMonitor = fesErrorMonitor;
        +    p5._checkForUserDefinedFunctions = checkForUserDefinedFunctions;
        +
        +    // logger for testing purposes.
        +    p5._fesLogger = null;
        +    p5._fesLogCache = {};
        +
        +    window.addEventListener('load', checkForUserDefinedFunctions, false);
        +    window.addEventListener('error', p5._fesErrorMonitor, false);
        +    window.addEventListener('unhandledrejection', p5._fesErrorMonitor, false);
        +
        +    /**
        +     * Prints out all the colors in the color pallete with white text.
        +     * For color blindness testing.
        +     */
        +    /* function testColors() {
        +      const str = 'A box of biscuits, a box of mixed biscuits and a biscuit mixer';
        +      p5._friendlyError(str, 'print', '#ED225D'); // p5.js magenta
        +      p5._friendlyError(str, 'print', '#2D7BB6'); // p5.js blue
        +      p5._friendlyError(str, 'print', '#EE9900'); // p5.js orange
        +      p5._friendlyError(str, 'print', '#A67F59'); // p5.js light brown
        +      p5._friendlyError(str, 'print', '#704F21'); // p5.js gold
        +      p5._friendlyError(str, 'print', '#1CC581'); // auto cyan
        +      p5._friendlyError(str, 'print', '#FF6625'); // auto orange
        +      p5._friendlyError(str, 'print', '#79EB22'); // auto green
        +      p5._friendlyError(str, 'print', '#B40033'); // p5.js darkened magenta
        +      p5._friendlyError(str, 'print', '#084B7F'); // p5.js darkened blue
        +      p5._friendlyError(str, 'print', '#945F00'); // p5.js darkened orange
        +      p5._friendlyError(str, 'print', '#6B441D'); // p5.js darkened brown
        +      p5._friendlyError(str, 'print', '#2E1B00'); // p5.js darkened gold
        +      p5._friendlyError(str, 'print', '#008851'); // auto dark cyan
        +      p5._friendlyError(str, 'print', '#C83C00'); // auto dark orange
        +      p5._friendlyError(str, 'print', '#4DB200'); // auto dark green
        +    } */
        +  }
         
        -            if (friendlyStack) printFriendlyStack(friendlyStack);
        -            break;
        -          }
        -          case 'READUDEFINED': {
        -            //Error if a property of undefined is accessed
        -            //let a; -> default value of a is undefined
        -            //console.log(a.property); -> a is undefined
        -            let errSym = matchedError.match[1];
        -            let url =
        -              'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_property#what_went_wrong';
        -            /*let url2 =
        -              'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined#description';*/
        -            p5._friendlyError(
        -              translator('fes.globalErrors.type.readFromUndefined', {
        -                url,
        -                symbol: errSym,
        -                location: locationObj
        -                  ? translator('fes.location', locationObj)
        -                  : ''
        -              })
        -            );
        +  // This is a lazily-defined list of p5 symbols that may be
        +  // misused by beginners at top-level code, outside of setup/draw. We'd like
        +  // to detect these errors and help the user by suggesting they move them
        +  // into setup/draw.
        +  //
        +  // For more details, see https://github.com/processing/p5.js/issues/1121.
        +  misusedAtTopLevelCode = null;
        +  const FAQ_URL =
        +    'https://github.com/processing/p5.js/wiki/p5.js-overview#why-cant-i-assign-variables-using-p5-functions-and-variables-before-setup';
         
        -            if (friendlyStack) printFriendlyStack(friendlyStack);
        -            break;
        +  /**
        +   * A helper function for populating misusedAtTopLevel list.
        +   *
        +   * @method defineMisusedAtTopLevelCode
        +   * @private
        +   */
        +  defineMisusedAtTopLevelCode = () => {
        +    const uniqueNamesFound = {};
        +
        +    const getSymbols = obj =>
        +      Object.getOwnPropertyNames(obj)
        +        .filter(name => {
        +          if (name[0] === '_') {
        +            return false;
        +          }
        +          if (name in uniqueNamesFound) {
        +            return false;
                   }
        -          case 'CONSTASSIGN': {
        -            //Error when a const variable is reassigned a value
        -            //const a = 100;
        -            //a=10;
        -            let url =
        -              'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_const_assignment#what_went_wrong';
        -            p5._friendlyError(
        -              translator('fes.globalErrors.type.constAssign', {
        -                url,
        -                location: locationObj
        -                  ? translator('fes.location', locationObj)
        -                  : ''
        -              })
        -            );
         
        -            if (friendlyStack) printFriendlyStack(friendlyStack);
        -            break;
        +          uniqueNamesFound[name] = true;
        +
        +          return true;
        +        })
        +        .map(name => {
        +          let type;
        +
        +          if (typeof obj[name] === 'function') {
        +            type = 'function';
        +          } else if (name === name.toUpperCase()) {
        +            type = 'constant';
        +          } else {
        +            type = 'variable';
                   }
        -        }
        -      }
        -    }
        -  };
         
        -  p5._fesErrorMonitor = fesErrorMonitor;
        -  p5._checkForUserDefinedFunctions = checkForUserDefinedFunctions;
        +          return { name, type };
        +        });
         
        -  // logger for testing purposes.
        -  p5._fesLogger = null;
        -  p5._fesLogCache = {};
        +    misusedAtTopLevelCode = [].concat(
        +      getSymbols(fn),
        +      // At present, p5 only adds its constants to fn during
        +      // construction, which may not have happened at the time a
        +      // ReferenceError is thrown, so we'll manually add them to our list.
        +      getSymbols(contants)
        +    );
         
        -  window.addEventListener('load', checkForUserDefinedFunctions, false);
        -  window.addEventListener('error', p5._fesErrorMonitor, false);
        -  window.addEventListener('unhandledrejection', p5._fesErrorMonitor, false);
        +    // This will ultimately ensure that we report the most specific error
        +    // possible to the user, e.g. advising them about HALF_PI instead of PI
        +    // when their code misuses the former.
        +    misusedAtTopLevelCode.sort((a, b) => b.name.length - a.name.length);
        +  };
         
           /**
        -   * Prints out all the colors in the color pallete with white text.
        -   * For color blindness testing.
        +   * Detects browser level error event for p5 constants/functions used outside
        +   * of setup() and draw().
        +   *
        +   * Generates and prints a friendly error message using key:
        +   * "fes.misusedTopLevel".
        +   *
        +   * @method helpForMisusedAtTopLevelCode
        +   * @private
        +   * @param {Event} e       Error event
        +   * @param {Boolean} log   false
        +   *
        +   * @returns {Boolean} true
            */
        -  /* function testColors() {
        -    const str = 'A box of biscuits, a box of mixed biscuits and a biscuit mixer';
        -    p5._friendlyError(str, 'print', '#ED225D'); // p5.js magenta
        -    p5._friendlyError(str, 'print', '#2D7BB6'); // p5.js blue
        -    p5._friendlyError(str, 'print', '#EE9900'); // p5.js orange
        -    p5._friendlyError(str, 'print', '#A67F59'); // p5.js light brown
        -    p5._friendlyError(str, 'print', '#704F21'); // p5.js gold
        -    p5._friendlyError(str, 'print', '#1CC581'); // auto cyan
        -    p5._friendlyError(str, 'print', '#FF6625'); // auto orange
        -    p5._friendlyError(str, 'print', '#79EB22'); // auto green
        -    p5._friendlyError(str, 'print', '#B40033'); // p5.js darkened magenta
        -    p5._friendlyError(str, 'print', '#084B7F'); // p5.js darkened blue
        -    p5._friendlyError(str, 'print', '#945F00'); // p5.js darkened orange
        -    p5._friendlyError(str, 'print', '#6B441D'); // p5.js darkened brown
        -    p5._friendlyError(str, 'print', '#2E1B00'); // p5.js darkened gold
        -    p5._friendlyError(str, 'print', '#008851'); // auto dark cyan
        -    p5._friendlyError(str, 'print', '#C83C00'); // auto dark orange
        -    p5._friendlyError(str, 'print', '#4DB200'); // auto dark green
        -  } */
        -}
        -
        -// This is a lazily-defined list of p5 symbols that may be
        -// misused by beginners at top-level code, outside of setup/draw. We'd like
        -// to detect these errors and help the user by suggesting they move them
        -// into setup/draw.
        -//
        -// For more details, see https://github.com/processing/p5.js/issues/1121.
        -misusedAtTopLevelCode = null;
        -const FAQ_URL =
        -  'https://github.com/processing/p5.js/wiki/p5.js-overview#why-cant-i-assign-variables-using-p5-functions-and-variables-before-setup';
        -
        -/**
        - * A helper function for populating misusedAtTopLevel list.
        - *
        - * @method defineMisusedAtTopLevelCode
        - * @private
        - */
        -defineMisusedAtTopLevelCode = () => {
        -  const uniqueNamesFound = {};
        -
        -  const getSymbols = obj =>
        -    Object.getOwnPropertyNames(obj)
        -      .filter(name => {
        -        if (name[0] === '_') {
        -          return false;
        -        }
        -        if (name in uniqueNamesFound) {
        -          return false;
        -        }
        +  const helpForMisusedAtTopLevelCode = (e, log) => {
        +    if (!log) {
        +      log = console.log.bind(console);
        +    }
         
        -        uniqueNamesFound[name] = true;
        +    if (!misusedAtTopLevelCode) {
        +      defineMisusedAtTopLevelCode();
        +    }
         
        -        return true;
        -      })
        -      .map(name => {
        -        let type;
        -
        -        if (typeof obj[name] === 'function') {
        -          type = 'function';
        -        } else if (name === name.toUpperCase()) {
        -          type = 'constant';
        +    // If we find that we're logging lots of false positives, we can
        +    // uncomment the following code to avoid displaying anything if the
        +    // user's code isn't likely to be using p5's global mode. (Note that
        +    // setup/draw are more likely to be defined due to JS function hoisting.)
        +    //
        +    //if (!('setup' in window || 'draw' in window)) {
        +    //  return;
        +    //}
        +
        +    misusedAtTopLevelCode.some(symbol => {
        +      // Note that while just checking for the occurrence of the
        +      // symbol name in the error message could result in false positives,
        +      // a more rigorous test is difficult because different browsers
        +      // log different messages, and the format of those messages may
        +      // change over time.
        +      //
        +      // For example, if the user uses 'PI' in their code, it may result
        +      // in any one of the following messages:
        +      //
        +      //   * 'PI' is undefined                           (Microsoft Edge)
        +      //   * ReferenceError: PI is undefined             (Firefox)
        +      //   * Uncaught ReferenceError: PI is not defined  (Chrome)
        +
        +      if (e.message && e.message.match(`\\W?${symbol.name}\\W`) !== null) {
        +        const symbolName =
        +          symbol.type === 'function' ? `${symbol.name}()` : symbol.name;
        +        if (typeof IS_MINIFIED !== 'undefined') {
        +          log(
        +            `Did you just try to use p5.js's ${symbolName} ${
        +              symbol.type
        +            }? If so, you may want to move it into your sketch's setup() function.\n\nFor more details, see: ${FAQ_URL}`
        +          );
                 } else {
        -          type = 'variable';
        +          log(
        +            translator('fes.misusedTopLevel', {
        +              symbolName,
        +              symbolType: symbol.type,
        +              url: FAQ_URL
        +            })
        +          );
                 }
        +        return true;
        +      }
        +    });
        +  };
         
        -        return { name, type };
        -      });
        -
        -  misusedAtTopLevelCode = [].concat(
        -    getSymbols(p5.prototype),
        -    // At present, p5 only adds its constants to p5.prototype during
        -    // construction, which may not have happened at the time a
        -    // ReferenceError is thrown, so we'll manually add them to our list.
        -    getSymbols(require('../constants'))
        -  );
        +  // Exposing this primarily for unit testing.
        +  fn._helpForMisusedAtTopLevelCode = helpForMisusedAtTopLevelCode;
         
        -  // This will ultimately ensure that we report the most specific error
        -  // possible to the user, e.g. advising them about HALF_PI instead of PI
        -  // when their code misuses the former.
        -  misusedAtTopLevelCode.sort((a, b) => b.name.length - a.name.length);
        -};
        +  if (document.readyState !== 'complete') {
        +    window.addEventListener('error', helpForMisusedAtTopLevelCode, false);
         
        -/**
        - * Detects browser level error event for p5 constants/functions used outside
        - * of setup() and draw().
        - *
        - * Generates and prints a friendly error message using key:
        - * "fes.misusedTopLevel".
        - *
        - * @method helpForMisusedAtTopLevelCode
        - * @private
        - * @param {Event} e       Error event
        - * @param {Boolean} log   false
        - *
        - * @returns {Boolean} true
        - */
        -const helpForMisusedAtTopLevelCode = (e, log) => {
        -  if (!log) {
        -    log = console.log.bind(console);
        +    // Our job is only to catch ReferenceErrors that are thrown when
        +    // global (non-instance mode) p5 APIs are used at the top-level
        +    // scope of a file, so we'll unbind our error listener now to make
        +    // sure we don't log false positives later.
        +    window.addEventListener('load', () => {
        +      window.removeEventListener('error', helpForMisusedAtTopLevelCode, false);
        +    });
           }
        +}
         
        -  if (!misusedAtTopLevelCode) {
        -    defineMisusedAtTopLevelCode();
        -  }
        +export default fesCore;
         
        -  // If we find that we're logging lots of false positives, we can
        -  // uncomment the following code to avoid displaying anything if the
        -  // user's code isn't likely to be using p5's global mode. (Note that
        -  // setup/draw are more likely to be defined due to JS function hoisting.)
        -  //
        -  //if (!('setup' in window || 'draw' in window)) {
        -  //  return;
        -  //}
        -
        -  misusedAtTopLevelCode.some(symbol => {
        -    // Note that while just checking for the occurrence of the
        -    // symbol name in the error message could result in false positives,
        -    // a more rigorous test is difficult because different browsers
        -    // log different messages, and the format of those messages may
        -    // change over time.
        -    //
        -    // For example, if the user uses 'PI' in their code, it may result
        -    // in any one of the following messages:
        -    //
        -    //   * 'PI' is undefined                           (Microsoft Edge)
        -    //   * ReferenceError: PI is undefined             (Firefox)
        -    //   * Uncaught ReferenceError: PI is not defined  (Chrome)
        -
        -    if (e.message && e.message.match(`\\W?${symbol.name}\\W`) !== null) {
        -      const symbolName =
        -        symbol.type === 'function' ? `${symbol.name}()` : symbol.name;
        -      if (typeof IS_MINIFIED !== 'undefined') {
        -        log(
        -          `Did you just try to use p5.js's ${symbolName} ${
        -            symbol.type
        -          }? If so, you may want to move it into your sketch's setup() function.\n\nFor more details, see: ${FAQ_URL}`
        -        );
        -      } else {
        -        log(
        -          translator('fes.misusedTopLevel', {
        -            symbolName,
        -            symbolType: symbol.type,
        -            url: FAQ_URL
        -          })
        -        );
        -      }
        -      return true;
        -    }
        -  });
        -};
        -
        -// Exposing this primarily for unit testing.
        -p5.prototype._helpForMisusedAtTopLevelCode = helpForMisusedAtTopLevelCode;
        -
        -if (document.readyState !== 'complete') {
        -  window.addEventListener('error', helpForMisusedAtTopLevelCode, false);
        -
        -  // Our job is only to catch ReferenceErrors that are thrown when
        -  // global (non-instance mode) p5 APIs are used at the top-level
        -  // scope of a file, so we'll unbind our error listener now to make
        -  // sure we don't log false positives later.
        -  window.addEventListener('load', () => {
        -    window.removeEventListener('error', helpForMisusedAtTopLevelCode, false);
        -  });
        +if (typeof p5 !== 'undefined') {
        +  fesCore(p5, p5.prototype);
         }
        -
        -export default p5;
        diff --git a/src/core/friendly_errors/file_errors.js b/src/core/friendly_errors/file_errors.js
        index 677d7a61b9..8f212c8355 100644
        --- a/src/core/friendly_errors/file_errors.js
        +++ b/src/core/friendly_errors/file_errors.js
        @@ -2,12 +2,9 @@
          * @for p5
          * @requires core
          */
        -import p5 from '../main';
         import { translator } from '../internationalization';
         
        -if (typeof IS_MINIFIED !== 'undefined') {
        -  p5._friendlyFileLoadError = () => {};
        -} else {
        +function fileErrors(p5, fn){
           // mapping used by `_friendlyFileLoadError`
           const fileLoadErrorCases = (num, filePath) => {
             const suggestion = translator('fes.fileLoadError.suggestion', {
        @@ -76,6 +73,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
                 };
             }
           };
        +
           /**
            * Called internally if there is an error during file loading.
            *
        @@ -93,4 +91,8 @@ if (typeof IS_MINIFIED !== 'undefined') {
           };
         }
         
        -export default p5;
        +export default fileErrors;
        +
        +if (typeof p5 !== 'undefined') {
        +  fileErrors(p5, p5.prototype);
        +}
        diff --git a/src/core/friendly_errors/index.js b/src/core/friendly_errors/index.js
        new file mode 100644
        index 0000000000..d69a3c604e
        --- /dev/null
        +++ b/src/core/friendly_errors/index.js
        @@ -0,0 +1,13 @@
        +import fesCore from './fes_core';
        +import stacktrace from './stacktrace';
        +import validateParams from './param_validator.js';
        +import sketchVerifier from './sketch_verifier.js';
        +import fileErrors from './file_errors';
        +
        +export default function (p5) {
        +  p5.registerAddon(fesCore);
        +  p5.registerAddon(stacktrace);
        +  p5.registerAddon(validateParams);
        +  p5.registerAddon(sketchVerifier);
        +  p5.registerAddon(fileErrors);
        +}
        diff --git a/src/core/friendly_errors/param_validator.js b/src/core/friendly_errors/param_validator.js
        new file mode 100644
        index 0000000000..7768c90f85
        --- /dev/null
        +++ b/src/core/friendly_errors/param_validator.js
        @@ -0,0 +1,535 @@
        +/**
        + * @for p5
        + * @requires core
        + */
        +import * as constants from '../constants.js';
        +import * as z from 'zod';
        +import dataDoc from '../../../docs/parameterData.json';
        +
        +function validateParams(p5, fn, lifecycles) {
        +  // Cache for Zod schemas
        +  let schemaRegistry = new Map();
        +
        +  // Mapping names of p5 types to their constructor functions.
        +  // p5Constructors:
        +  //   - Color: f()
        +  //   - Graphics: f()
        +  //   - Vector: f()
        +  // and so on.
        +  // const p5Constructors = {};
        +  // NOTE: This is a tempt fix for unit test but is not correct
        +  // Attaced constructors are `undefined`
        +  const p5Constructors = Object.keys(p5).reduce((acc, val) => {
        +    if (
        +      val.match(/^[A-Z]/) && // Starts with a capital
        +      !val.match(/^[A-Z][A-Z0-9]*$/) && // Is not an all caps constant
        +      p5[val] instanceof Function // Is a function
        +    ) {
        +      acc[val] = p5[val];
        +    }
        +    return acc;
        +  }, {});
        +
        +  function loadP5Constructors() {
        +    // Make a list of all p5 classes to be used for argument validation
        +    // This must be done only when everything has loaded otherwise we get
        +    // an empty array
        +    for (let key of Object.keys(p5)) {
        +      // Get a list of all constructors in p5. They are functions whose names
        +      // start with a capital letter
        +      if (typeof p5[key] === 'function' && key[0] !== key[0].toLowerCase()) {
        +        p5Constructors[key] = p5[key];
        +      }
        +    }
        +  }
        +
        +  // `constantsMap` maps constants to their values, e.g.
        +  // {
        +  //   ADD: 'lighter',
        +  //   ALT: 18,
        +  //   ARROW: 'default',
        +  //   AUTO: 'auto',
        +  //   ...
        +  // }
        +  const constantsMap = {};
        +  for (const [key, value] of Object.entries(constants)) {
        +    constantsMap[key] = value;
        +  }
        +
        +  // Start initializing `schemaMap` with primitive types. `schemaMap` will
        +  // eventually contain both primitive types and web API objects.
        +  const schemaMap = {
        +    'Any': z.any(),
        +    'Array': z.array(z.any()),
        +    'Boolean': z.boolean(),
        +    'Function': z.function(),
        +    'Integer': z.number().int(),
        +    'Number': z.number(),
        +    'Object': z.object({}),
        +    'String': z.string(),
        +  };
        +
        +  const webAPIObjects = [
        +    'AudioNode',
        +    'HTMLCanvasElement',
        +    'HTMLElement',
        +    'KeyboardEvent',
        +    'MouseEvent',
        +    'RegExp',
        +    'TouchEvent',
        +    'UIEvent',
        +    'WheelEvent'
        +  ];
        +
        +  function generateWebAPISchemas(apiObjects) {
        +    return apiObjects.reduce((acc, obj) => {
        +      acc[obj] = z.custom(data => data instanceof globalThis[obj], {
        +        message: `Expected a ${obj}`
        +      });
        +      return acc;
        +    }, {});
        +  }
        +
        +  const webAPISchemas = generateWebAPISchemas(webAPIObjects);
        +  // Add web API schemas to the schema map.
        +  Object.assign(schemaMap, webAPISchemas);
        +
        +  // For mapping 0-indexed parameters to their ordinal representation, e.g.
        +  // "first" for 0, "second" for 1, "third" for 2, etc.
        +  const ordinals = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth"];
        +
        +  function extractFuncNameAndClass(func) {
        +    const ichDot = func.lastIndexOf('.');
        +    const funcName = func.slice(ichDot + 1);
        +    const funcClass = func.slice(0, ichDot !== -1 ? ichDot : 0) || 'p5';
        +    return { funcName, funcClass };
        +  }
        +
        +  /**
        +   * This is a helper function that generates Zod schemas for a function based on
        +   * the parameter data from `docs/parameterData.json`.
        +   *
        +   * Example parameter data for function `background`:
        +   * "background": {
        +        "overloads": [
        +          ["p5.Color"],
        +          ["String", "Number?"],
        +          ["Number", "Number?"],
        +          ["Number", "Number", "Number", "Number?"],
        +          ["Number[]"],
        +          ["p5.Image", "Number?"]
        +        ]
        +      }
        +   * Where each array in `overloads` represents a set of valid overloaded
        +   * parameters, and `?` is a shorthand for `Optional`.
        +   *
        +   * @method generateZodSchemasForFunc
        +   * @param {String} func - Name of the function. Expect global functions like `sin` and class methods like `p5.Vector.add`
        +   * @returns {z.ZodSchema} Zod schema
        +   */
        +  fn.generateZodSchemasForFunc = function (func) {
        +    const { funcName, funcClass } = extractFuncNameAndClass(func);
        +    let funcInfo = dataDoc[funcClass][funcName];
        +
        +    if(!funcInfo) return;
        +
        +    let overloads = [];
        +    if (funcInfo.hasOwnProperty('overloads')) {
        +      overloads = funcInfo.overloads;
        +    }
        +
        +    // Returns a schema for a single type, i.e. z.boolean() for `boolean`.
        +    const generateTypeSchema = type => {
        +      if (!type) return z.any();
        +
        +      const isArray = type.endsWith('[]');
        +      const baseType = isArray ? type.slice(0, -2) : type;
        +
        +      let typeSchema;
        +
        +      // Check for constants. Note that because we're ultimately interested in the value of
        +      // the constant, mapping constants to their values via `constantsMap` is
        +      // necessary.
        +      if (baseType in constantsMap) {
        +        typeSchema = z.literal(constantsMap[baseType]);
        +      }
        +      // Some more constants are attached directly to p5.prototype, e.g. by addons:
        +      else if (baseType.match(/^[A-Z][A-Z0-9]*$/) && baseType in fn) {
        +        typeSchema = z.literal(fn[baseType]);
        +      }
        +      // Function types
        +      else if (baseType.startsWith('function')) {
        +        typeSchema = z.function();
        +      }
        +      // All p5 objects start with `p5` in the documentation, i.e. `p5.Camera`.
        +      else if (baseType.startsWith('p5')) {
        +        const className = baseType.substring(baseType.indexOf('.') + 1);
        +        typeSchema = z.instanceof(p5Constructors[className]);
        +      }
        +      // For primitive types and web API objects.
        +      else if (schemaMap[baseType]) {
        +        typeSchema = schemaMap[baseType];
        +      }
        +      // Tuple types
        +      else if (baseType.startsWith('[') && baseType.endsWith(']')) {
        +        typeSchema = z.tuple(
        +          baseType
        +            .slice(1, -1)
        +            .split(/, */g)
        +            .map(entry => generateTypeSchema(entry))
        +        );
        +      }
        +      // JavaScript classes, e.g. Request
        +      else if (baseType.match(/^[A-Z]/) && baseType in window) {
        +        typeSchema = z.instanceof(window[baseType]);
        +      } else {
        +        throw new Error(`Unsupported type '${type}' in parameter validation. Please report this issue.`);
        +      }
        +
        +      return isArray ? z.array(typeSchema) : typeSchema;
        +    };
        +
        +    // Generate a schema for a single parameter. In the case where a parameter can
        +    // be of multiple types, `generateTypeSchema` is called for each type.
        +    const generateParamSchema = param => {
        +      const isOptional = param?.endsWith('?');
        +      param = param?.replace(/\?$/, '');
        +
        +      let schema;
        +
        +      // Generate a schema for a single parameter that can be of multiple
        +      // types / constants, i.e. `String|Number|Array`.
        +      //
        +      // Here, z.union() is used over z.enum() (which seems more intuitive) for
        +      // constants for the following reasons:
        +      // 1) z.enum() only allows a fixed set of allowable string values. However,
        +      // our constants sometimes have numeric or non-primitive values.
        +      // 2) In some cases, the type can be constants or strings, making z.enum()
        +      // insufficient for the use case.
        +      if (param?.includes('|')) {
        +        const types = param.split('|');
        +        schema = z.union(types
        +          .map(t => generateTypeSchema(t))
        +          .filter(s => s !== undefined));
        +      } else {
        +        schema = generateTypeSchema(param);
        +      }
        +
        +      return isOptional ? schema.optional() : schema;
        +    };
        +
        +    // Note that in Zod, `optional()` only checks for undefined, not the absence
        +    // of value.
        +    //
        +    // Let's say we have a function with 3 parameters, and the last one is
        +    // optional, i.e. func(a, b, c?). If we only have a z.tuple() for the
        +    // parameters, where the third schema is optional, then we will only be able
        +    // to validate func(10, 10, undefined), but not func(10, 10), which is
        +    // a completely valid call.
        +    //
        +    // Therefore, on top of using `optional()`, we also have to generate parameter
        +    // combinations that are valid for all numbers of parameters.
        +    const generateOverloadCombinations = params => {
        +      // No optional parameters, return the original parameter list right away.
        +      if (!params.some(p => p?.endsWith('?'))) {
        +        return [params];
        +      }
        +
        +      const requiredParamsCount = params.filter(p => p === null || !p.endsWith('?')).length;
        +      const result = [];
        +
        +      for (let i = requiredParamsCount; i <= params.length; i++) {
        +        result.push(params.slice(0, i));
        +      }
        +
        +      return result;
        +    };
        +
        +    // Generate schemas for each function overload and merge them
        +    const overloadSchemas = overloads.flatMap(overload => {
        +      const combinations = generateOverloadCombinations(overload);
        +
        +      return combinations.map(combo =>
        +        z.tuple(
        +          combo
        +            .map(p => generateParamSchema(p))
        +            // For now, ignore schemas that cannot be mapped to a defined type
        +            .filter(schema => schema !== undefined)
        +        )
        +      );
        +    });
        +
        +    return overloadSchemas.length === 1
        +      ? overloadSchemas[0]
        +      : z.union(overloadSchemas);
        +  }
        +
        +  /**
        +   * Finds the closest schema to the input arguments.
        +   *
        +   * This is a helper function that identifies the closest schema to the input
        +   * arguments, in the case of an initial validation error. We will then use the
        +   * closest schema to generate a friendly error message.
        +   *
        +   * @param {z.ZodSchema} schema - Zod schema.
        +   * @param {Array} args - User input arguments.
        +   * @returns {z.ZodSchema} Closest schema matching the input arguments.
        +   */
        +  fn.findClosestSchema = function (schema, args) {
        +    if (!(schema instanceof z.ZodUnion)) {
        +      return schema;
        +    }
        +
        +    // Helper function that scores how close the input arguments are to a schema.
        +    // Lower score means closer match.
        +    const scoreSchema = schema => {
        +      let score = Infinity;
        +      if (!(schema instanceof z.ZodTuple)) {
        +        console.warn('Schema below is not a tuple: ');
        +        printZodSchema(schema);
        +        return score;
        +      }
        +
        +      const numArgs = args.length;
        +      const schemaItems = schema.items;
        +      const numSchemaItems = schemaItems.length;
        +      const numRequiredSchemaItems = schemaItems.filter(item => !item.isOptional()).length;
        +
        +      if (numArgs >= numRequiredSchemaItems && numArgs <= numSchemaItems) {
        +        score = 0;
        +      }
        +      // Here, give more weight to mismatch in number of arguments.
        +      //
        +      // For example, color() can either take [Number, Number?] or
        +      // [Number, Number, Number, Number?] as list of parameters.
        +      // If the user passed in 3 arguments, [10, undefined, undefined], it's
        +      // more than likely that they intended to pass in 3 arguments, but the
        +      // last two arguments are invalid.
        +      //
        +      // If there's no bias towards matching the number of arguments, the error
        +      // message will show that we're expecting at most 2 arguments, but more
        +      // are received.
        +      else {
        +        score = Math.abs(
        +          numArgs < numRequiredSchemaItems ? numRequiredSchemaItems - numArgs : numArgs - numSchemaItems
        +        ) * 4;
        +      }
        +
        +      for (let i = 0; i < Math.min(schemaItems.length, args.length); i++) {
        +        const paramSchema = schemaItems[i];
        +        const arg = args[i];
        +
        +        if (!paramSchema.safeParse(arg).success) score++;
        +      }
        +
        +      return score;
        +    };
        +
        +    // Default to the first schema, so that we are guaranteed to return a result.
        +    let closestSchema = schema._def.options[0];
        +    // We want to return the schema with the lowest score.
        +    let bestScore = Infinity;
        +
        +    const schemaUnion = schema._def.options;
        +    schemaUnion.forEach(schema => {
        +      const score = scoreSchema(schema);
        +      if (score < bestScore) {
        +        closestSchema = schema;
        +        bestScore = score;
        +      }
        +    });
        +
        +    return closestSchema;
        +  }
        +
        +  /**
        +   * Prints a friendly error message after parameter validation, if validation
        +   * has failed.
        +   *
        +   * @method _friendlyParamError
        +   * @private
        +   * @param {z.ZodError} zodErrorObj - The Zod error object containing validation errors.
        +   * @param {String} func - Name of the function. Expect global functions like `sin` and class methods like `p5.Vector.add`
        +   * @returns {String} The friendly error message.
        +   */
        +  fn.friendlyParamError = function (zodErrorObj, func) {
        +    let message = '🌸 p5.js says: ';
        +    // The `zodErrorObj` might contain multiple errors of equal importance
        +    // (after scoring the schema closeness in `findClosestSchema`). Here, we
        +    // always print the first error so that user can work through the errors
        +    // one by one.
        +    let currentError = zodErrorObj.errors[0];
        +
        +    // Helper function to build a type mismatch message.
        +    const buildTypeMismatchMessage = (actualType, expectedTypeStr, position) => {
        +      const positionStr = position ? `at the ${ordinals[position]} parameter` : '';
        +      const actualTypeStr = actualType ? `, but received ${actualType}` : '';
        +      return `Expected ${expectedTypeStr} ${positionStr}${actualTypeStr}`;
        +    }
        +
        +    // Union errors occur when a parameter can be of multiple types but is not
        +    // of any of them. In this case, aggregate all possible types and print
        +    // a friendly error message that indicates what the expected types are at
        +    // which position (position is not 0-indexed, for accessibility reasons).
        +    const processUnionError = (error) => {
        +      const expectedTypes = new Set();
        +      let actualType;
        +
        +      error.unionErrors.forEach(err => {
        +        const issue = err.issues[0];
        +        if (issue) {
        +          if (!actualType) {
        +            actualType = issue.received;
        +          }
        +
        +          if (issue.code === 'invalid_type') {
        +            expectedTypes.add(issue.expected);
        +          }
        +          // The case for constants. Since we don't want to print out the actual
        +          // constant values in the error message, the error message will
        +          // direct users to the documentation.
        +          else if (issue.code === 'invalid_literal') {
        +            expectedTypes.add("constant (please refer to documentation for allowed values)");
        +          } else if (issue.code === 'custom') {
        +            const match = issue.message.match(/Input not instance of (\w+)/);
        +            if (match) expectedTypes.add(match[1]);
        +          }
        +        }
        +      });
        +
        +      if (expectedTypes.size > 0) {
        +        const expectedTypesStr = Array.from(expectedTypes).join(' or ');
        +        const position = error.path.join('.');
        +
        +        message += buildTypeMismatchMessage(actualType, expectedTypesStr, position);
        +      }
        +
        +      return message;
        +    }
        +
        +    switch (currentError.code) {
        +      case 'invalid_union': {
        +        processUnionError(currentError);
        +        break;
        +      }
        +      case 'too_small': {
        +        const minArgs = currentError.minimum;
        +        message += `Expected at least ${minArgs} argument${minArgs > 1 ? 's' : ''}, but received fewer`;
        +        break;
        +      }
        +      case 'invalid_type': {
        +        message += buildTypeMismatchMessage(currentError.received, currentError.expected, currentError.path.join('.'));
        +        break;
        +      }
        +      case 'too_big': {
        +        const maxArgs = currentError.maximum;
        +        message += `Expected at most ${maxArgs} argument${maxArgs > 1 ? 's' : ''}, but received more`;
        +        break;
        +      }
        +      default: {
        +        console.log('Zod error object', currentError);
        +      }
        +    }
        +
        +    // Let the user know which function is generating the error.
        +    message += ` in ${func}().`;
        +
        +    // Generates a link to the documentation based on the given function name.
        +    // TODO: Check if the link is reachable before appending it to the error
        +    // message.
        +    const generateDocumentationLink = (func) => {
        +      const { funcName, funcClass } = extractFuncNameAndClass(func);
        +      const p5BaseUrl = 'https://p5js.org/reference';
        +      const url = `${p5BaseUrl}/${funcClass}/${funcName}`;
        +
        +      return url;
        +    }
        +
        +    if (currentError.code === 'too_big' || currentError.code === 'too_small') {
        +      const documentationLink = generateDocumentationLink(func);
        +      message += ` For more information, see ${documentationLink}.`;
        +    }
        +
        +    console.log(message);
        +    return message;
        +  }
        +
        +  /**
        +   * Runs parameter validation by matching the input parameters to Zod schemas
        +   * generated from the parameter data from `docs/parameterData.json`.
        +   *
        +   * @param {String} func - Name of the function.
        +   * @param {Array} args - User input arguments.
        +   * @returns {Object} The validation result.
        +   * @returns {Boolean} result.success - Whether the validation was successful.
        +   * @returns {any} [result.data] - The parsed data if validation was successful.
        +   * @returns {String} [result.error] - The validation error message if validation has failed.
        +   */
        +  fn.validate = function (func, args) {
        +    if (p5.disableFriendlyErrors) {
        +      return; // skip FES
        +    }
        +
        +    if (!Array.isArray(args)) {
        +      args = Array.from(args);
        +    }
        +
        +    // An edge case: even when all arguments are optional and therefore,
        +    // theoretically allowed to stay undefined and valid, it is likely that the
        +    // user intended to call the function with non-undefined arguments. Skip
        +    // regular workflow and return a friendly error message right away.
        +    if (Array.isArray(args) && args.every(arg => arg === undefined)) {
        +      const undefinedErrorMessage = `🌸 p5.js says: All arguments for ${func}() are undefined. There is likely an error in the code.`;
        +
        +      return {
        +        success: false,
        +        error: undefinedErrorMessage
        +      };
        +    }
        +
        +    let funcSchemas = schemaRegistry.get(func);
        +    if (!funcSchemas) {
        +      funcSchemas = fn.generateZodSchemasForFunc(func);
        +      if (!funcSchemas) return;
        +      schemaRegistry.set(func, funcSchemas);
        +    }
        +
        +    try {
        +      return {
        +        success: true,
        +        data: funcSchemas.parse(args)
        +      };
        +    } catch (error) {
        +      const closestSchema = fn.findClosestSchema(funcSchemas, args);
        +      const zodError = closestSchema.safeParse(args).error;
        +      const errorMessage = fn.friendlyParamError(zodError, func);
        +
        +      return {
        +        success: false,
        +        error: errorMessage
        +      };
        +    }
        +  };
        +
        +  lifecycles.presetup = function(){
        +    loadP5Constructors();
        +
        +    const excludes = ['validate'];
        +    for(const f in this){
        +      if(!excludes.includes(f) && !f.startsWith('_') && typeof this[f] === 'function'){
        +        const copy = this[f];
        +
        +        this[f] = function(...args) {
        +          this.validate(f, args);
        +          return copy.call(this, ...args);
        +        };
        +      }
        +    }
        +  };
        +}
        +
        +export default validateParams;
        +
        +if (typeof p5 !== 'undefined') {
        +  validateParams(p5, p5.prototype);
        +}
        diff --git a/src/core/friendly_errors/sketch_verifier.js b/src/core/friendly_errors/sketch_verifier.js
        new file mode 100644
        index 0000000000..0752150f1e
        --- /dev/null
        +++ b/src/core/friendly_errors/sketch_verifier.js
        @@ -0,0 +1,237 @@
        +import { parse } from 'acorn';
        +import { simple as walk } from 'acorn-walk';
        +import * as constants from '../constants';
        +
        +// List of functions to ignore as they either are meant to be re-defined or
        +// generate false positive outputs.
        +const ignoreFunction = [
        +  'setup',
        +  'draw',
        +  'preload',
        +  'deviceMoved',
        +  'deviceTurned',
        +  'deviceShaken',
        +  'doubleClicked',
        +  'mousePressed',
        +  'mouseReleased',
        +  'mouseMoved',
        +  'mouseDragged',
        +  'mouseClicked',
        +  'mouseWheel',
        +  'touchStarted',
        +  'touchMoved',
        +  'touchEnded',
        +  'keyPressed',
        +  'keyReleased',
        +  'keyTyped',
        +  'windowResized',
        +  // 'name',
        +  // 'parent',
        +  // 'toString',
        +  // 'print',
        +  // 'stop',
        +  // 'onended'
        +];
        +
        +export const verifierUtils = {
        +
        +  /**
        +   * Fetches the contents of a script element in the user's sketch.
        +   *
        +   * @private
        +   * @method fetchScript
        +   * @param {HTMLScriptElement} script
        +   * @returns {Promise<string>}
        + */
        +  fetchScript: async function (script) {
        +    if (script.src) {
        +      try {
        +        const contents = await fetch(script.src).then((res) => res.text());
        +        return contents;
        +      } catch (error) {
        +        // TODO: Handle CORS error here.
        +        console.error('Error fetching script:', error);
        +        return '';
        +      }
        +    } else {
        +      return script.textContent;
        +    }
        +  },
        +
        +  /**
        +   * Extracts the user-defined variables and functions from the user code with
        +   * the help of Espree parser.
        +   *
        +   * @private
        +   * @method extractUserDefinedVariablesAndFuncs
        +   * @param {string} code - The code to extract variables and functions from.
        +   * @returns {Object} An object containing the user's defined variables and functions.
        +   * @returns {Array<{name: string, line: number}>} [userDefinitions.variables] Array of user-defined variable names and their line numbers.
        +   * @returns {Array<{name: string, line: number}>} [userDefinitions.functions] Array of user-defined function names and their line numbers.
        +   */
        +  extractUserDefinedVariablesAndFuncs: function (code) {
        +    const userDefinitions = {
        +      variables: [],
        +      functions: []
        +    };
        +    // The line numbers from the parser are consistently off by one, add
        +    // `lineOffset` here to correct them.
        +    const lineOffset = -1;
        +
        +    try {
        +      const ast = parse(code, {
        +        ecmaVersion: 2021,
        +        sourceType: 'module',
        +        locations: true  // This helps us get the line number.
        +      });
        +
        +      walk(ast, {
        +        VariableDeclarator(node) {
        +          if (node.id.type === 'Identifier') {
        +            const category = node.init && ['ArrowFunctionExpression', 'FunctionExpression'].includes(node.init.type)
        +              ? 'functions'
        +              : 'variables';
        +            userDefinitions[category].push({
        +              name: node.id.name,
        +              line: node.loc.start.line + lineOffset
        +            });
        +          }
        +        },
        +        FunctionDeclaration(node) {
        +          if (node.id && node.id.type === 'Identifier') {
        +            userDefinitions.functions.push({
        +              name: node.id.name,
        +              line: node.loc.start.line + lineOffset
        +            });
        +          }
        +        },
        +        // We consider class declarations to be a special form of variable
        +        // declaration.
        +        ClassDeclaration(node) {
        +          if (node.id && node.id.type === 'Identifier') {
        +            userDefinitions.variables.push({
        +              name: node.id.name,
        +              line: node.loc.start.line + lineOffset
        +            });
        +          }
        +        }
        +      });
        +    } catch (error) {
        +      // TODO: Replace this with a friendly error message.
        +      console.error('Error parsing code:', error);
        +    }
        +
        +    return userDefinitions;
        +  },
        +
        +  /**
        +   * Checks user-defined variables and functions for conflicts with p5.js
        +   * constants and global functions.
        +   *
        +   * This function performs two main checks:
        +   * 1. Verifies if any user definition conflicts with p5.js constants.
        +   * 2. Checks if any user definition conflicts with global functions from
        +   * p5.js renderer classes.
        +   *
        +   * If a conflict is found, it reports a friendly error message and halts
        +   * further checking.
        +   *
        +   * @private
        +   * @param {Object} userDefinitions - An object containing user-defined variables and functions.
        +   * @param {Array<{name: string, line: number}>} userDefinitions.variables - Array of user-defined variable names and their line numbers.
        +   * @param {Array<{name: string, line: number}>} userDefinitions.functions - Array of user-defined function names and their line numbers.
        +   * @returns {boolean} - Returns true if a conflict is found, false otherwise.
        +   */
        +  checkForConstsAndFuncs: function (userDefinitions, p5) {
        +    const allDefinitions = [
        +      ...userDefinitions.variables,
        +      ...userDefinitions.functions
        +    ];
        +
        +    // Helper function that generates a friendly error message that contains
        +    // the type of redefinition (constant or function), the name of the
        +    // redefinition, the line number in user's code, and a link to its
        +    // reference on the p5.js website.
        +    function generateFriendlyError(errorType, name, line) {
        +      const url = `https://p5js.org/reference/p5/${name}`;
        +      const message = `${errorType} "${name}" on line ${line} is being redeclared and conflicts with a p5.js ${errorType.toLowerCase()}. p5.js reference: ${url}`;
        +      return message;
        +    }
        +
        +    // Checks for constant redefinitions.
        +    for (let { name, line } of allDefinitions) {
        +      const libDefinition = constants[name];
        +      if (libDefinition !== undefined) {
        +        const message = generateFriendlyError('Constant', name, line);
        +        console.log(message);
        +        return true;
        +      }
        +    }
        +
        +    // The new rules for attaching anything to global are (if true for both of
        +    // the following):
        +    //   - It is a member of p5.prototype
        +    //   - Its name does not start with `_`
        +    const globalFunctions = new Set(
        +      Object.getOwnPropertyNames(p5.prototype)
        +        .filter(key => !key.startsWith('_') && key !== 'constructor')
        +    );
        +
        +    for (let { name, line } of allDefinitions) {
        +      if (!ignoreFunction.includes(name) && globalFunctions.has(name)) {
        +        const message = generateFriendlyError('Function', name, line);
        +        console.log(message);
        +        return true;
        +      }
        +    }
        +
        +    return false;
        +  },
        +
        +  /**
        +   * Extracts the user's code from the script fetched. Note that this method
        +   * assumes that the user's code is always the last script element in the
        +   * sketch.
        +   *
        +   * @private
        +   * @method getUserCode
        +   * @returns {Promise<string>} The user's code as a string.
        +   */
        +  getUserCode: async function () {
        +    // TODO: think of a more robust way to get the user's code. Refer to
        +    // https://github.com/processing/p5.js/pull/7293.
        +    const scripts = document.querySelectorAll('script');
        +    const userCodeScript = scripts[scripts.length - 1];
        +    const userCode = await verifierUtils.fetchScript(userCodeScript);
        +
        +    return userCode;
        +  },
        +
        +  /**
        +   * @private
        +   */
        +  runFES: async function (p5) {
        +    const userCode = await verifierUtils.getUserCode();
        +    const userDefinedVariablesAndFuncs = verifierUtils.extractUserDefinedVariablesAndFuncs(userCode);
        +
        +    verifierUtils.checkForConstsAndFuncs(userDefinedVariablesAndFuncs, p5);
        +  }
        +};
        +
        +/**
        + * @for p5
        + * @requires core
        + */
        +function sketchVerifier(p5, _fn, lifecycles) {
        +  lifecycles.presetup = async function() {
        +    if (!p5.disableFriendlyErrors) {
        +      verifierUtils.runFES(p5);
        +    }
        +  };
        +}
        +
        +export default sketchVerifier;
        +
        +if (typeof p5 !== 'undefined') {
        +  sketchVerifier(p5, p5.prototype);
        +}
        diff --git a/src/core/friendly_errors/stacktrace.js b/src/core/friendly_errors/stacktrace.js
        index fe6a1b5722..1777abac41 100644
        --- a/src/core/friendly_errors/stacktrace.js
        +++ b/src/core/friendly_errors/stacktrace.js
        @@ -2,8 +2,6 @@
          * @for p5
          * @requires core
          */
        -import p5 from '../main';
        -
         // Borrow from stacktracejs https://github.com/stacktracejs/stacktrace.js with
         // minor modifications. The license for the same and the code is included below
         
        @@ -241,7 +239,14 @@ function ErrorStackParser() {
         // End borrow
         
         // wrapper exposing ErrorStackParser
        -p5._getErrorStackParser = function getErrorStackParser() {
        -  return new ErrorStackParser();
        -};
        -export default p5;
        +function stacktrace(p5, fn){
        +  p5._getErrorStackParser = function getErrorStackParser() {
        +    return new ErrorStackParser();
        +  };
        +}
        +
        +export default stacktrace;
        +
        +if (typeof p5 !== 'undefined') {
        +  stacktrace(p5, p5.prototype);
        +}
        diff --git a/src/core/friendly_errors/validate_params.js b/src/core/friendly_errors/validate_params.js
        index bf98dca98b..f8c974ea1e 100644
        --- a/src/core/friendly_errors/validate_params.js
        +++ b/src/core/friendly_errors/validate_params.js
        @@ -5,33 +5,33 @@
         import p5 from '../main';
         import * as constants from '../constants';
         import { translator } from '../internationalization';
        +// import dataDoc from '../../../docs/parameterData.json';
         
         if (typeof IS_MINIFIED !== 'undefined') {
           p5._validateParameters = p5._clearValidateParamsCache = () => {};
         } else {
           // for parameter validation
        -  const dataDoc = require('../../../docs/parameterData.json');
        -  const arrDoc = JSON.parse(JSON.stringify(dataDoc));
        -
        -  const docCache = {};
        -  const builtinTypes = new Set([
        -    'null',
        -    'number',
        -    'string',
        -    'boolean',
        -    'constant',
        -    'function',
        -    'any',
        -    'integer'
        -  ]);
        -
        -  const basicTypes = {
        -    number: true,
        -    boolean: true,
        -    string: true,
        -    function: true,
        -    undefined: true
        -  };
        +  // const arrDoc = JSON.parse(JSON.stringify(dataDoc));
        +
        +  // const docCache = {};
        +  // const builtinTypes = new Set([
        +  //   'null',
        +  //   'number',
        +  //   'string',
        +  //   'boolean',
        +  //   'constant',
        +  //   'function',
        +  //   'any',
        +  //   'integer'
        +  // ]);
        +
        +  // const basicTypes = {
        +  //   number: true,
        +  //   boolean: true,
        +  //   string: true,
        +  //   function: true,
        +  //   undefined: true
        +  // };
         
           // reverse map of all constants
           const constantsReverseMap = {};
        @@ -49,7 +49,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
         
           // For speedup over many runs. funcSpecificConstructors[func] only has the
           // constructors for types which were seen earlier as args of "func"
        -  const funcSpecificConstructors = {};
        +  // const funcSpecificConstructors = {};
           window.addEventListener('load', () => {
             // Make a list of all p5 classes to be used for argument validation
             // This must be done only when everything has loaded otherwise we get
        @@ -98,67 +98,67 @@ if (typeof IS_MINIFIED !== 'undefined') {
            * @method addType
            * @private
            */
        -  const addType = (value, obj, func) => {
        -    let type = typeof value;
        -    if (basicTypes[type]) {
        -      if (constantsReverseMap[value]) {
        -        // check if the value is a p5 constant and if it is, we would want the
        -        // value itself to be stored in the tree instead of the type
        -        obj = obj[value] || (obj[value] = {});
        -      } else {
        -        obj = obj[type] || (obj[type] = {});
        -      }
        -    } else if (value === null) {
        -      // typeof null -> "object". don't want that
        -      obj = obj['null'] || (obj['null'] = {});
        -    } else {
        -      // objects which are instances of p5 classes have nameless constructors.
        -      // native objects have a constructor named "Object". This check
        -      // differentiates between the two so that we dont waste time finding the
        -      // p5 class if we just have a native object
        -      if (value.constructor && value.constructor.name) {
        -        obj = obj[value.constructor.name] || (obj[value.constructor.name] = {});
        -        return obj;
        -      }
        -
        -      // constructors for types defined in p5 do not have a name property.
        -      // e.constructor.name gives "". Code in this segment is a workaround for it
        -
        -      // p5C will only have the name: constructor mapping for types
        -      // which were already seen as args of "func"
        -      let p5C = funcSpecificConstructors[func];
        -      // p5C would contain much fewer items than p5Constructors. if we find our
        -      // answer in p5C, we don't have to scan through p5Constructors
        -
        -      if (p5C === undefined) {
        -        // if there isn't an entry yet for func
        -        // make an entry of empty object
        -        p5C = funcSpecificConstructors[func] = {};
        -      }
        -
        -      for (let key in p5C) {
        -        // search on the constructors we have already seen (smaller search space)
        -        if (value instanceof p5C[key]) {
        -          obj = obj[key] || (obj[key] = {});
        -          return obj;
        -        }
        -      }
        -
        -      for (let key in p5Constructors) {
        -        // if the above search didn't work, search on all p5 constructors
        -        if (value instanceof p5Constructors[key]) {
        -          obj = obj[key] || (obj[key] = {});
        -          // if found, add to known constructors for this function
        -          p5C[key] = p5Constructors[key];
        -          return obj;
        -        }
        -      }
        -      // nothing worked, put the type as it is
        -      obj = obj[type] || (obj[type] = {});
        -    }
        -
        -    return obj;
        -  };
        +  // const addType = (value, obj, func) => {
        +  //   let type = typeof value;
        +  //   if (basicTypes[type]) {
        +  //     if (constantsReverseMap[value]) {
        +  //       // check if the value is a p5 constant and if it is, we would want the
        +  //       // value itself to be stored in the tree instead of the type
        +  //       obj = obj[value] || (obj[value] = {});
        +  //     } else {
        +  //       obj = obj[type] || (obj[type] = {});
        +  //     }
        +  //   } else if (value === null) {
        +  //     // typeof null -> "object". don't want that
        +  //     obj = obj['null'] || (obj['null'] = {});
        +  //   } else {
        +  //     // objects which are instances of p5 classes have nameless constructors.
        +  //     // native objects have a constructor named "Object". This check
        +  //     // differentiates between the two so that we dont waste time finding the
        +  //     // p5 class if we just have a native object
        +  //     if (value.constructor && value.constructor.name) {
        +  //       obj = obj[value.constructor.name] || (obj[value.constructor.name] = {});
        +  //       return obj;
        +  //     }
        +
        +  //     // constructors for types defined in p5 do not have a name property.
        +  //     // e.constructor.name gives "". Code in this segment is a workaround for it
        +
        +  //     // p5C will only have the name: constructor mapping for types
        +  //     // which were already seen as args of "func"
        +  //     let p5C = funcSpecificConstructors[func];
        +  //     // p5C would contain much fewer items than p5Constructors. if we find our
        +  //     // answer in p5C, we don't have to scan through p5Constructors
        +
        +  //     if (p5C === undefined) {
        +  //       // if there isn't an entry yet for func
        +  //       // make an entry of empty object
        +  //       p5C = funcSpecificConstructors[func] = {};
        +  //     }
        +
        +  //     for (let key in p5C) {
        +  //       // search on the constructors we have already seen (smaller search space)
        +  //       if (value instanceof p5C[key]) {
        +  //         obj = obj[key] || (obj[key] = {});
        +  //         return obj;
        +  //       }
        +  //     }
        +
        +  //     for (let key in p5Constructors) {
        +  //       // if the above search didn't work, search on all p5 constructors
        +  //       if (value instanceof p5Constructors[key]) {
        +  //         obj = obj[key] || (obj[key] = {});
        +  //         // if found, add to known constructors for this function
        +  //         p5C[key] = p5Constructors[key];
        +  //         return obj;
        +  //       }
        +  //     }
        +  //     // nothing worked, put the type as it is
        +  //     obj = obj[type] || (obj[type] = {});
        +  //   }
        +
        +  //   return obj;
        +  // };
         
           /**
            * Build the argument type tree, argumentTree
        @@ -168,31 +168,31 @@ if (typeof IS_MINIFIED !== 'undefined') {
            * @method buildArgTypeCache
            * @private
            */
        -  const buildArgTypeCache = (func, arr) => {
        -    // get the if an argument tree for current function already exists
        -    let obj = argumentTree[func];
        -    if (obj === undefined) {
        -      // if it doesn't, create an empty tree
        -      obj = argumentTree[func] = {};
        -    }
        -
        -    for (let i = 0, len = arr.length; i < len; ++i) {
        -      let value = arr[i];
        -      if (value instanceof Array) {
        -        // an array is passed as an argument, expand it and get the type of
        -        // each of its element. We distinguish the start of an array with 'as'
        -        // or arraystart. This would help distinguish between the arguments
        -        // (number, number, number) and (number, [number, number])
        -        obj = obj['as'] || (obj['as'] = {});
        -        for (let j = 0, lenA = value.length; j < lenA; ++j) {
        -          obj = addType(value[j], obj, func);
        -        }
        -      } else {
        -        obj = addType(value, obj, func);
        -      }
        -    }
        -    return obj;
        -  };
        +  // const buildArgTypeCache = (func, arr) => {
        +  //   // get the if an argument tree for current function already exists
        +  //   let obj = argumentTree[func];
        +  //   if (obj === undefined) {
        +  //     // if it doesn't, create an empty tree
        +  //     obj = argumentTree[func] = {};
        +  //   }
        +
        +  //   for (let i = 0, len = arr.length; i < len; ++i) {
        +  //     let value = arr[i];
        +  //     if (value instanceof Array) {
        +  //       // an array is passed as an argument, expand it and get the type of
        +  //       // each of its element. We distinguish the start of an array with 'as'
        +  //       // or arraystart. This would help distinguish between the arguments
        +  //       // (number, number, number) and (number, [number, number])
        +  //       obj = obj['as'] || (obj['as'] = {});
        +  //       for (let j = 0, lenA = value.length; j < lenA; ++j) {
        +  //         obj = addType(value[j], obj, func);
        +  //       }
        +  //     } else {
        +  //       obj = addType(value, obj, func);
        +  //     }
        +  //   }
        +  //   return obj;
        +  // };
         
           /**
            * Query data.json
        @@ -200,138 +200,151 @@ if (typeof IS_MINIFIED !== 'undefined') {
            * @method lookupParamDoc
            * @private
            */
        -  const lookupParamDoc = func => {
        -    // look for the docs in the `data.json` datastructure
        -
        -    const ichDot = func.lastIndexOf('.');
        -    const funcName = func.slice(ichDot + 1);
        -    const funcClass = func.slice(0, ichDot !== -1 ? ichDot : 0) || 'p5';
        -
        -    const classitems = arrDoc;
        -    let queryResult = classitems[funcClass][funcName];
        -
        -    // different JSON structure for funct with multi-format
        -    const overloads = [];
        -    if (queryResult.hasOwnProperty('overloads')) {
        -      // add all the overloads
        -      for (let i = 0; i < queryResult.overloads.length; i++) {
        -        overloads.push({ formats: queryResult.overloads[i].params });
        -      }
        -    } else {
        -      // no overloads, just add the main method definition
        -      overloads.push({ formats: queryResult.params || [] });
        -    }
        -
        -    // parse the parameter types for each overload
        -    const mapConstants = {};
        -    let maxParams = 0;
        -    overloads.forEach(overload => {
        -      const formats = overload.formats;
        -
        -      // keep a record of the maximum number of arguments
        -      // this method requires.
        -      if (maxParams < formats.length) {
        -        maxParams = formats.length;
        -      }
        -
        -      // calculate the minimum number of arguments
        -      // this overload requires.
        -      let minParams = formats.length;
        -      while (minParams > 0 && formats[minParams - 1].optional) {
        -        minParams--;
        -      }
        -      overload.minParams = minParams;
        -
        -      // loop through each parameter position, and parse its types
        -      formats.forEach(format => {
        -        // split this parameter's types
        -        format.types = format.type.split('|').map(function ct(type) {
        -          // array
        -          if (type.slice(-2) === '[]') {
        -            return {
        -              name: type,
        -              array: ct(type.slice(0, -2))
        -            };
        -          }
        -
        -          let lowerType = type.toLowerCase();
        -
        -          // constant
        -          if (lowerType === 'constant') {
        -            let constant;
        -            if (mapConstants.hasOwnProperty(format.name)) {
        -              constant = mapConstants[format.name];
        -            } else {
        -              // parse possible constant values from description
        -              const myRe = /either\s+(?:[A-Z0-9_]+\s*,?\s*(?:or)?\s*)+/g;
        -              const values = {};
        -              const names = [];
        -
        -              constant = mapConstants[format.name] = {
        -                values,
        -                names
        -              };
        -
        -              const myArray = myRe.exec(format.description);
        -              if (func === 'endShape' && format.name === 'mode') {
        -                values[constants.CLOSE] = true;
        -                names.push('CLOSE');
        -              } else {
        -                const match = myArray[0];
        -                const reConst = /[A-Z0-9_]+/g;
        -                let matchConst;
        -                while ((matchConst = reConst.exec(match)) !== null) {
        -                  const name = matchConst[0];
        -                  if (constants.hasOwnProperty(name)) {
        -                    values[constants[name]] = true;
        -                    names.push(name);
        -                  }
        -                }
        -              }
        -            }
        -            return {
        -              name: type,
        -              builtin: lowerType,
        -              names: constant.names,
        -              values: constant.values
        -            };
        -          }
        -
        -          // function
        -          if (lowerType.slice(0, 'function'.length) === 'function') {
        -            lowerType = 'function';
        -          }
        -          // builtin
        -          if (builtinTypes.has(lowerType)) {
        -            return { name: type, builtin: lowerType };
        -          }
        -
        -          // find type's prototype
        -          let t = window;
        -          const typeParts = type.split('.');
        -
        -          // special-case 'p5' since it may be non-global
        -          if (typeParts[0] === 'p5') {
        -            t = p5;
        -            typeParts.shift();
        -          }
        -
        -          typeParts.forEach(p => {
        -            t = t && t[p];
        -          });
        -          if (t) {
        -            return { name: type, prototype: t };
        -          }
        -
        -          return { name: type, type: lowerType };
        -        });
        -      });
        -    });
        -    return {
        -      overloads,
        -      maxParams
        -    };
        -  };
        +  // const lookupParamDoc = func => {
        +  //   // look for the docs in the `data.json` datastructure
        +
        +  //   const ichDot = func.lastIndexOf('.');
        +  //   const funcName = func.slice(ichDot + 1);
        +  //   const funcClass = func.slice(0, ichDot !== -1 ? ichDot : 0) || 'p5';
        +
        +  //   const classitems = arrDoc;
        +  //   let queryResult = classitems[funcClass][funcName];
        +
        +  //   // different JSON structure for funct with multi-format
        +  //   const overloads = [];
        +  //   if (queryResult.hasOwnProperty('overloads')) {
        +  //     // add all the overloads
        +  //     for (let i = 0; i < queryResult.overloads.length; i++) {
        +  //       overloads.push({ formats: queryResult.overloads[i].params || [] });
        +  //     }
        +  //   } else {
        +  //     // no overloads, just add the main method definition
        +  //     overloads.push({ formats: queryResult.params || [] });
        +  //   }
        +
        +  //   // parse the parameter types for each overload
        +  //   const mapConstants = {};
        +  //   let maxParams = 0;
        +  //   overloads.forEach(overload => {
        +  //     const formats = overload.formats;
        +
        +  //     // keep a record of the maximum number of arguments
        +  //     // this method requires.
        +  //     if (maxParams < formats.length) {
        +  //       maxParams = formats.length;
        +  //     }
        +
        +  //     // calculate the minimum number of arguments
        +  //     // this overload requires.
        +  //     let minParams = formats.length;
        +  //     while (minParams > 0 && formats[minParams - 1].optional) {
        +  //       minParams--;
        +  //     }
        +  //     overload.minParams = minParams;
        +
        +  //     // loop through each parameter position, and parse its types
        +  //     formats.forEach(format => {
        +  //       // split this parameter's types
        +  //       format.types = format.type.split('|').map(function ct(type) {
        +  //         // array
        +  //         if (type.slice(-2) === '[]') {
        +  //           return {
        +  //             name: type,
        +  //             array: ct(type.slice(0, -2))
        +  //           };
        +  //         }
        +
        +  //         let lowerType = type.toLowerCase();
        +
        +  //         // constant
        +  //         if (lowerType === 'constant') {
        +  //           let constant;
        +  //           if (mapConstants.hasOwnProperty(format.name)) {
        +  //             constant = mapConstants[format.name];
        +  //           } else {
        +  //             // parse possible constant values from description
        +  //             const myRe = /either\s+(?:[A-Z0-9_]+\s*,?\s*(?:or)?\s*)+/g;
        +  //             const values = {};
        +  //             const names = [];
        +
        +  //             constant = mapConstants[format.name] = {
        +  //               values,
        +  //               names
        +  //             };
        +
        +  //             const myArray = myRe.exec(format.description);
        +  //             if (func === 'endShape' && format.name === 'mode') {
        +  //               values[constants.CLOSE] = true;
        +  //               names.push('CLOSE');
        +  //             } else {
        +  //               const match = myArray[0];
        +  //               const reConst = /[A-Z0-9_]+/g;
        +  //               let matchConst;
        +  //               while ((matchConst = reConst.exec(match)) !== null) {
        +  //                 const name = matchConst[0];
        +  //                 if (name in constants) {
        +  //                   values[constants[name]] = true;
        +  //                   names.push(name);
        +  //                 }
        +  //               }
        +  //             }
        +  //           }
        +  //           return {
        +  //             name: type,
        +  //             builtin: lowerType,
        +  //             names: constant.names,
        +  //             values: constant.values
        +  //           };
        +  //         }
        +
        +  //         // Handle specific constants in types, e.g. in endShape:
        +  //         //   @param {CLOSE} [close]
        +  //         // Rather than trying to parse the types out of the description, we
        +  //         // can use the constant directly from the type
        +  //         if (type in constants) {
        +  //           return {
        +  //             name: type,
        +  //             builtin: 'constant',
        +  //             names: [type],
        +  //             values: { [constants[type]]: true }
        +  //           };
        +  //         }
        +
        +  //         // function
        +  //         if (lowerType.slice(0, 'function'.length) === 'function') {
        +  //           lowerType = 'function';
        +  //         }
        +  //         // builtin
        +  //         if (builtinTypes.has(lowerType)) {
        +  //           return { name: type, builtin: lowerType };
        +  //         }
        +
        +  //         // find type's prototype
        +  //         let t = window;
        +  //         const typeParts = type.split('.');
        +
        +  //         // special-case 'p5' since it may be non-global
        +  //         if (typeParts[0] === 'p5') {
        +  //           t = p5;
        +  //           typeParts.shift();
        +  //         }
        +
        +  //         typeParts.forEach(p => {
        +  //           t = t && t[p];
        +  //         });
        +  //         if (t) {
        +  //           return { name: type, prototype: t };
        +  //         }
        +
        +  //         return { name: type, type: lowerType };
        +  //       });
        +  //     });
        +  //   });
        +  //   return {
        +  //     overloads,
        +  //     maxParams
        +  //   };
        +  // };
         
           /**
            * Checks whether input type is Number
        @@ -341,80 +354,80 @@ if (typeof IS_MINIFIED !== 'undefined') {
            *
            * @returns {Boolean} a boolean indicating whether input type is Number
            */
        -  const isNumber = param => {
        -    if (isNaN(parseFloat(param))) return false;
        -    switch (typeof param) {
        -      case 'number':
        -        return true;
        -      case 'string':
        -        return !isNaN(param);
        -      default:
        -        return false;
        -    }
        -  };
        +  // const isNumber = param => {
        +  //   if (isNaN(parseFloat(param))) return false;
        +  //   switch (typeof param) {
        +  //     case 'number':
        +  //       return true;
        +  //     case 'string':
        +  //       return !isNaN(param);
        +  //     default:
        +  //       return false;
        +  //   }
        +  // };
         
           /**
            * Test type for non-object type parameter validation
            * @method testParamType
            * @private
            */
        -  const testParamType = (param, type) => {
        -    const isArray = param instanceof Array;
        -    let matches = true;
        -    if (type.array && isArray) {
        -      for (let i = 0; i < param.length; i++) {
        -        const error = testParamType(param[i], type.array);
        -        if (error) return error / 2; // half error for elements
        -      }
        -    } else if (type.prototype) {
        -      matches = param instanceof type.prototype;
        -    } else if (type.builtin) {
        -      switch (type.builtin) {
        -        case 'number':
        -          matches = isNumber(param);
        -          break;
        -        case 'integer':
        -          matches = isNumber(param) && Number(param) === Math.floor(param);
        -          break;
        -        case 'boolean':
        -        case 'any':
        -          matches = true;
        -          break;
        -        case 'array':
        -          matches = isArray;
        -          break;
        -        case 'string':
        -          matches = /*typeof param === 'number' ||*/ typeof param === 'string';
        -          break;
        -        case 'constant':
        -          matches = type.values.hasOwnProperty(param);
        -          break;
        -        case 'function':
        -          matches = param instanceof Function;
        -          break;
        -        case 'null':
        -          matches = param === null;
        -          break;
        -      }
        -    } else {
        -      matches = typeof param === type.t;
        -    }
        -    return matches ? 0 : 1;
        -  };
        +  // const testParamType = (param, type) => {
        +  //   const isArray = param instanceof Array;
        +  //   let matches = true;
        +  //   if (type.array && isArray) {
        +  //     for (let i = 0; i < param.length; i++) {
        +  //       const error = testParamType(param[i], type.array);
        +  //       if (error) return error / 2; // half error for elements
        +  //     }
        +  //   } else if (type.prototype) {
        +  //     matches = param instanceof type.prototype;
        +  //   } else if (type.builtin) {
        +  //     switch (type.builtin) {
        +  //       case 'number':
        +  //         matches = isNumber(param);
        +  //         break;
        +  //       case 'integer':
        +  //         matches = isNumber(param) && Number(param) === Math.floor(param);
        +  //         break;
        +  //       case 'boolean':
        +  //       case 'any':
        +  //         matches = true;
        +  //         break;
        +  //       case 'array':
        +  //         matches = isArray;
        +  //         break;
        +  //       case 'string':
        +  //         matches = /*typeof param === 'number' ||*/ typeof param === 'string';
        +  //         break;
        +  //       case 'constant':
        +  //         matches = type.values.hasOwnProperty(param);
        +  //         break;
        +  //       case 'function':
        +  //         matches = param instanceof Function;
        +  //         break;
        +  //       case 'null':
        +  //         matches = param === null;
        +  //         break;
        +  //     }
        +  //   } else {
        +  //     matches = typeof param === type.t;
        +  //   }
        +  //   return matches ? 0 : 1;
        +  // };
         
           /**
            * Test type for multiple parameters
            * @method testParamTypes
            * @private
            */
        -  const testParamTypes = (param, types) => {
        -    let minScore = 9999;
        -    for (let i = 0; minScore > 0 && i < types.length; i++) {
        -      const score = testParamType(param, types[i]);
        -      if (minScore > score) minScore = score;
        -    }
        -    return minScore;
        -  };
        +  // const testParamTypes = (param, types) => {
        +  //   let minScore = 9999;
        +  //   for (let i = 0; minScore > 0 && i < types.length; i++) {
        +  //     const score = testParamType(param, types[i]);
        +  //     if (minScore > score) minScore = score;
        +  //   }
        +  //   return minScore;
        +  // };
         
           /**
            * generate a score (higher is worse) for applying these args to
        @@ -422,91 +435,91 @@ if (typeof IS_MINIFIED !== 'undefined') {
            * @method scoreOverload
            * @private
            */
        -  const scoreOverload = (args, argCount, overload, minScore) => {
        -    let score = 0;
        -    const formats = overload.formats;
        -    const minParams = overload.minParams;
        -
        -    // check for too few/many args
        -    // the score is double number of extra/missing args
        -    if (argCount < minParams) {
        -      score = (minParams - argCount) * 2;
        -    } else if (argCount > formats.length) {
        -      score = (argCount - formats.length) * 2;
        -    }
        -
        -    // loop through the formats, adding up the error score for each arg.
        -    // quit early if the score gets higher than the previous best overload.
        -    for (let p = 0; score <= minScore && p < formats.length; p++) {
        -      const arg = args[p];
        -      const format = formats[p];
        -      // '== null' checks for 'null' and typeof 'undefined'
        -      if (arg == null) {
        -        // handle undefined args
        -        if (!format.optional || p < minParams || p < argCount) {
        -          score += 1;
        -        }
        -      } else {
        -        score += testParamTypes(arg, format.types);
        -      }
        -    }
        -    return score;
        -  };
        +  // const scoreOverload = (args, argCount, overload, minScore) => {
        +  //   let score = 0;
        +  //   const formats = overload.formats;
        +  //   const minParams = overload.minParams;
        +
        +  //   // check for too few/many args
        +  //   // the score is double number of extra/missing args
        +  //   if (argCount < minParams) {
        +  //     score = (minParams - argCount) * 2;
        +  //   } else if (argCount > formats.length) {
        +  //     score = (argCount - formats.length) * 2;
        +  //   }
        +
        +  //   // loop through the formats, adding up the error score for each arg.
        +  //   // quit early if the score gets higher than the previous best overload.
        +  //   for (let p = 0; score <= minScore && p < formats.length; p++) {
        +  //     const arg = args[p];
        +  //     const format = formats[p];
        +  //     // '== null' checks for 'null' and typeof 'undefined'
        +  //     if (arg == null) {
        +  //       // handle undefined args
        +  //       if (!format.optional || p < minParams || p < argCount) {
        +  //         score += 1;
        +  //       }
        +  //     } else {
        +  //       score += testParamTypes(arg, format.types);
        +  //     }
        +  //   }
        +  //   return score;
        +  // };
         
           /**
            * Gets a list of errors for this overload
            * @method getOverloadErrors
            * @private
            */
        -  const getOverloadErrors = (args, argCount, overload) => {
        -    const formats = overload.formats;
        -    const minParams = overload.minParams;
        -
        -    // check for too few/many args
        -    if (argCount < minParams) {
        -      return [
        -        {
        -          type: 'TOO_FEW_ARGUMENTS',
        -          argCount,
        -          minParams
        -        }
        -      ];
        -    } else if (argCount > formats.length) {
        -      return [
        -        {
        -          type: 'TOO_MANY_ARGUMENTS',
        -          argCount,
        -          maxParams: formats.length
        -        }
        -      ];
        -    }
        -
        -    const errorArray = [];
        -    for (let p = 0; p < formats.length; p++) {
        -      const arg = args[p];
        -      const format = formats[p];
        -      // '== null' checks for 'null' and typeof 'undefined'
        -      if (arg == null) {
        -        // handle undefined args
        -        if (!format.optional || p < minParams || p < argCount) {
        -          errorArray.push({
        -            type: 'EMPTY_VAR',
        -            position: p,
        -            format
        -          });
        -        }
        -      } else if (testParamTypes(arg, format.types) > 0) {
        -        errorArray.push({
        -          type: 'WRONG_TYPE',
        -          position: p,
        -          format,
        -          arg
        -        });
        -      }
        -    }
        -
        -    return errorArray;
        -  };
        +  // const getOverloadErrors = (args, argCount, overload) => {
        +  //   const formats = overload.formats;
        +  //   const minParams = overload.minParams;
        +
        +  //   // check for too few/many args
        +  //   if (argCount < minParams) {
        +  //     return [
        +  //       {
        +  //         type: 'TOO_FEW_ARGUMENTS',
        +  //         argCount,
        +  //         minParams
        +  //       }
        +  //     ];
        +  //   } else if (argCount > formats.length) {
        +  //     return [
        +  //       {
        +  //         type: 'TOO_MANY_ARGUMENTS',
        +  //         argCount,
        +  //         maxParams: formats.length
        +  //       }
        +  //     ];
        +  //   }
        +
        +  //   const errorArray = [];
        +  //   for (let p = 0; p < formats.length; p++) {
        +  //     const arg = args[p];
        +  //     const format = formats[p];
        +  //     // '== null' checks for 'null' and typeof 'undefined'
        +  //     if (arg == null) {
        +  //       // handle undefined args
        +  //       if (!format.optional || p < minParams || p < argCount) {
        +  //         errorArray.push({
        +  //           type: 'EMPTY_VAR',
        +  //           position: p,
        +  //           format
        +  //         });
        +  //       }
        +  //     } else if (testParamTypes(arg, format.types) > 0) {
        +  //       errorArray.push({
        +  //         type: 'WRONG_TYPE',
        +  //         position: p,
        +  //         format,
        +  //         arg
        +  //       });
        +  //     }
        +  //   }
        +
        +  //   return errorArray;
        +  // };
         
           /**
            * a custom error type, used by the mocha
        @@ -535,7 +548,7 @@ if (typeof IS_MINIFIED !== 'undefined') {
            * @method _friendlyParamError
            * @private
            */
        -  p5._friendlyParamError = function(errorObj, func) {
        +  p5._friendlyParamError = function (errorObj, func) {
             let message;
             let translationObj;
         
        @@ -704,57 +717,59 @@ if (typeof IS_MINIFIED !== 'undefined') {
            *           received "foo" instead."
            */
           p5._validateParameters = function validateParameters(func, args) {
        -    if (p5.disableFriendlyErrors) {
        -      return; // skip FES
        -    }
        -
        -    // query / build the argument type tree and check if this sequence
        -    // has already been seen before.
        -    let obj = buildArgTypeCache(func, args);
        -    if (obj.seen) {
        -      return;
        -    }
        -    // mark this sequence as seen
        -    obj.seen = true;
        -    // lookup the docs in the 'data.json' file
        -    const docs = docCache[func] || (docCache[func] = lookupParamDoc(func));
        -    const overloads = docs.overloads;
        -
        -    let argCount = args.length;
        -
        -    // the following line ignores trailing undefined arguments, commenting
        -    // it to resolve https://github.com/processing/p5.js/issues/4571
        -    // '== null' checks for 'null' and typeof 'undefined'
        -    // while (argCount > 0 && args[argCount - 1] == null) argCount--;
        -
        -    // find the overload with the best score
        -    let minScore = 99999;
        -    let minOverload;
        -    for (let i = 0; i < overloads.length; i++) {
        -      const score = scoreOverload(args, argCount, overloads[i], minScore);
        -      if (score === 0) {
        -        return; // done!
        -      } else if (minScore > score) {
        -        // this score is better that what we have so far...
        -        minScore = score;
        -        minOverload = i;
        -      }
        -    }
        -
        -    // this should _always_ be true here...
        -    if (minScore > 0) {
        -      // get the errors for the best overload
        -      const errorArray = getOverloadErrors(
        -        args,
        -        argCount,
        -        overloads[minOverload]
        -      );
        -
        -      // generate err msg
        -      for (let n = 0; n < errorArray.length; n++) {
        -        p5._friendlyParamError(errorArray[n], func);
        -      }
        -    }
        +    // NOTE: no-op for later removal
        +    return;
        +    // if (p5.disableFriendlyErrors) {
        +    //   return; // skip FES
        +    // }
        +
        +    // // query / build the argument type tree and check if this sequence
        +    // // has already been seen before.
        +    // let obj = buildArgTypeCache(func, args);
        +    // if (obj.seen) {
        +    //   return;
        +    // }
        +    // // mark this sequence as seen
        +    // obj.seen = true;
        +    // // lookup the docs in the 'data.json' file
        +    // const docs = docCache[func] || (docCache[func] = lookupParamDoc(func));
        +    // const overloads = docs.overloads;
        +
        +    // let argCount = args.length;
        +
        +    // // the following line ignores trailing undefined arguments, commenting
        +    // // it to resolve https://github.com/processing/p5.js/issues/4571
        +    // // '== null' checks for 'null' and typeof 'undefined'
        +    // // while (argCount > 0 && args[argCount - 1] == null) argCount--;
        +
        +    // // find the overload with the best score
        +    // let minScore = 99999;
        +    // let minOverload;
        +    // for (let i = 0; i < overloads.length; i++) {
        +    //   const score = scoreOverload(args, argCount, overloads[i], minScore);
        +    //   if (score === 0) {
        +    //     return; // done!
        +    //   } else if (minScore > score) {
        +    //     // this score is better that what we have so far...
        +    //     minScore = score;
        +    //     minOverload = i;
        +    //   }
        +    // }
        +
        +    // // this should _always_ be true here...
        +    // if (minScore > 0) {
        +    //   // get the errors for the best overload
        +    //   const errorArray = getOverloadErrors(
        +    //     args,
        +    //     argCount,
        +    //     overloads[minOverload]
        +    //   );
        +
        +    //   // generate err msg
        +    //   for (let n = 0; n < errorArray.length; n++) {
        +    //     p5._friendlyParamError(errorArray[n], func);
        +    //   }
        +    // }
           };
           p5.prototype._validateParameters = p5.validateParameters;
         }
        diff --git a/src/core/init.js b/src/core/init.js
        index 9cecfca8a8..764437b1ff 100644
        --- a/src/core/init.js
        +++ b/src/core/init.js
        @@ -2,9 +2,8 @@ import p5 from '../core/main';
         import { initialize as initTranslator } from './internationalization';
         
         /**
        - * _globalInit
        + * This file setup global mode automatic instantiation
          *
        - * TODO: ???
          * if sketch is on window
          * assume "global" mode
          * and instantiate p5 automatically
        @@ -13,7 +12,7 @@ import { initialize as initTranslator } from './internationalization';
          * @private
          * @return {Undefined}
          */
        -const _globalInit = () => {
        +export const _globalInit = () => {
           // Could have been any property defined within the p5 constructor.
           // If that property is already a part of the global object,
           // this code has already run before, likely due to a duplicate import
        @@ -41,7 +40,7 @@ const _globalInit = () => {
         };
         
         // make a promise that resolves when the document is ready
        -const waitForDocumentReady = () =>
        +export const waitForDocumentReady = () =>
           new Promise((resolve, reject) => {
             // if the page is ready, initialize p5 immediately
             if (document.readyState === 'complete') {
        @@ -54,7 +53,6 @@ const waitForDocumentReady = () =>
           });
         
         // only load translations if we're using the full, un-minified library
        -const waitingForTranslator =
        -  typeof IS_MINIFIED === 'undefined' ? initTranslator() : Promise.resolve();
        -
        -Promise.all([waitForDocumentReady(), waitingForTranslator]).then(_globalInit);
        +export const waitingForTranslator =
        +  typeof IS_MINIFIED === 'undefined' ? initTranslator() :
        +    Promise.resolve();
        diff --git a/src/core/internationalization.js b/src/core/internationalization.js
        index e8e140c12c..6f61925cff 100644
        --- a/src/core/internationalization.js
        +++ b/src/core/internationalization.js
        @@ -1,14 +1,10 @@
         import i18next from 'i18next';
         import LanguageDetector from 'i18next-browser-languagedetector';
        +import { default as fallbackResources, languages } from '../../translations';
         
        -let fallbackResources, languages;
         if (typeof IS_MINIFIED === 'undefined') {
           // internationalization is only for the unminified build
         
        -  const translationsModule = require('../../translations');
        -  fallbackResources = translationsModule.default;
        -  languages = translationsModule.languages;
        -
           if (typeof P5_DEV_BUILD !== 'undefined') {
             // When the library is built in development mode ( using npm run dev )
             // we want to use the current translation files on the disk, which may have
        @@ -27,9 +23,10 @@ if (typeof IS_MINIFIED === 'undefined') {
           }
         }
         
        -/**
        +/*
          * This is our i18next "backend" plugin. It tries to fetch languages
          * from a CDN.
        + * @private
          */
         class FetchResources {
           constructor(services, options) {
        @@ -123,8 +120,9 @@ export let translator = (key, values) => {
         };
         // (We'll set this to a real value in the init function below!)
         
        -/**
        +/*
          * Set up our translation function, with loaded languages
        + * @private
          */
         export const initialize = () => {
           let i18init = i18next
        @@ -168,24 +166,27 @@ export const initialize = () => {
           return i18init;
         };
         
        -/**
        +/*
          * Returns a list of languages we have translations loaded for
        + * @private
          */
         export const availableTranslatorLanguages = () => {
           return i18next.languages;
         };
         
        -/**
        +/*
          * Returns the current language selected for translation
        + * @private
          */
         export const currentTranslatorLanguage = language => {
           return i18next.language;
         };
         
        -/**
        +/*
          * Sets the current language for translation
          * Returns a promise that resolved when loading is finished,
          * or rejects if it fails.
        + * @private
          */
         export const setTranslatorLanguage = language => {
           return i18next.changeLanguage(language || undefined, e =>
        diff --git a/src/core/main.js b/src/core/main.js
        index 36db13d4e5..0e31be3397 100644
        --- a/src/core/main.js
        +++ b/src/core/main.js
        @@ -5,10 +5,8 @@
          * @requires constants
          */
         
        -import './shim';
        -
        -// Core needs the PVariables object
         import * as constants from './constants';
        +
         /**
          * This is the p5 instance constructor.
          *
        @@ -25,7 +23,6 @@ import * as constants from './constants';
          * "instance" - all properties and methods are bound to this p5 object
          *
          * @class p5
        - * @constructor
          * @param  {function(p5)}       sketch a closure that can set optional <a href="#/p5/preload">preload()</a>,
          *                              <a href="#/p5/setup">setup()</a>, and/or <a href="#/p5/draw">draw()</a> properties on the
          *                              given p5 instance
        @@ -33,272 +30,42 @@ import * as constants from './constants';
          * @return {p5}                 a p5 instance
          */
         class p5 {
        -  constructor(sketch, node) {
        -    //////////////////////////////////////////////
        -    // PUBLIC p5 PROPERTIES AND METHODS
        -    //////////////////////////////////////////////
        -
        -    /**
        -     * A function that's called once to load assets before the sketch runs.
        -     *
        -     * Declaring the function `preload()` sets a code block to run once
        -     * automatically before <a href="#/p5/setup">setup()</a> or
        -     * <a href="#/p5/draw">draw()</a>. It's used to load assets including
        -     * multimedia files, fonts, data, and 3D models:
        -     *
        -     * ```js
        -     * function preload() {
        -     *   // Code to run before the rest of the sketch.
        -     * }
        -     * ```
        -     *
        -     * Functions such as <a href="#/p5/loadImage">loadImage()</a>,
        -     * <a href="#/p5/loadFont">loadFont()</a>,
        -     * <a href="#/p5/loadJSON">loadJSON()</a>, and
        -     * <a href="#/p5/loadModel">loadModel()</a> are guaranteed to either
        -     * finish loading or raise an error if they're called within `preload()`.
        -     * Doing so ensures that assets are available when the sketch begins
        -     * running.
        -     *
        -     * @method preload
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * let img;
        -     *
        -     * // Load an image and create a p5.Image object.
        -     * function preload() {
        -     *   img = loadImage('assets/bricks.jpg');
        -     * }
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   // Draw the image.
        -     *   image(img, 0, 0);
        -     *
        -     *   describe('A red brick wall.');
        -     * }
        -     * </code>
        -     * </div>
        -     */
        -
        -    /**
        -     * A function that's called once when the sketch begins running.
        -     *
        -     * Declaring the function `setup()` sets a code block to run once
        -     * automatically when the sketch starts running. It's used to perform
        -     * setup tasks such as creating the canvas and initializing variables:
        -     *
        -     * ```js
        -     * function setup() {
        -     *   // Code to run once at the start of the sketch.
        -     * }
        -     * ```
        -     *
        -     * Code placed in `setup()` will run once before code placed in
        -     * <a href="#/p5/draw">draw()</a> begins looping. If the
        -     * <a href="#/p5/preload">preload()</a> is declared, then `setup()` will
        -     * run immediately after <a href="#/p5/preload">preload()</a> finishes
        -     * loading assets.
        -     *
        -     * Note: `setup()` doesn’t have to be declared, but it’s common practice to do so.
        -     *
        -     * @method setup
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   background(200);
        -     *
        -     *   // Draw the circle.
        -     *   circle(50, 50, 40);
        -     *
        -     *   describe('A white circle on a gray background.');
        -     * }
        -     * </code>
        -     * </div>
        -     *
        -     * <div>
        -     * <code>
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   // Paint the background once.
        -     *   background(200);
        -     *
        -     *   describe(
        -     *     'A white circle on a gray background. The circle follows the mouse as the user moves, leaving a trail.'
        -     *   );
        -     * }
        -     *
        -     * function draw() {
        -     *   // Draw circles repeatedly.
        -     *   circle(mouseX, mouseY, 40);
        -     * }
        -     * </code>
        -     * </div>
        -     *
        -     * <div>
        -     * <code>
        -     * let img;
        -     *
        -     * function preload() {
        -     *   img = loadImage('assets/bricks.jpg');
        -     * }
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   // Draw the image.
        -     *   image(img, 0, 0);
        -     *
        -     *   describe(
        -     *     'A white circle on a brick wall. The circle follows the mouse as the user moves, leaving a trail.'
        -     *   );
        -     * }
        -     *
        -     * function draw() {
        -     *   // Style the circle.
        -     *   noStroke();
        -     *
        -     *   // Draw the circle.
        -     *   circle(mouseX, mouseY, 10);
        -     * }
        -     * </code>
        -     * </div>
        -     */
        -
        -    /**
        -     * A function that's called repeatedly while the sketch runs.
        -     *
        -     * Declaring the function `draw()` sets a code block to run repeatedly
        -     * once the sketch starts. It’s used to create animations and respond to
        -     * user inputs:
        -     *
        -     * ```js
        -     * function draw() {
        -     *   // Code to run repeatedly.
        -     * }
        -     * ```
        -     *
        -     * This is often called the "draw loop" because p5.js calls the code in
        -     * `draw()` in a loop behind the scenes. By default, `draw()` tries to run
        -     * 60 times per second. The actual rate depends on many factors. The
        -     * drawing rate, called the "frame rate", can be controlled by calling
        -     * <a href="#/p5/frameRate">frameRate()</a>. The number of times `draw()`
        -     * has run is stored in the system variable
        -     * <a href="#/p5/frameCount">frameCount()</a>.
        -     *
        -     * Code placed within `draw()` begins looping after
        -     * <a href="#/p5/setup">setup()</a> runs. `draw()` will run until the user
        -     * closes the sketch. `draw()` can be stopped by calling the
        -     * <a href="#/p5/noLoop">noLoop()</a> function. `draw()` can be resumed by
        -     * calling the <a href="#/p5/loop">loop()</a> function.
        -     *
        -     * @method draw
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   // Paint the background once.
        -     *   background(200);
        -     *
        -     *   describe(
        -     *     'A white circle on a gray background. The circle follows the mouse as the user moves, leaving a trail.'
        -     *   );
        -     * }
        -     *
        -     * function draw() {
        -     *   // Draw circles repeatedly.
        -     *   circle(mouseX, mouseY, 40);
        -     * }
        -     * </code>
        -     * </div>
        -     *
        -     * <div>
        -     * <code>
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   describe(
        -     *     'A white circle on a gray background. The circle follows the mouse as the user moves.'
        -     *   );
        -     * }
        -     *
        -     * function draw() {
        -     *   // Paint the background repeatedly.
        -     *   background(200);
        -     *
        -     *   // Draw circles repeatedly.
        -     *   circle(mouseX, mouseY, 40);
        -     * }
        -     * </code>
        -     * </div>
        -     *
        -     * <div>
        -     * <code>
        -     * // Double-click the canvas to change the circle's color.
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   describe(
        -     *     'A white circle on a gray background. The circle follows the mouse as the user moves. The circle changes color to pink when the user double-clicks.'
        -     *   );
        -     * }
        -     *
        -     * function draw() {
        -     *   // Paint the background repeatedly.
        -     *   background(200);
        -     *
        -     *   // Draw circles repeatedly.
        -     *   circle(mouseX, mouseY, 40);
        -     * }
        -     *
        -     * // Change the fill color when the user double-clicks.
        -     * function doubleClicked() {
        -     *   fill('deeppink');
        -     * }
        -     * </code>
        -     * </div>
        -     */
        +  static VERSION = constants.VERSION;
        +  // This is a pointer to our global mode p5 instance, if we're in
        +  // global mode.
        +  static instance = null;
        +  static lifecycleHooks = {
        +    presetup: [],
        +    postsetup: [],
        +    predraw: [],
        +    postdraw: [],
        +    remove: []
        +  };
        +
        +  // FES stub
        +  static _checkForUserDefinedFunctions = () => {};
        +  static _friendlyFileLoadError = () => {};
         
        +  constructor(sketch, node) {
             //////////////////////////////////////////////
             // PRIVATE p5 PROPERTIES AND METHODS
             //////////////////////////////////////////////
         
             this._setupDone = false;
        -    this._preloadDone = false;
        -    // for handling hidpi
        -    this._pixelDensity = Math.ceil(window.devicePixelRatio) || 1;
        -    this._maxAllowedPixelDimensions = 0;
             this._userNode = node;
             this._curElement = null;
             this._elements = [];
             this._glAttributes = null;
             this._requestAnimId = 0;
        -    this._preloadCount = 0;
             this._isGlobal = false;
             this._loop = true;
             this._startListener = null;
             this._initializeInstanceVariables();
        -    this._defaultCanvasSize = {
        -      width: 100,
        -      height: 100
        -    };
             this._events = {
               // keep track of user-events for unregistering later
        -      mousemove: null,
        -      mousedown: null,
        -      mouseup: null,
        +      pointerdown: null,
        +      pointerup: null,
        +      pointermove: null,
               dragend: null,
               dragover: null,
               click: null,
        @@ -308,34 +75,16 @@ class p5 {
               keydown: null,
               keyup: null,
               keypress: null,
        -      touchstart: null,
        -      touchmove: null,
        -      touchend: null,
        +      wheel: null,
               resize: null,
               blur: null
             };
             this._millisStart = -1;
             this._recording = false;
        -    this.touchstart = false;
        -    this.touchend = false;
         
             // States used in the custom random generators
        -    this._lcg_random_state = null;
        -    this._gaussian_previous = false;
        -
        -    this._events.wheel = null;
        -    this._loadingScreenId = 'p5_loading';
        -
        -    // Allows methods to be registered on an instance that
        -    // are instance-specific.
        -    this._registeredMethods = {};
        -    const methods = Object.getOwnPropertyNames(p5.prototype._registeredMethods);
        -
        -    for (const prop of methods) {
        -      this._registeredMethods[prop] = p5.prototype._registeredMethods[
        -        prop
        -      ].slice();
        -    }
        +    this._lcg_random_state = null; // NOTE: move to random.js
        +    this._gaussian_previous = false; // NOTE: move to random.js
         
             if (window.DeviceOrientationEvent) {
               this._events.deviceorientation = null;
        @@ -344,360 +93,51 @@ class p5 {
               this._events.devicemotion = null;
             }
         
        -    // Function to invoke registered hooks before or after events such as preload, setup, and pre/post draw.
        -    p5.prototype.callRegisteredHooksFor = function (hookName) {
        -      const target = this || p5.prototype;
        -      const context = this._isGlobal ? window : this;
        -      if (target._registeredMethods.hasOwnProperty(hookName)) {
        -        const methods = target._registeredMethods[hookName];
        -        for (const method of methods) {
        -          if (typeof method === 'function') {
        -            method.call(context);
        -          }
        -        }
        -      }
        -    };
        -
        -    this._start = () => {
        -      // Find node if id given
        -      if (this._userNode) {
        -        if (typeof this._userNode === 'string') {
        -          this._userNode = document.getElementById(this._userNode);
        -        }
        -      }
        -
        -      const context = this._isGlobal ? window : this;
        -      if (context.preload) {
        -        this.callRegisteredHooksFor('beforePreload');
        -        // Setup loading screen
        -        // Set loading screen into dom if not present
        -        // Otherwise displays and removes user provided loading screen
        -        let loadingScreen = document.getElementById(this._loadingScreenId);
        -        if (!loadingScreen) {
        -          loadingScreen = document.createElement('div');
        -          loadingScreen.innerHTML = 'Loading...';
        -          loadingScreen.style.position = 'absolute';
        -          loadingScreen.id = this._loadingScreenId;
        -          const node = this._userNode || document.body;
        -          node.appendChild(loadingScreen);
        -        }
        -        const methods = this._preloadMethods;
        -        for (const method in methods) {
        -          // default to p5 if no object defined
        -          methods[method] = methods[method] || p5;
        -          let obj = methods[method];
        -          //it's p5, check if it's global or instance
        -          if (obj === p5.prototype || obj === p5) {
        -            if (this._isGlobal) {
        -              window[method] = this._wrapPreload(this, method);
        -            }
        -            obj = this;
        -          }
        -          this._registeredPreloadMethods[method] = obj[method];
        -          obj[method] = this._wrapPreload(obj, method);
        -        }
        -
        -        context.preload();
        -        this._runIfPreloadsAreDone();
        -      } else {
        -        this._setup();
        -        if (!this._recording) {
        -          this._draw();
        -        }
        -      }
        -    };
        -
        -    this._runIfPreloadsAreDone = function() {
        -      const context = this._isGlobal ? window : this;
        -      if (context._preloadCount === 0) {
        -        const loadingScreen = document.getElementById(context._loadingScreenId);
        -        if (loadingScreen) {
        -          loadingScreen.parentNode.removeChild(loadingScreen);
        -        }
        -        this.callRegisteredHooksFor('afterPreload');
        -        if (!this._setupDone) {
        -          this._lastTargetFrameTime = window.performance.now();
        -          this._lastRealFrameTime = window.performance.now();
        -          context._setup();
        -          if (!this._recording) {
        -            context._draw();
        -          }
        -        }
        -      }
        -    };
        -
        -    this._decrementPreload = function() {
        -      const context = this._isGlobal ? window : this;
        -      if (!context._preloadDone && typeof context.preload === 'function') {
        -        context._setProperty('_preloadCount', context._preloadCount - 1);
        -        context._runIfPreloadsAreDone();
        -      }
        -    };
        -
        -    this._wrapPreload = function(obj, fnName) {
        -      return (...args) => {
        -        //increment counter
        -        this._incrementPreload();
        -        //call original function
        -        return this._registeredPreloadMethods[fnName].apply(obj, args);
        -      };
        -    };
        -
        -    this._incrementPreload = function() {
        -      const context = this._isGlobal ? window : this;
        -      // Do nothing if we tried to increment preloads outside of `preload`
        -      if (context._preloadDone) return;
        -      context._setProperty('_preloadCount', context._preloadCount + 1);
        -    };
        -
        -    this._setup = () => {
        -      this.callRegisteredHooksFor('beforeSetup');
        -      // Always create a default canvas.
        -      // Later on if the user calls createCanvas, this default one
        -      // will be replaced
        -      this.createCanvas(
        -        this._defaultCanvasSize.width,
        -        this._defaultCanvasSize.height,
        -        'p2d'
        -      );
        -
        -      // return preload functions to their normal vals if switched by preload
        -      const context = this._isGlobal ? window : this;
        -      if (typeof context.preload === 'function') {
        -        for (const f in this._preloadMethods) {
        -          context[f] = this._preloadMethods[f][f];
        -          if (context[f] && this) {
        -            context[f] = context[f].bind(this);
        -          }
        -        }
        -      }
        -
        -      // Record the time when sketch starts
        -      this._millisStart = window.performance.now();
        -
        -      context._preloadDone = true;
        -
        -      // Short-circuit on this, in case someone used the library in "global"
        -      // mode earlier
        -      if (typeof context.setup === 'function') {
        -        context.setup();
        -      }
        -
        -      // unhide any hidden canvases that were created
        -      const canvases = document.getElementsByTagName('canvas');
        -
        -      for (const k of canvases) {
        -        if (k.dataset.hidden === 'true') {
        -          k.style.visibility = '';
        -          delete k.dataset.hidden;
        -        }
        -      }
        -
        -      this._lastTargetFrameTime = window.performance.now();
        -      this._lastRealFrameTime = window.performance.now();
        -      this._setupDone = true;
        -      if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        -        this._updateAccsOutput();
        -      }
        -      this.callRegisteredHooksFor('afterSetup');
        -    };
        -
        -    this._draw = requestAnimationFrameTimestamp => {
        -      const now = requestAnimationFrameTimestamp || window.performance.now();
        -      const time_since_last = now - this._lastTargetFrameTime;
        -      const target_time_between_frames = 1000 / this._targetFrameRate;
        -
        -      // only draw if we really need to; don't overextend the browser.
        -      // draw if we're within 5ms of when our next frame should paint
        -      // (this will prevent us from giving up opportunities to draw
        -      // again when it's really about time for us to do so). fixes an
        -      // issue where the frameRate is too low if our refresh loop isn't
        -      // in sync with the browser. note that we have to draw once even
        -      // if looping is off, so we bypass the time delay if that
        -      // is the case.
        -      const epsilon = 5;
        -      if (
        -        !this._loop ||
        -        time_since_last >= target_time_between_frames - epsilon
        -      ) {
        -        //mandatory update values(matrixes and stack)
        -        this.deltaTime = now - this._lastRealFrameTime;
        -        this._setProperty('deltaTime', this.deltaTime);
        -        this._frameRate = 1000.0 / this.deltaTime;
        -        this.redraw();
        -        this._lastTargetFrameTime = Math.max(this._lastTargetFrameTime
        -          + target_time_between_frames, now);
        -        this._lastRealFrameTime = now;
        -
        -        // If the user is actually using mouse module, then update
        -        // coordinates, otherwise skip. We can test this by simply
        -        // checking if any of the mouse functions are available or not.
        -        // NOTE : This reflects only in complete build or modular build.
        -        if (typeof this._updateMouseCoords !== 'undefined') {
        -          this._updateMouseCoords();
        -
        -          //reset delta values so they reset even if there is no mouse event to set them
        -          // for example if the mouse is outside the screen
        -          this._setProperty('movedX', 0);
        -          this._setProperty('movedY', 0);
        -        }
        -      }
        -
        -      // get notified the next time the browser gives us
        -      // an opportunity to draw.
        -      if (this._loop) {
        -        this._requestAnimId = window.requestAnimationFrame(this._draw);
        -      }
        -    };
        -
        -    this._setProperty = (prop, value) => {
        -      this[prop] = value;
        -      if (this._isGlobal) {
        -        window[prop] = value;
        -      }
        -    };
        -
        -    /**
        -     * Removes the sketch from the web page.
        -     *
        -     * Calling `remove()` stops the draw loop and removes any HTML elements
        -     * created by the sketch, including the canvas. A new sketch can be
        -     * created by using the <a href="#/p5/p5">p5()</a> constructor, as in
        -     * `new p5()`.
        -     *
        -     * @method remove
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * // Double-click to remove the canvas.
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   describe(
        -     *     'A white circle on a gray background. The circle follows the mouse as the user moves. The sketch disappears when the user double-clicks.'
        -     *   );
        -     * }
        -     *
        -     * function draw() {
        -     *   // Paint the background repeatedly.
        -     *   background(200);
        -     *
        -     *   // Draw circles repeatedly.
        -     *   circle(mouseX, mouseY, 40);
        -     * }
        -     *
        -     * // Remove the sketch when the user double-clicks.
        -     * function doubleClicked() {
        -     *   remove();
        -     * }
        -     * </code>
        -     * </div>
        -     */
        -    this.remove = () => {
        -      // Remove start listener to prevent orphan canvas being created
        -      if(this._startListener){
        -        window.removeEventListener('load', this._startListener, false);
        -      }
        -      const loadingScreen = document.getElementById(this._loadingScreenId);
        -      if (loadingScreen) {
        -        loadingScreen.parentNode.removeChild(loadingScreen);
        -        // Add 1 to preload counter to prevent the sketch ever executing setup()
        -        this._incrementPreload();
        -      }
        -      if (this._curElement) {
        -        // stop draw
        -        this._loop = false;
        -        if (this._requestAnimId) {
        -          window.cancelAnimationFrame(this._requestAnimId);
        -        }
        -
        -        // unregister events sketch-wide
        -        for (const ev in this._events) {
        -          window.removeEventListener(ev, this._events[ev]);
        -        }
        -
        -        // remove DOM elements created by p5, and listeners
        -        for (const e of this._elements) {
        -          if (e.elt && e.elt.parentNode) {
        -            e.elt.parentNode.removeChild(e.elt);
        -          }
        -          for (const elt_ev in e._events) {
        -            e.elt.removeEventListener(elt_ev, e._events[elt_ev]);
        -          }
        -        }
        +    // ensure correct reporting of window dimensions
        +    this._updateWindowSize();
         
        -        // call any registered remove functions
        -        const self = this;
        -        this._registeredMethods.remove.forEach(f => {
        -          if (typeof f !== 'undefined') {
        -            f.call(self);
        -          }
        -        });
        -      }
        -      // remove window bound properties and methods
        -      if (this._isGlobal) {
        -        for (const p in p5.prototype) {
        -          try {
        -            delete window[p];
        -          } catch (x) {
        -            window[p] = undefined;
        +    const bindGlobal = (property) => {
        +      Object.defineProperty(window, property, {
        +        configurable: true,
        +        enumerable: true,
        +        get: () => {
        +          if(typeof this[property] === 'function'){
        +            return this[property].bind(this);
        +          }else{
        +            return this[property];
                   }
        -        }
        -        for (const p2 in this) {
        -          if (this.hasOwnProperty(p2)) {
        -            try {
        -              delete window[p2];
        -            } catch (x) {
        -              window[p2] = undefined;
        -            }
        +        },
        +        set: (newValue) => {
        +          Object.defineProperty(window, property, {
        +            configurable: true,
        +            enumerable: true,
        +            value: newValue,
        +            writable: true
        +          });
        +          if (!p5.disableFriendlyErrors) {
        +            console.log(`You just changed the value of "${property}", which was a p5 global value. This could cause problems later if you're not careful.`);
                   }
                 }
        -        p5.instance = null;
        -      }
        +      })
             };
        -
        -    // ensure correct reporting of window dimensions
        -    this._updateWindowSize();
        -
        -    // call any registered init functions
        -    this._registeredMethods.init.forEach(function(f) {
        -      if (typeof f !== 'undefined') {
        -        f.call(this);
        -      }
        -    }, this);
        -    // Set up promise preloads
        -    this._setupPromisePreloads();
        -
        -    const friendlyBindGlobal = this._createFriendlyGlobalFunctionBinder();
        -
             // If the user has created a global setup or draw function,
             // assume "global" mode and make everything global (i.e. on the window)
             if (!sketch) {
               this._isGlobal = true;
               p5.instance = this;
        +
               // Loop through methods on the prototype and attach them to the window
        -      for (const p in p5.prototype) {
        -        if (typeof p5.prototype[p] === 'function') {
        -          const ev = p.substring(2);
        -          if (!this._events.hasOwnProperty(ev)) {
        -            if (Math.hasOwnProperty(p) && Math[p] === p5.prototype[p]) {
        -              // Multiple p5 methods are just native Math functions. These can be
        -              // called without any binding.
        -              friendlyBindGlobal(p, p5.prototype[p]);
        -            } else {
        -              friendlyBindGlobal(p, p5.prototype[p].bind(this));
        -            }
        -          }
        -        } else {
        -          friendlyBindGlobal(p, p5.prototype[p]);
        -        }
        +      // All methods and properties with name starting with '_' will be skipped
        +      for (const p of Object.getOwnPropertyNames(p5.prototype)) {
        +        if(p[0] === '_') continue;
        +        bindGlobal(p);
               }
        +
               // Attach its properties to the window
        -      for (const p2 in this) {
        -        if (this.hasOwnProperty(p2)) {
        -          friendlyBindGlobal(p2, this[p2]);
        +      for (const p in this) {
        +        if (this.hasOwnProperty(p)) {
        +          if(p[0] === '_') continue;
        +          bindGlobal(p);
                 }
               }
             } else {
        @@ -711,7 +151,6 @@ class p5 {
             }
         
             // Bind events to window (not using container div bc key events don't work)
        -
             for (const e in this._events) {
               const f = this[`_on${e}`];
               if (f) {
        @@ -722,157 +161,467 @@ class p5 {
             }
         
             const focusHandler = () => {
        -      this._setProperty('focused', true);
        +      this.focused = true;
             };
             const blurHandler = () => {
        -      this._setProperty('focused', false);
        +      this.focused = false;
             };
             window.addEventListener('focus', focusHandler);
             window.addEventListener('blur', blurHandler);
        -    this.registerMethod('remove', () => {
        +    p5.lifecycleHooks.remove.push(function() {
               window.removeEventListener('focus', focusHandler);
               window.removeEventListener('blur', blurHandler);
             });
         
        +    // Initialization complete, start runtime
             if (document.readyState === 'complete') {
        -      this._start();
        +      this.#_start();
             } else {
        -      this._startListener = this._start.bind(this);
        +      this._startListener = this.#_start.bind(this);
               window.addEventListener('load', this._startListener, false);
             }
           }
         
        -  _initializeInstanceVariables() {
        -    this._accessibleOutputs = {
        -      text: false,
        -      grid: false,
        -      textLabel: false,
        -      gridLabel: false
        -    };
        +  get pixels(){
        +    return this._renderer.pixels;
        +  }
         
        -    this._styles = [];
        +  static registerAddon(addon) {
        +    const lifecycles = {};
        +    addon(p5, p5.prototype, lifecycles);
         
        -    this._bezierDetail = 20;
        -    this._curveDetail = 20;
        +    const validLifecycles = Object.keys(p5.lifecycleHooks);
        +    for(const name of validLifecycles){
        +      if(typeof lifecycles[name] === 'function'){
        +        p5.lifecycleHooks[name].push(lifecycles[name]);
        +      }
        +    }
        +  }
         
        -    this._colorMode = constants.RGB;
        -    this._colorMaxes = {
        -      rgb: [255, 255, 255, 255],
        -      hsb: [360, 100, 100, 1],
        -      hsl: [360, 100, 100, 1]
        -    };
        +  async #_start() {
        +    // Find node if id given
        +    if (this._userNode) {
        +      if (typeof this._userNode === 'string') {
        +        this._userNode = document.getElementById(this._userNode);
        +      }
        +    }
         
        -    this._downKeys = {}; //Holds the key codes of currently pressed keys
        +    await this.#_setup();
        +    if (!this._recording) {
        +      this._draw();
        +    }
           }
         
        -  registerPreloadMethod(fnString, obj) {
        -    // obj = obj || p5.prototype;
        -    if (!p5.prototype._preloadMethods.hasOwnProperty(fnString)) {
        -      p5.prototype._preloadMethods[fnString] = obj;
        +  async #_setup() {
        +    // Run `presetup` hooks
        +    await this._runLifecycleHook('presetup');
        +
        +    // Always create a default canvas.
        +    // Later on if the user calls createCanvas, this default one
        +    // will be replaced
        +    this.createCanvas(
        +      100,
        +      100,
        +      constants.P2D
        +    );
        +
        +    // Record the time when sketch starts
        +    this._millisStart = window.performance.now();
        +
        +    const context = this._isGlobal ? window : this;
        +    if (typeof context.setup === 'function') {
        +      await context.setup();
             }
        -  }
         
        -  registerMethod(name, m) {
        -    const target = this || p5.prototype;
        -    if (!target._registeredMethods.hasOwnProperty(name)) {
        -      target._registeredMethods[name] = [];
        +    // unhide any hidden canvases that were created
        +    const canvases = document.getElementsByTagName('canvas');
        +
        +    // Apply touchAction = 'none' to canvases if pointer events exist
        +    if (Object.keys(this._events).some(event => event.startsWith('pointer'))) {
        +      for (const k of canvases) {
        +        k.style.touchAction = 'none';
        +      }
             }
        -    target._registeredMethods[name].push(m);
        -  }
         
        -  unregisterMethod(name, m) {
        -    const target = this || p5.prototype;
        -    if (target._registeredMethods.hasOwnProperty(name)) {
        -      const methods = target._registeredMethods[name];
        -      const indexesToRemove = [];
        -      // Find all indexes of the method `m` in the array of registered methods
        -      for (let i = 0; i < methods.length; i++) {
        -        if (methods[i] === m) {
        -          indexesToRemove.push(i);
        -        }
        +
        +    for (const k of canvases) {
        +      if (k.dataset.hidden === 'true') {
        +        k.style.visibility = '';
        +        delete k.dataset.hidden;
               }
        -      // Remove all instances of the method `m` from the array
        -      for (let i = indexesToRemove.length - 1; i >= 0; i--) {
        -        methods.splice(indexesToRemove[i], 1);
        +    }
        +
        +    this._lastTargetFrameTime = window.performance.now();
        +    this._lastRealFrameTime = window.performance.now();
        +    this._setupDone = true;
        +    if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        +      this._updateAccsOutput();
        +    }
        +
        +    // Run `postsetup` hooks
        +    await this._runLifecycleHook('postsetup');
        +  }
        +
        +  // While '#_draw' here is async, it is not awaited as 'requestAnimationFrame'
        +  // does not await its callback. Thus it is not recommended for 'draw()` to be
        +  // async and use await within as the next frame may start rendering before the
        +  // current frame finish awaiting. The same goes for lifecycle hooks 'predraw'
        +  // and 'postdraw'.
        +  async _draw(requestAnimationFrameTimestamp) {
        +    const now = requestAnimationFrameTimestamp || window.performance.now();
        +    const timeSinceLastFrame = now - this._lastTargetFrameTime;
        +    const targetTimeBetweenFrames = 1000 / this._targetFrameRate;
        +
        +    // only draw if we really need to; don't overextend the browser.
        +    // draw if we're within 5ms of when our next frame should paint
        +    // (this will prevent us from giving up opportunities to draw
        +    // again when it's really about time for us to do so). fixes an
        +    // issue where the frameRate is too low if our refresh loop isn't
        +    // in sync with the browser. note that we have to draw once even
        +    // if looping is off, so we bypass the time delay if that
        +    // is the case.
        +    const epsilon = 5;
        +    if (
        +      !this._loop ||
        +      timeSinceLastFrame >= targetTimeBetweenFrames - epsilon
        +    ) {
        +      //mandatory update values(matrixes and stack)
        +      this.deltaTime = now - this._lastRealFrameTime;
        +      this._frameRate = 1000.0 / this.deltaTime;
        +      await this.redraw();
        +      this._lastTargetFrameTime = Math.max(this._lastTargetFrameTime
        +        + targetTimeBetweenFrames, now);
        +      this._lastRealFrameTime = now;
        +
        +      // If the user is actually using mouse module, then update
        +      // coordinates, otherwise skip. We can test this by simply
        +      // checking if any of the mouse functions are available or not.
        +      // NOTE : This reflects only in complete build or modular build.
        +      if (typeof this._updateMouseCoords !== 'undefined') {
        +        this._updateMouseCoords();
        +
        +        //reset delta values so they reset even if there is no mouse event to set them
        +        // for example if the mouse is outside the screen
        +        this.movedX = 0;
        +        this.movedY = 0;
               }
             }
        +
        +    // get notified the next time the browser gives us
        +    // an opportunity to draw.
        +    if (this._loop) {
        +      this._requestAnimId = window.requestAnimationFrame(
        +        this._draw.bind(this)
        +      );
        +    }
           }
         
        -  // create a function which provides a standardized process for binding
        -  // globals; this is implemented as a factory primarily so that there's a
        -  // way to redefine what "global" means for the binding function so it
        -  // can be used in scenarios like unit testing where the window object
        -  // might not exist
        -  _createFriendlyGlobalFunctionBinder(options = {}) {
        -    const globalObject = options.globalObject || window;
        -    const log = options.log || console.log.bind(console);
        -    const propsToForciblyOverwrite = {
        -      // p5.print actually always overwrites an existing global function,
        -      // albeit one that is very unlikely to be used:
        -      //
        -      //   https://developer.mozilla.org/en-US/docs/Web/API/Window/print
        -      print: true
        -    };
        +  /**
        +   * Removes the sketch from the web page.
        +   *
        +   * Calling `remove()` stops the draw loop and removes any HTML elements
        +   * created by the sketch, including the canvas. A new sketch can be
        +   * created by using the <a href="#/p5/p5">p5()</a> constructor, as in
        +   * `new p5()`.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to remove the canvas.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A white circle on a gray background. The circle follows the mouse as the user moves. The sketch disappears when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   // Paint the background repeatedly.
        +   *   background(200);
        +   *
        +   *   // Draw circles repeatedly.
        +   *   circle(mouseX, mouseY, 40);
        +   * }
        +   *
        +   * // Remove the sketch when the user double-clicks.
        +   * function doubleClicked() {
        +   *   remove();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  async remove() {
        +    // Remove start listener to prevent orphan canvas being created
        +    if(this._startListener){
        +      window.removeEventListener('load', this._startListener, false);
        +    }
        +
        +    if (this._curElement) {
        +      // stop draw
        +      this._loop = false;
        +      if (this._requestAnimId) {
        +        window.cancelAnimationFrame(this._requestAnimId);
        +      }
        +
        +      // unregister events sketch-wide
        +      for (const ev in this._events) {
        +        window.removeEventListener(ev, this._events[ev]);
        +      }
        +
        +      // remove DOM elements created by p5, and listeners
        +      for (const e of this._elements) {
        +        if (e.elt && e.elt.parentNode) {
        +          e.elt.parentNode.removeChild(e.elt);
        +        }
        +        for (const elt_ev in e._events) {
        +          e.elt.removeEventListener(elt_ev, e._events[elt_ev]);
        +        }
        +      }
         
        -    return (prop, value) => {
        -      if (
        -        !p5.disableFriendlyErrors &&
        -        typeof IS_MINIFIED === 'undefined' &&
        -        typeof value === 'function' &&
        -        !(prop in p5.prototype._preloadMethods)
        -      ) {
        +      // Run `remove` hooks
        +      await this._runLifecycleHook('remove');
        +    }
        +
        +    // remove window bound properties and methods
        +    if (this._isGlobal) {
        +      for (const p in p5.prototype) {
                 try {
        -          // Because p5 has so many common function names, it's likely
        -          // that users may accidentally overwrite global p5 functions with
        -          // their own variables. Let's allow this but log a warning to
        -          // help users who may be doing this unintentionally.
        -          //
        -          // For more information, see:
        -          //
        -          //   https://github.com/processing/p5.js/issues/1317
        -
        -          if (prop in globalObject && !(prop in propsToForciblyOverwrite)) {
        -            throw new Error(`global "${prop}" already exists`);
        +          delete window[p];
        +        } catch (x) {
        +          window[p] = undefined;
        +        }
        +      }
        +      for (const p2 in this) {
        +        if (this.hasOwnProperty(p2)) {
        +          try {
        +            delete window[p2];
        +          } catch (x) {
        +            window[p2] = undefined;
                   }
        -
        -          // It's possible that this might throw an error because there
        -          // are a lot of edge-cases in which `Object.defineProperty` might
        -          // not succeed; since this functionality is only intended to
        -          // help beginners anyways, we'll just catch such an exception
        -          // if it occurs, and fall back to legacy behavior.
        -          Object.defineProperty(globalObject, prop, {
        -            configurable: true,
        -            enumerable: true,
        -            get() {
        -              return value;
        -            },
        -            set(newValue) {
        -              Object.defineProperty(globalObject, prop, {
        -                configurable: true,
        -                enumerable: true,
        -                value: newValue,
        -                writable: true
        -              });
        -              log(
        -                `You just changed the value of "${prop}", which was a p5 function. This could cause problems later if you're not careful.`
        -              );
        -            }
        -          });
        -        } catch (e) {
        -          let message = `p5 had problems creating the global function "${prop}", possibly because your code is already using that name as a variable. You may want to rename your variable to something else.`;
        -          p5._friendlyError(message, prop);
        -          globalObject[prop] = value;
                 }
        -      } else {
        -        globalObject[prop] = value;
               }
        +      p5.instance = null;
        +    }
        +  }
        +
        +  async _runLifecycleHook(hookName) {
        +    for(const hook of p5.lifecycleHooks[hookName]){
        +      await hook.call(this);
        +    }
        +  }
        +
        +  _initializeInstanceVariables() {
        +    this._accessibleOutputs = {
        +      text: false,
        +      grid: false,
        +      textLabel: false,
        +      gridLabel: false
             };
        +
        +    this._styles = [];
        +    this._downKeys = {}; //Holds the key codes of currently pressed keys
           }
         }
         
        -// This is a pointer to our global mode p5 instance, if we're in
        -// global mode.
        -p5.instance = null;
        +// Attach constants to p5 prototype
        +for (const k in constants) {
        +  p5.prototype[k] = constants[k];
        +}
        +
        +//////////////////////////////////////////////
        +// PUBLIC p5 PROPERTIES AND METHODS
        +//////////////////////////////////////////////
        +
        +/**
        + * A function that's called once when the sketch begins running.
        + *
        + * Declaring the function `setup()` sets a code block to run once
        + * automatically when the sketch starts running. It's used to perform
        + * setup tasks such as creating the canvas and initializing variables:
        + *
        + * ```js
        + * function setup() {
        + *   // Code to run once at the start of the sketch.
        + * }
        + * ```
        + *
        + * Code placed in `setup()` will run once before code placed in
        + * <a href="#/p5/draw">draw()</a> begins looping. If the
        + * <a href="#/p5/preload">preload()</a> is declared, then `setup()` will
        + * run immediately after <a href="#/p5/preload">preload()</a> finishes
        + * loading assets.
        + *
        + * Note: `setup()` doesn’t have to be declared, but it’s common practice to do so.
        + *
        + * @method setup
        + * @for p5
        + *
        + * @example
        + * <div>
        + * <code>
        + * function setup() {
        + *   createCanvas(100, 100);
        + *
        + *   background(200);
        + *
        + *   // Draw the circle.
        + *   circle(50, 50, 40);
        + *
        + *   describe('A white circle on a gray background.');
        + * }
        + * </code>
        + * </div>
        + *
        + * <div>
        + * <code>
        + * function setup() {
        + *   createCanvas(100, 100);
        + *
        + *   // Paint the background once.
        + *   background(200);
        + *
        + *   describe(
        + *     'A white circle on a gray background. The circle follows the mouse as the user moves, leaving a trail.'
        + *   );
        + * }
        + *
        + * function draw() {
        + *   // Draw circles repeatedly.
        + *   circle(mouseX, mouseY, 40);
        + * }
        + * </code>
        + * </div>
        + *
        + * <div>
        + * <code>
        + * let img;
        + *
        + * function preload() {
        + *   img = loadImage('assets/bricks.jpg');
        + * }
        + *
        + * function setup() {
        + *   createCanvas(100, 100);
        + *
        + *   // Draw the image.
        + *   image(img, 0, 0);
        + *
        + *   describe(
        + *     'A white circle on a brick wall. The circle follows the mouse as the user moves, leaving a trail.'
        + *   );
        + * }
        + *
        + * function draw() {
        + *   // Style the circle.
        + *   noStroke();
        + *
        + *   // Draw the circle.
        + *   circle(mouseX, mouseY, 10);
        + * }
        + * </code>
        + * </div>
        + */
        +
        +/**
        + * A function that's called repeatedly while the sketch runs.
        + *
        + * Declaring the function `draw()` sets a code block to run repeatedly
        + * once the sketch starts. It’s used to create animations and respond to
        + * user inputs:
        + *
        + * ```js
        + * function draw() {
        + *   // Code to run repeatedly.
        + * }
        + * ```
        + *
        + * This is often called the "draw loop" because p5.js calls the code in
        + * `draw()` in a loop behind the scenes. By default, `draw()` tries to run
        + * 60 times per second. The actual rate depends on many factors. The
        + * drawing rate, called the "frame rate", can be controlled by calling
        + * <a href="#/p5/frameRate">frameRate()</a>. The number of times `draw()`
        + * has run is stored in the system variable
        + * <a href="#/p5/frameCount">frameCount()</a>.
        + *
        + * Code placed within `draw()` begins looping after
        + * <a href="#/p5/setup">setup()</a> runs. `draw()` will run until the user
        + * closes the sketch. `draw()` can be stopped by calling the
        + * <a href="#/p5/noLoop">noLoop()</a> function. `draw()` can be resumed by
        + * calling the <a href="#/p5/loop">loop()</a> function.
        + *
        + * @method draw
        + * @for p5
        + *
        + * @example
        + * <div>
        + * <code>
        + * function setup() {
        + *   createCanvas(100, 100);
        + *
        + *   // Paint the background once.
        + *   background(200);
        + *
        + *   describe(
        + *     'A white circle on a gray background. The circle follows the mouse as the user moves, leaving a trail.'
        + *   );
        + * }
        + *
        + * function draw() {
        + *   // Draw circles repeatedly.
        + *   circle(mouseX, mouseY, 40);
        + * }
        + * </code>
        + * </div>
        + *
        + * <div>
        + * <code>
        + * function setup() {
        + *   createCanvas(100, 100);
        + *
        + *   describe(
        + *     'A white circle on a gray background. The circle follows the mouse as the user moves.'
        + *   );
        + * }
        + *
        + * function draw() {
        + *   // Paint the background repeatedly.
        + *   background(200);
        + *
        + *   // Draw circles repeatedly.
        + *   circle(mouseX, mouseY, 40);
        + * }
        + * </code>
        + * </div>
        + *
        + * <div>
        + * <code>
        + * // Double-click the canvas to change the circle's color.
        + *
        + * function setup() {
        + *   createCanvas(100, 100);
        + *
        + *   describe(
        + *     'A white circle on a gray background. The circle follows the mouse as the user moves. The circle changes color to pink when the user double-clicks.'
        + *   );
        + * }
        + *
        + * function draw() {
        + *   // Paint the background repeatedly.
        + *   background(200);
        + *
        + *   // Draw circles repeatedly.
        + *   circle(mouseX, mouseY, 40);
        + * }
        + *
        + * // Change the fill color when the user double-clicks.
        + * function doubleClicked() {
        + *   fill('deeppink');
        + * }
        + * </code>
        + * </div>
        + */
         
         /**
          * Turns off the parts of the Friendly Error System (FES) that impact performance.
        @@ -909,31 +658,20 @@ p5.instance = null;
          */
         p5.disableFriendlyErrors = false;
         
        -// attach constants to p5 prototype
        -for (const k in constants) {
        -  p5.prototype[k] = constants[k];
        -}
        -
        -// makes the `VERSION` constant available on the p5 object
        -// in instance mode, even if it hasn't been instantiated yet
        -p5.VERSION = constants.VERSION;
        -
        -// functions that cause preload to wait
        -// more can be added by using registerPreloadMethod(func)
        -p5.prototype._preloadMethods = {
        -  loadJSON: p5.prototype,
        -  loadImage: p5.prototype,
        -  loadStrings: p5.prototype,
        -  loadXML: p5.prototype,
        -  loadBytes: p5.prototype,
        -  loadTable: p5.prototype,
        -  loadFont: p5.prototype,
        -  loadModel: p5.prototype,
        -  loadShader: p5.prototype
        -};
        -
        -p5.prototype._registeredMethods = { init: [], pre: [], post: [], remove: [] };
        -
        -p5.prototype._registeredPreloadMethods = {};
        +import transform from './transform';
        +import structure from './structure';
        +import environment from './environment';
        +import rendering from './rendering';
        +import renderer from './p5.Renderer';
        +import renderer2D from './p5.Renderer2D';
        +import graphics from './p5.Graphics';
        +
        +p5.registerAddon(transform);
        +p5.registerAddon(structure);
        +p5.registerAddon(environment);
        +p5.registerAddon(rendering);
        +p5.registerAddon(renderer);
        +p5.registerAddon(renderer2D);
        +p5.registerAddon(graphics);
         
         export default p5;
        diff --git a/src/core/noop.js b/src/core/noop.js
        new file mode 100644
        index 0000000000..4767ab1ce7
        --- /dev/null
        +++ b/src/core/noop.js
        @@ -0,0 +1 @@
        +export default function(){}
        diff --git a/src/core/p5.Element.js b/src/core/p5.Element.js
        deleted file mode 100644
        index 3a42721d8c..0000000000
        --- a/src/core/p5.Element.js
        +++ /dev/null
        @@ -1,1023 +0,0 @@
        -/**
        - * @module DOM
        - * @submodule DOM
        - * @for p5.Element
        - */
        -
        -import p5 from './main';
        -
        -/**
        - * A class to describe an
        - * <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started" target="_blank">HTML element</a>.
        - *
        - * Sketches can use many elements. Common elements include the drawing canvas,
        - * buttons, sliders, webcam feeds, and so on.
        - *
        - * All elements share the methods of the `p5.Element` class. They're created
        - * with functions such as <a href="#/p5/createCanvas">createCanvas()</a> and
        - * <a href="#/p5/createButton">createButton()</a>.
        - *
        - * @class p5.Element
        - * @constructor
        - * @param {HTMLElement} elt wrapped DOM element.
        - * @param {p5} [pInst] pointer to p5 instance.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a button element and
        - *   // place it beneath the canvas.
        - *   let btn = createButton('change');
        - *   btn.position(0, 100);
        - *
        - *   // Call randomColor() when
        - *   // the button is pressed.
        - *   btn.mousePressed(randomColor);
        - *
        - *   describe('A gray square with a button that says "change" beneath it. The square changes color when the user presses the button.');
        - * }
        - *
        - * // Paint the background either
        - * // red, yellow, blue, or green.
        - * function randomColor() {
        - *   let c = random(['red', 'yellow', 'blue', 'green']);
        - *   background(c);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element = class {
        -  constructor(elt, pInst) {
        -    /**
        -     * The element's underlying `HTMLElement` object.
        -     *
        -     * The
        -     * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" target="_blank">HTMLElement</a>
        -     * object's properties and methods can be used directly.
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * function setup() {
        -     *   // Create a canvas element and
        -     *   // assign it to cnv.
        -     *   let cnv = createCanvas(100, 100);
        -     *
        -     *   background(200);
        -     *
        -     *   // Set the border style for the
        -     *   // canvas.
        -     *   cnv.elt.style.border = '5px dashed deeppink';
        -     *
        -     *   describe('A gray square with a pink border drawn with dashed lines.');
        -     * }
        -     * </code>
        -     * </div>
        -     *
        -     * @property elt
        -     * @name elt
        -     * @readOnly
        -     */
        -    this.elt = elt;
        -    /**
        -     * @private
        -     * @type {p5.Element}
        -     * @name _pInst
        -     */
        -    this._pInst = this._pixelsState = pInst;
        -    this._events = {};
        -    /**
        -     * A `Number` property that stores the element's width.
        -     *
        -     * @type {Number}
        -     * @property width
        -     * @name width
        -     */
        -    this.width = this.elt.offsetWidth;
        -    /**
        -     * A `Number` property that stores the element's height.
        -     *
        -     * @type {Number}
        -     * @property height
        -     * @name height
        -     */
        -    this.height = this.elt.offsetHeight;
        -  }
        -
        -  /**
        -   * Attaches the element to a parent element.
        -   *
        -   * For example, a `&lt;div&gt;&lt;/div&gt;` element may be used as a box to
        -   * hold two pieces of text, a header and a paragraph. The
        -   * `&lt;div&gt;&lt;/div&gt;` is the parent element of both the header and
        -   * paragraph.
        -   *
        -   * The parameter `parent` can have one of three types. `parent` can be a
        -   * string with the parent element's ID, as in
        -   * `myElement.parent('container')`. It can also be another
        -   * <a href="#/p5.Element">p5.Element</a> object, as in
        -   * `myElement.parent(myDiv)`. Finally, `parent` can be an `HTMLElement`
        -   * object, as in `myElement.parent(anotherElement)`.
        -   *
        -   * Calling `myElement.parent()` without an argument returns the element's
        -   * parent.
        -   *
        -   * @method parent
        -   * @param  {String|p5.Element|Object} parent ID, <a href="#/p5.Element">p5.Element</a>,
        -   *                                           or HTMLElement of desired parent element.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup()  {
        -   *   background(200);
        -   *
        -   *   // Create a div element.
        -   *   let div = createDiv();
        -   *
        -   *   // Place the div in the top-left corner.
        -   *   div.position(10, 20);
        -   *
        -   *   // Set its width and height.
        -   *   div.size(80, 60);
        -   *
        -   *   // Set its background color to white
        -   *   div.style('background-color', 'white');
        -   *
        -   *   // Align any text to the center.
        -   *   div.style('text-align', 'center');
        -   *
        -   *   // Set its ID to "container".
        -   *   div.id('container');
        -   *
        -   *   // Create a paragraph element.
        -   *   let p = createP('p5*js');
        -   *
        -   *   // Make the div its parent
        -   *   // using its ID "container".
        -   *   p.parent('container');
        -   *
        -   *   describe('The text "p5*js" written in black at the center of a white rectangle. The rectangle is inside a gray square.');
        -   * }
        -   * </code>
        -   * </div>
        -   *
        -   * <div>
        -   * <code>
        -   * function setup()  {
        -   *   background(200);
        -   *
        -   *   // Create rectangular div element.
        -   *   let div = createDiv();
        -   *
        -   *   // Place the div in the top-left corner.
        -   *   div.position(10, 20);
        -   *
        -   *   // Set its width and height.
        -   *   div.size(80, 60);
        -   *
        -   *   // Set its background color and align
        -   *   // any text to the center.
        -   *   div.style('background-color', 'white');
        -   *   div.style('text-align', 'center');
        -   *
        -   *   // Create a paragraph element.
        -   *   let p = createP('p5*js');
        -   *
        -   *   // Make the div its parent.
        -   *   p.parent(div);
        -   *
        -   *   describe('The text "p5*js" written in black at the center of a white rectangle. The rectangle is inside a gray square.');
        -   * }
        -   * </code>
        -   * </div>
        -   *
        -   * <div>
        -   * <code>
        -   * function setup()  {
        -   *   background(200);
        -   *
        -   *   // Create rectangular div element.
        -   *   let div = createDiv();
        -   *
        -   *   // Place the div in the top-left corner.
        -   *   div.position(10, 20);
        -   *
        -   *   // Set its width and height.
        -   *   div.size(80, 60);
        -   *
        -   *   // Set its background color and align
        -   *   // any text to the center.
        -   *   div.style('background-color', 'white');
        -   *   div.style('text-align', 'center');
        -   *
        -   *   // Create a paragraph element.
        -   *   let p = createP('p5*js');
        -   *
        -   *   // Make the div its parent
        -   *   // using the underlying
        -   *   // HTMLElement.
        -   *   p.parent(div.elt);
        -   *
        -   *   describe('The text "p5*js" written in black at the center of a white rectangle. The rectangle is inside a gray square.');
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  /**
        -   * @method parent
        -   * @return {p5.Element}
        -   */
        -  parent(p) {
        -    if (typeof p === 'undefined') {
        -      return this.elt.parentNode;
        -    }
        -
        -    if (typeof p === 'string') {
        -      if (p[0] === '#') {
        -        p = p.substring(1);
        -      }
        -      p = document.getElementById(p);
        -    } else if (p instanceof p5.Element) {
        -      p = p.elt;
        -    }
        -    p.appendChild(this.elt);
        -    return this;
        -  }
        -
        -  /**
        -   * Sets the element's ID using a given string.
        -   *
        -   * Calling `myElement.id()` without an argument returns its ID as a string.
        -   *
        -   * @method id
        -   * @param  {String} id ID of the element.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Set the canvas' ID
        -   *   // to "mycanvas".
        -   *   cnv.id('mycanvas');
        -   *
        -   *   // Get the canvas' ID.
        -   *   let id = cnv.id();
        -   *   text(id, 24, 54);
        -   *
        -   *   describe('The text "mycanvas" written in black on a gray background.');
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  /**
        -   * @method id
        -   * @return {String} ID of the element.
        -   */
        -  id(id) {
        -    if (typeof id === 'undefined') {
        -      return this.elt.id;
        -    }
        -
        -    this.elt.id = id;
        -    this.width = this.elt.offsetWidth;
        -    this.height = this.elt.offsetHeight;
        -    return this;
        -  }
        -
        -  /**
        -   * Adds a
        -   * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class" target="_blank">class attribute</a>
        -   * to the element using a given string.
        -   *
        -   * Calling `myElement.class()` without an argument returns a string with its current classes.
        -   *
        -   * @method class
        -   * @param  {String} class class to add.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Add the class "small" to the
        -   *   // canvas element.
        -   *   cnv.class('small');
        -   *
        -   *   // Get the canvas element's class
        -   *   // and display it.
        -   *   let c = cnv.class();
        -   *   text(c, 35, 54);
        -   *
        -   *   describe('The word "small" written in black on a gray canvas.');
        -   *
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  /**
        -   * @method class
        -   * @return {String} element's classes, if any.
        -   */
        -  class(c) {
        -    if (typeof c === 'undefined') {
        -      return this.elt.className;
        -    }
        -
        -    this.elt.className = c;
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when the mouse is pressed over the element.
        -   *
        -   * Calling `myElement.mousePressed(false)` disables the function.
        -   *
        -   * Note: Some mobile browsers may also trigger this event when the element
        -   * receives a quick tap.
        -   *
        -   * @method mousePressed
        -   * @param  {Function|Boolean} fxn function to call when the mouse is
        -   *                                pressed over the element.
        -   *                                `false` disables the function.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call randomColor() when the canvas
        -   *   // is pressed.
        -   *   cnv.mousePressed(randomColor);
        -   *
        -   *   describe('A gray square changes color when the mouse is pressed.');
        -   * }
        -   *
        -   * // Paint the background either
        -   * // red, yellow, blue, or green.
        -   * function randomColor() {
        -   *   let c = random(['red', 'yellow', 'blue', 'green']);
        -   *   background(c);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  mousePressed(fxn) {
        -    // Prepend the mouse property setters to the event-listener.
        -    // This is required so that mouseButton is set correctly prior to calling the callback (fxn).
        -    // For details, see https://github.com/processing/p5.js/issues/3087.
        -    const eventPrependedFxn = function (event) {
        -      this._pInst._setProperty('mouseIsPressed', true);
        -      this._pInst._setMouseButton(event);
        -      // Pass along the return-value of the callback:
        -      return fxn.call(this, event);
        -    };
        -    // Pass along the event-prepended form of the callback.
        -    p5.Element._adjustListener('mousedown', eventPrependedFxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when the mouse is pressed twice over the element.
        -   *
        -   * Calling `myElement.doubleClicked(false)` disables the function.
        -   *
        -   * @method doubleClicked
        -   * @param  {Function|Boolean} fxn function to call when the mouse is
        -   *                                double clicked over the element.
        -   *                                `false` disables the function.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call randomColor() when the
        -   *   // canvas is double-clicked.
        -   *   cnv.doubleClicked(randomColor);
        -   *
        -   *   describe('A gray square changes color when the user double-clicks the canvas.');
        -   * }
        -   *
        -   * // Paint the background either
        -   * // red, yellow, blue, or green.
        -   * function randomColor() {
        -   *   let c = random(['red', 'yellow', 'blue', 'green']);
        -   *   background(c);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  doubleClicked(fxn) {
        -    p5.Element._adjustListener('dblclick', fxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when the mouse wheel scrolls over the element.
        -   *
        -   * The callback function, `fxn`, is passed an `event` object. `event` has
        -   * two numeric properties, `deltaY` and `deltaX`. `event.deltaY` is
        -   * negative if the mouse wheel rotates away from the user. It's positive if
        -   * the mouse wheel rotates toward the user. `event.deltaX` is positive if
        -   * the mouse wheel moves to the right. It's negative if the mouse wheel moves
        -   * to the left.
        -   *
        -   * Calling `myElement.mouseWheel(false)` disables the function.
        -   *
        -   * @method mouseWheel
        -   * @param  {Function|Boolean} fxn function to call when the mouse wheel is
        -   *                                scrolled over the element.
        -   *                                `false` disables the function.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call randomColor() when the
        -   *   // mouse wheel moves.
        -   *   cnv.mouseWheel(randomColor);
        -   *
        -   *   describe('A gray square changes color when the user scrolls the mouse wheel over the canvas.');
        -   * }
        -   *
        -   * // Paint the background either
        -   * // red, yellow, blue, or green.
        -   * function randomColor() {
        -   *   let c = random(['red', 'yellow', 'blue', 'green']);
        -   *   background(c);
        -   * }
        -   * </code>
        -   * </div>
        -   *
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call changeBackground() when the
        -   *   // mouse wheel moves.
        -   *   cnv.mouseWheel(changeBackground);
        -   *
        -   *   describe('A gray square. When the mouse wheel scrolls over the square, it changes color and displays shapes.');
        -   * }
        -   *
        -   * function changeBackground(event) {
        -   *   // Change the background color
        -   *   // based on deltaY.
        -   *   if (event.deltaY > 0) {
        -   *     background('deeppink');
        -   *   } else if (event.deltaY < 0) {
        -   *     background('cornflowerblue');
        -   *   } else {
        -   *     background(200);
        -   *   }
        -   *
        -   *   // Draw a shape based on deltaX.
        -   *   if (event.deltaX > 0) {
        -   *     circle(50, 50, 20);
        -   *   } else if (event.deltaX < 0) {
        -   *     square(40, 40, 20);
        -   *   }
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  mouseWheel(fxn) {
        -    p5.Element._adjustListener('wheel', fxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when the mouse is released over the element.
        -   *
        -   * Calling `myElement.mouseReleased(false)` disables the function.
        -   *
        -   * Note: Some mobile browsers may also trigger this event when the element
        -   * receives a quick tap.
        -   *
        -   * @method mouseReleased
        -   * @param  {Function|Boolean} fxn function to call when the mouse is
        -   *                                pressed over the element.
        -   *                                `false` disables the function.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call randomColor() when a
        -   *   // mouse press ends.
        -   *   cnv.mouseReleased(randomColor);
        -   *
        -   *   describe('A gray square changes color when the user releases a mouse press.');
        -   * }
        -   *
        -   * // Paint the background either
        -   * // red, yellow, blue, or green.
        -   * function randomColor() {
        -   *   let c = random(['red', 'yellow', 'blue', 'green']);
        -   *   background(c);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  mouseReleased(fxn) {
        -    p5.Element._adjustListener('mouseup', fxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when the mouse is pressed and released over the element.
        -   *
        -   * Calling `myElement.mouseReleased(false)` disables the function.
        -   *
        -   * Note: Some mobile browsers may also trigger this event when the element
        -   * receives a quick tap.
        -   *
        -   * @method mouseClicked
        -   * @param  {Function|Boolean} fxn function to call when the mouse is
        -   *                                pressed and released over the element.
        -   *                                `false` disables the function.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call randomColor() when a
        -   *   // mouse press ends.
        -   *   cnv.mouseClicked(randomColor);
        -   *
        -   *   describe('A gray square changes color when the user releases a mouse press.');
        -   * }
        -   *
        -   * // Paint the background either
        -   * // red, yellow, blue, or green.
        -   * function randomColor() {
        -   *   let c = random(['red', 'yellow', 'blue', 'green']);
        -   *   background(c);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  mouseClicked(fxn) {
        -    p5.Element._adjustListener('click', fxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when the mouse moves over the element.
        -   *
        -   * Calling `myElement.mouseMoved(false)` disables the function.
        -   *
        -   * @method mouseMoved
        -   * @param  {Function|Boolean} fxn function to call when the mouse
        -   *                                moves over the element.
        -   *                                `false` disables the function.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call randomColor() when the
        -   *   // mouse moves.
        -   *   cnv.mouseMoved(randomColor);
        -   *
        -   *   describe('A gray square changes color when the mouse moves over the canvas.');
        -   * }
        -   *
        -   * // Paint the background either
        -   * // red, yellow, blue, or green.
        -   * function randomColor() {
        -   *   let c = random(['red', 'yellow', 'blue', 'green']);
        -   *   background(c);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  mouseMoved(fxn) {
        -    p5.Element._adjustListener('mousemove', fxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when the mouse moves onto the element.
        -   *
        -   * Calling `myElement.mouseOver(false)` disables the function.
        -   *
        -   * @method mouseOver
        -   * @param  {Function|Boolean} fxn function to call when the mouse
        -   *                                moves onto the element.
        -   *                                `false` disables the function.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call randomColor() when the
        -   *   // mouse moves onto the canvas.
        -   *   cnv.mouseOver(randomColor);
        -   *
        -   *   describe('A gray square changes color when the mouse moves onto the canvas.');
        -   * }
        -   *
        -   * // Paint the background either
        -   * // red, yellow, blue, or green.
        -   * function randomColor() {
        -   *   let c = random(['red', 'yellow', 'blue', 'green']);
        -   *   background(c);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  mouseOver(fxn) {
        -    p5.Element._adjustListener('mouseover', fxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when the mouse moves off the element.
        -   *
        -   * Calling `myElement.mouseOut(false)` disables the function.
        -   *
        -   * @method mouseOut
        -   * @param  {Function|Boolean} fxn function to call when the mouse
        -   *                                moves off the element.
        -   *                                `false` disables the function.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call randomColor() when the
        -   *   // mouse moves off the canvas.
        -   *   cnv.mouseOut(randomColor);
        -   *
        -   *   describe('A gray square changes color when the mouse moves off the canvas.');
        -   * }
        -   *
        -   * // Paint the background either
        -   * // red, yellow, blue, or green.
        -   * function randomColor() {
        -   *   let c = random(['red', 'yellow', 'blue', 'green']);
        -   *   background(c);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  mouseOut(fxn) {
        -    p5.Element._adjustListener('mouseout', fxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when the element is touched.
        -   *
        -   * Calling `myElement.touchStarted(false)` disables the function.
        -   *
        -   * Note: Touch functions only work on mobile devices.
        -   *
        -   * @method touchStarted
        -   * @param  {Function|Boolean} fxn function to call when the touch
        -   *                                starts.
        -   *                                `false` disables the function.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call randomColor() when the
        -   *   // user touches the canvas.
        -   *   cnv.touchStarted(randomColor);
        -   *
        -   *   describe('A gray square changes color when the user touches the canvas.');
        -   * }
        -   *
        -   * // Paint the background either
        -   * // red, yellow, blue, or green.
        -   * function randomColor() {
        -   *   let c = random(['red', 'yellow', 'blue', 'green']);
        -   *   background(c);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  touchStarted(fxn) {
        -    p5.Element._adjustListener('touchstart', fxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when the user touches the element and moves.
        -   *
        -   * Calling `myElement.touchMoved(false)` disables the function.
        -   *
        -   * Note: Touch functions only work on mobile devices.
        -   *
        -   * @method touchMoved
        -   * @param  {Function|Boolean} fxn function to call when the touch
        -   *                                moves over the element.
        -   *                                `false` disables the function.
        -   * @chainable
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call randomColor() when the
        -   *   // user touches the canvas
        -   *   // and moves.
        -   *   cnv.touchMoved(randomColor);
        -   *
        -   *   describe('A gray square changes color when the user touches the canvas and moves.');
        -   * }
        -   *
        -   * // Paint the background either
        -   * // red, yellow, blue, or green.
        -   * function randomColor() {
        -   *   let c = random(['red', 'yellow', 'blue', 'green']);
        -   *   background(c);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  touchMoved(fxn) {
        -    p5.Element._adjustListener('touchmove', fxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when the user stops touching the element.
        -   *
        -   * Calling `myElement.touchMoved(false)` disables the function.
        -   *
        -   * Note: Touch functions only work on mobile devices.
        -   *
        -   * @method touchEnded
        -   * @param  {Function|Boolean} fxn function to call when the touch
        -   *                                ends.
        -   *                                `false` disables the function.
        -   * @chainable
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call randomColor() when the
        -   *   // user touches the canvas,
        -   *   // then lifts their finger.
        -   *   cnv.touchEnded(randomColor);
        -   *
        -   *   describe('A gray square changes color when the user touches the canvas, then lifts their finger.');
        -   * }
        -   *
        -   * // Paint the background either
        -   * // red, yellow, blue, or green.
        -   * function randomColor() {
        -   *   let c = random(['red', 'yellow', 'blue', 'green']);
        -   *   background(c);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  touchEnded(fxn) {
        -    p5.Element._adjustListener('touchend', fxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when a file is dragged over the element.
        -   *
        -   * Calling `myElement.dragOver(false)` disables the function.
        -   *
        -   * @method dragOver
        -   * @param  {Function|Boolean} fxn function to call when the file is
        -   *                                dragged over the element.
        -   *                                `false` disables the function.
        -   * @chainable
        -   *
        -   * @example
        -   * <div>
        -   * <code>
        -   * // Drag a file over the canvas to test.
        -   *
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call helloFile() when a
        -   *   // file is dragged over
        -   *   // the canvas.
        -   *   cnv.dragOver(helloFile);
        -   *
        -   *   describe('A gray square. The text "hello, file" appears when a file is dragged over the square.');
        -   * }
        -   *
        -   * function helloFile() {
        -   *   text('hello, file', 50, 50);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  dragOver(fxn) {
        -    p5.Element._adjustListener('dragover', fxn, this);
        -    return this;
        -  }
        -
        -  /**
        -   * Calls a function when a file is dragged off the element.
        -   *
        -   * Calling `myElement.dragLeave(false)` disables the function.
        -   *
        -   * @method dragLeave
        -   * @param  {Function|Boolean} fxn function to call when the file is
        -   *                                dragged off the element.
        -   *                                `false` disables the function.
        -   * @chainable
        -   * @example
        -   * <div>
        -   * <code>
        -   * // Drag a file over, then off
        -   * // the canvas to test.
        -   *
        -   * function setup() {
        -   *   // Create a canvas element and
        -   *   // assign it to cnv.
        -   *   let cnv = createCanvas(100, 100);
        -   *
        -   *   background(200);
        -   *
        -   *   // Call byeFile() when a
        -   *   // file is dragged over,
        -   *   // then off the canvas.
        -   *   cnv.dragLeave(byeFile);
        -   *
        -   *   describe('A gray square. The text "bye, file" appears when a file is dragged over, then off the square.');
        -   * }
        -   *
        -   * function byeFile() {
        -   *   text('bye, file', 50, 50);
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -  dragLeave(fxn) {
        -    p5.Element._adjustListener('dragleave', fxn, this);
        -    return this;
        -  }
        -
        -
        -  /**
        -   *
        -   * @private
        -   * @static
        -   * @param {String} ev
        -   * @param {Boolean|Function} fxn
        -   * @param {Element} ctx
        -   * @chainable
        -   * @alt
        -   * General handler for event attaching and detaching
        -   */
        -  static _adjustListener(ev, fxn, ctx) {
        -    if (fxn === false) {
        -      p5.Element._detachListener(ev, ctx);
        -    } else {
        -      p5.Element._attachListener(ev, fxn, ctx);
        -    }
        -    return this;
        -  }
        -  /**
        -   *
        -   * @private
        -   * @static
        -   * @param {String} ev
        -   * @param {Function} fxn
        -   * @param {Element} ctx
        -   */
        -  static _attachListener(ev, fxn, ctx) {
        -    // detach the old listener if there was one
        -    if (ctx._events[ev]) {
        -      p5.Element._detachListener(ev, ctx);
        -    }
        -    const f = fxn.bind(ctx);
        -    ctx.elt.addEventListener(ev, f, false);
        -    ctx._events[ev] = f;
        -  }
        -  /**
        -   *
        -   * @private
        -   * @static
        -   * @param {String} ev
        -   * @param {Element} ctx
        -   */
        -  static _detachListener(ev, ctx) {
        -    const f = ctx._events[ev];
        -    ctx.elt.removeEventListener(ev, f, false);
        -    ctx._events[ev] = null;
        -  }
        -
        -  /**
        -   * Helper fxn for sharing pixel methods
        -   */
        -  _setProperty(prop, value) {
        -    this[prop] = value;
        -  }
        -};
        -
        -export default p5.Element;
        diff --git a/src/core/p5.Graphics.js b/src/core/p5.Graphics.js
        index 07ddb54301..403f934e2c 100644
        --- a/src/core/p5.Graphics.js
        +++ b/src/core/p5.Graphics.js
        @@ -4,312 +4,240 @@
          * @for p5
          */
         
        -import p5 from './main';
         import * as constants from './constants';
        +import { RGB, HSB, HSL } from '../color/creating_reading';
        +import primitives2D from '../shape/2d_primitives';
        +import attributes from '../shape/attributes';
        +import curves from '../shape/curves';
        +import vertex from '../shape/vertex';
        +import setting from '../color/setting';
        +import image from '../image/image';
        +import loadingDisplaying from '../image/loading_displaying';
        +import pixels from '../image/pixels';
        +import transform from './transform';
        +import { Framebuffer } from '../webgl/p5.Framebuffer';
         
        -/**
        - * A class to describe a drawing surface that's separate from the main canvas.
        - *
        - * Each `p5.Graphics` object provides a dedicated drawing surface called a
        - * *graphics buffer*. Graphics buffers are helpful when drawing should happen
        - * offscreen. For example, separate scenes can be drawn offscreen and
        - * displayed only when needed.
        - *
        - * `p5.Graphics` objects have nearly all the drawing features of the main
        - * canvas. For example, calling the method `myGraphics.circle(50, 50, 20)`
        - * draws to the graphics buffer. The resulting image can be displayed on the
        - * main canvas by passing the `p5.Graphics` object to the
        - * <a href="#/p5/image">image()</a> function, as in `image(myGraphics, 0, 0)`.
        - *
        - * Note: <a href="#/p5/createGraphics">createGraphics()</a> is the recommended
        - * way to create an instance of this class.
        - *
        - * @class p5.Graphics
        - * @constructor
        - * @extends p5.Element
        - * @param {Number} width width of the graphics buffer in pixels.
        - * @param {Number} height height of the graphics buffer in pixels.
        - * @param {Constant} renderer renderer to use, either P2D or WEBGL.
        - * @param {p5} [pInst] sketch instance.
        - * @param {HTMLCanvasElement} [canvas] existing `&lt;canvas&gt;` element to use.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let pg;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.Graphics object.
        - *   pg = createGraphics(50, 50);
        - *
        - *   // Draw to the p5.Graphics object.
        - *   pg.background(100);
        - *   pg.circle(25, 25, 20);
        - *
        - *   describe('A dark gray square with a white circle at its center drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Display the p5.Graphics object.
        - *   image(pg, 25, 25);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click the canvas to display the graphics buffer.
        - *
        - * let pg;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.Graphics object.
        - *   pg = createGraphics(50, 50);
        - *
        - *   describe('A square appears on a gray background when the user presses the mouse. The square cycles between white and black.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the background color.
        - *   let bg = frameCount % 255;
        - *
        - *   // Draw to the p5.Graphics object.
        - *   pg.background(bg);
        - *
        - *   // Display the p5.Graphics object while
        - *   // the user presses the mouse.
        - *   if (mouseIsPressed === true) {
        - *     image(pg, 25, 25);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Graphics = class extends p5.Element {
        +import primitives3D from '../webgl/3d_primitives';
        +import light from '../webgl/light';
        +import material from '../webgl/material';
        +import creatingReading from '../color/creating_reading';
        +import trigonometry from '../math/trigonometry';
        +import { renderers } from './rendering';
        +
        +class Graphics {
           constructor(w, h, renderer, pInst, canvas) {
        -    let canvasTemp;
        -    if (canvas) {
        -      canvasTemp = canvas;
        -    } else {
        -      canvasTemp = document.createElement('canvas');
        -    }
        +    const r = renderer || constants.P2D;
         
        -    super(canvasTemp, pInst);
        -    this.canvas = canvasTemp;
        +    this._pInst = pInst;
        +    this._renderer = new renderers[r](this._pInst, w, h, false, canvas);
         
        -    const r = renderer || constants.P2D;
        +    this._initializeInstanceVariables(this);
         
        -    const node = pInst._userNode || document.body;
        -    if (!canvas) {
        -      node.appendChild(this.canvas);
        -    }
        +    this._renderer._applyDefaults();
        +    return this;
        +  }
         
        -    // bind methods and props of p5 to the new object
        -    for (const p in p5.prototype) {
        -      if (!this[p]) {
        -        if (typeof p5.prototype[p] === 'function') {
        -          this[p] = p5.prototype[p].bind(this);
        -        } else {
        -          this[p] = p5.prototype[p];
        -        }
        -      }
        -    }
        +  get deltaTime(){
        +    return this._pInst.deltaTime;
        +  }
         
        -    p5.prototype._initializeInstanceVariables.apply(this);
        -    this.width = w;
        -    this.height = h;
        -    this._pixelDensity = pInst._pixelDensity;
        +  get canvas(){
        +    return this._renderer?.canvas;
        +  }
         
        -    if (r === constants.WEBGL) {
        -      this._renderer = new p5.RendererGL(this.canvas, this, false);
        -      const { adjustedWidth, adjustedHeight } =
        -        this._renderer._adjustDimensions(w, h);
        -      w = adjustedWidth;
        -      h = adjustedHeight;
        -    } else {
        -      this._renderer = new p5.Renderer2D(this.canvas, this, false);
        -    }
        -    pInst._elements.push(this);
        +  get drawingContext(){
        +    return this._renderer.drawingContext;
        +  }
        +
        +  get width(){
        +    return this._renderer?.width;
        +  }
        +
        +  get height(){
        +    return this._renderer?.height;
        +  }
        +
        +  get pixels(){
        +    return this._renderer?.pixels;
        +  }
         
        -    Object.defineProperty(this, 'deltaTime', {
        -      get() {
        -        return this._pInst.deltaTime;
        +  pixelDensity(val){
        +    let returnValue;
        +    if (typeof val === 'number') {
        +      if (val !== this._renderer._pixelDensity) {
        +        this._renderer._pixelDensity = val;
               }
        -    });
        +      returnValue = this;
        +      this.resizeCanvas(this.width, this.height, true); // as a side effect, it will clear the canvas
        +    } else {
        +      returnValue = this._renderer._pixelDensity;
        +    }
        +    return returnValue;
        +  }
         
        +  resizeCanvas(w, h){
             this._renderer.resize(w, h);
        -    this._renderer._applyDefaults();
        -    return this;
           }
         
           /**
        - * Resets the graphics buffer's transformations and lighting.
        - *
        - * By default, the main canvas resets certain transformation and lighting
        - * values each time <a href="#/p5/draw">draw()</a> executes. `p5.Graphics`
        - * objects must reset these values manually by calling `myGraphics.reset()`.
        - *
        - * @method reset
        - *
        - * @example
        - * <div>
        - * <code>
        - * let pg;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.Graphics object.
        - *   pg = createGraphics(60, 60);
        - *
        - *   describe('A white circle moves downward slowly within a dark square. The circle resets at the top of the dark square when the user presses the mouse.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the p5.Graphics object's coordinate system.
        - *   // The translation accumulates; the white circle moves.
        - *   pg.translate(0, 0.1);
        - *
        - *   // Draw to the p5.Graphics object.
        - *   pg.background(100);
        - *   pg.circle(30, 0, 10);
        - *
        - *   // Display the p5.Graphics object.
        - *   image(pg, 20, 20);
        - *
        - *   // Translate the main canvas' coordinate system.
        - *   // The translation doesn't accumulate; the dark
        - *   // square is always in the same place.
        - *   translate(0, 0.1);
        - *
        - *   // Reset the p5.Graphics object when the
        - *   // user presses the mouse.
        - *   if (mouseIsPressed === true) {
        - *     pg.reset();
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let pg;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.Graphics object.
        - *   pg = createGraphics(60, 60);
        - *
        - *   describe('A white circle at the center of a dark gray square. The image is drawn on a light gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the p5.Graphics object's coordinate system.
        - *   pg.translate(30, 30);
        - *
        - *   // Draw to the p5.Graphics object.
        - *   pg.background(100);
        - *   pg.circle(0, 0, 10);
        - *
        - *   // Display the p5.Graphics object.
        - *   image(pg, 20, 20);
        - *
        - *   // Reset the p5.Graphics object automatically.
        - *   pg.reset();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let pg;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.Graphics object using WebGL mode.
        - *   pg = createGraphics(100, 100, WEBGL);
        - *
        - *   describe("A sphere lit from above with a red light. The sphere's surface becomes glossy while the user clicks and holds the mouse.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Add a red point light from the top-right.
        - *   pg.pointLight(255, 0, 0, 50, -100, 50);
        - *
        - *   // Style the sphere.
        - *   // It should appear glossy when the
        - *   // lighting values are reset.
        - *   pg.noStroke();
        - *   pg.specularMaterial(255);
        - *   pg.shininess(100);
        - *
        - *   // Draw the sphere.
        - *   pg.sphere(30);
        - *
        - *   // Display the p5.Graphics object.
        - *   image(pg, -50, -50);
        - *
        - *   // Reset the p5.Graphics object when
        - *   // the user presses the mouse.
        - *   if (mouseIsPressed === true) {
        - *     pg.reset();
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let pg;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.Graphics object using WebGL mode.
        - *   pg = createGraphics(100, 100, WEBGL);
        - *
        - *   describe('A sphere with a glossy surface is lit from the top-right by a red light.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Add a red point light from the top-right.
        - *   pg.pointLight(255, 0, 0, 50, -100, 50);
        - *
        - *   // Style the sphere.
        - *   pg.noStroke();
        - *   pg.specularMaterial(255);
        - *   pg.shininess(100);
        - *
        - *   // Draw the sphere.
        - *   pg.sphere(30);
        - *
        - *   // Display the p5.Graphics object.
        - *   image(pg, 0, 0);
        - *
        - *   // Reset the p5.Graphics object automatically.
        - *   pg.reset();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Resets the graphics buffer's transformations and lighting.
        +   *
        +   * By default, the main canvas resets certain transformation and lighting
        +   * values each time <a href="#/p5/draw">draw()</a> executes. `p5.Graphics`
        +   * objects must reset these values manually by calling `myGraphics.reset()`.
        +   *
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let pg;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.Graphics object.
        +   *   pg = createGraphics(60, 60);
        +   *
        +   *   describe('A white circle moves downward slowly within a dark square. The circle resets at the top of the dark square when the user presses the mouse.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the p5.Graphics object's coordinate system.
        +   *   // The translation accumulates; the white circle moves.
        +   *   pg.translate(0, 0.1);
        +   *
        +   *   // Draw to the p5.Graphics object.
        +   *   pg.background(100);
        +   *   pg.circle(30, 0, 10);
        +   *
        +   *   // Display the p5.Graphics object.
        +   *   image(pg, 20, 20);
        +   *
        +   *   // Translate the main canvas' coordinate system.
        +   *   // The translation doesn't accumulate; the dark
        +   *   // square is always in the same place.
        +   *   translate(0, 0.1);
        +   *
        +   *   // Reset the p5.Graphics object when the
        +   *   // user presses the mouse.
        +   *   if (mouseIsPressed === true) {
        +   *     pg.reset();
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let pg;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.Graphics object.
        +   *   pg = createGraphics(60, 60);
        +   *
        +   *   describe('A white circle at the center of a dark gray square. The image is drawn on a light gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the p5.Graphics object's coordinate system.
        +   *   pg.translate(30, 30);
        +   *
        +   *   // Draw to the p5.Graphics object.
        +   *   pg.background(100);
        +   *   pg.circle(0, 0, 10);
        +   *
        +   *   // Display the p5.Graphics object.
        +   *   image(pg, 20, 20);
        +   *
        +   *   // Reset the p5.Graphics object automatically.
        +   *   pg.reset();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let pg;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.Graphics object using WebGL mode.
        +   *   pg = createGraphics(100, 100, WEBGL);
        +   *
        +   *   describe("A sphere lit from above with a red light. The sphere's surface becomes glossy while the user clicks and holds the mouse.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Add a red point light from the top-right.
        +   *   pg.pointLight(255, 0, 0, 50, -100, 50);
        +   *
        +   *   // Style the sphere.
        +   *   // It should appear glossy when the
        +   *   // lighting values are reset.
        +   *   pg.noStroke();
        +   *   pg.specularMaterial(255);
        +   *   pg.shininess(100);
        +   *
        +   *   // Draw the sphere.
        +   *   pg.sphere(30);
        +   *
        +   *   // Display the p5.Graphics object.
        +   *   image(pg, -50, -50);
        +   *
        +   *   // Reset the p5.Graphics object when
        +   *   // the user presses the mouse.
        +   *   if (mouseIsPressed === true) {
        +   *     pg.reset();
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let pg;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.Graphics object using WebGL mode.
        +   *   pg = createGraphics(100, 100, WEBGL);
        +   *
        +   *   describe('A sphere with a glossy surface is lit from the top-right by a red light.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Add a red point light from the top-right.
        +   *   pg.pointLight(255, 0, 0, 50, -100, 50);
        +   *
        +   *   // Style the sphere.
        +   *   pg.noStroke();
        +   *   pg.specularMaterial(255);
        +   *   pg.shininess(100);
        +   *
        +   *   // Draw the sphere.
        +   *   pg.sphere(30);
        +   *
        +   *   // Display the p5.Graphics object.
        +   *   image(pg, 0, 0);
        +   *
        +   *   // Reset the p5.Graphics object automatically.
        +   *   pg.reset();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           reset() {
             this._renderer.resetMatrix();
             if (this._renderer.isP3D) {
        @@ -318,82 +246,68 @@ p5.Graphics = class extends p5.Element {
           }
         
           /**
        - * Removes the graphics buffer from the web page.
        - *
        - * Calling `myGraphics.remove()` removes the graphics buffer's
        - * `&lt;canvas&gt;` element from the web page. The graphics buffer also uses
        - * a bit of memory on the CPU that can be freed like so:
        - *
        - * ```js
        - * // Remove the graphics buffer from the web page.
        - * myGraphics.remove();
        - *
        - * // Delete the graphics buffer from CPU memory.
        - * myGraphics = undefined;
        - * ```
        - *
        - * Note: All variables that reference the graphics buffer must be assigned
        - * the value `undefined` to delete the graphics buffer from CPU memory. If any
        - * variable still refers to the graphics buffer, then it won't be garbage
        - * collected.
        - *
        - * @method remove
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to remove the p5.Graphics object.
        - *
        - * let pg;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.Graphics object.
        - *   pg = createGraphics(60, 60);
        - *
        - *   // Draw to the p5.Graphics object.
        - *   pg.background(100);
        - *   pg.circle(30, 30, 20);
        - *
        - *   describe('A white circle at the center of a dark gray square disappears when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Display the p5.Graphics object if
        - *   // it's available.
        - *   if (pg) {
        - *     image(pg, 20, 20);
        - *   }
        - * }
        - *
        - * // Remove the p5.Graphics object when the
        - * // the user double-clicks.
        - * function doubleClicked() {
        - *   // Remove the p5.Graphics object from the web page.
        - *   pg.remove();
        - *   pg = undefined;
        - * }
        - * </code>
        - * </div>
        - */
        +   * Removes the graphics buffer from the web page.
        +   *
        +   * Calling `myGraphics.remove()` removes the graphics buffer's
        +   * `&lt;canvas&gt;` element from the web page. The graphics buffer also uses
        +   * a bit of memory on the CPU that can be freed like so:
        +   *
        +   * ```js
        +   * // Remove the graphics buffer from the web page.
        +   * myGraphics.remove();
        +   *
        +   * // Delete the graphics buffer from CPU memory.
        +   * myGraphics = undefined;
        +   * ```
        +   *
        +   * Note: All variables that reference the graphics buffer must be assigned
        +   * the value `undefined` to delete the graphics buffer from CPU memory. If any
        +   * variable still refers to the graphics buffer, then it won't be garbage
        +   * collected.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to remove the p5.Graphics object.
        +   *
        +   * let pg;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.Graphics object.
        +   *   pg = createGraphics(60, 60);
        +   *
        +   *   // Draw to the p5.Graphics object.
        +   *   pg.background(100);
        +   *   pg.circle(30, 30, 20);
        +   *
        +   *   describe('A white circle at the center of a dark gray square disappears when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Display the p5.Graphics object if
        +   *   // it's available.
        +   *   if (pg) {
        +   *     image(pg, 20, 20);
        +   *   }
        +   * }
        +   *
        +   * // Remove the p5.Graphics object when the
        +   * // the user double-clicks.
        +   * function doubleClicked() {
        +   *   // Remove the p5.Graphics object from the web page.
        +   *   pg.remove();
        +   *   pg = undefined;
        +   * }
        +   * </code>
        +   * </div>
        +   */
           remove() {
        -    if (this.elt.parentNode) {
        -      this.elt.parentNode.removeChild(this.elt);
        -    }
        -    const idx = this._pInst._elements.indexOf(this);
        -    if (idx !== -1) {
        -      this._pInst._elements.splice(idx, 1);
        -    }
        -    for (const elt_ev in this._events) {
        -      this.elt.removeEventListener(elt_ev, this._events[elt_ev]);
        -    }
        -
        +    this._renderer.remove();
             this._renderer = undefined;
        -    this.canvas = undefined;
        -    this.elt = undefined;
           }
         
         
        @@ -426,7 +340,6 @@ p5.Graphics = class extends p5.Element {
            * If the `width`, `height`, or `density` attributes are set, they won't
            * automatically match the graphics buffer and must be changed manually.
            *
        -   * @method createFramebuffer
            * @param {Object} [options] configuration options.
            * @return {p5.Framebuffer} new framebuffer.
            *
        @@ -640,8 +553,147 @@ p5.Graphics = class extends p5.Element {
            * </div>
            */
           createFramebuffer(options) {
        -    return new p5.Framebuffer(this, options);
        +    return new Framebuffer(this._renderer, options);
        +  }
        +
        +  _assert3d(name) {
        +    if (!this._renderer.isP3D)
        +      throw new Error(
        +        `${name}() is only supported in WEBGL mode. If you'd like to use 3D graphics and WebGL, see  https://p5js.org/examples/form-3d-primitives.html for more information.`
        +      );
        +  };
        +
        +  _initializeInstanceVariables() {
        +    this._accessibleOutputs = {
        +      text: false,
        +      grid: false,
        +      textLabel: false,
        +      gridLabel: false
        +    };
        +
        +    this._styles = [];
        +
        +    this._bezierDetail = 20;
        +    this._curveDetail = 20;
        +
        +    // this._colorMode = RGB;
        +    // this._colorMaxes = {
        +    //   rgb: [255, 255, 255, 255],
        +    //   hsb: [360, 100, 100, 1],
        +    //   hsl: [360, 100, 100, 1]
        +    // };
        +
        +    this._downKeys = {}; //Holds the key codes of currently pressed keys
           }
         };
         
        -export default p5.Graphics;
        +function graphics(p5, fn){
        +  /**
        +   * A class to describe a drawing surface that's separate from the main canvas.
        +   *
        +   * Each `p5.Graphics` object provides a dedicated drawing surface called a
        +   * *graphics buffer*. Graphics buffers are helpful when drawing should happen
        +   * offscreen. For example, separate scenes can be drawn offscreen and
        +   * displayed only when needed.
        +   *
        +   * `p5.Graphics` objects have nearly all the drawing features of the main
        +   * canvas. For example, calling the method `myGraphics.circle(50, 50, 20)`
        +   * draws to the graphics buffer. The resulting image can be displayed on the
        +   * main canvas by passing the `p5.Graphics` object to the
        +   * <a href="#/p5/image">image()</a> function, as in `image(myGraphics, 0, 0)`.
        +   *
        +   * Note: <a href="#/p5/createGraphics">createGraphics()</a> is the recommended
        +   * way to create an instance of this class.
        +   *
        +   * @class p5.Graphics
        +   * @extends p5.Element
        +   * @param {Number} w            width width of the graphics buffer in pixels.
        +   * @param {Number} h            height height of the graphics buffer in pixels.
        +   * @param {(P2D|WEBGL)} renderer   the renderer to use, either P2D or WEBGL.
        +   * @param {p5} [pInst]          sketch instance.
        +   * @param {HTMLCanvasElement} [canvas]     existing `&lt;canvas&gt;` element to use.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let pg;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.Graphics object.
        +   *   pg = createGraphics(50, 50);
        +   *
        +   *   // Draw to the p5.Graphics object.
        +   *   pg.background(100);
        +   *   pg.circle(25, 25, 20);
        +   *
        +   *   describe('A dark gray square with a white circle at its center drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Display the p5.Graphics object.
        +   *   image(pg, 25, 25);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click the canvas to display the graphics buffer.
        +   *
        +   * let pg;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.Graphics object.
        +   *   pg = createGraphics(50, 50);
        +   *
        +   *   describe('A square appears on a gray background when the user presses the mouse. The square cycles between white and black.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the background color.
        +   *   let bg = frameCount % 255;
        +   *
        +   *   // Draw to the p5.Graphics object.
        +   *   pg.background(bg);
        +   *
        +   *   // Display the p5.Graphics object while
        +   *   // the user presses the mouse.
        +   *   if (mouseIsPressed === true) {
        +   *     image(pg, 25, 25);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  p5.Graphics = Graphics;
        +
        +  // Shapes
        +  primitives2D(p5, p5.Graphics.prototype);
        +  attributes(p5, p5.Graphics.prototype);
        +  curves(p5, p5.Graphics.prototype);
        +  vertex(p5, p5.Graphics.prototype);
        +
        +  setting(p5, p5.Graphics.prototype);
        +  loadingDisplaying(p5, p5.Graphics.prototype);
        +  image(p5, p5.Graphics.prototype);
        +  pixels(p5, p5.Graphics.prototype);
        +
        +  transform(p5, p5.Graphics.prototype);
        +
        +  primitives3D(p5, p5.Graphics.prototype);
        +  light(p5, p5.Graphics.prototype);
        +  material(p5, p5.Graphics.prototype);
        +  creatingReading(p5, p5.Graphics.prototype);
        +  trigonometry(p5, p5.Graphics.prototype);
        +}
        +
        +export default graphics;
        +export { Graphics };
        diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js
        index 21ce3bd64b..e5ba194d63 100644
        --- a/src/core/p5.Renderer.js
        +++ b/src/core/p5.Renderer.js
        @@ -4,102 +4,216 @@
          * @for p5
          */
         
        -import p5 from './main';
        +import { Color } from '../color/p5.Color';
         import * as constants from '../core/constants';
        +import { Image } from '../image/p5.Image';
        +import { Vector } from '../math/p5.Vector';
        +import { Shape } from '../shape/custom_shapes';
        +
        +class ClonableObject {
        +  constructor(obj = {}) {
        +    for (const key in obj) {
        +      this[key] = obj[key];
        +    }
        +  }
        +
        +  clone() {
        +    return new ClonableObject(this);
        +  }
        +};
        +
        +class Renderer {
        +  static states = {
        +    strokeColor: null,
        +    strokeSet: false,
        +    fillColor: null,
        +    fillSet: false,
        +    tint: null,
        +
        +    imageMode: constants.CORNER,
        +    rectMode: constants.CORNER,
        +    ellipseMode: constants.CENTER,
        +    strokeWeight: 1,
        +
        +    textFont: { family: 'sans-serif' },
        +    textLeading: 15,
        +    leadingSet: false,
        +    textSize: 12,
        +    textAlign: constants.LEFT,
        +    textBaseline: constants.BASELINE,
        +    bezierOrder: 3,
        +    splineProperties: new ClonableObject({ ends: constants.INCLUDE, tightness: 0 }),
        +    textWrap: constants.WORD,
        +
        +    // added v2.0
        +    fontStyle: constants.NORMAL, // v1: textStyle
        +    fontStretch: constants.NORMAL,
        +    fontWeight: constants.NORMAL,
        +    lineHeight: constants.NORMAL,
        +    fontVariant: constants.NORMAL,
        +    direction: 'inherit'
        +  }
        +
        +  constructor(pInst, w, h, isMainCanvas) {
        +    this._pInst = pInst;
        +    this._isMainCanvas = isMainCanvas;
        +    this.pixels = [];
        +    this._pixelDensity = Math.ceil(window.devicePixelRatio) || 1;
        +
        +    this.width = w;
        +    this.height = h;
        +
        +    this._events = {};
         
        -/**
        - * Main graphics and rendering context, as well as the base API
        - * implementation for p5.js "core". To be used as the superclass for
        - * Renderer2D and Renderer3D classes, respectively.
        - *
        - * @class p5.Renderer
        - * @constructor
        - * @extends p5.Element
        - * @param {HTMLElement} elt DOM node that is wrapped
        - * @param {p5} [pInst] pointer to p5 instance
        - * @param {Boolean} [isMainCanvas] whether we're using it as main canvas
        - */
        -class Renderer extends p5.Element {
        -  constructor(elt, pInst, isMainCanvas) {
        -    super(elt, pInst);
        -    this.canvas = elt;
        -    this._pixelsState = pInst;
             if (isMainCanvas) {
               this._isMainCanvas = true;
        -      // for pixel method sharing with pimage
        -      this._pInst._setProperty('_curElement', this);
        -      this._pInst._setProperty('canvas', this.canvas);
        -      this._pInst._setProperty('width', this.width);
        -      this._pInst._setProperty('height', this.height);
        -    } else {
        -      // hide if offscreen buffer by default
        -      this.canvas.style.display = 'none';
        -      this._styles = []; // non-main elt styles stored in p5.Renderer
             }
         
        +    // Renderer state machine
        +    this.states = Object.assign({}, Renderer.states);
        +    // Clone properties that support it
        +    for (const key in this.states) {
        +      if (this.states[key] instanceof Array) {
        +        this.states[key] = this.states[key].slice();
        +      } else if (this.states[key] && this.states[key].clone instanceof Function) {
        +        this.states[key] = this.states[key].clone();
        +      }
        +    }
        +
        +    this.states.strokeColor = new Color([0, 0, 0]);
        +    this.states.fillColor = new Color([255, 255, 255]);
        +
        +    this._pushPopStack = [];
        +    // NOTE: can use the length of the push pop stack instead
        +    this._pushPopDepth = 0;
        +
             this._clipping = false;
             this._clipInvert = false;
         
        -    this._textSize = 12;
        -    this._textLeading = 15;
        -    this._textFont = 'sans-serif';
        -    this._textStyle = constants.NORMAL;
        -    this._textAscent = null;
        -    this._textDescent = null;
        -    this._textAlign = constants.LEFT;
        -    this._textBaseline = constants.BASELINE;
        -    this._textWrap = constants.WORD;
        -
        -    this._rectMode = constants.CORNER;
        -    this._ellipseMode = constants.CENTER;
        -    this._curveTightness = 0;
        -    this._imageMode = constants.CORNER;
        -
        -    this._tint = null;
        -    this._doStroke = true;
        -    this._doFill = true;
        -    this._strokeSet = false;
        -    this._fillSet = false;
        -    this._leadingSet = false;
        +    this._currentShape = undefined; // Lazily generate current shape
        +  }
        +
        +  get currentShape() {
        +    if (!this._currentShape) {
        +      this._currentShape = new Shape(this.getCommonVertexProperties());
        +    }
        +    return this._currentShape;
        +  }
        +
        +  remove() {
         
        -    this._pushPopDepth = 0;
           }
         
        -  // the renderer should return a 'style' object that it wishes to
        -  // store on the push stack.
        -  push () {
        +  pixelDensity(val){
        +    let returnValue;
        +    if (typeof val === 'number') {
        +      if (val !== this._pixelDensity) {
        +        this._pixelDensity = val;
        +      }
        +      returnValue = this;
        +      this.resize(this.width, this.height);
        +    } else {
        +      returnValue = this._pixelDensity;
        +    }
        +    return returnValue;
        +  }
        +
        +  // Makes a shallow copy of the current states
        +  // and push it into the push pop stack
        +  push() {
             this._pushPopDepth++;
        -    return {
        -      properties: {
        -        _doStroke: this._doStroke,
        -        _strokeSet: this._strokeSet,
        -        _doFill: this._doFill,
        -        _fillSet: this._fillSet,
        -        _tint: this._tint,
        -        _imageMode: this._imageMode,
        -        _rectMode: this._rectMode,
        -        _ellipseMode: this._ellipseMode,
        -        _textFont: this._textFont,
        -        _textLeading: this._textLeading,
        -        _leadingSet: this._leadingSet,
        -        _textSize: this._textSize,
        -        _textAlign: this._textAlign,
        -        _textBaseline: this._textBaseline,
        -        _textStyle: this._textStyle,
        -        _textWrap: this._textWrap
        +    const currentStates = Object.assign({}, this.states);
        +    // Clone properties that support it
        +    for (const key in currentStates) {
        +      if (currentStates[key] instanceof Array) {
        +        currentStates[key] = currentStates[key].slice();
        +      } else if (currentStates[key] && currentStates[key].clone instanceof Function) {
        +        currentStates[key] = currentStates[key].clone();
               }
        -    };
        +    }
        +    this._pushPopStack.push(currentStates);
        +    return currentStates;
           }
         
        -  // a pop() operation is in progress
        -  // the renderer is passed the 'style' object that it returned
        -  // from its push() method.
        -  pop (style) {
        +  // Pop the previous states out of the push pop stack and
        +  // assign it back to the current state
        +  pop() {
             this._pushPopDepth--;
        -    if (style.properties) {
        -    // copy the style properties back into the renderer
        -      Object.assign(this, style.properties);
        +    Object.assign(this.states, this._pushPopStack.pop());
        +    this.updateShapeVertexProperties();
        +    this.updateShapeProperties();
        +  }
        +
        +  bezierOrder(order) {
        +    if (order === undefined) {
        +      return this.states.bezierOrder;
        +    } else {
        +      this.states.bezierOrder = order;
        +      this.updateShapeProperties();
        +    }
        +  }
        +
        +  bezierVertex(x, y, z = 0, u = 0, v = 0) {
        +    const position = new Vector(x, y, z);
        +    const textureCoordinates = this.getSupportedIndividualVertexProperties().textureCoordinates
        +      ? new Vector(u, v)
        +      : undefined;
        +    this.currentShape.bezierVertex(position, textureCoordinates);
        +  }
        +
        +  splineProperty(key, value) {
        +    if (value === undefined) {
        +      return this.states.splineProperties[key];
        +    } else {
        +      this.states.splineProperties[key] = value;
             }
        +    this.updateShapeProperties();
        +  }
        +
        +  splineVertex(x, y, z = 0, u = 0, v = 0) {
        +    const position = new Vector(x, y, z);
        +    const textureCoordinates = this.getSupportedIndividualVertexProperties().textureCoordinates
        +      ? new Vector(u, v)
        +      : undefined;
        +    this.currentShape.splineVertex(position, textureCoordinates);
        +  }
        +
        +  curveDetail(d) {
        +    if (d === undefined) {
        +      return this.states.curveDetail;
        +    } else {
        +      this.states.curveDetail = d;
        +    }
        +  }
        +
        +  beginShape(...args) {
        +    this.currentShape.reset();
        +    this.currentShape.beginShape(...args);
        +  }
        +
        +  endShape(...args) {
        +    this.currentShape.endShape(...args);
        +    this.drawShape(this.currentShape);
        +  }
        +
        +  beginContour(shapeKind) {
        +    this.currentShape.beginContour(shapeKind);
        +  }
        +
        +  endContour(mode) {
        +    this.currentShape.endContour(mode);
        +  }
        +
        +  drawShape(shape, count) {
        +    throw new Error('Unimplemented')
        +  }
        +
        +  vertex(x, y, z = 0, u = 0, v = 0) {
        +    const position = new Vector(x, y, z);
        +    const textureCoordinates = this.getSupportedIndividualVertexProperties().textureCoordinates
        +      ? new Vector(u, v)
        +      : undefined;
        +    this.currentShape.vertex(position, textureCoordinates);
           }
         
           beginClip(options = {}) {
        @@ -118,31 +232,22 @@ class Renderer extends p5.Element {
           }
         
           /**
        - * Resize our canvas element.
        - */
        -  resize (w, h) {
        +   * Resize our canvas element.
        +   */
        +  resize(w, h) {
             this.width = w;
             this.height = h;
        -    this.elt.width = w * this._pInst._pixelDensity;
        -    this.elt.height = h * this._pInst._pixelDensity;
        -    this.elt.style.width = `${w}px`;
        -    this.elt.style.height = `${h}px`;
        -    if (this._isMainCanvas) {
        -      this._pInst._setProperty('width', this.width);
        -      this._pInst._setProperty('height', this.height);
        -    }
           }
         
        -  get (x, y, w, h) {
        -    const pixelsState = this._pixelsState;
        -    const pd = pixelsState._pixelDensity;
        +  get(x, y, w, h) {
        +    const pd = this._pixelDensity;
             const canvas = this.canvas;
         
             if (typeof x === 'undefined' && typeof y === 'undefined') {
             // get()
               x = y = 0;
        -      w = pixelsState.width;
        -      h = pixelsState.height;
        +      w = this.width;
        +      h = this.height;
             } else {
               x *= pd;
               y *= pd;
        @@ -158,7 +263,7 @@ class Renderer extends p5.Element {
             // get(x,y,w,h)
             }
         
        -    const region = new p5.Image(w*pd, h*pd);
        +    const region = new Image(w*pd, h*pd);
             region.pixelDensity(pd);
             region.canvas
               .getContext('2d')
        @@ -167,72 +272,140 @@ class Renderer extends p5.Element {
             return region;
           }
         
        +  scale(x, y){
        +
        +  }
        +
        +  fill(...args) {
        +    this.states.fillSet = true;
        +    this.states.fillColor = this._pInst.color(...args);
        +    this.updateShapeVertexProperties();
        +  }
        +
        +  noFill() {
        +    this.states.fillColor = null;
        +  }
        +
        +  strokeWeight(w) {
        +    if (w === undefined) {
        +      return this.states.strokeWeight;
        +    } else {
        +      this.states.strokeWeight = w;
        +    }
        +  }
        +
        +  stroke(...args) {
        +    this.states.strokeSet = true;
        +    this.states.strokeColor = this._pInst.color(...args);
        +    this.updateShapeVertexProperties();
        +  }
        +
        +  noStroke() {
        +    this.states.strokeColor = null;
        +  }
        +
        +  getCommonVertexProperties() {
        +    return {}
        +  }
        +
        +  getSupportedIndividualVertexProperties() {
        +    return {
        +      textureCoordinates: false,
        +    }
        +  }
        +
        +  updateShapeProperties() {
        +    this.currentShape.bezierOrder(this.states.bezierOrder);
        +    this.currentShape.splineProperty('ends', this.states.splineProperties.ends);
        +    this.currentShape.splineProperty('tightness', this.states.splineProperties.tightness);
        +  }
        +
        +  updateShapeVertexProperties() {
        +    const props = this.getCommonVertexProperties();
        +    for (const key in props) {
        +      this.currentShape[key](props[key]);
        +    }
        +  }
        +
        +  textSize(s) {
        +    if (typeof s === 'number') {
        +      this.states.textSize = s;
        +      if (!this.states.leadingSet) {
        +      // only use a default value if not previously set (#5181)
        +        this.states.textLeading = s * constants._DEFAULT_LEADMULT;
        +      }
        +      return this._applyTextProperties();
        +    }
        +
        +    return this.states.textSize;
        +  }
        +
           textLeading (l) {
             if (typeof l === 'number') {
        -      this._setProperty('_leadingSet', true);
        -      this._setProperty('_textLeading', l);
        +      this.states.leadingSet = true;
        +      this.states.textLeading = l;
               return this._pInst;
             }
         
        -    return this._textLeading;
        +    return this.states.textLeading;
           }
         
           textStyle (s) {
             if (s) {
               if (
                 s === constants.NORMAL ||
        -      s === constants.ITALIC ||
        -      s === constants.BOLD ||
        -      s === constants.BOLDITALIC
        +        s === constants.ITALIC ||
        +        s === constants.BOLD ||
        +        s === constants.BOLDITALIC
               ) {
        -        this._setProperty('_textStyle', s);
        +        this.states.fontStyle = s;
               }
         
               return this._applyTextProperties();
             }
         
        -    return this._textStyle;
        +    return this.states.fontStyle;
           }
         
           textAscent () {
        -    if (this._textAscent === null) {
        +    if (this.states.textAscent === null) {
               this._updateTextMetrics();
             }
        -    return this._textAscent;
        +    return this.states.textAscent;
           }
         
           textDescent () {
        -    if (this._textDescent === null) {
        +    if (this.states.textDescent === null) {
               this._updateTextMetrics();
             }
        -    return this._textDescent;
        +    return this.states.textDescent;
           }
         
           textAlign (h, v) {
             if (typeof h !== 'undefined') {
        -      this._setProperty('_textAlign', h);
        +      this.states.textAlign = h;
         
               if (typeof v !== 'undefined') {
        -        this._setProperty('_textBaseline', v);
        +        this.states.textBaseline = v;
               }
         
               return this._applyTextProperties();
             } else {
               return {
        -        horizontal: this._textAlign,
        -        vertical: this._textBaseline
        +        horizontal: this.states.textAlign,
        +        vertical: this.states.textBaseline
               };
             }
           }
         
           textWrap (wrapStyle) {
        -    this._setProperty('_textWrap', wrapStyle);
        -    return this._textWrap;
        +    this.states.textWrap = wrapStyle;
        +    return this.states.textWrap;
           }
         
           text(str, x, y, maxWidth, maxHeight) {
             const p = this._pInst;
        -    const textWrapStyle = this._textWrap;
        +    const textWrapStyle = this.states.textWrap;
         
             let lines;
             let line;
        @@ -245,7 +418,7 @@ class Renderer extends p5.Element {
             // fix for #5785 (top of bounding box)
             let finalMinHeight = y;
         
        -    if (!(this._doFill || this._doStroke)) {
        +    if (!(this.states.fillColor || this.states.strokeColor)) {
               return;
             }
         
        @@ -261,11 +434,11 @@ class Renderer extends p5.Element {
             lines = str.split('\n');
         
             if (typeof maxWidth !== 'undefined') {
        -      if (this._rectMode === constants.CENTER) {
        +      if (this.states.rectMode === constants.CENTER) {
                 x -= maxWidth / 2;
               }
         
        -      switch (this._textAlign) {
        +      switch (this.states.textAlign) {
                 case constants.CENTER:
                   x += maxWidth / 2;
                   break;
        @@ -275,7 +448,7 @@ class Renderer extends p5.Element {
               }
         
               if (typeof maxHeight !== 'undefined') {
        -        if (this._rectMode === constants.CENTER) {
        +        if (this.states.rectMode === constants.CENTER) {
                   y -= maxHeight / 2;
                   finalMinHeight -= maxHeight / 2;
                 }
        @@ -283,7 +456,7 @@ class Renderer extends p5.Element {
                 let originalY = y;
                 let ascent = p.textAscent();
         
        -        switch (this._textBaseline) {
        +        switch (this.states.textBaseline) {
                   case constants.BOTTOM:
                     shiftedY = y + maxHeight;
                     y = Math.max(shiftedY, y);
        @@ -302,15 +475,15 @@ class Renderer extends p5.Element {
                 finalMaxHeight = y + maxHeight - ascent;
         
                 // fix for #5785 (bottom of bounding box)
        -        if (this._textBaseline === constants.CENTER) {
        +        if (this.states.textBaseline === constants.CENTER) {
                   finalMaxHeight = originalY + maxHeight - ascent / 2;
                 }
               } else {
               // no text-height specified, show warning for BOTTOM / CENTER
        -        if (this._textBaseline === constants.BOTTOM ||
        -        this._textBaseline === constants.CENTER) {
        +        if (this.states.textBaseline === constants.BOTTOM ||
        +        this.states.textBaseline === constants.CENTER) {
                 // use rectHeight as an approximation for text height
        -          let rectHeight = p.textSize() * this._textLeading;
        +          let rectHeight = p.textSize() * this.states.textLeading;
                   finalMinHeight = y - rectHeight / 2;
                   finalMaxHeight = y + rectHeight / 2;
                 }
        @@ -338,9 +511,9 @@ class Renderer extends p5.Element {
                 }
         
                 let offset = 0;
        -        if (this._textBaseline === constants.CENTER) {
        +        if (this.states.textBaseline === constants.CENTER) {
                   offset = (nlines.length - 1) * p.textLeading() / 2;
        -        } else if (this._textBaseline === constants.BOTTOM) {
        +        } else if (this.states.textBaseline === constants.BOTTOM) {
                   offset = (nlines.length - 1) * p.textLeading();
                 }
         
        @@ -394,9 +567,9 @@ class Renderer extends p5.Element {
         
                 nlines.push(line);
                 let offset = 0;
        -        if (this._textBaseline === constants.CENTER) {
        +        if (this.states.textBaseline === constants.CENTER) {
                   offset = (nlines.length - 1) * p.textLeading() / 2;
        -        } else if (this._textBaseline === constants.BOTTOM) {
        +        } else if (this.states.textBaseline === constants.BOTTOM) {
                   offset = (nlines.length - 1) * p.textLeading();
                 }
         
        @@ -438,9 +611,9 @@ class Renderer extends p5.Element {
             // Offset to account for vertically centering multiple lines of text - no
             // need to adjust anything for vertical align top or baseline
               let offset = 0;
        -      if (this._textBaseline === constants.CENTER) {
        +      if (this.states.textBaseline === constants.CENTER) {
                 offset = (lines.length - 1) * p.textLeading() / 2;
        -      } else if (this._textBaseline === constants.BOTTOM) {
        +      } else if (this.states.textBaseline === constants.BOTTOM) {
                 offset = (lines.length - 1) * p.textLeading();
               }
         
        @@ -468,21 +641,21 @@ class Renderer extends p5.Element {
           /**
          * Helper function to check font type (system or otf)
          */
        -  _isOpenType(f = this._textFont) {
        -    return typeof f === 'object' && f.font && f.font.supported;
        +  _isOpenType({ font: f } = this.states.textFont) {
        +    return typeof f === 'object' && f.data;
           }
         
           _updateTextMetrics() {
             if (this._isOpenType()) {
        -      this._setProperty('_textAscent', this._textFont._textAscent());
        -      this._setProperty('_textDescent', this._textFont._textDescent());
        +      this.states.textAscent = this.states.textFont._textAscent();
        +      this.states.textDescent = this.states.textFont._textDescent();
               return this;
             }
         
             // Adapted from http://stackoverflow.com/a/25355178
             const text = document.createElement('span');
        -    text.style.fontFamily = this._textFont;
        -    text.style.fontSize = `${this._textSize}px`;
        +    text.style.fontFamily = this.states.textFont;
        +    text.style.fontSize = `${this.states.textSize}px`;
             text.innerHTML = 'ABCjgq|';
         
             const block = document.createElement('div');
        @@ -511,12 +684,27 @@ class Renderer extends p5.Element {
         
             document.body.removeChild(container);
         
        -    this._setProperty('_textAscent', ascent);
        -    this._setProperty('_textDescent', descent);
        +    this.states.textAscent = ascent;
        +    this.states.textDescent = descent;
         
             return this;
           }
        +};
        +
        +function renderer(p5, fn){
        +  /**
        +   * Main graphics and rendering context, as well as the base API
        +   * implementation for p5.js "core". To be used as the superclass for
        +   * Renderer2D and Renderer3D classes, respectively.
        +   *
        +   * @class p5.Renderer
        +   * @param {HTMLElement} elt DOM node that is wrapped
        +   * @param {p5} [pInst] pointer to p5 instance
        +   * @param {Boolean} [isMainCanvas] whether we're using it as main canvas
        +   */
        +  p5.Renderer = Renderer;
         }
        +
         /**
          * Helper fxn to measure ascent and descent.
          * Adapted from http://stackoverflow.com/a/25355178
        @@ -535,20 +723,6 @@ function calculateOffset(object) {
           }
           return [currentLeft, currentTop];
         }
        -// This caused the test to failed.
        -Renderer.prototype.textSize = function(s) {
        -  if (typeof s === 'number') {
        -    this._setProperty('_textSize', s);
        -    if (!this._leadingSet) {
        -    // only use a default value if not previously set (#5181)
        -      this._setProperty('_textLeading', s * constants._DEFAULT_LEADMULT);
        -    }
        -    return this._applyTextProperties();
        -  }
        -
        -  return this._textSize;
        -};
        -
        -p5.Renderer = Renderer;
         
        -export default p5.Renderer;
        +export default renderer;
        +export { Renderer };
        diff --git a/src/core/p5.Renderer2D.js b/src/core/p5.Renderer2D.js
        index ac41b13411..3b41e6abbc 100644
        --- a/src/core/p5.Renderer2D.js
        +++ b/src/core/p5.Renderer2D.js
        @@ -1,35 +1,101 @@
        -import p5 from './main';
         import * as constants from './constants';
        +import p5 from './main';
        +import { Renderer } from './p5.Renderer';
        +import { Graphics } from './p5.Graphics';
        +import { Image } from '../image/p5.Image';
        +import { Element } from '../dom/p5.Element';
        +import { MediaElement } from '../dom/p5.MediaElement';
        +import { RGBHDR } from '../color/creating_reading';
        +import FilterRenderer2D from '../image/filterRenderer2D';
        +import { PrimitiveToPath2DConverter } from '../shape/custom_shapes';
         
        -import './p5.Renderer';
         
        -/**
        - * p5.Renderer2D
        - * The 2D graphics canvas renderer class.
        - * extends p5.Renderer
        - */
         const styleEmpty = 'rgba(0,0,0,0)';
         // const alphaThreshold = 0.00125; // minimum visible
         
        -class Renderer2D extends p5.Renderer {
        -  constructor(elt, pInst, isMainCanvas) {
        -    super(elt, pInst, isMainCanvas);
        -    this.drawingContext = this.canvas.getContext('2d');
        -    this._pInst._setProperty('drawingContext', this.drawingContext);
        +class Renderer2D extends Renderer {
        +  constructor(pInst, w, h, isMainCanvas, elt, attributes = {}) {
        +    super(pInst, w, h, isMainCanvas);
        +
        +    this.canvas = this.elt = elt || document.createElement('canvas');
        +
        +    if (isMainCanvas) {
        +      // for pixel method sharing with pimage
        +      this._pInst._curElement = this;
        +      this._pInst.canvas = this.canvas;
        +    } else {
        +      // hide if offscreen buffer by default
        +      this.canvas.style.display = 'none';
        +    }
        +
        +    this.elt.id = 'defaultCanvas0';
        +    this.elt.classList.add('p5Canvas');
        +
        +    // Extend renderer with methods of p5.Element with getters
        +    for (const p of Object.getOwnPropertyNames(Element.prototype)) {
        +      if (p !== 'constructor' && p[0] !== '_') {
        +        Object.defineProperty(this, p, {
        +          get() {
        +            return this.wrappedElt[p];
        +          }
        +        })
        +      }
        +    }
        +
        +    // Set canvas size
        +    this.elt.width = w * this._pixelDensity;
        +    this.elt.height = h * this._pixelDensity;
        +    this.elt.style.width = `${w}px`;
        +    this.elt.style.height = `${h}px`;
        +
        +    // Attach canvas element to DOM
        +    if (this._pInst._userNode) {
        +      // user input node case
        +      this._pInst._userNode.appendChild(this.elt);
        +    } else {
        +      //create main element
        +      if (document.getElementsByTagName('main').length === 0) {
        +        let m = document.createElement('main');
        +        document.body.appendChild(m);
        +      }
        +      //append canvas to main
        +      document.getElementsByTagName('main')[0].appendChild(this.elt);
        +    }
        +
        +    // Get and store drawing context
        +    this.drawingContext = this.canvas.getContext('2d', attributes);
        +    if(attributes.colorSpace === 'display-p3'){
        +      this.states.colorMode = RGBHDR;
        +    }
        +    if (isMainCanvas) {
        +      this._pInst.drawingContext = this.drawingContext;
        +    }
        +    this.scale(this._pixelDensity, this._pixelDensity);
        +
        +    if(!this.filterRenderer){
        +      this.filterRenderer = new FilterRenderer2D(this);
        +    }
        +    // Set and return p5.Element
        +    this.wrappedElt = new Element(this.elt, this._pInst);
        +
        +    this.clipPath = null;
        +  }
        +
        +  remove(){
        +    this.wrappedElt.remove();
        +    this.wrappedElt = null;
        +    this.canvas = null;
        +    this.elt = null;
           }
         
           getFilterGraphicsLayer() {
             // create hidden webgl renderer if it doesn't exist
             if (!this.filterGraphicsLayer) {
        -      // the real _pInst is buried when this is a secondary p5.Graphics
        -      const pInst =
        -        this._pInst instanceof p5.Graphics ?
        -          this._pInst._pInst :
        -          this._pInst;
        +      const pInst = this._pInst;
         
               // create secondary layer
               this.filterGraphicsLayer =
        -        new p5.Graphics(
        +        new Graphics(
                   this.width,
                   this.height,
                   constants.WEBGL,
        @@ -48,6 +114,7 @@ class Renderer2D extends p5.Renderer {
             ) {
               this.filterGraphicsLayer.pixelDensity(this._pInst.pixelDensity());
             }
        +
             return this.filterGraphicsLayer;
           }
         
        @@ -62,10 +129,33 @@ class Renderer2D extends p5.Renderer {
         
           resize(w, h) {
             super.resize(w, h);
        +
        +    // save canvas properties
        +    const props = {};
        +    for (const key in this.drawingContext) {
        +      const val = this.drawingContext[key];
        +      if (typeof val !== 'object' && typeof val !== 'function') {
        +        props[key] = val;
        +      }
        +    }
        +
        +    this.canvas.width = w * this._pixelDensity;
        +    this.canvas.height = h * this._pixelDensity;
        +    this.canvas.style.width = `${w}px`;
        +    this.canvas.style.height = `${h}px`;
             this.drawingContext.scale(
        -      this._pInst._pixelDensity,
        -      this._pInst._pixelDensity
        +      this._pixelDensity,
        +      this._pixelDensity
             );
        +
        +    // reset canvas properties
        +    for (const savedKey in props) {
        +      try {
        +        this.drawingContext[savedKey] = props[savedKey];
        +      } catch (err) {
        +        // ignore read-only property errors
        +      }
        +    }
           }
         
           //////////////////////////////////////////////
        @@ -76,7 +166,7 @@ class Renderer2D extends p5.Renderer {
             this.drawingContext.save();
             this.resetMatrix();
         
        -    if (args[0] instanceof p5.Image) {
        +    if (args[0] instanceof Image) {
               if (args[1] >= 0) {
                 // set transparency of background
                 const img = args[0];
        @@ -92,7 +182,7 @@ class Renderer2D extends p5.Renderer {
         
               //accessible Outputs
               if (this._pInst._addAccsOutput()) {
        -        this._pInst._accsBackground(color.levels);
        +        this._pInst._accsBackground(color._getRGBA([255, 255, 255, 255]));
               }
         
               const newFill = color.toString();
        @@ -121,22 +211,24 @@ class Renderer2D extends p5.Renderer {
           }
         
           fill(...args) {
        -    const color = this._pInst.color(...args);
        +    super.fill(...args);
        +    const color = this.states.fillColor;
             this._setFill(color.toString());
         
             //accessible Outputs
             if (this._pInst._addAccsOutput()) {
        -      this._pInst._accsCanvasColors('fill', color.levels);
        +      this._pInst._accsCanvasColors('fill', color._getRGBA([255, 255, 255, 255]));
             }
           }
         
           stroke(...args) {
        -    const color = this._pInst.color(...args);
        +    super.stroke(...args);
        +    const color = this.states.strokeColor;
             this._setStroke(color.toString());
         
             //accessible Outputs
             if (this._pInst._addAccsOutput()) {
        -      this._pInst._accsCanvasColors('stroke', color.levels);
        +      this._pInst._accsCanvasColors('stroke', color._getRGBA([255, 255, 255, 255]));
             }
           }
         
        @@ -171,6 +263,21 @@ class Renderer2D extends p5.Renderer {
             }
           }
         
        +  drawShape(shape) {
        +    const visitor = new PrimitiveToPath2DConverter({ strokeWeight: this.states.strokeWeight });
        +    shape.accept(visitor);
        +    if (this._clipping) {
        +      this.clipPath.addPath(visitor.path);
        +    } else {
        +      if (this.states.fillColor) {
        +        this.drawingContext.fill(visitor.path);
        +      }
        +      if (this.states.strokeColor) {
        +        this.drawingContext.stroke(visitor.path);
        +      }
        +    }
        +  }
        +
           beginClip(options = {}) {
             super.beginClip(options);
         
        @@ -189,36 +296,37 @@ class Renderer2D extends p5.Renderer {
             this.blendMode(constants.BLEND);
             this._cachedBlendMode = tempBlendMode;
         
        +    // Since everything must be in one path, create a new single Path2D to chain all shapes onto.
             // Start a new path. Everything from here on out should become part of this
             // one path so that we can clip to the whole thing.
        -    this.drawingContext.beginPath();
        +    this.clipPath = new Path2D();
         
             if (this._clipInvert) {
               // Slight hack: draw a big rectangle over everything with reverse winding
               // order. This is hopefully large enough to cover most things.
        -      this.drawingContext.moveTo(
        +      this.clipPath.moveTo(
                 -2 * this.width,
                 -2 * this.height
               );
        -      this.drawingContext.lineTo(
        +      this.clipPath.lineTo(
                 -2 * this.width,
                 2 * this.height
               );
        -      this.drawingContext.lineTo(
        +      this.clipPath.lineTo(
                 2 * this.width,
                 2 * this.height
               );
        -      this.drawingContext.lineTo(
        +      this.clipPath.lineTo(
                 2 * this.width,
                 -2 * this.height
               );
        -      this.drawingContext.closePath();
        +      this.clipPath.closePath();
             }
           }
         
           endClip() {
        -    this._doFillStrokeClose();
        -    this.drawingContext.clip();
        +    this.drawingContext.clip(this.clipPath);
        +    this.clipPath = null;
         
             super.endClip();
         
        @@ -249,10 +357,10 @@ class Renderer2D extends p5.Renderer {
             }
         
             try {
        -      if (p5.MediaElement && img instanceof p5.MediaElement) {
        +      if (img instanceof MediaElement) {
                 img._ensureCanvas();
               }
        -      if (this._tint && img.canvas) {
        +      if (this.states.tint && img.canvas) {
                 cnv = this._getTintedImageCanvas(img);
               }
               if (!cnv) {
        @@ -265,6 +373,7 @@ class Renderer2D extends p5.Renderer {
               if (this._isErasing) {
                 this.blendMode(this._cachedBlendMode);
               }
        +
               this.drawingContext.drawImage(
                 cnv,
                 s * sx,
        @@ -313,7 +422,7 @@ class Renderer2D extends p5.Renderer {
             ctx.save();
             ctx.clearRect(0, 0, img.canvas.width, img.canvas.height);
         
        -    if (this._tint[0] < 255 || this._tint[1] < 255 || this._tint[2] < 255) {
        +    if (this.states.tint[0] < 255 || this.states.tint[1] < 255 || this.states.tint[2] < 255) {
               // Color tint: we need to use the multiply blend mode to change the colors.
               // However, the canvas implementation of this destroys the alpha channel of
               // the image. To accommodate, we first get a version of the image with full
        @@ -335,16 +444,16 @@ class Renderer2D extends p5.Renderer {
         
               // Apply color tint
               ctx.globalCompositeOperation = 'multiply';
        -      ctx.fillStyle = `rgb(${this._tint.slice(0, 3).join(', ')})`;
        +      ctx.fillStyle = `rgb(${this.states.tint.slice(0, 3).join(', ')})`;
               ctx.fillRect(0, 0, img.canvas.width, img.canvas.height);
         
               // Replace the alpha channel with the original alpha * the alpha tint
               ctx.globalCompositeOperation = 'destination-in';
        -      ctx.globalAlpha = this._tint[3] / 255;
        +      ctx.globalAlpha = this.states.tint[3] / 255;
               ctx.drawImage(img.canvas, 0, 0);
             } else {
               // If we only need to change the alpha, we can skip all the extra work!
        -      ctx.globalAlpha = this._tint[3] / 255;
        +      ctx.globalAlpha = this.states.tint[3] / 255;
               ctx.drawImage(img.canvas, 0, 0);
             }
         
        @@ -413,29 +522,26 @@ class Renderer2D extends p5.Renderer {
           }
         
           loadPixels() {
        -    const pixelsState = this._pixelsState; // if called by p5.Image
        -
        -    const pd = pixelsState._pixelDensity;
        +    const pd = this._pixelDensity;
             const w = this.width * pd;
             const h = this.height * pd;
             const imageData = this.drawingContext.getImageData(0, 0, w, h);
             // @todo this should actually set pixels per object, so diff buffers can
             // have diff pixel arrays.
        -    pixelsState._setProperty('imageData', imageData);
        -    pixelsState._setProperty('pixels', imageData.data);
        +    this.imageData = imageData;
        +    this.pixels = imageData.data;
           }
         
           set(x, y, imgOrCol) {
             // round down to get integer numbers
             x = Math.floor(x);
             y = Math.floor(y);
        -    const pixelsState = this._pixelsState;
        -    if (imgOrCol instanceof p5.Image) {
        +    if (imgOrCol instanceof Image) {
               this.drawingContext.save();
               this.drawingContext.setTransform(1, 0, 0, 1, 0, 0);
               this.drawingContext.scale(
        -        pixelsState._pixelDensity,
        -        pixelsState._pixelDensity
        +        this._pixelDensity,
        +        this._pixelDensity
               );
               this.drawingContext.clearRect(x, y, imgOrCol.width, imgOrCol.height);
               this.drawingContext.drawImage(imgOrCol.canvas, x, y);
        @@ -448,14 +554,14 @@ class Renderer2D extends p5.Renderer {
               let idx =
                 4 *
                 (y *
        -          pixelsState._pixelDensity *
        -          (this.width * pixelsState._pixelDensity) +
        -          x * pixelsState._pixelDensity);
        -      if (!pixelsState.imageData) {
        -        pixelsState.loadPixels();
        +          this._pixelDensity *
        +          (this.width * this._pixelDensity) +
        +          x * this._pixelDensity);
        +      if (!this.imageData) {
        +        this.loadPixels();
               }
               if (typeof imgOrCol === 'number') {
        -        if (idx < pixelsState.pixels.length) {
        +        if (idx < this.pixels.length) {
                   r = imgOrCol;
                   g = imgOrCol;
                   b = imgOrCol;
        @@ -466,7 +572,7 @@ class Renderer2D extends p5.Renderer {
                 if (imgOrCol.length < 4) {
                   throw new Error('pixel array must be of the form [R, G, B, A]');
                 }
        -        if (idx < pixelsState.pixels.length) {
        +        if (idx < this.pixels.length) {
                   r = imgOrCol[0];
                   g = imgOrCol[1];
                   b = imgOrCol[2];
        @@ -474,36 +580,32 @@ class Renderer2D extends p5.Renderer {
                   //this.updatePixels.call(this);
                 }
               } else if (imgOrCol instanceof p5.Color) {
        -        if (idx < pixelsState.pixels.length) {
        -          r = imgOrCol.levels[0];
        -          g = imgOrCol.levels[1];
        -          b = imgOrCol.levels[2];
        -          a = imgOrCol.levels[3];
        +        if (idx < this.pixels.length) {
        +          [r, g, b, a] = imgOrCol._getRGBA([255, 255, 255, 255]);
                   //this.updatePixels.call(this);
                 }
               }
               // loop over pixelDensity * pixelDensity
        -      for (let i = 0; i < pixelsState._pixelDensity; i++) {
        -        for (let j = 0; j < pixelsState._pixelDensity; j++) {
        +      for (let i = 0; i < this._pixelDensity; i++) {
        +        for (let j = 0; j < this._pixelDensity; j++) {
                   // loop over
                   idx =
                     4 *
        -            ((y * pixelsState._pixelDensity + j) *
        +            ((y * this._pixelDensity + j) *
                       this.width *
        -              pixelsState._pixelDensity +
        -              (x * pixelsState._pixelDensity + i));
        -          pixelsState.pixels[idx] = r;
        -          pixelsState.pixels[idx + 1] = g;
        -          pixelsState.pixels[idx + 2] = b;
        -          pixelsState.pixels[idx + 3] = a;
        +              this._pixelDensity +
        +              (x * this._pixelDensity + i));
        +          this.pixels[idx] = r;
        +          this.pixels[idx + 1] = g;
        +          this.pixels[idx + 2] = b;
        +          this.pixels[idx + 3] = a;
                 }
               }
             }
           }
         
           updatePixels(x, y, w, h) {
        -    const pixelsState = this._pixelsState;
        -    const pd = pixelsState._pixelDensity;
        +    const pd = this._pixelDensity;
             if (
               x === undefined &&
               y === undefined &&
        @@ -522,10 +624,10 @@ class Renderer2D extends p5.Renderer {
         
             if (this.gifProperties) {
               this.gifProperties.frames[this.gifProperties.displayIndex].image =
        -        pixelsState.imageData;
        +        this.imageData;
             }
         
        -    this.drawingContext.putImageData(pixelsState.imageData, 0, 0, x, y, w, h);
        +    this.drawingContext.putImageData(this.imageData, 0, 0, x, y, w, h);
           }
         
           //////////////////////////////////////////////
        @@ -533,14 +635,19 @@ class Renderer2D extends p5.Renderer {
           //////////////////////////////////////////////
         
           /*
        - * This function requires that:
        - *
        - *   0 <= start < TWO_PI
        - *
        - *   start <= stop < start + TWO_PI
        - */
        +   * This function requires that:
        +   *
        +   *   0 <= start < TWO_PI
        +   *
        +   *   start <= stop < start + TWO_PI
        +   */
           arc(x, y, w, h, start, stop, mode) {
        -    const ctx = this.drawingContext;
        +    const ctx = this.clipPa || this.drawingContext;
        +    const rx = w / 2.0;
        +    const ry = h / 2.0;
        +    const epsilon = 0.00001; // Smallest visible angle on displays up to 4K.
        +    let arcToDraw = 0;
        +    const curves = [];
         
             const centerX = x + w / 2,
               centerY = y + h / 2,
        @@ -556,8 +663,8 @@ class Renderer2D extends p5.Renderer {
               (stop - start) % constants.TWO_PI === 0
             );
         
        -    // Fill
        -    if (this._doFill) {
        +    // Fill curves
        +    if (this.states.fillColor) {
               if (!this._clipping) ctx.beginPath();
               ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, start, stop);
               if (createPieSlice) ctx.lineTo(centerX, centerY);
        @@ -565,8 +672,8 @@ class Renderer2D extends p5.Renderer {
               if (!this._clipping) ctx.fill();
             }
         
        -    // Stroke
        -    if (this._doStroke) {
        +    // Stroke curves
        +    if (this.states.strokeColor) {
               if (!this._clipping) ctx.beginPath();
               ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, start, stop);
         
        @@ -589,9 +696,9 @@ class Renderer2D extends p5.Renderer {
           }
         
           ellipse(args) {
        -    const ctx = this.drawingContext;
        -    const doFill = this._doFill,
        -      doStroke = this._doStroke;
        +    const ctx = this.clipPath || this.drawingContext;
        +    const doFill = !!this.states.fillColor,
        +      doStroke = this.states.strokeColor;
             const x = parseFloat(args[0]),
               y = parseFloat(args[1]),
               w = parseFloat(args[2]),
        @@ -623,8 +730,8 @@ class Renderer2D extends p5.Renderer {
           }
         
           line(x1, y1, x2, y2) {
        -    const ctx = this.drawingContext;
        -    if (!this._doStroke) {
        +    const ctx = this.clipPath || this.drawingContext;
        +    if (!this.states.strokeColor) {
               return this;
             } else if (this._getStroke() === styleEmpty) {
               return this;
        @@ -637,8 +744,8 @@ class Renderer2D extends p5.Renderer {
           }
         
           point(x, y) {
        -    const ctx = this.drawingContext;
        -    if (!this._doStroke) {
        +    const ctx = this.clipPath || this.drawingContext;
        +    if (!this.states.strokeColor) {
               return this;
             } else if (this._getStroke() === styleEmpty) {
               return this;
        @@ -658,9 +765,9 @@ class Renderer2D extends p5.Renderer {
           }
         
           quad(x1, y1, x2, y2, x3, y3, x4, y4) {
        -    const ctx = this.drawingContext;
        -    const doFill = this._doFill,
        -      doStroke = this._doStroke;
        +    const ctx = this.clipPath || this.drawingContext;
        +    const doFill = !!this.states.fillColor,
        +      doStroke = this.states.strokeColor;
             if (doFill && !doStroke) {
               if (this._getFill() === styleEmpty) {
                 return this;
        @@ -694,9 +801,9 @@ class Renderer2D extends p5.Renderer {
             let tr = args[5];
             let br = args[6];
             let bl = args[7];
        -    const ctx = this.drawingContext;
        -    const doFill = this._doFill,
        -      doStroke = this._doStroke;
        +    const ctx = this.clipPath || this.drawingContext;
        +    const doFill = !!this.states.fillColor,
        +      doStroke = this.states.strokeColor;
             if (doFill && !doStroke) {
               if (this._getFill() === styleEmpty) {
                 return this;
        @@ -758,10 +865,10 @@ class Renderer2D extends p5.Renderer {
         
               ctx.roundRect(x, y, w, h, [tl, tr, br, bl]);
             }
        -    if (!this._clipping && this._doFill) {
        +    if (!this._clipping && this.states.fillColor) {
               ctx.fill();
             }
        -    if (!this._clipping && this._doStroke) {
        +    if (!this._clipping && this.states.strokeColor) {
               ctx.stroke();
             }
             return this;
        @@ -769,9 +876,9 @@ class Renderer2D extends p5.Renderer {
         
         
           triangle(args) {
        -    const ctx = this.drawingContext;
        -    const doFill = this._doFill,
        -      doStroke = this._doStroke;
        +    const ctx = this.clipPath || this.drawingContext;
        +    const doFill = !!this.states.fillColor,
        +      doStroke = this.states.strokeColor;
             const x1 = args[0],
               y1 = args[1];
             const x2 = args[2],
        @@ -800,270 +907,6 @@ class Renderer2D extends p5.Renderer {
             }
           }
         
        -  endShape(
        -    mode,
        -    vertices,
        -    isCurve,
        -    isBezier,
        -    isQuadratic,
        -    isContour,
        -    shapeKind
        -  ) {
        -    if (vertices.length === 0) {
        -      return this;
        -    }
        -    if (!this._doStroke && !this._doFill) {
        -      return this;
        -    }
        -    const closeShape = mode === constants.CLOSE;
        -    let v;
        -    if (closeShape && !isContour) {
        -      vertices.push(vertices[0]);
        -    }
        -    let i, j;
        -    const numVerts = vertices.length;
        -    if (isCurve && shapeKind === null) {
        -      if (numVerts > 3) {
        -        const b = [],
        -          s = 1 - this._curveTightness;
        -        if (!this._clipping) this.drawingContext.beginPath();
        -        this.drawingContext.moveTo(vertices[1][0], vertices[1][1]);
        -        for (i = 1; i + 2 < numVerts; i++) {
        -          v = vertices[i];
        -          b[0] = [v[0], v[1]];
        -          b[1] = [
        -            v[0] + (s * vertices[i + 1][0] - s * vertices[i - 1][0]) / 6,
        -            v[1] + (s * vertices[i + 1][1] - s * vertices[i - 1][1]) / 6
        -          ];
        -          b[2] = [
        -            vertices[i + 1][0] +
        -            (s * vertices[i][0] - s * vertices[i + 2][0]) / 6,
        -            vertices[i + 1][1] +
        -            (s * vertices[i][1] - s * vertices[i + 2][1]) / 6
        -          ];
        -          b[3] = [vertices[i + 1][0], vertices[i + 1][1]];
        -          this.drawingContext.bezierCurveTo(
        -            b[1][0],
        -            b[1][1],
        -            b[2][0],
        -            b[2][1],
        -            b[3][0],
        -            b[3][1]
        -          );
        -        }
        -        if (closeShape) {
        -          this.drawingContext.lineTo(vertices[i + 1][0], vertices[i + 1][1]);
        -        }
        -        this._doFillStrokeClose(closeShape);
        -      }
        -    } else if (
        -      isBezier &&
        -      shapeKind === null
        -    ) {
        -      if (!this._clipping) this.drawingContext.beginPath();
        -      for (i = 0; i < numVerts; i++) {
        -        if (vertices[i].isVert) {
        -          if (vertices[i].moveTo) {
        -            this.drawingContext.moveTo(vertices[i][0], vertices[i][1]);
        -          } else {
        -            this.drawingContext.lineTo(vertices[i][0], vertices[i][1]);
        -          }
        -        } else {
        -          this.drawingContext.bezierCurveTo(
        -            vertices[i][0],
        -            vertices[i][1],
        -            vertices[i][2],
        -            vertices[i][3],
        -            vertices[i][4],
        -            vertices[i][5]
        -          );
        -        }
        -      }
        -      this._doFillStrokeClose(closeShape);
        -    } else if (
        -      isQuadratic &&
        -      shapeKind === null
        -    ) {
        -      if (!this._clipping) this.drawingContext.beginPath();
        -      for (i = 0; i < numVerts; i++) {
        -        if (vertices[i].isVert) {
        -          if (vertices[i].moveTo) {
        -            this.drawingContext.moveTo(vertices[i][0], vertices[i][1]);
        -          } else {
        -            this.drawingContext.lineTo(vertices[i][0], vertices[i][1]);
        -          }
        -        } else {
        -          this.drawingContext.quadraticCurveTo(
        -            vertices[i][0],
        -            vertices[i][1],
        -            vertices[i][2],
        -            vertices[i][3]
        -          );
        -        }
        -      }
        -      this._doFillStrokeClose(closeShape);
        -    } else {
        -      if (shapeKind === constants.POINTS) {
        -        for (i = 0; i < numVerts; i++) {
        -          v = vertices[i];
        -          if (this._doStroke) {
        -            this._pInst.stroke(v[6]);
        -          }
        -          this._pInst.point(v[0], v[1]);
        -        }
        -      } else if (shapeKind === constants.LINES) {
        -        for (i = 0; i + 1 < numVerts; i += 2) {
        -          v = vertices[i];
        -          if (this._doStroke) {
        -            this._pInst.stroke(vertices[i + 1][6]);
        -          }
        -          this._pInst.line(v[0], v[1], vertices[i + 1][0], vertices[i + 1][1]);
        -        }
        -      } else if (shapeKind === constants.TRIANGLES) {
        -        for (i = 0; i + 2 < numVerts; i += 3) {
        -          v = vertices[i];
        -          if (!this._clipping) this.drawingContext.beginPath();
        -          this.drawingContext.moveTo(v[0], v[1]);
        -          this.drawingContext.lineTo(vertices[i + 1][0], vertices[i + 1][1]);
        -          this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]);
        -          this.drawingContext.closePath();
        -          if (!this._clipping && this._doFill) {
        -            this._pInst.fill(vertices[i + 2][5]);
        -            this.drawingContext.fill();
        -          }
        -          if (!this._clipping && this._doStroke) {
        -            this._pInst.stroke(vertices[i + 2][6]);
        -            this.drawingContext.stroke();
        -          }
        -        }
        -      } else if (shapeKind === constants.TRIANGLE_STRIP) {
        -        for (i = 0; i + 1 < numVerts; i++) {
        -          v = vertices[i];
        -          if (!this._clipping) this.drawingContext.beginPath();
        -          this.drawingContext.moveTo(vertices[i + 1][0], vertices[i + 1][1]);
        -          this.drawingContext.lineTo(v[0], v[1]);
        -          if (!this._clipping && this._doStroke) {
        -            this._pInst.stroke(vertices[i + 1][6]);
        -          }
        -          if (!this._clipping && this._doFill) {
        -            this._pInst.fill(vertices[i + 1][5]);
        -          }
        -          if (i + 2 < numVerts) {
        -            this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]);
        -            if (!this._clipping && this._doStroke) {
        -              this._pInst.stroke(vertices[i + 2][6]);
        -            }
        -            if (!this._clipping && this._doFill) {
        -              this._pInst.fill(vertices[i + 2][5]);
        -            }
        -          }
        -          this._doFillStrokeClose(closeShape);
        -        }
        -      } else if (shapeKind === constants.TRIANGLE_FAN) {
        -        if (numVerts > 2) {
        -          // For performance reasons, try to batch as many of the
        -          // fill and stroke calls as possible.
        -          if (!this._clipping) this.drawingContext.beginPath();
        -          for (i = 2; i < numVerts; i++) {
        -            v = vertices[i];
        -            this.drawingContext.moveTo(vertices[0][0], vertices[0][1]);
        -            this.drawingContext.lineTo(vertices[i - 1][0], vertices[i - 1][1]);
        -            this.drawingContext.lineTo(v[0], v[1]);
        -            this.drawingContext.lineTo(vertices[0][0], vertices[0][1]);
        -            // If the next colour is going to be different, stroke / fill now
        -            if (i < numVerts - 1) {
        -              if (
        -                (this._doFill && v[5] !== vertices[i + 1][5]) ||
        -                (this._doStroke && v[6] !== vertices[i + 1][6])
        -              ) {
        -                if (!this._clipping && this._doFill) {
        -                  this._pInst.fill(v[5]);
        -                  this.drawingContext.fill();
        -                  this._pInst.fill(vertices[i + 1][5]);
        -                }
        -                if (!this._clipping && this._doStroke) {
        -                  this._pInst.stroke(v[6]);
        -                  this.drawingContext.stroke();
        -                  this._pInst.stroke(vertices[i + 1][6]);
        -                }
        -                this.drawingContext.closePath();
        -                if (!this._clipping) this.drawingContext.beginPath(); // Begin the next one
        -              }
        -            }
        -          }
        -          this._doFillStrokeClose(closeShape);
        -        }
        -      } else if (shapeKind === constants.QUADS) {
        -        for (i = 0; i + 3 < numVerts; i += 4) {
        -          v = vertices[i];
        -          if (!this._clipping) this.drawingContext.beginPath();
        -          this.drawingContext.moveTo(v[0], v[1]);
        -          for (j = 1; j < 4; j++) {
        -            this.drawingContext.lineTo(vertices[i + j][0], vertices[i + j][1]);
        -          }
        -          this.drawingContext.lineTo(v[0], v[1]);
        -          if (!this._clipping && this._doFill) {
        -            this._pInst.fill(vertices[i + 3][5]);
        -          }
        -          if (!this._clipping && this._doStroke) {
        -            this._pInst.stroke(vertices[i + 3][6]);
        -          }
        -          this._doFillStrokeClose(closeShape);
        -        }
        -      } else if (shapeKind === constants.QUAD_STRIP) {
        -        if (numVerts > 3) {
        -          for (i = 0; i + 1 < numVerts; i += 2) {
        -            v = vertices[i];
        -            if (!this._clipping) this.drawingContext.beginPath();
        -            if (i + 3 < numVerts) {
        -              this.drawingContext.moveTo(
        -                vertices[i + 2][0], vertices[i + 2][1]);
        -              this.drawingContext.lineTo(v[0], v[1]);
        -              this.drawingContext.lineTo(
        -                vertices[i + 1][0], vertices[i + 1][1]);
        -              this.drawingContext.lineTo(
        -                vertices[i + 3][0], vertices[i + 3][1]);
        -              if (!this._clipping && this._doFill) {
        -                this._pInst.fill(vertices[i + 3][5]);
        -              }
        -              if (!this._clipping && this._doStroke) {
        -                this._pInst.stroke(vertices[i + 3][6]);
        -              }
        -            } else {
        -              this.drawingContext.moveTo(v[0], v[1]);
        -              this.drawingContext.lineTo(
        -                vertices[i + 1][0], vertices[i + 1][1]);
        -            }
        -            this._doFillStrokeClose(closeShape);
        -          }
        -        }
        -      } else {
        -        if (!this._clipping) this.drawingContext.beginPath();
        -        this.drawingContext.moveTo(vertices[0][0], vertices[0][1]);
        -        for (i = 1; i < numVerts; i++) {
        -          v = vertices[i];
        -          if (v.isVert) {
        -            if (v.moveTo) {
        -              if (closeShape) this.drawingContext.closePath();
        -              this.drawingContext.moveTo(v[0], v[1]);
        -            } else {
        -              this.drawingContext.lineTo(v[0], v[1]);
        -            }
        -          }
        -        }
        -        this._doFillStrokeClose(closeShape);
        -      }
        -    }
        -    isCurve = false;
        -    isBezier = false;
        -    isQuadratic = false;
        -    isContour = false;
        -    if (closeShape) {
        -      vertices.pop();
        -    }
        -
        -    return this;
        -  }
           //////////////////////////////////////////////
           // SHAPE | Attributes
           //////////////////////////////////////////////
        @@ -1091,6 +934,7 @@ class Renderer2D extends p5.Renderer {
           }
         
           strokeWeight(w) {
        +    super.strokeWeight(w);
             if (typeof w === 'undefined' || w === 0) {
               // hack because lineWidth 0 doesn't work
               this.drawingContext.lineWidth = 0.0001;
        @@ -1141,30 +985,14 @@ class Renderer2D extends p5.Renderer {
         
           curve(x1, y1, x2, y2, x3, y3, x4, y4) {
             this._pInst.beginShape();
        -    this._pInst.curveVertex(x1, y1);
        -    this._pInst.curveVertex(x2, y2);
        -    this._pInst.curveVertex(x3, y3);
        -    this._pInst.curveVertex(x4, y4);
        +    this._pInst.splineVertex(x1, y1);
        +    this._pInst.splineVertex(x2, y2);
        +    this._pInst.splineVertex(x3, y3);
        +    this._pInst.splineVertex(x4, y4);
             this._pInst.endShape();
             return this;
           }
         
        -  //////////////////////////////////////////////
        -  // SHAPE | Vertex
        -  //////////////////////////////////////////////
        -
        -  _doFillStrokeClose(closeShape) {
        -    if (closeShape) {
        -      this.drawingContext.closePath();
        -    }
        -    if (!this._clipping && this._doFill) {
        -      this.drawingContext.fill();
        -    }
        -    if (!this._clipping && this._doStroke) {
        -      this.drawingContext.stroke();
        -    }
        -  }
        -
           //////////////////////////////////////////////
           // TRANSFORM
           //////////////////////////////////////////////
        @@ -1176,8 +1004,8 @@ class Renderer2D extends p5.Renderer {
           resetMatrix() {
             this.drawingContext.setTransform(1, 0, 0, 1, 0, 0);
             this.drawingContext.scale(
        -      this._pInst._pixelDensity,
        -      this._pInst._pixelDensity
        +      this._pixelDensity,
        +      this._pixelDensity
             );
             return this;
           }
        @@ -1219,13 +1047,13 @@ class Renderer2D extends p5.Renderer {
               // a system/browser font
         
               // no stroke unless specified by user
        -      if (this._doStroke && this._strokeSet) {
        +      if (this.states.strokeColor && this.states.strokeSet) {
                 this.drawingContext.strokeText(line, x, y);
               }
         
        -      if (!this._clipping && this._doFill) {
        +      if (!this._clipping && this.states.fillColor) {
                 // if fill hasn't been set by user, use default text fill
        -        if (!this._fillSet) {
        +        if (!this.states.fillSet) {
                   this._setFill(constants._DEFAULT_TEXT_FILL);
                 }
         
        @@ -1234,7 +1062,7 @@ class Renderer2D extends p5.Renderer {
             } else {
               // an opentype font, let it handle the rendering
         
        -      this._textFont._renderPath(line, x, y, { renderer: this });
        +      this.states.textFont._renderPath(line, x, y, { renderer: this });
             }
         
             p.pop();
        @@ -1243,24 +1071,47 @@ class Renderer2D extends p5.Renderer {
         
           textWidth(s) {
             if (this._isOpenType()) {
        -      return this._textFont._textWidth(s, this._textSize);
        +      return this.states.textFont._textWidth(s, this.states.textSize);
             }
         
             return this.drawingContext.measureText(s).width;
           }
         
        -  _applyTextProperties() {
        +  text(str, x, y, maxWidth, maxHeight) {
        +    let baselineHacked;
        +
        +    // baselineHacked: (HACK)
        +    // A temporary fix to conform to Processing's implementation
        +    // of BASELINE vertical alignment in a bounding box
        +
        +    if (typeof maxWidth !== 'undefined') {
        +      if (this.drawingContext.textBaseline === constants.BASELINE) {
        +        baselineHacked = true;
        +        this.drawingContext.textBaseline = constants.TOP;
        +      }
        +    }
        +
        +    const p = super.text(...arguments);
        +
        +    if (baselineHacked) {
        +      this.drawingContext.textBaseline = constants.BASELINE;
        +    }
        +
        +    return p;
        +  }
        +
        +  /*_applyTextProperties() {
             let font;
             const p = this._pInst;
         
        -    this._setProperty('_textAscent', null);
        -    this._setProperty('_textDescent', null);
        +    this.states.textAscent = null;
        +    this.states.textDescent = null;
         
        -    font = this._textFont;
        +    font = this.states.textFont;
         
             if (this._isOpenType()) {
        -      font = this._textFont.font.familyName;
        -      this._setProperty('_textStyle', this._textFont.font.styleName);
        +      font = this.states.textFont.font.familyName;
        +      this.states.textStyle = this._textFont.font.styleName;
             }
         
             let fontNameString = font || 'sans-serif';
        @@ -1268,18 +1119,18 @@ class Renderer2D extends p5.Renderer {
               // If the name includes spaces, surround in quotes
               fontNameString = `"${fontNameString}"`;
             }
        -    this.drawingContext.font = `${this._textStyle || 'normal'} ${this._textSize ||
        +    this.drawingContext.font = `${this.states.textStyle || 'normal'} ${this.states.textSize ||
               12}px ${fontNameString}`;
         
        -    this.drawingContext.textAlign = this._textAlign;
        -    if (this._textBaseline === constants.CENTER) {
        +    this.drawingContext.textAlign = this.states.textAlign;
        +    if (this.states.textBaseline === constants.CENTER) {
               this.drawingContext.textBaseline = constants._CTX_MIDDLE;
             } else {
        -      this.drawingContext.textBaseline = this._textBaseline;
        +      this.drawingContext.textBaseline = this.states.textBaseline;
             }
         
             return p;
        -  }
        +  }*/
         
           //////////////////////////////////////////////
           // STRUCTURE
        @@ -1312,30 +1163,21 @@ class Renderer2D extends p5.Renderer {
           }
         }
         
        -// Fix test
        -Renderer2D.prototype.text = function (str, x, y, maxWidth, maxHeight) {
        -  let baselineHacked;
        -
        -  // baselineHacked: (HACK)
        -  // A temporary fix to conform to Processing's implementation
        -  // of BASELINE vertical alignment in a bounding box
        -
        -  if (typeof maxWidth !== 'undefined') {
        -    if (this.drawingContext.textBaseline === constants.BASELINE) {
        -      baselineHacked = true;
        -      this.drawingContext.textBaseline = constants.TOP;
        +function renderer2D(p5, fn){
        +  /**
        +   * p5.Renderer2D
        +   * The 2D graphics canvas renderer class.
        +   * extends p5.Renderer
        +   * @private
        +   */
        +  p5.Renderer2D = Renderer2D;
        +  p5.renderers[constants.P2D] = Renderer2D;
        +  p5.renderers['p2d-hdr'] = new Proxy(Renderer2D, {
        +    construct(target, [pInst, w, h, isMainCanvas, elt]){
        +      return new target(pInst, w, h, isMainCanvas, elt, {colorSpace: "display-p3"})
             }
        -  }
        -
        -  const p = p5.Renderer.prototype.text.apply(this, arguments);
        -
        -  if (baselineHacked) {
        -    this.drawingContext.textBaseline = constants.BASELINE;
        -  }
        -
        -  return p;
        -};
        -
        -p5.Renderer2D = Renderer2D;
        +  })
        +}
         
        -export default p5.Renderer2D;
        +export default renderer2D;
        +export { Renderer2D };
        diff --git a/src/core/preload.js b/src/core/preload.js
        index f7155d361c..8a7660f5bb 100644
        --- a/src/core/preload.js
        +++ b/src/core/preload.js
        @@ -1,5 +1,68 @@
         import p5 from './main';
         
        +/**
        + * A function that's called once to load assets before the sketch runs.
        + *
        + * Declaring the function `preload()` sets a code block to run once
        + * automatically before <a href="#/p5/setup">setup()</a> or
        + * <a href="#/p5/draw">draw()</a>. It's used to load assets including
        + * multimedia files, fonts, data, and 3D models:
        + *
        + * ```js
        + * function preload() {
        + *   // Code to run before the rest of the sketch.
        + * }
        + * ```
        + *
        + * Functions such as <a href="#/p5/loadImage">loadImage()</a>,
        + * <a href="#/p5/loadFont">loadFont()</a>,
        + * <a href="#/p5/loadJSON">loadJSON()</a>, and
        + * <a href="#/p5/loadModel">loadModel()</a> are guaranteed to either
        + * finish loading or raise an error if they're called within `preload()`.
        + * Doing so ensures that assets are available when the sketch begins
        + * running.
        + *
        + * @method preload
        + * @for p5
        + *
        + * @example
        + * <div>
        + * <code>
        + * let img;
        + *
        + * // Load an image and create a p5.Image object.
        + * function preload() {
        + *   img = loadImage('assets/bricks.jpg');
        + * }
        + *
        + * function setup() {
        + *   createCanvas(100, 100);
        + *
        + *   // Draw the image.
        + *   image(img, 0, 0);
        + *
        + *   describe('A red brick wall.');
        + * }
        + * </code>
        + * </div>
        + */
        +
        +// functions that cause preload to wait
        +// more can be added by using registerPreloadMethod(func)
        +p5.prototype._preloadMethods = {
        +  loadJSON: p5.prototype,
        +  loadImage: p5.prototype,
        +  loadStrings: p5.prototype,
        +  loadXML: p5.prototype,
        +  loadBytes: p5.prototype,
        +  loadTable: p5.prototype,
        +  loadFont: p5.prototype,
        +  loadModel: p5.prototype,
        +  loadShader: p5.prototype
        +};
        +
        +p5.prototype._registeredPreloadMethods = {};
        +
         p5.prototype._promisePreloads = [
           /* Example object
           {
        @@ -16,6 +79,9 @@ p5.prototype._promisePreloads = [
           */
         ];
         
        +p5.prototype._preloadDone = false;
        +p5.prototype._preloadCount = 0;
        +
         p5.prototype.registerPromisePreload = function(setup) {
           p5.prototype._promisePreloads.push(setup);
         };
        @@ -134,3 +200,51 @@ p5.prototype._legacyPreloadGenerator = function(
           }
           return returnedFunction;
         };
        +
        +p5.prototype._decrementPreload = function() {
        +  if (!this._preloadDone && typeof this.preload === 'function') {
        +    this._preloadCount = context._preloadCount - 1;
        +    this._runIfPreloadsAreDone();
        +  }
        +};
        +
        +p5.prototype._wrapPreload = function(obj, fnName) {
        +  return (...args) => {
        +    //increment counter
        +    this._incrementPreload();
        +    //call original function
        +    return this._registeredPreloadMethods[fnName].apply(obj, args);
        +  };
        +};
        +
        +p5.prototype._incrementPreload = function() {
        +  // Do nothing if we tried to increment preloads outside of `preload`
        +  if (this._preloadDone) return;
        +  this._preloadCount = context._preloadCount + 1;
        +};
        +
        +p5.prototype._runIfPreloadsAreDone = function() {
        +  const context = this._isGlobal ? window : this;
        +  if (context._preloadCount === 0) {
        +    const loadingScreen = document.getElementById(context._loadingScreenId);
        +    if (loadingScreen) {
        +      loadingScreen.parentNode.removeChild(loadingScreen);
        +    }
        +    // this.callRegisteredHooksFor('afterPreload');
        +    if (!this._setupDone) {
        +      this._lastTargetFrameTime = window.performance.now();
        +      this._lastRealFrameTime = window.performance.now();
        +      context._setup();
        +      if (!this._recording) {
        +        context._draw();
        +      }
        +    }
        +  }
        +};
        +
        +p5.prototype.registerPreloadMethod = function(fnString, obj) {
        +  // obj = obj || p5.prototype;
        +  if (!p5.prototype._preloadMethods.hasOwnProperty(fnString)) {
        +    p5.prototype._preloadMethods[fnString] = obj;
        +  }
        +};
        diff --git a/src/core/rendering.js b/src/core/rendering.js
        index 22982b0f99..47f1a6447d 100644
        --- a/src/core/rendering.js
        +++ b/src/core/rendering.js
        @@ -4,1253 +4,694 @@
          * @for p5
          */
         
        -import p5 from './main';
         import * as constants from './constants';
        -import './p5.Graphics';
        -import './p5.Renderer2D';
        -import '../webgl/p5.RendererGL';
        -let defaultId = 'defaultCanvas0'; // this gets set again in createCanvas
        -const defaultClass = 'p5Canvas';
        +import { Framebuffer } from '../webgl/p5.Framebuffer';
         
        -/**
        - * Creates a canvas element on the web page.
        - *
        - * `createCanvas()` creates the main drawing canvas for a sketch. It should
        - * only be called once at the beginning of <a href="#/p5/setup">setup()</a>.
        - * Calling `createCanvas()` more than once causes unpredictable behavior.
        - *
        - * The first two parameters, `width` and `height`, are optional. They set the
        - * dimensions of the canvas and the values of the
        - * <a href="#/p5/width">width</a> and <a href="#/p5/height">height</a> system
        - * variables. For example, calling `createCanvas(900, 500)` creates a canvas
        - * that's 900×500 pixels. By default, `width` and `height` are both 100.
        - *
        - * The third parameter is also optional. If either of the constants `P2D` or
        - * `WEBGL` is passed, as in `createCanvas(900, 500, WEBGL)`, then it will set
        - * the sketch's rendering mode. If an existing
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement" target="_blank">HTMLCanvasElement</a>
        - * is passed, as in `createCanvas(900, 500, myCanvas)`, then it will be used
        - * by the sketch.
        - *
        - * The fourth parameter is also optional. If an existing
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement" target="_blank">HTMLCanvasElement</a>
        - * is passed, as in `createCanvas(900, 500, WEBGL, myCanvas)`, then it will be
        - * used by the sketch.
        - *
        - * Note: In WebGL mode, the canvas will use a WebGL2 context if it's supported
        - * by the browser. Check the <a href="#/p5/webglVersion">webglVersion</a>
        - * system variable to check what version is being used, or call
        - * `setAttributes({ version: 1 })` to create a WebGL1 context.
        - *
        - * @method createCanvas
        - * @param  {Number} [width] width of the canvas. Defaults to 100.
        - * @param  {Number} [height] height of the canvas. Defaults to 100.
        - * @param  {Constant} [renderer] either P2D or WEBGL. Defaults to `P2D`.
        - * @param  {HTMLCanvasElement} [canvas] existing canvas element that should be used for the sketch.
        - * @return {p5.Renderer} new `p5.Renderer` that holds the canvas.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Draw a diagonal line.
        - *   line(0, 0, width, height);
        - *
        - *   describe('A diagonal line drawn from top-left to bottom-right on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 50);
        - *
        - *   background(180);
        - *
        - *   // Draw a diagonal line.
        - *   line(0, 0, width, height);
        - *
        - *   describe('A diagonal line drawn from top-left to bottom-right on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Use WebGL mode.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(180);
        - *
        - *   // Draw a diagonal line.
        - *   line(-width / 2, -height / 2, width / 2, height / 2);
        - *
        - *   describe('A diagonal line drawn from top-left to bottom-right on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Create a p5.Render object.
        - *   let cnv = createCanvas(50, 50);
        - *
        - *   // Position the canvas.
        - *   cnv.position(10, 20);
        - *
        - *   background(180);
        - *
        - *   // Draw a diagonal line.
        - *   line(0, 0, width, height);
        - *
        - *   describe('A diagonal line drawn from top-left to bottom-right on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method createCanvas
        - * @param  {Number} [width]
        - * @param  {Number} [height]
        - * @param  {HTMLCanvasElement} [canvas]
        - * @return {p5.Renderer}
        - */
        -p5.prototype.createCanvas = function(w, h, renderer, canvas) {
        -  p5._validateParameters('createCanvas', arguments);
        -  //optional: renderer, otherwise defaults to p2d
        -
        -  let r;
        -  if (arguments[2] instanceof HTMLCanvasElement) {
        -    renderer = constants.P2D;
        -    canvas = arguments[2];
        -  } else {
        -    r = renderer || constants.P2D;
        -  }
        +let renderers;
        +function rendering(p5, fn){
        +  // Extend additional renderers object to p5 class, new renderer can be similarly attached
        +  renderers = p5.renderers = {};
         
        -  let c;
        +  /**
        +   * Creates a canvas element on the web page.
        +   *
        +   * `createCanvas()` creates the main drawing canvas for a sketch. It should
        +   * only be called once at the beginning of <a href="#/p5/setup">setup()</a>.
        +   * Calling `createCanvas()` more than once causes unpredictable behavior.
        +   *
        +   * The first two parameters, `width` and `height`, are optional. They set the
        +   * dimensions of the canvas and the values of the
        +   * <a href="#/p5/width">width</a> and <a href="#/p5/height">height</a> system
        +   * variables. For example, calling `createCanvas(900, 500)` creates a canvas
        +   * that's 900×500 pixels. By default, `width` and `height` are both 100.
        +   *
        +   * The third parameter is also optional. If either of the constants `P2D` or
        +   * `WEBGL` is passed, as in `createCanvas(900, 500, WEBGL)`, then it will set
        +   * the sketch's rendering mode. If an existing
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement" target="_blank">HTMLCanvasElement</a>
        +   * is passed, as in `createCanvas(900, 500, myCanvas)`, then it will be used
        +   * by the sketch.
        +   *
        +   * The fourth parameter is also optional. If an existing
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement" target="_blank">HTMLCanvasElement</a>
        +   * is passed, as in `createCanvas(900, 500, WEBGL, myCanvas)`, then it will be
        +   * used by the sketch.
        +   *
        +   * Note: In WebGL mode, the canvas will use a WebGL2 context if it's supported
        +   * by the browser. Check the <a href="#/p5/webglVersion">webglVersion</a>
        +   * system variable to check what version is being used, or call
        +   * `setAttributes({ version: 1 })` to create a WebGL1 context.
        +   *
        +   * @method createCanvas
        +   * @param  {Number} [width] width of the canvas. Defaults to 100.
        +   * @param  {Number} [height] height of the canvas. Defaults to 100.
        +   * @param  {(P2D|WEBGL)} [renderer] either P2D or WEBGL. Defaults to `P2D`.
        +   * @param  {HTMLCanvasElement} [canvas] existing canvas element that should be used for the sketch.
        +   * @return {p5.Renderer} new `p5.Renderer` that holds the canvas.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw a diagonal line.
        +   *   line(0, 0, width, height);
        +   *
        +   *   describe('A diagonal line drawn from top-left to bottom-right on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 50);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw a diagonal line.
        +   *   line(0, 0, width, height);
        +   *
        +   *   describe('A diagonal line drawn from top-left to bottom-right on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Use WebGL mode.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw a diagonal line.
        +   *   line(-width / 2, -height / 2, width / 2, height / 2);
        +   *
        +   *   describe('A diagonal line drawn from top-left to bottom-right on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Render object.
        +   *   let cnv = createCanvas(50, 50);
        +   *
        +   *   // Position the canvas.
        +   *   cnv.position(10, 20);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw a diagonal line.
        +   *   line(0, 0, width, height);
        +   *
        +   *   describe('A diagonal line drawn from top-left to bottom-right on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method createCanvas
        +   * @param  {Number} [width]
        +   * @param  {Number} [height]
        +   * @param  {HTMLCanvasElement} [canvas]
        +   * @return {p5.Renderer}
        +   */
        +  fn.createCanvas = function (w, h, renderer, ...args) {
        +    // p5._validateParameters('createCanvas', arguments);
        +    //optional: renderer, otherwise defaults to p2d
         
        -  if (canvas) {
        -    c = document.getElementById(defaultId);
        -    if (c) {
        -      c.parentNode.removeChild(c); //replace the existing defaultCanvas
        -    }
        -    c = canvas;
        -    this._defaultGraphicsCreated = false;
        -  } else {
        -    if (r === constants.WEBGL) {
        -      c = document.getElementById(defaultId);
        -      if (c) {
        -        //if defaultCanvas already exists
        -        c.parentNode.removeChild(c); //replace the existing defaultCanvas
        -        const thisRenderer = this._renderer;
        -        this._elements = this._elements.filter(e => e !== thisRenderer);
        -      }
        -      c = document.createElement('canvas');
        -      c.id = defaultId;
        -      c.classList.add(defaultClass);
        -    } else {
        -      if (!this._defaultGraphicsCreated) {
        -        if (canvas) {
        -          c = canvas;
        -        } else {
        -          c = document.createElement('canvas');
        -        }
        -        let i = 0;
        -        while (document.getElementById(`defaultCanvas${i}`)) {
        -          i++;
        -        }
        -        defaultId = `defaultCanvas${i}`;
        -        c.id = defaultId;
        -        c.classList.add(defaultClass);
        -      } else {
        -        // resize the default canvas if new one is created
        -        c = this.canvas;
        -      }
        +    let selectedRenderer = constants.P2D
        +    // Check third argument whether it is renderer constants
        +    if(Reflect.ownKeys(renderers).includes(renderer)){
        +      selectedRenderer = renderer;
        +    }else{
        +      args.unshift(renderer);
             }
         
        -    // set to invisible if still in setup (to prevent flashing with manipulate)
        -    if (!this._setupDone) {
        -      c.dataset.hidden = true; // tag to show later
        -      c.style.visibility = 'hidden';
        -    }
        +    // Init our graphics renderer
        +    if(this._renderer) this._renderer.remove();
        +    this._renderer = new renderers[selectedRenderer](this, w, h, true, ...args);
        +    this._defaultGraphicsCreated = true;
        +    this._elements.push(this._renderer);
        +    this._renderer._applyDefaults();
         
        -    if (this._userNode) {
        -      // user input node case
        -      this._userNode.appendChild(c);
        -    } else {
        -      //create main element
        -      if (document.getElementsByTagName('main').length === 0) {
        -        let m = document.createElement('main');
        -        document.body.appendChild(m);
        -      }
        -      //append canvas to main
        -      document.getElementsByTagName('main')[0].appendChild(c);
        +    // Make the renderer own `pixels`
        +    if (!Object.hasOwn(this, 'pixels')) {
        +      Object.defineProperty(this, 'pixels', {
        +        get(){
        +          return this._renderer?.pixels;
        +        }
        +      });
             }
        -  }
         
        -  // Init our graphics renderer
        -  //webgl mode
        -  if (r === constants.WEBGL) {
        -    this._setProperty('_renderer', new p5.RendererGL(c, this, true));
        -    this._elements.push(this._renderer);
        -    const dimensions =
        -      this._renderer._adjustDimensions(w, h);
        -    w = dimensions.adjustedWidth;
        -    h = dimensions.adjustedHeight;
        -  } else {
        -    //P2D mode
        -    if (!this._defaultGraphicsCreated) {
        -      this._setProperty('_renderer', new p5.Renderer2D(c, this, true));
        -      this._defaultGraphicsCreated = true;
        -      this._elements.push(this._renderer);
        -    }
        -  }
        -  this._renderer.resize(w, h);
        -  this._renderer._applyDefaults();
        -  return this._renderer;
        -};
        +    return this._renderer;
        +  };
         
        -/**
        - * Resizes the canvas to a given width and height.
        - *
        - * `resizeCanvas()` immediately clears the canvas and calls
        - * <a href="#/p5/redraw">redraw()</a>. It's common to call `resizeCanvas()`
        - * within the body of <a href="#/p5/windowResized">windowResized()</a> like
        - * so:
        - *
        - * ```js
        - * function windowResized() {
        - *   resizeCanvas(windowWidth, windowHeight);
        - * }
        - * ```
        - *
        - * The first two parameters, `width` and `height`, set the dimensions of the
        - * canvas. They also the values of the <a href="#/p5/width">width</a> and
        - * <a href="#/p5/height">height</a> system variables. For example, calling
        - * `resizeCanvas(300, 500)` resizes the canvas to 300×500 pixels, then sets
        - * <a href="#/p5/width">width</a> to 300 and
        - * <a href="#/p5/height">height</a> 500.
        - *
        - * The third parameter, `noRedraw`, is optional. If `true` is passed, as in
        - * `resizeCanvas(300, 500, true)`, then the canvas will be canvas to 300×500
        - * pixels but the <a href="#/p5/redraw">redraw()</a> function won't be called
        - * immediately. By default, <a href="#/p5/redraw">redraw()</a> is called
        - * immediately when `resizeCanvas()` finishes executing.
        - *
        - * @method resizeCanvas
        - * @param  {Number} width width of the canvas.
        - * @param  {Number} height height of the canvas.
        - * @param  {Boolean} [noRedraw] whether to delay calling
        - *                              <a href="#/p5/redraw">redraw()</a>. Defaults
        - *                              to `false`.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to resize the canvas.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A white circle drawn on a gray background. The canvas shrinks by half the first time the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(180);
        - *
        - *   // Draw a circle at the center of the canvas.
        - *   circle(width / 2, height / 2, 20);
        - * }
        - *
        - * // Resize the canvas when the user double-clicks.
        - * function doubleClicked() {
        - *   resizeCanvas(50, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Resize the web browser to change the canvas size.
        - *
        - * function setup() {
        - *   createCanvas(windowWidth, windowHeight);
        - *
        - *   describe('A white circle drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(180);
        - *
        - *   // Draw a circle at the center of the canvas.
        - *   circle(width / 2, height / 2, 20);
        - * }
        - *
        - * // Always resize the canvas to fill the browser window.
        - * function windowResized() {
        - *   resizeCanvas(windowWidth, windowHeight);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.resizeCanvas = function(w, h, noRedraw) {
        -  p5._validateParameters('resizeCanvas', arguments);
        -  if (this._renderer) {
        -    // save canvas properties
        -    const props = {};
        -    for (const key in this.drawingContext) {
        -      const val = this.drawingContext[key];
        -      if (typeof val !== 'object' && typeof val !== 'function') {
        -        props[key] = val;
        -      }
        -    }
        -    if (this._renderer instanceof p5.RendererGL) {
        -      const dimensions =
        -        this._renderer._adjustDimensions(w, h);
        -      w = dimensions.adjustedWidth;
        -      h = dimensions.adjustedHeight;
        -    }
        -    this.width = w;
        -    this.height = h;
        -    // Make sure width and height are updated before the renderer resizes so
        -    // that framebuffers updated from the resize read the correct size
        -    this._renderer.resize(w, h);
        -    // reset canvas properties
        -    for (const savedKey in props) {
        -      try {
        -        this.drawingContext[savedKey] = props[savedKey];
        -      } catch (err) {
        -        // ignore read-only property errors
        +  /**
        +   * Resizes the canvas to a given width and height.
        +   *
        +   * `resizeCanvas()` immediately clears the canvas and calls
        +   * <a href="#/p5/redraw">redraw()</a>. It's common to call `resizeCanvas()`
        +   * within the body of <a href="#/p5/windowResized">windowResized()</a> like
        +   * so:
        +   *
        +   * ```js
        +   * function windowResized() {
        +   *   resizeCanvas(windowWidth, windowHeight);
        +   * }
        +   * ```
        +   *
        +   * The first two parameters, `width` and `height`, set the dimensions of the
        +   * canvas. They also the values of the <a href="#/p5/width">width</a> and
        +   * <a href="#/p5/height">height</a> system variables. For example, calling
        +   * `resizeCanvas(300, 500)` resizes the canvas to 300×500 pixels, then sets
        +   * <a href="#/p5/width">width</a> to 300 and
        +   * <a href="#/p5/height">height</a> 500.
        +   *
        +   * The third parameter, `noRedraw`, is optional. If `true` is passed, as in
        +   * `resizeCanvas(300, 500, true)`, then the canvas will be canvas to 300×500
        +   * pixels but the <a href="#/p5/redraw">redraw()</a> function won't be called
        +   * immediately. By default, <a href="#/p5/redraw">redraw()</a> is called
        +   * immediately when `resizeCanvas()` finishes executing.
        +   *
        +   * @method resizeCanvas
        +   * @param  {Number} width width of the canvas.
        +   * @param  {Number} height height of the canvas.
        +   * @param  {Boolean} [noRedraw] whether to delay calling
        +   *                              <a href="#/p5/redraw">redraw()</a>. Defaults
        +   *                              to `false`.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to resize the canvas.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A white circle drawn on a gray background. The canvas shrinks by half the first time the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a circle at the center of the canvas.
        +   *   circle(width / 2, height / 2, 20);
        +   * }
        +   *
        +   * // Resize the canvas when the user double-clicks.
        +   * function doubleClicked() {
        +   *   resizeCanvas(50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Resize the web browser to change the canvas size.
        +   *
        +   * function setup() {
        +   *   createCanvas(windowWidth, windowHeight);
        +   *
        +   *   describe('A white circle drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a circle at the center of the canvas.
        +   *   circle(width / 2, height / 2, 20);
        +   * }
        +   *
        +   * // Always resize the canvas to fill the browser window.
        +   * function windowResized() {
        +   *   resizeCanvas(windowWidth, windowHeight);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.resizeCanvas = function (w, h, noRedraw) {
        +    // p5._validateParameters('resizeCanvas', arguments);
        +    if (this._renderer) {
        +      // Make sure width and height are updated before the renderer resizes so
        +      // that framebuffers updated from the resize read the correct size
        +      this._renderer.resize(w, h);
        +
        +      if (!noRedraw) {
        +        this.redraw();
               }
             }
        -    if (!noRedraw) {
        -      this.redraw();
        +    //accessible Outputs
        +    if (this._addAccsOutput()) {
        +      this._updateAccsOutput();
             }
        -  }
        -  //accessible Outputs
        -  if (this._addAccsOutput()) {
        -    this._updateAccsOutput();
        -  }
        -};
        +  };
         
        -/**
        - * Removes the default canvas.
        - *
        - * By default, a 100×100 pixels canvas is created without needing to call
        - * <a href="#/p5/createCanvas">createCanvas()</a>. `noCanvas()` removes the
        - * default canvas for sketches that don't need it.
        - *
        - * @method noCanvas
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   noCanvas();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noCanvas = function() {
        -  if (this.canvas) {
        -    this.canvas.parentNode.removeChild(this.canvas);
        -  }
        -};
        +  /**
        +   * Removes the default canvas.
        +   *
        +   * By default, a 100×100 pixels canvas is created without needing to call
        +   * <a href="#/p5/createCanvas">createCanvas()</a>. `noCanvas()` removes the
        +   * default canvas for sketches that don't need it.
        +   *
        +   * @method noCanvas
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   noCanvas();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noCanvas = function () {
        +    if (this.canvas) {
        +      this.canvas.parentNode.removeChild(this.canvas);
        +    }
        +  };
         
        -/**
        - * Creates a <a href="#/p5.Graphics">p5.Graphics</a> object.
        - *
        - * `createGraphics()` creates an offscreen drawing canvas (graphics buffer)
        - * and returns it as a <a href="#/p5.Graphics">p5.Graphics</a> object. Drawing
        - * to a separate graphics buffer can be helpful for performance and for
        - * organizing code.
        - *
        - * The first two parameters, `width` and `height`, are optional. They set the
        - * dimensions of the <a href="#/p5.Graphics">p5.Graphics</a> object. For
        - * example, calling `createGraphics(900, 500)` creates a graphics buffer
        - * that's 900×500 pixels.
        - *
        - * The third parameter is also optional. If either of the constants `P2D` or
        - * `WEBGL` is passed, as in `createGraphics(900, 500, WEBGL)`, then it will set
        - * the <a href="#/p5.Graphics">p5.Graphics</a> object's rendering mode. If an
        - * existing
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement" target="_blank">HTMLCanvasElement</a>
        - * is passed, as in `createGraphics(900, 500, myCanvas)`, then it will be used
        - * by the graphics buffer.
        - *
        - * The fourth parameter is also optional. If an existing
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement" target="_blank">HTMLCanvasElement</a>
        - * is passed, as in `createGraphics(900, 500, WEBGL, myCanvas)`, then it will be
        - * used by the graphics buffer.
        - *
        - * Note: In WebGL mode, the <a href="#/p5.Graphics">p5.Graphics</a> object
        - * will use a WebGL2 context if it's supported by the browser. Check the
        - * <a href="#/p5/webglVersion">webglVersion</a> system variable to check what
        - * version is being used, or call `setAttributes({ version: 1 })` to create a
        - * WebGL1 context.
        - *
        - * @method createGraphics
        - * @param  {Number} width width of the graphics buffer.
        - * @param  {Number} height height of the graphics buffer.
        - * @param  {Constant} [renderer] either P2D or WEBGL. Defaults to P2D.
        - * @param  {HTMLCanvasElement} [canvas] existing canvas element that should be
        - *                                      used for the graphics buffer..
        - * @return {p5.Graphics} new graphics buffer.
        - *
        - * @example
        - * <div>
        - * <code>
        - * //  Double-click to draw the contents of the graphics buffer.
        - *
        - * let pg;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Create the p5.Graphics object.
        - *   pg = createGraphics(50, 50);
        - *
        - *   // Draw to the graphics buffer.
        - *   pg.background(100);
        - *   pg.circle(pg.width / 2, pg.height / 2, 20);
        - *
        - *   describe('A gray square. A smaller, darker square with a white circle at its center appears when the user double-clicks.');
        - * }
        - *
        - * // Display the graphics buffer when the user double-clicks.
        - * function doubleClicked() {
        - *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        - *     image(pg, 25, 25);
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * //  Double-click to draw the contents of the graphics buffer.
        - *
        - * let pg;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Create the p5.Graphics object in WebGL mode.
        - *   pg = createGraphics(50, 50, WEBGL);
        - *
        - *   // Draw to the graphics buffer.
        - *   pg.background(100);
        - *   pg.lights();
        - *   pg.noStroke();
        - *   pg.rotateX(QUARTER_PI);
        - *   pg.rotateY(QUARTER_PI);
        - *   pg.torus(15, 5);
        - *
        - *   describe('A gray square. A smaller, darker square with a white torus at its center appears when the user double-clicks.');
        - * }
        - *
        - * // Display the graphics buffer when the user double-clicks.
        - * function doubleClicked() {
        - *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        - *     image(pg, 25, 25);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method createGraphics
        - * @param  {Number} width
        - * @param  {Number} height
        - * @param  {HTMLCanvasElement} [canvas]
        - * @return {p5.Graphics}
        - */
        -p5.prototype.createGraphics = function(w, h, ...args) {
        -/**
        -  * args[0] is expected to be renderer
        -  * args[1] is expected to be canvas
        -  */
        -  if (args[0] instanceof HTMLCanvasElement) {
        -    args[1] = args[0];
        -    args[0] = constants.P2D;
        -  }
        -  p5._validateParameters('createGraphics', arguments);
        -  return new p5.Graphics(w, h, args[0], this, args[1]);
        -};
        +  /**
        +   * Creates a <a href="#/p5.Graphics">p5.Graphics</a> object.
        +   *
        +   * `createGraphics()` creates an offscreen drawing canvas (graphics buffer)
        +   * and returns it as a <a href="#/p5.Graphics">p5.Graphics</a> object. Drawing
        +   * to a separate graphics buffer can be helpful for performance and for
        +   * organizing code.
        +   *
        +   * The first two parameters, `width` and `height`, are optional. They set the
        +   * dimensions of the <a href="#/p5.Graphics">p5.Graphics</a> object. For
        +   * example, calling `createGraphics(900, 500)` creates a graphics buffer
        +   * that's 900×500 pixels.
        +   *
        +   * The third parameter is also optional. If either of the constants `P2D` or
        +   * `WEBGL` is passed, as in `createGraphics(900, 500, WEBGL)`, then it will set
        +   * the <a href="#/p5.Graphics">p5.Graphics</a> object's rendering mode. If an
        +   * existing
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement" target="_blank">HTMLCanvasElement</a>
        +   * is passed, as in `createGraphics(900, 500, myCanvas)`, then it will be used
        +   * by the graphics buffer.
        +   *
        +   * The fourth parameter is also optional. If an existing
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement" target="_blank">HTMLCanvasElement</a>
        +   * is passed, as in `createGraphics(900, 500, WEBGL, myCanvas)`, then it will be
        +   * used by the graphics buffer.
        +   *
        +   * Note: In WebGL mode, the <a href="#/p5.Graphics">p5.Graphics</a> object
        +   * will use a WebGL2 context if it's supported by the browser. Check the
        +   * <a href="#/p5/webglVersion">webglVersion</a> system variable to check what
        +   * version is being used, or call `setAttributes({ version: 1 })` to create a
        +   * WebGL1 context.
        +   *
        +   * @method createGraphics
        +   * @param  {Number} width width of the graphics buffer.
        +   * @param  {Number} height height of the graphics buffer.
        +   * @param  {(P2D|WEBGL)} [renderer] either P2D or WEBGL. Defaults to P2D.
        +   * @param  {HTMLCanvasElement} [canvas] existing canvas element that should be
        +   *                                      used for the graphics buffer..
        +   * @return {p5.Graphics} new graphics buffer.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * //  Double-click to draw the contents of the graphics buffer.
        +   *
        +   * let pg;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create the p5.Graphics object.
        +   *   pg = createGraphics(50, 50);
        +   *
        +   *   // Draw to the graphics buffer.
        +   *   pg.background(100);
        +   *   pg.circle(pg.width / 2, pg.height / 2, 20);
        +   *
        +   *   describe('A gray square. A smaller, darker square with a white circle at its center appears when the user double-clicks.');
        +   * }
        +   *
        +   * // Display the graphics buffer when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        +   *     image(pg, 25, 25);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * //  Double-click to draw the contents of the graphics buffer.
        +   *
        +   * let pg;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create the p5.Graphics object in WebGL mode.
        +   *   pg = createGraphics(50, 50, WEBGL);
        +   *
        +   *   // Draw to the graphics buffer.
        +   *   pg.background(100);
        +   *   pg.lights();
        +   *   pg.noStroke();
        +   *   pg.rotateX(QUARTER_PI);
        +   *   pg.rotateY(QUARTER_PI);
        +   *   pg.torus(15, 5);
        +   *
        +   *   describe('A gray square. A smaller, darker square with a white torus at its center appears when the user double-clicks.');
        +   * }
        +   *
        +   * // Display the graphics buffer when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        +   *     image(pg, 25, 25);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method createGraphics
        +   * @param  {Number} width
        +   * @param  {Number} height
        +   * @param  {HTMLCanvasElement} [canvas]
        +   * @return {p5.Graphics}
        +   */
        +  fn.createGraphics = function (w, h, ...args) {
        +    /**
        +      * args[0] is expected to be renderer
        +      * args[1] is expected to be canvas
        +      */
        +    if (args[0] instanceof HTMLCanvasElement) {
        +      args[1] = args[0];
        +      args[0] = constants.P2D;
        +    }
        +    // p5._validateParameters('createGraphics', arguments);
        +    return new p5.Graphics(w, h, args[0], this, args[1]);
        +  };
         
        -/**
        - * Creates and a new <a href="#/p5.Framebuffer">p5.Framebuffer</a> object.
        - *
        - * <a href="#/p5.Framebuffer">p5.Framebuffer</a> objects are separate drawing
        - * surfaces that can be used as textures in WebGL mode. They're similar to
        - * <a href="#/p5.Graphics">p5.Graphics</a> objects and generally run much
        - * faster when used as textures.
        - *
        - * The parameter, `options`, is optional. An object can be passed to configure
        - * the <a href="#/p5.Framebuffer">p5.Framebuffer</a> object. The available
        - * properties are:
        - *
        - * - `format`: data format of the texture, either `UNSIGNED_BYTE`, `FLOAT`, or `HALF_FLOAT`. Default is `UNSIGNED_BYTE`.
        - * - `channels`: whether to store `RGB` or `RGBA` color channels. Default is to match the main canvas which is `RGBA`.
        - * - `depth`: whether to include a depth buffer. Default is `true`.
        - * - `depthFormat`: data format of depth information, either `UNSIGNED_INT` or `FLOAT`. Default is `FLOAT`.
        - * - `stencil`: whether to include a stencil buffer for masking. `depth` must be `true` for this feature to work. Defaults to the value of `depth` which is `true`.
        - * - `antialias`: whether to perform anti-aliasing. If set to `true`, as in `{ antialias: true }`, 2 samples will be used by default. The number of samples can also be set, as in `{ antialias: 4 }`. Default is to match <a href="#/p5/setAttributes">setAttributes()</a> which is `false` (`true` in Safari).
        - * - `width`: width of the <a href="#/p5.Framebuffer">p5.Framebuffer</a> object. Default is to always match the main canvas width.
        - * - `height`: height of the <a href="#/p5.Framebuffer">p5.Framebuffer</a> object. Default is to always match the main canvas height.
        - * - `density`: pixel density of the <a href="#/p5.Framebuffer">p5.Framebuffer</a> object. Default is to always match the main canvas pixel density.
        - * - `textureFiltering`: how to read values from the <a href="#/p5.Framebuffer">p5.Framebuffer</a> object. Either `LINEAR` (nearby pixels will be interpolated) or `NEAREST` (no interpolation). Generally, use `LINEAR` when using the texture as an image and `NEAREST` if reading the texture as data. Default is `LINEAR`.
        - *
        - * If the `width`, `height`, or `density` attributes are set, they won't automatically match the main canvas and must be changed manually.
        - *
        - * Note: `createFramebuffer()` can only be used in WebGL mode.
        - *
        - * @method createFramebuffer
        - * @param {Object} [options] configuration options.
        - * @return {p5.Framebuffer} new framebuffer.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myBuffer;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Framebuffer object.
        - *   myBuffer = createFramebuffer();
        - *
        - *   describe('A grid of white toruses rotating against a dark gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Start drawing to the p5.Framebuffer object.
        - *   myBuffer.begin();
        - *
        - *   // Clear the drawing surface.
        - *   clear();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Rotate the coordinate system.
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Style the torus.
        - *   noStroke();
        - *
        - *   // Draw the torus.
        - *   torus(20);
        - *
        - *   // Stop drawing to the p5.Framebuffer object.
        - *   myBuffer.end();
        - *
        - *   // Iterate from left to right.
        - *   for (let x = -50; x < 50; x += 25) {
        - *     // Iterate from top to bottom.
        - *     for (let y = -50; y < 50; y += 25) {
        - *       // Draw the p5.Framebuffer object to the canvas.
        - *       image(myBuffer, x, y, 25, 25);
        - *     }
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myBuffer;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create an options object.
        - *   let options = { width: 25, height: 25 };
        - *
        - *   // Create a p5.Framebuffer object.
        - *   // Use options for configuration.
        - *   myBuffer = createFramebuffer(options);
        - *
        - *   describe('A grid of white toruses rotating against a dark gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Start drawing to the p5.Framebuffer object.
        - *   myBuffer.begin();
        - *
        - *   // Clear the drawing surface.
        - *   clear();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Rotate the coordinate system.
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Style the torus.
        - *   noStroke();
        - *
        - *   // Draw the torus.
        - *   torus(5, 2.5);
        - *
        - *   // Stop drawing to the p5.Framebuffer object.
        - *   myBuffer.end();
        - *
        - *   // Iterate from left to right.
        - *   for (let x = -50; x < 50; x += 25) {
        - *     // Iterate from top to bottom.
        - *     for (let y = -50; y < 50; y += 25) {
        - *       // Draw the p5.Framebuffer object to the canvas.
        - *       image(myBuffer, x, y);
        - *     }
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createFramebuffer = function(options) {
        -  return new p5.Framebuffer(this, options);
        -};
        +  /**
        +   * Creates and a new <a href="#/p5.Framebuffer">p5.Framebuffer</a> object.
        +   *
        +   * <a href="#/p5.Framebuffer">p5.Framebuffer</a> objects are separate drawing
        +   * surfaces that can be used as textures in WebGL mode. They're similar to
        +   * <a href="#/p5.Graphics">p5.Graphics</a> objects and generally run much
        +   * faster when used as textures.
        +   *
        +   * The parameter, `options`, is optional. An object can be passed to configure
        +   * the <a href="#/p5.Framebuffer">p5.Framebuffer</a> object. The available
        +   * properties are:
        +   *
        +   * - `format`: data format of the texture, either `UNSIGNED_BYTE`, `FLOAT`, or `HALF_FLOAT`. Default is `UNSIGNED_BYTE`.
        +   * - `channels`: whether to store `RGB` or `RGBA` color channels. Default is to match the main canvas which is `RGBA`.
        +   * - `depth`: whether to include a depth buffer. Default is `true`.
        +   * - `depthFormat`: data format of depth information, either `UNSIGNED_INT` or `FLOAT`. Default is `FLOAT`.
        +   * - `stencil`: whether to include a stencil buffer for masking. `depth` must be `true` for this feature to work. Defaults to the value of `depth` which is `true`.
        +   * - `antialias`: whether to perform anti-aliasing. If set to `true`, as in `{ antialias: true }`, 2 samples will be used by default. The number of samples can also be set, as in `{ antialias: 4 }`. Default is to match <a href="#/p5/setAttributes">setAttributes()</a> which is `false` (`true` in Safari).
        +   * - `width`: width of the <a href="#/p5.Framebuffer">p5.Framebuffer</a> object. Default is to always match the main canvas width.
        +   * - `height`: height of the <a href="#/p5.Framebuffer">p5.Framebuffer</a> object. Default is to always match the main canvas height.
        +   * - `density`: pixel density of the <a href="#/p5.Framebuffer">p5.Framebuffer</a> object. Default is to always match the main canvas pixel density.
        +   * - `textureFiltering`: how to read values from the <a href="#/p5.Framebuffer">p5.Framebuffer</a> object. Either `LINEAR` (nearby pixels will be interpolated) or `NEAREST` (no interpolation). Generally, use `LINEAR` when using the texture as an image and `NEAREST` if reading the texture as data. Default is `LINEAR`.
        +   *
        +   * If the `width`, `height`, or `density` attributes are set, they won't automatically match the main canvas and must be changed manually.
        +   *
        +   * Note: `createFramebuffer()` can only be used in WebGL mode.
        +   *
        +   * @method createFramebuffer
        +   * @param {Object} [options] configuration options.
        +   * @return {p5.Framebuffer} new framebuffer.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myBuffer;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Framebuffer object.
        +   *   myBuffer = createFramebuffer();
        +   *
        +   *   describe('A grid of white toruses rotating against a dark gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Start drawing to the p5.Framebuffer object.
        +   *   myBuffer.begin();
        +   *
        +   *   // Clear the drawing surface.
        +   *   clear();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Style the torus.
        +   *   noStroke();
        +   *
        +   *   // Draw the torus.
        +   *   torus(20);
        +   *
        +   *   // Stop drawing to the p5.Framebuffer object.
        +   *   myBuffer.end();
        +   *
        +   *   // Iterate from left to right.
        +   *   for (let x = -50; x < 50; x += 25) {
        +   *     // Iterate from top to bottom.
        +   *     for (let y = -50; y < 50; y += 25) {
        +   *       // Draw the p5.Framebuffer object to the canvas.
        +   *       image(myBuffer, x, y, 25, 25);
        +   *     }
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let myBuffer;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create an options object.
        +   *   let options = { width: 25, height: 25 };
        +   *
        +   *   // Create a p5.Framebuffer object.
        +   *   // Use options for configuration.
        +   *   myBuffer = createFramebuffer(options);
        +   *
        +   *   describe('A grid of white toruses rotating against a dark gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Start drawing to the p5.Framebuffer object.
        +   *   myBuffer.begin();
        +   *
        +   *   // Clear the drawing surface.
        +   *   clear();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Style the torus.
        +   *   noStroke();
        +   *
        +   *   // Draw the torus.
        +   *   torus(5, 2.5);
        +   *
        +   *   // Stop drawing to the p5.Framebuffer object.
        +   *   myBuffer.end();
        +   *
        +   *   // Iterate from left to right.
        +   *   for (let x = -50; x < 50; x += 25) {
        +   *     // Iterate from top to bottom.
        +   *     for (let y = -50; y < 50; y += 25) {
        +   *       // Draw the p5.Framebuffer object to the canvas.
        +   *       image(myBuffer, x, y);
        +   *     }
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createFramebuffer = function (options) {
        +    return new Framebuffer(this._renderer, options);
        +  };
         
        -/**
        - * Clears the depth buffer in WebGL mode.
        - *
        - * `clearDepth()` clears information about how far objects are from the camera
        - * in 3D space. This information is stored in an object called the
        - * *depth buffer*. Clearing the depth buffer ensures new objects aren't drawn
        - * behind old ones. Doing so can be useful for feedback effects in which the
        - * previous frame serves as the background for the current frame.
        - *
        - * The parameter, `depth`, is optional. If a number is passed, as in
        - * `clearDepth(0.5)`, it determines the range of objects to clear from the
        - * depth buffer. 0 doesn't clear any depth information, 0.5 clears depth
        - * information halfway between the near and far clipping planes, and 1 clears
        - * depth information all the way to the far clipping plane. By default,
        - * `depth` is 1.
        - *
        - * Note: `clearDepth()` can only be used in WebGL mode.
        - *
        - * @method clearDepth
        - * @param {Number} [depth] amount of the depth buffer to clear between 0
        - *                         (none) and 1 (far clipping plane). Defaults to 1.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let previous;
        - * let current;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the p5.Framebuffer objects.
        - *   previous = createFramebuffer({ format: FLOAT });
        - *   current = createFramebuffer({ format: FLOAT });
        - *
        - *   describe(
        - *     'A multicolor box drifts from side to side on a white background. It leaves a trail that fades over time.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   // Swap the previous p5.Framebuffer and the
        - *   // current one so it can be used as a texture.
        - *   [previous, current] = [current, previous];
        - *
        - *   // Start drawing to the current p5.Framebuffer.
        - *   current.begin();
        - *
        - *   // Paint the background.
        - *   background(255);
        - *
        - *   // Draw the previous p5.Framebuffer.
        - *   // Clear the depth buffer so the previous
        - *   // frame doesn't block the current one.
        - *   push();
        - *   tint(255, 250);
        - *   image(previous, -50, -50);
        - *   clearDepth();
        - *   pop();
        - *
        - *   // Draw the box on top of the previous frame.
        - *   push();
        - *   let x = 25 * sin(frameCount * 0.01);
        - *   let y = 25 * sin(frameCount * 0.02);
        - *   translate(x, y, 0);
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   normalMaterial();
        - *   box(12);
        - *   pop();
        - *
        - *   // Stop drawing to the current p5.Framebuffer.
        - *   current.end();
        - *
        - *   // Display the current p5.Framebuffer.
        - *   image(current, -50, -50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.clearDepth = function(depth) {
        -  this._assert3d('clearDepth');
        -  this._renderer.clearDepth(depth);
        -};
        +  /**
        +   * Clears the depth buffer in WebGL mode.
        +   *
        +   * `clearDepth()` clears information about how far objects are from the camera
        +   * in 3D space. This information is stored in an object called the
        +   * *depth buffer*. Clearing the depth buffer ensures new objects aren't drawn
        +   * behind old ones.Creates a <a href="#/p5.Graphics">p5.Gra Doing so can be useful for feedback effects in which the
        +   * previous frame serves as the background for the current frame.
        +   *
        +   * The parameter, `depth`, is optional. If a number is passed, as in
        +   * `clearDepth(0.5)`, it determines the range of objects to clear from the
        +   * depth buffer. 0 doesn't clear any depth information, 0.5 clears depth
        +   * information halfway between the near and far clipping planes, and 1 clears
        +   * depth information all the way to the far clipping plane. By default,
        +   * `depth` is 1.
        +   *
        +   * Note: `clearDepth()` can only be used in WebGL mode.
        +   *
        +   * @method clearDepth
        +   * @param {Number} [depth] amount of the depth buffer to clear between 0
        +   *                         (none) and 1 (far clipping plane). Defaults to 1.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let previous;
        +   * let current;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the p5.Framebuffer objects.
        +   *   previous = createFramebuffer({ format: FLOAT });
        +   *   current = createFramebuffer({ format: FLOAT });
        +   *
        +   *   describe(
        +   *     'A multicolor box drifts from side to side on a white background. It leaves a trail that fades over time.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   // Swap the previous p5.Framebuffer and the
        +   *   // current one so it can be used as a texture.
        +   *   [previous, current] = [current, previous];
        +   *
        +   *   // Start drawing to the current p5.Framebuffer.
        +   *   current.begin();
        +   *
        +   *   // Paint the background.
        +   *   background(255);
        +   *
        +   *   // Draw the previous p5.Framebuffer.
        +   *   // Clear the depth buffer so the previous
        +   *   // frame doesn't block the current one.
        +   *   push();
        +   *   tint(255, 250);
        +   *   image(previous, -50, -50);
        +   *   clearDepth();
        +   *   pop();
        +   *
        +   *   // Draw the box on top of the previous frame.
        +   *   push();
        +   *   let x = 25 * sin(frameCount * 0.01);
        +   *   let y = 25 * sin(frameCount * 0.02);
        +   *   translate(x, y, 0);
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *   normalMaterial();
        +   *   box(12);
        +   *   pop();
        +   *
        +   *   // Stop drawing to the current p5.Framebuffer.
        +   *   current.end();
        +   *
        +   *   // Display the current p5.Framebuffer.
        +   *   image(current, -50, -50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.clearDepth = function (depth) {
        +    this._assert3d('clearDepth');
        +    this._renderer.clearDepth(depth);
        +  };
         
        -/**
        - * Sets the way colors blend when added to the canvas.
        - *
        - * By default, drawing with a solid color paints over the current pixel values
        - * on the canvas. `blendMode()` offers many options for blending colors.
        - *
        - * Shapes, images, and text can be used as sources for drawing to the canvas.
        - * A source pixel changes the color of the canvas pixel where it's drawn. The
        - * final color results from blending the source pixel's color with the canvas
        - * pixel's color. RGB color values from the source and canvas pixels are
        - * compared, added, subtracted, multiplied, and divided to create different
        - * effects. Red values with red values, greens with greens, and blues with
        - * blues.
        - *
        - * The parameter, `mode`, sets the blend mode. For example, calling
        - * `blendMode(ADD)` sets the blend mode to `ADD`. The following blend modes
        - * are available in both 2D and WebGL mode:
        - *
        - * - `BLEND`: color values from the source overwrite the canvas. This is the default mode.
        - * - `ADD`: color values from the source are added to values from the canvas.
        - * - `DARKEST`: keeps the darkest color value.
        - * - `LIGHTEST`: keeps the lightest color value.
        - * - `EXCLUSION`: similar to `DIFFERENCE` but with less contrast.
        - * - `MULTIPLY`: color values from the source are multiplied with values from the canvas. The result is always darker.
        - * - `SCREEN`: all color values are inverted, then multiplied, then inverted again. The result is always lighter. (Opposite of `MULTIPLY`)
        - * - `REPLACE`: the last source drawn completely replaces the rest of the canvas.
        - * - `REMOVE`: overlapping pixels are removed by making them completely transparent.
        - *
        - * The following blend modes are only available in 2D mode:
        - *
        - * - `DIFFERENCE`: color values from the source are subtracted from the values from the canvas. If the difference is a negative number, it's made positive.
        - * - `OVERLAY`: combines `MULTIPLY` and `SCREEN`. Dark values in the canvas get darker and light values get lighter.
        - * - `HARD_LIGHT`: combines `MULTIPLY` and `SCREEN`. Dark values in the source get darker and light values get lighter.
        - * - `SOFT_LIGHT`: a softer version of `HARD_LIGHT`.
        - * - `DODGE`: lightens light tones and increases contrast. Divides the canvas color values by the inverted color values from the source.
        - * - `BURN`: darkens dark tones and increases contrast. Divides the source color values by the inverted color values from the canvas, then inverts the result.
        - *
        - * The following blend modes are only available in WebGL mode:
        - *
        - * - `SUBTRACT`: RGB values from the source are subtracted from the values from the canvas. If the difference is a negative number, it's made positive. Alpha (transparency) values from the source and canvas are added.
        - *
        - * @method blendMode
        - * @param  {Constant} mode blend mode to set.
        - *                either BLEND, DARKEST, LIGHTEST, DIFFERENCE, MULTIPLY,
        - *                EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,
        - *                SOFT_LIGHT, DODGE, BURN, ADD, REMOVE or SUBTRACT
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Use the default blend mode.
        - *   blendMode(BLEND);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('A Sky Blue line and a Deep Rose line form an X on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(HARD_LIGHT);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('An ocean blue line and a hot pink line form an X on a gray background. The area where they overlap is Magenta purple.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(ADD);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('An icy blue line and a light lavender line form an X on a gray background. The area where they overlap is white.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(DARKEST);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('A steel blue line and a cranberry line form an X on a gray background. The area where they overlap is deep purple.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(BURN);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('A cobalt blue line and a burgundy line form an X on a gray background. The area where they overlap is black.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(LIGHTEST);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('A pale lavender line and a soft beige line form an X on a gray background. The area where they overlap is pale lilac.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(EXCLUSION);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('An earthy brown line and a muted sage line form an X on a gray background. The area where they overlap is sage green.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(MULTIPLY);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('A slate blue line and a plum line form an X on a gray background. The area where they overlap is dark Indigo.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(SCREEN);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('A baby blue line and a peach pink line form an X on a gray background. The area where they overlap is misty lilac.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(REPLACE);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('A diagonal deep rose line.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(REMOVE);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('The silhouette of an X is missing from a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(DIFFERENCE);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('A light burgundy line and a forest green line form an X on a gray background. The area where they overlap is dark cocoa.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(OVERLAY);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('A cornflower blue line and a light rose line form an X on a gray background. The area where they overlap is violet.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(SOFT_LIGHT);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('A pale sky line and a rose blush line form an X on a gray background. The area where they overlap is lavender.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Set the blend mode.
        - *   blendMode(DODGE);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the first line.
        - *   stroke('#1a85ff');
        - *   line(25, 25, 75, 75);
        - *
        - *   // Draw the second line.
        - *   stroke('#d41159');
        - *   line(75, 25, 25, 75);
        - *
        - *   describe('An aqua blue line and a light pink line form an X on a gray background. The area where they overlap is white.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Create a canvas with WEBGL mode.
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Set the background color.
        - *   background(180);
        - *
        - *   // Set the blend mode to SUBTRACT.
        - *   blendMode(SUBTRACT);
        - *
        - *   // Style the lines.
        - *   strokeWeight(30);
        - *
        - *   // Draw the blue line.
        - *   stroke('#1a85ff');
        - *   line(-25, -25, 25, 25);
        - *
        - *   // Draw the red line.
        - *   stroke('#d41159');
        - *   line(25, -25, -25, 25);
        - *
        - *   describe('A burnt orange and a sea green line form an X on a gray background. The area where they overlap is forest green.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.blendMode = function(mode) {
        -  p5._validateParameters('blendMode', arguments);
        -  if (mode === constants.NORMAL) {
        -    // Warning added 3/26/19, can be deleted in future (1.0 release?)
        -    console.warn(
        -      'NORMAL has been deprecated for use in blendMode. defaulting to BLEND instead.'
        -    );
        -    mode = constants.BLEND;
        -  }
        -  this._renderer.blendMode(mode);
        -};
        +  /**
        +   * A system variable that provides direct access to the sketch's
        +   * `&lt;canvas&gt;` element.
        +   *
        +   * The `&lt;canvas&gt;` element provides many specialized features that aren't
        +   * included in the p5.js library. The `drawingContext` system variable
        +   * provides access to these features by exposing the sketch's
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">CanvasRenderingContext2D</a>
        +   * object.
        +   *
        +   * @property drawingContext
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the circle using shadows.
        +   *   drawingContext.shadowOffsetX = 5;
        +   *   drawingContext.shadowOffsetY = -5;
        +   *   drawingContext.shadowBlur = 10;
        +   *   drawingContext.shadowColor = 'black';
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, 40);
        +   *
        +   *   describe("A white circle on a gray background. The circle's edges are shadowy.");
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background('skyblue');
        +   *
        +   *   // Style the circle using a color gradient.
        +   *   let myGradient = drawingContext.createRadialGradient(50, 50, 3, 50, 50, 40);
        +   *   myGradient.addColorStop(0, 'yellow');
        +   *   myGradient.addColorStop(0.6, 'orangered');
        +   *   myGradient.addColorStop(1, 'yellow');
        +   *   drawingContext.fillStyle = myGradient;
        +   *   drawingContext.strokeStyle = 'rgba(0, 0, 0, 0)';
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, 40);
        +   *
        +   *   describe('A fiery sun drawn on a light blue background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +}
         
        -/**
        - * A system variable that provides direct access to the sketch's
        - * `&lt;canvas&gt;` element.
        - *
        - * The `&lt;canvas&gt;` element provides many specialized features that aren't
        - * included in the p5.js library. The `drawingContext` system variable
        - * provides access to these features by exposing the sketch's
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">CanvasRenderingContext2D</a>
        - * object.
        - *
        - * @property drawingContext
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(180);
        - *
        - *   // Style the circle using shadows.
        - *   drawingContext.shadowOffsetX = 5;
        - *   drawingContext.shadowOffsetY = -5;
        - *   drawingContext.shadowBlur = 10;
        - *   drawingContext.shadowColor = 'black';
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, 40);
        - *
        - *   describe("A white circle on a gray background. The circle's edges are shadowy.");
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background('skyblue');
        - *
        - *   // Style the circle using a color gradient.
        - *   let myGradient = drawingContext.createRadialGradient(50, 50, 3, 50, 50, 40);
        - *   myGradient.addColorStop(0, 'yellow');
        - *   myGradient.addColorStop(0.6, 'orangered');
        - *   myGradient.addColorStop(1, 'yellow');
        - *   drawingContext.fillStyle = myGradient;
        - *   drawingContext.strokeStyle = 'rgba(0, 0, 0, 0)';
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, 40);
        - *
        - *   describe('A fiery sun drawn on a light blue background.');
        - * }
        - * </code>
        - * </div>
        - */
        +export default rendering;
        +export { renderers };
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  rendering(p5, p5.prototype);
        +}
        diff --git a/src/core/shape/2d_primitives.js b/src/core/shape/2d_primitives.js
        deleted file mode 100644
        index f916339514..0000000000
        --- a/src/core/shape/2d_primitives.js
        +++ /dev/null
        @@ -1,1449 +0,0 @@
        -/**
        - * @module Shape
        - * @submodule 2D Primitives
        - * @for p5
        - * @requires core
        - * @requires constants
        - */
        -
        -import p5 from '../main';
        -import * as constants from '../constants';
        -import canvas from '../helpers';
        -import '../friendly_errors/fes_core';
        -import '../friendly_errors/file_errors';
        -import '../friendly_errors/validate_params';
        -
        -/**
        - * This function does 3 things:
        - *
        - *   1. Bounds the desired start/stop angles for an arc (in radians) so that:
        - *
        - *          0 <= start < TWO_PI ;    start <= stop < start + TWO_PI
        - *
        - *      This means that the arc rendering functions don't have to be concerned
        - *      with what happens if stop is smaller than start, or if the arc 'goes
        - *      round more than once', etc.: they can just start at start and increase
        - *      until stop and the correct arc will be drawn.
        - *
        - *   2. Optionally adjusts the angles within each quadrant to counter the naive
        - *      scaling of the underlying ellipse up from the unit circle.  Without
        - *      this, the angles become arbitrary when width != height: 45 degrees
        - *      might be drawn at 5 degrees on a 'wide' ellipse, or at 85 degrees on
        - *      a 'tall' ellipse.
        - *
        - *   3. Flags up when start and stop correspond to the same place on the
        - *      underlying ellipse.  This is useful if you want to do something special
        - *      there (like rendering a whole ellipse instead).
        - */
        -p5.prototype._normalizeArcAngles = (
        -  start,
        -  stop,
        -  width,
        -  height,
        -  correctForScaling
        -) => {
        -  const epsilon = 0.00001; // Smallest visible angle on displays up to 4K.
        -  let separation;
        -
        -  // The order of the steps is important here: each one builds upon the
        -  // adjustments made in the steps that precede it.
        -
        -  // Constrain both start and stop to [0,TWO_PI).
        -  start = start - constants.TWO_PI * Math.floor(start / constants.TWO_PI);
        -  stop = stop - constants.TWO_PI * Math.floor(stop / constants.TWO_PI);
        -
        -  // Get the angular separation between the requested start and stop points.
        -  //
        -  // Technically this separation only matches what gets drawn if
        -  // correctForScaling is enabled.  We could add a more complicated calculation
        -  // for when the scaling is uncorrected (in which case the drawn points could
        -  // end up pushed together or pulled apart quite dramatically relative to what
        -  // was requested), but it would make things more opaque for little practical
        -  // benefit.
        -  //
        -  // (If you do disable correctForScaling and find that correspondToSamePoint
        -  // is set too aggressively, the easiest thing to do is probably to just make
        -  // epsilon smaller...)
        -  separation = Math.min(
        -    Math.abs(start - stop),
        -    constants.TWO_PI - Math.abs(start - stop)
        -  );
        -
        -  // Optionally adjust the angles to counter linear scaling.
        -  if (correctForScaling) {
        -    if (start <= constants.HALF_PI) {
        -      start = Math.atan(width / height * Math.tan(start));
        -    } else if (start > constants.HALF_PI && start <= 3 * constants.HALF_PI) {
        -      start = Math.atan(width / height * Math.tan(start)) + constants.PI;
        -    } else {
        -      start = Math.atan(width / height * Math.tan(start)) + constants.TWO_PI;
        -    }
        -    if (stop <= constants.HALF_PI) {
        -      stop = Math.atan(width / height * Math.tan(stop));
        -    } else if (stop > constants.HALF_PI && stop <= 3 * constants.HALF_PI) {
        -      stop = Math.atan(width / height * Math.tan(stop)) + constants.PI;
        -    } else {
        -      stop = Math.atan(width / height * Math.tan(stop)) + constants.TWO_PI;
        -    }
        -  }
        -
        -  // Ensure that start <= stop < start + TWO_PI.
        -  if (start > stop) {
        -    stop += constants.TWO_PI;
        -  }
        -
        -  return {
        -    start,
        -    stop,
        -    correspondToSamePoint: separation < epsilon
        -  };
        -};
        -
        -/**
        - * Draws an arc.
        - *
        - * An arc is a section of an ellipse defined by the `x`, `y`, `w`, and
        - * `h` parameters. `x` and `y` set the location of the arc's center. `w` and
        - * `h` set the arc's width and height. See
        - * <a href="#/p5/ellipse">ellipse()</a> and
        - * <a href="#/p5/ellipseMode">ellipseMode()</a> for more details.
        - *
        - * The fifth and sixth parameters, `start` and `stop`, set the angles
        - * between which to draw the arc. Arcs are always drawn clockwise from
        - * `start` to `stop`. Angles are always given in radians.
        - *
        - * The seventh parameter, `mode`, is optional. It determines the arc's fill
        - * style. The fill modes are a semi-circle (`OPEN`), a closed semi-circle
        - * (`CHORD`), or a closed pie segment (`PIE`).
        - *
        - * The eighth parameter, `detail`, is also optional. It determines how many
        - * vertices are used to draw the arc in WebGL mode. The default value is 25.
        - *
        - * @method arc
        - * @param  {Number} x      x-coordinate of the arc's ellipse.
        - * @param  {Number} y      y-coordinate of the arc's ellipse.
        - * @param  {Number} w      width of the arc's ellipse by default.
        - * @param  {Number} h      height of the arc's ellipse by default.
        - * @param  {Number} start  angle to start the arc, specified in radians.
        - * @param  {Number} stop   angle to stop the arc, specified in radians.
        - * @param  {Constant} [mode] optional parameter to determine the way of drawing
        - *                         the arc. either CHORD, PIE, or OPEN.
        - * @param  {Integer} [detail] optional parameter for WebGL mode only. This is to
        - *                         specify the number of vertices that makes up the
        - *                         perimeter of the arc. Default value is 25. Won't
        - *                         draw a stroke for a detail of more than 50.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   arc(50, 50, 80, 80, 0, PI + HALF_PI);
        - *
        - *   describe('A white circle on a gray canvas. The top-right quarter of the circle is missing.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   arc(50, 50, 80, 40, 0, PI + HALF_PI);
        - *
        - *   describe('A white ellipse on a gray canvas. The top-right quarter of the ellipse is missing.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Bottom-right.
        - *   arc(50, 55, 50, 50, 0, HALF_PI);
        - *
        - *   noFill();
        - *
        - *   // Bottom-left.
        - *   arc(50, 55, 60, 60, HALF_PI, PI);
        - *
        - *   // Top-left.
        - *   arc(50, 55, 70, 70, PI, PI + QUARTER_PI);
        - *
        - *   // Top-right.
        - *   arc(50, 55, 80, 80, PI + QUARTER_PI, TWO_PI);
        - *
        - *   describe(
        - *     'A shattered outline of an circle with a quarter of a white circle at the bottom-right.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Default fill mode.
        - *   arc(50, 50, 80, 80, 0, PI + QUARTER_PI);
        - *
        - *   describe('A white circle with the top-right third missing. The bottom is outlined in black.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // OPEN fill mode.
        - *   arc(50, 50, 80, 80, 0, PI + QUARTER_PI, OPEN);
        - *
        - *   describe(
        - *     'A white circle missing a section from the top-right. The bottom is outlined in black.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // CHORD fill mode.
        - *   arc(50, 50, 80, 80, 0, PI + QUARTER_PI, CHORD);
        - *
        - *   describe('A white circle with a black outline missing a section from the top-right.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // PIE fill mode.
        - *   arc(50, 50, 80, 80, 0, PI + QUARTER_PI, PIE);
        - *
        - *   describe('A white circle with a black outline. The top-right third is missing.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   // PIE fill mode.
        - *   arc(0, 0, 80, 80, 0, PI + QUARTER_PI, PIE);
        - *
        - *   describe('A white circle with a black outline. The top-right third is missing.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   // PIE fill mode with 5 vertices.
        - *   arc(0, 0, 80, 80, 0, PI + QUARTER_PI, PIE, 5);
        - *
        - *   describe('A white circle with a black outline. The top-right third is missing.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A yellow circle on a black background. The circle opens and closes its mouth.');
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Style the arc.
        - *   noStroke();
        - *   fill(255, 255, 0);
        - *
        - *   // Update start and stop angles.
        - *   let biteSize = PI / 16;
        - *   let startAngle = biteSize * sin(frameCount * 0.1) + biteSize;
        - *   let endAngle = TWO_PI - startAngle;
        - *
        - *   // Draw the arc.
        - *   arc(50, 50, 80, 80, startAngle, endAngle, PIE);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.arc = function(x, y, w, h, start, stop, mode, detail) {
        -  p5._validateParameters('arc', arguments);
        -
        -  // if the current stroke and fill settings wouldn't result in something
        -  // visible, exit immediately
        -  if (!this._renderer._doStroke && !this._renderer._doFill) {
        -    return this;
        -  }
        -
        -  if (start === stop) {
        -    return this;
        -  }
        -
        -  start = this._toRadians(start);
        -  stop = this._toRadians(stop);
        -
        -  const vals = canvas.modeAdjust(x, y, w, h, this._renderer._ellipseMode);
        -  const angles = this._normalizeArcAngles(start, stop, vals.w, vals.h, true);
        -
        -  if (angles.correspondToSamePoint) {
        -    // If the arc starts and ends at (near enough) the same place, we choose to
        -    // draw an ellipse instead.  This is preferable to faking an ellipse (by
        -    // making stop ever-so-slightly less than start + TWO_PI) because the ends
        -    // join up to each other rather than at a vertex at the centre (leaving
        -    // an unwanted spike in the stroke/fill).
        -    this._renderer.ellipse([vals.x, vals.y, vals.w, vals.h, detail]);
        -  } else {
        -    this._renderer.arc(
        -      vals.x,
        -      vals.y,
        -      vals.w,
        -      vals.h,
        -      angles.start, // [0, TWO_PI)
        -      angles.stop, // [start, start + TWO_PI)
        -      mode,
        -      detail
        -    );
        -
        -    //accessible Outputs
        -    if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        -      this._accsOutput('arc', [
        -        vals.x,
        -        vals.y,
        -        vals.w,
        -        vals.h,
        -        angles.start,
        -        angles.stop,
        -        mode
        -      ]);
        -    }
        -  }
        -
        -  return this;
        -};
        -
        -/**
        - * Draws an ellipse (oval).
        - *
        - * An ellipse is a round shape defined by the `x`, `y`, `w`, and
        - * `h` parameters. `x` and `y` set the location of its center. `w` and
        - * `h` set its width and height. See
        - * <a href="#/p5/ellipseMode">ellipseMode()</a> for other ways to set
        - * its position.
        - *
        - * If no height is set, the value of width is used for both the width and
        - * height. If a negative height or width is specified, the absolute value is
        - * taken.
        - *
        - * The fifth parameter, `detail`, is also optional. It determines how many
        - * vertices are used to draw the ellipse in WebGL mode. The default value is
        - * 25.
        - *
        - * @method ellipse
        - * @param  {Number} x x-coordinate of the center of the ellipse.
        - * @param  {Number} y y-coordinate of the center of the ellipse.
        - * @param  {Number} w width of the ellipse.
        - * @param  {Number} [h] height of the ellipse.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   ellipse(50, 50, 80, 80);
        - *
        - *   describe('A white circle on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   ellipse(50, 50, 80);
        - *
        - *   describe('A white circle on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   ellipse(50, 50, 80, 40);
        - *
        - *   describe('A white ellipse on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   ellipse(0, 0, 80, 40);
        - *
        - *   describe('A white ellipse on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   // Use 6 vertices.
        - *   ellipse(0, 0, 80, 40, 6);
        - *
        - *   describe('A white hexagon on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method ellipse
        - * @param  {Number} x
        - * @param  {Number} y
        - * @param  {Number} w
        - * @param  {Number} h
        - * @param  {Integer} [detail] optional parameter for WebGL mode only. This is to
        - *                         specify the number of vertices that makes up the
        - *                         perimeter of the ellipse. Default value is 25. Won't
        - *                         draw a stroke for a detail of more than 50.
        - */
        -p5.prototype.ellipse = function(x, y, w, h, detailX) {
        -  p5._validateParameters('ellipse', arguments);
        -  return this._renderEllipse(...arguments);
        -};
        -
        -/**
        - * Draws a circle.
        - *
        - * A circle is a round shape defined by the `x`, `y`, and `d` parameters.
        - * `x` and `y` set the location of its center. `d` sets its width and height (diameter).
        - * Every point on the circle's edge is the same distance, `0.5 * d`, from its center.
        - * `0.5 * d` (half the diameter) is the circle's radius.
        - * See <a href="#/p5/ellipseMode">ellipseMode()</a> for other ways to set its position.
        - *
        - * @method circle
        - * @param  {Number} x  x-coordinate of the center of the circle.
        - * @param  {Number} y  y-coordinate of the center of the circle.
        - * @param  {Number} d  diameter of the circle.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   circle(50, 50, 25);
        - *
        - *   describe('A white circle with black outline in the middle of a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   circle(0, 0, 25);
        - *
        - *   describe('A white circle with black outline in the middle of a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.circle = function(...args) {
        -  p5._validateParameters('circle', args);
        -  const argss = args.slice( 0, 2);
        -  argss.push(args[2], args[2]);
        -  return this._renderEllipse(...argss);
        -};
        -
        -// internal method for drawing ellipses (without parameter validation)
        -p5.prototype._renderEllipse = function(x, y, w, h, detailX) {
        -  // if the current stroke and fill settings wouldn't result in something
        -  // visible, exit immediately
        -  if (!this._renderer._doStroke && !this._renderer._doFill) {
        -    return this;
        -  }
        -
        -  // Duplicate 3rd argument if only 3 given.
        -  if (typeof h === 'undefined') {
        -    h = w;
        -  }
        -
        -  const vals = canvas.modeAdjust(x, y, w, h, this._renderer._ellipseMode);
        -  this._renderer.ellipse([vals.x, vals.y, vals.w, vals.h, detailX]);
        -
        -  //accessible Outputs
        -  if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        -    this._accsOutput('ellipse', [vals.x, vals.y, vals.w, vals.h]);
        -  }
        -
        -  return this;
        -};
        -
        -/**
        - * Draws a straight line between two points.
        - *
        - * A line's default width is one pixel. The version of `line()` with four
        - * parameters draws the line in 2D. To color a line, use the
        - * <a href="#/p5/stroke">stroke()</a> function. To change its width, use the
        - * <a href="#/p5/strokeWeight">strokeWeight()</a> function. A line
        - * can't be filled, so the <a href="#/p5/fill">fill()</a> function won't
        - * affect the line's color.
        - *
        - * The version of `line()` with six parameters allows the line to be drawn in
        - * 3D space. Doing so requires adding the `WEBGL` argument to
        - * <a href="#/p5/createCanvas">createCanvas()</a>.
        - *
        - * @method line
        - * @param  {Number} x1 the x-coordinate of the first point.
        - * @param  {Number} y1 the y-coordinate of the first point.
        - * @param  {Number} x2 the x-coordinate of the second point.
        - * @param  {Number} y2 the y-coordinate of the second point.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   line(30, 20, 85, 75);
        - *
        - *   describe(
        - *     'A black line on a gray canvas running from top-center to bottom-right.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the line.
        - *   stroke('magenta');
        - *   strokeWeight(5);
        - *
        - *   line(30, 20, 85, 75);
        - *
        - *   describe(
        - *     'A thick, magenta line on a gray canvas running from top-center to bottom-right.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top.
        - *   line(30, 20, 85, 20);
        - *
        - *   // Right.
        - *   stroke(126);
        - *   line(85, 20, 85, 75);
        - *
        - *   // Bottom.
        - *   stroke(255);
        - *   line(85, 75, 30, 75);
        - *
        - *   describe(
        - *     'Three lines drawn in grayscale on a gray canvas. They form the top, right, and bottom sides of a square.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   line(-20, -30, 35, 25);
        - *
        - *   describe(
        - *     'A black line on a gray canvas running from top-center to bottom-right.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A black line connecting two spheres. The scene spins slowly.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Draw a line.
        - *   line(0, 0, 0, 30, 20, -10);
        - *
        - *   // Draw the center sphere.
        - *   sphere(10);
        - *
        - *   // Translate to the second point.
        - *   translate(30, 20, -10);
        - *
        - *   // Draw the bottom-right sphere.
        - *   sphere(10);
        - * }
        - * </code>
        - * </div>
        - *
        - */
        -
        -/**
        - * @method line
        - * @param  {Number} x1
        - * @param  {Number} y1
        - * @param  {Number} z1 the z-coordinate of the first point.
        - * @param  {Number} x2
        - * @param  {Number} y2
        - * @param  {Number} z2 the z-coordinate of the second point.
        - * @chainable
        - */
        -p5.prototype.line = function(...args) {
        -  p5._validateParameters('line', args);
        -
        -  if (this._renderer._doStroke) {
        -    this._renderer.line(...args);
        -  }
        -
        -  //accessible Outputs
        -  if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        -    this._accsOutput('line', args);
        -  }
        -
        -  return this;
        -};
        -
        -/**
        - * Draws a single point in space.
        - *
        - * A point's default width is one pixel. To color a point, use the
        - * <a href="#/p5/stroke">stroke()</a> function. To change its width, use the
        - * <a href="#/p5/strokeWeight">strokeWeight()</a> function. A point
        - * can't be filled, so the <a href="#/p5/fill">fill()</a> function won't
        - * affect the point's color.
        - *
        - * The version of `point()` with two parameters allows the point's location to
        - * be set with its x- and y-coordinates, as in `point(10, 20)`.
        - *
        - * The version of `point()` with three parameters allows the point to be drawn
        - * in 3D space with x-, y-, and z-coordinates, as in `point(10, 20, 30)`.
        - * Doing so requires adding the `WEBGL` argument to
        - * <a href="#/p5/createCanvas">createCanvas()</a>.
        - *
        - * The version of `point()` with one parameter allows the point's location to
        - * be set with a <a href="#/p5/p5.Vector">p5.Vector</a> object.
        - *
        - * @method point
        - * @param  {Number} x the x-coordinate.
        - * @param  {Number} y the y-coordinate.
        - * @param  {Number} [z] the z-coordinate (for WebGL mode).
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top-left.
        - *   point(30, 20);
        - *
        - *   // Top-right.
        - *   point(85, 20);
        - *
        - *   // Bottom-right.
        - *   point(85, 75);
        - *
        - *   // Bottom-left.
        - *   point(30, 75);
        - *
        - *   describe(
        - *     'Four small, black points drawn on a gray canvas. The points form the corners of a square.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top-left.
        - *   point(30, 20);
        - *
        - *   // Top-right.
        - *   point(70, 20);
        - *
        - *   // Style the next points.
        - *   stroke('purple');
        - *   strokeWeight(10);
        - *
        - *   // Bottom-right.
        - *   point(70, 80);
        - *
        - *   // Bottom-left.
        - *   point(30, 80);
        - *
        - *   describe(
        - *     'Four points drawn on a gray canvas. Two are black and two are purple. The points form the corners of a square.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top-left.
        - *   let a = createVector(30, 20);
        - *   point(a);
        - *
        - *   // Top-right.
        - *   let b = createVector(70, 20);
        - *   point(b);
        - *
        - *   // Bottom-right.
        - *   let c = createVector(70, 80);
        - *   point(c);
        - *
        - *   // Bottom-left.
        - *   let d = createVector(30, 80);
        - *   point(d);
        - *
        - *   describe(
        - *     'Four small, black points drawn on a gray canvas. The points form the corners of a square.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('Two purple points drawn on a gray canvas.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the points.
        - *   stroke('purple');
        - *   strokeWeight(10);
        - *
        - *   // Top-left.
        - *   point(-20, -30);
        - *
        - *   // Bottom-right.
        - *   point(20, 30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('Two purple points drawn on a gray canvas. The scene spins slowly.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Style the points.
        - *   stroke('purple');
        - *   strokeWeight(10);
        - *
        - *   // Top-left.
        - *   point(-20, -30, 0);
        - *
        - *   // Bottom-right.
        - *   point(20, 30, -50);
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method point
        - * @param {p5.Vector} coordinateVector the coordinate vector.
        - * @chainable
        - */
        -p5.prototype.point = function(...args) {
        -  p5._validateParameters('point', args);
        -
        -  if (this._renderer._doStroke) {
        -    if (args.length === 1 && args[0] instanceof p5.Vector) {
        -      this._renderer.point.call(
        -        this._renderer,
        -        args[0].x,
        -        args[0].y,
        -        args[0].z
        -      );
        -    } else {
        -      this._renderer.point(...args);
        -      //accessible Outputs
        -      if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        -        this._accsOutput('point', args);
        -      }
        -    }
        -  }
        -
        -  return this;
        -};
        -
        -/**
        - * Draws a quadrilateral (four-sided shape).
        - *
        - * Quadrilaterals include rectangles, squares, rhombuses, and trapezoids. The
        - * first pair of parameters `(x1, y1)` sets the quad's first point. The next
        - * three pairs of parameters set the coordinates for its next three points
        - * `(x2, y2)`, `(x3, y3)`, and `(x4, y4)`. Points should be added in either
        - * clockwise or counter-clockwise order.
        - *
        - * The version of `quad()` with twelve parameters allows the quad to be drawn
        - * in 3D space. Doing so requires adding the `WEBGL` argument to
        - * <a href="#/p5/createCanvas">createCanvas()</a>.
        - *
        - * The thirteenth and fourteenth parameters are optional. In WebGL mode, they
        - * set the number of segments used to draw the quadrilateral in the x- and
        - * y-directions. They're both 2 by default.
        - *
        - * @method quad
        - * @param {Number} x1 the x-coordinate of the first point.
        - * @param {Number} y1 the y-coordinate of the first point.
        - * @param {Number} x2 the x-coordinate of the second point.
        - * @param {Number} y2 the y-coordinate of the second point.
        - * @param {Number} x3 the x-coordinate of the third point.
        - * @param {Number} y3 the y-coordinate of the third point.
        - * @param {Number} x4 the x-coordinate of the fourth point.
        - * @param {Number} y4 the y-coordinate of the fourth point.
        - * @param {Integer} [detailX] number of segments in the x-direction.
        - * @param {Integer} [detailY] number of segments in the y-direction.
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   quad(20, 20, 80, 20, 80, 80, 20, 80);
        - *
        - *   describe('A white square with a black outline drawn on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   quad(20, 30, 80, 30, 80, 70, 20, 70);
        - *
        - *   describe('A white rectangle with a black outline drawn on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   quad(50, 62, 86, 50, 50, 38, 14, 50);
        - *
        - *   describe('A white rhombus with a black outline drawn on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   quad(20, 50, 80, 30, 80, 70, 20, 70);
        - *
        - *   describe('A white trapezoid with a black outline drawn on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   quad(-30, -30, 30, -30, 30, 30, -30, 30);
        - *
        - *   describe('A white square with a black outline drawn on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A wavy white surface spins around on gray canvas.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Draw the quad.
        - *   quad(-30, -30, 0, 30, -30, 0, 30, 30, 20, -30, 30, -20);
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method quad
        - * @param {Number} x1
        - * @param {Number} y1
        - * @param {Number} z1 the z-coordinate of the first point.
        - * @param {Number} x2
        - * @param {Number} y2
        - * @param {Number} z2 the z-coordinate of the second point.
        - * @param {Number} x3
        - * @param {Number} y3
        - * @param {Number} z3 the z-coordinate of the third point.
        - * @param {Number} x4
        - * @param {Number} y4
        - * @param {Number} z4 the z-coordinate of the fourth point.
        - * @param {Integer} [detailX]
        - * @param {Integer} [detailY]
        - * @chainable
        - */
        -p5.prototype.quad = function(...args) {
        -  p5._validateParameters('quad', args);
        -
        -  if (this._renderer._doStroke || this._renderer._doFill) {
        -    if (this._renderer.isP3D && args.length < 12) {
        -      // if 3D and we weren't passed 12 args, assume Z is 0
        -      this._renderer.quad.call(
        -        this._renderer,
        -        args[0], args[1], 0,
        -        args[2], args[3], 0,
        -        args[4], args[5], 0,
        -        args[6], args[7], 0,
        -        args[8], args[9]);
        -    } else {
        -      this._renderer.quad(...args);
        -      //accessibile outputs
        -      if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        -        this._accsOutput('quadrilateral', args);
        -      }
        -    }
        -  }
        -
        -  return this;
        -};
        -
        -/**
        - * Draws a rectangle.
        - *
        - * A rectangle is a four-sided shape defined by the `x`, `y`, `w`, and `h`
        - * parameters. `x` and `y` set the location of its top-left corner. `w` sets
        - * its width and `h` sets its height. Every angle in the rectangle measures
        - * 90˚. See <a href="#/p5/rectMode">rectMode()</a> for other ways to define
        - * rectangles.
        - *
        - * The version of `rect()` with five parameters creates a rounded rectangle. The
        - * fifth parameter sets the radius for all four corners.
        - *
        - * The version of `rect()` with eight parameters also creates a rounded
        - * rectangle. Each of the last four parameters set the radius of a corner. The
        - * radii start with the top-left corner and move clockwise around the
        - * rectangle. If any of these parameters are omitted, they are set to the
        - * value of the last radius that was set.
        - *
        - * @method rect
        - * @param  {Number} x  x-coordinate of the rectangle.
        - * @param  {Number} y  y-coordinate of the rectangle.
        - * @param  {Number} w  width of the rectangle.
        - * @param  {Number} [h]  height of the rectangle.
        - * @param  {Number} [tl] optional radius of top-left corner.
        - * @param  {Number} [tr] optional radius of top-right corner.
        - * @param  {Number} [br] optional radius of bottom-right corner.
        - * @param  {Number} [bl] optional radius of bottom-left corner.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   rect(30, 20, 55, 55);
        - *
        - *   describe('A white square with a black outline on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   rect(30, 20, 55, 40);
        - *
        - *   describe('A white rectangle with a black outline on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Give all corners a radius of 20.
        - *   rect(30, 20, 55, 50, 20);
        - *
        - *   describe('A white rectangle with a black outline and round edges on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Give each corner a unique radius.
        - *   rect(30, 20, 55, 50, 20, 15, 10, 5);
        - *
        - *   describe('A white rectangle with a black outline and round edges of different radii.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   rect(-20, -30, 55, 55);
        - *
        - *   describe('A white square with a black outline on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white square spins around on gray canvas.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Draw the rectangle.
        - *   rect(-20, -30, 55, 55);
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method rect
        - * @param  {Number} x
        - * @param  {Number} y
        - * @param  {Number} w
        - * @param  {Number} h
        - * @param  {Integer} [detailX] number of segments in the x-direction (for WebGL mode).
        - * @param  {Integer} [detailY] number of segments in the y-direction (for WebGL mode).
        - * @chainable
        - */
        -p5.prototype.rect = function(...args) {
        -  p5._validateParameters('rect', args);
        -  return this._renderRect(...args);
        -};
        -
        -/**
        - * Draws a square.
        - *
        - * A square is a four-sided shape defined by the `x`, `y`, and `s`
        - * parameters. `x` and `y` set the location of its top-left corner. `s` sets
        - * its width and height. Every angle in the square measures 90˚ and all its
        - * sides are the same length. See <a href="#/p5/rectMode">rectMode()</a> for
        - * other ways to define squares.
        - *
        - * The version of `square()` with four parameters creates a rounded square.
        - * The fourth parameter sets the radius for all four corners.
        - *
        - * The version of `square()` with seven parameters also creates a rounded
        - * square. Each of the last four parameters set the radius of a corner. The
        - * radii start with the top-left corner and move clockwise around the
        - * square. If any of these parameters are omitted, they are set to the
        - * value of the last radius that was set.
        - *
        - * @method square
        - * @param  {Number} x  x-coordinate of the square.
        - * @param  {Number} y  y-coordinate of the square.
        - * @param  {Number} s  side size of the square.
        - * @param  {Number} [tl] optional radius of top-left corner.
        - * @param  {Number} [tr] optional radius of top-right corner.
        - * @param  {Number} [br] optional radius of bottom-right corner.
        - * @param  {Number} [bl] optional radius of bottom-left corner.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   square(30, 20, 55);
        - *
        - *   describe('A white square with a black outline in on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Give all corners a radius of 20.
        - *   square(30, 20, 55, 20);
        - *
        - *   describe(
        - *     'A white square with a black outline and round edges on a gray canvas.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Give each corner a unique radius.
        - *   square(30, 20, 55, 20, 15, 10, 5);
        - *
        - *   describe('A white square with a black outline and round edges of different radii.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   square(-20, -30, 55);
        - *
        - *   describe('A white square with a black outline in on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white square spins around on gray canvas.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Draw the square.
        - *   square(-20, -30, 55);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.square = function(x, y, s, tl, tr, br, bl) {
        -  p5._validateParameters('square', arguments);
        -  // duplicate width for height in case of square
        -  return this._renderRect.call(this, x, y, s, s, tl, tr, br, bl);
        -};
        -
        -// internal method to have renderer draw a rectangle
        -p5.prototype._renderRect = function() {
        -  if (this._renderer._doStroke || this._renderer._doFill) {
        -    // duplicate width for height in case only 3 arguments is provided
        -    if (arguments.length === 3) {
        -      arguments[3] = arguments[2];
        -    }
        -    const vals = canvas.modeAdjust(
        -      arguments[0],
        -      arguments[1],
        -      arguments[2],
        -      arguments[3],
        -      this._renderer._rectMode
        -    );
        -
        -    // For the default rectMode (CORNER), restore a possible negative width/height
        -    // removed by modeAdjust(). This results in flipped/mirrored rendering,
        -    // which is especially noticable when using WEGBL rendering and texture().
        -    // Note that this behavior only applies to rect(), NOT to ellipse() and arc().
        -    if (this._renderer._rectMode === constants.CORNER) {
        -      vals.w = arguments[2];
        -      vals.h = arguments[3];
        -    }
        -
        -    const args = [vals.x, vals.y, vals.w, vals.h];
        -    // append the additional arguments (either cornder radii, or
        -    // segment details) to the argument list
        -    for (let i = 4; i < arguments.length; i++) {
        -      args[i] = arguments[i];
        -    }
        -    this._renderer.rect(args);
        -
        -    //accessible outputs
        -    if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        -      this._accsOutput('rectangle', [vals.x, vals.y, vals.w, vals.h]);
        -    }
        -  }
        -
        -  return this;
        -};
        -
        -/**
        - * Draws a triangle.
        - *
        - * A triangle is a three-sided shape defined by three points. The
        - * first two parameters specify the triangle's first point `(x1, y1)`. The
        - * middle two parameters specify its second point `(x2, y2)`. And the last two
        - * parameters specify its third point `(x3, y3)`.
        - *
        - * @method triangle
        - * @param  {Number} x1 x-coordinate of the first point.
        - * @param  {Number} y1 y-coordinate of the first point.
        - * @param  {Number} x2 x-coordinate of the second point.
        - * @param  {Number} y2 y-coordinate of the second point.
        - * @param  {Number} x3 x-coordinate of the third point.
        - * @param  {Number} y3 y-coordinate of the third point.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   triangle(30, 75, 58, 20, 86, 75);
        - *
        - *   describe('A white triangle with a black outline on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   triangle(-20, 25, 8, -30, 36, 25);
        - *
        - *   describe('A white triangle with a black outline on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white triangle spins around on a gray canvas.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Draw the triangle.
        - *   triangle(-20, 25, 8, -30, 36, 25);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.triangle = function(...args) {
        -  p5._validateParameters('triangle', args);
        -
        -  if (this._renderer._doStroke || this._renderer._doFill) {
        -    this._renderer.triangle(args);
        -  }
        -
        -  //accessible outputs
        -  if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        -    this._accsOutput('triangle', args);
        -  }
        -
        -  return this;
        -};
        -
        -export default p5;
        diff --git a/src/core/shape/attributes.js b/src/core/shape/attributes.js
        deleted file mode 100644
        index b88ab13afd..0000000000
        --- a/src/core/shape/attributes.js
        +++ /dev/null
        @@ -1,602 +0,0 @@
        -/**
        - * @module Shape
        - * @submodule Attributes
        - * @for p5
        - * @requires core
        - * @requires constants
        - */
        -
        -import p5 from '../main';
        -import * as constants from '../constants';
        -
        -/**
        - * Changes where ellipses, circles, and arcs are drawn.
        - *
        - * By default, the first two parameters of
        - * <a href="#/p5/ellipse">ellipse()</a>, <a href="#/p5/circle">circle()</a>,
        - * and <a href="#/p5/arc">arc()</a>
        - * are the x- and y-coordinates of the shape's center. The next parameters set
        - * the shape's width and height. This is the same as calling
        - * `ellipseMode(CENTER)`.
        - *
        - * `ellipseMode(RADIUS)` also uses the first two parameters to set the x- and
        - * y-coordinates of the shape's center. The next parameters are half of the
        - * shapes's width and height. Calling `ellipse(0, 0, 10, 15)` draws a shape
        - * with a width of 20 and height of 30.
        - *
        - * `ellipseMode(CORNER)` uses the first two parameters as the upper-left
        - * corner of the shape. The next parameters are its width and height.
        - *
        - * `ellipseMode(CORNERS)` uses the first two parameters as the location of one
        - * corner of the ellipse's bounding box. The next parameters are the location
        - * of the opposite corner.
        - *
        - * The argument passed to `ellipseMode()` must be written in ALL CAPS because
        - * the constants `CENTER`, `RADIUS`, `CORNER`, and `CORNERS` are defined this
        - * way. JavaScript is a case-sensitive language.
        - *
        - * @method ellipseMode
        - * @param  {Constant} mode either CENTER, RADIUS, CORNER, or CORNERS
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // White ellipse.
        - *   ellipseMode(RADIUS);
        - *   fill(255);
        - *   ellipse(50, 50, 30, 30);
        - *
        - *   // Gray ellipse.
        - *   ellipseMode(CENTER);
        - *   fill(100);
        - *   ellipse(50, 50, 30, 30);
        - *
        - *   describe('A white circle with a gray circle at its center. Both circles have black outlines.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // White ellipse.
        - *   ellipseMode(CORNER);
        - *   fill(255);
        - *   ellipse(25, 25, 50, 50);
        - *
        - *   // Gray ellipse.
        - *   ellipseMode(CORNERS);
        - *   fill(100);
        - *   ellipse(25, 25, 50, 50);
        - *
        - *   describe('A white circle with a gray circle at its top-left corner. Both circles have black outlines.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.ellipseMode = function(m) {
        -  p5._validateParameters('ellipseMode', arguments);
        -  if (
        -    m === constants.CORNER ||
        -    m === constants.CORNERS ||
        -    m === constants.RADIUS ||
        -    m === constants.CENTER
        -  ) {
        -    this._renderer._ellipseMode = m;
        -  }
        -  return this;
        -};
        -
        -/**
        - * Draws certain features with jagged (aliased) edges.
        - *
        - * <a href="#/p5/smooth">smooth()</a> is active by default. In 2D mode,
        - * `noSmooth()` is helpful for scaling up images without blurring. The
        - * functions don't affect shapes or fonts.
        - *
        - * In WebGL mode, `noSmooth()` causes all shapes to be drawn with jagged
        - * (aliased) edges. The functions don't affect images or fonts.
        - *
        - * @method noSmooth
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * let heart;
        - *
        - * // Load a pixelated heart image from an image data string.
        - * function preload() {
        - *   heart = loadImage('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAAXNSR0IArs4c6QAAAEZJREFUGFd9jcsNACAIQ9tB2MeR3YdBMBBq8CIXPi2vBICIiOwkOedatllqWO6Y8yOWoyuNf1GZwgmf+RRG2YXr+xVFmA8HZ9Mx/KGPMtcAAAAASUVORK5CYII=');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Antialiased hearts.
        - *   image(heart, 10, 10);
        - *   image(heart, 20, 10, 16, 16);
        - *   image(heart, 40, 10, 32, 32);
        - *
        - *   // Aliased hearts.
        - *   noSmooth();
        - *   image(heart, 10, 60);
        - *   image(heart, 20, 60, 16, 16);
        - *   image(heart, 40, 60, 32, 32);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   circle(0, 0, 80);
        - *
        - *   describe('A white circle on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Disable smoothing.
        - *   noSmooth();
        - *
        - *   background(200);
        - *
        - *   circle(0, 0, 80);
        - *
        - *   describe('A pixelated white circle on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noSmooth = function() {
        -  if (!this._renderer.isP3D) {
        -    if ('imageSmoothingEnabled' in this.drawingContext) {
        -      this.drawingContext.imageSmoothingEnabled = false;
        -    }
        -  } else {
        -    this.setAttributes('antialias', false);
        -  }
        -  return this;
        -};
        -
        -/**
        - * Changes where rectangles and squares are drawn.
        - *
        - * By default, the first two parameters of
        - * <a href="#/p5/rect">rect()</a> and <a href="#/p5/square">square()</a>,
        - * are the x- and y-coordinates of the shape's upper left corner. The next parameters set
        - * the shape's width and height. This is the same as calling
        - * `rectMode(CORNER)`.
        - *
        - * `rectMode(CORNERS)` also uses the first two parameters as the location of
        - * one of the corners. The next parameters are the location of the opposite
        - * corner. This mode only works for <a href="#/p5/rect">rect()</a>.
        - *
        - * `rectMode(CENTER)` uses the first two parameters as the x- and
        - * y-coordinates of the shape's center. The next parameters are its width and
        - * height.
        - *
        - * `rectMode(RADIUS)` also uses the first two parameters as the x- and
        - * y-coordinates of the shape's center. The next parameters are
        - * half of the shape's width and height.
        - *
        - * The argument passed to `rectMode()` must be written in ALL CAPS because the
        - * constants `CENTER`, `RADIUS`, `CORNER`, and `CORNERS` are defined this way.
        - * JavaScript is a case-sensitive language.
        - *
        - * @method rectMode
        - * @param  {Constant} mode either CORNER, CORNERS, CENTER, or RADIUS
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   rectMode(CORNER);
        - *   fill(255);
        - *   rect(25, 25, 50, 50);
        - *
        - *   rectMode(CORNERS);
        - *   fill(100);
        - *   rect(25, 25, 50, 50);
        - *
        - *   describe('A small gray square drawn at the top-left corner of a white square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   rectMode(RADIUS);
        - *   fill(255);
        - *   rect(50, 50, 30, 30);
        - *
        - *   rectMode(CENTER);
        - *   fill(100);
        - *   rect(50, 50, 30, 30);
        - *
        - *   describe('A small gray square drawn at the center of a white square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   rectMode(CORNER);
        - *   fill(255);
        - *   square(25, 25, 50);
        - *
        - *   describe('A white square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   rectMode(RADIUS);
        - *   fill(255);
        - *   square(50, 50, 30);
        - *
        - *   rectMode(CENTER);
        - *   fill(100);
        - *   square(50, 50, 30);
        - *
        - *   describe('A small gray square drawn at the center of a white square.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.rectMode = function(m) {
        -  p5._validateParameters('rectMode', arguments);
        -  if (
        -    m === constants.CORNER ||
        -    m === constants.CORNERS ||
        -    m === constants.RADIUS ||
        -    m === constants.CENTER
        -  ) {
        -    this._renderer._rectMode = m;
        -  }
        -  return this;
        -};
        -
        -/**
        - * Draws certain features with smooth (antialiased) edges.
        - *
        - * `smooth()` is active by default. In 2D mode,
        - * <a href="#/p5/noSmooth">noSmooth()</a> is helpful for scaling up images
        - * without blurring. The functions don't affect shapes or fonts.
        - *
        - * In WebGL mode, <a href="#/p5/noSmooth">noSmooth()</a> causes all shapes to
        - * be drawn with jagged (aliased) edges. The functions don't affect images or
        - * fonts.
        - *
        - * @method smooth
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * let heart;
        - *
        - * // Load a pixelated heart image from an image data string.
        - * function preload() {
        - *   heart = loadImage('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAAXNSR0IArs4c6QAAAEZJREFUGFd9jcsNACAIQ9tB2MeR3YdBMBBq8CIXPi2vBICIiOwkOedatllqWO6Y8yOWoyuNf1GZwgmf+RRG2YXr+xVFmA8HZ9Mx/KGPMtcAAAAASUVORK5CYII=');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Antialiased hearts.
        - *   image(heart, 10, 10);
        - *   image(heart, 20, 10, 16, 16);
        - *   image(heart, 40, 10, 32, 32);
        - *
        - *   // Aliased hearts.
        - *   noSmooth();
        - *   image(heart, 10, 60);
        - *   image(heart, 20, 60, 16, 16);
        - *   image(heart, 40, 60, 32, 32);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   circle(0, 0, 80);
        - *
        - *   describe('A white circle on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Disable smoothing.
        - *   noSmooth();
        - *
        - *   background(200);
        - *
        - *   circle(0, 0, 80);
        - *
        - *   describe('A pixelated white circle on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.smooth = function() {
        -  if (!this._renderer.isP3D) {
        -    if ('imageSmoothingEnabled' in this.drawingContext) {
        -      this.drawingContext.imageSmoothingEnabled = true;
        -    }
        -  } else {
        -    this.setAttributes('antialias', true);
        -  }
        -  return this;
        -};
        -
        -/**
        - * Sets the style for rendering the ends of lines.
        - *
        - * The caps for line endings are either rounded (`ROUND`), squared
        - * (`SQUARE`), or extended (`PROJECT`). The default cap is `ROUND`.
        - *
        - * The argument passed to `strokeCap()` must be written in ALL CAPS because
        - * the constants `ROUND`, `SQUARE`, and `PROJECT` are defined this way.
        - * JavaScript is a case-sensitive language.
        - *
        - * @method strokeCap
        - * @param  {Constant} cap either ROUND, SQUARE, or PROJECT
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   strokeWeight(12);
        - *
        - *   // Top.
        - *   strokeCap(ROUND);
        - *   line(20, 30, 80, 30);
        - *
        - *   // Middle.
        - *   strokeCap(SQUARE);
        - *   line(20, 50, 80, 50);
        - *
        - *   // Bottom.
        - *   strokeCap(PROJECT);
        - *   line(20, 70, 80, 70);
        - *
        - *   describe(
        - *     'Three horizontal lines. The top line has rounded ends, the middle line has squared ends, and the bottom line has longer, squared ends.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.strokeCap = function(cap) {
        -  p5._validateParameters('strokeCap', arguments);
        -  if (
        -    cap === constants.ROUND ||
        -    cap === constants.SQUARE ||
        -    cap === constants.PROJECT
        -  ) {
        -    this._renderer.strokeCap(cap);
        -  }
        -  return this;
        -};
        -
        -/**
        - * Sets the style of the joints that connect line segments.
        - *
        - * Joints are either mitered (`MITER`), beveled (`BEVEL`), or rounded
        - * (`ROUND`). The default joint is `MITER` in 2D mode and `ROUND` in WebGL
        - * mode.
        - *
        - * The argument passed to `strokeJoin()` must be written in ALL CAPS because
        - * the constants `MITER`, `BEVEL`, and `ROUND` are defined this way.
        - * JavaScript is a case-sensitive language.
        - *
        - * @method strokeJoin
        - * @param  {Constant} join either MITER, BEVEL, or ROUND
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the line.
        - *   noFill();
        - *   strokeWeight(10);
        - *   strokeJoin(MITER);
        - *
        - *   // Draw the line.
        - *   beginShape();
        - *   vertex(35, 20);
        - *   vertex(65, 50);
        - *   vertex(35, 80);
        - *   endShape();
        - *
        - *   describe('A right-facing arrowhead shape with a pointed tip in center of canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the line.
        - *   noFill();
        - *   strokeWeight(10);
        - *   strokeJoin(BEVEL);
        - *
        - *   // Draw the line.
        - *   beginShape();
        - *   vertex(35, 20);
        - *   vertex(65, 50);
        - *   vertex(35, 80);
        - *   endShape();
        - *
        - *   describe('A right-facing arrowhead shape with a flat tip in center of canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the line.
        - *   noFill();
        - *   strokeWeight(10);
        - *   strokeJoin(ROUND);
        - *
        - *   // Draw the line.
        - *   beginShape();
        - *   vertex(35, 20);
        - *   vertex(65, 50);
        - *   vertex(35, 80);
        - *   endShape();
        - *
        - *   describe('A right-facing arrowhead shape with a rounded tip in center of canvas.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.strokeJoin = function(join) {
        -  p5._validateParameters('strokeJoin', arguments);
        -  if (
        -    join === constants.ROUND ||
        -    join === constants.BEVEL ||
        -    join === constants.MITER
        -  ) {
        -    this._renderer.strokeJoin(join);
        -  }
        -  return this;
        -};
        -
        -/**
        - * Sets the width of the stroke used for points, lines, and the outlines of
        - * shapes.
        - *
        - * Note: `strokeWeight()` is affected by transformations, especially calls to
        - * <a href="#/p5/scale">scale()</a>.
        - *
        - * @method strokeWeight
        - * @param  {Number} weight the weight of the stroke (in pixels).
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top.
        - *   line(20, 20, 80, 20);
        - *
        - *   // Middle.
        - *   strokeWeight(4);
        - *   line(20, 40, 80, 40);
        - *
        - *   // Bottom.
        - *   strokeWeight(10);
        - *   line(20, 70, 80, 70);
        - *
        - *   describe('Three horizontal black lines. The top line is thin, the middle is medium, and the bottom is thick.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top.
        - *   line(20, 20, 80, 20);
        - *
        - *   // Scale by a factor of 5.
        - *   scale(5);
        - *
        - *   // Bottom. Coordinates are adjusted for scaling.
        - *   line(4, 8, 16, 8);
        - *
        - *   describe('Two horizontal black lines. The top line is thin and the bottom is five times thicker than the top.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.strokeWeight = function(w) {
        -  p5._validateParameters('strokeWeight', arguments);
        -  this._renderer.strokeWeight(w);
        -  return this;
        -};
        -
        -export default p5;
        diff --git a/src/core/shape/curves.js b/src/core/shape/curves.js
        deleted file mode 100644
        index 0a3695118c..0000000000
        --- a/src/core/shape/curves.js
        +++ /dev/null
        @@ -1,1171 +0,0 @@
        -/**
        - * @module Shape
        - * @submodule Curves
        - * @for p5
        - * @requires core
        - */
        -
        -import p5 from '../main';
        -import '../friendly_errors/fes_core';
        -import '../friendly_errors/file_errors';
        -import '../friendly_errors/validate_params';
        -
        -/**
        - * Draws a Bézier curve.
        - *
        - * Bézier curves can form shapes and curves that slope gently. They're defined
        - * by two anchor points and two control points. Bézier curves provide more
        - * control than the spline curves created with the
        - * <a href="#/p5/curve">curve()</a> function.
        - *
        - * The first two parameters, `x1` and `y1`, set the first anchor point. The
        - * first anchor point is where the curve starts.
        - *
        - * The next four parameters, `x2`, `y2`, `x3`, and `y3`, set the two control
        - * points. The control points "pull" the curve towards them.
        - *
        - * The seventh and eighth parameters, `x4` and `y4`, set the last anchor
        - * point. The last anchor point is where the curve ends.
        - *
        - * Bézier curves can also be drawn in 3D using WebGL mode. The 3D version of
        - * `bezier()` has twelve arguments because each point has x-, y-,
        - * and z-coordinates.
        - *
        - * @method bezier
        - * @param  {Number} x1 x-coordinate of the first anchor point.
        - * @param  {Number} y1 y-coordinate of the first anchor point.
        - * @param  {Number} x2 x-coordinate of the first control point.
        - * @param  {Number} y2 y-coordinate of the first control point.
        - * @param  {Number} x3 x-coordinate of the second control point.
        - * @param  {Number} y3 y-coordinate of the second control point.
        - * @param  {Number} x4 x-coordinate of the second anchor point.
        - * @param  {Number} y4 y-coordinate of the second anchor point.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw the anchor points in black.
        - *   stroke(0);
        - *   strokeWeight(5);
        - *   point(85, 20);
        - *   point(15, 80);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(10, 10);
        - *   point(90, 90);
        - *
        - *   // Draw a black bezier curve.
        - *   noFill();
        - *   stroke(0);
        - *   strokeWeight(1);
        - *   bezier(85, 20, 10, 10, 90, 90, 15, 80);
        - *
        - *   // Draw red lines from the anchor points to the control points.
        - *   stroke(255, 0, 0);
        - *   line(85, 20, 10, 10);
        - *   line(15, 80, 90, 90);
        - *
        - *   describe(
        - *     'A gray square with three curves. A black s-curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click the mouse near the red dot in the top-left corner
        - * // and drag to change the curve's shape.
        - *
        - * let x2 = 10;
        - * let y2 = 10;
        - * let isChanging = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with three curves. A black s-curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the anchor points in black.
        - *   stroke(0);
        - *   strokeWeight(5);
        - *   point(85, 20);
        - *   point(15, 80);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(x2, y2);
        - *   point(90, 90);
        - *
        - *   // Draw a black bezier curve.
        - *   noFill();
        - *   stroke(0);
        - *   strokeWeight(1);
        - *   bezier(85, 20, x2, y2, 90, 90, 15, 80);
        - *
        - *   // Draw red lines from the anchor points to the control points.
        - *   stroke(255, 0, 0);
        - *   line(85, 20, x2, y2);
        - *   line(15, 80, 90, 90);
        - * }
        - *
        - * // Start changing the first control point if the user clicks near it.
        - * function mousePressed() {
        - *   if (dist(mouseX, mouseY, x2, y2) < 20) {
        - *     isChanging = true;
        - *   }
        - * }
        - *
        - * // Stop changing the first control point when the user releases the mouse.
        - * function mouseReleased() {
        - *   isChanging = false;
        - * }
        - *
        - * // Update the first control point while the user drags the mouse.
        - * function mouseDragged() {
        - *   if (isChanging === true) {
        - *     x2 = mouseX;
        - *     y2 = mouseY;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background('skyblue');
        - *
        - *   // Draw the red balloon.
        - *   fill('red');
        - *   bezier(50, 60, 5, 15, 95, 15, 50, 60);
        - *
        - *   // Draw the balloon string.
        - *   line(50, 60, 50, 80);
        - *
        - *   describe('A red balloon in a blue sky.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A red balloon in a blue sky. The balloon rotates slowly, revealing that it is flat.');
        - * }
        - *
        - * function draw() {
        - *   background('skyblue');
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Draw the red balloon.
        - *   fill('red');
        - *   bezier(0, 0, 0, -45, -45, 0, 45, -45, 0, 0, 0, 0);
        - *
        - *   // Draw the balloon string.
        - *   line(0, 0, 0, 0, 20, 0);
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method bezier
        - * @param  {Number} x1
        - * @param  {Number} y1
        - * @param  {Number} z1 z-coordinate of the first anchor point.
        - * @param  {Number} x2
        - * @param  {Number} y2
        - * @param  {Number} z2 z-coordinate of the first control point.
        - * @param  {Number} x3
        - * @param  {Number} y3
        - * @param  {Number} z3 z-coordinate of the second control point.
        - * @param  {Number} x4
        - * @param  {Number} y4
        - * @param  {Number} z4 z-coordinate of the second anchor point.
        - * @chainable
        - */
        -p5.prototype.bezier = function(...args) {
        -  p5._validateParameters('bezier', args);
        -
        -  // if the current stroke and fill settings wouldn't result in something
        -  // visible, exit immediately
        -  if (!this._renderer._doStroke && !this._renderer._doFill) {
        -    return this;
        -  }
        -
        -  this._renderer.bezier(...args);
        -
        -  return this;
        -};
        -
        -/**
        - * Sets the number of segments used to draw Bézier curves in WebGL mode.
        - *
        - * In WebGL mode, smooth shapes are drawn using many flat segments. Adding
        - * more flat segments makes shapes appear smoother.
        - *
        - * The parameter, `detail`, is the number of segments to use while drawing a
        - * Bézier curve. For example, calling `bezierDetail(5)` will use 5 segments to
        - * draw curves with the <a href="#/p5/bezier">bezier()</a> function. By
        - * default,`detail` is 20.
        - *
        - * Note: `bezierDetail()` has no effect in 2D mode.
        - *
        - * @method bezierDetail
        - * @param {Number} detail number of segments to use. Defaults to 20.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Draw the original curve.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw the anchor points in black.
        - *   stroke(0);
        - *   strokeWeight(5);
        - *   point(85, 20);
        - *   point(15, 80);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(10, 10);
        - *   point(90, 90);
        - *
        - *   // Draw a black bezier curve.
        - *   noFill();
        - *   stroke(0);
        - *   strokeWeight(1);
        - *   bezier(85, 20, 10, 10, 90, 90, 15, 80);
        - *
        - *   // Draw red lines from the anchor points to the control points.
        - *   stroke(255, 0, 0);
        - *   line(85, 20, 10, 10);
        - *   line(15, 80, 90, 90);
        - *
        - *   describe(
        - *     'A gray square with three curves. A black s-curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Draw the curve with less detail.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   // Set the curveDetail() to 5.
        - *   bezierDetail(5);
        - *
        - *   // Draw the anchor points in black.
        - *   stroke(0);
        - *   strokeWeight(5);
        - *   point(35, -30, 0);
        - *   point(-35, 30, 0);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(-40, -40, 0);
        - *   point(40, 40, 0);
        - *
        - *   // Draw a black bezier curve.
        - *   noFill();
        - *   stroke(0);
        - *   strokeWeight(1);
        - *   bezier(35, -30, 0, -40, -40, 0, 40, 40, 0, -35, 30, 0);
        - *
        - *   // Draw red lines from the anchor points to the control points.
        - *   stroke(255, 0, 0);
        - *   line(35, -30, -40, -40);
        - *   line(-35, 30, 40, 40);
        - *
        - *   describe(
        - *     'A gray square with three curves. A black s-curve is drawn with jagged segments. Two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.bezierDetail = function(d) {
        -  p5._validateParameters('bezierDetail', arguments);
        -  this._bezierDetail = d;
        -  return this;
        -};
        -
        -/**
        - * Calculates coordinates along a Bézier curve using interpolation.
        - *
        - * `bezierPoint()` calculates coordinates along a Bézier curve using the
        - * anchor and control points. It expects points in the same order as the
        - * <a href="#/p5/bezier">bezier()</a> function. `bezierPoint()` works one axis
        - * at a time. Passing the anchor and control points' x-coordinates will
        - * calculate the x-coordinate of a point on the curve. Passing the anchor and
        - * control points' y-coordinates will calculate the y-coordinate of a point on
        - * the curve.
        - *
        - * The first parameter, `a`, is the coordinate of the first anchor point.
        - *
        - * The second and third parameters, `b` and `c`, are the coordinates of the
        - * control points.
        - *
        - * The fourth parameter, `d`, is the coordinate of the last anchor point.
        - *
        - * The fifth parameter, `t`, is the amount to interpolate along the curve. 0
        - * is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
        - * between them.
        - *
        - * @method bezierPoint
        - * @param {Number} a coordinate of first control point.
        - * @param {Number} b coordinate of first anchor point.
        - * @param {Number} c coordinate of second anchor point.
        - * @param {Number} d coordinate of second control point.
        - * @param {Number} t amount to interpolate between 0 and 1.
        - * @return {Number} coordinate of the point on the curve.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Set the coordinates for the curve's anchor and control points.
        - *   let x1 = 85;
        - *   let x2 = 10;
        - *   let x3 = 90;
        - *   let x4 = 15;
        - *   let y1 = 20;
        - *   let y2 = 10;
        - *   let y3 = 90;
        - *   let y4 = 80;
        - *
        - *   // Style the curve.
        - *   noFill();
        - *
        - *   // Draw the curve.
        - *   bezier(x1, y1, x2, y2, x3, y3, x4, y4);
        - *
        - *   // Draw circles along the curve's path.
        - *   fill(255);
        - *
        - *   // Top-right.
        - *   let x = bezierPoint(x1, x2, x3, x4, 0);
        - *   let y = bezierPoint(y1, y2, y3, y4, 0);
        - *   circle(x, y, 5);
        - *
        - *   // Center.
        - *   x = bezierPoint(x1, x2, x3, x4, 0.5);
        - *   y = bezierPoint(y1, y2, y3, y4, 0.5);
        - *   circle(x, y, 5);
        - *
        - *   // Bottom-left.
        - *   x = bezierPoint(x1, x2, x3, x4, 1);
        - *   y = bezierPoint(y1, y2, y3, y4, 1);
        - *   circle(x, y, 5);
        - *
        - *   describe('A black s-curve on a gray square. The endpoints and center of the curve are marked with white circles.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A black s-curve on a gray square. A white circle moves back and forth along the curve.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Set the coordinates for the curve's anchor and control points.
        - *   let x1 = 85;
        - *   let x2 = 10;
        - *   let x3 = 90;
        - *   let x4 = 15;
        - *   let y1 = 20;
        - *   let y2 = 10;
        - *   let y3 = 90;
        - *   let y4 = 80;
        - *
        - *   // Draw the curve.
        - *   noFill();
        - *   bezier(x1, y1, x2, y2, x3, y3, x4, y4);
        - *
        - *   // Calculate the circle's coordinates.
        - *   let t = 0.5 * sin(frameCount * 0.01) + 0.5;
        - *   let x = bezierPoint(x1, x2, x3, x4, t);
        - *   let y = bezierPoint(y1, y2, y3, y4, t);
        - *
        - *   // Draw the circle.
        - *   fill(255);
        - *   circle(x, y, 5);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.bezierPoint = function(a, b, c, d, t) {
        -  p5._validateParameters('bezierPoint', arguments);
        -
        -  const adjustedT = 1 - t;
        -  return (
        -    Math.pow(adjustedT, 3) * a +
        -    3 * Math.pow(adjustedT, 2) * t * b +
        -    3 * adjustedT * Math.pow(t, 2) * c +
        -    Math.pow(t, 3) * d
        -  );
        -};
        -
        -/**
        - * Calculates coordinates along a line that's tangent to a Bézier curve.
        - *
        - * Tangent lines skim the surface of a curve. A tangent line's slope equals
        - * the curve's slope at the point where it intersects.
        - *
        - * `bezierTangent()` calculates coordinates along a tangent line using the
        - * Bézier curve's anchor and control points. It expects points in the same
        - * order as the <a href="#/p5/bezier">bezier()</a> function. `bezierTangent()`
        - * works one axis at a time. Passing the anchor and control points'
        - * x-coordinates will calculate the x-coordinate of a point on the tangent
        - * line. Passing the anchor and control points' y-coordinates will calculate
        - * the y-coordinate of a point on the tangent line.
        - *
        - * The first parameter, `a`, is the coordinate of the first anchor point.
        - *
        - * The second and third parameters, `b` and `c`, are the coordinates of the
        - * control points.
        - *
        - * The fourth parameter, `d`, is the coordinate of the last anchor point.
        - *
        - * The fifth parameter, `t`, is the amount to interpolate along the curve. 0
        - * is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
        - * between them.
        - *
        - * @method bezierTangent
        - * @param {Number} a coordinate of first anchor point.
        - * @param {Number} b coordinate of first control point.
        - * @param {Number} c coordinate of second control point.
        - * @param {Number} d coordinate of second anchor point.
        - * @param {Number} t amount to interpolate between 0 and 1.
        - * @return {Number} coordinate of a point on the tangent line.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Set the coordinates for the curve's anchor and control points.
        - *   let x1 = 85;
        - *   let x2 = 10;
        - *   let x3 = 90;
        - *   let x4 = 15;
        - *   let y1 = 20;
        - *   let y2 = 10;
        - *   let y3 = 90;
        - *   let y4 = 80;
        - *
        - *   // Style the curve.
        - *   noFill();
        - *
        - *   // Draw the curve.
        - *   bezier(x1, y1, x2, y2, x3, y3, x4, y4);
        - *
        - *   // Draw tangents along the curve's path.
        - *   fill(255);
        - *
        - *   // Top-right circle.
        - *   stroke(0);
        - *   let x = bezierPoint(x1, x2, x3, x4, 0);
        - *   let y = bezierPoint(y1, y2, y3, y4, 0);
        - *   circle(x, y, 5);
        - *
        - *   // Top-right tangent line.
        - *   // Scale the tangent point to draw a shorter line.
        - *   stroke(255, 0, 0);
        - *   let tx = 0.1 * bezierTangent(x1, x2, x3, x4, 0);
        - *   let ty = 0.1 * bezierTangent(y1, y2, y3, y4, 0);
        - *   line(x + tx, y + ty, x - tx, y - ty);
        - *
        - *   // Center circle.
        - *   stroke(0);
        - *   x = bezierPoint(x1, x2, x3, x4, 0.5);
        - *   y = bezierPoint(y1, y2, y3, y4, 0.5);
        - *   circle(x, y, 5);
        - *
        - *   // Center tangent line.
        - *   // Scale the tangent point to draw a shorter line.
        - *   stroke(255, 0, 0);
        - *   tx = 0.1 * bezierTangent(x1, x2, x3, x4, 0.5);
        - *   ty = 0.1 * bezierTangent(y1, y2, y3, y4, 0.5);
        - *   line(x + tx, y + ty, x - tx, y - ty);
        - *
        - *   // Bottom-left circle.
        - *   stroke(0);
        - *   x = bezierPoint(x1, x2, x3, x4, 1);
        - *   y = bezierPoint(y1, y2, y3, y4, 1);
        - *   circle(x, y, 5);
        - *
        - *   // Bottom-left tangent.
        - *   // Scale the tangent point to draw a shorter line.
        - *   stroke(255, 0, 0);
        - *   tx = 0.1 * bezierTangent(x1, x2, x3, x4, 1);
        - *   ty = 0.1 * bezierTangent(y1, y2, y3, y4, 1);
        - *   line(x + tx, y + ty, x - tx, y - ty);
        - *
        - *   describe(
        - *     'A black s-curve on a gray square. The endpoints and center of the curve are marked with white circles. Red tangent lines extend from the white circles.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.bezierTangent = function(a, b, c, d, t) {
        -  p5._validateParameters('bezierTangent', arguments);
        -
        -  const adjustedT = 1 - t;
        -  return (
        -    3 * d * Math.pow(t, 2) -
        -    3 * c * Math.pow(t, 2) +
        -    6 * c * adjustedT * t -
        -    6 * b * adjustedT * t +
        -    3 * b * Math.pow(adjustedT, 2) -
        -    3 * a * Math.pow(adjustedT, 2)
        -  );
        -};
        -
        -/**
        - * Draws a curve using a Catmull-Rom spline.
        - *
        - * Spline curves can form shapes and curves that slope gently. They’re like
        - * cables that are attached to a set of points. Splines are defined by two
        - * anchor points and two control points.
        - *
        - * The first two parameters, `x1` and `y1`, set the first control point. This
        - * point isn’t drawn and can be thought of as the curve’s starting point.
        - *
        - * The next four parameters, `x2`, `y2`, `x3`, and `y3`, set the two anchor
        - * points. The anchor points are the start and end points of the curve’s
        - * visible segment.
        - *
        - * The seventh and eighth parameters, `x4` and `y4`, set the last control
        - * point. This point isn’t drawn and can be thought of as the curve’s ending
        - * point.
        - *
        - * Spline curves can also be drawn in 3D using WebGL mode. The 3D version of
        - * `curve()` has twelve arguments because each point has x-, y-, and
        - * z-coordinates.
        - *
        - * @method curve
        - * @param  {Number} x1 x-coordinate of the first control point.
        - * @param  {Number} y1 y-coordinate of the first control point.
        - * @param  {Number} x2 x-coordinate of the first anchor point.
        - * @param  {Number} y2 y-coordinate of the first anchor point.
        - * @param  {Number} x3 x-coordinate of the second anchor point.
        - * @param  {Number} y3 y-coordinate of the second anchor point.
        - * @param  {Number} x4 x-coordinate of the second control point.
        - * @param  {Number} y4 y-coordinate of the second control point.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw a black spline curve.
        - *   noFill();
        - *   strokeWeight(1);
        - *   stroke(0);
        - *   curve(5, 26, 73, 24, 73, 61, 15, 65);
        - *
        - *   // Draw red spline curves from the anchor points to the control points.
        - *   stroke(255, 0, 0);
        - *   curve(5, 26, 5, 26, 73, 24, 73, 61);
        - *   curve(73, 24, 73, 61, 15, 65, 15, 65);
        - *
        - *   // Draw the anchor points in black.
        - *   strokeWeight(5);
        - *   stroke(0);
        - *   point(73, 24);
        - *   point(73, 61);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(5, 26);
        - *   point(15, 65);
        - *
        - *   describe(
        - *     'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let x1 = 5;
        - * let y1 = 26;
        - * let isChanging = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a black spline curve.
        - *   noFill();
        - *   strokeWeight(1);
        - *   stroke(0);
        - *   curve(x1, y1, 73, 24, 73, 61, 15, 65);
        - *
        - *   // Draw red spline curves from the anchor points to the control points.
        - *   stroke(255, 0, 0);
        - *   curve(x1, y1, x1, y1, 73, 24, 73, 61);
        - *   curve(73, 24, 73, 61, 15, 65, 15, 65);
        - *
        - *   // Draw the anchor points in black.
        - *   strokeWeight(5);
        - *   stroke(0);
        - *   point(73, 24);
        - *   point(73, 61);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(x1, y1);
        - *   point(15, 65);
        - * }
        - *
        - * // Start changing the first control point if the user clicks near it.
        - * function mousePressed() {
        - *   if (dist(mouseX, mouseY, x1, y1) < 20) {
        - *     isChanging = true;
        - *   }
        - * }
        - *
        - * // Stop changing the first control point when the user releases the mouse.
        - * function mouseReleased() {
        - *   isChanging = false;
        - * }
        - *
        - * // Update the first control point while the user drags the mouse.
        - * function mouseDragged() {
        - *   if (isChanging === true) {
        - *     x1 = mouseX;
        - *     y1 = mouseY;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background('skyblue');
        - *
        - *   // Draw the red balloon.
        - *   fill('red');
        - *   curve(-150, 275, 50, 60, 50, 60, 250, 275);
        - *
        - *   // Draw the balloon string.
        - *   line(50, 60, 50, 80);
        - *
        - *   describe('A red balloon in a blue sky.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A red balloon in a blue sky.');
        - * }
        - *
        - * function draw() {
        - *   background('skyblue');
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Draw the red balloon.
        - *   fill('red');
        - *   curve(-200, 225, 0, 0, 10, 0, 0, 10, 0, 200, 225, 0);
        - *
        - *   // Draw the balloon string.
        - *   line(0, 10, 0, 0, 30, 0);
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method curve
        - * @param  {Number} x1
        - * @param  {Number} y1
        - * @param  {Number} z1 z-coordinate of the first control point.
        - * @param  {Number} x2
        - * @param  {Number} y2
        - * @param  {Number} z2 z-coordinate of the first anchor point.
        - * @param  {Number} x3
        - * @param  {Number} y3
        - * @param  {Number} z3 z-coordinate of the second anchor point.
        - * @param  {Number} x4
        - * @param  {Number} y4
        - * @param  {Number} z4 z-coordinate of the second control point.
        - * @chainable
        - */
        -p5.prototype.curve = function(...args) {
        -  p5._validateParameters('curve', args);
        -
        -  if (this._renderer._doStroke) {
        -    this._renderer.curve(...args);
        -  }
        -
        -  return this;
        -};
        -
        -/**
        - * Sets the number of segments used to draw spline curves in WebGL mode.
        - *
        - * In WebGL mode, smooth shapes are drawn using many flat segments. Adding
        - * more flat segments makes shapes appear smoother.
        - *
        - * The parameter, `detail`, is the number of segments to use while drawing a
        - * spline curve. For example, calling `curveDetail(5)` will use 5 segments to
        - * draw curves with the <a href="#/p5/curve">curve()</a> function. By
        - * default,`detail` is 20.
        - *
        - * Note: `curveDetail()` has no effect in 2D mode.
        - *
        - * @method curveDetail
        - * @param {Number} resolution number of segments to use. Defaults to 20.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw a black spline curve.
        - *   noFill();
        - *   strokeWeight(1);
        - *   stroke(0);
        - *   curve(5, 26, 73, 24, 73, 61, 15, 65);
        - *
        - *   // Draw red spline curves from the anchor points to the control points.
        - *   stroke(255, 0, 0);
        - *   curve(5, 26, 5, 26, 73, 24, 73, 61);
        - *   curve(73, 24, 73, 61, 15, 65, 15, 65);
        - *
        - *   // Draw the anchor points in black.
        - *   strokeWeight(5);
        - *   stroke(0);
        - *   point(73, 24);
        - *   point(73, 61);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(5, 26);
        - *   point(15, 65);
        - *
        - *   describe(
        - *     'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   // Set the curveDetail() to 3.
        - *   curveDetail(3);
        - *
        - *   // Draw a black spline curve.
        - *   noFill();
        - *   strokeWeight(1);
        - *   stroke(0);
        - *   curve(-45, -24, 0, 23, -26, 0, 23, 11, 0, -35, 15, 0);
        - *
        - *   // Draw red spline curves from the anchor points to the control points.
        - *   stroke(255, 0, 0);
        - *   curve(-45, -24, 0, -45, -24, 0, 23, -26, 0, 23, 11, 0);
        - *   curve(23, -26, 0, 23, 11, 0, -35, 15, 0, -35, 15, 0);
        - *
        - *   // Draw the anchor points in black.
        - *   strokeWeight(5);
        - *   stroke(0);
        - *   point(23, -26);
        - *   point(23, 11);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(-45, -24);
        - *   point(-35, 15);
        - *
        - *   describe(
        - *     'A gray square with a jagged curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.curveDetail = function(d) {
        -  p5._validateParameters('curveDetail', arguments);
        -  if (d < 3) {
        -    this._curveDetail = 3;
        -  } else {
        -    this._curveDetail = d;
        -  }
        -  return this;
        -};
        -
        -/**
        - * Adjusts the way <a href="#/p5/curve">curve()</a> and
        - * <a href="#/p5/curveVertex">curveVertex()</a> draw.
        - *
        - * Spline curves are like cables that are attached to a set of points.
        - * `curveTightness()` adjusts how tightly the cable is attached to the points.
        - *
        - * The parameter, `tightness`, determines how the curve fits to the vertex
        - * points. By default, `tightness` is set to 0. Setting tightness to 1,
        - * as in `curveTightness(1)`, connects the curve's points using straight
        - * lines. Values in the range from –5 to  5 deform curves while leaving them
        - * recognizable.
        - *
        - * @method curveTightness
        - * @param {Number} amount amount of tightness.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Move the mouse left and right to see the curve change.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A black curve forms a sideways U shape. The curve deforms as the user moves the mouse from left to right');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Set the curve's tightness using the mouse.
        - *   let t = map(mouseX, 0, 100, -5, 5, true);
        - *   curveTightness(t);
        - *
        - *   // Draw the curve.
        - *   noFill();
        - *   beginShape();
        - *   curveVertex(10, 26);
        - *   curveVertex(10, 26);
        - *   curveVertex(83, 24);
        - *   curveVertex(83, 61);
        - *   curveVertex(25, 65);
        - *   curveVertex(25, 65);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.curveTightness = function(t) {
        -  p5._validateParameters('curveTightness', arguments);
        -  this._renderer._curveTightness = t;
        -  return this;
        -};
        -
        -/**
        - * Calculates coordinates along a spline curve using interpolation.
        - *
        - * `curvePoint()` calculates coordinates along a spline curve using the
        - * anchor and control points. It expects points in the same order as the
        - * <a href="#/p5/curve">curve()</a> function. `curvePoint()` works one axis
        - * at a time. Passing the anchor and control points' x-coordinates will
        - * calculate the x-coordinate of a point on the curve. Passing the anchor and
        - * control points' y-coordinates will calculate the y-coordinate of a point on
        - * the curve.
        - *
        - * The first parameter, `a`, is the coordinate of the first control point.
        - *
        - * The second and third parameters, `b` and `c`, are the coordinates of the
        - * anchor points.
        - *
        - * The fourth parameter, `d`, is the coordinate of the last control point.
        - *
        - * The fifth parameter, `t`, is the amount to interpolate along the curve. 0
        - * is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
        - * between them.
        - *
        - * @method curvePoint
        - * @param {Number} a coordinate of first anchor point.
        - * @param {Number} b coordinate of first control point.
        - * @param {Number} c coordinate of second control point.
        - * @param {Number} d coordinate of second anchor point.
        - * @param {Number} t amount to interpolate between 0 and 1.
        - * @return {Number} coordinate of a point on the curve.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Set the coordinates for the curve's anchor and control points.
        - *   let x1 = 5;
        - *   let y1 = 26;
        - *   let x2 = 73;
        - *   let y2 = 24;
        - *   let x3 = 73;
        - *   let y3 = 61;
        - *   let x4 = 15;
        - *   let y4 = 65;
        - *
        - *   // Draw the curve.
        - *   noFill();
        - *   curve(x1, y1, x2, y2, x3, y3, x4, y4);
        - *
        - *   // Draw circles along the curve's path.
        - *   fill(255);
        - *
        - *   // Top.
        - *   let x = curvePoint(x1, x2, x3, x4, 0);
        - *   let y = curvePoint(y1, y2, y3, y4, 0);
        - *   circle(x, y, 5);
        - *
        - *   // Center.
        - *   x = curvePoint(x1, x2, x3, x4, 0.5);
        - *   y = curvePoint(y1, y2, y3, y4, 0.5);
        - *   circle(x, y, 5);
        - *
        - *   // Bottom.
        - *   x = curvePoint(x1, x2, x3, x4, 1);
        - *   y = curvePoint(y1, y2, y3, y4, 1);
        - *   circle(x, y, 5);
        - *
        - *   describe('A black curve on a gray square. The endpoints and center of the curve are marked with white circles.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A black curve on a gray square. A white circle moves back and forth along the curve.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Set the coordinates for the curve's anchor and control points.
        - *   let x1 = 5;
        - *   let y1 = 26;
        - *   let x2 = 73;
        - *   let y2 = 24;
        - *   let x3 = 73;
        - *   let y3 = 61;
        - *   let x4 = 15;
        - *   let y4 = 65;
        - *
        - *   // Draw the curve.
        - *   noFill();
        - *   curve(x1, y1, x2, y2, x3, y3, x4, y4);
        - *
        - *   // Calculate the circle's coordinates.
        - *   let t = 0.5 * sin(frameCount * 0.01) + 0.5;
        - *   let x = curvePoint(x1, x2, x3, x4, t);
        - *   let y = curvePoint(y1, y2, y3, y4, t);
        - *
        - *   // Draw the circle.
        - *   fill(255);
        - *   circle(x, y, 5);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.curvePoint = function(a, b, c, d, t) {
        -  p5._validateParameters('curvePoint', arguments);
        -  const s = this._renderer._curveTightness,
        -    t3 = t * t * t,
        -    t2 = t * t,
        -    f1 = (s - 1) / 2 * t3 + (1 - s) * t2 + (s - 1) / 2 * t,
        -    f2 = (s + 3) / 2 * t3 + (-5 - s) / 2 * t2 + 1.0,
        -    f3 = (-3 - s) / 2 * t3 + (s + 2) * t2 + (1 - s) / 2 * t,
        -    f4 = (1 - s) / 2 * t3 + (s - 1) / 2 * t2;
        -  return a * f1 + b * f2 + c * f3 + d * f4;
        -};
        -
        -/**
        - * Calculates coordinates along a line that's tangent to a spline curve.
        - *
        - * Tangent lines skim the surface of a curve. A tangent line's slope equals
        - * the curve's slope at the point where it intersects.
        - *
        - * `curveTangent()` calculates coordinates along a tangent line using the
        - * spline curve's anchor and control points. It expects points in the same
        - * order as the <a href="#/p5/curve">curve()</a> function. `curveTangent()`
        - * works one axis at a time. Passing the anchor and control points'
        - * x-coordinates will calculate the x-coordinate of a point on the tangent
        - * line. Passing the anchor and control points' y-coordinates will calculate
        - * the y-coordinate of a point on the tangent line.
        - *
        - * The first parameter, `a`, is the coordinate of the first control point.
        - *
        - * The second and third parameters, `b` and `c`, are the coordinates of the
        - * anchor points.
        - *
        - * The fourth parameter, `d`, is the coordinate of the last control point.
        - *
        - * The fifth parameter, `t`, is the amount to interpolate along the curve. 0
        - * is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
        - * between them.
        - *
        - * @method curveTangent
        - * @param {Number} a coordinate of first control point.
        - * @param {Number} b coordinate of first anchor point.
        - * @param {Number} c coordinate of second anchor point.
        - * @param {Number} d coordinate of second control point.
        - * @param {Number} t amount to interpolate between 0 and 1.
        - * @return {Number} coordinate of a point on the tangent line.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Set the coordinates for the curve's anchor and control points.
        - *   let x1 = 5;
        - *   let y1 = 26;
        - *   let x2 = 73;
        - *   let y2 = 24;
        - *   let x3 = 73;
        - *   let y3 = 61;
        - *   let x4 = 15;
        - *   let y4 = 65;
        - *
        - *   // Draw the curve.
        - *   noFill();
        - *   curve(x1, y1, x2, y2, x3, y3, x4, y4);
        - *
        - *   // Draw tangents along the curve's path.
        - *   fill(255);
        - *
        - *   // Top circle.
        - *   stroke(0);
        - *   let x = curvePoint(x1, x2, x3, x4, 0);
        - *   let y = curvePoint(y1, y2, y3, y4, 0);
        - *   circle(x, y, 5);
        - *
        - *   // Top tangent line.
        - *   // Scale the tangent point to draw a shorter line.
        - *   stroke(255, 0, 0);
        - *   let tx = 0.2 * curveTangent(x1, x2, x3, x4, 0);
        - *   let ty = 0.2 * curveTangent(y1, y2, y3, y4, 0);
        - *   line(x + tx, y + ty, x - tx, y - ty);
        - *
        - *   // Center circle.
        - *   stroke(0);
        - *   x = curvePoint(x1, x2, x3, x4, 0.5);
        - *   y = curvePoint(y1, y2, y3, y4, 0.5);
        - *   circle(x, y, 5);
        - *
        - *   // Center tangent line.
        - *   // Scale the tangent point to draw a shorter line.
        - *   stroke(255, 0, 0);
        - *   tx = 0.2 * curveTangent(x1, x2, x3, x4, 0.5);
        - *   ty = 0.2 * curveTangent(y1, y2, y3, y4, 0.5);
        - *   line(x + tx, y + ty, x - tx, y - ty);
        - *
        - *   // Bottom circle.
        - *   stroke(0);
        - *   x = curvePoint(x1, x2, x3, x4, 1);
        - *   y = curvePoint(y1, y2, y3, y4, 1);
        - *   circle(x, y, 5);
        - *
        - *   // Bottom tangent line.
        - *   // Scale the tangent point to draw a shorter line.
        - *   stroke(255, 0, 0);
        - *   tx = 0.2 * curveTangent(x1, x2, x3, x4, 1);
        - *   ty = 0.2 * curveTangent(y1, y2, y3, y4, 1);
        - *   line(x + tx, y + ty, x - tx, y - ty);
        - *
        - *   describe(
        - *     'A black curve on a gray square. A white circle moves back and forth along the curve.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.curveTangent = function(a, b, c, d, t) {
        -  p5._validateParameters('curveTangent', arguments);
        -
        -  const s = this._renderer._curveTightness,
        -    tt3 = t * t * 3,
        -    t2 = t * 2,
        -    f1 = (s - 1) / 2 * tt3 + (1 - s) * t2 + (s - 1) / 2,
        -    f2 = (s + 3) / 2 * tt3 + (-5 - s) / 2 * t2,
        -    f3 = (-3 - s) / 2 * tt3 + (s + 2) * t2 + (1 - s) / 2,
        -    f4 = (1 - s) / 2 * tt3 + (s - 1) / 2 * t2;
        -  return a * f1 + b * f2 + c * f3 + d * f4;
        -};
        -
        -export default p5;
        diff --git a/src/core/shape/vertex.js b/src/core/shape/vertex.js
        deleted file mode 100644
        index 2a854077d1..0000000000
        --- a/src/core/shape/vertex.js
        +++ /dev/null
        @@ -1,2256 +0,0 @@
        -/**
        - * @module Shape
        - * @submodule Vertex
        - * @for p5
        - * @requires core
        - * @requires constants
        - */
        -
        -import p5 from '../main';
        -import * as constants from '../constants';
        -let shapeKind = null;
        -let vertices = [];
        -let contourVertices = [];
        -let isBezier = false;
        -let isCurve = false;
        -let isQuadratic = false;
        -let isContour = false;
        -let isFirstContour = true;
        -
        -/**
        - * Begins creating a hole within a flat shape.
        - *
        - * The `beginContour()` and <a href="#/p5/endContour">endContour()</a>
        - * functions allow for creating negative space within custom shapes that are
        - * flat. `beginContour()` begins adding vertices to a negative space and
        - * <a href="#/p5/endContour">endContour()</a> stops adding them.
        - * `beginContour()` and <a href="#/p5/endContour">endContour()</a> must be
        - * called between <a href="#/p5/beginShape">beginShape()</a> and
        - * <a href="#/p5/endShape">endShape()</a>.
        - *
        - * Transformations such as <a href="#/p5/translate">translate()</a>,
        - * <a href="#/p5/rotate">rotate()</a>, and <a href="#/p5/scale">scale()</a>
        - * don't work between `beginContour()` and
        - * <a href="#/p5/endContour">endContour()</a>. It's also not possible to use
        - * other shapes, such as <a href="#/p5/ellipse">ellipse()</a> or
        - * <a href="#/p5/rect">rect()</a>, between `beginContour()` and
        - * <a href="#/p5/endContour">endContour()</a>.
        - *
        - * Note: The vertices that define a negative space must "wind" in the opposite
        - * direction from the outer shape. First, draw vertices for the outer shape
        - * clockwise order. Then, draw vertices for the negative space in
        - * counter-clockwise order.
        - *
        - * @method beginContour
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Exterior vertices, clockwise winding.
        - *   vertex(10, 10);
        - *   vertex(90, 10);
        - *   vertex(90, 90);
        - *   vertex(10, 90);
        - *
        - *   // Interior vertices, counter-clockwise winding.
        - *   beginContour();
        - *   vertex(30, 30);
        - *   vertex(30, 70);
        - *   vertex(70, 70);
        - *   vertex(70, 30);
        - *   endContour();
        - *
        - *   // Stop drawing the shape.
        - *   endShape(CLOSE);
        - *
        - *   describe('A white square with a square hole in its center drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white square with a square hole in its center drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Exterior vertices, clockwise winding.
        - *   vertex(-40, -40);
        - *   vertex(40, -40);
        - *   vertex(40, 40);
        - *   vertex(-40, 40);
        - *
        - *   // Interior vertices, counter-clockwise winding.
        - *   beginContour();
        - *   vertex(-20, -20);
        - *   vertex(-20, 20);
        - *   vertex(20, 20);
        - *   vertex(20, -20);
        - *   endContour();
        - *
        - *   // Stop drawing the shape.
        - *   endShape(CLOSE);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.beginContour = function() {
        -  if (this._renderer.isP3D) {
        -    this._renderer.beginContour();
        -  } else {
        -    contourVertices = [];
        -    isContour = true;
        -  }
        -  return this;
        -};
        -
        -/**
        - * Begins adding vertices to a custom shape.
        - *
        - * The `beginShape()` and <a href="#/p5/endShape">endShape()</a> functions
        - * allow for creating custom shapes in 2D or 3D. `beginShape()` begins adding
        - * vertices to a custom shape and <a href="#/p5/endShape">endShape()</a> stops
        - * adding them.
        - *
        - * The parameter, `kind`, sets the kind of shape to make. By default, any
        - * irregular polygon can be drawn. The available modes for kind are:
        - *
        - * - `POINTS` to draw a series of points.
        - * - `LINES` to draw a series of unconnected line segments.
        - * - `TRIANGLES` to draw a series of separate triangles.
        - * - `TRIANGLE_FAN` to draw a series of connected triangles sharing the first vertex in a fan-like fashion.
        - * - `TRIANGLE_STRIP` to draw a series of connected triangles in strip fashion.
        - * - `QUADS` to draw a series of separate quadrilaterals (quads).
        - * - `QUAD_STRIP` to draw quad strip using adjacent edges to form the next quad.
        - * - `TESS` to create a filling curve by explicit tessellation (WebGL only).
        - *
        - * After calling `beginShape()`, shapes can be built by calling
        - * <a href="#/p5/vertex">vertex()</a>,
        - * <a href="#/p5/bezierVertex">bezierVertex()</a>,
        - * <a href="#/p5/quadraticVertex">quadraticVertex()</a>, and/or
        - * <a href="#/p5/curveVertex">curveVertex()</a>. Calling
        - * <a href="#/p5/endShape">endShape()</a> will stop adding vertices to the
        - * shape. Each shape will be outlined with the current stroke color and filled
        - * with the current fill color.
        - *
        - * Transformations such as <a href="#/p5/translate">translate()</a>,
        - * <a href="#/p5/rotate">rotate()</a>, and
        - * <a href="#/p5/scale">scale()</a> don't work between `beginShape()` and
        - * <a href="#/p5/endShape">endShape()</a>. It's also not possible to use
        - * other shapes, such as <a href="#/p5/ellipse">ellipse()</a> or
        - * <a href="#/p5/rect">rect()</a>, between `beginShape()` and
        - * <a href="#/p5/endShape">endShape()</a>.
        - *
        - * @method beginShape
        - * @param  {Constant} [kind] either POINTS, LINES, TRIANGLES, TRIANGLE_FAN
        - *                                TRIANGLE_STRIP, QUADS, QUAD_STRIP or TESS.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add vertices.
        - *   vertex(30, 20);
        - *   vertex(85, 20);
        - *   vertex(85, 75);
        - *   vertex(30, 75);
        - *
        - *   // Stop drawing the shape.
        - *   endShape(CLOSE);
        - *
        - *   describe('A white square on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   // Only draw the vertices (points).
        - *   beginShape(POINTS);
        - *
        - *   // Add vertices.
        - *   vertex(30, 20);
        - *   vertex(85, 20);
        - *   vertex(85, 75);
        - *   vertex(30, 75);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('Four black dots that form a square are drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   // Only draw lines between alternating pairs of vertices.
        - *   beginShape(LINES);
        - *
        - *   // Add vertices.
        - *   vertex(30, 20);
        - *   vertex(85, 20);
        - *   vertex(85, 75);
        - *   vertex(30, 75);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('Two horizontal black lines on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the shape.
        - *   noFill();
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add vertices.
        - *   vertex(30, 20);
        - *   vertex(85, 20);
        - *   vertex(85, 75);
        - *   vertex(30, 75);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('Three black lines form a sideways U shape on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the shape.
        - *   noFill();
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add vertices.
        - *   vertex(30, 20);
        - *   vertex(85, 20);
        - *   vertex(85, 75);
        - *   vertex(30, 75);
        - *
        - *   // Stop drawing the shape.
        - *   // Connect the first and last vertices.
        - *   endShape(CLOSE);
        - *
        - *   describe('A black outline of a square drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   // Draw a series of triangles.
        - *   beginShape(TRIANGLES);
        - *
        - *   // Left triangle.
        - *   vertex(30, 75);
        - *   vertex(40, 20);
        - *   vertex(50, 75);
        - *
        - *   // Right triangle.
        - *   vertex(60, 20);
        - *   vertex(70, 75);
        - *   vertex(80, 20);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('Two white triangles drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   // Draw a series of triangles.
        - *   beginShape(TRIANGLE_STRIP);
        - *
        - *   // Add vertices.
        - *   vertex(30, 75);
        - *   vertex(40, 20);
        - *   vertex(50, 75);
        - *   vertex(60, 20);
        - *   vertex(70, 75);
        - *   vertex(80, 20);
        - *   vertex(90, 75);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('Five white triangles that are interleaved drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   // Draw a series of triangles that share their first vertex.
        - *   beginShape(TRIANGLE_FAN);
        - *
        - *   // Add vertices.
        - *   vertex(57.5, 50);
        - *   vertex(57.5, 15);
        - *   vertex(92, 50);
        - *   vertex(57.5, 85);
        - *   vertex(22, 50);
        - *   vertex(57.5, 15);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('Four white triangles form a square are drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   // Draw a series of quadrilaterals.
        - *   beginShape(QUADS);
        - *
        - *   // Left rectangle.
        - *   vertex(30, 20);
        - *   vertex(30, 75);
        - *   vertex(50, 75);
        - *   vertex(50, 20);
        - *
        - *   // Right rectangle.
        - *   vertex(65, 20);
        - *   vertex(65, 75);
        - *   vertex(85, 75);
        - *   vertex(85, 20);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('Two white rectangles drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   // Draw a series of quadrilaterals.
        - *   beginShape(QUAD_STRIP);
        - *
        - *   // Add vertices.
        - *   vertex(30, 20);
        - *   vertex(30, 75);
        - *   vertex(50, 20);
        - *   vertex(50, 75);
        - *   vertex(65, 20);
        - *   vertex(65, 75);
        - *   vertex(85, 20);
        - *   vertex(85, 75);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('Three white rectangles that share edges are drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   // Draw a series of quadrilaterals.
        - *   beginShape(TESS);
        - *
        - *   // Add the vertices.
        - *   vertex(-30, -30, 0);
        - *   vertex(30, -30, 0);
        - *   vertex(30, -10, 0);
        - *   vertex(-10, -10, 0);
        - *   vertex(-10, 10, 0);
        - *   vertex(30, 10, 0);
        - *   vertex(30, 30, 0);
        - *   vertex(-30, 30, 0);
        - *
        - *   // Stop drawing the shape.
        - *   // Connect the first and last vertices.
        - *   endShape(CLOSE);
        - *
        - *   describe('A blocky C shape drawn in white on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag with the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A blocky C shape drawn in red, blue, and green on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Start drawing the shape.
        - *   // Draw a series of quadrilaterals.
        - *   beginShape(TESS);
        - *
        - *   // Add the vertices.
        - *   fill('red');
        - *   stroke('red');
        - *   vertex(-30, -30, 0);
        - *   vertex(30, -30, 0);
        - *   vertex(30, -10, 0);
        - *   fill('green');
        - *   stroke('green');
        - *   vertex(-10, -10, 0);
        - *   vertex(-10, 10, 0);
        - *   vertex(30, 10, 0);
        - *   fill('blue');
        - *   stroke('blue');
        - *   vertex(30, 30, 0);
        - *   vertex(-30, 30, 0);
        - *
        - *   // Stop drawing the shape.
        - *   // Connect the first and last vertices.
        - *   endShape(CLOSE);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.beginShape = function(kind) {
        -  p5._validateParameters('beginShape', arguments);
        -  if (this._renderer.isP3D) {
        -    this._renderer.beginShape(...arguments);
        -  } else {
        -    if (
        -      kind === constants.POINTS ||
        -      kind === constants.LINES ||
        -      kind === constants.TRIANGLES ||
        -      kind === constants.TRIANGLE_FAN ||
        -      kind === constants.TRIANGLE_STRIP ||
        -      kind === constants.QUADS ||
        -      kind === constants.QUAD_STRIP
        -    ) {
        -      shapeKind = kind;
        -    } else {
        -      shapeKind = null;
        -    }
        -
        -    vertices = [];
        -    contourVertices = [];
        -  }
        -  return this;
        -};
        -
        -/**
        - * Adds a Bézier curve segment to a custom shape.
        - *
        - * `bezierVertex()` adds a curved segment to custom shapes. The Bézier curves
        - * it creates are defined like those made by the
        - * <a href="#/p5/bezier">bezier()</a> function. `bezierVertex()` must be
        - * called between the
        - * <a href="#/p5/beginShape">beginShape()</a> and
        - * <a href="#/p5/endShape">endShape()</a> functions. The curved segment uses
        - * the previous vertex as the first anchor point, so there must be at least
        - * one call to <a href="#/p5/vertex">vertex()</a> before `bezierVertex()` can
        - * be used.
        - *
        - * The first four parameters, `x2`, `y2`, `x3`, and `y3`, set the curve’s two
        - * control points. The control points "pull" the curve towards them.
        - *
        - * The fifth and sixth parameters, `x4`, and `y4`, set the last anchor point.
        - * The last anchor point is where the curve ends.
        - *
        - * Bézier curves can also be drawn in 3D using WebGL mode. The 3D version of
        - * `bezierVertex()` has eight arguments because each point has x-, y-, and
        - * z-coordinates.
        - *
        - * Note: `bezierVertex()` won’t work when an argument is passed to
        - * <a href="#/p5/beginShape">beginShape()</a>.
        - *
        - * @method bezierVertex
        - * @param  {Number} x2 x-coordinate of the first control point.
        - * @param  {Number} y2 y-coordinate of the first control point.
        - * @param  {Number} x3 x-coordinate of the second control point.
        - * @param  {Number} y3 y-coordinate of the second control point.
        - * @param  {Number} x4 x-coordinate of the anchor point.
        - * @param  {Number} y4 y-coordinate of the anchor point.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the shape.
        - *   noFill();
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add the first anchor point.
        - *   vertex(30, 20);
        - *
        - *   // Add the Bézier vertex.
        - *   bezierVertex(80, 0, 80, 75, 30, 75);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('A black C curve on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw the anchor points in black.
        - *   stroke(0);
        - *   strokeWeight(5);
        - *   point(30, 20);
        - *   point(30, 75);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(80, 0);
        - *   point(80, 75);
        - *
        - *   // Style the shape.
        - *   noFill();
        - *   stroke(0);
        - *   strokeWeight(1);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add the first anchor point.
        - *   vertex(30, 20);
        - *
        - *   // Add the Bézier vertex.
        - *   bezierVertex(80, 0, 80, 75, 30, 75);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   // Draw red lines from the anchor points to the control points.
        - *   stroke(255, 0, 0);
        - *   line(30, 20, 80, 0);
        - *   line(30, 75, 80, 75);
        - *
        - *   describe(
        - *     'A gray square with three curves. A black curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click the mouse near the red dot in the top-right corner
        - * // and drag to change the curve's shape.
        - *
        - * let x2 = 80;
        - * let y2 = 0;
        - * let isChanging = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with three curves. A black curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the anchor points in black.
        - *   stroke(0);
        - *   strokeWeight(5);
        - *   point(30, 20);
        - *   point(30, 75);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(x2, y2);
        - *   point(80, 75);
        - *
        - *   // Style the shape.
        - *   noFill();
        - *   stroke(0);
        - *   strokeWeight(1);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add the first anchor point.
        - *   vertex(30, 20);
        - *
        - *   // Add the Bézier vertex.
        - *   bezierVertex(x2, y2, 80, 75, 30, 75);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   // Draw red lines from the anchor points to the control points.
        - *   stroke(255, 0, 0);
        - *   line(30, 20, x2, y2);
        - *   line(30, 75, 80, 75);
        - * }
        - *
        - * // Start changing the first control point if the user clicks near it.
        - * function mousePressed() {
        - *   if (dist(mouseX, mouseY, x2, y2) < 20) {
        - *     isChanging = true;
        - *   }
        - * }
        - *
        - * // Stop changing the first control point when the user releases the mouse.
        - * function mouseReleased() {
        - *   isChanging = false;
        - * }
        - *
        - * // Update the first control point while the user drags the mouse.
        - * function mouseDragged() {
        - *   if (isChanging === true) {
        - *     x2 = mouseX;
        - *     y2 = mouseY;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add the first anchor point.
        - *   vertex(30, 20);
        - *
        - *   // Add the Bézier vertices.
        - *   bezierVertex(80, 0, 80, 75, 30, 75);
        - *   bezierVertex(50, 80, 60, 25, 30, 20);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('A crescent moon shape drawn in white on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A crescent moon shape drawn in white on a blue background. When the user drags the mouse, the scene rotates and a second moon is revealed.');
        - * }
        - *
        - * function draw() {
        - *   background('midnightblue');
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the moons.
        - *   noStroke();
        - *   fill('lemonchiffon');
        - *
        - *   // Draw the first moon.
        - *   beginShape();
        - *   vertex(-20, -30, 0);
        - *   bezierVertex(30, -50, 0, 30, 25, 0, -20, 25, 0);
        - *   bezierVertex(0, 30, 0, 10, -25, 0, -20, -30, 0);
        - *   endShape();
        - *
        - *   // Draw the second moon.
        - *   beginShape();
        - *   vertex(-20, -30, -20);
        - *   bezierVertex(30, -50, -20, 30, 25, -20, -20, 25, -20);
        - *   bezierVertex(0, 30, -20, 10, -25, -20, -20, -30, -20);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method bezierVertex
        - * @param  {Number} x2
        - * @param  {Number} y2
        - * @param  {Number} z2 z-coordinate of the first control point.
        - * @param  {Number} x3
        - * @param  {Number} y3
        - * @param  {Number} z3 z-coordinate of the second control point.
        - * @param  {Number} x4
        - * @param  {Number} y4
        - * @param  {Number} z4 z-coordinate of the anchor point.
        - * @chainable
        - */
        -p5.prototype.bezierVertex = function(...args) {
        -  p5._validateParameters('bezierVertex', args);
        -  if (this._renderer.isP3D) {
        -    this._renderer.bezierVertex(...args);
        -  } else {
        -    if (vertices.length === 0) {
        -      p5._friendlyError(
        -        'vertex() must be used once before calling bezierVertex()',
        -        'bezierVertex'
        -      );
        -    } else {
        -      isBezier = true;
        -      const vert = [];
        -      for (let i = 0; i < args.length; i++) {
        -        vert[i] = args[i];
        -      }
        -      vert.isVert = false;
        -      if (isContour) {
        -        contourVertices.push(vert);
        -      } else {
        -        vertices.push(vert);
        -      }
        -    }
        -  }
        -  return this;
        -};
        -
        -/**
        - * Adds a spline curve segment to a custom shape.
        - *
        - * `curveVertex()` adds a curved segment to custom shapes. The spline curves
        - * it creates are defined like those made by the
        - * <a href="#/p5/curve">curve()</a> function. `curveVertex()` must be called
        - * between the <a href="#/p5/beginShape">beginShape()</a> and
        - * <a href="#/p5/endShape">endShape()</a> functions.
        - *
        - * Spline curves can form shapes and curves that slope gently. They’re like
        - * cables that are attached to a set of points. Splines are defined by two
        - * anchor points and two control points. `curveVertex()` must be called at
        - * least four times between
        - * <a href="#/p5/beginShape">beginShape()</a> and
        - * <a href="#/p5/endShape">endShape()</a> in order to draw a curve:
        - *
        - * ```js
        - * beginShape();
        - *
        - * // Add the first control point.
        - * curveVertex(84, 91);
        - *
        - * // Add the anchor points to draw between.
        - * curveVertex(68, 19);
        - * curveVertex(21, 17);
        - *
        - * // Add the second control point.
        - * curveVertex(32, 91);
        - *
        - * endShape();
        - * ```
        - *
        - * The code snippet above would only draw the curve between the anchor points,
        - * similar to the <a href="#/p5/curve">curve()</a> function. The segments
        - * between the control and anchor points can be drawn by calling
        - * `curveVertex()` with the coordinates of the control points:
        - *
        - * ```js
        - * beginShape();
        - *
        - * // Add the first control point and draw a segment to it.
        - * curveVertex(84, 91);
        - * curveVertex(84, 91);
        - *
        - * // Add the anchor points to draw between.
        - * curveVertex(68, 19);
        - * curveVertex(21, 17);
        - *
        - * // Add the second control point.
        - * curveVertex(32, 91);
        - *
        - * // Uncomment the next line to draw the segment to the second control point.
        - * // curveVertex(32, 91);
        - *
        - * endShape();
        - * ```
        - *
        - * The first two parameters, `x` and `y`, set the vertex’s location. For
        - * example, calling `curveVertex(10, 10)` adds a point to the curve at
        - * `(10, 10)`.
        - *
        - * Spline curves can also be drawn in 3D using WebGL mode. The 3D version of
        - * `curveVertex()` has three arguments because each point has x-, y-, and
        - * z-coordinates. By default, the vertex’s z-coordinate is set to 0.
        - *
        - * Note: `curveVertex()` won’t work when an argument is passed to
        - * <a href="#/p5/beginShape">beginShape()</a>.
        - *
        - * @method curveVertex
        - * @param {Number} x x-coordinate of the vertex
        - * @param {Number} y y-coordinate of the vertex
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the shape.
        - *   noFill();
        - *   strokeWeight(1);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add the first control point.
        - *   curveVertex(32, 91);
        - *
        - *   // Add the anchor points.
        - *   curveVertex(21, 17);
        - *   curveVertex(68, 19);
        - *
        - *   // Add the second control point.
        - *   curveVertex(84, 91);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   // Style the anchor and control points.
        - *   strokeWeight(5);
        - *
        - *   // Draw the anchor points in black.
        - *   stroke(0);
        - *   point(21, 17);
        - *   point(68, 19);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(32, 91);
        - *   point(84, 91);
        - *
        - *   describe(
        - *     'A black curve drawn on a gray background. The curve has black dots at its ends. Two red dots appear near the bottom of the canvas.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the shape.
        - *   noFill();
        - *   strokeWeight(1);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add the first control point and draw a segment to it.
        - *   curveVertex(32, 91);
        - *   curveVertex(32, 91);
        - *
        - *   // Add the anchor points.
        - *   curveVertex(21, 17);
        - *   curveVertex(68, 19);
        - *
        - *   // Add the second control point.
        - *   curveVertex(84, 91);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   // Style the anchor and control points.
        - *   strokeWeight(5);
        - *
        - *   // Draw the anchor points in black.
        - *   stroke(0);
        - *   point(21, 17);
        - *   point(68, 19);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(32, 91);
        - *   point(84, 91);
        - *
        - *   describe(
        - *     'A black curve drawn on a gray background. The curve passes through one red dot and two black dots. Another red dot appears near the bottom of the canvas.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the shape.
        - *   noFill();
        - *   strokeWeight(1);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add the first control point and draw a segment to it.
        - *   curveVertex(32, 91);
        - *   curveVertex(32, 91);
        - *
        - *   // Add the anchor points.
        - *   curveVertex(21, 17);
        - *   curveVertex(68, 19);
        - *
        - *   // Add the second control point and draw a segment to it.
        - *   curveVertex(84, 91);
        - *   curveVertex(84, 91);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   // Style the anchor and control points.
        - *   strokeWeight(5);
        - *
        - *   // Draw the anchor points in black.
        - *   stroke(0);
        - *   point(21, 17);
        - *   point(68, 19);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(32, 91);
        - *   point(84, 91);
        - *
        - *   describe(
        - *     'A black U curve drawn upside down on a gray background. The curve passes from one red dot through two black dots and ends at another red dot.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click the mouse near the red dot in the bottom-left corner
        - * // and drag to change the curve's shape.
        - *
        - * let x1 = 32;
        - * let y1 = 91;
        - * let isChanging = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A black U curve drawn upside down on a gray background. The curve passes from one red dot through two black dots and ends at another red dot.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the shape.
        - *   noFill();
        - *   stroke(0);
        - *   strokeWeight(1);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add the first control point and draw a segment to it.
        - *   curveVertex(x1, y1);
        - *   curveVertex(x1, y1);
        - *
        - *   // Add the anchor points.
        - *   curveVertex(21, 17);
        - *   curveVertex(68, 19);
        - *
        - *   // Add the second control point and draw a segment to it.
        - *   curveVertex(84, 91);
        - *   curveVertex(84, 91);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   // Style the anchor and control points.
        - *   strokeWeight(5);
        - *
        - *   // Draw the anchor points in black.
        - *   stroke(0);
        - *   point(21, 17);
        - *   point(68, 19);
        - *
        - *   // Draw the control points in red.
        - *   stroke(255, 0, 0);
        - *   point(x1, y1);
        - *   point(84, 91);
        - * }
        - *
        - * // Start changing the first control point if the user clicks near it.
        - * function mousePressed() {
        - *   if (dist(mouseX, mouseY, x1, y1) < 20) {
        - *     isChanging = true;
        - *   }
        - * }
        - *
        - * // Stop changing the first control point when the user releases the mouse.
        - * function mouseReleased() {
        - *   isChanging = false;
        - * }
        - *
        - * // Update the first control point while the user drags the mouse.
        - * function mouseDragged() {
        - *   if (isChanging === true) {
        - *     x1 = mouseX;
        - *     y1 = mouseY;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add the first control point and draw a segment to it.
        - *   curveVertex(32, 91);
        - *   curveVertex(32, 91);
        - *
        - *   // Add the anchor points.
        - *   curveVertex(21, 17);
        - *   curveVertex(68, 19);
        - *
        - *   // Add the second control point.
        - *   curveVertex(84, 91);
        - *   curveVertex(84, 91);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('A ghost shape drawn in white on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method curveVertex
        - * @param {Number} x
        - * @param {Number} y
        - * @param {Number} [z] z-coordinate of the vertex.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A ghost shape drawn in white on a blue background. When the user drags the mouse, the scene rotates to reveal the outline of a second ghost.');
        - * }
        - *
        - * function draw() {
        - *   background('midnightblue');
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the first ghost.
        - *   noStroke();
        - *   fill('ghostwhite');
        - *
        - *   beginShape();
        - *   curveVertex(-28, 41, 0);
        - *   curveVertex(-28, 41, 0);
        - *   curveVertex(-29, -33, 0);
        - *   curveVertex(18, -31, 0);
        - *   curveVertex(34, 41, 0);
        - *   curveVertex(34, 41, 0);
        - *   endShape();
        - *
        - *   // Draw the second ghost.
        - *   noFill();
        - *   stroke('ghostwhite');
        - *
        - *   beginShape();
        - *   curveVertex(-28, 41, -20);
        - *   curveVertex(-28, 41, -20);
        - *   curveVertex(-29, -33, -20);
        - *   curveVertex(18, -31, -20);
        - *   curveVertex(34, 41, -20);
        - *   curveVertex(34, 41, -20);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.curveVertex = function(...args) {
        -  p5._validateParameters('curveVertex', args);
        -  if (this._renderer.isP3D) {
        -    this._renderer.curveVertex(...args);
        -  } else {
        -    isCurve = true;
        -    this.vertex(args[0], args[1]);
        -  }
        -  return this;
        -};
        -
        -/**
        - * Stops creating a hole within a flat shape.
        - *
        - * The <a href="#/p5/beginContour">beginContour()</a> and `endContour()`
        - * functions allow for creating negative space within custom shapes that are
        - * flat. <a href="#/p5/beginContour">beginContour()</a> begins adding vertices
        - * to a negative space and `endContour()` stops adding them.
        - * <a href="#/p5/beginContour">beginContour()</a> and `endContour()` must be
        - * called between <a href="#/p5/beginShape">beginShape()</a> and
        - * <a href="#/p5/endShape">endShape()</a>.
        - *
        - * Transformations such as <a href="#/p5/translate">translate()</a>,
        - * <a href="#/p5/rotate">rotate()</a>, and <a href="#/p5/scale">scale()</a>
        - * don't work between <a href="#/p5/beginContour">beginContour()</a> and
        - * `endContour()`. It's also not possible to use other shapes, such as
        - * <a href="#/p5/ellipse">ellipse()</a> or <a href="#/p5/rect">rect()</a>,
        - * between <a href="#/p5/beginContour">beginContour()</a> and `endContour()`.
        - *
        - * Note: The vertices that define a negative space must "wind" in the opposite
        - * direction from the outer shape. First, draw vertices for the outer shape
        - * clockwise order. Then, draw vertices for the negative space in
        - * counter-clockwise order.
        - *
        - * @method endContour
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Exterior vertices, clockwise winding.
        - *   vertex(10, 10);
        - *   vertex(90, 10);
        - *   vertex(90, 90);
        - *   vertex(10, 90);
        - *
        - *   // Interior vertices, counter-clockwise winding.
        - *   beginContour();
        - *   vertex(30, 30);
        - *   vertex(30, 70);
        - *   vertex(70, 70);
        - *   vertex(70, 30);
        - *   endContour();
        - *
        - *   // Stop drawing the shape.
        - *   endShape(CLOSE);
        - *
        - *   describe('A white square with a square hole in its center drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white square with a square hole in its center drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Exterior vertices, clockwise winding.
        - *   vertex(-40, -40);
        - *   vertex(40, -40);
        - *   vertex(40, 40);
        - *   vertex(-40, 40);
        - *
        - *   // Interior vertices, counter-clockwise winding.
        - *   beginContour();
        - *   vertex(-20, -20);
        - *   vertex(-20, 20);
        - *   vertex(20, 20);
        - *   vertex(20, -20);
        - *   endContour();
        - *
        - *   // Stop drawing the shape.
        - *   endShape(CLOSE);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.endContour = function() {
        -  if (this._renderer.isP3D) {
        -    return this;
        -  }
        -
        -  const vert = contourVertices[0].slice(); // copy all data
        -  vert.isVert = contourVertices[0].isVert;
        -  vert.moveTo = false;
        -  contourVertices.push(vert);
        -
        -  // prevent stray lines with multiple contours
        -  if (isFirstContour) {
        -    vertices.push(vertices[0]);
        -    isFirstContour = false;
        -  }
        -
        -  for (let i = 0; i < contourVertices.length; i++) {
        -    vertices.push(contourVertices[i]);
        -  }
        -  return this;
        -};
        -
        -/**
        - * Begins adding vertices to a custom shape.
        - *
        - * The <a href="#/p5/beginShape">beginShape()</a> and `endShape()` functions
        - * allow for creating custom shapes in 2D or 3D.
        - * <a href="#/p5/beginShape">beginShape()</a> begins adding vertices to a
        - * custom shape and `endShape()` stops adding them.
        - *
        - * The first parameter, `mode`, is optional. By default, the first and last
        - * vertices of a shape aren't connected. If the constant `CLOSE` is passed, as
        - * in `endShape(CLOSE)`, then the first and last vertices will be connected.
        - *
        - * The second parameter, `count`, is also optional. In WebGL mode, it’s more
        - * efficient to draw many copies of the same shape using a technique called
        - * <a href="https://webglfundamentals.org/webgl/lessons/webgl-instanced-drawing.html" target="_blank">instancing</a>.
        - * The `count` parameter tells WebGL mode how many copies to draw. For
        - * example, calling `endShape(CLOSE, 400)` after drawing a custom shape will
        - * make it efficient to draw 400 copies. This feature requires
        - * <a href="https://p5js.org/tutorials/intro-to-shaders/" target="_blank">writing a custom shader</a>.
        - *
        - * After calling <a href="#/p5/beginShape">beginShape()</a>, shapes can be
        - * built by calling <a href="#/p5/vertex">vertex()</a>,
        - * <a href="#/p5/bezierVertex">bezierVertex()</a>,
        - * <a href="#/p5/quadraticVertex">quadraticVertex()</a>, and/or
        - * <a href="#/p5/curveVertex">curveVertex()</a>. Calling
        - * `endShape()` will stop adding vertices to the
        - * shape. Each shape will be outlined with the current stroke color and filled
        - * with the current fill color.
        - *
        - * Transformations such as <a href="#/p5/translate">translate()</a>,
        - * <a href="#/p5/rotate">rotate()</a>, and
        - * <a href="#/p5/scale">scale()</a> don't work between
        - * <a href="#/p5/beginShape">beginShape()</a> and `endShape()`. It's also not
        - * possible to use other shapes, such as <a href="#/p5/ellipse">ellipse()</a> or
        - * <a href="#/p5/rect">rect()</a>, between
        - * <a href="#/p5/beginShape">beginShape()</a> and `endShape()`.
        - *
        - * @method endShape
        - * @param  {Constant} [mode] use CLOSE to close the shape
        - * @param  {Integer} [count] number of times you want to draw/instance the shape (for WebGL mode).
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the shapes.
        - *   noFill();
        - *
        - *   // Left triangle.
        - *   beginShape();
        - *   vertex(20, 20);
        - *   vertex(45, 20);
        - *   vertex(45, 80);
        - *   endShape(CLOSE);
        - *
        - *   // Right triangle.
        - *   beginShape();
        - *   vertex(50, 20);
        - *   vertex(75, 20);
        - *   vertex(75, 80);
        - *   endShape();
        - *
        - *   describe(
        - *     'Two sets of black lines drawn on a gray background. The three lines on the left form a right triangle. The two lines on the right form a right angle.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * // Create a string with the vertex shader program.
        - * // The vertex shader is called for each vertex.
        - * let vertSrc = `#version 300 es
        - *
        - * precision mediump float;
        - *
        - * in vec3 aPosition;
        - * flat out int instanceID;
        - *
        - * uniform mat4 uModelViewMatrix;
        - * uniform mat4 uProjectionMatrix;
        - *
        - * void main() {
        - *
        - *   // Copy the instance ID to the fragment shader.
        - *   instanceID = gl_InstanceID;
        - *   vec4 positionVec4 = vec4(aPosition, 1.0);
        - *
        - *   // gl_InstanceID represents a numeric value for each instance.
        - *   // Using gl_InstanceID allows us to move each instance separately.
        - *   // Here we move each instance horizontally by ID * 23.
        - *   float xOffset = float(gl_InstanceID) * 23.0;
        - *
        - *   // Apply the offset to the final position.
        - *   gl_Position = uProjectionMatrix * uModelViewMatrix * (positionVec4 -
        - *     vec4(xOffset, 0.0, 0.0, 0.0));
        - * }
        - * `;
        - *
        - * // Create a string with the fragment shader program.
        - * // The fragment shader is called for each pixel.
        - * let fragSrc = `#version 300 es
        - *
        - * precision mediump float;
        - *
        - * out vec4 outColor;
        - * flat in int instanceID;
        - * uniform float numInstances;
        - *
        - * void main() {
        - *   vec4 red = vec4(1.0, 0.0, 0.0, 1.0);
        - *   vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);
        - *
        - *   // Normalize the instance ID.
        - *   float normId = float(instanceID) / numInstances;
        - *
        - *   // Mix between two colors using the normalized instance ID.
        - *   outColor = mix(red, blue, normId);
        - * }
        - * `;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Shader object.
        - *   let myShader = createShader(vertSrc, fragSrc);
        - *
        - *   background(220);
        - *
        - *   // Compile and apply the p5.Shader.
        - *   shader(myShader);
        - *
        - *   // Set the numInstances uniform.
        - *   myShader.setUniform('numInstances', 4);
        - *
        - *   // Translate the origin to help align the drawing.
        - *   translate(25, -10);
        - *
        - *   // Style the shapes.
        - *   noStroke();
        - *
        - *   // Draw the shapes.
        - *   beginShape();
        - *   vertex(0, 0);
        - *   vertex(0, 20);
        - *   vertex(20, 20);
        - *   vertex(20, 0);
        - *   vertex(0, 0);
        - *   endShape(CLOSE, 4);
        - *
        - *   describe('A row of four squares. Their colors transition from purple on the left to red on the right');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.endShape = function(mode, count = 1) {
        -  p5._validateParameters('endShape', arguments);
        -  if (count < 1) {
        -    console.log('🌸 p5.js says: You can not have less than one instance');
        -    count = 1;
        -  }
        -
        -  if (this._renderer.isP3D) {
        -    this._renderer.endShape(
        -      mode,
        -      isCurve,
        -      isBezier,
        -      isQuadratic,
        -      isContour,
        -      shapeKind,
        -      count
        -    );
        -  } else {
        -    if (count !== 1) {
        -      console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode');
        -    }
        -    if (vertices.length === 0) {
        -      return this;
        -    }
        -    if (!this._renderer._doStroke && !this._renderer._doFill) {
        -      return this;
        -    }
        -
        -    const closeShape = mode === constants.CLOSE;
        -
        -    // if the shape is closed, the first element is also the last element
        -    if (closeShape && !isContour) {
        -      vertices.push(vertices[0]);
        -    }
        -
        -    this._renderer.endShape(
        -      mode,
        -      vertices,
        -      isCurve,
        -      isBezier,
        -      isQuadratic,
        -      isContour,
        -      shapeKind
        -    );
        -
        -    // Reset some settings
        -    isCurve = false;
        -    isBezier = false;
        -    isQuadratic = false;
        -    isContour = false;
        -    isFirstContour = true;
        -
        -    // If the shape is closed, the first element was added as last element.
        -    // We must remove it again to prevent the list of vertices from growing
        -    // over successive calls to endShape(CLOSE)
        -    if (closeShape) {
        -      vertices.pop();
        -    }
        -  }
        -  return this;
        -};
        -
        -/**
        - * Adds a quadratic Bézier curve segment to a custom shape.
        - *
        - * `quadraticVertex()` adds a curved segment to custom shapes. The Bézier
        - * curve segments it creates are similar to those made by the
        - * <a href="#/p5/bezierVertex">bezierVertex()</a> function.
        - * `quadraticVertex()` must be called between the
        - * <a href="#/p5/beginShape">beginShape()</a> and
        - * <a href="#/p5/endShape">endShape()</a> functions. The curved segment uses
        - * the previous vertex as the first anchor point, so there must be at least
        - * one call to <a href="#/p5/vertex">vertex()</a> before `quadraticVertex()` can
        - * be used.
        - *
        - * The first two parameters, `cx` and `cy`, set the curve’s control point.
        - * The control point "pulls" the curve towards its.
        - *
        - * The last two parameters, `x3`, and `y3`, set the last anchor point. The
        - * last anchor point is where the curve ends.
        - *
        - * Bézier curves can also be drawn in 3D using WebGL mode. The 3D version of
        - * `bezierVertex()` has eight arguments because each point has x-, y-, and
        - * z-coordinates.
        - *
        - * Note: `quadraticVertex()` won’t work when an argument is passed to
        - * <a href="#/p5/beginShape">beginShape()</a>.
        - *
        - * @method quadraticVertex
        - * @param  {Number} cx x-coordinate of the control point.
        - * @param  {Number} cy y-coordinate of the control point.
        - * @param  {Number} x3 x-coordinate of the anchor point.
        - * @param  {Number} y3 y-coordinate of the anchor point.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the curve.
        - *   noFill();
        - *
        - *   // Draw the curve.
        - *   beginShape();
        - *   vertex(20, 20);
        - *   quadraticVertex(80, 20, 50, 50);
        - *   endShape();
        - *
        - *   describe('A black curve drawn on a gray square. The curve starts at the top-left corner and ends at the center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw the curve.
        - *   noFill();
        - *   beginShape();
        - *   vertex(20, 20);
        - *   quadraticVertex(80, 20, 50, 50);
        - *   endShape();
        - *
        - *   // Draw red lines from the anchor points to the control point.
        - *   stroke(255, 0, 0);
        - *   line(20, 20, 80, 20);
        - *   line(50, 50, 80, 20);
        - *
        - *   // Draw the anchor points in black.
        - *   strokeWeight(5);
        - *   stroke(0);
        - *   point(20, 20);
        - *   point(50, 50);
        - *
        - *   // Draw the control point in red.
        - *   stroke(255, 0, 0);
        - *   point(80, 20);
        - *
        - *   describe('A black curve that starts at the top-left corner and ends at the center. Its anchor and control points are marked with dots. Red lines connect both anchor points to the control point.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click the mouse near the red dot in the top-right corner
        - * // and drag to change the curve's shape.
        - *
        - * let x2 = 80;
        - * let y2 = 20;
        - * let isChanging = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A black curve that starts at the top-left corner and ends at the center. Its anchor and control points are marked with dots. Red lines connect both anchor points to the control point.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the curve.
        - *   noFill();
        - *   strokeWeight(1);
        - *   stroke(0);
        - *
        - *   // Draw the curve.
        - *   beginShape();
        - *   vertex(20, 20);
        - *   quadraticVertex(x2, y2, 50, 50);
        - *   endShape();
        - *
        - *   // Draw red lines from the anchor points to the control point.
        - *   stroke(255, 0, 0);
        - *   line(20, 20, x2, y2);
        - *   line(50, 50, x2, y2);
        - *
        - *   // Draw the anchor points in black.
        - *   strokeWeight(5);
        - *   stroke(0);
        - *   point(20, 20);
        - *   point(50, 50);
        - *
        - *   // Draw the control point in red.
        - *   stroke(255, 0, 0);
        - *   point(x2, y2);
        - * }
        - *
        - * // Start changing the first control point if the user clicks near it.
        - * function mousePressed() {
        - *   if (dist(mouseX, mouseY, x2, y2) < 20) {
        - *     isChanging = true;
        - *   }
        - * }
        - *
        - * // Stop changing the first control point when the user releases the mouse.
        - * function mouseReleased() {
        - *   isChanging = false;
        - * }
        - *
        - * // Update the first control point while the user drags the mouse.
        - * function mouseDragged() {
        - *   if (isChanging === true) {
        - *     x2 = mouseX;
        - *     y2 = mouseY;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add the curved segments.
        - *   vertex(20, 20);
        - *   quadraticVertex(80, 20, 50, 50);
        - *   quadraticVertex(20, 80, 80, 80);
        - *
        - *   // Add the straight segments.
        - *   vertex(80, 10);
        - *   vertex(20, 10);
        - *   vertex(20, 20);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('A white puzzle piece drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click the and drag the mouse to view the scene from a different angle.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white puzzle piece on a dark gray background. When the user clicks and drags the scene, the outline of a second puzzle piece is revealed.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the first puzzle piece.
        - *   noStroke();
        - *   fill(255);
        - *
        - *   // Draw the first puzzle piece.
        - *   beginShape();
        - *   vertex(-30, -30, 0);
        - *   quadraticVertex(30, -30, 0, 0, 0, 0);
        - *   quadraticVertex(-30, 30, 0, 30, 30, 0);
        - *   vertex(30, -40, 0);
        - *   vertex(-30, -40, 0);
        - *   vertex(-30, -30, 0);
        - *   endShape();
        - *
        - *   // Style the second puzzle piece.
        - *   stroke(255);
        - *   noFill();
        - *
        - *   // Draw the second puzzle piece.
        - *   beginShape();
        - *   vertex(-30, -30, -20);
        - *   quadraticVertex(30, -30, -20, 0, 0, -20);
        - *   quadraticVertex(-30, 30, -20, 30, 30, -20);
        - *   vertex(30, -40, -20);
        - *   vertex(-30, -40, -20);
        - *   vertex(-30, -30, -20);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method quadraticVertex
        - * @param  {Number} cx
        - * @param  {Number} cy
        - * @param  {Number} cz z-coordinate of the control point.
        - * @param  {Number} x3
        - * @param  {Number} y3
        - * @param  {Number} z3 z-coordinate of the anchor point.
        - */
        -p5.prototype.quadraticVertex = function(...args) {
        -  p5._validateParameters('quadraticVertex', args);
        -  if (this._renderer.isP3D) {
        -    this._renderer.quadraticVertex(...args);
        -  } else {
        -    //if we're drawing a contour, put the points into an
        -    // array for inside drawing
        -    if (this._contourInited) {
        -      const pt = {};
        -      pt.x = args[0];
        -      pt.y = args[1];
        -      pt.x3 = args[2];
        -      pt.y3 = args[3];
        -      pt.type = constants.QUADRATIC;
        -      this._contourVertices.push(pt);
        -
        -      return this;
        -    }
        -    if (vertices.length > 0) {
        -      isQuadratic = true;
        -      const vert = [];
        -      for (let i = 0; i < args.length; i++) {
        -        vert[i] = args[i];
        -      }
        -      vert.isVert = false;
        -      if (isContour) {
        -        contourVertices.push(vert);
        -      } else {
        -        vertices.push(vert);
        -      }
        -    } else {
        -      p5._friendlyError(
        -        'vertex() must be used once before calling quadraticVertex()',
        -        'quadraticVertex'
        -      );
        -    }
        -  }
        -  return this;
        -};
        -
        -/**
        - * Adds a vertex to a custom shape.
        - *
        - * `vertex()` sets the coordinates of vertices drawn between the
        - * <a href="#/p5/beginShape">beginShape()</a> and
        - * <a href="#/p5/endShape">endShape()</a> functions.
        - *
        - * The first two parameters, `x` and `y`, set the x- and y-coordinates of the
        - * vertex.
        - *
        - * The third parameter, `z`, is optional. It sets the z-coordinate of the
        - * vertex in WebGL mode. By default, `z` is 0.
        - *
        - * The fourth and fifth parameters, `u` and `v`, are also optional. They set
        - * the u- and v-coordinates for the vertex’s texture when used with
        - * <a href="#/p5/endShape">endShape()</a>. By default, `u` and `v` are both 0.
        - *
        - * @method vertex
        - * @param  {Number} x x-coordinate of the vertex.
        - * @param  {Number} y y-coordinate of the vertex.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the shape.
        - *   strokeWeight(3);
        - *
        - *   // Start drawing the shape.
        - *   // Only draw the vertices.
        - *   beginShape(POINTS);
        - *
        - *   // Add the vertices.
        - *   vertex(30, 20);
        - *   vertex(85, 20);
        - *   vertex(85, 75);
        - *   vertex(30, 75);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - *
        - *   describe('Four black dots that form a square are drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add vertices.
        - *   vertex(30, 20);
        - *   vertex(85, 20);
        - *   vertex(85, 75);
        - *   vertex(30, 75);
        - *
        - *   // Stop drawing the shape.
        - *   endShape(CLOSE);
        - *
        - *   describe('A white square on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add vertices.
        - *   vertex(-20, -30, 0);
        - *   vertex(35, -30, 0);
        - *   vertex(35, 25, 0);
        - *   vertex(-20, 25, 0);
        - *
        - *   // Stop drawing the shape.
        - *   endShape(CLOSE);
        - *
        - *   describe('A white square on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white square spins around slowly on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Start drawing the shape.
        - *   beginShape();
        - *
        - *   // Add vertices.
        - *   vertex(-20, -30, 0);
        - *   vertex(35, -30, 0);
        - *   vertex(35, 25, 0);
        - *   vertex(-20, 25, 0);
        - *
        - *   // Stop drawing the shape.
        - *   endShape(CLOSE);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load an image to apply as a texture.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A photograph of a ceiling rotates slowly against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Style the shape.
        - *   noStroke();
        - *
        - *   // Apply the texture.
        - *   texture(img);
        - *   textureMode(NORMAL);
        - *
        - *   // Start drawing the shape
        - *   beginShape();
        - *
        - *   // Add vertices.
        - *   vertex(-20, -30, 0, 0, 0);
        - *   vertex(35, -30, 0, 1, 0);
        - *   vertex(35, 25, 0, 1, 1);
        - *   vertex(-20, 25, 0, 0, 1);
        - *
        - *   // Stop drawing the shape.
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method vertex
        - * @param  {Number} x
        - * @param  {Number} y
        - * @param  {Number} [z]   z-coordinate of the vertex. Defaults to 0.
        - * @chainable
        - */
        -/**
        - * @method vertex
        - * @param  {Number} x
        - * @param  {Number} y
        - * @param  {Number} [z]
        - * @param  {Number} [u]   u-coordinate of the vertex's texture. Defaults to 0.
        - * @param  {Number} [v]   v-coordinate of the vertex's texture. Defaults to 0.
        - * @chainable
        - */
        -p5.prototype.vertex = function(x, y, moveTo, u, v) {
        -  if (this._renderer.isP3D) {
        -    this._renderer.vertex(...arguments);
        -  } else {
        -    const vert = [];
        -    vert.isVert = true;
        -    vert[0] = x;
        -    vert[1] = y;
        -    vert[2] = 0;
        -    vert[3] = 0;
        -    vert[4] = 0;
        -    vert[5] = this._renderer._getFill();
        -    vert[6] = this._renderer._getStroke();
        -
        -    if (moveTo) {
        -      vert.moveTo = moveTo;
        -    }
        -    if (isContour) {
        -      if (contourVertices.length === 0) {
        -        vert.moveTo = true;
        -      }
        -      contourVertices.push(vert);
        -    } else {
        -      vertices.push(vert);
        -    }
        -  }
        -  return this;
        -};
        -
        -/**
        - * Sets the normal vector for vertices in a custom 3D shape.
        - *
        - * 3D shapes created with <a href="#/p5/beginShape">beginShape()</a> and
        - * <a href="#/p5/endShape">endShape()</a> are made by connecting sets of
        - * points called vertices. Each vertex added with
        - * <a href="#/p5/vertex">vertex()</a> has a normal vector that points away
        - * from it. The normal vector controls how light reflects off the shape.
        - *
        - * `normal()` can be called two ways with different parameters to define the
        - * normal vector's components.
        - *
        - * The first way to call `normal()` has three parameters, `x`, `y`, and `z`.
        - * If `Number`s are passed, as in `normal(1, 2, 3)`, they set the x-, y-, and
        - * z-components of the normal vector.
        - *
        - * The second way to call `normal()` has one parameter, `vector`. If a
        - * <a href="#/p5.Vector">p5.Vector</a> object is passed, as in
        - * `normal(myVector)`, its components will be used to set the normal vector.
        - *
        - * `normal()` changes the normal vector of vertices added to a custom shape
        - * with <a href="#/p5/vertex">vertex()</a>. `normal()` must be called between
        - * the <a href="#/p5/beginShape">beginShape()</a> and
        - * <a href="#/p5/endShape">endShape()</a> functions, just like
        - * <a href="#/p5/vertex">vertex()</a>. The normal vector set by calling
        - * `normal()` will affect all following vertices until `normal()` is called
        - * again:
        - *
        - * ```js
        - * beginShape();
        - *
        - * // Set the vertex normal.
        - * normal(-0.4, -0.4, 0.8);
        - *
        - * // Add a vertex.
        - * vertex(-30, -30, 0);
        - *
        - * // Set the vertex normal.
        - * normal(0, 0, 1);
        - *
        - * // Add vertices.
        - * vertex(30, -30, 0);
        - * vertex(30, 30, 0);
        - *
        - * // Set the vertex normal.
        - * normal(0.4, -0.4, 0.8);
        - *
        - * // Add a vertex.
        - * vertex(-30, 30, 0);
        - *
        - * endShape();
        - * ```
        - *
        - * @method normal
        - * @param  {p5.Vector} vector vertex normal as a <a href="#/p5.Vector">p5.Vector</a> object.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click the and drag the mouse to view the scene from a different angle.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'A colorful square on a black background. The square changes color and rotates when the user drags the mouse. Parts of its surface reflect light in different directions.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the shape.
        - *   normalMaterial();
        - *   noStroke();
        - *
        - *   // Draw the shape.
        - *   beginShape();
        - *   vertex(-30, -30, 0);
        - *   vertex(30, -30, 0);
        - *   vertex(30, 30, 0);
        - *   vertex(-30, 30, 0);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click the and drag the mouse to view the scene from a different angle.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'A colorful square on a black background. The square changes color and rotates when the user drags the mouse. Parts of its surface reflect light in different directions.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the shape.
        - *   normalMaterial();
        - *   noStroke();
        - *
        - *   // Draw the shape.
        - *   // Use normal() to set vertex normals.
        - *   beginShape();
        - *   normal(-0.4, -0.4, 0.8);
        - *   vertex(-30, -30, 0);
        - *
        - *   normal(0, 0, 1);
        - *   vertex(30, -30, 0);
        - *   vertex(30, 30, 0);
        - *
        - *   normal(0.4, -0.4, 0.8);
        - *   vertex(-30, 30, 0);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='notest'>
        - * <code>
        - * // Click the and drag the mouse to view the scene from a different angle.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'A colorful square on a black background. The square changes color and rotates when the user drags the mouse. Parts of its surface reflect light in different directions.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the shape.
        - *   normalMaterial();
        - *   noStroke();
        - *
        - *   // Create p5.Vector objects.
        - *   let n1 = createVector(-0.4, -0.4, 0.8);
        - *   let n2 = createVector(0, 0, 1);
        - *   let n3 = createVector(0.4, -0.4, 0.8);
        - *
        - *   // Draw the shape.
        - *   // Use normal() to set vertex normals.
        - *   beginShape();
        - *   normal(n1);
        - *   vertex(-30, -30, 0);
        - *
        - *   normal(n2);
        - *   vertex(30, -30, 0);
        - *   vertex(30, 30, 0);
        - *
        - *   normal(n3);
        - *   vertex(-30, 30, 0);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method normal
        - * @param  {Number} x x-component of the vertex normal.
        - * @param  {Number} y y-component of the vertex normal.
        - * @param  {Number} z z-component of the vertex normal.
        - * @chainable
        - */
        -p5.prototype.normal = function(x, y, z) {
        -  this._assert3d('normal');
        -  p5._validateParameters('normal', arguments);
        -  this._renderer.normal(...arguments);
        -
        -  return this;
        -};
        -
        -export default p5;
        diff --git a/src/core/shim.js b/src/core/shim.js
        deleted file mode 100644
        index e69de29bb2..0000000000
        diff --git a/src/core/structure.js b/src/core/structure.js
        index 309f4380c5..e675cd0517 100644
        --- a/src/core/structure.js
        +++ b/src/core/structure.js
        @@ -5,1145 +5,582 @@
          * @requires core
          */
         
        -import p5 from './main';
        -/**
        - * Stops the code in <a href="#/p5/draw">draw()</a> from running repeatedly.
        - *
        - * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
        - * second. Calling `noLoop()` stops <a href="#/p5/draw">draw()</a> from
        - * repeating. The draw loop can be restarted by calling
        - * <a href="#/p5/loop">loop()</a>. <a href="#/p5/draw">draw()</a> can be run
        - * once by calling <a href="#/p5/redraw">redraw()</a>.
        - *
        - * The <a href="#/p5/isLooping">isLooping()</a> function can be used to check
        - * whether a sketch is looping, as in `isLooping() === true`.
        - *
        - * @method noLoop
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Turn off the draw loop.
        - *   noLoop();
        - *
        - *   describe('A white half-circle on the left edge of a gray square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the circle's x-coordinate.
        - *   let x = frameCount;
        - *
        - *   // Draw the circle.
        - *   // Normally, the circle would move from left to right.
        - *   circle(x, 50, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Double-click to stop the draw loop.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(5);
        - *
        - *   describe('A white circle moves randomly on a gray background. It stops moving when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the circle's coordinates.
        - *   let x = random(0, 100);
        - *   let y = random(0, 100);
        - *
        - *   // Draw the circle.
        - *   // Normally, the circle would move from left to right.
        - *   circle(x, y, 20);
        - * }
        - *
        - * // Stop the draw loop when the user double-clicks.
        - * function doubleClicked() {
        - *   noLoop();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let startButton;
        - * let stopButton;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create the button elements and place them
        - *   // beneath the canvas.
        - *   startButton = createButton('▶');
        - *   startButton.position(0, 100);
        - *   startButton.size(50, 20);
        - *   stopButton = createButton('◾');
        - *   stopButton.position(50, 100);
        - *   stopButton.size(50, 20);
        - *
        - *   // Set functions to call when the buttons are pressed.
        - *   startButton.mousePressed(loop);
        - *   stopButton.mousePressed(noLoop);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(5);
        - *
        - *   describe(
        - *     'A white circle moves randomly on a gray background. Play and stop buttons are shown beneath the canvas. The circle stops or starts moving when the user presses a button.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the circle's coordinates.
        - *   let x = random(0, 100);
        - *   let y = random(0, 100);
        - *
        - *   // Draw the circle.
        - *   // Normally, the circle would move from left to right.
        - *   circle(x, y, 20);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noLoop = function() {
        -  this._loop = false;
        -};
        +function structure(p5, fn){
        +  /**
        +   * Stops the code in <a href="#/p5/draw">draw()</a> from running repeatedly.
        +   *
        +   * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
        +   * second. Calling `noLoop()` stops <a href="#/p5/draw">draw()</a> from
        +   * repeating. The draw loop can be restarted by calling
        +   * <a href="#/p5/loop">loop()</a>. <a href="#/p5/draw">draw()</a> can be run
        +   * once by calling <a href="#/p5/redraw">redraw()</a>.
        +   *
        +   * The <a href="#/p5/isLooping">isLooping()</a> function can be used to check
        +   * whether a sketch is looping, as in `isLooping() === true`.
        +   *
        +   * @method noLoop
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Turn off the draw loop.
        +   *   noLoop();
        +   *
        +   *   describe('A white half-circle on the left edge of a gray square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the circle's x-coordinate.
        +   *   let x = frameCount;
        +   *
        +   *   // Draw the circle.
        +   *   // Normally, the circle would move from left to right.
        +   *   circle(x, 50, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Double-click to stop the draw loop.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(5);
        +   *
        +   *   describe('A white circle moves randomly on a gray background. It stops moving when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the circle's coordinates.
        +   *   let x = random(0, 100);
        +   *   let y = random(0, 100);
        +   *
        +   *   // Draw the circle.
        +   *   // Normally, the circle would move from left to right.
        +   *   circle(x, y, 20);
        +   * }
        +   *
        +   * // Stop the draw loop when the user double-clicks.
        +   * function doubleClicked() {
        +   *   noLoop();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let startButton;
        +   * let stopButton;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create the button elements and place them
        +   *   // beneath the canvas.
        +   *   startButton = createButton('▶');
        +   *   startButton.position(0, 100);
        +   *   startButton.size(50, 20);
        +   *   stopButton = createButton('◾');
        +   *   stopButton.position(50, 100);
        +   *   stopButton.size(50, 20);
        +   *
        +   *   // Set functions to call when the buttons are pressed.
        +   *   startButton.mousePressed(loop);
        +   *   stopButton.mousePressed(noLoop);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(5);
        +   *
        +   *   describe(
        +   *     'A white circle moves randomly on a gray background. Play and stop buttons are shown beneath the canvas. The circle stops or starts moving when the user presses a button.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the circle's coordinates.
        +   *   let x = random(0, 100);
        +   *   let y = random(0, 100);
        +   *
        +   *   // Draw the circle.
        +   *   // Normally, the circle would move from left to right.
        +   *   circle(x, y, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noLoop = function() {
        +    this._loop = false;
        +  };
         
        -/**
        - * Resumes the draw loop after <a href="#/p5/noLoop">noLoop()</a> has been
        - * called.
        - *
        - * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
        - * second. Calling <a href="#/p5/noLoop">noLoop()</a> stops
        - * <a href="#/p5/draw">draw()</a> from repeating. The draw loop can be
        - * restarted by calling `loop()`.
        - *
        - * The <a href="#/p5/isLooping">isLooping()</a> function can be used to check
        - * whether a sketch is looping, as in `isLooping() === true`.
        - *
        - * @method loop
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Turn off the draw loop.
        - *   noLoop();
        - *
        - *   describe(
        - *     'A white half-circle on the left edge of a gray square. The circle starts moving to the right when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the circle's x-coordinate.
        - *   let x = frameCount;
        - *
        - *   // Draw the circle.
        - *   circle(x, 50, 20);
        - * }
        - *
        - * // Resume the draw loop when the user double-clicks.
        - * function doubleClicked() {
        - *   loop();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let startButton;
        - * let stopButton;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create the button elements and place them
        - *   // beneath the canvas.
        - *   startButton = createButton('▶');
        - *   startButton.position(0, 100);
        - *   startButton.size(50, 20);
        - *   stopButton = createButton('◾');
        - *   stopButton.position(50, 100);
        - *   stopButton.size(50, 20);
        - *
        - *   // Set functions to call when the buttons are pressed.
        - *   startButton.mousePressed(loop);
        - *   stopButton.mousePressed(noLoop);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(5);
        - *
        - *   describe(
        - *     'A white circle moves randomly on a gray background. Play and stop buttons are shown beneath the canvas. The circle stops or starts moving when the user presses a button.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the circle's coordinates.
        - *   let x = random(0, 100);
        - *   let y = random(0, 100);
        - *
        - *   // Draw the circle.
        - *   // Normally, the circle would move from left to right.
        - *   circle(x, y, 20);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.loop = function() {
        -  if (!this._loop) {
        -    this._loop = true;
        -    if (this._setupDone) {
        -      this._draw();
        +  /**
        +   * Resumes the draw loop after <a href="#/p5/noLoop">noLoop()</a> has been
        +   * called.
        +   *
        +   * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
        +   * second. Calling <a href="#/p5/noLoop">noLoop()</a> stops
        +   * <a href="#/p5/draw">draw()</a> from repeating. The draw loop can be
        +   * restarted by calling `loop()`.
        +   *
        +   * The <a href="#/p5/isLooping">isLooping()</a> function can be used to check
        +   * whether a sketch is looping, as in `isLooping() === true`.
        +   *
        +   * @method loop
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Turn off the draw loop.
        +   *   noLoop();
        +   *
        +   *   describe(
        +   *     'A white half-circle on the left edge of a gray square. The circle starts moving to the right when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the circle's x-coordinate.
        +   *   let x = frameCount;
        +   *
        +   *   // Draw the circle.
        +   *   circle(x, 50, 20);
        +   * }
        +   *
        +   * // Resume the draw loop when the user double-clicks.
        +   * function doubleClicked() {
        +   *   loop();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let startButton;
        +   * let stopButton;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create the button elements and place them
        +   *   // beneath the canvas.
        +   *   startButton = createButton('▶');
        +   *   startButton.position(0, 100);
        +   *   startButton.size(50, 20);
        +   *   stopButton = createButton('◾');
        +   *   stopButton.position(50, 100);
        +   *   stopButton.size(50, 20);
        +   *
        +   *   // Set functions to call when the buttons are pressed.
        +   *   startButton.mousePressed(loop);
        +   *   stopButton.mousePressed(noLoop);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(5);
        +   *
        +   *   describe(
        +   *     'A white circle moves randomly on a gray background. Play and stop buttons are shown beneath the canvas. The circle stops or starts moving when the user presses a button.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the circle's coordinates.
        +   *   let x = random(0, 100);
        +   *   let y = random(0, 100);
        +   *
        +   *   // Draw the circle.
        +   *   // Normally, the circle would move from left to right.
        +   *   circle(x, y, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.loop = function() {
        +    if (!this._loop) {
        +      this._loop = true;
        +      if (this._setupDone) {
        +        this._draw();
        +      }
             }
        -  }
        -};
        -
        -/**
        - * Returns `true` if the draw loop is running and `false` if not.
        - *
        - * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
        - * second. Calling <a href="#/p5/noLoop">noLoop()</a> stops
        - * <a href="#/p5/draw">draw()</a> from repeating. The draw loop can be
        - * restarted by calling <a href="#/p5/loop">loop()</a>.
        - *
        - * The `isLooping()` function can be used to check whether a sketch is
        - * looping, as in `isLooping() === true`.
        - *
        - * @method isLooping
        - * @returns {boolean}
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white circle drawn against a gray background. When the user double-clicks, the circle stops or resumes following the mouse.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the circle at the mouse's position.
        - *   circle(mouseX, mouseY, 20);
        - * }
        - *
        - * // Toggle the draw loop when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isLooping() === true) {
        - *     noLoop();
        - *   } else {
        - *     loop();
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.isLooping = function() {
        -  return this._loop;
        -};
        +  };
         
        -/**
        - * Begins a drawing group that contains its own styles and transformations.
        - *
        - * By default, styles such as <a href="#/p5/fill">fill()</a> and
        - * transformations such as <a href="#/p5/rotate">rotate()</a> are applied to
        - * all drawing that follows. The `push()` and <a href="#/p5/pop">pop()</a>
        - * functions can limit the effect of styles and transformations to a specific
        - * group of shapes, images, and text. For example, a group of shapes could be
        - * translated to follow the mouse without affecting the rest of the sketch:
        - *
        - * ```js
        - * // Begin the drawing group.
        - * push();
        - *
        - * // Translate the origin to the mouse's position.
        - * translate(mouseX, mouseY);
        - *
        - * // Style the face.
        - * noStroke();
        - * fill('green');
        - *
        - * // Draw the face.
        - * circle(0, 0, 60);
        - *
        - * // Style the eyes.
        - * fill('white');
        - *
        - * // Draw the left eye.
        - * ellipse(-20, -20, 30, 20);
        - *
        - * // Draw the right eye.
        - * ellipse(20, -20, 30, 20);
        - *
        - * // End the drawing group.
        - * pop();
        - *
        - * // Draw a bug.
        - * let x = random(0, 100);
        - * let y = random(0, 100);
        - * text('🦟', x, y);
        - * ```
        - *
        - * In the code snippet above, the bug's position isn't affected by
        - * `translate(mouseX, mouseY)` because that transformation is contained
        - * between `push()` and <a href="#/p5/pop">pop()</a>. The bug moves around
        - * the entire canvas as expected.
        - *
        - * Note: `push()` and <a href="#/p5/pop">pop()</a> are always called as a
        - * pair. Both functions are required to begin and end a drawing group.
        - *
        - * `push()` and <a href="#/p5/pop">pop()</a> can also be nested to create
        - * subgroups. For example, the code snippet above could be changed to give
        - * more detail to the frog’s eyes:
        - *
        - * ```js
        - * // Begin the drawing group.
        - * push();
        - *
        - * // Translate the origin to the mouse's position.
        - * translate(mouseX, mouseY);
        - *
        - * // Style the face.
        - * noStroke();
        - * fill('green');
        - *
        - * // Draw a face.
        - * circle(0, 0, 60);
        - *
        - * // Style the eyes.
        - * fill('white');
        - *
        - * // Draw the left eye.
        - * push();
        - * translate(-20, -20);
        - * ellipse(0, 0, 30, 20);
        - * fill('black');
        - * circle(0, 0, 8);
        - * pop();
        - *
        - * // Draw the right eye.
        - * push();
        - * translate(20, -20);
        - * ellipse(0, 0, 30, 20);
        - * fill('black');
        - * circle(0, 0, 8);
        - * pop();
        - *
        - * // End the drawing group.
        - * pop();
        - *
        - * // Draw a bug.
        - * let x = random(0, 100);
        - * let y = random(0, 100);
        - * text('🦟', x, y);
        - * ```
        - *
        - * In this version, the code to draw each eye is contained between its own
        - * `push()` and <a href="#/p5/pop">pop()</a> functions. Doing so makes it
        - * easier to add details in the correct part of a drawing.
        - *
        - * `push()` and <a href="#/p5/pop">pop()</a> contain the effects of the
        - * following functions:
        - *
        - * - <a href="#/p5/fill">fill()</a>
        - * - <a href="#/p5/noFill">noFill()</a>
        - * - <a href="#/p5/noStroke">noStroke()</a>
        - * - <a href="#/p5/stroke">stroke()</a>
        - * - <a href="#/p5/tint">tint()</a>
        - * - <a href="#/p5/noTint">noTint()</a>
        - * - <a href="#/p5/strokeWeight">strokeWeight()</a>
        - * - <a href="#/p5/strokeCap">strokeCap()</a>
        - * - <a href="#/p5/strokeJoin">strokeJoin()</a>
        - * - <a href="#/p5/imageMode">imageMode()</a>
        - * - <a href="#/p5/rectMode">rectMode()</a>
        - * - <a href="#/p5/ellipseMode">ellipseMode()</a>
        - * - <a href="#/p5/colorMode">colorMode()</a>
        - * - <a href="#/p5/textAlign">textAlign()</a>
        - * - <a href="#/p5/textFont">textFont()</a>
        - * - <a href="#/p5/textSize">textSize()</a>
        - * - <a href="#/p5/textLeading">textLeading()</a>
        - * - <a href="#/p5/applyMatrix">applyMatrix()</a>
        - * - <a href="#/p5/resetMatrix">resetMatrix()</a>
        - * - <a href="#/p5/rotate">rotate()</a>
        - * - <a href="#/p5/scale">scale()</a>
        - * - <a href="#/p5/shearX">shearX()</a>
        - * - <a href="#/p5/shearY">shearY()</a>
        - * - <a href="#/p5/translate">translate()</a>
        - *
        - * In WebGL mode, `push()` and <a href="#/p5/pop">pop()</a> contain the
        - * effects of a few additional styles:
        - *
        - * - <a href="#/p5/setCamera">setCamera()</a>
        - * - <a href="#/p5/ambientLight">ambientLight()</a>
        - * - <a href="#/p5/directionalLight">directionalLight()</a>
        - * - <a href="#/p5/pointLight">pointLight()</a> <a href="#/p5/texture">texture()</a>
        - * - <a href="#/p5/specularMaterial">specularMaterial()</a>
        - * - <a href="#/p5/shininess">shininess()</a>
        - * - <a href="#/p5/normalMaterial">normalMaterial()</a>
        - * - <a href="#/p5/shader">shader()</a>
        - *
        - * @method push
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw the left circle.
        - *   circle(25, 50, 20);
        - *
        - *   // Begin the drawing group.
        - *   push();
        - *
        - *   // Translate the origin to the center.
        - *   translate(50, 50);
        - *
        - *   // Style the circle.
        - *   strokeWeight(5);
        - *   stroke('royalblue');
        - *   fill('orange');
        - *
        - *   // Draw the circle.
        - *   circle(0, 0, 20);
        - *
        - *   // End the drawing group.
        - *   pop();
        - *
        - *   // Draw the right circle.
        - *   circle(75, 50, 20);
        - *
        - *   describe(
        - *     'Three circles drawn in a row on a gray background. The left and right circles are white with thin, black borders. The middle circle is orange with a thick, blue border.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(24);
        - *
        - *   describe('A mosquito buzzes in front of a green frog. The frog follows the mouse as the user moves.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Begin the drawing group.
        - *   push();
        - *
        - *   // Translate the origin to the mouse's position.
        - *   translate(mouseX, mouseY);
        - *
        - *   // Style the face.
        - *   noStroke();
        - *   fill('green');
        - *
        - *   // Draw a face.
        - *   circle(0, 0, 60);
        - *
        - *   // Style the eyes.
        - *   fill('white');
        - *
        - *   // Draw the left eye.
        - *   push();
        - *   translate(-20, -20);
        - *   ellipse(0, 0, 30, 20);
        - *   fill('black');
        - *   circle(0, 0, 8);
        - *   pop();
        - *
        - *   // Draw the right eye.
        - *   push();
        - *   translate(20, -20);
        - *   ellipse(0, 0, 30, 20);
        - *   fill('black');
        - *   circle(0, 0, 8);
        - *   pop();
        - *
        - *   // End the drawing group.
        - *   pop();
        - *
        - *   // Draw a bug.
        - *   let x = random(0, 100);
        - *   let y = random(0, 100);
        - *   text('🦟', x, y);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'Two spheres drawn on a gray background. The sphere on the left is red and lit from the front. The sphere on the right is a blue wireframe.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the red sphere.
        - *   push();
        - *   translate(-25, 0, 0);
        - *   noStroke();
        - *   directionalLight(255, 0, 0, 0, 0, -1);
        - *   sphere(20);
        - *   pop();
        - *
        - *   // Draw the blue sphere.
        - *   push();
        - *   translate(25, 0, 0);
        - *   strokeWeight(0.3);
        - *   stroke(0, 0, 255);
        - *   noFill();
        - *   sphere(20);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.push = function() {
        -  this._styles.push({
        -    props: {
        -      _colorMode: this._colorMode
        -    },
        -    renderer: this._renderer.push()
        -  });
        -};
        +  /**
        +   * Returns `true` if the draw loop is running and `false` if not.
        +   *
        +   * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
        +   * second. Calling <a href="#/p5/noLoop">noLoop()</a> stops
        +   * <a href="#/p5/draw">draw()</a> from repeating. The draw loop can be
        +   * restarted by calling <a href="#/p5/loop">loop()</a>.
        +   *
        +   * The `isLooping()` function can be used to check whether a sketch is
        +   * looping, as in `isLooping() === true`.
        +   *
        +   * @method isLooping
        +   * @returns {boolean}
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white circle drawn against a gray background. When the user double-clicks, the circle stops or resumes following the mouse.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the circle at the mouse's position.
        +   *   circle(mouseX, mouseY, 20);
        +   * }
        +   *
        +   * // Toggle the draw loop when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isLooping() === true) {
        +   *     noLoop();
        +   *   } else {
        +   *     loop();
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.isLooping = function() {
        +    return this._loop;
        +  };
         
        -/**
        - * Ends a drawing group that contains its own styles and transformations.
        - *
        - * By default, styles such as <a href="#/p5/fill">fill()</a> and
        - * transformations such as <a href="#/p5/rotate">rotate()</a> are applied to
        - * all drawing that follows. The <a href="#/p5/push">push()</a> and `pop()`
        - * functions can limit the effect of styles and transformations to a specific
        - * group of shapes, images, and text. For example, a group of shapes could be
        - * translated to follow the mouse without affecting the rest of the sketch:
        - *
        - * ```js
        - * // Begin the drawing group.
        - * push();
        - *
        - * // Translate the origin to the mouse's position.
        - * translate(mouseX, mouseY);
        - *
        - * // Style the face.
        - * noStroke();
        - * fill('green');
        - *
        - * // Draw the face.
        - * circle(0, 0, 60);
        - *
        - * // Style the eyes.
        - * fill('white');
        - *
        - * // Draw the left eye.
        - * ellipse(-20, -20, 30, 20);
        - *
        - * // Draw the right eye.
        - * ellipse(20, -20, 30, 20);
        - *
        - * // End the drawing group.
        - * pop();
        - *
        - * // Draw a bug.
        - * let x = random(0, 100);
        - * let y = random(0, 100);
        - * text('🦟', x, y);
        - * ```
        - *
        - * In the code snippet above, the bug's position isn't affected by
        - * `translate(mouseX, mouseY)` because that transformation is contained
        - * between <a href="#/p5/push">push()</a> and `pop()`. The bug moves around
        - * the entire canvas as expected.
        - *
        - * Note: <a href="#/p5/push">push()</a> and `pop()` are always called as a
        - * pair. Both functions are required to begin and end a drawing group.
        - *
        - * <a href="#/p5/push">push()</a> and `pop()` can also be nested to create
        - * subgroups. For example, the code snippet above could be changed to give
        - * more detail to the frog’s eyes:
        - *
        - * ```js
        - * // Begin the drawing group.
        - * push();
        - *
        - * // Translate the origin to the mouse's position.
        - * translate(mouseX, mouseY);
        - *
        - * // Style the face.
        - * noStroke();
        - * fill('green');
        - *
        - * // Draw a face.
        - * circle(0, 0, 60);
        - *
        - * // Style the eyes.
        - * fill('white');
        - *
        - * // Draw the left eye.
        - * push();
        - * translate(-20, -20);
        - * ellipse(0, 0, 30, 20);
        - * fill('black');
        - * circle(0, 0, 8);
        - * pop();
        - *
        - * // Draw the right eye.
        - * push();
        - * translate(20, -20);
        - * ellipse(0, 0, 30, 20);
        - * fill('black');
        - * circle(0, 0, 8);
        - * pop();
        - *
        - * // End the drawing group.
        - * pop();
        - *
        - * // Draw a bug.
        - * let x = random(0, 100);
        - * let y = random(0, 100);
        - * text('🦟', x, y);
        - * ```
        - *
        - * In this version, the code to draw each eye is contained between its own
        - * <a href="#/p5/push">push()</a> and `pop()` functions. Doing so makes it
        - * easier to add details in the correct part of a drawing.
        - *
        - * <a href="#/p5/push">push()</a> and `pop()` contain the effects of the
        - * following functions:
        - *
        - * - <a href="#/p5/fill">fill()</a>
        - * - <a href="#/p5/noFill">noFill()</a>
        - * - <a href="#/p5/noStroke">noStroke()</a>
        - * - <a href="#/p5/stroke">stroke()</a>
        - * - <a href="#/p5/tint">tint()</a>
        - * - <a href="#/p5/noTint">noTint()</a>
        - * - <a href="#/p5/strokeWeight">strokeWeight()</a>
        - * - <a href="#/p5/strokeCap">strokeCap()</a>
        - * - <a href="#/p5/strokeJoin">strokeJoin()</a>
        - * - <a href="#/p5/imageMode">imageMode()</a>
        - * - <a href="#/p5/rectMode">rectMode()</a>
        - * - <a href="#/p5/ellipseMode">ellipseMode()</a>
        - * - <a href="#/p5/colorMode">colorMode()</a>
        - * - <a href="#/p5/textAlign">textAlign()</a>
        - * - <a href="#/p5/textFont">textFont()</a>
        - * - <a href="#/p5/textSize">textSize()</a>
        - * - <a href="#/p5/textLeading">textLeading()</a>
        - * - <a href="#/p5/applyMatrix">applyMatrix()</a>
        - * - <a href="#/p5/resetMatrix">resetMatrix()</a>
        - * - <a href="#/p5/rotate">rotate()</a>
        - * - <a href="#/p5/scale">scale()</a>
        - * - <a href="#/p5/shearX">shearX()</a>
        - * - <a href="#/p5/shearY">shearY()</a>
        - * - <a href="#/p5/translate">translate()</a>
        - *
        - * In WebGL mode, <a href="#/p5/push">push()</a> and `pop()` contain the
        - * effects of a few additional styles:
        - *
        - * - <a href="#/p5/setCamera">setCamera()</a>
        - * - <a href="#/p5/ambientLight">ambientLight()</a>
        - * - <a href="#/p5/directionalLight">directionalLight()</a>
        - * - <a href="#/p5/pointLight">pointLight()</a> <a href="#/p5/texture">texture()</a>
        - * - <a href="#/p5/specularMaterial">specularMaterial()</a>
        - * - <a href="#/p5/shininess">shininess()</a>
        - * - <a href="#/p5/normalMaterial">normalMaterial()</a>
        - * - <a href="#/p5/shader">shader()</a>
        - *
        - * @method pop
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw the left circle.
        - *   circle(25, 50, 20);
        - *
        - *   // Begin the drawing group.
        - *   push();
        - *
        - *   // Translate the origin to the center.
        - *   translate(50, 50);
        - *
        - *   // Style the circle.
        - *   strokeWeight(5);
        - *   stroke('royalblue');
        - *   fill('orange');
        - *
        - *   // Draw the circle.
        - *   circle(0, 0, 20);
        - *
        - *   // End the drawing group.
        - *   pop();
        - *
        - *   // Draw the right circle.
        - *   circle(75, 50, 20);
        - *
        - *   describe(
        - *     'Three circles drawn in a row on a gray background. The left and right circles are white with thin, black borders. The middle circle is orange with a thick, blue border.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(24);
        - *
        - *   describe('A mosquito buzzes in front of a green frog. The frog follows the mouse as the user moves.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Begin the drawing group.
        - *   push();
        - *
        - *   // Translate the origin to the mouse's position.
        - *   translate(mouseX, mouseY);
        - *
        - *   // Style the face.
        - *   noStroke();
        - *   fill('green');
        - *
        - *   // Draw a face.
        - *   circle(0, 0, 60);
        - *
        - *   // Style the eyes.
        - *   fill('white');
        - *
        - *   // Draw the left eye.
        - *   push();
        - *   translate(-20, -20);
        - *   ellipse(0, 0, 30, 20);
        - *   fill('black');
        - *   circle(0, 0, 8);
        - *   pop();
        - *
        - *   // Draw the right eye.
        - *   push();
        - *   translate(20, -20);
        - *   ellipse(0, 0, 30, 20);
        - *   fill('black');
        - *   circle(0, 0, 8);
        - *   pop();
        - *
        - *   // End the drawing group.
        - *   pop();
        - *
        - *   // Draw a bug.
        - *   let x = random(0, 100);
        - *   let y = random(0, 100);
        - *   text('🦟', x, y);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'Two spheres drawn on a gray background. The sphere on the left is red and lit from the front. The sphere on the right is a blue wireframe.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the red sphere.
        - *   push();
        - *   translate(-25, 0, 0);
        - *   noStroke();
        - *   directionalLight(255, 0, 0, 0, 0, -1);
        - *   sphere(20);
        - *   pop();
        - *
        - *   // Draw the blue sphere.
        - *   push();
        - *   translate(25, 0, 0);
        - *   strokeWeight(0.3);
        - *   stroke(0, 0, 255);
        - *   noFill();
        - *   sphere(20);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.pop = function() {
        -  const style = this._styles.pop();
        -  if (style) {
        -    this._renderer.pop(style.renderer);
        -    Object.assign(this, style.props);
        -  } else {
        -    console.warn('pop() was called without matching push()');
        -  }
        -};
        -
        -/**
        - * Runs the code in <a href="#/p5/draw">draw()</a> once.
        - *
        - * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
        - * second. Calling <a href="#/p5/noLoop">noLoop()</a> stops
        - * <a href="#/p5/draw">draw()</a> from repeating. Calling `redraw()` will
        - * execute the code in the <a href="#/p5/draw">draw()</a> function a set
        - * number of times.
        - *
        - * The parameter, `n`, is optional. If a number is passed, as in `redraw(5)`,
        - * then the draw loop will run the given number of times. By default, `n` is
        - * 1.
        - *
        - * @method redraw
        - * @param  {Integer} [n] number of times to run <a href="#/p5/draw">draw()</a>. Defaults to 1.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click the canvas to move the circle.
        - *
        - * let x = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Turn off the draw loop.
        - *   noLoop();
        - *
        - *   describe(
        - *     'A white half-circle on the left edge of a gray square. The circle moves a little to the right when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the circle.
        - *   circle(x, 50, 20);
        - *
        - *   // Increment x.
        - *   x += 5;
        - * }
        - *
        - * // Run the draw loop when the user double-clicks.
        - * function doubleClicked() {
        - *   redraw();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Double-click the canvas to move the circle.
        - *
        - * let x = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Turn off the draw loop.
        - *   noLoop();
        - *
        - *   describe(
        - *     'A white half-circle on the left edge of a gray square. The circle hops to the right when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the circle.
        - *   circle(x, 50, 20);
        - *
        - *   // Increment x.
        - *   x += 5;
        - * }
        - *
        - * // Run the draw loop three times when the user double-clicks.
        - * function doubleClicked() {
        - *   redraw(3);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.redraw = function(n) {
        -  if (this._inUserDraw || !this._setupDone) {
        -    return;
        -  }
        -
        -  let numberOfRedraws = parseInt(n);
        -  if (isNaN(numberOfRedraws) || numberOfRedraws < 1) {
        -    numberOfRedraws = 1;
        -  }
        +  /**
        +   * Runs the code in <a href="#/p5/draw">draw()</a> once.
        +   *
        +   * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
        +   * second. Calling <a href="#/p5/noLoop">noLoop()</a> stops
        +   * <a href="#/p5/draw">draw()</a> from repeating. Calling `redraw()` will
        +   * execute the code in the <a href="#/p5/draw">draw()</a> function a set
        +   * number of times.
        +   *
        +   * The parameter, `n`, is optional. If a number is passed, as in `redraw(5)`,
        +   * then the draw loop will run the given number of times. By default, `n` is
        +   * 1.
        +   *
        +   * @method redraw
        +   * @param  {Integer} [n] number of times to run <a href="#/p5/draw">draw()</a>. Defaults to 1.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click the canvas to move the circle.
        +   *
        +   * let x = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Turn off the draw loop.
        +   *   noLoop();
        +   *
        +   *   describe(
        +   *     'A white half-circle on the left edge of a gray square. The circle moves a little to the right when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the circle.
        +   *   circle(x, 50, 20);
        +   *
        +   *   // Increment x.
        +   *   x += 5;
        +   * }
        +   *
        +   * // Run the draw loop when the user double-clicks.
        +   * function doubleClicked() {
        +   *   redraw();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Double-click the canvas to move the circle.
        +   *
        +   * let x = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Turn off the draw loop.
        +   *   noLoop();
        +   *
        +   *   describe(
        +   *     'A white half-circle on the left edge of a gray square. The circle hops to the right when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the circle.
        +   *   circle(x, 50, 20);
        +   *
        +   *   // Increment x.
        +   *   x += 5;
        +   * }
        +   *
        +   * // Run the draw loop three times when the user double-clicks.
        +   * function doubleClicked() {
        +   *   redraw(3);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.redraw = async function(n) {
        +    if (this._inUserDraw || !this._setupDone) {
        +      return;
        +    }
         
        -  const context = this._isGlobal ? window : this;
        -  if (typeof context.draw === 'function') {
        -    if (typeof context.setup === 'undefined') {
        -      context.scale(context._pixelDensity, context._pixelDensity);
        +    let numberOfRedraws = parseInt(n);
        +    if (isNaN(numberOfRedraws) || numberOfRedraws < 1) {
        +      numberOfRedraws = 1;
             }
        -    for (let idxRedraw = 0; idxRedraw < numberOfRedraws; idxRedraw++) {
        -      context.resetMatrix();
        -      if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        -        this._updateAccsOutput();
        -      }
        -      if (context._renderer.isP3D) {
        -        context._renderer._update();
        +
        +    const context = this._isGlobal ? window : this;
        +    if (typeof context.draw === 'function') {
        +      if (typeof context.setup === 'undefined') {
        +        context.scale(context._pixelDensity, context._pixelDensity);
               }
        -      context._setProperty('frameCount', context.frameCount + 1);
        -      this.callRegisteredHooksFor('pre');
        -      this._inUserDraw = true;
        -      try {
        -        context.draw();
        -      } finally {
        -        this._inUserDraw = false;
        +      for (let idxRedraw = 0; idxRedraw < numberOfRedraws; idxRedraw++) {
        +        context.resetMatrix();
        +        if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        +          this._updateAccsOutput();
        +        }
        +        if (this._renderer.isP3D) {
        +          this._renderer._update();
        +        }
        +        this.frameCount = context.frameCount + 1;
        +        await this._runLifecycleHook('predraw');
        +        this._inUserDraw = true;
        +        try {
        +          await context.draw();
        +        } finally {
        +          this._inUserDraw = false;
        +        }
        +        await this._runLifecycleHook('postdraw');
               }
        -      this.callRegisteredHooksFor('post');
             }
        -  }
        -};
        +  };
         
        -/**
        - * Creates a new sketch in "instance" mode.
        - *
        - * All p5.js sketches are instances of the `p5` class. Put another way, all
        - * p5.js sketches are objects with methods including `pInst.setup()`,
        - * `pInst.draw()`, `pInst.circle()`, and `pInst.fill()`. By default, sketches
        - * run in "global mode" to hide some of this complexity.
        - *
        - * In global mode, a default instance of the `p5` class is created
        - * automatically. The default `p5` instance searches the web page's source
        - * code for declarations of system functions such as `setup()`, `draw()`,
        - * and `mousePressed()`, then attaches those functions to itself as methods.
        - * Calling a function such as `circle()` in global mode actually calls the
        - * default `p5` object's `pInst.circle()` method.
        - *
        - * It's often helpful to isolate the code within sketches from the rest of the
        - * code on a web page. Two common use cases are web pages that use other
        - * JavaScript libraries and web pages with multiple sketches. "Instance mode"
        - * makes it easy to support both of these scenarios.
        - *
        - * Instance mode sketches support the same API as global mode sketches. They
        - * use a function to bundle, or encapsulate, an entire sketch. The function
        - * containing the sketch is then passed to the `p5()` constructor.
        - *
        - * The first parameter, `sketch`, is a function that contains the sketch. For
        - * example, the statement `new p5(mySketch)` would create a new instance mode
        - * sketch from a function named `mySketch`. The function should have one
        - * parameter, `p`, that's a `p5` object.
        - *
        - * The second parameter, `node`, is optional. If a string is passed, as in
        - * `new p5(mySketch, 'sketch-one')` the new instance mode sketch will become a
        - * child of the HTML element with the id `sketch-one`. If an HTML element is
        - * passed, as in `new p5(mySketch, myElement)`, then the new instance mode
        - * sketch will become a child of the `Element` object called `myElement`.
        - *
        - * @method p5
        - * @param {Object} sketch function containing the sketch.
        - * @param {String|HTMLElement} node ID or reference to the HTML element that will contain the sketch.
        - *
        - * @example
        - * <div class='norender notest'>
        - * <code>
        - * // Declare the function containing the sketch.
        - * function sketch(p) {
        - *
        - *   // Declare the setup() method.
        - *   p.setup = function () {
        - *     p.createCanvas(100, 100);
        - *
        - *     p.describe('A white circle drawn on a gray background.');
        - *   };
        - *
        - *   // Declare the draw() method.
        - *   p.draw = function () {
        - *     p.background(200);
        - *
        - *     // Draw the circle.
        - *     p.circle(50, 50, 20);
        - *   };
        - * }
        - *
        - * // Initialize the sketch.
        - * new p5(sketch);
        - * </code>
        - * </div>
        - *
        - * <div class='norender notest'>
        - * <code>
        - * // Declare the function containing the sketch.
        - * function sketch(p) {
        - *   // Create the sketch's variables within its scope.
        - *   let x = 50;
        - *   let y = 50;
        - *
        - *   // Declare the setup() method.
        - *   p.setup = function () {
        - *     p.createCanvas(100, 100);
        - *
        - *     p.describe('A white circle moves randomly on a gray background.');
        - *   };
        - *
        - *   // Declare the draw() method.
        - *   p.draw = function () {
        - *     p.background(200);
        - *
        - *     // Update x and y.
        - *     x += p.random(-1, 1);
        - *     y += p.random(-1, 1);
        - *
        - *     // Draw the circle.
        - *     p.circle(x, y, 20);
        - *   };
        - * }
        - *
        - * // Initialize the sketch.
        - * new p5(sketch);
        - * </code>
        - * </div>
        - *
        - * <div class='norender notest'>
        - * <code>
        - * // Declare the function containing the sketch.
        - * function sketch(p) {
        - *
        - *   // Declare the setup() method.
        - *   p.setup = function () {
        - *     p.createCanvas(100, 100);
        - *
        - *     p.describe('A white circle drawn on a gray background.');
        - *   };
        - *
        - *   // Declare the draw() method.
        - *   p.draw = function () {
        - *     p.background(200);
        - *
        - *     // Draw the circle.
        - *     p.circle(50, 50, 20);
        - *   };
        - * }
        - *
        - * // Select the web page's body element.
        - * let body = document.querySelector('body');
        - *
        - * // Initialize the sketch and attach it to the web page's body.
        - * new p5(sketch, body);
        - * </code>
        - * </div>
        - *
        - * <div class='norender notest'>
        - * <code>
        - * // Declare the function containing the sketch.
        - * function sketch(p) {
        - *
        - *   // Declare the setup() method.
        - *   p.setup = function () {
        - *     p.createCanvas(100, 100);
        - *
        - *     p.describe(
        - *       'A white circle drawn on a gray background. The circle follows the mouse as the user moves.'
        - *     );
        - *   };
        - *
        - *   // Declare the draw() method.
        - *   p.draw = function () {
        - *     p.background(200);
        - *
        - *     // Draw the circle.
        - *     p.circle(p.mouseX, p.mouseY, 20);
        - *   };
        - * }
        - *
        - * // Initialize the sketch.
        - * new p5(sketch);
        - * </code>
        - * </div>
        - *
        - * <div class='norender notest'>
        - * <code>
        - * // Declare the function containing the sketch.
        - * function sketch(p) {
        - *
        - *   // Declare the setup() method.
        - *   p.setup = function () {
        - *     p.createCanvas(100, 100);
        - *
        - *     p.describe(
        - *       'A white circle drawn on a gray background. The circle follows the mouse as the user moves. The circle becomes black when the user double-clicks.'
        - *     );
        - *   };
        - *
        - *   // Declare the draw() method.
        - *   p.draw = function () {
        - *     p.background(200);
        - *
        - *     // Draw the circle.
        - *     p.circle(p.mouseX, p.mouseY, 20);
        - *   };
        - *
        - *   // Declare the doubleClicked() method.
        - *   p.doubleClicked = function () {
        - *     // Change the fill color when the user double-clicks.
        - *     p.fill(0);
        - *   };
        - * }
        - *
        - * // Initialize the sketch.
        - * new p5(sketch);
        - * </code>
        - * </div>
        - */
        -export default p5;
        +  /**
        +   * Creates a new sketch in "instance" mode.
        +   *
        +   * All p5.js sketches are instances of the `p5` class. Put another way, all
        +   * p5.js sketches are objects with methods including `pInst.setup()`,
        +   * `pInst.draw()`, `pInst.circle()`, and `pInst.fill()`. By default, sketches
        +   * run in "global mode" to hide some of this complexity.
        +   *
        +   * In global mode, a default instance of the `p5` class is created
        +   * automatically. The default `p5` instance searches the web page's source
        +   * code for declarations of system functions such as `setup()`, `draw()`,
        +   * and `mousePressed()`, then attaches those functions to itself as methods.
        +   * Calling a function such as `circle()` in global mode actually calls the
        +   * default `p5` object's `pInst.circle()` method.
        +   *
        +   * It's often helpful to isolate the code within sketches from the rest of the
        +   * code on a web page. Two common use cases are web pages that use other
        +   * JavaScript libraries and web pages with multiple sketches. "Instance mode"
        +   * makes it easy to support both of these scenarios.
        +   *
        +   * Instance mode sketches support the same API as global mode sketches. They
        +   * use a function to bundle, or encapsulate, an entire sketch. The function
        +   * containing the sketch is then passed to the `p5()` constructor.
        +   *
        +   * The first parameter, `sketch`, is a function that contains the sketch. For
        +   * example, the statement `new p5(mySketch)` would create a new instance mode
        +   * sketch from a function named `mySketch`. The function should have one
        +   * parameter, `p`, that's a `p5` object.
        +   *
        +   * The second parameter, `node`, is optional. If a string is passed, as in
        +   * `new p5(mySketch, 'sketch-one')` the new instance mode sketch will become a
        +   * child of the HTML element with the id `sketch-one`. If an HTML element is
        +   * passed, as in `new p5(mySketch, myElement)`, then the new instance mode
        +   * sketch will become a child of the `Element` object called `myElement`.
        +   *
        +   * @method p5
        +   * @param {Object} sketch function containing the sketch.
        +   * @param {String|HTMLElement} node ID or reference to the HTML element that will contain the sketch.
        +   *
        +   * @example
        +   * <div class='norender notest'>
        +   * <code>
        +   * // Declare the function containing the sketch.
        +   * function sketch(p) {
        +   *
        +   *   // Declare the setup() method.
        +   *   p.setup = function () {
        +   *     p.createCanvas(100, 100);
        +   *
        +   *     p.describe('A white circle drawn on a gray background.');
        +   *   };
        +   *
        +   *   // Declare the draw() method.
        +   *   p.draw = function () {
        +   *     p.background(200);
        +   *
        +   *     // Draw the circle.
        +   *     p.circle(50, 50, 20);
        +   *   };
        +   * }
        +   *
        +   * // Initialize the sketch.
        +   * new p5(sketch);
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender notest'>
        +   * <code>
        +   * // Declare the function containing the sketch.
        +   * function sketch(p) {
        +   *   // Create the sketch's variables within its scope.
        +   *   let x = 50;
        +   *   let y = 50;
        +   *
        +   *   // Declare the setup() method.
        +   *   p.setup = function () {
        +   *     p.createCanvas(100, 100);
        +   *
        +   *     p.describe('A white circle moves randomly on a gray background.');
        +   *   };
        +   *
        +   *   // Declare the draw() method.
        +   *   p.draw = function () {
        +   *     p.background(200);
        +   *
        +   *     // Update x and y.
        +   *     x += p.random(-1, 1);
        +   *     y += p.random(-1, 1);
        +   *
        +   *     // Draw the circle.
        +   *     p.circle(x, y, 20);
        +   *   };
        +   * }
        +   *
        +   * // Initialize the sketch.
        +   * new p5(sketch);
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender notest'>
        +   * <code>
        +   * // Declare the function containing the sketch.
        +   * function sketch(p) {
        +   *
        +   *   // Declare the setup() method.
        +   *   p.setup = function () {
        +   *     p.createCanvas(100, 100);
        +   *
        +   *     p.describe('A white circle drawn on a gray background.');
        +   *   };
        +   *
        +   *   // Declare the draw() method.
        +   *   p.draw = function () {
        +   *     p.background(200);
        +   *
        +   *     // Draw the circle.
        +   *     p.circle(50, 50, 20);
        +   *   };
        +   * }
        +   *
        +   * // Select the web page's body element.
        +   * let body = document.querySelector('body');
        +   *
        +   * // Initialize the sketch and attach it to the web page's body.
        +   * new p5(sketch, body);
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender notest'>
        +   * <code>
        +   * // Declare the function containing the sketch.
        +   * function sketch(p) {
        +   *
        +   *   // Declare the setup() method.
        +   *   p.setup = function () {
        +   *     p.createCanvas(100, 100);
        +   *
        +   *     p.describe(
        +   *       'A white circle drawn on a gray background. The circle follows the mouse as the user moves.'
        +   *     );
        +   *   };
        +   *
        +   *   // Declare the draw() method.
        +   *   p.draw = function () {
        +   *     p.background(200);
        +   *
        +   *     // Draw the circle.
        +   *     p.circle(p.mouseX, p.mouseY, 20);
        +   *   };
        +   * }
        +   *
        +   * // Initialize the sketch.
        +   * new p5(sketch);
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender notest'>
        +   * <code>
        +   * // Declare the function containing the sketch.
        +   * function sketch(p) {
        +   *
        +   *   // Declare the setup() method.
        +   *   p.setup = function () {
        +   *     p.createCanvas(100, 100);
        +   *
        +   *     p.describe(
        +   *       'A white circle drawn on a gray background. The circle follows the mouse as the user moves. The circle becomes black when the user double-clicks.'
        +   *     );
        +   *   };
        +   *
        +   *   // Declare the draw() method.
        +   *   p.draw = function () {
        +   *     p.background(200);
        +   *
        +   *     // Draw the circle.
        +   *     p.circle(p.mouseX, p.mouseY, 20);
        +   *   };
        +   *
        +   *   // Declare the doubleClicked() method.
        +   *   p.doubleClicked = function () {
        +   *     // Change the fill color when the user double-clicks.
        +   *     p.fill(0);
        +   *   };
        +   * }
        +   *
        +   * // Initialize the sketch.
        +   * new p5(sketch);
        +   * </code>
        +   * </div>
        +   */
        +}
        +
        +export default structure;
        +
        +if(typeof p5 !== 'undefined'){
        +  structure(p5, p5.prototype);
        +}
        \ No newline at end of file
        diff --git a/src/core/transform.js b/src/core/transform.js
        index 911d074d1a..5ba580999c 100644
        --- a/src/core/transform.js
        +++ b/src/core/transform.js
        @@ -6,1404 +6,1966 @@
          * @requires constants
          */
         
        -import p5 from './main';
        +function transform(p5, fn){
        +  /**
        +   * Applies a transformation matrix to the coordinate system.
        +   *
        +   * Transformations such as
        +   * <a href="#/p5/translate">translate()</a>,
        +   * <a href="#/p5/rotate">rotate()</a>, and
        +   * <a href="#/p5/scale">scale()</a>
        +   * use matrix-vector multiplication behind the scenes. A table of numbers,
        +   * called a matrix, encodes each transformation. The values in the matrix
        +   * then multiply each point on the canvas, which is represented by a vector.
        +   *
        +   * `applyMatrix()` allows for many transformations to be applied at once. See
        +   * <a href="https://en.wikipedia.org/wiki/Transformation_matrix" target="_blank">Wikipedia</a>
        +   * and <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web" target="_blank">MDN</a>
        +   * for more details about transformations.
        +   *
        +   * There are two ways to call `applyMatrix()` in two and three dimensions.
        +   *
        +   * In 2D mode, the parameters `a`, `b`, `c`, `d`, `e`, and `f`, correspond to
        +   * elements in the following transformation matrix:
        +   *
        +   * > <img style="max-width: 150px" src="assets/transformation-matrix.png"
        +   * alt="The transformation matrix used when applyMatrix is called in 2D mode."/>
        +   *
        +   * The numbers can be passed individually, as in
        +   * `applyMatrix(2, 0, 0, 0, 2, 0)`. They can also be passed in an array, as in
        +   * `applyMatrix([2, 0, 0, 0, 2, 0])`.
        +   *
        +   * In 3D mode, the parameters `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h`, `i`,
        +   * `j`, `k`, `l`, `m`, `n`, `o`, and `p` correspond to elements in the
        +   * following transformation matrix:
        +   *
        +   * <img style="max-width: 300px" src="assets/transformation-matrix-4-4.png"
        +   * alt="The transformation matrix used when applyMatrix is called in 3D mode."/>
        +   *
        +   * The numbers can be passed individually, as in
        +   * `applyMatrix(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1)`. They can
        +   * also be passed in an array, as in
        +   * `applyMatrix([2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1])`.
        +   *
        +   * By default, transformations accumulate. The
        +   * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        +   * can be used to isolate transformations within distinct drawing groups.
        +   *
        +   * Note: Transformations are reset at the beginning of the draw loop. Calling
        +   * `applyMatrix()` inside the <a href="#/p5/draw">draw()</a> function won't
        +   * cause shapes to transform continuously.
        +   *
        +   * @method applyMatrix
        +   * @param  {Array} arr an array containing the elements of the transformation matrix. Its length should be either 6 (2D) or 16 (3D).
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white circle on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin to the center.
        +   *   applyMatrix(1, 0, 0, 1, 50, 50);
        +   *
        +   *   // Draw the circle at coordinates (0, 0).
        +   *   circle(0, 0, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white circle on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin to the center.
        +   *   let m = [1, 0, 0, 1, 50, 50];
        +   *   applyMatrix(m);
        +   *
        +   *   // Draw the circle at coordinates (0, 0).
        +   *   circle(0, 0, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe("A white rectangle on a gray background. The rectangle's long axis runs from top-left to bottom-right.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate the coordinate system 1/8 turn.
        +   *   let angle = QUARTER_PI;
        +   *   let ca = cos(angle);
        +   *   let sa = sin(angle);
        +   *   applyMatrix(ca, sa, -sa, ca, 0, 0);
        +   *
        +   *   // Draw a rectangle at coordinates (50, 0).
        +   *   rect(50, 0, 40, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'Two white squares on a gray background. The larger square appears at the top-center. The smaller square appears at the top-left.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a square at (30, 20).
        +   *   square(30, 20, 40);
        +   *
        +   *   // Scale the coordinate system by a factor of 0.5.
        +   *   applyMatrix(0.5, 0, 0, 0.5, 0, 0);
        +   *
        +   *   // Draw a square at (30, 20).
        +   *   // It appears at (15, 10) after scaling.
        +   *   square(30, 20, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white quadrilateral on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the shear factor.
        +   *   let angle = QUARTER_PI;
        +   *   let shearFactor = 1 / tan(HALF_PI - angle);
        +   *
        +   *   // Shear the coordinate system along the x-axis.
        +   *   applyMatrix(1, 0, shearFactor, 1, 0, 0);
        +   *
        +   *   // Draw the square.
        +   *   square(0, 0, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube rotates slowly against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system a little more each frame.
        +   *   let angle = frameCount * 0.01;
        +   *   let ca = cos(angle);
        +   *   let sa = sin(angle);
        +   *   applyMatrix(ca, 0, sa, 0, 0, 1, 0, 0, -sa, 0, ca, 0, 0, 0, 0, 1);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method applyMatrix
        +   * @param  {Number} a an element of the transformation matrix.
        +   * @param  {Number} b an element of the transformation matrix.
        +   * @param  {Number} c an element of the transformation matrix.
        +   * @param  {Number} d an element of the transformation matrix.
        +   * @param  {Number} e an element of the transformation matrix.
        +   * @param  {Number} f an element of the transformation matrix.
        +   * @chainable
        +   */
        +  /**
        +   * @method applyMatrix
        +   * @param  {Number} a
        +   * @param  {Number} b
        +   * @param  {Number} c
        +   * @param  {Number} d
        +   * @param  {Number} e
        +   * @param  {Number} f
        +   * @param  {Number} g an element of the transformation matrix.
        +   * @param  {Number} h an element of the transformation matrix.
        +   * @param  {Number} i an element of the transformation matrix.
        +   * @param  {Number} j an element of the transformation matrix.
        +   * @param  {Number} k an element of the transformation matrix.
        +   * @param  {Number} l an element of the transformation matrix.
        +   * @param  {Number} m an element of the transformation matrix.
        +   * @param  {Number} n an element of the transformation matrix.
        +   * @param  {Number} o an element of the transformation matrix.
        +   * @param  {Number} p an element of the transformation matrix.
        +   * @chainable
        +   */
        +  fn.applyMatrix = function(...args) {
        +    let isTypedArray = args[0] instanceof Object.getPrototypeOf(Uint8Array);
        +    if (Array.isArray(args[0]) || isTypedArray) {
        +      this._renderer.applyMatrix(...args[0]);
        +    } else {
        +      this._renderer.applyMatrix(...args);
        +    }
        +    return this;
        +  };
         
        -/**
        - * Applies a transformation matrix to the coordinate system.
        - *
        - * Transformations such as
        - * <a href="#/p5/translate">translate()</a>,
        - * <a href="#/p5/rotate">rotate()</a>, and
        - * <a href="#/p5/scale">scale()</a>
        - * use matrix-vector multiplication behind the scenes. A table of numbers,
        - * called a matrix, encodes each transformation. The values in the matrix
        - * then multiply each point on the canvas, which is represented by a vector.
        - *
        - * `applyMatrix()` allows for many transformations to be applied at once. See
        - * <a href="https://en.wikipedia.org/wiki/Transformation_matrix" target="_blank">Wikipedia</a>
        - * and <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Matrix_math_for_the_web" target="_blank">MDN</a>
        - * for more details about transformations.
        - *
        - * There are two ways to call `applyMatrix()` in two and three dimensions.
        - *
        - * In 2D mode, the parameters `a`, `b`, `c`, `d`, `e`, and `f`, correspond to
        - * elements in the following transformation matrix:
        - *
        - * > <img style="max-width: 150px" src="assets/transformation-matrix.png"
        - * alt="The transformation matrix used when applyMatrix is called in 2D mode."/>
        - *
        - * The numbers can be passed individually, as in
        - * `applyMatrix(2, 0, 0, 0, 2, 0)`. They can also be passed in an array, as in
        - * `applyMatrix([2, 0, 0, 0, 2, 0])`.
        - *
        - * In 3D mode, the parameters `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h`, `i`,
        - * `j`, `k`, `l`, `m`, `n`, `o`, and `p` correspond to elements in the
        - * following transformation matrix:
        - *
        - * <img style="max-width: 300px" src="assets/transformation-matrix-4-4.png"
        - * alt="The transformation matrix used when applyMatrix is called in 3D mode."/>
        - *
        - * The numbers can be passed individually, as in
        - * `applyMatrix(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1)`. They can
        - * also be passed in an array, as in
        - * `applyMatrix([2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1])`.
        - *
        - * By default, transformations accumulate. The
        - * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        - * can be used to isolate transformations within distinct drawing groups.
        - *
        - * Note: Transformations are reset at the beginning of the draw loop. Calling
        - * `applyMatrix()` inside the <a href="#/p5/draw">draw()</a> function won't
        - * cause shapes to transform continuously.
        - *
        - * @method applyMatrix
        - * @param  {Array} arr an array containing the elements of the transformation matrix. Its length should be either 6 (2D) or 16 (3D).
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white circle on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin to the center.
        - *   applyMatrix(1, 0, 0, 1, 50, 50);
        - *
        - *   // Draw the circle at coordinates (0, 0).
        - *   circle(0, 0, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white circle on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin to the center.
        - *   let m = [1, 0, 0, 1, 50, 50];
        - *   applyMatrix(m);
        - *
        - *   // Draw the circle at coordinates (0, 0).
        - *   circle(0, 0, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe("A white rectangle on a gray background. The rectangle's long axis runs from top-left to bottom-right.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate the coordinate system 1/8 turn.
        - *   let angle = QUARTER_PI;
        - *   let ca = cos(angle);
        - *   let sa = sin(angle);
        - *   applyMatrix(ca, sa, -sa, ca, 0, 0);
        - *
        - *   // Draw a rectangle at coordinates (50, 0).
        - *   rect(50, 0, 40, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'Two white squares on a gray background. The larger square appears at the top-center. The smaller square appears at the top-left.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a square at (30, 20).
        - *   square(30, 20, 40);
        - *
        - *   // Scale the coordinate system by a factor of 0.5.
        - *   applyMatrix(0.5, 0, 0, 0.5, 0, 0);
        - *
        - *   // Draw a square at (30, 20).
        - *   // It appears at (15, 10) after scaling.
        - *   square(30, 20, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white quadrilateral on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the shear factor.
        - *   let angle = QUARTER_PI;
        - *   let shearFactor = 1 / tan(HALF_PI - angle);
        - *
        - *   // Shear the coordinate system along the x-axis.
        - *   applyMatrix(1, 0, shearFactor, 1, 0, 0);
        - *
        - *   // Draw the square.
        - *   square(0, 0, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube rotates slowly against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system a little more each frame.
        - *   let angle = frameCount * 0.01;
        - *   let ca = cos(angle);
        - *   let sa = sin(angle);
        - *   applyMatrix(ca, 0, sa, 0, 0, 1, 0, 0, -sa, 0, ca, 0, 0, 0, 0, 1);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method applyMatrix
        - * @param  {Number} a an element of the transformation matrix.
        - * @param  {Number} b an element of the transformation matrix.
        - * @param  {Number} c an element of the transformation matrix.
        - * @param  {Number} d an element of the transformation matrix.
        - * @param  {Number} e an element of the transformation matrix.
        - * @param  {Number} f an element of the transformation matrix.
        - * @chainable
        - */
        -/**
        - * @method applyMatrix
        - * @param  {Number} a
        - * @param  {Number} b
        - * @param  {Number} c
        - * @param  {Number} d
        - * @param  {Number} e
        - * @param  {Number} f
        - * @param  {Number} g an element of the transformation matrix.
        - * @param  {Number} h an element of the transformation matrix.
        - * @param  {Number} i an element of the transformation matrix.
        - * @param  {Number} j an element of the transformation matrix.
        - * @param  {Number} k an element of the transformation matrix.
        - * @param  {Number} l an element of the transformation matrix.
        - * @param  {Number} m an element of the transformation matrix.
        - * @param  {Number} n an element of the transformation matrix.
        - * @param  {Number} o an element of the transformation matrix.
        - * @param  {Number} p an element of the transformation matrix.
        - * @chainable
        - */
        -p5.prototype.applyMatrix = function(...args) {
        -  let isTypedArray = args[0] instanceof Object.getPrototypeOf(Uint8Array);
        -  if (Array.isArray(args[0]) || isTypedArray) {
        -    this._renderer.applyMatrix(...args[0]);
        -  } else {
        -    this._renderer.applyMatrix(...args);
        -  }
        -  return this;
        -};
        +  /**
        +   * Clears all transformations applied to the coordinate system.
        +   *
        +   * @method resetMatrix
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'Two circles drawn on a gray background. A blue circle is at the top-left and a red circle is at the bottom-right.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin to the center.
        +   *   translate(50, 50);
        +   *
        +   *   // Draw a blue circle at the coordinates (25, 25).
        +   *   fill('blue');
        +   *   circle(25, 25, 20);
        +   *
        +   *   // Clear all transformations.
        +   *   // The origin is now at the top-left corner.
        +   *   resetMatrix();
        +   *
        +   *   // Draw a red circle at the coordinates (25, 25).
        +   *   fill('red');
        +   *   circle(25, 25, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.resetMatrix = function() {
        +    this._renderer.resetMatrix();
        +    return this;
        +  };
         
        -/**
        - * Clears all transformations applied to the coordinate system.
        - *
        - * @method resetMatrix
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'Two circles drawn on a gray background. A blue circle is at the top-left and a red circle is at the bottom-right.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin to the center.
        - *   translate(50, 50);
        - *
        - *   // Draw a blue circle at the coordinates (25, 25).
        - *   fill('blue');
        - *   circle(25, 25, 20);
        - *
        - *   // Clear all transformations.
        - *   // The origin is now at the top-left corner.
        - *   resetMatrix();
        - *
        - *   // Draw a red circle at the coordinates (25, 25).
        - *   fill('red');
        - *   circle(25, 25, 20);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.resetMatrix = function() {
        -  this._renderer.resetMatrix();
        -  return this;
        -};
        +  /**
        +   * Rotates the coordinate system.
        +   *
        +   * By default, the positive x-axis points to the right and the positive y-axis
        +   * points downward. The `rotate()` function changes this orientation by
        +   * rotating the coordinate system about the origin. Everything drawn after
        +   * `rotate()` is called will appear to be rotated.
        +   *
        +   * The first parameter, `angle`, is the amount to rotate. For example, calling
        +   * `rotate(1)` rotates the coordinate system clockwise 1 radian which is
        +   * nearly 57˚. `rotate()` interprets angle values using the current
        +   * <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * The second parameter, `axis`, is optional. It's used to orient 3D rotations
        +   * in WebGL mode. If a <a href="#/p5.Vector">p5.Vector</a> is passed, as in
        +   * `rotate(QUARTER_PI, myVector)`, then the coordinate system will rotate
        +   * `QUARTER_PI` radians about `myVector`. If an array of vector components is
        +   * passed, as in `rotate(QUARTER_PI, [1, 0, 0])`, then the coordinate system
        +   * will rotate `QUARTER_PI` radians about a vector with the components
        +   * `[1, 0, 0]`.
        +   *
        +   * By default, transformations accumulate. For example, calling `rotate(1)`
        +   * twice has the same effect as calling `rotate(2)` once. The
        +   * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        +   * can be used to isolate transformations within distinct drawing groups.
        +   *
        +   * Note: Transformations are reset at the beginning of the draw loop. Calling
        +   * `rotate(1)` inside the <a href="#/p5/draw">draw()</a> function won't cause
        +   * shapes to spin.
        +   *
        +   * @method rotate
        +   * @param  {Number} angle angle of rotation in the current <a href="#/p5/angleMode">angleMode()</a>.
        +   * @param  {p5.Vector|Number[]} [axis] axis to rotate about in 3D.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     "A white rectangle on a gray background. The rectangle's long axis runs from top-left to bottom-right."
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate the coordinate system 1/8 turn.
        +   *   rotate(QUARTER_PI);
        +   *
        +   *   // Draw a rectangle at coordinates (50, 0).
        +   *   rect(50, 0, 40, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     "A white rectangle on a gray background. The rectangle's long axis runs from top-left to bottom-right."
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate the coordinate system 1/16 turn.
        +   *   rotate(QUARTER_PI / 2);
        +   *
        +   *   // Rotate the coordinate system another 1/16 turn.
        +   *   rotate(QUARTER_PI / 2);
        +   *
        +   *   // Draw a rectangle at coordinates (50, 0).
        +   *   rect(50, 0, 40, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   describe(
        +   *     "A white rectangle on a gray background. The rectangle's long axis runs from top-left to bottom-right."
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate the coordinate system 1/8 turn.
        +   *   rotate(45);
        +   *
        +   *   // Draw a rectangle at coordinates (50, 0).
        +   *   rect(50, 0, 40, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A white rectangle on a gray background. The rectangle rotates slowly about the top-left corner. It disappears and reappears periodically.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate the coordinate system a little more each frame.
        +   *   let angle = frameCount * 0.01;
        +   *   rotate(angle);
        +   *
        +   *   // Draw a rectangle at coordinates (50, 0).
        +   *   rect(50, 0, 40, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe("A cube on a gray background. The cube's front face points to the top-right.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate the coordinate system 1/8 turn about
        +   *   // the axis [1, 1, 0].
        +   *   let axis = createVector(1, 1, 0);
        +   *   rotate(QUARTER_PI, axis);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe("A cube on a gray background. The cube's front face points to the top-right.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate the coordinate system 1/8 turn about
        +   *   // the axis [1, 1, 0].
        +   *   let axis = [1, 1, 0];
        +   *   rotate(QUARTER_PI, axis);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.rotate = function(angle, axis) {
        +    // p5._validateParameters('rotate', arguments);
        +    this._renderer.rotate(this._toRadians(angle), axis);
        +    return this;
        +  };
         
        -/**
        - * Rotates the coordinate system.
        - *
        - * By default, the positive x-axis points to the right and the positive y-axis
        - * points downward. The `rotate()` function changes this orientation by
        - * rotating the coordinate system about the origin. Everything drawn after
        - * `rotate()` is called will appear to be rotated.
        - *
        - * The first parameter, `angle`, is the amount to rotate. For example, calling
        - * `rotate(1)` rotates the coordinate system clockwise 1 radian which is
        - * nearly 57˚. `rotate()` interprets angle values using the current
        - * <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * The second parameter, `axis`, is optional. It's used to orient 3D rotations
        - * in WebGL mode. If a <a href="#/p5.Vector">p5.Vector</a> is passed, as in
        - * `rotate(QUARTER_PI, myVector)`, then the coordinate system will rotate
        - * `QUARTER_PI` radians about `myVector`. If an array of vector components is
        - * passed, as in `rotate(QUARTER_PI, [1, 0, 0])`, then the coordinate system
        - * will rotate `QUARTER_PI` radians about a vector with the components
        - * `[1, 0, 0]`.
        - *
        - * By default, transformations accumulate. For example, calling `rotate(1)`
        - * twice has the same effect as calling `rotate(2)` once. The
        - * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        - * can be used to isolate transformations within distinct drawing groups.
        - *
        - * Note: Transformations are reset at the beginning of the draw loop. Calling
        - * `rotate(1)` inside the <a href="#/p5/draw">draw()</a> function won't cause
        - * shapes to spin.
        - *
        - * @method rotate
        - * @param  {Number} angle angle of rotation in the current <a href="#/p5/angleMode">angleMode()</a>.
        - * @param  {p5.Vector|Number[]} [axis] axis to rotate about in 3D.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     "A white rectangle on a gray background. The rectangle's long axis runs from top-left to bottom-right."
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate the coordinate system 1/8 turn.
        - *   rotate(QUARTER_PI);
        - *
        - *   // Draw a rectangle at coordinates (50, 0).
        - *   rect(50, 0, 40, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     "A white rectangle on a gray background. The rectangle's long axis runs from top-left to bottom-right."
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate the coordinate system 1/16 turn.
        - *   rotate(QUARTER_PI / 2);
        - *
        - *   // Rotate the coordinate system another 1/16 turn.
        - *   rotate(QUARTER_PI / 2);
        - *
        - *   // Draw a rectangle at coordinates (50, 0).
        - *   rect(50, 0, 40, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   describe(
        - *     "A white rectangle on a gray background. The rectangle's long axis runs from top-left to bottom-right."
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate the coordinate system 1/8 turn.
        - *   rotate(45);
        - *
        - *   // Draw a rectangle at coordinates (50, 0).
        - *   rect(50, 0, 40, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A white rectangle on a gray background. The rectangle rotates slowly about the top-left corner. It disappears and reappears periodically.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate the coordinate system a little more each frame.
        - *   let angle = frameCount * 0.01;
        - *   rotate(angle);
        - *
        - *   // Draw a rectangle at coordinates (50, 0).
        - *   rect(50, 0, 40, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe("A cube on a gray background. The cube's front face points to the top-right.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate the coordinate system 1/8 turn about
        - *   // the axis [1, 1, 0].
        - *   let axis = createVector(1, 1, 0);
        - *   rotate(QUARTER_PI, axis);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe("A cube on a gray background. The cube's front face points to the top-right.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Rotate the coordinate system 1/8 turn about
        - *   // the axis [1, 1, 0].
        - *   let axis = [1, 1, 0];
        - *   rotate(QUARTER_PI, axis);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.rotate = function(angle, axis) {
        -  p5._validateParameters('rotate', arguments);
        -  this._renderer.rotate(this._toRadians(angle), axis);
        -  return this;
        -};
        +  /**
        +   * Rotates the coordinate system about the x-axis in WebGL mode.
        +   *
        +   * The parameter, `angle`, is the amount to rotate. For example, calling
        +   * `rotateX(1)` rotates the coordinate system about the x-axis by 1 radian.
        +   * `rotateX()` interprets angle values using the current
        +   * <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * By default, transformations accumulate. For example, calling `rotateX(1)`
        +   * twice has the same effect as calling `rotateX(2)` once. The
        +   * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        +   * can be used to isolate transformations within distinct drawing groups.
        +   *
        +   * Note: Transformations are reset at the beginning of the draw loop. Calling
        +   * `rotateX(1)` inside the <a href="#/p5/draw">draw()</a> function won't cause
        +   * shapes to spin.
        +   *
        +   * @method  rotateX
        +   * @param  {Number} angle angle of rotation in the current <a href="#/p5/angleMode">angleMode()</a>.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system 1/8 turn.
        +   *   rotateX(QUARTER_PI);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system 1/16 turn.
        +   *   rotateX(QUARTER_PI / 2);
        +   *
        +   *   // Rotate the coordinate system 1/16 turn.
        +   *   rotateX(QUARTER_PI / 2);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system 1/8 turn.
        +   *   rotateX(45);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube rotates slowly against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system a little more each frame.
        +   *   let angle = frameCount * 0.01;
        +   *   rotateX(angle);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.rotateX = function(angle) {
        +    this._assert3d('rotateX');
        +    // p5._validateParameters('rotateX', arguments);
        +    this._renderer.rotateX(this._toRadians(angle));
        +    return this;
        +  };
         
        -/**
        - * Rotates the coordinate system about the x-axis in WebGL mode.
        - *
        - * The parameter, `angle`, is the amount to rotate. For example, calling
        - * `rotateX(1)` rotates the coordinate system about the x-axis by 1 radian.
        - * `rotateX()` interprets angle values using the current
        - * <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * By default, transformations accumulate. For example, calling `rotateX(1)`
        - * twice has the same effect as calling `rotateX(2)` once. The
        - * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        - * can be used to isolate transformations within distinct drawing groups.
        - *
        - * Note: Transformations are reset at the beginning of the draw loop. Calling
        - * `rotateX(1)` inside the <a href="#/p5/draw">draw()</a> function won't cause
        - * shapes to spin.
        - *
        - * @method  rotateX
        - * @param  {Number} angle angle of rotation in the current <a href="#/p5/angleMode">angleMode()</a>.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system 1/8 turn.
        - *   rotateX(QUARTER_PI);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system 1/16 turn.
        - *   rotateX(QUARTER_PI / 2);
        - *
        - *   // Rotate the coordinate system 1/16 turn.
        - *   rotateX(QUARTER_PI / 2);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system 1/8 turn.
        - *   rotateX(45);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube rotates slowly against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system a little more each frame.
        - *   let angle = frameCount * 0.01;
        - *   rotateX(angle);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.rotateX = function(angle) {
        -  this._assert3d('rotateX');
        -  p5._validateParameters('rotateX', arguments);
        -  this._renderer.rotateX(this._toRadians(angle));
        -  return this;
        -};
        +  /**
        +   * Rotates the coordinate system about the y-axis in WebGL mode.
        +   *
        +   * The parameter, `angle`, is the amount to rotate. For example, calling
        +   * `rotateY(1)` rotates the coordinate system about the y-axis by 1 radian.
        +   * `rotateY()` interprets angle values using the current
        +   * <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * By default, transformations accumulate. For example, calling `rotateY(1)`
        +   * twice has the same effect as calling `rotateY(2)` once. The
        +   * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        +   * can be used to isolate transformations within distinct drawing groups.
        +   *
        +   * Note: Transformations are reset at the beginning of the draw loop. Calling
        +   * `rotateY(1)` inside the <a href="#/p5/draw">draw()</a> function won't cause
        +   * shapes to spin.
        +   *
        +   * @method rotateY
        +   * @param  {Number} angle angle of rotation in the current <a href="#/p5/angleMode">angleMode()</a>.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system 1/8 turn.
        +   *   rotateY(QUARTER_PI);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system 1/16 turn.
        +   *   rotateY(QUARTER_PI / 2);
        +   *
        +   *   // Rotate the coordinate system 1/16 turn.
        +   *   rotateY(QUARTER_PI / 2);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system 1/8 turn.
        +   *   rotateY(45);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube rotates slowly against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system a little more each frame.
        +   *   let angle = frameCount * 0.01;
        +   *   rotateY(angle);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.rotateY = function(angle) {
        +    this._assert3d('rotateY');
        +    // p5._validateParameters('rotateY', arguments);
        +    this._renderer.rotateY(this._toRadians(angle));
        +    return this;
        +  };
         
        -/**
        - * Rotates the coordinate system about the y-axis in WebGL mode.
        - *
        - * The parameter, `angle`, is the amount to rotate. For example, calling
        - * `rotateY(1)` rotates the coordinate system about the y-axis by 1 radian.
        - * `rotateY()` interprets angle values using the current
        - * <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * By default, transformations accumulate. For example, calling `rotateY(1)`
        - * twice has the same effect as calling `rotateY(2)` once. The
        - * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        - * can be used to isolate transformations within distinct drawing groups.
        - *
        - * Note: Transformations are reset at the beginning of the draw loop. Calling
        - * `rotateY(1)` inside the <a href="#/p5/draw">draw()</a> function won't cause
        - * shapes to spin.
        - *
        - * @method rotateY
        - * @param  {Number} angle angle of rotation in the current <a href="#/p5/angleMode">angleMode()</a>.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system 1/8 turn.
        - *   rotateY(QUARTER_PI);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system 1/16 turn.
        - *   rotateY(QUARTER_PI / 2);
        - *
        - *   // Rotate the coordinate system 1/16 turn.
        - *   rotateY(QUARTER_PI / 2);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system 1/8 turn.
        - *   rotateY(45);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube rotates slowly against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system a little more each frame.
        - *   let angle = frameCount * 0.01;
        - *   rotateY(angle);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.rotateY = function(angle) {
        -  this._assert3d('rotateY');
        -  p5._validateParameters('rotateY', arguments);
        -  this._renderer.rotateY(this._toRadians(angle));
        -  return this;
        -};
        +  /**
        +   * Rotates the coordinate system about the z-axis in WebGL mode.
        +   *
        +   * The parameter, `angle`, is the amount to rotate. For example, calling
        +   * `rotateZ(1)` rotates the coordinate system about the z-axis by 1 radian.
        +   * `rotateZ()` interprets angle values using the current
        +   * <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * By default, transformations accumulate. For example, calling `rotateZ(1)`
        +   * twice has the same effect as calling `rotateZ(2)` once. The
        +   * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        +   * can be used to isolate transformations within distinct drawing groups.
        +   *
        +   * Note: Transformations are reset at the beginning of the draw loop. Calling
        +   * `rotateZ(1)` inside the <a href="#/p5/draw">draw()</a> function won't cause
        +   * shapes to spin.
        +   *
        +   * @method rotateZ
        +   * @param  {Number} angle angle of rotation in the current <a href="#/p5/angleMode">angleMode()</a>.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system 1/8 turn.
        +   *   rotateZ(QUARTER_PI);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system 1/16 turn.
        +   *   rotateZ(QUARTER_PI / 2);
        +   *
        +   *   // Rotate the coordinate system 1/16 turn.
        +   *   rotateZ(QUARTER_PI / 2);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system 1/8 turn.
        +   *   rotateZ(45);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube rotates slowly against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rotate the coordinate system a little more each frame.
        +   *   let angle = frameCount * 0.01;
        +   *   rotateZ(angle);
        +   *
        +   *   // Draw a box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.rotateZ = function(angle) {
        +    this._assert3d('rotateZ');
        +    // p5._validateParameters('rotateZ', arguments);
        +    this._renderer.rotateZ(this._toRadians(angle));
        +    return this;
        +  };
         
        -/**
        - * Rotates the coordinate system about the z-axis in WebGL mode.
        - *
        - * The parameter, `angle`, is the amount to rotate. For example, calling
        - * `rotateZ(1)` rotates the coordinate system about the z-axis by 1 radian.
        - * `rotateZ()` interprets angle values using the current
        - * <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * By default, transformations accumulate. For example, calling `rotateZ(1)`
        - * twice has the same effect as calling `rotateZ(2)` once. The
        - * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        - * can be used to isolate transformations within distinct drawing groups.
        - *
        - * Note: Transformations are reset at the beginning of the draw loop. Calling
        - * `rotateZ(1)` inside the <a href="#/p5/draw">draw()</a> function won't cause
        - * shapes to spin.
        - *
        - * @method rotateZ
        - * @param  {Number} angle angle of rotation in the current <a href="#/p5/angleMode">angleMode()</a>.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system 1/8 turn.
        - *   rotateZ(QUARTER_PI);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system 1/16 turn.
        - *   rotateZ(QUARTER_PI / 2);
        - *
        - *   // Rotate the coordinate system 1/16 turn.
        - *   rotateZ(QUARTER_PI / 2);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system 1/8 turn.
        - *   rotateZ(45);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube rotates slowly against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rotate the coordinate system a little more each frame.
        - *   let angle = frameCount * 0.01;
        - *   rotateZ(angle);
        - *
        - *   // Draw a box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.rotateZ = function(angle) {
        -  this._assert3d('rotateZ');
        -  p5._validateParameters('rotateZ', arguments);
        -  this._renderer.rotateZ(this._toRadians(angle));
        -  return this;
        -};
        +  /**
        +   * Scales the coordinate system.
        +   *
        +   * By default, shapes are drawn at their original scale. A rectangle that's 50
        +   * pixels wide appears to take up half the width of a 100 pixel-wide canvas.
        +   * The `scale()` function can shrink or stretch the coordinate system so that
        +   * shapes appear at different sizes. There are two ways to call `scale()` with
        +   * parameters that set the scale factor(s).
        +   *
        +   * The first way to call `scale()` uses numbers to set the amount of scaling.
        +   * The first parameter, `s`, sets the amount to scale each axis. For example,
        +   * calling `scale(2)` stretches the x-, y-, and z-axes by a factor of 2. The
        +   * next two parameters, `y` and `z`, are optional. They set the amount to
        +   * scale the y- and z-axes. For example, calling `scale(2, 0.5, 1)` stretches
        +   * the x-axis by a factor of 2, shrinks the y-axis by a factor of 0.5, and
        +   * leaves the z-axis unchanged.
        +   *
        +   * The second way to call `scale()` uses a <a href="#/p5.Vector">p5.Vector</a>
        +   * object to set the scale factors. For example, calling `scale(myVector)`
        +   * uses the x-, y-, and z-components of `myVector` to set the amount of
        +   * scaling along the x-, y-, and z-axes. Doing so is the same as calling
        +   * `scale(myVector.x, myVector.y, myVector.z)`.
        +   *
        +   * By default, transformations accumulate. For example, calling `scale(1)`
        +   * twice has the same effect as calling `scale(2)` once. The
        +   * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        +   * can be used to isolate transformations within distinct drawing groups.
        +   *
        +   * Note: Transformations are reset at the beginning of the draw loop. Calling
        +   * `scale(2)` inside the <a href="#/p5/draw">draw()</a> function won't cause
        +   * shapes to grow continuously.
        +   *
        +   * @method scale
        +   * @param  {Number|p5.Vector|Number[]} s amount to scale along the positive x-axis.
        +   * @param  {Number} [y] amount to scale along the positive y-axis. Defaults to `s`.
        +   * @param  {Number} [z] amount to scale along the positive z-axis. Defaults to `y`.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'Two white squares on a gray background. The larger square appears at the top-center. The smaller square appears at the top-left.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a square at (30, 20).
        +   *   square(30, 20, 40);
        +   *
        +   *   // Scale the coordinate system by a factor of 0.5.
        +   *   scale(0.5);
        +   *
        +   *   // Draw a square at (30, 20).
        +   *   // It appears at (15, 10) after scaling.
        +   *   square(30, 20, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A rectangle and a square drawn in white on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a square at (30, 20).
        +   *   square(30, 20, 40);
        +   *
        +   *   // Scale the coordinate system by factors of
        +   *   // 0.5 along the x-axis and
        +   *   // 1.3 along the y-axis.
        +   *   scale(0.5, 1.3);
        +   *
        +   *   // Draw a square at (30, 20).
        +   *   // It appears as a rectangle at (15, 26) after scaling.
        +   *   square(30, 20, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A rectangle and a square drawn in white on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a square at (30, 20).
        +   *   square(30, 20, 40);
        +   *
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(0.5, 1.3);
        +   *
        +   *   // Scale the coordinate system by factors of
        +   *   // 0.5 along the x-axis and
        +   *   // 1.3 along the y-axis.
        +   *   scale(v);
        +   *
        +   *   // Draw a square at (30, 20).
        +   *   // It appears as a rectangle at (15, 26) after scaling.
        +   *   square(30, 20, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'A red box and a blue box drawn on a gray background. The red box appears embedded in the blue box.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the spheres.
        +   *   noStroke();
        +   *
        +   *   // Draw the red sphere.
        +   *   fill('red');
        +   *   box();
        +   *
        +   *   // Scale the coordinate system by factors of
        +   *   // 0.5 along the x-axis and
        +   *   // 1.3 along the y-axis and
        +   *   // 2 along the z-axis.
        +   *   scale(0.5, 1.3, 2);
        +   *
        +   *   // Draw the blue sphere.
        +   *   fill('blue');
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method scale
        +   * @param  {p5.Vector|Number[]} scales vector whose components should be used to scale.
        +   * @chainable
        +   */
        +  fn.scale = function(x, y, z) {
        +    // p5._validateParameters('scale', arguments);
        +    // Only check for Vector argument type if Vector is available
        +    if (x instanceof p5.Vector) {
        +      const v = x;
        +      x = v.x;
        +      y = v.y;
        +      z = v.z;
        +    } else if (Array.isArray(x)) {
        +      const rg = x;
        +      x = rg[0];
        +      y = rg[1];
        +      z = rg[2] || 1;
        +    }
        +    if (isNaN(y)) {
        +      y = z = x;
        +    } else if (isNaN(z)) {
        +      z = 1;
        +    }
         
        -/**
        - * Scales the coordinate system.
        - *
        - * By default, shapes are drawn at their original scale. A rectangle that's 50
        - * pixels wide appears to take up half the width of a 100 pixel-wide canvas.
        - * The `scale()` function can shrink or stretch the coordinate system so that
        - * shapes appear at different sizes. There are two ways to call `scale()` with
        - * parameters that set the scale factor(s).
        - *
        - * The first way to call `scale()` uses numbers to set the amount of scaling.
        - * The first parameter, `s`, sets the amount to scale each axis. For example,
        - * calling `scale(2)` stretches the x-, y-, and z-axes by a factor of 2. The
        - * next two parameters, `y` and `z`, are optional. They set the amount to
        - * scale the y- and z-axes. For example, calling `scale(2, 0.5, 1)` stretches
        - * the x-axis by a factor of 2, shrinks the y-axis by a factor of 0.5, and
        - * leaves the z-axis unchanged.
        - *
        - * The second way to call `scale()` uses a <a href="#/p5.Vector">p5.Vector</a>
        - * object to set the scale factors. For example, calling `scale(myVector)`
        - * uses the x-, y-, and z-components of `myVector` to set the amount of
        - * scaling along the x-, y-, and z-axes. Doing so is the same as calling
        - * `scale(myVector.x, myVector.y, myVector.z)`.
        - *
        - * By default, transformations accumulate. For example, calling `scale(1)`
        - * twice has the same effect as calling `scale(2)` once. The
        - * <a href="#/p5/push">push()</a> and <a href="#/p5/pop">pop()</a> functions
        - * can be used to isolate transformations within distinct drawing groups.
        - *
        - * Note: Transformations are reset at the beginning of the draw loop. Calling
        - * `scale(2)` inside the <a href="#/p5/draw">draw()</a> function won't cause
        - * shapes to grow continuously.
        - *
        - * @method scale
        - * @param  {Number|p5.Vector|Number[]} s amount to scale along the positive x-axis.
        - * @param  {Number} [y] amount to scale along the positive y-axis. Defaults to `s`.
        - * @param  {Number} [z] amount to scale along the positive z-axis. Defaults to `y`.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'Two white squares on a gray background. The larger square appears at the top-center. The smaller square appears at the top-left.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a square at (30, 20).
        - *   square(30, 20, 40);
        - *
        - *   // Scale the coordinate system by a factor of 0.5.
        - *   scale(0.5);
        - *
        - *   // Draw a square at (30, 20).
        - *   // It appears at (15, 10) after scaling.
        - *   square(30, 20, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A rectangle and a square drawn in white on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a square at (30, 20).
        - *   square(30, 20, 40);
        - *
        - *   // Scale the coordinate system by factors of
        - *   // 0.5 along the x-axis and
        - *   // 1.3 along the y-axis.
        - *   scale(0.5, 1.3);
        - *
        - *   // Draw a square at (30, 20).
        - *   // It appears as a rectangle at (15, 26) after scaling.
        - *   square(30, 20, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A rectangle and a square drawn in white on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a square at (30, 20).
        - *   square(30, 20, 40);
        - *
        - *   // Create a p5.Vector object.
        - *   let v = createVector(0.5, 1.3);
        - *
        - *   // Scale the coordinate system by factors of
        - *   // 0.5 along the x-axis and
        - *   // 1.3 along the y-axis.
        - *   scale(v);
        - *
        - *   // Draw a square at (30, 20).
        - *   // It appears as a rectangle at (15, 26) after scaling.
        - *   square(30, 20, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'A red box and a blue box drawn on a gray background. The red box appears embedded in the blue box.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the spheres.
        - *   noStroke();
        - *
        - *   // Draw the red sphere.
        - *   fill('red');
        - *   box();
        - *
        - *   // Scale the coordinate system by factors of
        - *   // 0.5 along the x-axis and
        - *   // 1.3 along the y-axis and
        - *   // 2 along the z-axis.
        - *   scale(0.5, 1.3, 2);
        - *
        - *   // Draw the blue sphere.
        - *   fill('blue');
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method scale
        - * @param  {p5.Vector|Number[]} scales vector whose components should be used to scale.
        - * @chainable
        - */
        -p5.prototype.scale = function(x, y, z) {
        -  p5._validateParameters('scale', arguments);
        -  // Only check for Vector argument type if Vector is available
        -  if (x instanceof p5.Vector) {
        -    const v = x;
        -    x = v.x;
        -    y = v.y;
        -    z = v.z;
        -  } else if (Array.isArray(x)) {
        -    const rg = x;
        -    x = rg[0];
        -    y = rg[1];
        -    z = rg[2] || 1;
        -  }
        -  if (isNaN(y)) {
        -    y = z = x;
        -  } else if (isNaN(z)) {
        -    z = 1;
        -  }
        +    this._renderer.scale(x, y, z);
         
        -  this._renderer.scale(x, y, z);
        +    return this;
        +  };
         
        -  return this;
        -};
        +  /**
        +   * Shears the x-axis so that shapes appear skewed.
        +   *
        +   * By default, the x- and y-axes are perpendicular. The `shearX()` function
        +   * transforms the coordinate system so that x-coordinates are translated while
        +   * y-coordinates are fixed.
        +   *
        +   * The first parameter, `angle`, is the amount to shear. For example, calling
        +   * `shearX(1)` transforms all x-coordinates using the formula
        +   * `x = x + y * tan(angle)`. `shearX()` interprets angle values using the
        +   * current <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * By default, transformations accumulate. For example, calling
        +   * `shearX(1)` twice has the same effect as calling `shearX(2)` once. The
        +   * <a href="#/p5/push">push()</a> and
        +   * <a href="#/p5/pop">pop()</a> functions can be used to isolate
        +   * transformations within distinct drawing groups.
        +   *
        +   * Note: Transformations are reset at the beginning of the draw loop. Calling
        +   * `shearX(1)` inside the <a href="#/p5/draw">draw()</a> function won't
        +   * cause shapes to shear continuously.
        +   *
        +   * @method shearX
        +   * @param  {Number} angle angle to shear by in the current <a href="#/p5/angleMode">angleMode()</a>.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white quadrilateral on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Shear the coordinate system along the x-axis.
        +   *   shearX(QUARTER_PI);
        +   *
        +   *   // Draw the square.
        +   *   square(0, 0, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   describe('A white quadrilateral on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Shear the coordinate system along the x-axis.
        +   *   shearX(45);
        +   *
        +   *   // Draw the square.
        +   *   square(0, 0, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.shearX = function(angle) {
        +    // p5._validateParameters('shearX', arguments);
        +    const rad = this._toRadians(angle);
        +    this._renderer.applyMatrix(1, 0, Math.tan(rad), 1, 0, 0);
        +    return this;
        +  };
         
        -/**
        - * Shears the x-axis so that shapes appear skewed.
        - *
        - * By default, the x- and y-axes are perpendicular. The `shearX()` function
        - * transforms the coordinate system so that x-coordinates are translated while
        - * y-coordinates are fixed.
        - *
        - * The first parameter, `angle`, is the amount to shear. For example, calling
        - * `shearX(1)` transforms all x-coordinates using the formula
        - * `x = x + y * tan(angle)`. `shearX()` interprets angle values using the
        - * current <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * By default, transformations accumulate. For example, calling
        - * `shearX(1)` twice has the same effect as calling `shearX(2)` once. The
        - * <a href="#/p5/push">push()</a> and
        - * <a href="#/p5/pop">pop()</a> functions can be used to isolate
        - * transformations within distinct drawing groups.
        - *
        - * Note: Transformations are reset at the beginning of the draw loop. Calling
        - * `shearX(1)` inside the <a href="#/p5/draw">draw()</a> function won't
        - * cause shapes to shear continuously.
        - *
        - * @method shearX
        - * @param  {Number} angle angle to shear by in the current <a href="#/p5/angleMode">angleMode()</a>.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white quadrilateral on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Shear the coordinate system along the x-axis.
        - *   shearX(QUARTER_PI);
        - *
        - *   // Draw the square.
        - *   square(0, 0, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   describe('A white quadrilateral on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Shear the coordinate system along the x-axis.
        - *   shearX(45);
        - *
        - *   // Draw the square.
        - *   square(0, 0, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.shearX = function(angle) {
        -  p5._validateParameters('shearX', arguments);
        -  const rad = this._toRadians(angle);
        -  this._renderer.applyMatrix(1, 0, Math.tan(rad), 1, 0, 0);
        -  return this;
        -};
        +  /**
        +   * Shears the y-axis so that shapes appear skewed.
        +   *
        +   * By default, the x- and y-axes are perpendicular. The `shearY()` function
        +   * transforms the coordinate system so that y-coordinates are translated while
        +   * x-coordinates are fixed.
        +   *
        +   * The first parameter, `angle`, is the amount to shear. For example, calling
        +   * `shearY(1)` transforms all y-coordinates using the formula
        +   * `y = y + x * tan(angle)`. `shearY()` interprets angle values using the
        +   * current <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * By default, transformations accumulate. For example, calling
        +   * `shearY(1)` twice has the same effect as calling `shearY(2)` once. The
        +   * <a href="#/p5/push">push()</a> and
        +   * <a href="#/p5/pop">pop()</a> functions can be used to isolate
        +   * transformations within distinct drawing groups.
        +   *
        +   * Note: Transformations are reset at the beginning of the draw loop. Calling
        +   * `shearY(1)` inside the <a href="#/p5/draw">draw()</a> function won't
        +   * cause shapes to shear continuously.
        +   *
        +   * @method shearY
        +   * @param  {Number} angle angle to shear by in the current <a href="#/p5/angleMode">angleMode()</a>.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white quadrilateral on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Shear the coordinate system along the x-axis.
        +   *   shearY(QUARTER_PI);
        +   *
        +   *   // Draw the square.
        +   *   square(0, 0, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   describe('A white quadrilateral on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Shear the coordinate system along the x-axis.
        +   *   shearY(45);
        +   *
        +   *   // Draw the square.
        +   *   square(0, 0, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.shearY = function(angle) {
        +    // p5._validateParameters('shearY', arguments);
        +    const rad = this._toRadians(angle);
        +    this._renderer.applyMatrix(1, Math.tan(rad), 0, 1, 0, 0);
        +    return this;
        +  };
         
        -/**
        - * Shears the y-axis so that shapes appear skewed.
        - *
        - * By default, the x- and y-axes are perpendicular. The `shearY()` function
        - * transforms the coordinate system so that y-coordinates are translated while
        - * x-coordinates are fixed.
        - *
        - * The first parameter, `angle`, is the amount to shear. For example, calling
        - * `shearY(1)` transforms all y-coordinates using the formula
        - * `y = y + x * tan(angle)`. `shearY()` interprets angle values using the
        - * current <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * By default, transformations accumulate. For example, calling
        - * `shearY(1)` twice has the same effect as calling `shearY(2)` once. The
        - * <a href="#/p5/push">push()</a> and
        - * <a href="#/p5/pop">pop()</a> functions can be used to isolate
        - * transformations within distinct drawing groups.
        - *
        - * Note: Transformations are reset at the beginning of the draw loop. Calling
        - * `shearY(1)` inside the <a href="#/p5/draw">draw()</a> function won't
        - * cause shapes to shear continuously.
        - *
        - * @method shearY
        - * @param  {Number} angle angle to shear by in the current <a href="#/p5/angleMode">angleMode()</a>.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white quadrilateral on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Shear the coordinate system along the x-axis.
        - *   shearY(QUARTER_PI);
        - *
        - *   // Draw the square.
        - *   square(0, 0, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   describe('A white quadrilateral on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Shear the coordinate system along the x-axis.
        - *   shearY(45);
        - *
        - *   // Draw the square.
        - *   square(0, 0, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.shearY = function(angle) {
        -  p5._validateParameters('shearY', arguments);
        -  const rad = this._toRadians(angle);
        -  this._renderer.applyMatrix(1, Math.tan(rad), 0, 1, 0, 0);
        -  return this;
        -};
        +  /**
        +   * Translates the coordinate system.
        +   *
        +   * By default, the origin `(0, 0)` is at the sketch's top-left corner in 2D
        +   * mode and center in WebGL mode. The `translate()` function shifts the origin
        +   * to a different position. Everything drawn after `translate()` is called
        +   * will appear to be shifted. There are two ways to call `translate()` with
        +   * parameters that set the origin's position.
        +   *
        +   * The first way to call `translate()` uses numbers to set the amount of
        +   * translation. The first two parameters, `x` and `y`, set the amount to
        +   * translate along the positive x- and y-axes. For example, calling
        +   * `translate(20, 30)` translates the origin 20 pixels along the x-axis and 30
        +   * pixels along the y-axis. The third parameter, `z`, is optional. It sets the
        +   * amount to translate along the positive z-axis. For example, calling
        +   * `translate(20, 30, 40)` translates the origin 20 pixels along the x-axis,
        +   * 30 pixels along the y-axis, and 40 pixels along the z-axis.
        +   *
        +   * The second way to call `translate()` uses a
        +   * <a href="#/p5.Vector">p5.Vector</a> object to set the amount of
        +   * translation. For example, calling `translate(myVector)` uses the x-, y-,
        +   * and z-components of `myVector` to set the amount to translate along the x-,
        +   * y-, and z-axes. Doing so is the same as calling
        +   * `translate(myVector.x, myVector.y, myVector.z)`.
        +   *
        +   * By default, transformations accumulate. For example, calling
        +   * `translate(10, 0)` twice has the same effect as calling
        +   * `translate(20, 0)` once. The <a href="#/p5/push">push()</a> and
        +   * <a href="#/p5/pop">pop()</a> functions can be used to isolate
        +   * transformations within distinct drawing groups.
        +   *
        +   * Note: Transformations are reset at the beginning of the draw loop. Calling
        +   * `translate(10, 0)` inside the <a href="#/p5/draw">draw()</a> function won't
        +   * cause shapes to move continuously.
        +   *
        +   * @method translate
        +   * @param  {Number} x amount to translate along the positive x-axis.
        +   * @param  {Number} y amount to translate along the positive y-axis.
        +   * @param  {Number} [z] amount to translate along the positive z-axis.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white circle on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin to the center.
        +   *   translate(50, 50);
        +   *
        +   *   // Draw a circle at coordinates (0, 0).
        +   *   circle(0, 0, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'Two circles drawn on a gray background. The blue circle on the right overlaps the red circle at the center.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin to the center.
        +   *   translate(50, 50);
        +   *
        +   *   // Draw the red circle.
        +   *   fill('red');
        +   *   circle(0, 0, 40);
        +   *
        +   *   // Translate the origin to the right.
        +   *   translate(25, 0);
        +   *
        +   *   // Draw the blue circle.
        +   *   fill('blue');
        +   *   circle(0, 0, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white circle moves slowly from left to right on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the x-coordinate.
        +   *   let x = frameCount * 0.2;
        +   *
        +   *   // Translate the origin.
        +   *   translate(x, 50);
        +   *
        +   *   // Draw a circle at coordinates (0, 0).
        +   *   circle(0, 0, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white circle on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(50, 50);
        +   *
        +   *   // Translate the origin by the vector.
        +   *   translate(v);
        +   *
        +   *   // Draw a circle at coordinates (0, 0).
        +   *   circle(0, 0, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'Two spheres sitting side-by-side on gray background. The sphere at the center is red. The sphere on the right is blue.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the spheres.
        +   *   noStroke();
        +   *
        +   *   // Draw the red sphere.
        +   *   fill('red');
        +   *   sphere(10);
        +   *
        +   *   // Translate the origin to the right.
        +   *   translate(30, 0, 0);
        +   *
        +   *   // Draw the blue sphere.
        +   *   fill('blue');
        +   *   sphere(10);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method translate
        +   * @param  {p5.Vector} vector vector by which to translate.
        +   * @chainable
        +   */
        +  fn.translate = function(x, y, z) {
        +    // p5._validateParameters('translate', arguments);
        +    if (this._renderer.isP3D) {
        +      this._renderer.translate(x, y, z);
        +    } else {
        +      this._renderer.translate(x, y);
        +    }
        +    return this;
        +  };
         
        -/**
        - * Translates the coordinate system.
        - *
        - * By default, the origin `(0, 0)` is at the sketch's top-left corner in 2D
        - * mode and center in WebGL mode. The `translate()` function shifts the origin
        - * to a different position. Everything drawn after `translate()` is called
        - * will appear to be shifted. There are two ways to call `translate()` with
        - * parameters that set the origin's position.
        - *
        - * The first way to call `translate()` uses numbers to set the amount of
        - * translation. The first two parameters, `x` and `y`, set the amount to
        - * translate along the positive x- and y-axes. For example, calling
        - * `translate(20, 30)` translates the origin 20 pixels along the x-axis and 30
        - * pixels along the y-axis. The third parameter, `z`, is optional. It sets the
        - * amount to translate along the positive z-axis. For example, calling
        - * `translate(20, 30, 40)` translates the origin 20 pixels along the x-axis,
        - * 30 pixels along the y-axis, and 40 pixels along the z-axis.
        - *
        - * The second way to call `translate()` uses a
        - * <a href="#/p5.Vector">p5.Vector</a> object to set the amount of
        - * translation. For example, calling `translate(myVector)` uses the x-, y-,
        - * and z-components of `myVector` to set the amount to translate along the x-,
        - * y-, and z-axes. Doing so is the same as calling
        - * `translate(myVector.x, myVector.y, myVector.z)`.
        - *
        - * By default, transformations accumulate. For example, calling
        - * `translate(10, 0)` twice has the same effect as calling
        - * `translate(20, 0)` once. The <a href="#/p5/push">push()</a> and
        - * <a href="#/p5/pop">pop()</a> functions can be used to isolate
        - * transformations within distinct drawing groups.
        - *
        - * Note: Transformations are reset at the beginning of the draw loop. Calling
        - * `translate(10, 0)` inside the <a href="#/p5/draw">draw()</a> function won't
        - * cause shapes to move continuously.
        - *
        - * @method translate
        - * @param  {Number} x amount to translate along the positive x-axis.
        - * @param  {Number} y amount to translate along the positive y-axis.
        - * @param  {Number} [z] amount to translate along the positive z-axis.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white circle on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin to the center.
        - *   translate(50, 50);
        - *
        - *   // Draw a circle at coordinates (0, 0).
        - *   circle(0, 0, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'Two circles drawn on a gray background. The blue circle on the right overlaps the red circle at the center.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin to the center.
        - *   translate(50, 50);
        - *
        - *   // Draw the red circle.
        - *   fill('red');
        - *   circle(0, 0, 40);
        - *
        - *   // Translate the origin to the right.
        - *   translate(25, 0);
        - *
        - *   // Draw the blue circle.
        - *   fill('blue');
        - *   circle(0, 0, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white circle moves slowly from left to right on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the x-coordinate.
        - *   let x = frameCount * 0.2;
        - *
        - *   // Translate the origin.
        - *   translate(x, 50);
        - *
        - *   // Draw a circle at coordinates (0, 0).
        - *   circle(0, 0, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white circle on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create a p5.Vector object.
        - *   let v = createVector(50, 50);
        - *
        - *   // Translate the origin by the vector.
        - *   translate(v);
        - *
        - *   // Draw a circle at coordinates (0, 0).
        - *   circle(0, 0, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'Two spheres sitting side-by-side on gray background. The sphere at the center is red. The sphere on the right is blue.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the spheres.
        - *   noStroke();
        - *
        - *   // Draw the red sphere.
        - *   fill('red');
        - *   sphere(10);
        - *
        - *   // Translate the origin to the right.
        - *   translate(30, 0, 0);
        - *
        - *   // Draw the blue sphere.
        - *   fill('blue');
        - *   sphere(10);
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method translate
        - * @param  {p5.Vector} vector vector by which to translate.
        - * @chainable
        - */
        -p5.prototype.translate = function(x, y, z) {
        -  p5._validateParameters('translate', arguments);
        -  if (this._renderer.isP3D) {
        -    this._renderer.translate(x, y, z);
        -  } else {
        -    this._renderer.translate(x, y);
        -  }
        -  return this;
        -};
        +  /**
        +   * Begins a drawing group that contains its own styles and transformations.
        +   *
        +   * By default, styles such as <a href="#/p5/fill">fill()</a> and
        +   * transformations such as <a href="#/p5/rotate">rotate()</a> are applied to
        +   * all drawing that follows. The `push()` and <a href="#/p5/pop">pop()</a>
        +   * functions can limit the effect of styles and transformations to a specific
        +   * group of shapes, images, and text. For example, a group of shapes could be
        +   * translated to follow the mouse without affecting the rest of the sketch:
        +   *
        +   * ```js
        +   * // Begin the drawing group.
        +   * push();
        +   *
        +   * // Translate the origin to the mouse's position.
        +   * translate(mouseX, mouseY);
        +   *
        +   * // Style the face.
        +   * noStroke();
        +   * fill('green');
        +   *
        +   * // Draw the face.
        +   * circle(0, 0, 60);
        +   *
        +   * // Style the eyes.
        +   * fill('white');
        +   *
        +   * // Draw the left eye.
        +   * ellipse(-20, -20, 30, 20);
        +   *
        +   * // Draw the right eye.
        +   * ellipse(20, -20, 30, 20);
        +   *
        +   * // End the drawing group.
        +   * pop();
        +   *
        +   * // Draw a bug.
        +   * let x = random(0, 100);
        +   * let y = random(0, 100);
        +   * text('🦟', x, y);
        +   * ```
        +   *
        +   * In the code snippet above, the bug's position isn't affected by
        +   * `translate(mouseX, mouseY)` because that transformation is contained
        +   * between `push()` and <a href="#/p5/pop">pop()</a>. The bug moves around
        +   * the entire canvas as expected.
        +   *
        +   * Note: `push()` and <a href="#/p5/pop">pop()</a> are always called as a
        +   * pair. Both functions are required to begin and end a drawing group.
        +   *
        +   * `push()` and <a href="#/p5/pop">pop()</a> can also be nested to create
        +   * subgroups. For example, the code snippet above could be changed to give
        +   * more detail to the frog’s eyes:
        +   *
        +   * ```js
        +   * // Begin the drawing group.
        +   * push();
        +   *
        +   * // Translate the origin to the mouse's position.
        +   * translate(mouseX, mouseY);
        +   *
        +   * // Style the face.
        +   * noStroke();
        +   * fill('green');
        +   *
        +   * // Draw a face.
        +   * circle(0, 0, 60);
        +   *
        +   * // Style the eyes.
        +   * fill('white');
        +   *
        +   * // Draw the left eye.
        +   * push();
        +   * translate(-20, -20);
        +   * ellipse(0, 0, 30, 20);
        +   * fill('black');
        +   * circle(0, 0, 8);
        +   * pop();
        +   *
        +   * // Draw the right eye.
        +   * push();
        +   * translate(20, -20);
        +   * ellipse(0, 0, 30, 20);
        +   * fill('black');
        +   * circle(0, 0, 8);
        +   * pop();
        +   *
        +   * // End the drawing group.
        +   * pop();
        +   *
        +   * // Draw a bug.
        +   * let x = random(0, 100);
        +   * let y = random(0, 100);
        +   * text('🦟', x, y);
        +   * ```
        +   *
        +   * In this version, the code to draw each eye is contained between its own
        +   * `push()` and <a href="#/p5/pop">pop()</a> functions. Doing so makes it
        +   * easier to add details in the correct part of a drawing.
        +   *
        +   * `push()` and <a href="#/p5/pop">pop()</a> contain the effects of the
        +   * following functions:
        +   *
        +   * - <a href="#/p5/fill">fill()</a>
        +   * - <a href="#/p5/noFill">noFill()</a>
        +   * - <a href="#/p5/noStroke">noStroke()</a>
        +   * - <a href="#/p5/stroke">stroke()</a>
        +   * - <a href="#/p5/tint">tint()</a>
        +   * - <a href="#/p5/noTint">noTint()</a>
        +   * - <a href="#/p5/strokeWeight">strokeWeight()</a>
        +   * - <a href="#/p5/strokeCap">strokeCap()</a>
        +   * - <a href="#/p5/strokeJoin">strokeJoin()</a>
        +   * - <a href="#/p5/imageMode">imageMode()</a>
        +   * - <a href="#/p5/rectMode">rectMode()</a>
        +   * - <a href="#/p5/ellipseMode">ellipseMode()</a>
        +   * - <a href="#/p5/colorMode">colorMode()</a>
        +   * - <a href="#/p5/textAlign">textAlign()</a>
        +   * - <a href="#/p5/textFont">textFont()</a>
        +   * - <a href="#/p5/textSize">textSize()</a>
        +   * - <a href="#/p5/textLeading">textLeading()</a>
        +   * - <a href="#/p5/applyMatrix">applyMatrix()</a>
        +   * - <a href="#/p5/resetMatrix">resetMatrix()</a>
        +   * - <a href="#/p5/rotate">rotate()</a>
        +   * - <a href="#/p5/scale">scale()</a>
        +   * - <a href="#/p5/shearX">shearX()</a>
        +   * - <a href="#/p5/shearY">shearY()</a>
        +   * - <a href="#/p5/translate">translate()</a>
        +   *
        +   * In WebGL mode, `push()` and <a href="#/p5/pop">pop()</a> contain the
        +   * effects of a few additional styles:
        +   *
        +   * - <a href="#/p5/setCamera">setCamera()</a>
        +   * - <a href="#/p5/ambientLight">ambientLight()</a>
        +   * - <a href="#/p5/directionalLight">directionalLight()</a>
        +   * - <a href="#/p5/pointLight">pointLight()</a> <a href="#/p5/texture">texture()</a>
        +   * - <a href="#/p5/specularMaterial">specularMaterial()</a>
        +   * - <a href="#/p5/shininess">shininess()</a>
        +   * - <a href="#/p5/normalMaterial">normalMaterial()</a>
        +   * - <a href="#/p5/shader">shader()</a>
        +   *
        +   * @method push
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw the left circle.
        +   *   circle(25, 50, 20);
        +   *
        +   *   // Begin the drawing group.
        +   *   push();
        +   *
        +   *   // Translate the origin to the center.
        +   *   translate(50, 50);
        +   *
        +   *   // Style the circle.
        +   *   strokeWeight(5);
        +   *   stroke('royalblue');
        +   *   fill('orange');
        +   *
        +   *   // Draw the circle.
        +   *   circle(0, 0, 20);
        +   *
        +   *   // End the drawing group.
        +   *   pop();
        +   *
        +   *   // Draw the right circle.
        +   *   circle(75, 50, 20);
        +   *
        +   *   describe(
        +   *     'Three circles drawn in a row on a gray background. The left and right circles are white with thin, black borders. The middle circle is orange with a thick, blue border.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(24);
        +   *
        +   *   describe('A mosquito buzzes in front of a green frog. The frog follows the mouse as the user moves.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Begin the drawing group.
        +   *   push();
        +   *
        +   *   // Translate the origin to the mouse's position.
        +   *   translate(mouseX, mouseY);
        +   *
        +   *   // Style the face.
        +   *   noStroke();
        +   *   fill('green');
        +   *
        +   *   // Draw a face.
        +   *   circle(0, 0, 60);
        +   *
        +   *   // Style the eyes.
        +   *   fill('white');
        +   *
        +   *   // Draw the left eye.
        +   *   push();
        +   *   translate(-20, -20);
        +   *   ellipse(0, 0, 30, 20);
        +   *   fill('black');
        +   *   circle(0, 0, 8);
        +   *   pop();
        +   *
        +   *   // Draw the right eye.
        +   *   push();
        +   *   translate(20, -20);
        +   *   ellipse(0, 0, 30, 20);
        +   *   fill('black');
        +   *   circle(0, 0, 8);
        +   *   pop();
        +   *
        +   *   // End the drawing group.
        +   *   pop();
        +   *
        +   *   // Draw a bug.
        +   *   let x = random(0, 100);
        +   *   let y = random(0, 100);
        +   *   text('🦟', x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'Two spheres drawn on a gray background. The sphere on the left is red and lit from the front. The sphere on the right is a blue wireframe.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the red sphere.
        +   *   push();
        +   *   translate(-25, 0, 0);
        +   *   noStroke();
        +   *   directionalLight(255, 0, 0, 0, 0, -1);
        +   *   sphere(20);
        +   *   pop();
        +   *
        +   *   // Draw the blue sphere.
        +   *   push();
        +   *   translate(25, 0, 0);
        +   *   strokeWeight(0.3);
        +   *   stroke(0, 0, 255);
        +   *   noFill();
        +   *   sphere(20);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.push = function() {
        +    this._renderer.push();
        +  };
        +
        +  /**
        +   * Ends a drawing group that contains its own styles and transformations.
        +   *
        +   * By default, styles such as <a href="#/p5/fill">fill()</a> and
        +   * transformations such as <a href="#/p5/rotate">rotate()</a> are applied to
        +   * all drawing that follows. The <a href="#/p5/push">push()</a> and `pop()`
        +   * functions can limit the effect of styles and transformations to a specific
        +   * group of shapes, images, and text. For example, a group of shapes could be
        +   * translated to follow the mouse without affecting the rest of the sketch:
        +   *
        +   * ```js
        +   * // Begin the drawing group.
        +   * push();
        +   *
        +   * // Translate the origin to the mouse's position.
        +   * translate(mouseX, mouseY);
        +   *
        +   * // Style the face.
        +   * noStroke();
        +   * fill('green');
        +   *
        +   * // Draw the face.
        +   * circle(0, 0, 60);
        +   *
        +   * // Style the eyes.
        +   * fill('white');
        +   *
        +   * // Draw the left eye.
        +   * ellipse(-20, -20, 30, 20);
        +   *
        +   * // Draw the right eye.
        +   * ellipse(20, -20, 30, 20);
        +   *
        +   * // End the drawing group.
        +   * pop();
        +   *
        +   * // Draw a bug.
        +   * let x = random(0, 100);
        +   * let y = random(0, 100);
        +   * text('🦟', x, y);
        +   * ```
        +   *
        +   * In the code snippet above, the bug's position isn't affected by
        +   * `translate(mouseX, mouseY)` because that transformation is contained
        +   * between <a href="#/p5/push">push()</a> and `pop()`. The bug moves around
        +   * the entire canvas as expected.
        +   *
        +   * Note: <a href="#/p5/push">push()</a> and `pop()` are always called as a
        +   * pair. Both functions are required to begin and end a drawing group.
        +   *
        +   * <a href="#/p5/push">push()</a> and `pop()` can also be nested to create
        +   * subgroups. For example, the code snippet above could be changed to give
        +   * more detail to the frog’s eyes:
        +   *
        +   * ```js
        +   * // Begin the drawing group.
        +   * push();
        +   *
        +   * // Translate the origin to the mouse's position.
        +   * translate(mouseX, mouseY);
        +   *
        +   * // Style the face.
        +   * noStroke();
        +   * fill('green');
        +   *
        +   * // Draw a face.
        +   * circle(0, 0, 60);
        +   *
        +   * // Style the eyes.
        +   * fill('white');
        +   *
        +   * // Draw the left eye.
        +   * push();
        +   * translate(-20, -20);
        +   * ellipse(0, 0, 30, 20);
        +   * fill('black');
        +   * circle(0, 0, 8);
        +   * pop();
        +   *
        +   * // Draw the right eye.
        +   * push();
        +   * translate(20, -20);
        +   * ellipse(0, 0, 30, 20);
        +   * fill('black');
        +   * circle(0, 0, 8);
        +   * pop();
        +   *
        +   * // End the drawing group.
        +   * pop();
        +   *
        +   * // Draw a bug.
        +   * let x = random(0, 100);
        +   * let y = random(0, 100);
        +   * text('🦟', x, y);
        +   * ```
        +   *
        +   * In this version, the code to draw each eye is contained between its own
        +   * <a href="#/p5/push">push()</a> and `pop()` functions. Doing so makes it
        +   * easier to add details in the correct part of a drawing.
        +   *
        +   * <a href="#/p5/push">push()</a> and `pop()` contain the effects of the
        +   * following functions:
        +   *
        +   * - <a href="#/p5/fill">fill()</a>
        +   * - <a href="#/p5/noFill">noFill()</a>
        +   * - <a href="#/p5/noStroke">noStroke()</a>
        +   * - <a href="#/p5/stroke">stroke()</a>
        +   * - <a href="#/p5/tint">tint()</a>
        +   * - <a href="#/p5/noTint">noTint()</a>
        +   * - <a href="#/p5/strokeWeight">strokeWeight()</a>
        +   * - <a href="#/p5/strokeCap">strokeCap()</a>
        +   * - <a href="#/p5/strokeJoin">strokeJoin()</a>
        +   * - <a href="#/p5/imageMode">imageMode()</a>
        +   * - <a href="#/p5/rectMode">rectMode()</a>
        +   * - <a href="#/p5/ellipseMode">ellipseMode()</a>
        +   * - <a href="#/p5/colorMode">colorMode()</a>
        +   * - <a href="#/p5/textAlign">textAlign()</a>
        +   * - <a href="#/p5/textFont">textFont()</a>
        +   * - <a href="#/p5/textSize">textSize()</a>
        +   * - <a href="#/p5/textLeading">textLeading()</a>
        +   * - <a href="#/p5/applyMatrix">applyMatrix()</a>
        +   * - <a href="#/p5/resetMatrix">resetMatrix()</a>
        +   * - <a href="#/p5/rotate">rotate()</a>
        +   * - <a href="#/p5/scale">scale()</a>
        +   * - <a href="#/p5/shearX">shearX()</a>
        +   * - <a href="#/p5/shearY">shearY()</a>
        +   * - <a href="#/p5/translate">translate()</a>
        +   *
        +   * In WebGL mode, <a href="#/p5/push">push()</a> and `pop()` contain the
        +   * effects of a few additional styles:
        +   *
        +   * - <a href="#/p5/setCamera">setCamera()</a>
        +   * - <a href="#/p5/ambientLight">ambientLight()</a>
        +   * - <a href="#/p5/directionalLight">directionalLight()</a>
        +   * - <a href="#/p5/pointLight">pointLight()</a> <a href="#/p5/texture">texture()</a>
        +   * - <a href="#/p5/specularMaterial">specularMaterial()</a>
        +   * - <a href="#/p5/shininess">shininess()</a>
        +   * - <a href="#/p5/normalMaterial">normalMaterial()</a>
        +   * - <a href="#/p5/shader">shader()</a>
        +   *
        +   * @method pop
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw the left circle.
        +   *   circle(25, 50, 20);
        +   *
        +   *   // Begin the drawing group.
        +   *   push();
        +   *
        +   *   // Translate the origin to the center.
        +   *   translate(50, 50);
        +   *
        +   *   // Style the circle.
        +   *   strokeWeight(5);
        +   *   stroke('royalblue');
        +   *   fill('orange');
        +   *
        +   *   // Draw the circle.
        +   *   circle(0, 0, 20);
        +   *
        +   *   // End the drawing group.
        +   *   pop();
        +   *
        +   *   // Draw the right circle.
        +   *   circle(75, 50, 20);
        +   *
        +   *   describe(
        +   *     'Three circles drawn in a row on a gray background. The left and right circles are white with thin, black borders. The middle circle is orange with a thick, blue border.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(24);
        +   *
        +   *   describe('A mosquito buzzes in front of a green frog. The frog follows the mouse as the user moves.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Begin the drawing group.
        +   *   push();
        +   *
        +   *   // Translate the origin to the mouse's position.
        +   *   translate(mouseX, mouseY);
        +   *
        +   *   // Style the face.
        +   *   noStroke();
        +   *   fill('green');
        +   *
        +   *   // Draw a face.
        +   *   circle(0, 0, 60);
        +   *
        +   *   // Style the eyes.
        +   *   fill('white');
        +   *
        +   *   // Draw the left eye.
        +   *   push();
        +   *   translate(-20, -20);
        +   *   ellipse(0, 0, 30, 20);
        +   *   fill('black');
        +   *   circle(0, 0, 8);
        +   *   pop();
        +   *
        +   *   // Draw the right eye.
        +   *   push();
        +   *   translate(20, -20);
        +   *   ellipse(0, 0, 30, 20);
        +   *   fill('black');
        +   *   circle(0, 0, 8);
        +   *   pop();
        +   *
        +   *   // End the drawing group.
        +   *   pop();
        +   *
        +   *   // Draw a bug.
        +   *   let x = random(0, 100);
        +   *   let y = random(0, 100);
        +   *   text('🦟', x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'Two spheres drawn on a gray background. The sphere on the left is red and lit from the front. The sphere on the right is a blue wireframe.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the red sphere.
        +   *   push();
        +   *   translate(-25, 0, 0);
        +   *   noStroke();
        +   *   directionalLight(255, 0, 0, 0, 0, -1);
        +   *   sphere(20);
        +   *   pop();
        +   *
        +   *   // Draw the blue sphere.
        +   *   push();
        +   *   translate(25, 0, 0);
        +   *   strokeWeight(0.3);
        +   *   stroke(0, 0, 255);
        +   *   noFill();
        +   *   sphere(20);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.pop = function() {
        +    this._renderer.pop();
        +  };
        +}
        +
        +export default transform;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  transform(p5, p5.prototype);
        +}
        diff --git a/src/data/index.js b/src/data/index.js
        new file mode 100644
        index 0000000000..6b80d2be0c
        --- /dev/null
        +++ b/src/data/index.js
        @@ -0,0 +1,7 @@
        +import storage from './local_storage.js';
        +import typedDict from './p5.TypedDict.js';
        +
        +export default function(p5){
        +  p5.registerAddon(storage);
        +  p5.registerAddon(typedDict);
        +}
        diff --git a/src/data/local_storage.js b/src/data/local_storage.js
        index a34f3172fa..09c4ff8749 100644
        --- a/src/data/local_storage.js
        +++ b/src/data/local_storage.js
        @@ -6,448 +6,455 @@
          * This module defines the p5 methods for working with local storage
          */
         
        -import p5 from '../core/main';
        -/**
        - * Stores a value in the web browser's local storage.
        - *
        - * Web browsers can save small amounts of data using the built-in
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" target="_blank">localStorage object</a>.
        - * Data stored in `localStorage` can be retrieved at any point, even after
        - * refreshing a page or restarting the browser. Data are stored as key-value
        - * pairs.
        - *
        - * `storeItem()` makes it easy to store values in `localStorage` and
        - * <a href="#/p5/getItem">getItem()</a> makes it easy to retrieve them.
        - *
        - * The first parameter, `key`, is the name of the value to be stored as a
        - * string.
        - *
        - * The second parameter, `value`, is the value to be stored. Values can have
        - * any type.
        - *
        - * Note: Sensitive data such as passwords or personal information shouldn't be
        - * stored in `localStorage`.
        - *
        - * @method storeItem
        - * @for p5
        - * @param {String} key name of the value.
        - * @param {String|Number|Boolean|Object|Array} value value to be stored.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Store the player's name.
        - *   storeItem('name', 'Feist');
        - *
        - *   // Store the player's score.
        - *   storeItem('score', 1234);
        - *
        - *   describe('The text "Feist: 1234" written in black on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(14);
        - *
        - *   // Retrieve the name.
        - *   let name = getItem('name');
        - *
        - *   // Retrieve the score.
        - *   let score = getItem('score');
        - *
        - *   // Display the score.
        - *   text(`${name}: ${score}`, 50, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create an object.
        - *   let p = { x: 50, y: 50 };
        - *
        - *   // Store the object.
        - *   storeItem('position', p);
        - *
        - *   describe('A white circle on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Retrieve the object.
        - *   let p = getItem('position');
        - *
        - *   // Draw the circle.
        - *   circle(p.x, p.y, 30);
        - * }
        - * </code>
        - * </div>
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color('deeppink');
        - *
        - *   // Store the object.
        - *   storeItem('color', c);
        - *
        - *   describe('A pink circle on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Retrieve the object.
        - *   let c = getItem('color');
        - *
        - *   // Style the circle.
        - *   fill(c);
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, 30);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.storeItem = function(key, value) {
        -  if (typeof key !== 'string') {
        -    console.log(
        -      `The argument that you passed to storeItem() - ${key} is not a string.`
        -    );
        -  }
        -  if (key.endsWith('p5TypeID')) {
        -    console.log(
        -      `The argument that you passed to storeItem() - ${key} must not end with 'p5TypeID'.`
        -    );
        -  }
        -
        -  if (typeof value === 'undefined') {
        -    console.log('You cannot store undefined variables using storeItem().');
        -  }
        -  let type = typeof value;
        -  switch (type) {
        -    case 'number':
        -    case 'boolean':
        -      value = value.toString();
        -      break;
        -    case 'object':
        -      if (value instanceof p5.Color) {
        -        type = 'p5.Color';
        -      } else if (value instanceof p5.Vector) {
        -        type = 'p5.Vector';
        -        const coord = [value.x, value.y, value.z];
        -        value = coord;
        -      }
        -      value = JSON.stringify(value);
        -      break;
        -    case 'string':
        -    default:
        -      break;
        -  }
        -
        -  localStorage.setItem(key, value);
        -  const typeKey = `${key}p5TypeID`;
        -  localStorage.setItem(typeKey, type);
        -};
        +function storage(p5, fn){
        +  /**
        +   * Stores a value in the web browser's local storage.
        +   *
        +   * Web browsers can save small amounts of data using the built-in
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" target="_blank">localStorage object</a>.
        +   * Data stored in `localStorage` can be retrieved at any point, even after
        +   * refreshing a page or restarting the browser. Data are stored as key-value
        +   * pairs.
        +   *
        +   * `storeItem()` makes it easy to store values in `localStorage` and
        +   * <a href="#/p5/getItem">getItem()</a> makes it easy to retrieve them.
        +   *
        +   * The first parameter, `key`, is the name of the value to be stored as a
        +   * string.
        +   *
        +   * The second parameter, `value`, is the value to be stored. Values can have
        +   * any type.
        +   *
        +   * Note: Sensitive data such as passwords or personal information shouldn't be
        +   * stored in `localStorage`.
        +   *
        +   * @method storeItem
        +   * @for p5
        +   * @param {String} key name of the value.
        +   * @param {String|Number|Boolean|Object|Array} value value to be stored.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Store the player's name.
        +   *   storeItem('name', 'Feist');
        +   *
        +   *   // Store the player's score.
        +   *   storeItem('score', 1234);
        +   *
        +   *   describe('The text "Feist: 1234" written in black on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(14);
        +   *
        +   *   // Retrieve the name.
        +   *   let name = getItem('name');
        +   *
        +   *   // Retrieve the score.
        +   *   let score = getItem('score');
        +   *
        +   *   // Display the score.
        +   *   text(`${name}: ${score}`, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create an object.
        +   *   let p = { x: 50, y: 50 };
        +   *
        +   *   // Store the object.
        +   *   storeItem('position', p);
        +   *
        +   *   describe('A white circle on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Retrieve the object.
        +   *   let p = getItem('position');
        +   *
        +   *   // Draw the circle.
        +   *   circle(p.x, p.y, 30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color('deeppink');
        +   *
        +   *   // Store the object.
        +   *   storeItem('color', c);
        +   *
        +   *   describe('A pink circle on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Retrieve the object.
        +   *   let c = getItem('color');
        +   *
        +   *   // Style the circle.
        +   *   fill(c);
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, 30);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.storeItem = function(key, value) {
        +    if (typeof key !== 'string') {
        +      console.log(
        +        `The argument that you passed to storeItem() - ${key} is not a string.`
        +      );
        +    }
        +    if (key.endsWith('p5TypeID')) {
        +      console.log(
        +        `The argument that you passed to storeItem() - ${key} must not end with 'p5TypeID'.`
        +      );
        +    }
         
        -/**
        - * Returns a value in the web browser's local storage.
        - *
        - * Web browsers can save small amounts of data using the built-in
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" target="_blank">localStorage object</a>.
        - * Data stored in `localStorage` can be retrieved at any point, even after
        - * refreshing a page or restarting the browser. Data are stored as key-value
        - * pairs.
        - *
        - * <a href="#/p5/storeItem">storeItem()</a> makes it easy to store values in
        - * `localStorage` and `getItem()` makes it easy to retrieve them.
        - *
        - * The first parameter, `key`, is the name of the value to be stored as a
        - * string.
        - *
        - * The second parameter, `value`, is the value to be retrieved a string. For
        - * example, calling `getItem('size')` retrieves the value with the key `size`.
        - *
        - * Note: Sensitive data such as passwords or personal information shouldn't be
        - * stored in `localStorage`.
        - *
        - * @method getItem
        - * @for p5
        - * @param {String} key name of the value.
        - * @return {String|Number|Boolean|Object|Array} stored item.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Store the player's name.
        - *   storeItem('name', 'Feist');
        - *
        - *   // Store the player's score.
        - *   storeItem('score', 1234);
        - *
        - *   describe('The text "Feist: 1234" written in black on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(14);
        - *
        - *   // Retrieve the name.
        - *   let name = getItem('name');
        - *
        - *   // Retrieve the score.
        - *   let score = getItem('score');
        - *
        - *   // Display the score.
        - *   text(`${name}: ${score}`, 50, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create an object.
        - *   let p = { x: 50, y: 50 };
        - *
        - *   // Store the object.
        - *   storeItem('position', p);
        - *
        - *   describe('A white circle on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Retrieve the object.
        - *   let p = getItem('position');
        - *
        - *   // Draw the circle.
        - *   circle(p.x, p.y, 30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color('deeppink');
        - *
        - *   // Store the object.
        - *   storeItem('color', c);
        - *
        - *   describe('A pink circle on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Retrieve the object.
        - *   let c = getItem('color');
        - *
        - *   // Style the circle.
        - *   fill(c);
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, 30);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.getItem = function(key) {
        -  let value = localStorage.getItem(key);
        -  const type = localStorage.getItem(`${key}p5TypeID`);
        -  if (typeof type === 'undefined') {
        -    console.log(
        -      `Unable to determine type of item stored under ${key}in local storage. Did you save the item with something other than setItem()?`
        -    );
        -  } else if (value !== null) {
        +    if (typeof value === 'undefined') {
        +      console.log('You cannot store undefined variables using storeItem().');
        +    }
        +    let type = typeof value;
             switch (type) {
               case 'number':
        -        value = parseFloat(value);
        -        break;
               case 'boolean':
        -        value = value === 'true';
        +        value = value.toString();
                 break;
               case 'object':
        -        value = JSON.parse(value);
        -        break;
        -      case 'p5.Color':
        -        value = JSON.parse(value);
        -        value = this.color(...value.levels);
        -        break;
        -      case 'p5.Vector':
        -        value = JSON.parse(value);
        -        value = this.createVector(...value);
        +        if (value instanceof p5.Color) {
        +          type = 'p5.Color';
        +          value = value.toString();
        +        } else if (value instanceof p5.Vector) {
        +          type = 'p5.Vector';
        +          const coord = [value.x, value.y, value.z];
        +          value = coord;
        +        }
        +        value = JSON.stringify(value);
                 break;
               case 'string':
               default:
                 break;
             }
        -  }
        -  return value;
        -};
         
        -/**
        - * Removes all items in the web browser's local storage.
        - *
        - * Web browsers can save small amounts of data using the built-in
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" target="_blank">localStorage object</a>.
        - * Data stored in `localStorage` can be retrieved at any point, even after
        - * refreshing a page or restarting the browser. Data are stored as key-value
        - * pairs. Calling `clearStorage()` removes all data from `localStorage`.
        - *
        - * Note: Sensitive data such as passwords or personal information shouldn't be
        - * stored in `localStorage`.
        - *
        - * @method clearStorage
        - * @for p5
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to clear localStorage.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Store the player's name.
        - *   storeItem('name', 'Feist');
        - *
        - *   // Store the player's score.
        - *   storeItem('score', 1234);
        - *
        - *   describe(
        - *     'The text "Feist: 1234" written in black on a gray background. The text "null: null" appears when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(14);
        - *
        - *   // Retrieve the name.
        - *   let name = getItem('name');
        - *
        - *   // Retrieve the score.
        - *   let score = getItem('score');
        - *
        - *   // Display the score.
        - *   text(`${name}: ${score}`, 50, 50);
        - * }
        - *
        - * // Clear localStorage when the user double-clicks.
        - * function doubleClicked() {
        - *   clearStorage();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.clearStorage = function () {
        -  const keys = Object.keys(localStorage);
        -  keys.forEach(key => {
        -    if (key.endsWith('p5TypeID')) {
        -      this.removeItem(key.replace('p5TypeID', ''));
        +    localStorage.setItem(key, value);
        +    const typeKey = `${key}p5TypeID`;
        +    localStorage.setItem(typeKey, type);
        +  };
        +
        +  /**
        +   * Returns a value in the web browser's local storage.
        +   *
        +   * Web browsers can save small amounts of data using the built-in
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" target="_blank">localStorage object</a>.
        +   * Data stored in `localStorage` can be retrieved at any point, even after
        +   * refreshing a page or restarting the browser. Data are stored as key-value
        +   * pairs.
        +   *
        +   * <a href="#/p5/storeItem">storeItem()</a> makes it easy to store values in
        +   * `localStorage` and `getItem()` makes it easy to retrieve them.
        +   *
        +   * The first parameter, `key`, is the name of the value to be stored as a
        +   * string.
        +   *
        +   * The second parameter, `value`, is the value to be retrieved a string. For
        +   * example, calling `getItem('size')` retrieves the value with the key `size`.
        +   *
        +   * Note: Sensitive data such as passwords or personal information shouldn't be
        +   * stored in `localStorage`.
        +   *
        +   * @method getItem
        +   * @for p5
        +   * @param {String} key name of the value.
        +   * @return {String|Number|Boolean|Object|Array} stored item.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Store the player's name.
        +   *   storeItem('name', 'Feist');
        +   *
        +   *   // Store the player's score.
        +   *   storeItem('score', 1234);
        +   *
        +   *   describe('The text "Feist: 1234" written in black on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(14);
        +   *
        +   *   // Retrieve the name.
        +   *   let name = getItem('name');
        +   *
        +   *   // Retrieve the score.
        +   *   let score = getItem('score');
        +   *
        +   *   // Display the score.
        +   *   text(`${name}: ${score}`, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create an object.
        +   *   let p = { x: 50, y: 50 };
        +   *
        +   *   // Store the object.
        +   *   storeItem('position', p);
        +   *
        +   *   describe('A white circle on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Retrieve the object.
        +   *   let p = getItem('position');
        +   *
        +   *   // Draw the circle.
        +   *   circle(p.x, p.y, 30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color('deeppink');
        +   *
        +   *   // Store the object.
        +   *   storeItem('color', c);
        +   *
        +   *   describe('A pink circle on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Retrieve the object.
        +   *   let c = getItem('color');
        +   *
        +   *   // Style the circle.
        +   *   fill(c);
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, 30);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.getItem = function(key) {
        +    let value = localStorage.getItem(key);
        +    const type = localStorage.getItem(`${key}p5TypeID`);
        +    if (typeof type === 'undefined') {
        +      console.log(
        +        `Unable to determine type of item stored under ${key}in local storage. Did you save the item with something other than setItem()?`
        +      );
        +    } else if (value !== null) {
        +      switch (type) {
        +        case 'number':
        +          value = parseFloat(value);
        +          break;
        +        case 'boolean':
        +          value = value === 'true';
        +          break;
        +        case 'object':
        +          value = JSON.parse(value);
        +          break;
        +        case 'p5.Color':
        +          value = this.color(JSON.parse(value));
        +          break;
        +        case 'p5.Vector':
        +          value = JSON.parse(value);
        +          value = this.createVector(...value);
        +          break;
        +        case 'string':
        +        default:
        +          break;
        +      }
             }
        -  });
        -};
        +    return value;
        +  };
         
        -/**
        - * Removes an item from the web browser's local storage.
        - *
        - * Web browsers can save small amounts of data using the built-in
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" target="_blank">localStorage object</a>.
        - * Data stored in `localStorage` can be retrieved at any point, even after
        - * refreshing a page or restarting the browser. Data are stored as key-value
        - * pairs.
        - *
        - * <a href="#/p5/storeItem">storeItem()</a> makes it easy to store values in
        - * `localStorage` and `removeItem()` makes it easy to delete them.
        - *
        - * The parameter, `key`, is the name of the value to remove as a string. For
        - * example, calling `removeItem('size')` removes the item with the key `size`.
        - *
        - * Note: Sensitive data such as passwords or personal information shouldn't be
        - * stored in `localStorage`.
        - *
        - * @method removeItem
        - * @param {String} key name of the value to remove.
        - * @for p5
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to remove an item from localStorage.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Store the player's name.
        - *   storeItem('name', 'Feist');
        - *
        - *   // Store the player's score.
        - *   storeItem('score', 1234);
        - *
        - *   describe(
        - *     'The text "Feist: 1234" written in black on a gray background. The text "Feist: null" appears when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(14);
        - *
        - *   // Retrieve the name.
        - *   let name = getItem('name');
        - *
        - *   // Retrieve the score.
        - *   let score = getItem('score');
        - *
        - *   // Display the score.
        - *   text(`${name}: ${score}`, 50, 50);
        - * }
        - *
        - * // Remove the word from localStorage when the user double-clicks.
        - * function doubleClicked() {
        - *   removeItem('score');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.removeItem = function(key) {
        -  if (typeof key !== 'string') {
        -    console.log(
        -      `The argument that you passed to removeItem() - ${key} is not a string.`
        -    );
        -  }
        -  localStorage.removeItem(key);
        -  localStorage.removeItem(`${key}p5TypeID`);
        -};
        +  /**
        +   * Removes all items in the web browser's local storage.
        +   *
        +   * Web browsers can save small amounts of data using the built-in
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" target="_blank">localStorage object</a>.
        +   * Data stored in `localStorage` can be retrieved at any point, even after
        +   * refreshing a page or restarting the browser. Data are stored as key-value
        +   * pairs. Calling `clearStorage()` removes all data from `localStorage`.
        +   *
        +   * Note: Sensitive data such as passwords or personal information shouldn't be
        +   * stored in `localStorage`.
        +   *
        +   * @method clearStorage
        +   * @for p5
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to clear localStorage.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Store the player's name.
        +   *   storeItem('name', 'Feist');
        +   *
        +   *   // Store the player's score.
        +   *   storeItem('score', 1234);
        +   *
        +   *   describe(
        +   *     'The text "Feist: 1234" written in black on a gray background. The text "null: null" appears when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(14);
        +   *
        +   *   // Retrieve the name.
        +   *   let name = getItem('name');
        +   *
        +   *   // Retrieve the score.
        +   *   let score = getItem('score');
        +   *
        +   *   // Display the score.
        +   *   text(`${name}: ${score}`, 50, 50);
        +   * }
        +   *
        +   * // Clear localStorage when the user double-clicks.
        +   * function doubleClicked() {
        +   *   clearStorage();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.clearStorage = function () {
        +    const keys = Object.keys(localStorage);
        +    keys.forEach(key => {
        +      if (key.endsWith('p5TypeID')) {
        +        this.removeItem(key.replace('p5TypeID', ''));
        +      }
        +    });
        +  };
        +
        +  /**
        +   * Removes an item from the web browser's local storage.
        +   *
        +   * Web browsers can save small amounts of data using the built-in
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" target="_blank">localStorage object</a>.
        +   * Data stored in `localStorage` can be retrieved at any point, even after
        +   * refreshing a page or restarting the browser. Data are stored as key-value
        +   * pairs.
        +   *
        +   * <a href="#/p5/storeItem">storeItem()</a> makes it easy to store values in
        +   * `localStorage` and `removeItem()` makes it easy to delete them.
        +   *
        +   * The parameter, `key`, is the name of the value to remove as a string. For
        +   * example, calling `removeItem('size')` removes the item with the key `size`.
        +   *
        +   * Note: Sensitive data such as passwords or personal information shouldn't be
        +   * stored in `localStorage`.
        +   *
        +   * @method removeItem
        +   * @param {String} key name of the value to remove.
        +   * @for p5
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to remove an item from localStorage.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Store the player's name.
        +   *   storeItem('name', 'Feist');
        +   *
        +   *   // Store the player's score.
        +   *   storeItem('score', 1234);
        +   *
        +   *   describe(
        +   *     'The text "Feist: 1234" written in black on a gray background. The text "Feist: null" appears when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(14);
        +   *
        +   *   // Retrieve the name.
        +   *   let name = getItem('name');
        +   *
        +   *   // Retrieve the score.
        +   *   let score = getItem('score');
        +   *
        +   *   // Display the score.
        +   *   text(`${name}: ${score}`, 50, 50);
        +   * }
        +   *
        +   * // Remove the word from localStorage when the user double-clicks.
        +   * function doubleClicked() {
        +   *   removeItem('score');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.removeItem = function(key) {
        +    if (typeof key !== 'string') {
        +      console.log(
        +        `The argument that you passed to removeItem() - ${key} is not a string.`
        +      );
        +    }
        +    localStorage.removeItem(key);
        +    localStorage.removeItem(`${key}p5TypeID`);
        +  };
        +}
        +
        +export default storage;
        +
        +if(typeof p5 !== 'undefined'){
        +  storage(p5, p5.prototype);
        +}
        diff --git a/src/data/p5.TypedDict.js b/src/data/p5.TypedDict.js
        index 449887157b..51fcda7d5b 100644
        --- a/src/data/p5.TypedDict.js
        +++ b/src/data/p5.TypedDict.js
        @@ -9,124 +9,17 @@
          * with key-value pairs.
          */
         
        -import p5 from '../core/main';
        -
        -/**
        - *
        - * Creates a new instance of p5.StringDict using the key-value pair
        - * or the object you provide.
        - *
        - * @method createStringDict
        - * @for p5
        - * @param {String} key
        - * @param {String} value
        - * @return {p5.StringDict}
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   let myDictionary = createStringDict('p5', 'js');
        - *   print(myDictionary.hasKey('p5')); // logs true to console
        - *
        - *   let anotherDictionary = createStringDict({ happy: 'coding' });
        - *   print(anotherDictionary.hasKey('happy')); // logs true to console
        - * }
        - * </code></div>
        - */
        -/**
        - * @method createStringDict
        - * @param {Object} object object
        - * @return {p5.StringDict}
        - */
        -
        -p5.prototype.createStringDict = function (key, value) {
        -  p5._validateParameters('createStringDict', arguments);
        -  return new p5.StringDict(key, value);
        -};
        -
        -/**
        - *
        - * Creates a new instance of <a href="#/p5.NumberDict">p5.NumberDict</a> using the key-value pair
        - * or object you provide.
        - *
        - * @method createNumberDict
        - * @for p5
        - * @param {Number} key
        - * @param {Number} value
        - * @return {p5.NumberDict}
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   let myDictionary = createNumberDict(100, 42);
        - *   print(myDictionary.hasKey(100)); // logs true to console
        - *
        - *   let anotherDictionary = createNumberDict({ 200: 84 });
        - *   print(anotherDictionary.hasKey(200)); // logs true to console
        - * }
        - * </code></div>
        - */
        -/**
        - * @method createNumberDict
        - * @param {Object} object object
        - * @return {p5.NumberDict}
        - */
        -
        -p5.prototype.createNumberDict = function (key, value) {
        -  p5._validateParameters('createNumberDict', arguments);
        -  return new p5.NumberDict(key, value);
        -};
        -
        -/**
        - *
        - * Base class for all p5.Dictionary types. Specifically
        - * typed Dictionary classes inherit from this class.
        - *
        - * @class p5.TypedDict
        - * @constructor
        - */
        -
        -p5.TypedDict = class TypedDict {
        -  constructor(key, value) {
        -    if (key instanceof Object) {
        -      this.data = key;
        -    } else {
        -      this.data = {};
        -      this.data[key] = value;
        -    }
        -    return this;
        -  }
        -
        +function typedDict(p5, fn){
           /**
        -   * Returns the number of key-value pairs currently stored in the Dictionary.
            *
        -   * @method size
        -   * @return {Integer} the number of key-value pairs in the Dictionary
        +   * Creates a new instance of p5.StringDict using the key-value pair
        +   * or the object you provide.
            *
        -   * @example
        -   * <div class="norender">
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createNumberDict(1, 10);
        -   *   myDictionary.create(2, 20);
        -   *   myDictionary.create(3, 30);
        -   *   print(myDictionary.size()); // logs 3 to the console
        -   * }
        -   * </code></div>
        -   */
        -  size() {
        -    return Object.keys(this.data).length;
        -  }
        -
        -  /**
        -   * Returns true if the given key exists in the Dictionary,
        -   * otherwise returns false.
        -   *
        -   * @method hasKey
        -   * @param {Number|String} key that you want to look up
        -   * @return {Boolean} whether that key exists in Dictionary
        +   * @method createStringDict
        +   * @for p5
        +   * @param {String} key
        +   * @param {String} value
        +   * @return {p5.StringDict}
            *
            * @example
            * <div class="norender">
        @@ -134,537 +27,602 @@ p5.TypedDict = class TypedDict {
            * function setup() {
            *   let myDictionary = createStringDict('p5', 'js');
            *   print(myDictionary.hasKey('p5')); // logs true to console
        -   * }
        -   * </code></div>
        -   */
        -
        -  hasKey(key) {
        -    return this.data.hasOwnProperty(key);
        -  }
        -
        -  /**
        -   * Returns the value stored at the given key.
        -   *
        -   * @method get
        -   * @param {Number|String} the key you want to access
        -   * @return {Number|String} the value stored at that key
        -   *
        -   * @example
        -   * <div class="norender">
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createStringDict('p5', 'js');
        -   *   let myValue = myDictionary.get('p5');
        -   *   print(myValue === 'js'); // logs true to console
        -   * }
        -   * </code></div>
        -   */
        -
        -  get(key) {
        -    if (this.data.hasOwnProperty(key)) {
        -      return this.data[key];
        -    } else {
        -      console.log(`${key} does not exist in this Dictionary`);
        -    }
        -  }
        -
        -  /**
        -   * Updates the value associated with the given key in case it already exists
        -   * in the Dictionary. Otherwise a new key-value pair is added.
            *
        -   * @method set
        -   * @param {Number|String} key
        -   * @param {Number|String} value
        -   *
        -   * @example
        -   * <div class="norender">
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createStringDict('p5', 'js');
        -   *   myDictionary.set('p5', 'JS');
        -   *   myDictionary.print(); // logs "key: p5 - value: JS" to console
        +   *   let anotherDictionary = createStringDict({ happy: 'coding' });
        +   *   print(anotherDictionary.hasKey('happy')); // logs true to console
            * }
            * </code></div>
            */
        -
        -  set(key, value) {
        -    if (this._validate(value)) {
        -      this.data[key] = value;
        -    } else {
        -      console.log('Those values dont work for this dictionary type.');
        -    }
        -  }
        -
           /**
        -   * private helper function to handle the user passing in objects
        -   * during construction or calls to create()
        +   * @method createStringDict
        +   * @param {Object} object object
        +   * @return {p5.StringDict}
            */
         
        -  _addObj(obj) {
        -    for (const key in obj) {
        -      this.set(key, obj[key]);
        -    }
        -  }
        +  fn.createStringDict = function (key, value) {
        +    // p5._validateParameters('createStringDict', arguments);
        +    return new p5.StringDict(key, value);
        +  };
         
           /**
        -   * Creates a new key-value pair in the Dictionary.
            *
        -   * @method create
        -   * @param {Number|String} key
        -   * @param {Number|String} value
        +   * Creates a new instance of <a href="#/p5.NumberDict">p5.NumberDict</a> using the key-value pair
        +   * or object you provide.
        +   *
        +   * @method createNumberDict
        +   * @for p5
        +   * @param {Number} key
        +   * @param {Number} value
        +   * @return {p5.NumberDict}
            *
            * @example
            * <div class="norender">
            * <code>
            * function setup() {
        -   *   let myDictionary = createStringDict('p5', 'js');
        -   *   myDictionary.create('happy', 'coding');
        -   *   myDictionary.print();
        -   *   // above logs "key: p5 - value: js, key: happy - value: coding" to console
        +   *   let myDictionary = createNumberDict(100, 42);
        +   *   print(myDictionary.hasKey(100)); // logs true to console
        +   *
        +   *   let anotherDictionary = createNumberDict({ 200: 84 });
        +   *   print(anotherDictionary.hasKey(200)); // logs true to console
            * }
            * </code></div>
            */
           /**
        -   * @method create
        -   * @param {Object} obj key/value pair
        -   */
        -
        -  create(key, value) {
        -    if (key instanceof Object && typeof value === 'undefined') {
        -      this._addObj(key);
        -    } else if (typeof key !== 'undefined') {
        -      this.set(key, value);
        -    } else {
        -      console.log(
        -        'In order to create a new Dictionary entry you must pass ' +
        -        'an object or a key, value pair'
        -      );
        -    }
        -  }
        -
        -  /**
        -   * Removes all previously stored key-value pairs from the Dictionary.
        -   *
        -   * @method clear
        -   * @example
        -   * <div class="norender">
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createStringDict('p5', 'js');
        -   *   print(myDictionary.hasKey('p5')); // prints 'true'
        -   *   myDictionary.clear();
        -   *   print(myDictionary.hasKey('p5')); // prints 'false'
        -   * }
        -   * </code>
        -   * </div>
        +   * @method createNumberDict
        +   * @param {Object} object object
        +   * @return {p5.NumberDict}
            */
         
        -  clear() {
        -    this.data = {};
        -  }
        +  fn.createNumberDict = function (key, value) {
        +    // p5._validateParameters('createNumberDict', arguments);
        +    return new p5.NumberDict(key, value);
        +  };
         
           /**
        -   * Removes the key-value pair stored at the given key from the Dictionary.
            *
        -   * @method remove
        -   * @param {Number|String} key for the pair to remove
        +   * Base class for all p5.Dictionary types. Specifically
        +   * typed Dictionary classes inherit from this class.
            *
        -   * @example
        -   * <div class="norender">
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createStringDict('p5', 'js');
        -   *   myDictionary.create('happy', 'coding');
        -   *   myDictionary.print();
        -   *   // above logs "key: p5 - value: js, key: happy - value: coding" to console
        -   *   myDictionary.remove('p5');
        -   *   myDictionary.print();
        -   *   // above logs "key: happy value: coding" to console
        -   * }
        -   * </code></div>
        +   * @class p5.TypedDict
            */
        -
        -  remove(key) {
        -    if (this.data.hasOwnProperty(key)) {
        -      delete this.data[key];
        -    } else {
        -      throw new Error(`${key} does not exist in this Dictionary`);
        +  p5.TypedDict = class TypedDict {
        +    constructor(key, value) {
        +      if (key instanceof Object) {
        +        this.data = key;
        +      } else {
        +        this.data = {};
        +        this.data[key] = value;
        +      }
        +      return this;
             }
        -  }
        -
        -  /**
        -   * Logs the set of items currently stored in the Dictionary to the console.
        -   *
        -   * @method print
        -   *
        -   * @example
        -   * <div class="norender">
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createStringDict('p5', 'js');
        -   *   myDictionary.create('happy', 'coding');
        -   *   myDictionary.print();
        -   *   // above logs "key: p5 - value: js, key: happy - value: coding" to console
        -   * }
        -   * </code>
        -   * </div>
        -   */
         
        -  print() {
        -    for (const item in this.data) {
        -      console.log(`key:${item} value:${this.data[item]}`);
        +    /**
        +     * Returns the number of key-value pairs currently stored in the Dictionary.
        +     *
        +     * @return {Integer} the number of key-value pairs in the Dictionary
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createNumberDict(1, 10);
        +     *   myDictionary.create(2, 20);
        +     *   myDictionary.create(3, 30);
        +     *   print(myDictionary.size()); // logs 3 to the console
        +     * }
        +     * </code></div>
        +     */
        +    size() {
        +      return Object.keys(this.data).length;
             }
        -  }
         
        -  /**
        -   * Converts the Dictionary into a CSV file for local download.
        -   *
        -   * @method saveTable
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   createCanvas(100, 100);
        -   *   background(200);
        -   *   text('click here to save', 10, 10, 70, 80);
        -   * }
        -   *
        -   * function mousePressed() {
        -   *   if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
        -   *     createStringDict({
        -   *       john: 1940,
        -   *       paul: 1942,
        -   *       george: 1943,
        -   *       ringo: 1940
        -   *     }).saveTable('beatles');
        -   *   }
        -   * }
        -   * </code>
        -   * </div>
        -   */
        -
        -  saveTable(filename) {
        -    let output = '';
        +    /**
        +     * Returns true if the given key exists in the Dictionary,
        +     * otherwise returns false.
        +     *
        +     * @param {Number|String} key that you want to look up
        +     * @return {Boolean} whether that key exists in Dictionary
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createStringDict('p5', 'js');
        +     *   print(myDictionary.hasKey('p5')); // logs true to console
        +     * }
        +     * </code></div>
        +     */
        +
        +    hasKey(key) {
        +      return this.data.hasOwnProperty(key);
        +    }
         
        -    for (const key in this.data) {
        -      output += `${key},${this.data[key]}\n`;
        +    /**
        +     * Returns the value stored at the given key.
        +     *
        +     * @param {Number|String} the key you want to access
        +     * @return {Number|String} the value stored at that key
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createStringDict('p5', 'js');
        +     *   let myValue = myDictionary.get('p5');
        +     *   print(myValue === 'js'); // logs true to console
        +     * }
        +     * </code></div>
        +     */
        +
        +    get(key) {
        +      if (this.data.hasOwnProperty(key)) {
        +        return this.data[key];
        +      } else {
        +        console.log(`${key} does not exist in this Dictionary`);
        +      }
             }
         
        -    const blob = new Blob([output], { type: 'text/csv' });
        -    p5.prototype.downloadFile(blob, filename || 'mycsv', 'csv');
        -  }
        +    /**
        +     * Updates the value associated with the given key in case it already exists
        +     * in the Dictionary. Otherwise a new key-value pair is added.
        +     *
        +     * @param {Number|String} key
        +     * @param {Number|String} value
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createStringDict('p5', 'js');
        +     *   myDictionary.set('p5', 'JS');
        +     *   myDictionary.print(); // logs "key: p5 - value: JS" to console
        +     * }
        +     * </code></div>
        +     */
        +
        +    set(key, value) {
        +      if (this._validate(value)) {
        +        this.data[key] = value;
        +      } else {
        +        console.log('Those values dont work for this dictionary type.');
        +      }
        +    }
         
        -  /**
        -   * Converts the Dictionary into a JSON file for local download.
        -   *
        -   * @method saveJSON
        -   * @example
        -   * <div>
        -   * <code>
        -   * function setup() {
        -   *   createCanvas(100, 100);
        -   *   background(200);
        -   *   text('click here to save', 10, 10, 70, 80);
        -   * }
        -   *
        -   * function mousePressed() {
        -   *   if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
        -   *     createStringDict({
        -   *       john: 1940,
        -   *       paul: 1942,
        -   *       george: 1943,
        -   *       ringo: 1940
        -   *     }).saveJSON('beatles');
        -   *   }
        -   * }
        -   * </code>
        -   * </div>
        -   */
        +    /**
        +     * private helper function to handle the user passing in objects
        +     * during construction or calls to create()
        +     */
         
        -  saveJSON(filename, opt) {
        -    p5.prototype.saveJSON(this.data, filename, opt);
        -  }
        +    _addObj(obj) {
        +      for (const key in obj) {
        +        this.set(key, obj[key]);
        +      }
        +    }
         
        -  /**
        -   * private helper function to ensure that the user passed in valid
        -   * values for the Dictionary type
        -   */
        -  _validate(value) {
        -    return true;
        -  }
        -};
        +    /**
        +     * Creates a new key-value pair in the Dictionary.
        +     *
        +     * @param {Number|String} key
        +     * @param {Number|String} value
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createStringDict('p5', 'js');
        +     *   myDictionary.create('happy', 'coding');
        +     *   myDictionary.print();
        +     *   // above logs "key: p5 - value: js, key: happy - value: coding" to console
        +     * }
        +     * </code></div>
        +     */
        +    /**
        +     * @param {Object} obj key/value pair
        +     */
        +    create(key, value) {
        +      if (key instanceof Object && typeof value === 'undefined') {
        +        this._addObj(key);
        +      } else if (typeof key !== 'undefined') {
        +        this.set(key, value);
        +      } else {
        +        console.log(
        +          'In order to create a new Dictionary entry you must pass ' +
        +          'an object or a key, value pair'
        +        );
        +      }
        +    }
         
        -/**
        - *
        - * A simple Dictionary class for Strings.
        - *
        - * @class p5.StringDict
        - * @extends p5.TypedDict
        - */
        +    /**
        +     * Removes all previously stored key-value pairs from the Dictionary.
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createStringDict('p5', 'js');
        +     *   print(myDictionary.hasKey('p5')); // prints 'true'
        +     *   myDictionary.clear();
        +     *   print(myDictionary.hasKey('p5')); // prints 'false'
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    clear() {
        +      this.data = {};
        +    }
         
        -p5.StringDict = class StringDict extends p5.TypedDict {
        -  constructor(...args) {
        -    super(...args);
        -  }
        +    /**
        +     * Removes the key-value pair stored at the given key from the Dictionary.
        +     *
        +     * @param {Number|String} key for the pair to remove
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createStringDict('p5', 'js');
        +     *   myDictionary.create('happy', 'coding');
        +     *   myDictionary.print();
        +     *   // above logs "key: p5 - value: js, key: happy - value: coding" to console
        +     *   myDictionary.remove('p5');
        +     *   myDictionary.print();
        +     *   // above logs "key: happy value: coding" to console
        +     * }
        +     * </code></div>
        +     */
        +    remove(key) {
        +      if (this.data.hasOwnProperty(key)) {
        +        delete this.data[key];
        +      } else {
        +        throw new Error(`${key} does not exist in this Dictionary`);
        +      }
        +    }
         
        -  _validate(value) {
        -    return typeof value === 'string';
        -  }
        -};
        +    /**
        +     * Logs the set of items currently stored in the Dictionary to the console.
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createStringDict('p5', 'js');
        +     *   myDictionary.create('happy', 'coding');
        +     *   myDictionary.print();
        +     *   // above logs "key: p5 - value: js, key: happy - value: coding" to console
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    print() {
        +      for (const item in this.data) {
        +        console.log(`key:${item} value:${this.data[item]}`);
        +      }
        +    }
         
        -/**
        - *
        - * A simple Dictionary class for Numbers.
        - *
        - * @class p5.NumberDict
        - * @constructor
        - * @extends p5.TypedDict
        - */
        +    /**
        +     * Converts the Dictionary into a CSV file for local download.
        +     *
        +     * @example
        +     * <div>
        +     * <code>
        +     * function setup() {
        +     *   createCanvas(100, 100);
        +     *   background(200);
        +     *   text('click here to save', 10, 10, 70, 80);
        +     * }
        +     *
        +     * function mousePressed() {
        +     *   if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
        +     *     createStringDict({
        +     *       john: 1940,
        +     *       paul: 1942,
        +     *       george: 1943,
        +     *       ringo: 1940
        +     *     }).saveTable('beatles');
        +     *   }
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    saveTable(filename) {
        +      let output = '';
         
        -p5.NumberDict = class NumberDict extends p5.TypedDict {
        -  constructor(...args) {
        -    super(...args);
        -  }
        +      for (const key in this.data) {
        +        output += `${key},${this.data[key]}\n`;
        +      }
         
        +      const blob = new Blob([output], { type: 'text/csv' });
        +      fn.downloadFile(blob, filename || 'mycsv', 'csv');
        +    }
         
        -  /**
        -   * private helper function to ensure that the user passed in valid
        -   * values for the Dictionary type
        -   */
        +    /**
        +     * Converts the Dictionary into a JSON file for local download.
        +     *
        +     * @example
        +     * <div>
        +     * <code>
        +     * function setup() {
        +     *   createCanvas(100, 100);
        +     *   background(200);
        +     *   text('click here to save', 10, 10, 70, 80);
        +     * }
        +     *
        +     * function mousePressed() {
        +     *   if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
        +     *     createStringDict({
        +     *       john: 1940,
        +     *       paul: 1942,
        +     *       george: 1943,
        +     *       ringo: 1940
        +     *     }).saveJSON('beatles');
        +     *   }
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    saveJSON(filename, opt) {
        +      fn.saveJSON(this.data, filename, opt);
        +    }
         
        -  _validate(value) {
        -    return typeof value === 'number';
        -  }
        +    /**
        +     * private helper function to ensure that the user passed in valid
        +     * values for the Dictionary type
        +     */
        +    _validate(value) {
        +      return true;
        +    }
        +  };
         
           /**
        -   * Add the given number to the value currently stored at the given key.
        -   * The sum then replaces the value previously stored in the Dictionary.
            *
        -   * @method add
        -   * @param {Number} Key for the value you wish to add to
        -   * @param {Number} Number to add to the value
        -   * @example
        -   * <div class='norender'>
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createNumberDict(2, 5);
        -   *   myDictionary.add(2, 2);
        -   *   print(myDictionary.get(2)); // logs 7 to console.
        -   * }
        -   * </code></div>
        +   * A simple Dictionary class for Strings.
            *
        +   * @class p5.StringDict
        +   * @extends p5.TypedDict
            */
        +  p5.StringDict = class StringDict extends p5.TypedDict {
        +    constructor(...args) {
        +      super(...args);
        +    }
         
        -  add(key, amount) {
        -    if (this.data.hasOwnProperty(key)) {
        -      this.data[key] += amount;
        -    } else {
        -      console.log(`The key - ${key} does not exist in this dictionary.`);
        +    _validate(value) {
        +      return typeof value === 'string';
             }
        -  }
        +  };
         
           /**
        -   * Subtract the given number from the value currently stored at the given key.
        -   * The difference then replaces the value previously stored in the Dictionary.
            *
        -   * @method sub
        -   * @param {Number} Key for the value you wish to subtract from
        -   * @param {Number} Number to subtract from the value
        -   * @example
        -   * <div class='norender'>
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createNumberDict(2, 5);
        -   *   myDictionary.sub(2, 2);
        -   *   print(myDictionary.get(2)); // logs 3 to console.
        -   * }
        -   * </code></div>
        +   * A simple Dictionary class for Numbers.
            *
        +   * @class p5.NumberDict
        +   * @extends p5.TypedDict
            */
         
        -  sub(key, amount) {
        -    this.add(key, -amount);
        -  }
        +  p5.NumberDict = class NumberDict extends p5.TypedDict {
        +    constructor(...args) {
        +      super(...args);
        +    }
         
        -  /**
        -   * Multiply the given number with the value currently stored at the given key.
        -   * The product then replaces the value previously stored in the Dictionary.
        -   *
        -   * @method mult
        -   * @param {Number} Key for value you wish to multiply
        -   * @param {Number} Amount to multiply the value by
        -   * @example
        -   * <div class='norender'>
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createNumberDict(2, 4);
        -   *   myDictionary.mult(2, 2);
        -   *   print(myDictionary.get(2)); // logs 8 to console.
        -   * }
        -   * </code></div>
        -   *
        -   */
        +    /**
        +     * private helper function to ensure that the user passed in valid
        +     * values for the Dictionary type
        +     */
        +    _validate(value) {
        +      return typeof value === 'number';
        +    }
         
        -  mult(key, amount) {
        -    if (this.data.hasOwnProperty(key)) {
        -      this.data[key] *= amount;
        -    } else {
        -      console.log(`The key - ${key} does not exist in this dictionary.`);
        +    /**
        +     * Add the given number to the value currently stored at the given key.
        +     * The sum then replaces the value previously stored in the Dictionary.
        +     *
        +     * @param {Number} Key for the value you wish to add to
        +     * @param {Number} Number to add to the value
        +     * @example
        +     * <div class='norender'>
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createNumberDict(2, 5);
        +     *   myDictionary.add(2, 2);
        +     *   print(myDictionary.get(2)); // logs 7 to console.
        +     * }
        +     * </code></div>
        +     *
        +     */
        +    add(key, amount) {
        +      if (this.data.hasOwnProperty(key)) {
        +        this.data[key] += amount;
        +      } else {
        +        console.log(`The key - ${key} does not exist in this dictionary.`);
        +      }
             }
        -  }
         
        -  /**
        -   * Divide the given number with the value currently stored at the given key.
        -   * The quotient then replaces the value previously stored in the Dictionary.
        -   *
        -   * @method div
        -   * @param {Number} Key for value you wish to divide
        -   * @param {Number} Amount to divide the value by
        -   * @example
        -   * <div class='norender'>
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createNumberDict(2, 8);
        -   *   myDictionary.div(2, 2);
        -   *   print(myDictionary.get(2)); // logs 4 to console.
        -   * }
        -   * </code></div>
        -   *
        -   */
        +    /**
        +     * Subtract the given number from the value currently stored at the given key.
        +     * The difference then replaces the value previously stored in the Dictionary.
        +     *
        +     * @param {Number} Key for the value you wish to subtract from
        +     * @param {Number} Number to subtract from the value
        +     * @example
        +     * <div class='norender'>
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createNumberDict(2, 5);
        +     *   myDictionary.sub(2, 2);
        +     *   print(myDictionary.get(2)); // logs 3 to console.
        +     * }
        +     * </code></div>
        +     *
        +     */
        +    sub(key, amount) {
        +      this.add(key, -amount);
        +    }
         
        -  div(key, amount) {
        -    if (this.data.hasOwnProperty(key)) {
        -      this.data[key] /= amount;
        -    } else {
        -      console.log(`The key - ${key} does not exist in this dictionary.`);
        +    /**
        +     * Multiply the given number with the value currently stored at the given key.
        +     * The product then replaces the value previously stored in the Dictionary.
        +     *
        +     * @param {Number} Key for value you wish to multiply
        +     * @param {Number} Amount to multiply the value by
        +     * @example
        +     * <div class='norender'>
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createNumberDict(2, 4);
        +     *   myDictionary.mult(2, 2);
        +     *   print(myDictionary.get(2)); // logs 8 to console.
        +     * }
        +     * </code></div>
        +     *
        +     */
        +    mult(key, amount) {
        +      if (this.data.hasOwnProperty(key)) {
        +        this.data[key] *= amount;
        +      } else {
        +        console.log(`The key - ${key} does not exist in this dictionary.`);
        +      }
             }
        -  }
         
        -  /**
        -   * private helper function for finding lowest or highest value
        -   * the argument 'flip' is used to flip the comparison arrow
        -   * from 'less than' to 'greater than'
        -   */
        +    /**
        +     * Divide the given number with the value currently stored at the given key.
        +     * The quotient then replaces the value previously stored in the Dictionary.
        +     *
        +     * @param {Number} Key for value you wish to divide
        +     * @param {Number} Amount to divide the value by
        +     * @example
        +     * <div class='norender'>
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createNumberDict(2, 8);
        +     *   myDictionary.div(2, 2);
        +     *   print(myDictionary.get(2)); // logs 4 to console.
        +     * }
        +     * </code></div>
        +     *
        +     */
        +    div(key, amount) {
        +      if (this.data.hasOwnProperty(key)) {
        +        this.data[key] /= amount;
        +      } else {
        +        console.log(`The key - ${key} does not exist in this dictionary.`);
        +      }
        +    }
         
        -  _valueTest(flip) {
        -    if (Object.keys(this.data).length === 0) {
        -      throw new Error(
        -        'Unable to search for a minimum or maximum value on an empty NumberDict'
        -      );
        -    } else if (Object.keys(this.data).length === 1) {
        -      return this.data[Object.keys(this.data)[0]];
        -    } else {
        -      let result = this.data[Object.keys(this.data)[0]];
        -      for (const key in this.data) {
        -        if (this.data[key] * flip < result * flip) {
        -          result = this.data[key];
        +    /**
        +     * private helper function for finding lowest or highest value
        +     * the argument 'flip' is used to flip the comparison arrow
        +     * from 'less than' to 'greater than'
        +     */
        +    _valueTest(flip) {
        +      if (Object.keys(this.data).length === 0) {
        +        throw new Error(
        +          'Unable to search for a minimum or maximum value on an empty NumberDict'
        +        );
        +      } else if (Object.keys(this.data).length === 1) {
        +        return this.data[Object.keys(this.data)[0]];
        +      } else {
        +        let result = this.data[Object.keys(this.data)[0]];
        +        for (const key in this.data) {
        +          if (this.data[key] * flip < result * flip) {
        +            result = this.data[key];
        +          }
                 }
        +        return result;
               }
        -      return result;
             }
        -  }
         
        -  /**
        -   * Return the lowest number currently stored in the Dictionary.
        -   *
        -   * @method minValue
        -   * @return {Number}
        -   * @example
        -   * <div class='norender'>
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 });
        -   *   let lowestValue = myDictionary.minValue(); // value is -10
        -   *   print(lowestValue);
        -   * }
        -   * </code></div>
        -   */
        -
        -  minValue() {
        -    return this._valueTest(1);
        -  }
        -
        -  /**
        -   * Return the highest number currently stored in the Dictionary.
        -   *
        -   * @method maxValue
        -   * @return {Number}
        -   * @example
        -   * <div class='norender'>
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 });
        -   *   let highestValue = myDictionary.maxValue(); // value is 3
        -   *   print(highestValue);
        -   * }
        -   * </code></div>
        -   */
        -
        -  maxValue() {
        -    return this._valueTest(-1);
        -  }
        +    /**
        +     * Return the lowest number currently stored in the Dictionary.
        +     *
        +     * @return {Number}
        +     * @example
        +     * <div class='norender'>
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 });
        +     *   let lowestValue = myDictionary.minValue(); // value is -10
        +     *   print(lowestValue);
        +     * }
        +     * </code></div>
        +     */
        +    minValue() {
        +      return this._valueTest(1);
        +    }
         
        -  /**
        -   * private helper function for finding lowest or highest key
        -   * the argument 'flip' is used to flip the comparison arrow
        -   * from 'less than' to 'greater than'
        -   */
        +    /**
        +     * Return the highest number currently stored in the Dictionary.
        +     *
        +     * @return {Number}
        +     * @example
        +     * <div class='norender'>
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createNumberDict({ 2: -10, 4: 0.65, 1.2: 3 });
        +     *   let highestValue = myDictionary.maxValue(); // value is 3
        +     *   print(highestValue);
        +     * }
        +     * </code></div>
        +     */
        +    maxValue() {
        +      return this._valueTest(-1);
        +    }
         
        -  _keyTest(flip) {
        -    if (Object.keys(this.data).length === 0) {
        -      throw new Error('Unable to use minValue on an empty NumberDict');
        -    } else if (Object.keys(this.data).length === 1) {
        -      return Object.keys(this.data)[0];
        -    } else {
        -      let result = Object.keys(this.data)[0];
        -      for (let i = 1; i < Object.keys(this.data).length; i++) {
        -        if (Object.keys(this.data)[i] * flip < result * flip) {
        -          result = Object.keys(this.data)[i];
        +    /**
        +     * private helper function for finding lowest or highest key
        +     * the argument 'flip' is used to flip the comparison arrow
        +     * from 'less than' to 'greater than'
        +     */
        +    _keyTest(flip) {
        +      if (Object.keys(this.data).length === 0) {
        +        throw new Error('Unable to use minValue on an empty NumberDict');
        +      } else if (Object.keys(this.data).length === 1) {
        +        return Object.keys(this.data)[0];
        +      } else {
        +        let result = Object.keys(this.data)[0];
        +        for (let i = 1; i < Object.keys(this.data).length; i++) {
        +          if (Object.keys(this.data)[i] * flip < result * flip) {
        +            result = Object.keys(this.data)[i];
        +          }
                 }
        +        return result;
               }
        -      return result;
             }
        -  }
         
        -  /**
        -   * Return the lowest key currently used in the Dictionary.
        -   *
        -   * @method minKey
        -   * @return {Number}
        -   * @example
        -   * <div class='norender'>
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 });
        -   *   let lowestKey = myDictionary.minKey(); // value is 1.2
        -   *   print(lowestKey);
        -   * }
        -   * </code></div>
        -   */
        -
        -  minKey() {
        -    return this._keyTest(1);
        -  }
        -
        -  /**
        -   * Return the highest key currently used in the Dictionary.
        -   *
        -   * @method maxKey
        -   * @return {Number}
        -   * @example
        -   * <div class='norender'>
        -   * <code>
        -   * function setup() {
        -   *   let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 });
        -   *   let highestKey = myDictionary.maxKey(); // value is 4
        -   *   print(highestKey);
        -   * }
        -   * </code></div>
        -   */
        +    /**
        +     * Return the lowest key currently used in the Dictionary.
        +     *
        +     * @return {Number}
        +     * @example
        +     * <div class='norender'>
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 });
        +     *   let lowestKey = myDictionary.minKey(); // value is 1.2
        +     *   print(lowestKey);
        +     * }
        +     * </code></div>
        +     */
        +    minKey() {
        +      return this._keyTest(1);
        +    }
         
        -  maxKey() {
        -    return this._keyTest(-1);
        -  }
        -};
        +    /**
        +     * Return the highest key currently used in the Dictionary.
        +     *
        +     * @return {Number}
        +     * @example
        +     * <div class='norender'>
        +     * <code>
        +     * function setup() {
        +     *   let myDictionary = createNumberDict({ 2: 4, 4: 6, 1.2: 3 });
        +     *   let highestKey = myDictionary.maxKey(); // value is 4
        +     *   print(highestKey);
        +     * }
        +     * </code></div>
        +     */
        +    maxKey() {
        +      return this._keyTest(-1);
        +    }
        +  };
        +}
         
        -export default p5.TypedDict;
        +export default typedDict;
        diff --git a/src/dom/dom.js b/src/dom/dom.js
        index 2f04be1ef2..8287e4136f 100644
        --- a/src/dom/dom.js
        +++ b/src/dom/dom.js
        @@ -16,5832 +16,1967 @@
          * @requires p5
          */
         
        -import p5 from '../core/main';
        +import { Element } from './p5.Element';
        +import { MediaElement } from './p5.MediaElement';
        +import { File } from './p5.File';
         
        -/**
        - * Searches the page for the first element that matches the given
        - * <a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/CSS_basics#different_types_of_selectors" target="_blank">CSS selector string</a>.
        - *
        - * The selector string can be an ID, class, tag name, or a combination.
        - * `select()` returns a <a href="#/p5.Element">p5.Element</a> object if it
        - * finds a match and `null` if not.
        - *
        - * The second parameter, `container`, is optional. It specifies a container to
        - * search within. `container` can be CSS selector string, a
        - * <a href="#/p5.Element">p5.Element</a> object, or an
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" target="_blank">HTMLElement</a> object.
        - *
        - * @method select
        - * @param  {String} selectors CSS selector string of element to search for.
        - * @param  {String|p5.Element|HTMLElement} [container] CSS selector string, <a href="#/p5.Element">p5.Element</a>, or
        - *                                             <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" target="_blank">HTMLElement</a> to search within.
        - * @return {p5.Element|null} <a href="#/p5.Element">p5.Element</a> containing the element.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *   background(200);
        - *
        - *   // Select the canvas by its tag.
        - *   let cnv = select('canvas');
        - *   cnv.style('border', '5px deeppink dashed');
        - *
        - *   describe('A gray square with a dashed pink border.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   let cnv = createCanvas(100, 100);
        - *
        - *   // Add a class attribute to the canvas.
        - *   cnv.class('pinkborder');
        - *
        - *   background(200);
        - *
        - *   // Select the canvas by its class.
        - *   cnv = select('.pinkborder');
        - *
        - *   // Style its border.
        - *   cnv.style('border', '5px deeppink dashed');
        - *
        - *   describe('A gray square with a dashed pink border.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   let cnv = createCanvas(100, 100);
        - *
        - *   // Set the canvas' ID.
        - *   cnv.id('mycanvas');
        - *
        - *   background(200);
        - *
        - *   // Select the canvas by its ID.
        - *   cnv = select('#mycanvas');
        - *
        - *   // Style its border.
        - *   cnv.style('border', '5px deeppink dashed');
        - *
        - *   describe('A gray square with a dashed pink border.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.select = function (e, p) {
        -  p5._validateParameters('select', arguments);
        -  const container = this._getContainer(p);
        -  const res = container.querySelector(e);
        -  if (res) {
        -    return this._wrapElement(res);
        -  } else {
        -    return null;
        -  }
        -};
        -
        -/**
        - * Searches the page for all elements that matches the given
        - * <a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/CSS_basics#different_types_of_selectors" target="_blank">CSS selector string</a>.
        - *
        - * The selector string can be an ID, class, tag name, or a combination.
        - * `selectAll()` returns an array of <a href="#/p5.Element">p5.Element</a>
        - * objects if it finds any matches and an empty array if none are found.
        - *
        - * The second parameter, `container`, is optional. It specifies a container to
        - * search within. `container` can be CSS selector string, a
        - * <a href="#/p5.Element">p5.Element</a> object, or an
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" target="_blank">HTMLElement</a> object.
        - *
        - * @method selectAll
        - * @param  {String} selectors CSS selector string of element to search for.
        - * @param  {String|p5.Element|HTMLElement} [container] CSS selector string, <a href="#/p5.Element">p5.Element</a>, or
        - *                                             <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" target="_blank">HTMLElement</a> to search within.
        - * @return {p5.Element[]} array of <a href="#/p5.Element">p5.Element</a>s containing any elements found.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create three buttons.
        - *   createButton('1');
        - *   createButton('2');
        - *   createButton('3');
        - *
        - *   // Select the buttons by their tag.
        - *   let buttons = selectAll('button');
        - *
        - *   // Position the buttons.
        - *   for (let i = 0; i < 3; i += 1) {
        - *     buttons[i].position(0, i * 30);
        - *   }
        - *
        - *   describe('Three buttons stacked vertically. The buttons are labeled, "1", "2", and "3".');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Create three buttons and position them.
        - *   let b1 = createButton('1');
        - *   b1.position(0, 0);
        - *   let b2 = createButton('2');
        - *   b2.position(0, 30);
        - *   let b3 = createButton('3');
        - *   b3.position(0, 60);
        - *
        - *   // Add a class attribute to each button.
        - *   b1.class('btn');
        - *   b2.class('btn btn-pink');
        - *   b3.class('btn');
        - *
        - *   // Select the buttons by their class.
        - *   let buttons = selectAll('.btn');
        - *   let pinkButtons = selectAll('.btn-pink');
        - *
        - *   // Style the selected buttons.
        - *   buttons.forEach(setFont);
        - *   pinkButtons.forEach(setColor);
        - *
        - *   describe('Three buttons stacked vertically. The buttons are labeled, "1", "2", and "3". Buttons "1" and "3" are gray. Button "2" is pink.');
        - * }
        - *
        - * // Set a button's font to Comic Sans MS.
        - * function setFont(btn) {
        - *   btn.style('font-family', 'Comic Sans MS');
        - * }
        - *
        - * // Set a button's background and font color.
        - * function setColor(btn) {
        - *   btn.style('background', 'deeppink');
        - *   btn.style('color', 'white');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.selectAll = function (e, p) {
        -  p5._validateParameters('selectAll', arguments);
        -  const arr = [];
        -  const container = this._getContainer(p);
        -  const res = container.querySelectorAll(e);
        -  if (res) {
        -    for (let j = 0; j < res.length; j++) {
        -      const obj = this._wrapElement(res[j]);
        -      arr.push(obj);
        -    }
        -  }
        -  return arr;
        -};
        -
        -/**
        - * Helper function for select and selectAll
        - */
        -p5.prototype._getContainer = function (p) {
        -  let container = document;
        -  if (typeof p === 'string') {
        -    container = document.querySelector(p) || document;
        -  } else if (p instanceof p5.Element) {
        -    container = p.elt;
        -  } else if (p instanceof HTMLElement) {
        -    container = p;
        -  }
        -  return container;
        -};
        -
        -/**
        - * Helper function for getElement and getElements.
        - */
        -p5.prototype._wrapElement = function (elt) {
        -  const children = Array.prototype.slice.call(elt.children);
        -  if (elt.tagName === 'INPUT' && elt.type === 'checkbox') {
        -    let converted = new p5.Element(elt, this);
        -    converted.checked = function(...args) {
        -      if (args.length === 0) {
        -        return this.elt.checked;
        -      } else if (args[0]) {
        -        this.elt.checked = true;
        -      } else {
        -        this.elt.checked = false;
        -      }
        -      return this;
        -    };
        -    return converted;
        -  } else if (elt.tagName === 'VIDEO' || elt.tagName === 'AUDIO') {
        -    return new p5.MediaElement(elt, this);
        -  } else if (elt.tagName === 'SELECT') {
        -    return this.createSelect(new p5.Element(elt, this));
        -  } else if (
        -    children.length > 0 &&
        -    children.every(function (c) {
        -      return c.tagName === 'INPUT' || c.tagName === 'LABEL';
        -    }) &&
        -    (elt.tagName === 'DIV' || elt.tagName === 'SPAN')
        -  ) {
        -    return this.createRadio(new p5.Element(elt, this));
        -  } else {
        -    return new p5.Element(elt, this);
        -  }
        -};
        -
        -/**
        - * Removes all elements created by p5.js, including any event handlers.
        - *
        - * There are two exceptions:
        - * canvas elements created by <a href="#/p5/createCanvas">createCanvas()</a>
        - * and <a href="#/p5.Renderer">p5.Render</a> objects created by
        - * <a href="#/p5/createGraphics">createGraphics()</a>.
        - *
        - * @method removeElements
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a paragraph element and place
        - *   // it in the middle of the canvas.
        - *   let p = createP('p5*js');
        - *   p.position(25, 25);
        - *
        - *   describe('A gray square with the text "p5*js" written in its center. The text disappears when the mouse is pressed.');
        - * }
        - *
        - * // Remove all elements when the mouse is pressed.
        - * function mousePressed() {
        - *   removeElements();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let slider;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a paragraph element and place
        - *   // it at the top of the canvas.
        - *   let p = createP('p5*js');
        - *   p.position(25, 25);
        - *
        - *   // Create a slider element and place it
        - *   // beneath the canvas.
        - *   slider = createSlider(0, 255, 200);
        - *   slider.position(0, 100);
        - *
        - *   describe('A gray square with the text "p5*js" written in its center and a range slider beneath it. The square changes color when the slider is moved. The text and slider disappear when the square is double-clicked.');
        - * }
        - *
        - * function draw() {
        - *   // Use the slider value to change the background color.
        - *   let g = slider.value();
        - *   background(g);
        - * }
        - *
        - * // Remove all elements when the mouse is double-clicked.
        - * function doubleClicked() {
        - *   removeElements();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.removeElements = function (e) {
        -  p5._validateParameters('removeElements', arguments);
        -  // el.remove splices from this._elements, so don't mix iteration with it
        -  const isNotCanvasElement = el => !(el.elt instanceof HTMLCanvasElement);
        -  const removeableElements = this._elements.filter(isNotCanvasElement);
        -  removeableElements.map(el => el.remove());
        -};
        -
        -/**
        - * Calls a function when the element changes.
        - *
        - * Calling `myElement.changed(false)` disables the function.
        - *
        - * @method changed
        - * @param  {Function|Boolean} fxn function to call when the element changes.
        - *                                `false` disables the function.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * let dropdown;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a dropdown menu and add a few color options.
        - *   dropdown = createSelect();
        - *   dropdown.position(0, 0);
        - *   dropdown.option('red');
        - *   dropdown.option('green');
        - *   dropdown.option('blue');
        - *
        - *   // Call paintBackground() when the color option changes.
        - *   dropdown.changed(paintBackground);
        - *
        - *   describe('A gray square with a dropdown menu at the top. The square changes color when an option is selected.');
        - * }
        - *
        - * // Paint the background with the selected color.
        - * function paintBackground() {
        - *   let c = dropdown.value();
        - *   background(c);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let checkbox;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a checkbox and place it beneath the canvas.
        - *   checkbox = createCheckbox(' circle');
        - *   checkbox.position(0, 100);
        - *
        - *   // Call repaint() when the checkbox changes.
        - *   checkbox.changed(repaint);
        - *
        - *   describe('A gray square with a checkbox underneath it that says "circle". A white circle appears when the box is checked and disappears otherwise.');
        - * }
        - *
        - * // Paint the background gray and determine whether to draw a circle.
        - * function repaint() {
        - *   background(200);
        - *   if (checkbox.checked() === true) {
        - *     circle(50, 50, 30);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.changed = function (fxn) {
        -  p5.Element._adjustListener('change', fxn, this);
        -  return this;
        -};
        -
        -/**
        - * Calls a function when the element receives input.
        - *
        - * `myElement.input()` is often used to with text inputs and sliders. Calling
        - * `myElement.input(false)` disables the function.
        - *
        - * @method input
        - * @param  {Function|Boolean} fxn function to call when input is detected within
        - *                                the element.
        - *                                `false` disables the function.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * let slider;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a slider and place it beneath the canvas.
        - *   slider = createSlider(0, 255, 200);
        - *   slider.position(0, 100);
        - *
        - *   // Call repaint() when the slider changes.
        - *   slider.input(repaint);
        - *
        - *   describe('A gray square with a range slider underneath it. The background changes shades of gray when the slider is moved.');
        - * }
        - *
        - * // Paint the background using slider's value.
        - * function repaint() {
        - *   let g = slider.value();
        - *   background(g);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let input;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an input and place it beneath the canvas.
        - *   input = createInput('');
        - *   input.position(0, 100);
        - *
        - *   // Call repaint() when input is detected.
        - *   input.input(repaint);
        - *
        - *   describe('A gray square with a text input bar beneath it. Any text written in the input appears in the middle of the square.');
        - * }
        - *
        - * // Paint the background gray and display the input's value.
        - * function repaint() {
        - *   background(200);
        - *   let msg = input.value();
        - *   text(msg, 5, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.input = function (fxn) {
        -  p5.Element._adjustListener('input', fxn, this);
        -  return this;
        -};
        -
        -/**
        - * Helpers for create methods.
        - */
        -function addElement(elt, pInst, media) {
        -  const node = pInst._userNode ? pInst._userNode : document.body;
        -  node.appendChild(elt);
        -  const c = media
        -    ? new p5.MediaElement(elt, pInst)
        -    : new p5.Element(elt, pInst);
        -  pInst._elements.push(c);
        -  return c;
        -}
        -
        -/**
        - * Creates a `&lt;div&gt;&lt;/div&gt;` element.
        - *
        - * `&lt;div&gt;&lt;/div&gt;` elements are commonly used as containers for
        - * other elements.
        - *
        - * The parameter `html` is optional. It accepts a string that sets the
        - * inner HTML of the new `&lt;div&gt;&lt;/div&gt;`.
        - *
        - * @method createDiv
        - * @param  {String} [html] inner HTML for the new `&lt;div&gt;&lt;/div&gt;` element.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a div element and set its position.
        - *   let div = createDiv('p5*js');
        - *   div.position(25, 35);
        - *
        - *   describe('A gray square with the text "p5*js" written in its center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an h3 element within the div.
        - *   let div = createDiv('<h3>p5*js</h3>');
        - *   div.position(20, 5);
        - *
        - *   describe('A gray square with the text "p5*js" written in its center.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createDiv = function (html = '') {
        -  let elt = document.createElement('div');
        -  elt.innerHTML = html;
        -  return addElement(elt, this);
        -};
        -
        -/**
        - * Creates a `&lt;p&gt;&lt;/p&gt;` element.
        - *
        - * `&lt;p&gt;&lt;/p&gt;` elements are commonly used for paragraph-length text.
        - *
        - * The parameter `html` is optional. It accepts a string that sets the
        - * inner HTML of the new `&lt;p&gt;&lt;/p&gt;`.
        - *
        - * @method createP
        - * @param  {String} [html] inner HTML for the new `&lt;p&gt;&lt;/p&gt;` element.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a paragraph element and set its position.
        - *   let p = createP('Tell me a story.');
        - *   p.position(5, 0);
        - *
        - *   describe('A gray square displaying the text "Tell me a story." written in black.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createP = function (html = '') {
        -  let elt = document.createElement('p');
        -  elt.innerHTML = html;
        -  return addElement(elt, this);
        -};
        -
        -/**
        - * Creates a `&lt;span&gt;&lt;/span&gt;` element.
        - *
        - * `&lt;span&gt;&lt;/span&gt;` elements are commonly used as containers
        - * for inline elements. For example, a `&lt;span&gt;&lt;/span&gt;`
        - * can hold part of a sentence that's a
        - * <span style="color: deeppink;">different</span> style.
        - *
        - * The parameter `html` is optional. It accepts a string that sets the
        - * inner HTML of the new `&lt;span&gt;&lt;/span&gt;`.
        - *
        - * @method createSpan
        - * @param  {String} [html] inner HTML for the new `&lt;span&gt;&lt;/span&gt;` element.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a span element and set its position.
        - *   let span = createSpan('p5*js');
        - *   span.position(25, 35);
        - *
        - *   describe('A gray square with the text "p5*js" written in its center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *
        - *   // Create a div element as a container.
        - *   let div = createDiv();
        - *
        - *   // Place the div at the center.
        - *   div.position(25, 35);
        - *
        - *   // Create a span element.
        - *   let s1 = createSpan('p5');
        - *
        - *   // Create a second span element.
        - *   let s2 = createSpan('*');
        - *
        - *   // Set the second span's font color.
        - *   s2.style('color', 'deeppink');
        - *
        - *   // Create a third span element.
        - *   let s3 = createSpan('js');
        - *
        - *   // Add all the spans to the container div.
        - *   s1.parent(div);
        - *   s2.parent(div);
        - *   s3.parent(div);
        - *
        - *   describe('A gray square with the text "p5*js" written in black at its center. The asterisk is pink.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createSpan = function (html = '') {
        -  let elt = document.createElement('span');
        -  elt.innerHTML = html;
        -  return addElement(elt, this);
        -};
        -
        -/**
        - * Creates an `&lt;img&gt;` element that can appear outside of the canvas.
        - *
        - * The first parameter, `src`, is a string with the path to the image file.
        - * `src` should be a relative path, as in `'assets/image.png'`, or a URL, as
        - * in `'https://example.com/image.png'`.
        - *
        - * The second parameter, `alt`, is a string with the
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/alt#usage_notes" target="_blank">alternate text</a>
        - * for the image. An empty string `''` can be used for images that aren't displayed.
        - *
        - * The third parameter, `crossOrigin`, is optional. It's a string that sets the
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes" target="_blank">crossOrigin property</a>
        - * of the image. Use `'anonymous'` or `'use-credentials'` to fetch the image
        - * with cross-origin access.
        - *
        - * The fourth parameter, `callback`, is also optional. It sets a function to
        - * call after the image loads. The new image is passed to the callback
        - * function as a <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @method createImg
        - * @param  {String} src relative path or URL for the image.
        - * @param  {String} alt alternate text for the image.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   let img = createImg(
        - *     'https://p5js.org/assets/img/asterisk-01.png',
        - *     'The p5.js magenta asterisk.'
        - *   );
        - *   img.position(0, -10);
        - *
        - *   describe('A gray square with a magenta asterisk in its center.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method createImg
        - * @param  {String} src
        - * @param  {String} alt
        - * @param  {String} [crossOrigin] crossOrigin property to use when fetching the image.
        - * @param  {Function} [successCallback] function to call once the image loads. The new image will be passed
        - *                                      to the function as a <a href="#/p5.Element">p5.Element</a> object.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - */
        -p5.prototype.createImg = function () {
        -  p5._validateParameters('createImg', arguments);
        -  const elt = document.createElement('img');
        -  const args = arguments;
        -  let self;
        -  if (args.length > 1 && typeof args[1] === 'string') {
        -    elt.alt = args[1];
        -  }
        -  if (args.length > 2 && typeof args[2] === 'string') {
        -    elt.crossOrigin = args[2];
        -  }
        -  elt.src = args[0];
        -  self = addElement(elt, this);
        -  elt.addEventListener('load', function () {
        -    self.width = elt.offsetWidth || elt.width;
        -    self.height = elt.offsetHeight || elt.height;
        -    const last = args[args.length - 1];
        -    if (typeof last === 'function') last(self);
        -  });
        -  return self;
        -};
        -
        -/**
        - * Creates an `&lt;a&gt;&lt;/a&gt;` element that links to another web page.
        - *
        - * The first parmeter, `href`, is a string that sets the URL of the linked
        - * page.
        - *
        - * The second parameter, `html`, is a string that sets the inner HTML of the
        - * link. It's common to use text, images, or buttons as links.
        - *
        - * The third parameter, `target`, is optional. It's a string that tells the
        - * web browser where to open the link. By default, links open in the current
        - * browser tab. Passing `'_blank'` will cause the link to open in a new
        - * browser tab. MDN describes a few
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target" target="_blank">other options</a>.
        - *
        - * @method createA
        - * @param  {String} href       URL of linked page.
        - * @param  {String} html       inner HTML of link element to display.
        - * @param  {String} [target]   target where the new link should open,
        - *                             either `'_blank'`, `'_self'`, `'_parent'`, or `'_top'`.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an anchor element that links to p5js.org.
        - *   let a = createA('http://p5js.org/', 'p5*js');
        - *   a.position(25, 35);
        - *
        - *   describe('The text "p5*js" written at the center of a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *
        - *   // Create an anchor tag that links to p5js.org.
        - *   // Open the link in a new tab.
        - *   let a = createA('http://p5js.org/', 'p5*js', '_blank');
        - *   a.position(25, 35);
        - *
        - *   describe('The text "p5*js" written at the center of a gray square.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createA = function (href, html, target) {
        -  p5._validateParameters('createA', arguments);
        -  const elt = document.createElement('a');
        -  elt.href = href;
        -  elt.innerHTML = html;
        -  if (target) elt.target = target;
        -  return addElement(elt, this);
        -};
        -
        -/** INPUT **/
        -
        -/**
        - * Creates a slider `&lt;input&gt;&lt;/input&gt;` element.
        - *
        - * Range sliders are useful for quickly selecting numbers from a given range.
        - *
        - * The first two parameters, `min` and `max`, are numbers that set the
        - * slider's minimum and maximum.
        - *
        - * The third parameter, `value`, is optional. It's a number that sets the
        - * slider's default value.
        - *
        - * The fourth parameter, `step`, is also optional. It's a number that sets the
        - * spacing between each value in the slider's range. Setting `step` to 0
        - * allows the slider to move smoothly from `min` to `max`.
        - *
        - * @method createSlider
        - * @param  {Number} min minimum value of the slider.
        - * @param  {Number} max maximum value of the slider.
        - * @param  {Number} [value] default value of the slider.
        - * @param  {Number} [step] size for each step in the slider's range.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let slider;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a slider and place it at the top of the canvas.
        - *   slider = createSlider(0, 255);
        - *   slider.position(10, 10);
        - *   slider.size(80);
        - *
        - *   describe('A dark gray square with a range slider at the top. The square changes color when the slider is moved.');
        - * }
        - *
        - * function draw() {
        - *   // Use the slider as a grayscale value.
        - *   let g = slider.value();
        - *   background(g);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let slider;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a slider and place it at the top of the canvas.
        - *   // Set its default value to 0.
        - *   slider = createSlider(0, 255, 0);
        - *   slider.position(10, 10);
        - *   slider.size(80);
        - *
        - *   describe('A black square with a range slider at the top. The square changes color when the slider is moved.');
        - * }
        - *
        - * function draw() {
        - *   // Use the slider as a grayscale value.
        - *   let g = slider.value();
        - *   background(g);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let slider;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a slider and place it at the top of the canvas.
        - *   // Set its default value to 0.
        - *   // Set its step size to 50.
        - *   slider = createSlider(0, 255, 0, 50);
        - *   slider.position(10, 10);
        - *   slider.size(80);
        - *
        - *   describe('A black square with a range slider at the top. The square changes color when the slider is moved.');
        - * }
        - *
        - * function draw() {
        - *   // Use the slider as a grayscale value.
        - *   let g = slider.value();
        - *   background(g);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let slider;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a slider and place it at the top of the canvas.
        - *   // Set its default value to 0.
        - *   // Set its step size to 0 so that it moves smoothly.
        - *   slider = createSlider(0, 255, 0, 0);
        - *   slider.position(10, 10);
        - *   slider.size(80);
        - *
        - *   describe('A black square with a range slider at the top. The square changes color when the slider is moved.');
        - * }
        - *
        - * function draw() {
        - *   // Use the slider as a grayscale value.
        - *   let g = slider.value();
        - *   background(g);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createSlider = function (min, max, value, step) {
        -  p5._validateParameters('createSlider', arguments);
        -  const elt = document.createElement('input');
        -  elt.type = 'range';
        -  elt.min = min;
        -  elt.max = max;
        -  if (step === 0) {
        -    elt.step = 0.000000000000000001; // smallest valid step
        -  } else if (step) {
        -    elt.step = step;
        -  }
        -  if (typeof value === 'number') elt.value = value;
        -  return addElement(elt, this);
        -};
        -
        -/**
        - * Creates a `&lt;button&gt;&lt;/button&gt;` element.
        - *
        - * The first parameter, `label`, is a string that sets the label displayed on
        - * the button.
        - *
        - * The second parameter, `value`, is optional. It's a string that sets the
        - * button's value. See
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#value" target="_blank">MDN</a>
        - * for more details.
        - *
        - * @method createButton
        - * @param  {String} label label displayed on the button.
        - * @param  {String} [value] value of the button.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a button and place it beneath the canvas.
        - *   let button = createButton('click me');
        - *   button.position(0, 100);
        - *
        - *   // Call repaint() when the button is pressed.
        - *   button.mousePressed(repaint);
        - *
        - *   describe('A gray square with a button that says "click me" beneath it. The square changes color when the button is clicked.');
        - * }
        - *
        - * // Change the background color.
        - * function repaint() {
        - *   let g = random(255);
        - *   background(g);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let button;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a button and set its value to 0.
        - *   // Place the button beneath the canvas.
        - *   button = createButton('click me', 'red');
        - *   button.position(0, 100);
        - *
        - *   // Call randomColor() when the button is pressed.
        - *   button.mousePressed(randomColor);
        - *
        - *   describe('A red square with a button that says "click me" beneath it. The square changes color when the button is clicked.');
        - * }
        - *
        - * function draw() {
        - *   // Use the button's value to set the background color.
        - *   let c = button.value();
        - *   background(c);
        - * }
        - *
        - * // Set the button's value to a random color.
        - * function randomColor() {
        - *   let c = random(['red', 'green', 'blue', 'yellow']);
        - *   button.value(c);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createButton = function (label, value) {
        -  p5._validateParameters('createButton', arguments);
        -  const elt = document.createElement('button');
        -  elt.innerHTML = label;
        -  if (value) elt.value = value;
        -  return addElement(elt, this);
        -};
        -
        -/**
        - * Creates a checkbox `&lt;input&gt;&lt;/input&gt;` element.
        - *
        - * Checkboxes extend the <a href="#/p5.Element">p5.Element</a> class with a
        - * `checked()` method. Calling `myBox.checked()` returns `true` if it the box
        - * is checked and `false` if not.
        - *
        - * The first parameter, `label`, is optional. It's a string that sets the label
        - * to display next to the checkbox.
        - *
        - * The second parameter, `value`, is also optional. It's a boolean that sets the
        - * checkbox's value.
        - *
        - * @method createCheckbox
        - * @param  {String} [label] label displayed after the checkbox.
        - * @param  {boolean} [value] value of the checkbox. Checked is `true` and unchecked is `false`.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let checkbox;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a checkbox and place it beneath the canvas.
        - *   checkbox = createCheckbox();
        - *   checkbox.position(0, 100);
        - *
        - *   describe('A black square with a checkbox beneath it. The square turns white when the box is checked.');
        - * }
        - *
        - * function draw() {
        - *   // Use the checkbox to set the background color.
        - *   if (checkbox.checked()) {
        - *     background(255);
        - *   } else {
        - *     background(0);
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let checkbox;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a checkbox and place it beneath the canvas.
        - *   // Label the checkbox "white".
        - *   checkbox = createCheckbox(' white');
        - *   checkbox.position(0, 100);
        - *
        - *   describe('A black square with a checkbox labeled "white" beneath it. The square turns white when the box is checked.');
        - * }
        - *
        - * function draw() {
        - *   // Use the checkbox to set the background color.
        - *   if (checkbox.checked()) {
        - *     background(255);
        - *   } else {
        - *     background(0);
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let checkbox;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a checkbox and place it beneath the canvas.
        - *   // Label the checkbox "white" and set its value to true.
        - *   checkbox = createCheckbox(' white', true);
        - *   checkbox.position(0, 100);
        - *
        - *   describe('A white square with a checkbox labeled "white" beneath it. The square turns black when the box is unchecked.');
        - * }
        - *
        - * function draw() {
        - *   // Use the checkbox to set the background color.
        - *   if (checkbox.checked()) {
        - *     background(255);
        - *   } else {
        - *     background(0);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createCheckbox = function(...args) {
        -  p5._validateParameters('createCheckbox', args);
        -
        -  // Create a container element
        -  const elt = document.createElement('div');
        -
        -  // Create checkbox type input element
        -  const checkbox = document.createElement('input');
        -  checkbox.type = 'checkbox';
        -
        -  // Create label element and wrap it around checkbox
        -  const label = document.createElement('label');
        -  label.appendChild(checkbox);
        -
        -  // Append label element inside the container
        -  elt.appendChild(label);
        -
        -  //checkbox must be wrapped in p5.Element before label so that label appears after
        -  const self = addElement(elt, this);
        -
        -  self.checked = function(...args) {
        -    const cb = self.elt.firstElementChild.getElementsByTagName('input')[0];
        -    if (cb) {
        -      if (args.length === 0) {
        -        return cb.checked;
        -      } else if (args[0]) {
        -        cb.checked = true;
        -      } else {
        -        cb.checked = false;
        -      }
        -    }
        -    return self;
        -  };
        -
        -  this.value = function (val) {
        -    self.value = val;
        -    return this;
        -  };
        -
        -  // Set the span element innerHTML as the label value if passed
        -  if (args[0]) {
        -    self.value(args[0]);
        -    const span = document.createElement('span');
        -    span.innerHTML = args[0];
        -    label.appendChild(span);
        -  }
        -
        -  // Set the checked value of checkbox if passed
        -  if (args[1]) {
        -    checkbox.checked = true;
        -  }
        -
        -  return self;
        -};
        -
        -/**
        - * Creates a dropdown menu `&lt;select&gt;&lt;/select&gt;` element.
        - *
        - * The parameter is optional. If `true` is passed, as in
        - * `let mySelect = createSelect(true)`, then the dropdown will support
        - * multiple selections. If an existing `&lt;select&gt;&lt;/select&gt;` element
        - * is passed, as in `let mySelect = createSelect(otherSelect)`, the existing
        - * element will be wrapped in a new <a href="#/p5.Element">p5.Element</a>
        - * object.
        - *
        - * Dropdowns extend the <a href="#/p5.Element">p5.Element</a> class with a few
        - * helpful methods for managing options:
        - * - `mySelect.option(name, [value])` adds an option to the menu. The first paremeter, `name`, is a string that sets the option's name and value. The second parameter, `value`, is optional. If provided, it sets the value that corresponds to the key `name`. If an option with `name` already exists, its value is changed to `value`.
        - * - `mySelect.value()` returns the currently-selected option's value.
        - * - `mySelect.selected()` returns the currently-selected option.
        - * - `mySelect.selected(option)` selects the given option by default.
        - * - `mySelect.disable()` marks the whole dropdown element as disabled.
        - * - `mySelect.disable(option)` marks a given option as disabled.
        - * - `mySelect.enable()` marks the whole dropdown element as enabled.
        - * - `mySelect.enable(option)` marks a given option as enabled.
        - *
        - * @method createSelect
        - * @param {boolean} [multiple] support multiple selections.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let mySelect;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a dropdown and place it beneath the canvas.
        - *   mySelect = createSelect();
        - *   mySelect.position(0, 100);
        - *
        - *   // Add color options.
        - *   mySelect.option('red');
        - *   mySelect.option('green');
        - *   mySelect.option('blue');
        - *   mySelect.option('yellow');
        - *
        - *   // Set the selected option to "red".
        - *   mySelect.selected('red');
        - *
        - *   describe('A red square with a dropdown menu beneath it. The square changes color when a new color is selected.');
        - * }
        - *
        - * function draw() {
        - *   // Use the selected value to paint the background.
        - *   let c = mySelect.selected();
        - *   background(c);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let mySelect;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a dropdown and place it beneath the canvas.
        - *   mySelect = createSelect();
        - *   mySelect.position(0, 100);
        - *
        - *   // Add color options.
        - *   mySelect.option('red');
        - *   mySelect.option('green');
        - *   mySelect.option('blue');
        - *   mySelect.option('yellow');
        - *
        - *   // Set the selected option to "red".
        - *   mySelect.selected('red');
        - *
        - *   // Disable the "yellow" option.
        - *   mySelect.disable('yellow');
        - *
        - *   describe('A red square with a dropdown menu beneath it. The square changes color when a new color is selected.');
        - * }
        - *
        - * function draw() {
        - *   // Use the selected value to paint the background.
        - *   let c = mySelect.selected();
        - *   background(c);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let mySelect;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a dropdown and place it beneath the canvas.
        - *   mySelect = createSelect();
        - *   mySelect.position(0, 100);
        - *
        - *   // Add color options with names and values.
        - *   mySelect.option('one', 'red');
        - *   mySelect.option('two', 'green');
        - *   mySelect.option('three', 'blue');
        - *   mySelect.option('four', 'yellow');
        - *
        - *   // Set the selected option to "one".
        - *   mySelect.selected('one');
        - *
        - *   describe('A red square with a dropdown menu beneath it. The square changes color when a new color is selected.');
        - * }
        - *
        - * function draw() {
        - *   // Use the selected value to paint the background.
        - *   let c = mySelect.selected();
        - *   background(c);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Hold CTRL to select multiple options on Windows and Linux.
        - * // Hold CMD to select multiple options on macOS.
        - * let mySelect;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a dropdown and allow multiple selections.
        - *   // Place it beneath the canvas.
        - *   mySelect = createSelect(true);
        - *   mySelect.position(0, 100);
        - *
        - *   // Add color options.
        - *   mySelect.option('red');
        - *   mySelect.option('green');
        - *   mySelect.option('blue');
        - *   mySelect.option('yellow');
        - *
        - *   describe('A gray square with a dropdown menu beneath it. Colorful circles appear when their color is selected.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Use the selected value(s) to draw circles.
        - *   let colors = mySelect.selected();
        - *   for (let i = 0; i < colors.length; i += 1) {
        - *     // Calculate the x-coordinate.
        - *     let x = 10 + i * 20;
        - *
        - *     // Access the color.
        - *     let c = colors[i];
        - *
        - *     // Draw the circle.
        - *     fill(c);
        - *     circle(x, 50, 20);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method createSelect
        - * @param {Object} existing select element to wrap, either as a <a href="#/p5.Element">p5.Element</a> or
        - *                          a <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement" target="_blank">HTMLSelectElement</a>.
        - * @return {p5.Element}
        - */
        -
        -p5.prototype.createSelect = function(...args) {
        -  p5._validateParameters('createSelect', args);
        -  let self;
        -  let arg = args[0];
        -  if (arg instanceof p5.Element && arg.elt instanceof HTMLSelectElement) {
        -    // If given argument is p5.Element of select type
        -    self = arg;
        -    this.elt = arg.elt;
        -  } else if (arg instanceof HTMLSelectElement) {
        -    self = addElement(arg, this);
        -    this.elt = arg;
        -  } else {
        -    const elt = document.createElement('select');
        -    if (arg && typeof arg === 'boolean') {
        -      elt.setAttribute('multiple', 'true');
        -    }
        -    self = addElement(elt, this);
        -    this.elt = elt;
        -  }
        -  self.option = function (name, value) {
        -    let index;
        -
        -    // if no name is passed, return
        -    if (name === undefined) {
        -      return;
        -    }
        -    //see if there is already an option with this name
        -    for (let i = 0; i < this.elt.length; i += 1) {
        -      if (this.elt[i].textContent === name) {
        -        index = i;
        -        break;
        -      }
        -    }
        -    //if there is an option with this name we will modify it
        -    if (index !== undefined) {
        -      //if the user passed in false then delete that option
        -      if (value === false) {
        -        this.elt.remove(index);
        -      } else {
        -        // Update the option at index with the value
        -        this.elt[index].value = value;
        -      }
        -    } else {
        -      //if it doesn't exist create it
        -      const opt = document.createElement('option');
        -      opt.textContent = name;
        -      opt.value = value === undefined ? name : value;
        -      this.elt.appendChild(opt);
        -      this._pInst._elements.push(opt);
        -    }
        -  };
        -
        -  self.selected = function (value) {
        -    // Update selected status of option
        -    if (value !== undefined) {
        -      for (let i = 0; i < this.elt.length; i += 1) {
        -        if (this.elt[i].value.toString() === value.toString()) {
        -          this.elt.selectedIndex = i;
        -        }
        -      }
        -      return this;
        -    } else {
        -      if (this.elt.getAttribute('multiple')) {
        -        let arr = [];
        -        for (const selectedOption of this.elt.selectedOptions) {
        -          arr.push(selectedOption.value);
        -        }
        -        return arr;
        -      } else {
        -        return this.elt.value;
        -      }
        -    }
        -  };
        -
        -  self.disable = function (value) {
        -    if (typeof value === 'string') {
        -      for (let i = 0; i < this.elt.length; i++) {
        -        if (this.elt[i].value.toString() === value) {
        -          this.elt[i].disabled = true;
        -          this.elt[i].selected = false;
        -        }
        -      }
        -    } else {
        -      this.elt.disabled = true;
        -    }
        -    return this;
        -  };
        -
        -  self.enable = function (value) {
        -    if (typeof value === 'string') {
        -      for (let i = 0; i < this.elt.length; i++) {
        -        if (this.elt[i].value.toString() === value) {
        -          this.elt[i].disabled = false;
        -          this.elt[i].selected = false;
        -        }
        -      }
        -    } else {
        -      this.elt.disabled = false;
        -      for (let i = 0; i < this.elt.length; i++) {
        -        this.elt[i].disabled = false;
        -        this.elt[i].selected = false;
        -      }
        -    }
        -    return this;
        -  };
        -
        -  return self;
        -};
        -
        -/**
        - * Creates a radio button element.
        - *
        - * The parameter is optional. If a string is passed, as in
        - * `let myRadio = createSelect('food')`, then each radio option will
        - * have `"food"` as its `name` parameter: `&lt;input name="food"&gt;`.
        - * If an existing `&lt;div&gt;&lt;/div&gt;` or `&lt;span&gt;&lt;/span&gt;`
        - * element is passed, as in `let myRadio = createSelect(container)`, it will
        - * become the radio button's parent element.
        - *
        - * Radio buttons extend the <a href="#/p5.Element">p5.Element</a> class with a few
        - * helpful methods for managing options:
        - * - `myRadio.option(value, [label])` adds an option to the menu. The first parameter, `value`, is a string that sets the option's value and label. The second parameter, `label`, is optional. If provided, it sets the label displayed for the `value`. If an option with `value` already exists, its label is changed and its value is returned.
        - * - `myRadio.value()` returns the currently-selected option's value.
        - * - `myRadio.selected()` returns the currently-selected option.
        - * - `myRadio.selected(value)` selects the given option and returns it as an <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement" target="_blank">`HTMLInputElement`</a>.
        - * - `myRadio.disable(shouldDisable)` Disables the radio button if `true` is passed, and enables it if `false` is passed.
        - *
        - * @method createRadio
        - * @param  {Object} [containerElement] container HTML Element, either a `&lt;div&gt;&lt;/div&gt;`
        - * or `&lt;span&gt;&lt;/span&gt;`.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let style = document.createElement('style');
        - * style.innerHTML = `
        - * .p5-radio label {
        - *    display: flex;
        - *    align-items: center;
        - *  }
        - *  .p5-radio input {
        - *    margin-right: 5px;
        - *  }
        - *  `;
        - * document.head.appendChild(style);
        - *
        - * let myRadio;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a radio button element and place it
        - *   // in the top-left corner.
        - *   myRadio = createRadio();
        - *   myRadio.position(0, 0);
        - *   myRadio.class('p5-radio');
        - *   myRadio.size(60);
        - *
        - *   // Add a few color options.
        - *   myRadio.option('red');
        - *   myRadio.option('yellow');
        - *   myRadio.option('blue');
        - *
        - *   // Choose a default option.
        - *   myRadio.selected('yellow');
        - *
        - *   describe('A yellow square with three color options listed, "red", "yellow", and "blue". The square changes color when the user selects a new option.');
        - * }
        - *
        - * function draw() {
        - *   // Set the background color using the radio button.
        - *   let g = myRadio.value();
        - *   background(g);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myRadio;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a radio button element and place it
        - *   // in the top-left corner.
        - *   myRadio = createRadio();
        - *   myRadio.position(0, 0);
        - *   myRadio.size(50);
        - *
        - *   // Add a few color options.
        - *   // Color values are labeled with
        - *   // emotions they evoke.
        - *   myRadio.option('red', 'love');
        - *   myRadio.option('yellow', 'joy');
        - *   myRadio.option('blue', 'trust');
        - *
        - *   // Choose a default option.
        - *   myRadio.selected('yellow');
        - *
        - *   describe('A yellow square with three options listed, "love", "joy", and "trust". The square changes color when the user selects a new option.');
        - * }
        - *
        - * function draw() {
        - *   // Set the background color using the radio button.
        - *   let c = myRadio.value();
        - *   background(c);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myRadio;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a radio button element and place it
        - *   // in the top-left corner.
        - *   myRadio = createRadio();
        - *   myRadio.position(0, 0);
        - *   myRadio.class('p5-radio');
        - *   myRadio.size(50);
        - *
        - *   // Add a few color options.
        - *   myRadio.option('red');
        - *   myRadio.option('yellow');
        - *   myRadio.option('blue');
        - *
        - *   // Choose a default option.
        - *   myRadio.selected('yellow');
        - *
        - *   // Create a button and place it beneath the canvas.
        - *   let btn = createButton('disable');
        - *   btn.position(0, 100);
        - *
        - *   // Call disableRadio() when btn is pressed.
        - *   btn.mousePressed(disableRadio);
        - *
        - *   describe('A yellow square with three options listed, "red", "yellow", and "blue". The square changes color when the user selects a new option. A "disable" button beneath the canvas disables the color options when pressed.');
        - * }
        - *
        - * function draw() {
        - *   // Set the background color using the radio button.
        - *   let c = myRadio.value();
        - *   background(c);
        - * }
        - *
        - * // Disable myRadio.
        - * function disableRadio() {
        - *   myRadio.disable(true);
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method createRadio
        - * @param {String} [name] name parameter assigned to each option's `&lt;input&gt;&lt;/input&gt;` element.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - */
        -/**
        - * @method createRadio
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - */
        -
        -//counter for unique names on radio button
        -let counter = 0;
        -p5.prototype.createRadio = function(...args) {
        -  // Creates a div, adds each option as an individual input inside it.
        -  // If already given with a containerEl, will search for all input[radio]
        -  // it, create a p5.Element out of it, add options to it and return the p5.Element.
        -
        -  let self;
        -  let radioElement;
        -  let name;
        -  const arg0 = args[0];
        -  if (
        -    arg0 instanceof p5.Element &&
        -    (arg0.elt instanceof HTMLDivElement || arg0.elt instanceof HTMLSpanElement)
        -  ) {
        -    // If given argument is p5.Element of div/span type
        -    self = arg0;
        -    this.elt = arg0.elt;
        -  } else if (
        -    // If existing radio Element is provided as argument 0
        -    arg0 instanceof HTMLDivElement ||
        -    arg0 instanceof HTMLSpanElement
        -  ) {
        -    self = addElement(arg0, this);
        -    this.elt = arg0;
        -    radioElement = arg0;
        -    if (typeof args[1] === 'string') name = args[1];
        -  } else {
        -    if (typeof arg0 === 'string') name = arg0;
        -    radioElement = document.createElement('div');
        -    self = addElement(radioElement, this);
        -    this.elt = radioElement;
        -  }
        -
        -  // Generate a unique name for each radio group if not provided
        -  self._name = name || `radioOption_${counter++}`;
        -  // setup member functions
        -  const isRadioInput = el =>
        -    el instanceof HTMLInputElement && el.type === 'radio';
        -  const isLabelElement = el => el instanceof HTMLLabelElement;
        -  const isSpanElement = el => el instanceof HTMLSpanElement;
        -
        -  self._getOptionsArray = function () {
        -    return Array.from(this.elt.children)
        -      .filter(
        -        el =>
        -          isRadioInput(el) ||
        -          (isLabelElement(el) && isRadioInput(el.firstElementChild))
        -      )
        -      .map(el => (isRadioInput(el) ? el : el.firstElementChild));
        -  };
        -
        -  self.option = function (value, label) {
        -    // return an option with this value, create if not exists.
        -    let optionEl;
        -    for (const option of self._getOptionsArray()) {
        -      if (option.value === value) {
        -        optionEl = option;
        -        break;
        -      }
        -    }
        -
        -    // Create a new option, add it to radioElement and return it.
        -    if (optionEl === undefined) {
        -      optionEl = document.createElement('input');
        -      optionEl.setAttribute('type', 'radio');
        -      optionEl.setAttribute('value', value);
        -    }
        -    optionEl.setAttribute('name', self._name);
        -
        -    // Check if label element exists, else create it
        -    let labelElement;
        -    if (!isLabelElement(optionEl.parentElement)) {
        -      labelElement = document.createElement('label');
        -      labelElement.insertAdjacentElement('afterbegin', optionEl);
        -    } else {
        -      labelElement = optionEl.parentElement;
        -    }
        -
        -    // Check if span element exists, else create it
        -    let spanElement;
        -    if (!isSpanElement(labelElement.lastElementChild)) {
        -      spanElement = document.createElement('span');
        -      optionEl.insertAdjacentElement('afterend', spanElement);
        -    } else {
        -      spanElement = labelElement.lastElementChild;
        -    }
        -
        -    // Set the innerHTML of span element as the label text
        -    spanElement.innerHTML = label === undefined ? value : label;
        -
        -    // Append the label element, which includes option element and
        -    // span element to the radio container element
        -    this.elt.appendChild(labelElement);
        -
        -    return optionEl;
        -  };
        -
        -  self.remove = function (value) {
        -    for (const optionEl of self._getOptionsArray()) {
        -      if (optionEl.value === value) {
        -        if (isLabelElement(optionEl.parentElement)) {
        -          // Remove parent label which also removes children elements
        -          optionEl.parentElement.remove();
        -        } else {
        -          // Remove the option input if parent label does not exist
        -          optionEl.remove();
        -        }
        -        return;
        -      }
        -    }
        -  };
        -
        -  self.value = function () {
        -    let result = '';
        -    for (const option of self._getOptionsArray()) {
        -      if (option.checked) {
        -        result = option.value;
        -        break;
        -      }
        -    }
        -    return result;
        -  };
        -
        -  self.selected = function (value) {
        -    let result = null;
        -    if (value === undefined) {
        -      for (const option of self._getOptionsArray()) {
        -        if (option.checked) {
        -          result = option;
        -          break;
        -        }
        -      }
        -    } else {
        -      // forEach loop to uncheck all radio buttons before
        -      // setting any one as checked.
        -      self._getOptionsArray().forEach(option => {
        -        option.checked = false;
        -        option.removeAttribute('checked');
        -      });
        -
        -      for (const option of self._getOptionsArray()) {
        -        if (option.value === value) {
        -          option.setAttribute('checked', true);
        -          option.checked = true;
        -          result = option;
        -        }
        -      }
        -    }
        -    return result;
        -  };
        -
        -  self.disable = function (shouldDisable = true) {
        -    for (const radioInput of self._getOptionsArray()) {
        -      radioInput.setAttribute('disabled', shouldDisable);
        -    }
        -  };
        -
        -  return self;
        -};
        -
        -/**
        - * Creates a color picker element.
        - *
        - * The parameter, `value`, is optional. If a color string or
        - * <a href="#/p5.Color">p5.Color</a> object is passed, it will set the default
        - * color.
        - *
        - * Color pickers extend the <a href="#/p5.Element">p5.Element</a> class with a
        - * couple of helpful methods for managing colors:
        - * - `myPicker.value()` returns the current color as a hex string in the format `'#rrggbb'`.
        - * - `myPicker.color()` returns the current color as a <a href="#/p5.Color">p5.Color</a> object.
        - *
        - * @method createColorPicker
        - * @param {String|p5.Color} [value] default color as a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color" target="_blank">CSS color string</a>.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myPicker;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a color picker and set its position.
        - *   myPicker = createColorPicker('deeppink');
        - *   myPicker.position(0, 100);
        - *
        - *   describe('A pink square with a color picker beneath it. The square changes color when the user picks a new color.');
        - * }
        - *
        - * function draw() {
        - *   // Use the color picker to paint the background.
        - *   let c = myPicker.color();
        - *   background(c);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myPicker;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a color picker and set its position.
        - *   myPicker = createColorPicker('deeppink');
        - *   myPicker.position(0, 100);
        - *
        - *   describe('A number with the format "#rrggbb" is displayed on a pink canvas. The background color and number change when the user picks a new color.');
        - * }
        - *
        - * function draw() {
        - *   // Use the color picker to paint the background.
        - *   let c = myPicker.value();
        - *   background(c);
        - *
        - *   // Display the current color as a hex string.
        - *   text(c, 25, 55);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createColorPicker = function (value) {
        -  p5._validateParameters('createColorPicker', arguments);
        -  const elt = document.createElement('input');
        -  let self;
        -  elt.type = 'color';
        -  if (value) {
        -    if (value instanceof p5.Color) {
        -      elt.value = value.toString('#rrggbb');
        -    } else {
        -      p5.prototype._colorMode = 'rgb';
        -      p5.prototype._colorMaxes = {
        -        rgb: [255, 255, 255, 255],
        -        hsb: [360, 100, 100, 1],
        -        hsl: [360, 100, 100, 1]
        -      };
        -      elt.value = p5.prototype.color(value).toString('#rrggbb');
        -    }
        -  } else {
        -    elt.value = '#000000';
        -  }
        -  self = addElement(elt, this);
        -  // Method to return a p5.Color object for the given color.
        -  self.color = function () {
        -    if (value) {
        -      if (value.mode) {
        -        p5.prototype._colorMode = value.mode;
        -      }
        -      if (value.maxes) {
        -        p5.prototype._colorMaxes = value.maxes;
        -      }
        -    }
        -    return p5.prototype.color(this.elt.value);
        -  };
        -  return self;
        -};
        -
        -/**
        - * Creates a text `&lt;input&gt;&lt;/input&gt;` element.
        - *
        - * Call `myInput.size()` to set the length of the text box.
        - *
        - * The first parameter, `value`, is optional. It's a string that sets the
        - * input's default value. The input is blank by default.
        - *
        - * The second parameter, `type`, is also optional. It's a string that
        - * specifies the type of text being input. See MDN for a full
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input" target="_blank">list of options</a>.
        - * The default is `'text'`.
        - *
        - * @method createInput
        - * @param {String} [value] default value of the input box. Defaults to an empty string `''`.
        - * @param {String} [type] type of input. Defaults to `'text'`.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myInput;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create an input element and place it
        - *   // beneath the canvas.
        - *   myInput = createInput();
        - *   myInput.position(0, 100);
        - *
        - *   describe('A gray square with a text box beneath it. The text in the square changes when the user types something new in the input bar.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Use the input to display a message.
        - *   let msg = myInput.value();
        - *   text(msg, 25, 55);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myInput;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create an input element and place it
        - *   // beneath the canvas. Set its default
        - *   // text to "hello!".
        - *   myInput = createInput('hello!');
        - *   myInput.position(0, 100);
        - *
        - *   describe('The text "hello!" written at the center of a gray square. A text box beneath the square also says "hello!". The text in the square changes when the user types something new in the input bar.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Use the input to display a message.
        - *   let msg = myInput.value();
        - *   text(msg, 25, 55);
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method createInput
        - * @param {String} [value]
        - * @return {p5.Element}
        - */
        -p5.prototype.createInput = function (value = '', type = 'text') {
        -  p5._validateParameters('createInput', arguments);
        -  let elt = document.createElement('input');
        -  elt.setAttribute('value', value);
        -  elt.setAttribute('type', type);
        -  return addElement(elt, this);
        -};
        -
        -/**
        - * Creates an `&lt;input&gt;&lt;/input&gt;` element of type `'file'`.
        - *
        - * `createFileInput()` allows users to select local files for use in a sketch.
        - * It returns a <a href="#/p5.File">p5.File</a> object.
        - *
        - * The first parameter, `callback`, is a function that's called when the file
        - * loads. The callback function should have one parameter, `file`, that's a
        - * <a href="#/p5.File">p5.File</a> object.
        - *
        - * The second parameter, `multiple`, is optional. It's a boolean value that
        - * allows loading multiple files if set to `true`. If `true`, `callback`
        - * will be called once per file.
        - *
        - * @method createFileInput
        - * @param  {Function} callback function to call once the file loads.
        - * @param  {Boolean} [multiple] allow multiple files to be selected.
        - * @return {p5.File} new <a href="#/p5.File">p5.File</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Use the file input to select an image to
        - * // load and display.
        - * let input;
        - * let img;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a file input and place it beneath
        - *   // the canvas.
        - *   input = createFileInput(handleImage);
        - *   input.position(0, 100);
        - *
        - *   describe('A gray square with a file input beneath it. If the user selects an image file to load, it is displayed on the square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the image if loaded.
        - *   if (img) {
        - *     image(img, 0, 0, width, height);
        - *   }
        - * }
        - *
        - * // Create an image if the file is an image.
        - * function handleImage(file) {
        - *   if (file.type === 'image') {
        - *     img = createImg(file.data, '');
        - *     img.hide();
        - *   } else {
        - *     img = null;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Use the file input to select multiple images
        - * // to load and display.
        - * let input;
        - * let images = [];
        - *
        - * function setup() {
        - *   // Create a file input and place it beneath
        - *   // the canvas. Allow it to load multiple files.
        - *   input = createFileInput(handleImage, true);
        - *   input.position(0, 100);
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the images if loaded. Each image
        - *   // is drawn 20 pixels lower than the
        - *   // previous image.
        - *   for (let i = 0; i < images.length; i += 1) {
        - *     // Calculate the y-coordinate.
        - *     let y = i * 20;
        - *
        - *     // Draw the image.
        - *     image(img, 0, y, 100, 100);
        - *   }
        - *
        - *   describe('A gray square with a file input beneath it. If the user selects multiple image files to load, they are displayed on the square.');
        - * }
        - *
        - * // Create an image if the file is an image,
        - * // then add it to the images array.
        - * function handleImage(file) {
        - *   if (file.type === 'image') {
        - *     let img = createImg(file.data, '');
        - *     img.hide();
        - *     images.push(img);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createFileInput = function (callback, multiple = false) {
        -  p5._validateParameters('createFileInput', arguments);
        -
        -  const handleFileSelect = function (event) {
        -    for (const file of event.target.files) {
        -      p5.File._load(file, callback);
        -    }
        -  };
        -
        -  // If File API's are not supported, throw Error
        -  if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
        -    console.log(
        -      'The File APIs are not fully supported in this browser. Cannot create element.'
        -    );
        -    return;
        -  }
        -
        -  const fileInput = document.createElement('input');
        -  fileInput.setAttribute('type', 'file');
        -  if (multiple) fileInput.setAttribute('multiple', true);
        -  fileInput.addEventListener('change', handleFileSelect, false);
        -  return addElement(fileInput, this);
        -};
        -
        -/** VIDEO STUFF **/
        -
        -// Helps perform similar tasks for media element methods.
        -function createMedia(pInst, type, src, callback) {
        -  const elt = document.createElement(type);
        -
        -  // Create source elements from given sources
        -  src = src || '';
        -  if (typeof src === 'string') {
        -    src = [src];
        -  }
        -  for (const mediaSource of src) {
        -    const sourceEl = document.createElement('source');
        -    sourceEl.setAttribute('src', mediaSource);
        -    elt.appendChild(sourceEl);
        -  }
        -
        -  // If callback is provided, attach to element
        -  if (typeof callback === 'function') {
        -    const callbackHandler = () => {
        -      callback();
        -      elt.removeEventListener('canplaythrough', callbackHandler);
        -    };
        -    elt.addEventListener('canplaythrough', callbackHandler);
        -  }
        -
        -  const mediaEl = addElement(elt, pInst, true);
        -  mediaEl.loadedmetadata = false;
        -
        -  // set width and height onload metadata
        -  elt.addEventListener('loadedmetadata', () => {
        -    mediaEl.width = elt.videoWidth;
        -    mediaEl.height = elt.videoHeight;
        -
        -    // set elt width and height if not set
        -    if (mediaEl.elt.width === 0) mediaEl.elt.width = elt.videoWidth;
        -    if (mediaEl.elt.height === 0) mediaEl.elt.height = elt.videoHeight;
        -    if (mediaEl.presetPlaybackRate) {
        -      mediaEl.elt.playbackRate = mediaEl.presetPlaybackRate;
        -      delete mediaEl.presetPlaybackRate;
        -    }
        -    mediaEl.loadedmetadata = true;
        -  });
        -
        -  return mediaEl;
        -}
        -
        -/**
        - * Creates a `&lt;video&gt;` element for simple audio/video playback.
        - *
        - * `createVideo()` returns a new
        - * <a href="#/p5.MediaElement">p5.MediaElement</a> object. Videos are shown by
        - * default. They can be hidden by calling `video.hide()` and drawn to the
        - * canvas using <a href="#/p5/image">image()</a>.
        - *
        - * The first parameter, `src`, is the path the video. If a single string is
        - * passed, as in `'assets/topsecret.mp4'`, a single video is loaded. An array
        - * of strings can be used to load the same video in different formats. For
        - * example, `['assets/topsecret.mp4', 'assets/topsecret.ogv', 'assets/topsecret.webm']`.
        - * This is useful for ensuring that the video can play across different browsers with
        - * different capabilities. See
        - * <a href='https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats'>MDN</a>
        - * for more information about supported formats.
        - *
        - * The second parameter, `callback`, is optional. It's a function to call once
        - * the video is ready to play.
        - *
        - * @method createVideo
        - * @param  {String|String[]} src path to a video file, or an array of paths for
        - *                               supporting different browsers.
        - * @param  {Function} [callback] function to call once the video is ready to play.
        - * @return {p5.MediaElement}   new <a href="#/p5.MediaElement">p5.MediaElement</a> object.
        - *
        - * @example
        - * <div class='notest'>
        - * <code>
        - * function setup() {
        - *   noCanvas();
        - *
        - *   // Load a video and add it to the page.
        - *   // Note: this may not work in some browsers.
        - *   let video = createVideo('assets/small.mp4');
        - *
        - *   // Show the default video controls.
        - *   video.showControls();
        - *
        - *   describe('A video of a toy robot with playback controls beneath it.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='notest'>
        - * <code>
        - * function setup() {
        - *   noCanvas();
        - *
        - *   // Load a video and add it to the page.
        - *   // Provide an array options for different file formats.
        - *   let video = createVideo(
        - *     ['assets/small.mp4', 'assets/small.ogv', 'assets/small.webm']
        - *   );
        - *
        - *   // Show the default video controls.
        - *   video.showControls();
        - *
        - *   describe('A video of a toy robot with playback controls beneath it.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='notest'>
        - * <code>
        - * let video;
        - *
        - * function setup() {
        - *   noCanvas();
        - *
        - *   // Load a video and add it to the page.
        - *   // Provide an array options for different file formats.
        - *   // Call mute() once the video loads.
        - *   video = createVideo(
        - *     ['assets/small.mp4', 'assets/small.ogv', 'assets/small.webm'],
        - *     muteVideo
        - *   );
        - *
        - *   // Show the default video controls.
        - *   video.showControls();
        - *
        - *   describe('A video of a toy robot with playback controls beneath it.');
        - * }
        - *
        - * // Mute the video once it loads.
        - * function muteVideo() {
        - *   video.volume(0);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createVideo = function (src, callback) {
        -  p5._validateParameters('createVideo', arguments);
        -  return createMedia(this, 'video', src, callback);
        -};
        -
        -/** AUDIO STUFF **/
        -
        -/**
        - * Creates a hidden `&lt;audio&gt;` element for simple audio playback.
        - *
        - * `createAudio()` returns a new
        - * <a href="#/p5.MediaElement">p5.MediaElement</a> object.
        - *
        - * The first parameter, `src`, is the path the video. If a single string is
        - * passed, as in `'assets/video.mp4'`, a single video is loaded. An array
        - * of strings can be used to load the same video in different formats. For
        - * example, `['assets/video.mp4', 'assets/video.ogv', 'assets/video.webm']`.
        - * This is useful for ensuring that the video can play across different
        - * browsers with different capabilities. See
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats" target="_blank">MDN</a>
        - * for more information about supported formats.
        - *
        - * The second parameter, `callback`, is optional. It's a function to call once
        - * the audio is ready to play.
        - *
        - * @method createAudio
        - * @param  {String|String[]} [src] path to an audio file, or an array of paths
        - *                                 for supporting different browsers.
        - * @param  {Function} [callback]   function to call once the audio is ready to play.
        - * @return {p5.MediaElement}       new <a href="#/p5.MediaElement">p5.MediaElement</a> object.
        - *
        - * @example
        - * <div class='notest'>
        - * <code>
        - * function setup() {
        - *   noCanvas();
        - *
        - *   // Load the audio.
        - *   let beat = createAudio('assets/beat.mp3');
        - *
        - *   // Show the default audio controls.
        - *   beat.showControls();
        - *
        - *   describe('An audio beat plays when the user double-clicks the square.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createAudio = function (src, callback) {
        -  p5._validateParameters('createAudio', arguments);
        -  return createMedia(this, 'audio', src, callback);
        -};
        -
        -/** CAMERA STUFF **/
        -
        -p5.prototype.VIDEO = 'video';
        -
        -p5.prototype.AUDIO = 'audio';
        -
        -// from: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
        -// Older browsers might not implement mediaDevices at all, so we set an empty object first
        -if (navigator.mediaDevices === undefined) {
        -  navigator.mediaDevices = {};
        -}
        -
        -// Some browsers partially implement mediaDevices. We can't just assign an object
        -// with getUserMedia as it would overwrite existing properties.
        -// Here, we will just add the getUserMedia property if it's missing.
        -if (navigator.mediaDevices.getUserMedia === undefined) {
        -  navigator.mediaDevices.getUserMedia = function (constraints) {
        -    // First get ahold of the legacy getUserMedia, if present
        -    const getUserMedia =
        -      navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
        -
        -    // Some browsers just don't implement it - return a rejected promise with an error
        -    // to keep a consistent interface
        -    if (!getUserMedia) {
        -      return Promise.reject(
        -        new Error('getUserMedia is not implemented in this browser')
        -      );
        -    }
        -
        -    // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
        -    return new Promise(function (resolve, reject) {
        -      getUserMedia.call(navigator, constraints, resolve, reject);
        -    });
        -  };
        -}
        -
        -/**
        - * Creates a `&lt;video&gt;` element that "captures" the audio/video stream from
        - * the webcam and microphone.
        - *
        - * `createCapture()` returns a new
        - * <a href="#/p5.MediaElement">p5.MediaElement</a> object. Videos are shown by
        - * default. They can be hidden by calling `capture.hide()` and drawn to the
        - * canvas using <a href="#/p5/image">image()</a>.
        - *
        - * The first parameter, `type`, is optional. It sets the type of capture to
        - * use. By default, `createCapture()` captures both audio and video. If `VIDEO`
        - * is passed, as in `createCapture(VIDEO)`, only video will be captured.
        - * If `AUDIO` is passed, as in `createCapture(AUDIO)`, only audio will be
        - * captured. A constraints object can also be passed to customize the stream.
        - * See the <a href="http://w3c.github.io/mediacapture-main/getusermedia.html#media-track-constraints" target="_blank">
        - * W3C documentation</a> for possible properties. Different browsers support different
        - * properties.
        - *
        - * The 'flipped' property is an optional property which can be set to `{flipped:true}`
        - * to mirror the video output.If it is true then it means that video will be mirrored
        - * or flipped and if nothing is mentioned then by default it will be `false`.
        - *
        - * The second parameter,`callback`, is optional. It's a function to call once
        - * the capture is ready for use. The callback function should have one
        - * parameter, `stream`, that's a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaStream" target="_blank">MediaStream</a> object.
        - *
        - * Note: `createCapture()` only works when running a sketch locally or using HTTPS. Learn more
        - * <a href="http://stackoverflow.com/questions/34197653/getusermedia-in-chrome-47-without-using-https" target="_blank">here</a>
        - * and <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia" target="_blank">here</a>.
        - *
        - * @method createCapture
        - * @param  {String|Constant|Object}  [type] type of capture, either AUDIO or VIDEO,
        - *                                   or a constraints object. Both video and audio
        - *                                   audio streams are captured by default.
        - * @param  {Object}                  [flipped] flip the capturing video and mirror the output with `{flipped:true}`. By
        - *                                   default it is false.
        - * @param  {Function}                [callback] function to call once the stream
        - *                                   has loaded.
        - * @return {p5.MediaElement} new <a href="#/p5.MediaElement">p5.MediaElement</a> object.
        - *
        - * @example
        - * <div class='notest'>
        - * <code>
        - * function setup() {
        - *   noCanvas();
        - *
        - *   // Create the video capture.
        - *   createCapture(VIDEO);
        - *
        - *   describe('A video stream from the webcam.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='notest'>
        - * <code>
        - * let capture;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create the video capture and hide the element.
        - *   capture = createCapture(VIDEO);
        - *   capture.hide();
        - *
        - *   describe('A video stream from the webcam with inverted colors.');
        - * }
        - *
        - * function draw() {
        - *   // Draw the video capture within the canvas.
        - *   image(capture, 0, 0, width, width * capture.height / capture.width);
        - *
        - *   // Invert the colors in the stream.
        - *   filter(INVERT);
        - * }
        - * </code>
        - * </div>
        - * <div class='notest'>
        - * <code>
        - * let capture;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create the video capture with mirrored output.
        - *   capture = createCapture(VIDEO,{ flipped:true });
        - *   capture.size(100,100);
        - *
        - *   describe('A video stream from the webcam with flipped or mirrored output.');
        - * }
        - *
        - * </code>
        - * </div>
        - *
        - * <div class='notest norender'>
        - * <code>
        - * function setup() {
        - *   createCanvas(480, 120);
        - *
        - *   // Create a constraints object.
        - *   let constraints = {
        - *     video: {
        - *       mandatory: {
        - *         minWidth: 1280,
        - *         minHeight: 720
        - *       },
        - *       optional: [{ maxFrameRate: 10 }]
        - *     },
        - *     audio: false
        - *   };
        - *
        - *   // Create the video capture.
        - *   createCapture(constraints);
        - *
        - *   describe('A video stream from the webcam.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createCapture = function(...args) {
        -  p5._validateParameters('createCapture', args);
        -
        -  // return if getUserMedia is not supported by the browser
        -  if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
        -    throw new DOMException('getUserMedia not supported in this browser');
        -  }
        -
        -  let useVideo = true;
        -  let useAudio = true;
        -  let constraints;
        -  let callback;
        -  let flipped = false;
        -
        -  for (const arg of args) {
        -    if (arg === p5.prototype.VIDEO) useAudio = false;
        -    else if (arg === p5.prototype.AUDIO) useVideo = false;
        -    else if (typeof arg === 'object') {
        -      if (arg.flipped !== undefined) {
        -        flipped = arg.flipped;
        -        delete arg.flipped;
        -      }
        -      constraints = Object.assign({}, constraints, arg);
        -    }
        -    else if (typeof arg === 'function') {
        -      callback = arg;
        -    }
        -  }
        -
        -  const videoConstraints = { video: useVideo, audio: useAudio };
        -  constraints = Object.assign({}, videoConstraints, constraints);
        -  const domElement = document.createElement('video');
        -  // required to work in iOS 11 & up:
        -  domElement.setAttribute('playsinline', '');
        -  navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
        -    try {
        -      if ('srcObject' in domElement) {
        -        domElement.srcObject = stream;
        -      } else {
        -        domElement.src = window.URL.createObjectURL(stream);
        -      }
        -    }
        -    catch(err) {
        -      domElement.src = stream;
        -    }
        -  }).catch(e => {
        -    if (e.name === 'NotFoundError')
        -      p5._friendlyError('No webcam found on this device', 'createCapture');
        -    if (e.name === 'NotAllowedError')
        -      p5._friendlyError('Access to the camera was denied', 'createCapture');
        -
        -    console.error(e);
        -  });
        -
        -  const videoEl = addElement(domElement, this, true);
        -  videoEl.loadedmetadata = false;
        -  // set width and height onload metadata
        -  domElement.addEventListener('loadedmetadata', function () {
        -    domElement.play();
        -    if (domElement.width) {
        -      videoEl.width = domElement.width;
        -      videoEl.height = domElement.height;
        -      if (flipped) {
        -        videoEl.elt.style.transform = 'scaleX(-1)';
        -      }
        -    } else {
        -      videoEl.width = videoEl.elt.width = domElement.videoWidth;
        -      videoEl.height = videoEl.elt.height = domElement.videoHeight;
        -    }
        -    videoEl.loadedmetadata = true;
        -
        -    if (callback) callback(domElement.srcObject);
        -  });
        -  videoEl.flipped=flipped;
        -  return videoEl;
        -};
        -
        -
        -/**
        - * Creates a new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * The first parameter, `tag`, is a string an HTML tag such as `'h5'`.
        - *
        - * The second parameter, `content`, is optional. It's a string that sets the
        - * HTML content to insert into the new element. New elements have no content
        - * by default.
        - *
        - * @method createElement
        - * @param  {String} tag tag for the new element.
        - * @param  {String} [content] HTML content to insert into the element.
        - * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an h5 element with nothing in it.
        - *   createElement('h5');
        - *
        - *   describe('A gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an h5 element with the content "p5*js".
        - *   let h5 = createElement('h5', 'p5*js');
        - *
        - *   // Set the element's style and position.
        - *   h5.style('color', 'deeppink');
        - *   h5.position(30, 15);
        - *
        - *   describe('The text "p5*js" written in pink in the middle of a gray square.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createElement = function (tag, content) {
        -  p5._validateParameters('createElement', arguments);
        -  const elt = document.createElement(tag);
        -  if (typeof content !== 'undefined') {
        -    elt.innerHTML = content;
        -  }
        -  return addElement(elt, this);
        -};
        -
        -// =============================================================================
        -//                         p5.Element additions
        -// =============================================================================
        -/**
        - *
        - * Adds a class to the element.
        - *
        - * @for p5.Element
        - * @method addClass
        - * @param  {String} class name of class to add.
        - * @chainable
        - *
        - * @example
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a div element.
        - *   let div = createDiv('div');
        - *
        - *   // Add a class to the div.
        - *   div.addClass('myClass');
        - *
        - *   describe('A gray square.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.addClass = function (c) {
        -  if (this.elt.className) {
        -    if (!this.hasClass(c)) {
        -      this.elt.className = this.elt.className + ' ' + c;
        -    }
        -  } else {
        -    this.elt.className = c;
        -  }
        -  return this;
        -};
        -
        -/**
        - * Removes a class from the element.
        - *
        - * @method removeClass
        - * @param  {String} class name of class to remove.
        - * @chainable
        - *
        - * @example
        - * <div class='norender'>
        - * <code>
        - * // In this example, a class is set when the div is created
        - * // and removed when mouse is pressed. This could link up
        - * // with a CSS style rule to toggle style properties.
        - *
        - * let div;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a div element.
        - *   div = createDiv('div');
        - *
        - *   // Add a class to the div.
        - *   div.addClass('myClass');
        - *
        - *   describe('A gray square.');
        - * }
        - *
        - * // Remove 'myClass' from the div when the user presses the mouse.
        - * function mousePressed() {
        - *   div.removeClass('myClass');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.removeClass = function (c) {
        -  // Note: Removing a class that does not exist does NOT throw an error in classList.remove method
        -  this.elt.classList.remove(c);
        -  return this;
        -};
        -
        -/**
        - * Checks if a class is already applied to element.
        - *
        - * @method hasClass
        - * @returns {boolean} a boolean value if element has specified class.
        - * @param c {String} name of class to check.
        - *
        - * @example
        - * <div class='norender'>
        - * <code>
        - * let div;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a div element.
        - *   div = createDiv('div');
        - *
        - *   // Add the class 'show' to the div.
        - *   div.addClass('show');
        - *
        - *   describe('A gray square.');
        - * }
        - *
        - * // Toggle the class 'show' when the mouse is pressed.
        - * function mousePressed() {
        - *   if (div.hasClass('show')) {
        - *     div.addClass('show');
        - *   } else {
        - *     div.removeClass('show');
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.hasClass = function (c) {
        -  return this.elt.classList.contains(c);
        -};
        -
        -/**
        - * Toggles whether a class is applied to the element.
        - *
        - * @method toggleClass
        - * @param c {String} class name to toggle.
        - * @chainable
        - *
        - * @example
        - * <div class='norender'>
        - * <code>
        - * let div;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a div element.
        - *   div = createDiv('div');
        - *
        - *   // Add the 'show' class to the div.
        - *   div.addClass('show');
        - *
        - *   describe('A gray square.');
        - * }
        - *
        - * // Toggle the 'show' class when the mouse is pressed.
        - * function mousePressed() {
        - *   div.toggleClass('show');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.toggleClass = function (c) {
        -  // classList also has a toggle() method, but we cannot use that yet as support is unclear.
        -  // See https://github.com/processing/p5.js/issues/3631
        -  // this.elt.classList.toggle(c);
        -  if (this.elt.classList.contains(c)) {
        -    this.elt.classList.remove(c);
        -  } else {
        -    this.elt.classList.add(c);
        -  }
        -  return this;
        -};
        -
        -/**
        - * Attaches the element as a child of another element.
        - *
        - * `myElement.child()` accepts either a string ID, DOM node, or
        - * <a href="#/p5.Element">p5.Element</a>. For example,
        - * `myElement.child(otherElement)`. If no argument is provided, an array of
        - * children DOM nodes is returned.
        - *
        - * @method child
        - * @returns {Node[]} an array of child nodes.
        - *
        - * @example
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create the div elements.
        - *   let div0 = createDiv('Parent');
        - *   let div1 = createDiv('Child');
        - *
        - *   // Make div1 the child of div0
        - *   // using the p5.Element.
        - *   div0.child(div1);
        - *
        - *   describe('A gray square with the words "Parent" and "Child" written beneath it.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create the div elements.
        - *   let div0 = createDiv('Parent');
        - *   let div1 = createDiv('Child');
        - *
        - *   // Give div1 an ID.
        - *   div1.id('apples');
        - *
        - *   // Make div1 the child of div0
        - *   // using its ID.
        - *   div0.child('apples');
        - *
        - *   describe('A gray square with the words "Parent" and "Child" written beneath it.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='norender notest'>
        - * <code>
        - * // This example assumes there is a div already on the page
        - * // with id "myChildDiv".
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create the div elements.
        - *   let div0 = createDiv('Parent');
        - *
        - *   // Select the child element by its ID.
        - *   let elt = document.getElementById('myChildDiv');
        - *
        - *   // Make div1 the child of div0
        - *   // using its HTMLElement object.
        - *   div0.child(elt);
        - *
        - *   describe('A gray square with the words "Parent" and "Child" written beneath it.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method child
        - * @param  {String|p5.Element} [child] the ID, DOM node, or <a href="#/p5.Element">p5.Element</a>
        - *                         to add to the current element
        - * @chainable
        - */
        -p5.Element.prototype.child = function (childNode) {
        -  if (typeof childNode === 'undefined') {
        -    return this.elt.childNodes;
        -  }
        -  if (typeof childNode === 'string') {
        -    if (childNode[0] === '#') {
        -      childNode = childNode.substring(1);
        -    }
        -    childNode = document.getElementById(childNode);
        -  } else if (childNode instanceof p5.Element) {
        -    childNode = childNode.elt;
        -  }
        -
        -  if (childNode instanceof HTMLElement) {
        -    this.elt.appendChild(childNode);
        -  }
        -  return this;
        -};
        -
        -/**
        - * Centers the element either vertically, horizontally, or both.
        - *
        - * `center()` will center the element relative to its parent or according to
        - * the page's body if the element has no parent.
        - *
        - * If no argument is passed, as in `myElement.center()` the element is aligned
        - * both vertically and horizontally.
        - *
        - * @method center
        - * @param  {String} [align] passing 'vertical', 'horizontal' aligns element accordingly
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create the div element and style it.
        - *   let div = createDiv('');
        - *   div.size(10, 10);
        - *   div.style('background-color', 'orange');
        - *
        - *   // Center the div relative to the page's body.
        - *   div.center();
        - *
        - *   describe('A gray square and an orange rectangle. The rectangle is at the center of the page.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.center = function (align) {
        -  const style = this.elt.style.display;
        -  const hidden = this.elt.style.display === 'none';
        -  const parentHidden = this.parent().style.display === 'none';
        -  const pos = { x: this.elt.offsetLeft, y: this.elt.offsetTop };
        -
        -  if (hidden) this.show();
        -  if (parentHidden) this.parent().show();
        -  this.elt.style.display = 'block';
        -
        -  this.position(0, 0);
        -  const wOffset = Math.abs(this.parent().offsetWidth - this.elt.offsetWidth);
        -  const hOffset = Math.abs(this.parent().offsetHeight - this.elt.offsetHeight);
        -
        -  if (align === 'both' || align === undefined) {
        -    this.position(
        -      wOffset / 2 + this.parent().offsetLeft,
        -      hOffset / 2 + this.parent().offsetTop
        -    );
        -  } else if (align === 'horizontal') {
        -    this.position(wOffset / 2 + this.parent().offsetLeft, pos.y);
        -  } else if (align === 'vertical') {
        -    this.position(pos.x, hOffset / 2 + this.parent().offsetTop);
        -  }
        -
        -  this.style('display', style);
        -  if (hidden) this.hide();
        -  if (parentHidden) this.parent().hide();
        -
        -  return this;
        -};
        -
        -/**
        - * Sets the inner HTML of the element, replacing any existing HTML.
        - *
        - * The second parameter, `append`, is optional. If `true` is passed, as in
        - * `myElement.html('hi', true)`, the HTML is appended instead of replacing
        - * existing HTML.
        - *
        - * If no arguments are passed, as in `myElement.html()`, the element's inner
        - * HTML is returned.
        - *
        - * @for p5.Element
        - * @method html
        - * @returns {String} the inner HTML of the element
        - *
        - * @example
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create the div element and set its size.
        - *   let div = createDiv('');
        - *   div.size(100, 100);
        - *
        - *   // Set the inner HTML to "hi".
        - *   div.html('hi');
        - *
        - *   describe('A gray square with the word "hi" written beneath it.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create the div element and set its size.
        - *   let div = createDiv('Hello ');
        - *   div.size(100, 100);
        - *
        - *   // Append "World" to the div's HTML.
        - *   div.html('World', true);
        - *
        - *   describe('A gray square with the text "Hello World" written beneath it.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create the div element.
        - *   let div = createDiv('Hello');
        - *
        - *   // Prints "Hello" to the console.
        - *   print(div.html());
        - *
        - *   describe('A gray square with the word "Hello!" written beneath it.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method html
        - * @param  {String} [html] the HTML to be placed inside the element
        - * @param  {boolean} [append] whether to append HTML to existing
        - * @chainable
        - */
        -p5.Element.prototype.html = function(...args) {
        -  if (args.length === 0) {
        -    return this.elt.innerHTML;
        -  } else if (args[1]) {
        -    this.elt.insertAdjacentHTML('beforeend', args[0]);
        -    return this;
        -  } else {
        -    this.elt.innerHTML = args[0];
        -    return this;
        -  }
        -};
        -
        -/**
        - * Sets the element's position.
        - *
        - * The first two parameters, `x` and `y`, set the element's position relative
        - * to the top-left corner of the web page.
        - *
        - * The third parameter, `positionType`, is optional. It sets the element's
        - * <a target="_blank"
        - * href="https://developer.mozilla.org/en-US/docs/Web/CSS/position">positioning scheme</a>.
        - * `positionType` is a string that can be either `'static'`, `'fixed'`,
        - * `'relative'`, `'sticky'`, `'initial'`, or `'inherit'`.
        - *
        - * If no arguments passed, as in `myElement.position()`, the method returns
        - * the element's position in an object, as in `{ x: 0, y: 0 }`.
        - *
        - * @method position
        - * @returns {Object} object of form `{ x: 0, y: 0 }` containing the element's position.
        - *
        - * @example
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   let cnv = createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Positions the canvas 50px to the right and 100px
        - *   // below the top-left corner of the window.
        - *   cnv.position(50, 100);
        - *
        - *   describe('A gray square that is 50 pixels to the right and 100 pixels down from the top-left corner of the web page.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   let cnv = createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Positions the canvas at the top-left corner
        - *   // of the window with a 'fixed' position type.
        - *   cnv.position(0, 0, 'fixed');
        - *
        - *   describe('A gray square in the top-left corner of the web page.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method position
        - * @param  {Number} [x] x-position relative to top-left of window (optional)
        - * @param  {Number} [y] y-position relative to top-left of window (optional)
        - * @param  {String} [positionType] it can be static, fixed, relative, sticky, initial or inherit (optional)
        - * @chainable
        - */
        -p5.Element.prototype.position = function(...args) {
        -  if (args.length === 0) {
        -    return { x: this.elt.offsetLeft, y: this.elt.offsetTop };
        -  } else {
        -    let positionType = 'absolute';
        -    if (
        -      args[2] === 'static' ||
        -      args[2] === 'fixed' ||
        -      args[2] === 'relative' ||
        -      args[2] === 'sticky' ||
        -      args[2] === 'initial' ||
        -      args[2] === 'inherit'
        -    ) {
        -      positionType = args[2];
        -    }
        -    this.elt.style.position = positionType;
        -    this.elt.style.left = args[0] + 'px';
        -    this.elt.style.top = args[1] + 'px';
        -    this.x = args[0];
        -    this.y = args[1];
        -    return this;
        -  }
        -};
        -
        -/* Helper method called by p5.Element.style() */
        -p5.Element.prototype._translate = function(...args) {
        -  this.elt.style.position = 'absolute';
        -  // save out initial non-translate transform styling
        -  let transform = '';
        -  if (this.elt.style.transform) {
        -    transform = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
        -    transform = transform.replace(/translate[X-Z]?\(.*\)/g, '');
        -  }
        -  if (args.length === 2) {
        -    this.elt.style.transform =
        -      'translate(' + args[0] + 'px, ' + args[1] + 'px)';
        -  } else if (args.length > 2) {
        -    this.elt.style.transform =
        -      'translate3d(' +
        -      args[0] +
        -      'px,' +
        -      args[1] +
        -      'px,' +
        -      args[2] +
        -      'px)';
        -    if (args.length === 3) {
        -      this.elt.parentElement.style.perspective = '1000px';
        -    } else {
        -      this.elt.parentElement.style.perspective = args[3] + 'px';
        -    }
        -  }
        -  // add any extra transform styling back on end
        -  this.elt.style.transform += transform;
        -  return this;
        -};
        -
        -/* Helper method called by p5.Element.style() */
        -p5.Element.prototype._rotate = function(...args) {
        -  // save out initial non-rotate transform styling
        -  let transform = '';
        -  if (this.elt.style.transform) {
        -    transform = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
        -    transform = transform.replace(/rotate[X-Z]?\(.*\)/g, '');
        -  }
        -
        -  if (args.length === 1) {
        -    this.elt.style.transform = 'rotate(' + args[0] + 'deg)';
        -  } else if (args.length === 2) {
        -    this.elt.style.transform =
        -      'rotate(' + args[0] + 'deg, ' + args[1] + 'deg)';
        -  } else if (args.length === 3) {
        -    this.elt.style.transform = 'rotateX(' + args[0] + 'deg)';
        -    this.elt.style.transform += 'rotateY(' + args[1] + 'deg)';
        -    this.elt.style.transform += 'rotateZ(' + args[2] + 'deg)';
        -  }
        -  // add remaining transform back on
        -  this.elt.style.transform += transform;
        -  return this;
        -};
        -
        -/**
        - * Applies a style to the element by adding a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax" target="_blank">CSS declaration</a>.
        - *
        - * The first parameter, `property`, is a string. If the name of a style
        - * property is passed, as in `myElement.style('color')`, the method returns
        - * the current value as a string or `null` if it hasn't been set. If a
        - * `property:style` string is passed, as in
        - * `myElement.style('color:deeppink')`, the method sets the style `property`
        - * to `value`.
        - *
        - * The second parameter, `value`, is optional. It sets the property's value.
        - * `value` can be a string, as in
        - * `myElement.style('color', 'deeppink')`, or a
        - * <a href="#/p5.Color">p5.Color</a> object, as in
        - * `myElement.style('color', myColor)`.
        - *
        - * @method style
        - * @param  {String} property style property to set.
        - * @returns {String} value of the property.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a paragraph element and set its font color to "deeppink".
        - *   let p = createP('p5*js');
        - *   p.position(25, 20);
        - *   p.style('color', 'deeppink');
        - *
        - *   describe('The text p5*js written in pink on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object.
        - *   let c = color('deeppink');
        - *
        - *   // Create a paragraph element and set its font color using a p5.Color object.
        - *   let p = createP('p5*js');
        - *   p.position(25, 20);
        - *   p.style('color', c);
        - *
        - *   describe('The text p5*js written in pink on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a paragraph element and set its font color to "deeppink"
        - *   // using property:value syntax.
        - *   let p = createP('p5*js');
        - *   p.position(25, 20);
        - *   p.style('color:deeppink');
        - *
        - *   describe('The text p5*js written in pink on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an empty paragraph element and set its font color to "deeppink".
        - *   let p = createP();
        - *   p.position(5, 5);
        - *   p.style('color', 'deeppink');
        - *
        - *   // Get the element's color as an  RGB color string.
        - *   let c = p.style('color');
        - *
        - *   // Set the element's inner HTML using the RGB color string.
        - *   p.html(c);
        - *
        - *   describe('The text "rgb(255, 20, 147)" written in pink on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method style
        - * @param  {String} property
        - * @param  {String|p5.Color} value value to assign to the property.
        - * @return {String} value of the property.
        - * @chainable
        - */
        -p5.Element.prototype.style = function (prop, val) {
        -  const self = this;
        -
        -  if (val instanceof p5.Color) {
        -    val =
        -      'rgba(' +
        -      val.levels[0] +
        -      ',' +
        -      val.levels[1] +
        -      ',' +
        -      val.levels[2] +
        -      ',' +
        -      val.levels[3] / 255 +
        -      ')';
        -  }
        -
        -  if (typeof val === 'undefined') {
        -    if (prop.indexOf(':') === -1) {
        -      // no value set, so assume requesting a value
        -      let styles = window.getComputedStyle(self.elt);
        -      let style = styles.getPropertyValue(prop);
        -      return style;
        -    } else {
        -      // value set using `:` in a single line string
        -      const attrs = prop.split(';');
        -      for (let i = 0; i < attrs.length; i++) {
        -        const parts = attrs[i].split(':');
        -        if (parts[0] && parts[1]) {
        -          this.elt.style[parts[0].trim()] = parts[1].trim();
        -        }
        -      }
        -    }
        -  } else {
        -    // input provided as key,val pair
        -    this.elt.style[prop] = val;
        -    if (
        -      prop === 'width' ||
        -      prop === 'height' ||
        -      prop === 'left' ||
        -      prop === 'top'
        -    ) {
        -      let styles = window.getComputedStyle(self.elt);
        -      let styleVal = styles.getPropertyValue(prop);
        -      let numVal = styleVal.replace(/[^\d.]/g, '');
        -      this[prop] = Math.round(parseFloat(numVal, 10));
        -    }
        -  }
        -  return this;
        -};
        -
        -/**
        - * Adds an
        - * <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started#attributes" target="_blank">attribute</a>
        - * to the element.
        - *
        - * This method is useful for advanced tasks. Most commonly-used attributes,
        - * such as `id`, can be set with their dedicated methods. For example,
        - * `nextButton.id('next')` sets an element's `id` attribute. Calling
        - * `nextButton.attribute('id', 'next')` has the same effect.
        - *
        - * The first parameter, `attr`, is the attribute's name as a string. Calling
        - * `myElement.attribute('align')` returns the attribute's current value as a
        - * string or `null` if it hasn't been set.
        - *
        - * The second parameter, `value`, is optional. It's a string used to set the
        - * attribute's value. For example, calling
        - * `myElement.attribute('align', 'center')` sets the element's horizontal
        - * alignment to `center`.
        - *
        - * @method attribute
        - * @return {String} value of the attribute.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a container div element and place it at the top-left corner.
        - *   let container = createDiv();
        - *   container.position(0, 0);
        - *
        - *   // Create a paragraph element and place it within the container.
        - *   // Set its horizontal alignment to "left".
        - *   let p1 = createP('hi');
        - *   p1.parent(container);
        - *   p1.attribute('align', 'left');
        - *
        - *   // Create a paragraph element and place it within the container.
        - *   // Set its horizontal alignment to "center".
        - *   let p2 = createP('hi');
        - *   p2.parent(container);
        - *   p2.attribute('align', 'center');
        - *
        - *   // Create a paragraph element and place it within the container.
        - *   // Set its horizontal alignment to "right".
        - *   let p3 = createP('hi');
        - *   p3.parent(container);
        - *   p3.attribute('align', 'right');
        - *
        - *   describe('A gray square with the text "hi" written on three separate lines, each placed further to the right.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method attribute
        - * @param  {String} attr       attribute to set.
        - * @param  {String} value      value to assign to the attribute.
        - * @chainable
        - */
        -p5.Element.prototype.attribute = function (attr, value) {
        -  //handling for checkboxes and radios to ensure options get
        -  //attributes not divs
        -  if (
        -    this.elt.firstChild != null &&
        -    (this.elt.firstChild.type === 'checkbox' ||
        -      this.elt.firstChild.type === 'radio')
        -  ) {
        -    if (typeof value === 'undefined') {
        -      return this.elt.firstChild.getAttribute(attr);
        +function dom(p5, fn){
        +  /**
        +   * Searches the page for the first element that matches the given
        +   * <a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/CSS_basics#different_types_of_selectors" target="_blank">CSS selector string</a>.
        +   *
        +   * The selector string can be an ID, class, tag name, or a combination.
        +   * `select()` returns a <a href="#/p5.Element">p5.Element</a> object if it
        +   * finds a match and `null` if not.
        +   *
        +   * The second parameter, `container`, is optional. It specifies a container to
        +   * search within. `container` can be CSS selector string, a
        +   * <a href="#/p5.Element">p5.Element</a> object, or an
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" target="_blank">HTMLElement</a> object.
        +   *
        +   * @method select
        +   * @param  {String} selectors CSS selector string of element to search for.
        +   * @param  {String|p5.Element|HTMLElement} [container] CSS selector string, <a href="#/p5.Element">p5.Element</a>, or
        +   *                                             <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" target="_blank">HTMLElement</a> to search within.
        +   * @return {p5.Element|null} <a href="#/p5.Element">p5.Element</a> containing the element.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *   background(200);
        +   *
        +   *   // Select the canvas by its tag.
        +   *   let cnv = select('canvas');
        +   *   cnv.style('border', '5px deeppink dashed');
        +   *
        +   *   describe('A gray square with a dashed pink border.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   // Add a class attribute to the canvas.
        +   *   cnv.class('pinkborder');
        +   *
        +   *   background(200);
        +   *
        +   *   // Select the canvas by its class.
        +   *   cnv = select('.pinkborder');
        +   *
        +   *   // Style its border.
        +   *   cnv.style('border', '5px deeppink dashed');
        +   *
        +   *   describe('A gray square with a dashed pink border.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   // Set the canvas' ID.
        +   *   cnv.id('mycanvas');
        +   *
        +   *   background(200);
        +   *
        +   *   // Select the canvas by its ID.
        +   *   cnv = select('#mycanvas');
        +   *
        +   *   // Style its border.
        +   *   cnv.style('border', '5px deeppink dashed');
        +   *
        +   *   describe('A gray square with a dashed pink border.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.select = function (e, p) {
        +    // p5._validateParameters('select', arguments);
        +    const container = this._getContainer(p);
        +    const res = container.querySelector(e);
        +    if (res) {
        +      return this._wrapElement(res);
             } else {
        -      for (let i = 0; i < this.elt.childNodes.length; i++) {
        -        this.elt.childNodes[i].setAttribute(attr, value);
        -      }
        -    }
        -  } else if (typeof value === 'undefined') {
        -    return this.elt.getAttribute(attr);
        -  } else {
        -    this.elt.setAttribute(attr, value);
        -    return this;
        -  }
        -};
        -
        -/**
        - * Removes an attribute from the element.
        - *
        - * The parameter `attr` is the attribute's name as a string. For example,
        - * calling `myElement.removeAttribute('align')` removes its `align`
        - * attribute if it's been set.
        - *
        - * @method removeAttribute
        - * @param  {String} attr       attribute to remove.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * let p;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a paragraph element and place it in the center of the canvas.
        - *   // Set its "align" attribute to "center".
        - *   p = createP('hi');
        - *   p.position(0, 20);
        - *   p.attribute('align', 'center');
        - *
        - *   describe('The text "hi" written in black at the center of a gray square. The text moves to the left edge when double-clicked.');
        - * }
        - *
        - * // Remove the 'align' attribute when the user double-clicks the paragraph.
        - * function doubleClicked() {
        - *   p.removeAttribute('align');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.removeAttribute = function (attr) {
        -  if (
        -    this.elt.firstChild != null &&
        -    (this.elt.firstChild.type === 'checkbox' ||
        -      this.elt.firstChild.type === 'radio')
        -  ) {
        -    for (let i = 0; i < this.elt.childNodes.length; i++) {
        -      this.elt.childNodes[i].removeAttribute(attr);
        +      return null;
             }
        -  }
        -  this.elt.removeAttribute(attr);
        -  return this;
        -};
        -
        -/**
        - * Returns or sets the element's value.
        - *
        - * Calling `myElement.value()` returns the element's current value.
        - *
        - * The parameter, `value`, is an optional number or string. If provided,
        - * as in `myElement.value(123)`, it's used to set the element's value.
        - *
        - * @method value
        - * @return {String|Number} value of the element.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let input;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a text input and place it beneath the canvas.
        - *   // Set its default value to "hello".
        - *   input = createInput('hello');
        - *   input.position(0, 100);
        - *
        - *   describe('The text from an input box is displayed on a gray square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Use the input's value to display a message.
        - *   let msg = input.value();
        - *   text(msg, 0, 55);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let input;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a text input and place it beneath the canvas.
        - *   // Set its default value to "hello".
        - *   input = createInput('hello');
        - *   input.position(0, 100);
        - *
        - *   describe('The text from an input box is displayed on a gray square. The text resets to "hello" when the user double-clicks the square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Use the input's value to display a message.
        - *   let msg = input.value();
        - *   text(msg, 0, 55);
        - * }
        - *
        - * // Reset the input's value.
        - * function doubleClicked() {
        - *   input.value('hello');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method value
        - * @param  {String|Number}     value
        - * @chainable
        - */
        -p5.Element.prototype.value = function(...args) {
        -  if (args.length > 0) {
        -    this.elt.value = args[0];
        -    return this;
        -  } else {
        -    if (this.elt.type === 'range') {
        -      return parseFloat(this.elt.value);
        -    } else return this.elt.value;
        -  }
        -};
        -
        -/**
        - * Shows the current element.
        - *
        - * @method show
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * let p;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a paragraph element and hide it.
        - *   p = createP('p5*js');
        - *   p.position(10, 10);
        - *   p.hide();
        - *
        - *   describe('A gray square. The text "p5*js" appears when the user double-clicks the square.');
        - * }
        - *
        - * // Show the paragraph when the user double-clicks.
        - * function doubleClicked() {
        - *   p.show();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.show = function () {
        -  this.elt.style.display = 'block';
        -  return this;
        -};
        -
        -/**
        - * Hides the current element.
        - *
        - * @method hide
        - * @chainable
        - *
        - * @example
        - * let p;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a paragraph element.
        - *   p = createP('p5*js');
        - *   p.position(10, 10);
        - *
        - *   describe('The text "p5*js" at the center of a gray square. The text disappears when the user double-clicks the square.');
        - * }
        - *
        - * // Hide the paragraph when the user double-clicks.
        - * function doubleClicked() {
        - *   p.hide();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.hide = function () {
        -  this.elt.style.display = 'none';
        -  return this;
        -};
        +  };
         
        -/**
        - * Sets the element's width and height.
        - *
        - * Calling `myElement.size()` without an argument returns the element's size
        - * as an object with the properties `width` and `height`. For example,
        - *  `{ width: 20, height: 10 }`.
        - *
        - * The first parameter, `width`, is optional. It's a number used to set the
        - * element's width. Calling `myElement.size(10)`
        - *
        - * The second parameter, 'height`, is also optional. It's a
        - * number used to set the element's height. For example, calling
        - * `myElement.size(20, 10)` sets the element's width to 20 pixels and height
        - * to 10 pixels.
        - *
        - * The constant `AUTO` can be used to adjust one dimension at a time while
        - * maintaining the aspect ratio, which is `width / height`. For example,
        - * consider an element that's 200 pixels wide and 100 pixels tall. Calling
        - * `myElement.size(20, AUTO)` sets the width to 20 pixels and height to 10
        - * pixels.
        - *
        - * Note: In the case of elements that need to load data, such as images, wait
        - * to call `myElement.size()` until after the data loads.
        - *
        - * @method size
        - * @return {Object} width and height of the element in an object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a pink div element and place it at the top-left corner.
        - *   let div = createDiv();
        - *   div.position(10, 10);
        - *   div.style('background-color', 'deeppink');
        - *
        - *   // Set the div's width to 80 pixels and height to 20 pixels.
        - *   div.size(80, 20);
        - *
        - *   describe('A gray square with a pink rectangle near its top.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a pink div element and place it at the top-left corner.
        - *   let div = createDiv();
        - *   div.position(10, 10);
        - *   div.style('background-color', 'deeppink');
        - *
        - *   // Set the div's width to 80 pixels and height to 40 pixels.
        - *   div.size(80, 40);
        - *
        - *   // Get the div's size as an object.
        - *   let s = div.size();
        - *
        - *   // Display the div's dimensions.
        - *   div.html(`${s.width} x ${s.height}`);
        - *
        - *   describe('A gray square with a pink rectangle near its top. The text "80 x 40" is written within the rectangle.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img1;
        - * let img2;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Load an image of an astronaut on the moon
        - *   // and place it at the top-left of the canvas.
        - *   img1 = createImg(
        - *     'assets/moonwalk.jpg',
        - *     'An astronaut walking on the moon',
        - *     ''
        - *   );
        - *   img1.position(0, 0);
        - *
        - *   // Load an image of an astronaut on the moon
        - *   // and place it at the top-left of the canvas.
        - *   // Resize the image once it's loaded.
        - *   img2 = createImg(
        - *     'assets/moonwalk.jpg',
        - *     'An astronaut walking on the moon',
        - *     '',
        - *     resizeImage
        - *   );
        - *   img2.position(0, 0);
        - *
        - *   describe('A gray square two copies of a space image at the top-left. The copy in front is smaller.');
        - * }
        - *
        - * // Resize img2 and keep its aspect ratio.
        - * function resizeImage() {
        - *   img2.size(50, AUTO);
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method size
        - * @param  {Number|Constant} [w]   width of the element, either AUTO, or a number.
        - * @param  {Number|Constant} [h] height of the element, either AUTO, or a number.
        - * @chainable
        - */
        -p5.Element.prototype.size = function (w, h) {
        -  if (arguments.length === 0) {
        -    return { width: this.elt.offsetWidth, height: this.elt.offsetHeight };
        -  } else {
        -    let aW = w;
        -    let aH = h;
        -    const AUTO = p5.prototype.AUTO;
        -    if (aW !== AUTO || aH !== AUTO) {
        -      if (aW === AUTO) {
        -        aW = h * this.width / this.height;
        -      } else if (aH === AUTO) {
        -        aH = w * this.height / this.width;
        -      }
        -      // set diff for cnv vs normal div
        -      if (this.elt instanceof HTMLCanvasElement) {
        -        const j = {};
        -        const k = this.elt.getContext('2d');
        -        let prop;
        -        for (prop in k) {
        -          j[prop] = k[prop];
        -        }
        -        this.elt.setAttribute('width', aW * this._pInst._pixelDensity);
        -        this.elt.setAttribute('height', aH * this._pInst._pixelDensity);
        -        this.elt.style.width = aW + 'px';
        -        this.elt.style.height = aH + 'px';
        -        this._pInst.scale(this._pInst._pixelDensity, this._pInst._pixelDensity);
        -        for (prop in j) {
        -          this.elt.getContext('2d')[prop] = j[prop];
        -        }
        -      } else {
        -        this.elt.style.width = aW + 'px';
        -        this.elt.style.height = aH + 'px';
        -        this.elt.width = aW;
        -        this.elt.height = aH;
        -      }
        -      this.width = aW;
        -      this.height = aH;
        -      if (this._pInst && this._pInst._curElement) {
        -        // main canvas associated with p5 instance
        -        if (this._pInst._curElement.elt === this.elt) {
        -          this._pInst._setProperty('width', aW);
        -          this._pInst._setProperty('height', aH);
        -        }
        +  /**
        +   * Searches the page for all elements that matches the given
        +   * <a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/CSS_basics#different_types_of_selectors" target="_blank">CSS selector string</a>.
        +   *
        +   * The selector string can be an ID, class, tag name, or a combination.
        +   * `selectAll()` returns an array of <a href="#/p5.Element">p5.Element</a>
        +   * objects if it finds any matches and an empty array if none are found.
        +   *
        +   * The second parameter, `container`, is optional. It specifies a container to
        +   * search within. `container` can be CSS selector string, a
        +   * <a href="#/p5.Element">p5.Element</a> object, or an
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" target="_blank">HTMLElement</a> object.
        +   *
        +   * @method selectAll
        +   * @param  {String} selectors CSS selector string of element to search for.
        +   * @param  {String|p5.Element|HTMLElement} [container] CSS selector string, <a href="#/p5.Element">p5.Element</a>, or
        +   *                                             <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" target="_blank">HTMLElement</a> to search within.
        +   * @return {p5.Element[]} array of <a href="#/p5.Element">p5.Element</a>s containing any elements found.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create three buttons.
        +   *   createButton('1');
        +   *   createButton('2');
        +   *   createButton('3');
        +   *
        +   *   // Select the buttons by their tag.
        +   *   let buttons = selectAll('button');
        +   *
        +   *   // Position the buttons.
        +   *   for (let i = 0; i < 3; i += 1) {
        +   *     buttons[i].position(0, i * 30);
        +   *   }
        +   *
        +   *   describe('Three buttons stacked vertically. The buttons are labeled, "1", "2", and "3".');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create three buttons and position them.
        +   *   let b1 = createButton('1');
        +   *   b1.position(0, 0);
        +   *   let b2 = createButton('2');
        +   *   b2.position(0, 30);
        +   *   let b3 = createButton('3');
        +   *   b3.position(0, 60);
        +   *
        +   *   // Add a class attribute to each button.
        +   *   b1.class('btn');
        +   *   b2.class('btn btn-pink');
        +   *   b3.class('btn');
        +   *
        +   *   // Select the buttons by their class.
        +   *   let buttons = selectAll('.btn');
        +   *   let pinkButtons = selectAll('.btn-pink');
        +   *
        +   *   // Style the selected buttons.
        +   *   buttons.forEach(setFont);
        +   *   pinkButtons.forEach(setColor);
        +   *
        +   *   describe('Three buttons stacked vertically. The buttons are labeled, "1", "2", and "3". Buttons "1" and "3" are gray. Button "2" is pink.');
        +   * }
        +   *
        +   * // Set a button's font to Comic Sans MS.
        +   * function setFont(btn) {
        +   *   btn.style('font-family', 'Comic Sans MS');
        +   * }
        +   *
        +   * // Set a button's background and font color.
        +   * function setColor(btn) {
        +   *   btn.style('background', 'deeppink');
        +   *   btn.style('color', 'white');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.selectAll = function (e, p) {
        +    // p5._validateParameters('selectAll', arguments);
        +    const arr = [];
        +    const container = this._getContainer(p);
        +    const res = container.querySelectorAll(e);
        +    if (res) {
        +      for (let j = 0; j < res.length; j++) {
        +        const obj = this._wrapElement(res[j]);
        +        arr.push(obj);
               }
             }
        -    return this;
        -  }
        -};
        +    return arr;
        +  };
         
        -/**
        - * Removes the element, stops all audio/video streams, and removes all
        - * callback functions.
        - *
        - * @method remove
        - *
        - * @example
        - * <div>
        - * <code>
        - * let p;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a paragraph element.
        - *   p = createP('p5*js');
        - *   p.position(10, 10);
        - *
        - *   describe('The text "p5*js" written at the center of a gray square. ');
        - * }
        - *
        - * // Remove the paragraph when the user double-clicks.
        - * function doubleClicked() {
        - *   p.remove();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.remove = function () {
        -  // stop all audios/videos and detach all devices like microphone/camera etc
        -  // used as input/output for audios/videos.
        -  if (this instanceof p5.MediaElement) {
        -    this.stop();
        -    const sources = this.elt.srcObject;
        -    if (sources !== null) {
        -      const tracks = sources.getTracks();
        -      tracks.forEach(track => {
        -        track.stop();
        -      });
        +  /**
        +   * Helper function for select and selectAll
        +   */
        +  fn._getContainer = function (p) {
        +    let container = document;
        +    if (typeof p === 'string') {
        +      container = document.querySelector(p) || document;
        +    } else if (p instanceof Element) {
        +      container = p.elt;
        +    } else if (p instanceof HTMLElement) {
        +      container = p;
             }
        -  }
        -
        -  // delete the reference in this._pInst._elements
        -  const index = this._pInst._elements.indexOf(this);
        -  if (index !== -1) {
        -    this._pInst._elements.splice(index, 1);
        -  }
        -
        -  // deregister events
        -  for (let ev in this._events) {
        -    this.elt.removeEventListener(ev, this._events[ev]);
        -  }
        -  if (this.elt && this.elt.parentNode) {
        -    this.elt.parentNode.removeChild(this.elt);
        -  }
        -};
        -
        -/**
        - * Calls a function when the user drops a file on the element.
        - *
        - * The first parameter, `callback`, is a function to call once the file loads.
        - * The callback function should have one parameter, `file`, that's a
        - * <a href="#/p5.File">p5.File</a> object. If the user drops multiple files on
        - * the element, `callback`, is called once for each file.
        - *
        - * The second parameter, `fxn`, is a function to call when the browser detects
        - * one or more dropped files. The callback function should have one
        - * parameter, `event`, that's a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent">DragEvent</a>.
        - *
        - * @method drop
        - * @param  {Function} callback  called when a file loads. Called once for each file dropped.
        - * @param  {Function} [fxn]     called once when any files are dropped.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Drop an image on the canvas to view
        - * // this example.
        - * let img;
        - *
        - * function setup() {
        - *   let c = createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Call handleFile() when a file that's dropped on the canvas has loaded.
        - *   c.drop(handleFile);
        - *
        - *   describe('A gray square. When the user drops an image on the square, it is displayed.');
        - * }
        - *
        - * // Remove the existing image and display the new one.
        - * function handleFile(file) {
        - *   // Remove the current image, if any.
        - *   if (img) {
        - *     img.remove();
        - *   }
        - *
        - *   // Create an <img> element with the
        - *   // dropped file.
        - *   img = createImg(file.data, '');
        - *   img.hide();
        - *
        - *   // Draw the image.
        - *   image(img, 0, 0, width, height);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Drop an image on the canvas to view
        - * // this example.
        - * let img;
        - * let msg;
        - *
        - * function setup() {
        - *   let c = createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Call functions when the user drops a file on the canvas
        - *   // and when the file loads.
        - *   c.drop(handleFile, handleDrop);
        - *
        - *   describe('A gray square. When the user drops an image on the square, it is displayed. The id attribute of canvas element is also displayed.');
        - * }
        - *
        - * // Display the image when it loads.
        - * function handleFile(file) {
        - *   // Remove the current image, if any.
        - *   if (img) {
        - *     img.remove();
        - *   }
        - *
        - *   // Create an img element with the dropped file.
        - *   img = createImg(file.data, '');
        - *   img.hide();
        - *
        - *   // Draw the image.
        - *   image(img, 0, 0, width, height);
        - * }
        - *
        - * // Display the file's name when it loads.
        - * function handleDrop(event) {
        - *   // Remove current paragraph, if any.
        - *   if (msg) {
        - *     msg.remove();
        - *   }
        - *
        - *   // Use event to get the drop target's id.
        - *   let id = event.target.id;
        - *
        - *   // Write the canvas' id beneath it.
        - *   msg = createP(id);
        - *   msg.position(0, 100);
        - *
        - *   // Set the font color randomly for each drop.
        - *   let c = random(['red', 'green', 'blue']);
        - *   msg.style('color', c);
        - *   msg.style('font-size', '12px');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.drop = function (callback, fxn) {
        -  // Is the file stuff supported?
        -  if (window.File && window.FileReader && window.FileList && window.Blob) {
        -    if (!this._dragDisabled) {
        -      this._dragDisabled = true;
        +    return container;
        +  };
         
        -      const preventDefault = function (evt) {
        -        evt.preventDefault();
        +  /**
        +   * Helper function for getElement and getElements.
        +   */
        +  fn._wrapElement = function (elt) {
        +    const children = Array.prototype.slice.call(elt.children);
        +    if (elt.tagName === 'INPUT' && elt.type === 'checkbox') {
        +      let converted = new Element(elt, this);
        +      converted.checked = function (...args) {
        +        if (args.length === 0) {
        +          return this.elt.checked;
        +        } else if (args[0]) {
        +          this.elt.checked = true;
        +        } else {
        +          this.elt.checked = false;
        +        }
        +        return this;
               };
        -
        -      // If you want to be able to drop you've got to turn off
        -      // a lot of default behavior.
        -      // avoid `attachListener` here, since it overrides other handlers.
        -      this.elt.addEventListener('dragover', preventDefault);
        -
        -      // If this is a drag area we need to turn off the default behavior
        -      this.elt.addEventListener('dragleave', preventDefault);
        +      return converted;
        +    } else if (elt.tagName === 'VIDEO' || elt.tagName === 'AUDIO') {
        +      return new MediaElement(elt, this);
        +    } else if (elt.tagName === 'SELECT') {
        +      return this.createSelect(new Element(elt, this));
        +    } else if (
        +      children.length > 0 &&
        +      children.every(function (c) {
        +        return c.tagName === 'INPUT' || c.tagName === 'LABEL';
        +      }) &&
        +      (elt.tagName === 'DIV' || elt.tagName === 'SPAN')
        +    ) {
        +      return this.createRadio(new Element(elt, this));
        +    } else {
        +      return new Element(elt, this);
             }
        +  };
         
        -    // Deal with the files
        -    p5.Element._attachListener(
        -      'drop',
        -      function (evt) {
        -        evt.preventDefault();
        -        // Call the second argument as a callback that receives the raw drop event
        -        if (typeof fxn === 'function') {
        -          fxn.call(this, evt);
        -        }
        -        // A FileList
        -        const files = evt.dataTransfer.files;
        -
        -        // Load each one and trigger the callback
        -        for (const f of files) {
        -          p5.File._load(f, callback);
        -        }
        -      },
        -      this
        -    );
        -  } else {
        -    console.log('The File APIs are not fully supported in this browser.');
        -  }
        -
        -  return this;
        -};
        -
        -/**
        - * Makes the element draggable.
        - *
        - * The parameter, `elmnt`, is optional. If another
        - * <a href="#/p5.Element">p5.Element</a> object is passed, as in
        - * `myElement.draggable(otherElement)`, the other element will become draggable.
        - *
        - * @method draggable
        - * @param  {p5.Element} [elmnt]  another <a href="#/p5.Element">p5.Element</a>.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * let stickyNote;
        - * let textInput;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a div element and style it.
        - *   stickyNote = createDiv('Note');
        - *   stickyNote.position(5, 5);
        - *   stickyNote.size(80, 20);
        - *   stickyNote.style('font-size', '16px');
        - *   stickyNote.style('font-family', 'Comic Sans MS');
        - *   stickyNote.style('background', 'orchid');
        - *   stickyNote.style('padding', '5px');
        - *
        - *   // Make the note draggable.
        - *   stickyNote.draggable();
        - *
        - *   // Create a panel div and style it.
        - *   let panel = createDiv('');
        - *   panel.position(5, 40);
        - *   panel.size(80, 50);
        - *   panel.style('background', 'orchid');
        - *   panel.style('font-size', '16px');
        - *   panel.style('padding', '5px');
        - *   panel.style('text-align', 'center');
        - *
        - *   // Make the panel draggable.
        - *   panel.draggable();
        - *
        - *   // Create a text input and style it.
        - *   textInput = createInput('Note');
        - *   textInput.size(70);
        - *
        - *   // Add the input to the panel.
        - *   textInput.parent(panel);
        - *
        - *   // Call handleInput() when text is input.
        - *   textInput.input(handleInput);
        - *
        - *   describe(
        - *     'A gray square with two purple rectangles that move when dragged. The top rectangle displays the text that is typed into the bottom rectangle.'
        - *   );
        - * }
        - *
        - * // Update stickyNote's HTML when text is input.
        - * function handleInput() {
        - *   stickyNote.html(textInput.value());
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Element.prototype.draggable = function (elmMove) {
        -  let isTouch = 'ontouchstart' in window;
        +  /**
        +   * Creates a new <a href="#/p5.Element">p5.Element</a> object.
        +   *
        +   * The first parameter, `tag`, is a string an HTML tag such as `'h5'`.
        +   *
        +   * The second parameter, `content`, is optional. It's a string that sets the
        +   * HTML content to insert into the new element. New elements have no content
        +   * by default.
        +   *
        +   * @method createElement
        +   * @param  {String} tag tag for the new element.
        +   * @param  {String} [content] HTML content to insert into the element.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an h5 element with nothing in it.
        +   *   createElement('h5');
        +   *
        +   *   describe('A gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an h5 element with the content "p5*js".
        +   *   let h5 = createElement('h5', 'p5*js');
        +   *
        +   *   // Set the element's style and position.
        +   *   h5.style('color', 'deeppink');
        +   *   h5.position(30, 15);
        +   *
        +   *   describe('The text "p5*js" written in pink in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createElement = function (tag, content) {
        +    // p5._validateParameters('createElement', arguments);
        +    const elt = document.createElement(tag);
        +    if (typeof content !== 'undefined') {
        +      elt.innerHTML = content;
        +    }
        +    return addElement(elt, this);
        +  };
         
        -  let x = 0,
        -    y = 0,
        -    px = 0,
        -    py = 0,
        -    elmDrag,
        -    dragMouseDownEvt = isTouch ? 'touchstart' : 'mousedown',
        -    closeDragElementEvt = isTouch ? 'touchend' : 'mouseup',
        -    elementDragEvt = isTouch ? 'touchmove' : 'mousemove';
        +  /**
        +   * Removes all elements created by p5.js, including any event handlers.
        +   *
        +   * There are two exceptions:
        +   * canvas elements created by <a href="#/p5/createCanvas">createCanvas()</a>
        +   * and <a href="#/p5.Renderer">p5.Render</a> objects created by
        +   * <a href="#/p5/createGraphics">createGraphics()</a>.
        +   *
        +   * @method removeElements
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a paragraph element and place
        +   *   // it in the middle of the canvas.
        +   *   let p = createP('p5*js');
        +   *   p.position(25, 25);
        +   *
        +   *   describe('A gray square with the text "p5*js" written in its center. The text disappears when the mouse is pressed.');
        +   * }
        +   *
        +   * // Remove all elements when the mouse is pressed.
        +   * function mousePressed() {
        +   *   removeElements();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let slider;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a paragraph element and place
        +   *   // it at the top of the canvas.
        +   *   let p = createP('p5*js');
        +   *   p.position(25, 25);
        +   *
        +   *   // Create a slider element and place it
        +   *   // beneath the canvas.
        +   *   slider = createSlider(0, 255, 200);
        +   *   slider.position(0, 100);
        +   *
        +   *   describe('A gray square with the text "p5*js" written in its center and a range slider beneath it. The square changes color when the slider is moved. The text and slider disappear when the square is double-clicked.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Use the slider value to change the background color.
        +   *   let g = slider.value();
        +   *   background(g);
        +   * }
        +   *
        +   * // Remove all elements when the mouse is double-clicked.
        +   * function doubleClicked() {
        +   *   removeElements();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.removeElements = function (e) {
        +    // p5._validateParameters('removeElements', arguments);
        +    // el.remove splices from this._elements, so don't mix iteration with it
        +    const isNotCanvasElement = el => !(el.elt instanceof HTMLCanvasElement);
        +    const removeableElements = this._elements.filter(isNotCanvasElement);
        +    removeableElements.map(el => el.remove());
        +  };
         
        -  if(elmMove === undefined){
        -    elmMove = this.elt;
        -    elmDrag = elmMove;
        -  }else if(elmMove !== this.elt && elmMove.elt !== this.elt){
        -    elmMove = elmMove.elt;
        -    elmDrag = this.elt;
        +  /**
        +   * Helpers for create methods.
        +   */
        +  function addElement(elt, pInst, media) {
        +    const node = pInst._userNode ? pInst._userNode : document.body;
        +    node.appendChild(elt);
        +    const c = media
        +      ? new MediaElement(elt, pInst)
        +      : new Element(elt, pInst);
        +    pInst._elements.push(c);
        +    return c;
           }
         
        -  elmDrag.addEventListener(dragMouseDownEvt, dragMouseDown, false);
        -  elmDrag.style.cursor = 'move';
        -
        -  function dragMouseDown(e) {
        -    e = e || window.event;
        -
        -    if(isTouch){
        -      const touches = e.changedTouches;
        -      px = parseInt(touches[0].clientX);
        -      py = parseInt(touches[0].clientY);
        -    }else{
        -      px = parseInt(e.clientX);
        -      py = parseInt(e.clientY);
        -    }
        +  /**
        +   * Creates a `&lt;div&gt;&lt;/div&gt;` element.
        +   *
        +   * `&lt;div&gt;&lt;/div&gt;` elements are commonly used as containers for
        +   * other elements.
        +   *
        +   * The parameter `html` is optional. It accepts a string that sets the
        +   * inner HTML of the new `&lt;div&gt;&lt;/div&gt;`.
        +   *
        +   * @method createDiv
        +   * @param  {String} [html] inner HTML for the new `&lt;div&gt;&lt;/div&gt;` element.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a div element and set its position.
        +   *   let div = createDiv('p5*js');
        +   *   div.position(25, 35);
        +   *
        +   *   describe('A gray square with the text "p5*js" written in its center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an h3 element within the div.
        +   *   let div = createDiv('<h3>p5*js</h3>');
        +   *   div.position(20, 5);
        +   *
        +   *   describe('A gray square with the text "p5*js" written in its center.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createDiv = function (html = '') {
        +    let elt = document.createElement('div');
        +    elt.innerHTML = html;
        +    return addElement(elt, this);
        +  };
         
        -    document.addEventListener(closeDragElementEvt, closeDragElement, false);
        -    document.addEventListener(elementDragEvt, elementDrag, false);
        -    return false;
        -  }
        +  /**
        +   * Creates a `&lt;p&gt;&lt;/p&gt;` element.
        +   *
        +   * `&lt;p&gt;&lt;/p&gt;` elements are commonly used for paragraph-length text.
        +   *
        +   * The parameter `html` is optional. It accepts a string that sets the
        +   * inner HTML of the new `&lt;p&gt;&lt;/p&gt;`.
        +   *
        +   * @method createP
        +   * @param  {String} [html] inner HTML for the new `&lt;p&gt;&lt;/p&gt;` element.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a paragraph element and set its position.
        +   *   let p = createP('Tell me a story.');
        +   *   p.position(5, 0);
        +   *
        +   *   describe('A gray square displaying the text "Tell me a story." written in black.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createP = function (html = '') {
        +    let elt = document.createElement('p');
        +    elt.innerHTML = html;
        +    return addElement(elt, this);
        +  };
         
        -  function elementDrag(e) {
        -    e = e || window.event;
        +  /**
        +   * Creates a `&lt;span&gt;&lt;/span&gt;` element.
        +   *
        +   * `&lt;span&gt;&lt;/span&gt;` elements are commonly used as containers
        +   * for inline elements. For example, a `&lt;span&gt;&lt;/span&gt;`
        +   * can hold part of a sentence that's a
        +   * <span style="color: deeppink;">different</span> style.
        +   *
        +   * The parameter `html` is optional. It accepts a string that sets the
        +   * inner HTML of the new `&lt;span&gt;&lt;/span&gt;`.
        +   *
        +   * @method createSpan
        +   * @param  {String} [html] inner HTML for the new `&lt;span&gt;&lt;/span&gt;` element.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a span element and set its position.
        +   *   let span = createSpan('p5*js');
        +   *   span.position(25, 35);
        +   *
        +   *   describe('A gray square with the text "p5*js" written in its center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background(200);
        +   *
        +   *   // Create a div element as a container.
        +   *   let div = createDiv();
        +   *
        +   *   // Place the div at the center.
        +   *   div.position(25, 35);
        +   *
        +   *   // Create a span element.
        +   *   let s1 = createSpan('p5');
        +   *
        +   *   // Create a second span element.
        +   *   let s2 = createSpan('*');
        +   *
        +   *   // Set the second span's font color.
        +   *   s2.style('color', 'deeppink');
        +   *
        +   *   // Create a third span element.
        +   *   let s3 = createSpan('js');
        +   *
        +   *   // Add all the spans to the container div.
        +   *   s1.parent(div);
        +   *   s2.parent(div);
        +   *   s3.parent(div);
        +   *
        +   *   describe('A gray square with the text "p5*js" written in black at its center. The asterisk is pink.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createSpan = function (html = '') {
        +    let elt = document.createElement('span');
        +    elt.innerHTML = html;
        +    return addElement(elt, this);
        +  };
         
        -    if(isTouch){
        -      const touches = e.changedTouches;
        -      x = px - parseInt(touches[0].clientX);
        -      y = py - parseInt(touches[0].clientY);
        -      px = parseInt(touches[0].clientX);
        -      py = parseInt(touches[0].clientY);
        -    }else{
        -      x = px - parseInt(e.clientX);
        -      y = py - parseInt(e.clientY);
        -      px = parseInt(e.clientX);
        -      py = parseInt(e.clientY);
        +  /**
        +   * Creates an `&lt;img&gt;` element that can appear outside of the canvas.
        +   *
        +   * The first parameter, `src`, is a string with the path to the image file.
        +   * `src` should be a relative path, as in `'assets/image.png'`, or a URL, as
        +   * in `'https://example.com/image.png'`.
        +   *
        +   * The second parameter, `alt`, is a string with the
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/alt#usage_notes" target="_blank">alternate text</a>
        +   * for the image. An empty string `''` can be used for images that aren't displayed.
        +   *
        +   * The third parameter, `crossOrigin`, is optional. It's a string that sets the
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes" target="_blank">crossOrigin property</a>
        +   * of the image. Use `'anonymous'` or `'use-credentials'` to fetch the image
        +   * with cross-origin access.
        +   *
        +   * The fourth parameter, `callback`, is also optional. It sets a function to
        +   * call after the image loads. The new image is passed to the callback
        +   * function as a <a href="#/p5.Element">p5.Element</a> object.
        +   *
        +   * @method createImg
        +   * @param  {String} src relative path or URL for the image.
        +   * @param  {String} alt alternate text for the image.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   let img = createImg(
        +   *     'https://p5js.org/assets/img/asterisk-01.png',
        +   *     'The p5.js magenta asterisk.'
        +   *   );
        +   *   img.position(0, -10);
        +   *
        +   *   describe('A gray square with a magenta asterisk in its center.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method createImg
        +   * @param  {String} src
        +   * @param  {String} alt
        +   * @param  {String} [crossOrigin] crossOrigin property to use when fetching the image.
        +   * @param  {Function} [successCallback] function to call once the image loads. The new image will be passed
        +   *                                      to the function as a <a href="#/p5.Element">p5.Element</a> object.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        +   */
        +  fn.createImg = function () {
        +    // p5._validateParameters('createImg', arguments);
        +    const elt = document.createElement('img');
        +    const args = arguments;
        +    let self;
        +    if (args.length > 1 && typeof args[1] === 'string') {
        +      elt.alt = args[1];
             }
        -
        -    elmMove.style.left = elmMove.offsetLeft - x + 'px';
        -    elmMove.style.top = elmMove.offsetTop - y + 'px';
        -  }
        -
        -  function closeDragElement() {
        -    document.removeEventListener(closeDragElementEvt, closeDragElement, false);
        -    document.removeEventListener(elementDragEvt, elementDrag, false);
        -  }
        -
        -  return this;
        -};
        -
        -/*** SCHEDULE EVENTS ***/
        -
        -// Cue inspired by JavaScript setTimeout, and the
        -// Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org
        -// eslint-disable-next-line no-unused-vars
        -class Cue {
        -  constructor(callback, time, id, val) {
        -    this.callback = callback;
        -    this.time = time;
        -    this.id = id;
        -    this.val = val;
        -  }
        -}
        -
        -// =============================================================================
        -//                         p5.MediaElement additions
        -// =============================================================================
        -
        -/**
        - * A class to handle audio and video.
        - *
        - * `p5.MediaElement` extends <a href="#/p5.Element">p5.Element</a> with
        - * methods to handle audio and video. `p5.MediaElement` objects are created by
        - * calling <a href="#/p5/createVideo">createVideo</a>,
        - * <a href="#/p5/createAudio">createAudio</a>, and
        - * <a href="#/p5/createCapture">createCapture</a>.
        - *
        - * @class p5.MediaElement
        - * @constructor
        - * @param {String} elt DOM node that is wrapped
        - * @extends p5.Element
        - *
        - * @example
        - * <div class='notest'>
        - * <code>
        - * let capture;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.MediaElement using createCapture().
        - *   capture = createCapture(VIDEO);
        - *   capture.hide();
        - *
        - *   describe('A webcam feed with inverted colors.');
        - * }
        - *
        - * function draw() {
        - *   // Display the video stream and invert the colors.
        - *   image(capture, 0, 0, width, width * capture.height / capture.width);
        - *   filter(INVERT);
        - * }
        - * </code>
        - * </div>
        - */
        -class MediaElement extends p5.Element {
        -  constructor(elt, pInst) {
        -    super(elt, pInst);
        -
        -    const self = this;
        -    this.elt.crossOrigin = 'anonymous';
        -
        -    this._prevTime = 0;
        -    this._cueIDCounter = 0;
        -    this._cues = [];
        -    this.pixels = [];
        -    this._pixelsState = this;
        -    this._pixelDensity = 1;
        -    this._modified = false;
        -
        -    // Media has an internal canvas that is used when drawing it to the main
        -    // canvas. It will need to be updated each frame as the video itself plays.
        -    // We don't want to update it every time we draw, however, in case the user
        -    // has used load/updatePixels. To handle this, we record the frame drawn to
        -    // the internal canvas so we only update it if the frame has changed.
        -    this._frameOnCanvas = -1;
        -
        -    /**
        -     * Path to the media element's source as a string.
        -     *
        -     * @property src
        -     * @return {String} src
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * let beat;
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   // Create a p5.MediaElement using createAudio().
        -     *   beat = createAudio('assets/beat.mp3');
        -     *
        -     *   describe('The text "https://p5js.org/reference/assets/beat.mp3" written in black on a gray background.');
        -     * }
        -     *
        -     * function draw() {
        -     *   background(200);
        -     *
        -     *   textWrap(CHAR);
        -     *   text(beat.src, 10, 10, 80, 80);
        -     * }
        -     * </code>
        -     * </div>
        -     */
        -    Object.defineProperty(self, 'src', {
        -      get() {
        -        const firstChildSrc = self.elt.children[0].src;
        -        const srcVal = self.elt.src === window.location.href ? '' : self.elt.src;
        -        const ret =
        -          firstChildSrc === window.location.href ? srcVal : firstChildSrc;
        -        return ret;
        -      },
        -      set(newValue) {
        -        for (let i = 0; i < self.elt.children.length; i++) {
        -          self.elt.removeChild(self.elt.children[i]);
        -        }
        -        const source = document.createElement('source');
        -        source.src = newValue;
        -        elt.appendChild(source);
        -        self.elt.src = newValue;
        -        self.modified = true;
        -      }
        +    if (args.length > 2 && typeof args[2] === 'string') {
        +      elt.crossOrigin = args[2];
        +    }
        +    elt.src = args[0];
        +    self = addElement(elt, this);
        +    elt.addEventListener('load', function () {
        +      self.width = elt.offsetWidth || elt.width;
        +      self.height = elt.offsetHeight || elt.height;
        +      const last = args[args.length - 1];
        +      if (typeof last === 'function') last(self);
             });
        +    return self;
        +  };
         
        -    // private _onended callback, set by the method: onended(callback)
        -    self._onended = function () { };
        -    self.elt.onended = function () {
        -      self._onended(self);
        -    };
        -  }
        +  /**
        +   * Creates an `&lt;a&gt;&lt;/a&gt;` element that links to another web page.
        +   *
        +   * The first parmeter, `href`, is a string that sets the URL of the linked
        +   * page.
        +   *
        +   * The second parameter, `html`, is a string that sets the inner HTML of the
        +   * link. It's common to use text, images, or buttons as links.
        +   *
        +   * The third parameter, `target`, is optional. It's a string that tells the
        +   * web browser where to open the link. By default, links open in the current
        +   * browser tab. Passing `'_blank'` will cause the link to open in a new
        +   * browser tab. MDN describes a few
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target" target="_blank">other options</a>.
        +   *
        +   * @method createA
        +   * @param  {String} href       URL of linked page.
        +   * @param  {String} html       inner HTML of link element to display.
        +   * @param  {String} [target]   target where the new link should open,
        +   *                             either `'_blank'`, `'_self'`, `'_parent'`, or `'_top'`.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an anchor element that links to p5js.org.
        +   *   let a = createA('http://p5js.org/', 'p5*js');
        +   *   a.position(25, 35);
        +   *
        +   *   describe('The text "p5*js" written at the center of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   background(200);
        +   *
        +   *   // Create an anchor tag that links to p5js.org.
        +   *   // Open the link in a new tab.
        +   *   let a = createA('http://p5js.org/', 'p5*js', '_blank');
        +   *   a.position(25, 35);
        +   *
        +   *   describe('The text "p5*js" written at the center of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createA = function (href, html, target) {
        +    // p5._validateParameters('createA', arguments);
        +    const elt = document.createElement('a');
        +    elt.href = href;
        +    elt.innerHTML = html;
        +    if (target) elt.target = target;
        +    return addElement(elt, this);
        +  };
         
        +  /* INPUT */
        +  /**
        +   * Creates a slider `&lt;input&gt;&lt;/input&gt;` element.
        +   *
        +   * Range sliders are useful for quickly selecting numbers from a given range.
        +   *
        +   * The first two parameters, `min` and `max`, are numbers that set the
        +   * slider's minimum and maximum.
        +   *
        +   * The third parameter, `value`, is optional. It's a number that sets the
        +   * slider's default value.
        +   *
        +   * The fourth parameter, `step`, is also optional. It's a number that sets the
        +   * spacing between each value in the slider's range. Setting `step` to 0
        +   * allows the slider to move smoothly from `min` to `max`.
        +   *
        +   * @method createSlider
        +   * @param  {Number} min minimum value of the slider.
        +   * @param  {Number} max maximum value of the slider.
        +   * @param  {Number} [value] default value of the slider.
        +   * @param  {Number} [step] size for each step in the slider's range.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let slider;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a slider and place it at the top of the canvas.
        +   *   slider = createSlider(0, 255);
        +   *   slider.position(10, 10);
        +   *   slider.size(80);
        +   *
        +   *   describe('A dark gray square with a range slider at the top. The square changes color when the slider is moved.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Use the slider as a grayscale value.
        +   *   let g = slider.value();
        +   *   background(g);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let slider;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a slider and place it at the top of the canvas.
        +   *   // Set its default value to 0.
        +   *   slider = createSlider(0, 255, 0);
        +   *   slider.position(10, 10);
        +   *   slider.size(80);
        +   *
        +   *   describe('A black square with a range slider at the top. The square changes color when the slider is moved.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Use the slider as a grayscale value.
        +   *   let g = slider.value();
        +   *   background(g);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let slider;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a slider and place it at the top of the canvas.
        +   *   // Set its default value to 0.
        +   *   // Set its step size to 50.
        +   *   slider = createSlider(0, 255, 0, 50);
        +   *   slider.position(10, 10);
        +   *   slider.size(80);
        +   *
        +   *   describe('A black square with a range slider at the top. The square changes color when the slider is moved.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Use the slider as a grayscale value.
        +   *   let g = slider.value();
        +   *   background(g);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let slider;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a slider and place it at the top of the canvas.
        +   *   // Set its default value to 0.
        +   *   // Set its step size to 0 so that it moves smoothly.
        +   *   slider = createSlider(0, 255, 0, 0);
        +   *   slider.position(10, 10);
        +   *   slider.size(80);
        +   *
        +   *   describe('A black square with a range slider at the top. The square changes color when the slider is moved.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Use the slider as a grayscale value.
        +   *   let g = slider.value();
        +   *   background(g);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createSlider = function (min, max, value, step) {
        +    // p5._validateParameters('createSlider', arguments);
        +    const elt = document.createElement('input');
        +    elt.type = 'range';
        +    elt.min = min;
        +    elt.max = max;
        +    if (step === 0) {
        +      elt.step = 0.000000000000000001; // smallest valid step
        +    } else if (step) {
        +      elt.step = step;
        +    }
        +    if (typeof value === 'number') elt.value = value;
        +    return addElement(elt, this);
        +  };
         
           /**
        -   * Plays audio or video from a media element.
        +   * Creates a `&lt;button&gt;&lt;/button&gt;` element.
            *
        -   * @method play
        -   * @chainable
        +   * The first parameter, `label`, is a string that sets the label displayed on
        +   * the button.
        +   *
        +   * The second parameter, `value`, is optional. It's a string that sets the
        +   * button's value. See
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#value" target="_blank">MDN</a>
        +   * for more details.
        +   *
        +   * @method createButton
        +   * @param  {String} label label displayed on the button.
        +   * @param  {String} [value] value of the button.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
            *
            * @example
            * <div>
            * <code>
        -   * let beat;
        -   *
            * function setup() {
            *   createCanvas(100, 100);
            *
            *   background(200);
            *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(16);
        -   *
        -   *   // Display a message.
        -   *   text('Click to play', 50, 50);
        +   *   // Create a button and place it beneath the canvas.
        +   *   let button = createButton('click me');
        +   *   button.position(0, 100);
            *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   beat = createAudio('assets/beat.mp3');
        +   *   // Call repaint() when the button is pressed.
        +   *   button.mousePressed(repaint);
            *
        -   *   describe('The text "Click to play" written in black on a gray background. A beat plays when the user clicks the square.');
        +   *   describe('A gray square with a button that says "click me" beneath it. The square changes color when the button is clicked.');
            * }
            *
        -   * // Play the beat when the user presses the mouse.
        -   * function mousePressed() {
        -   *   beat.play();
        +   * // Change the background color.
        +   * function repaint() {
        +   *   let g = random(255);
        +   *   background(g);
            * }
            * </code>
            * </div>
        -   */
        -  play() {
        -    if (this.elt.currentTime === this.elt.duration) {
        -      this.elt.currentTime = 0;
        -    }
        -    let promise;
        -    if (this.elt.readyState > 1) {
        -      promise = this.elt.play();
        -    } else {
        -      // in Chrome, playback cannot resume after being stopped and must reload
        -      this.elt.load();
        -      promise = this.elt.play();
        -    }
        -    if (promise && promise.catch) {
        -      promise.catch(e => {
        -        // if it's an autoplay failure error
        -        if (e.name === 'NotAllowedError') {
        -          if (typeof IS_MINIFIED === 'undefined') {
        -            p5._friendlyAutoplayError(this.src);
        -          } else {
        -            console.error(e);
        -          }
        -        } else {
        -          // any other kind of error
        -          console.error('Media play method encountered an unexpected error', e);
        -        }
        -      });
        -    }
        -    return this;
        -  }
        -
        -  /**
        -   * Stops a media element and sets its current time to 0.
        -   *
        -   * Calling `media.play()` will restart playing audio/video from the beginning.
            *
        -   * @method stop
        -   * @chainable
        -   *
        -   * @example
            * <div>
            * <code>
        -   * let beat;
        -   * let isStopped = true;
        +   * let button;
            *
            * function setup() {
            *   createCanvas(100, 100);
            *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   beat = createAudio('assets/beat.mp3');
        +   *   background(200);
            *
        -   *   describe('The text "Click to start" written in black on a gray background. The beat starts or stops when the user presses the mouse.');
        -   * }
        +   *   // Create a button and set its value to 0.
        +   *   // Place the button beneath the canvas.
        +   *   button = createButton('click me', 'red');
        +   *   button.position(0, 100);
            *
        -   * function draw() {
        -   *   background(200);
        +   *   // Call randomColor() when the button is pressed.
        +   *   button.mousePressed(randomColor);
            *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(16);
        +   *   describe('A red square with a button that says "click me" beneath it. The square changes color when the button is clicked.');
        +   * }
            *
        -   *   // Display different instructions based on playback.
        -   *   if (isStopped === true) {
        -   *     text('Click to start', 50, 50);
        -   *   } else {
        -   *     text('Click to stop', 50, 50);
        -   *   }
        +   * function draw() {
        +   *   // Use the button's value to set the background color.
        +   *   let c = button.value();
        +   *   background(c);
            * }
            *
        -   * // Adjust playback when the user presses the mouse.
        -   * function mousePressed() {
        -   *   if (isStopped === true) {
        -   *     // If the beat is stopped, play it.
        -   *     beat.play();
        -   *     isStopped = false;
        -   *   } else {
        -   *     // If the beat is playing, stop it.
        -   *     beat.stop();
        -   *     isStopped = true;
        -   *   }
        +   * // Set the button's value to a random color.
        +   * function randomColor() {
        +   *   let c = random(['red', 'green', 'blue', 'yellow']);
        +   *   button.value(c);
            * }
            * </code>
            * </div>
            */
        -  stop() {
        -    this.elt.pause();
        -    this.elt.currentTime = 0;
        -    return this;
        -  }
        +  fn.createButton = function (label, value) {
        +    // p5._validateParameters('createButton', arguments);
        +    const elt = document.createElement('button');
        +    elt.innerHTML = label;
        +    if (value) elt.value = value;
        +    return addElement(elt, this);
        +  };
         
           /**
        -   * Pauses a media element.
        +   * Creates a checkbox `&lt;input&gt;&lt;/input&gt;` element.
        +   *
        +   * Checkboxes extend the <a href="#/p5.Element">p5.Element</a> class with a
        +   * `checked()` method. Calling `myBox.checked()` returns `true` if it the box
        +   * is checked and `false` if not.
            *
        -   * Calling `media.play()` will resume playing audio/video from the moment it paused.
        +   * The first parameter, `label`, is optional. It's a string that sets the label
        +   * to display next to the checkbox.
            *
        -   * @method pause
        -   * @chainable
        +   * The second parameter, `value`, is also optional. It's a boolean that sets the
        +   * checkbox's value.
        +   *
        +   * @method createCheckbox
        +   * @param  {String} [label] label displayed after the checkbox.
        +   * @param  {Boolean} [value] value of the checkbox. Checked is `true` and unchecked is `false`.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
            *
            * @example
            * <div>
            * <code>
        -   * let beat;
        -   * let isPaused = true;
        +   * let checkbox;
            *
            * function setup() {
            *   createCanvas(100, 100);
            *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   beat = createAudio('assets/beat.mp3');
        +   *   // Create a checkbox and place it beneath the canvas.
        +   *   checkbox = createCheckbox();
        +   *   checkbox.position(0, 100);
            *
        -   *   describe('The text "Click to play" written in black on a gray background. The beat plays or pauses when the user clicks the square.');
        +   *   describe('A black square with a checkbox beneath it. The square turns white when the box is checked.');
            * }
            *
            * function draw() {
        -   *   background(200);
        -   *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(16);
        -   *
        -   *   // Display different instructions based on playback.
        -   *   if (isPaused === true) {
        -   *     text('Click to play', 50, 50);
        -   *   } else {
        -   *     text('Click to pause', 50, 50);
        -   *   }
        -   * }
        -   *
        -   * // Adjust playback when the user presses the mouse.
        -   * function mousePressed() {
        -   *   if (isPaused === true) {
        -   *     // If the beat is paused,
        -   *     // play it.
        -   *     beat.play();
        -   *     isPaused = false;
        +   *   // Use the checkbox to set the background color.
        +   *   if (checkbox.checked()) {
        +   *     background(255);
            *   } else {
        -   *     // If the beat is playing,
        -   *     // pause it.
        -   *     beat.pause();
        -   *     isPaused = true;
        +   *     background(0);
            *   }
            * }
            * </code>
            * </div>
        -   */
        -  pause() {
        -    this.elt.pause();
        -    return this;
        -  }
        -
        -  /**
        -   * Plays the audio/video repeatedly in a loop.
        -   *
        -   * @method loop
        -   * @chainable
            *
        -   * @example
            * <div>
            * <code>
        -   * let beat;
        -   * let isLooping = false;
        +   * let checkbox;
            *
            * function setup() {
            *   createCanvas(100, 100);
            *
        -   *   background(200);
        -   *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   beat = createAudio('assets/beat.mp3');
        +   *   // Create a checkbox and place it beneath the canvas.
        +   *   // Label the checkbox "white".
        +   *   checkbox = createCheckbox(' white');
        +   *   checkbox.position(0, 100);
            *
        -   *   describe('The text "Click to loop" written in black on a gray background. A beat plays repeatedly in a loop when the user clicks. The beat stops when the user clicks again.');
        +   *   describe('A black square with a checkbox labeled "white" beneath it. The square turns white when the box is checked.');
            * }
            *
            * function draw() {
        -   *   background(200);
        -   *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(16);
        -   *
        -   *   // Display different instructions based on playback.
        -   *   if (isLooping === true) {
        -   *     text('Click to stop', 50, 50);
        -   *   } else {
        -   *     text('Click to loop', 50, 50);
        -   *   }
        -   * }
        -   *
        -   * // Adjust playback when the user presses the mouse.
        -   * function mousePressed() {
        -   *   if (isLooping === true) {
        -   *     // If the beat is looping, stop it.
        -   *     beat.stop();
        -   *     isLooping = false;
        +   *   // Use the checkbox to set the background color.
        +   *   if (checkbox.checked()) {
        +   *     background(255);
            *   } else {
        -   *     // If the beat is stopped, loop it.
        -   *     beat.loop();
        -   *     isLooping = true;
        +   *     background(0);
            *   }
            * }
            * </code>
            * </div>
        -   */
        -  loop() {
        -    this.elt.setAttribute('loop', true);
        -    this.play();
        -    return this;
        -  }
        -  /**
        -   * Stops the audio/video from playing in a loop.
        -   *
        -   * The media will stop when it finishes playing.
        -   *
        -   * @method noLoop
        -   * @chainable
            *
        -   * @example
            * <div>
            * <code>
        -   * let beat;
        -   * let isPlaying = false;
        +   * let checkbox;
            *
            * function setup() {
            *   createCanvas(100, 100);
            *
        -   *   background(200);
        -   *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   beat = createAudio('assets/beat.mp3');
        +   *   // Create a checkbox and place it beneath the canvas.
        +   *   // Label the checkbox "white" and set its value to true.
        +   *   checkbox = createCheckbox(' white', true);
        +   *   checkbox.position(0, 100);
            *
        -   *   describe('The text "Click to play" written in black on a gray background. A beat plays when the user clicks. The beat stops when the user clicks again.');
        +   *   describe('A white square with a checkbox labeled "white" beneath it. The square turns black when the box is unchecked.');
            * }
            *
            * function draw() {
        -   *   background(200);
        -   *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(16);
        -   *
        -   *   // Display different instructions based on playback.
        -   *   if (isPlaying === true) {
        -   *     text('Click to stop', 50, 50);
        -   *   } else {
        -   *     text('Click to play', 50, 50);
        -   *   }
        -   * }
        -   *
        -   * // Adjust playback when the user presses the mouse.
        -   * function mousePressed() {
        -   *   if (isPlaying === true) {
        -   *     // If the beat is playing, stop it.
        -   *     beat.stop();
        -   *     isPlaying = false;
        +   *   // Use the checkbox to set the background color.
        +   *   if (checkbox.checked()) {
        +   *     background(255);
            *   } else {
        -   *     // If the beat is stopped, play it.
        -   *     beat.play();
        -   *     isPlaying = true;
        +   *     background(0);
            *   }
            * }
            * </code>
            * </div>
            */
        -  noLoop() {
        -    this.elt.removeAttribute('loop');
        -    return this;
        -  }
        +  fn.createCheckbox = function (...args) {
        +    // p5._validateParameters('createCheckbox', args);
         
        -  /**
        -   * Sets up logic to check that autoplay succeeded.
        -   *
        -   * @method setupAutoplayFailDetection
        -   * @private
        -   */
        -  _setupAutoplayFailDetection() {
        -    const timeout = setTimeout(() => {
        -      if (typeof IS_MINIFIED === 'undefined') {
        -        p5._friendlyAutoplayError(this.src);
        -      } else {
        -        console.error(e);
        +    // Create a container element
        +    const elt = document.createElement('div');
        +
        +    // Create checkbox type input element
        +    const checkbox = document.createElement('input');
        +    checkbox.type = 'checkbox';
        +
        +    // Create label element and wrap it around checkbox
        +    const label = document.createElement('label');
        +    label.appendChild(checkbox);
        +
        +    // Append label element inside the container
        +    elt.appendChild(label);
        +
        +    //checkbox must be wrapped in p5.Element before label so that label appears after
        +    const self = addElement(elt, this);
        +
        +    self.checked = function (...args) {
        +      const cb = self.elt.firstElementChild.getElementsByTagName('input')[0];
        +      if (cb) {
        +        if (args.length === 0) {
        +          return cb.checked;
        +        } else if (args[0]) {
        +          cb.checked = true;
        +        } else {
        +          cb.checked = false;
        +        }
               }
        -    }, 500);
        -    this.elt.addEventListener('play', () => clearTimeout(timeout), {
        -      passive: true,
        -      once: true
        -    });
        -  }
        +      return self;
        +    };
        +
        +    this.value = function (val) {
        +      self.value = val;
        +      return this;
        +    };
        +
        +    // Set the span element innerHTML as the label value if passed
        +    if (args[0]) {
        +      self.value(args[0]);
        +      const span = document.createElement('span');
        +      span.innerHTML = args[0];
        +      label.appendChild(span);
        +    }
        +
        +    // Set the checked value of checkbox if passed
        +    if (args[1]) {
        +      checkbox.checked = true;
        +    }
        +
        +    return self;
        +  };
         
           /**
        -   * Sets the audio/video to play once it's loaded.
        -   *
        -   * The parameter, `shouldAutoplay`, is optional. Calling
        -   * `media.autoplay()` without an argument causes the media to play
        -   * automatically. If `true` is passed, as in `media.autoplay(true)`, the
        -   * media will automatically play. If `false` is passed, as in
        -   * `media.autoPlay(false)`, it won't play automatically.
        -   *
        -   * @method autoplay
        -   * @param {Boolean} [shouldAutoplay] whether the element should autoplay.
        -   * @chainable
        +   * Creates a dropdown menu `&lt;select&gt;&lt;/select&gt;` element.
        +   *
        +   * The parameter is optional. If `true` is passed, as in
        +   * `let mySelect = createSelect(true)`, then the dropdown will support
        +   * multiple selections. If an existing `&lt;select&gt;&lt;/select&gt;` element
        +   * is passed, as in `let mySelect = createSelect(otherSelect)`, the existing
        +   * element will be wrapped in a new <a href="#/p5.Element">p5.Element</a>
        +   * object.
        +   *
        +   * Dropdowns extend the <a href="#/p5.Element">p5.Element</a> class with a few
        +   * helpful methods for managing options:
        +   * - `mySelect.option(name, [value])` adds an option to the menu. The first paremeter, `name`, is a string that sets the option's name and value. The second parameter, `value`, is optional. If provided, it sets the value that corresponds to the key `name`. If an option with `name` already exists, its value is changed to `value`.
        +   * - `mySelect.value()` returns the currently-selected option's value.
        +   * - `mySelect.selected()` returns the currently-selected option.
        +   * - `mySelect.selected(option)` selects the given option by default.
        +   * - `mySelect.disable()` marks the whole dropdown element as disabled.
        +   * - `mySelect.disable(option)` marks a given option as disabled.
        +   * - `mySelect.enable()` marks the whole dropdown element as enabled.
        +   * - `mySelect.enable(option)` marks a given option as enabled.
        +   *
        +   * @method createSelect
        +   * @param {Boolean} [multiple] support multiple selections.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
            *
            * @example
        -   * <div class='notest'>
        +   * <div>
            * <code>
        -   * let video;
        +   * let mySelect;
            *
            * function setup() {
        -   *   noCanvas();
        +   *   createCanvas(100, 100);
            *
        -   *   // Call handleVideo() once the video loads.
        -   *   video = createVideo('assets/fingers.mov', handleVideo);
        +   *   // Create a dropdown and place it beneath the canvas.
        +   *   mySelect = createSelect();
        +   *   mySelect.position(0, 100);
            *
        -   *   describe('A video of fingers walking on a treadmill.');
        +   *   // Add color options.
        +   *   mySelect.option('red');
        +   *   mySelect.option('green');
        +   *   mySelect.option('blue');
        +   *   mySelect.option('yellow');
        +   *
        +   *   // Set the selected option to "red".
        +   *   mySelect.selected('red');
        +   *
        +   *   describe('A red square with a dropdown menu beneath it. The square changes color when a new color is selected.');
            * }
            *
        -   * // Set the video's size and play it.
        -   * function handleVideo() {
        -   *   video.size(100, 100);
        -   *   video.autoplay();
        +   * function draw() {
        +   *   // Use the selected value to paint the background.
        +   *   let c = mySelect.selected();
        +   *   background(c);
            * }
            * </code>
            * </div>
            *
        -   * <div class='notest'>
        +   * <div>
            * <code>
        +   * let mySelect;
        +   *
            * function setup() {
        -   *   noCanvas();
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a dropdown and place it beneath the canvas.
        +   *   mySelect = createSelect();
        +   *   mySelect.position(0, 100);
        +   *
        +   *   // Add color options.
        +   *   mySelect.option('red');
        +   *   mySelect.option('green');
        +   *   mySelect.option('blue');
        +   *   mySelect.option('yellow');
            *
        -   *   // Load a video, but don't play it automatically.
        -   *   let video = createVideo('assets/fingers.mov', handleVideo);
        +   *   // Set the selected option to "red".
        +   *   mySelect.selected('red');
            *
        -   *   // Play the video when the user clicks on it.
        -   *   video.mousePressed(handlePress);
        +   *   // Disable the "yellow" option.
        +   *   mySelect.disable('yellow');
            *
        -   *   describe('An image of fingers on a treadmill. They start walking when the user double-clicks on them.');
        +   *   describe('A red square with a dropdown menu beneath it. The square changes color when a new color is selected.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Use the selected value to paint the background.
        +   *   let c = mySelect.selected();
        +   *   background(c);
            * }
            * </code>
            * </div>
            *
        -   * // Set the video's size and playback mode.
        -   * function handleVideo() {
        -   *   video.size(100, 100);
        -   *   video.autoplay(false);
        -   * }
        +   * <div>
        +   * <code>
        +   * let mySelect;
            *
        -   * // Play the video.
        -   * function handleClick() {
        -   *   video.play();
        -   * }
        -   */
        -  autoplay(val) {
        -    const oldVal = this.elt.getAttribute('autoplay');
        -    this.elt.setAttribute('autoplay', val);
        -    // if we turned on autoplay
        -    if (val && !oldVal) {
        -      // bind method to this scope
        -      const setupAutoplayFailDetection =
        -        () => this._setupAutoplayFailDetection();
        -      // if media is ready to play, schedule check now
        -      if (this.elt.readyState === 4) {
        -        setupAutoplayFailDetection();
        -      } else {
        -        // otherwise, schedule check whenever it is ready
        -        this.elt.addEventListener('canplay', setupAutoplayFailDetection, {
        -          passive: true,
        -          once: true
        -        });
        -      }
        -    }
        -
        -    return this;
        -  }
        -
        -  /**
        -   * Sets the audio/video volume.
        +   * function setup() {
        +   *   createCanvas(100, 100);
            *
        -   * Calling `media.volume()` without an argument returns the current volume
        -   * as a number in the range 0 (off) to 1 (maximum).
        +   *   // Create a dropdown and place it beneath the canvas.
        +   *   mySelect = createSelect();
        +   *   mySelect.position(0, 100);
            *
        -   * The parameter, `val`, is optional. It's a number that sets the volume
        -   * from 0 (off) to 1 (maximum). For example, calling `media.volume(0.5)`
        -   * sets the volume to half of its maximum.
        +   *   // Add color options with names and values.
        +   *   mySelect.option('one', 'red');
        +   *   mySelect.option('two', 'green');
        +   *   mySelect.option('three', 'blue');
        +   *   mySelect.option('four', 'yellow');
            *
        -   * @method volume
        -   * @return {Number} current volume.
        +   *   // Set the selected option to "one".
        +   *   mySelect.selected('one');
        +   *
        +   *   describe('A red square with a dropdown menu beneath it. The square changes color when a new color is selected.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Use the selected value to paint the background.
        +   *   let c = mySelect.selected();
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
            *
        -   * @example
            * <div>
            * <code>
        -   * let dragon;
        +   * // Hold CTRL to select multiple options on Windows and Linux.
        +   * // Hold CMD to select multiple options on macOS.
        +   * let mySelect;
            *
            * function setup() {
            *   createCanvas(100, 100);
            *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   dragon = createAudio('assets/lucky_dragons.mp3');
        +   *   // Create a dropdown and allow multiple selections.
        +   *   // Place it beneath the canvas.
        +   *   mySelect = createSelect(true);
        +   *   mySelect.position(0, 100);
            *
        -   *   // Show the default media controls.
        -   *   dragon.showControls();
        +   *   // Add color options.
        +   *   mySelect.option('red');
        +   *   mySelect.option('green');
        +   *   mySelect.option('blue');
        +   *   mySelect.option('yellow');
            *
        -   *   describe('The text "Volume: V" on a gray square with media controls beneath it. The number "V" oscillates between 0 and 1 as the music plays.');
        +   *   describe('A gray square with a dropdown menu beneath it. Colorful circles appear when their color is selected.');
            * }
            *
            * function draw() {
            *   background(200);
            *
        -   *   // Produce a number between 0 and 1.
        -   *   let n = 0.5 * sin(frameCount * 0.01) + 0.5;
        -   *
        -   *   // Use n to set the volume.
        -   *   dragon.volume(n);
        -   *
        -   *   // Get the current volume and display it.
        -   *   let v = dragon.volume();
        +   *   // Use the selected value(s) to draw circles.
        +   *   let colors = mySelect.selected();
        +   *   for (let i = 0; i < colors.length; i += 1) {
        +   *     // Calculate the x-coordinate.
        +   *     let x = 10 + i * 20;
            *
        -   *   // Round v to 1 decimal place for display.
        -   *   v = round(v, 1);
        +   *     // Access the color.
        +   *     let c = colors[i];
            *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(16);
        -   *
        -   *   // Display the volume.
        -   *   text(`Volume: ${v}`, 50, 50);
        +   *     // Draw the circle.
        +   *     fill(c);
        +   *     circle(x, 50, 20);
        +   *   }
            * }
            * </code>
            * </div>
            */
           /**
        -   * @method volume
        -   * @param {Number}            val volume between 0.0 and 1.0.
        -   * @chainable
        +   * @method createSelect
        +   * @param {Object} existing select element to wrap, either as a <a href="#/p5.Element">p5.Element</a> or
        +   *                          a <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement" target="_blank">HTMLSelectElement</a>.
        +   * @return {p5.Element}
            */
        -  volume(val) {
        -    if (typeof val === 'undefined') {
        -      return this.elt.volume;
        +
        +  fn.createSelect = function (...args) {
        +    // p5._validateParameters('createSelect', args);
        +    let self;
        +    let arg = args[0];
        +    if (arg instanceof Element && arg.elt instanceof HTMLSelectElement) {
        +      // If given argument is p5.Element of select type
        +      self = arg;
        +      this.elt = arg.elt;
        +    } else if (arg instanceof HTMLSelectElement) {
        +      self = addElement(arg, this);
        +      this.elt = arg;
             } else {
        -      this.elt.volume = val;
        +      const elt = document.createElement('select');
        +      if (arg && typeof arg === 'boolean') {
        +        elt.setAttribute('multiple', 'true');
        +      }
        +      self = addElement(elt, this);
        +      this.elt = elt;
             }
        -  }
        +    self.option = function (name, value) {
        +      let index;
        +
        +      // if no name is passed, return
        +      if (name === undefined) {
        +        return;
        +      }
        +      //see if there is already an option with this name
        +      for (let i = 0; i < this.elt.length; i += 1) {
        +        if (this.elt[i].textContent === name) {
        +          index = i;
        +          break;
        +        }
        +      }
        +      //if there is an option with this name we will modify it
        +      if (index !== undefined) {
        +        //if the user passed in false then delete that option
        +        if (value === false) {
        +          this.elt.remove(index);
        +        } else {
        +          // Update the option at index with the value
        +          this.elt[index].value = value;
        +        }
        +      } else {
        +        //if it doesn't exist create it
        +        const opt = document.createElement('option');
        +        opt.textContent = name;
        +        opt.value = value === undefined ? name : value;
        +        this.elt.appendChild(opt);
        +        this._pInst._elements.push(opt);
        +      }
        +    };
        +
        +    self.selected = function (value) {
        +      // Update selected status of option
        +      if (value !== undefined) {
        +        for (let i = 0; i < this.elt.length; i += 1) {
        +          if (this.elt[i].value.toString() === value.toString()) {
        +            this.elt.selectedIndex = i;
        +          }
        +        }
        +        return this;
        +      } else {
        +        if (this.elt.getAttribute('multiple')) {
        +          let arr = [];
        +          for (const selectedOption of this.elt.selectedOptions) {
        +            arr.push(selectedOption.value);
        +          }
        +          return arr;
        +        } else {
        +          return this.elt.value;
        +        }
        +      }
        +    };
        +
        +    self.disable = function (value) {
        +      if (typeof value === 'string') {
        +        for (let i = 0; i < this.elt.length; i++) {
        +          if (this.elt[i].value.toString() === value) {
        +            this.elt[i].disabled = true;
        +            this.elt[i].selected = false;
        +          }
        +        }
        +      } else {
        +        this.elt.disabled = true;
        +      }
        +      return this;
        +    };
        +
        +    self.enable = function (value) {
        +      if (typeof value === 'string') {
        +        for (let i = 0; i < this.elt.length; i++) {
        +          if (this.elt[i].value.toString() === value) {
        +            this.elt[i].disabled = false;
        +            this.elt[i].selected = false;
        +          }
        +        }
        +      } else {
        +        this.elt.disabled = false;
        +        for (let i = 0; i < this.elt.length; i++) {
        +          this.elt[i].disabled = false;
        +          this.elt[i].selected = false;
        +        }
        +      }
        +      return this;
        +    };
        +
        +    return self;
        +  };
         
           /**
        -   * Sets the audio/video playback speed.
        +   * Creates a radio button element.
        +   *
        +   * The parameter is optional. If a string is passed, as in
        +   * `let myRadio = createSelect('food')`, then each radio option will
        +   * have `"food"` as its `name` parameter: `&lt;input name="food"&gt;&lt;/input&gt;`.
        +   * If an existing `&lt;div&gt;&lt;/div&gt;` or `&lt;span&gt;&lt;/span&gt;`
        +   * element is passed, as in `let myRadio = createSelect(container)`, it will
        +   * become the radio button's parent element.
        +   *
        +   * Radio buttons extend the <a href="#/p5.Element">p5.Element</a> class with a few
        +   * helpful methods for managing options:
        +   * - `myRadio.option(value, [label])` adds an option to the menu. The first paremeter, `value`, is a string that sets the option's value and label. The second parameter, `label`, is optional. If provided, it sets the label displayed for the `value`. If an option with `value` already exists, its label is changed and its value is returned.
        +   * - `myRadio.value()` returns the currently-selected option's value.
        +   * - `myRadio.selected()` returns the currently-selected option.
        +   * - `myRadio.selected(value)` selects the given option and returns it as an <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement" target="_blank">`HTMLInputElement`</a>.
        +   * - `myRadio.disable(shouldDisable)` enables the entire radio button if `true` is passed and disables it if `false` is passed.
        +   *
        +   * @method createRadio
        +   * @param  {Object} [containerElement] container HTML Element, either a `&lt;div&gt;&lt;/div&gt;`
        +   * or `&lt;span&gt;&lt;/span&gt;`.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let style = document.createElement('style');
        +   * style.innerHTML = `
        +   * .p5-radio label {
        +   *    display: flex;
        +   *    align-items: center;
        +   *  }
        +   *  .p5-radio input {
        +   *    margin-right: 5px;
        +   *  }
        +   *  `;
        +   * document.head.appendChild(style);
        +   *
        +   * let myRadio;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
            *
        -   * The parameter, `val`, is optional. It's a number that sets the playback
        -   * speed. 1 plays the media at normal speed, 0.5 plays it at half speed, 2
        -   * plays it at double speed, and so on. -1 plays the media at normal speed
        -   * in reverse.
        +   *   // Create a radio button element and place it
        +   *   // in the top-left corner.
        +   *   myRadio = createRadio();
        +   *   myRadio.position(0, 0);
        +   *   myRadio.class('p5-radio');
        +   *   myRadio.size(60);
            *
        -   * Calling `media.speed()` returns the current speed as a number.
        +   *   // Add a few color options.
        +   *   myRadio.option('red');
        +   *   myRadio.option('yellow');
        +   *   myRadio.option('blue');
            *
        -   * Note: Not all browsers support backward playback. Even if they do,
        -   * playback might not be smooth.
        +   *   // Choose a default option.
        +   *   myRadio.selected('yellow');
            *
        -   * @method speed
        -   * @return {Number} current playback speed.
        +   *   describe('A yellow square with three color options listed, "red", "yellow", and "blue". The square changes color when the user selects a new option.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Set the background color using the radio button.
        +   *   let g = myRadio.value();
        +   *   background(g);
        +   * }
        +   * </code>
        +   * </div>
            *
        -   * @example
            * <div>
            * <code>
        -   * let dragon;
        +   * let myRadio;
            *
            * function setup() {
            *   createCanvas(100, 100);
            *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   dragon = createAudio('assets/lucky_dragons.mp3');
        +   *   // Create a radio button element and place it
        +   *   // in the top-left corner.
        +   *   myRadio = createRadio();
        +   *   myRadio.position(0, 0);
        +   *   myRadio.size(50);
            *
        -   *   // Show the default media controls.
        -   *   dragon.showControls();
        +   *   // Add a few color options.
        +   *   // Color values are labeled with
        +   *   // emotions they evoke.
        +   *   myRadio.option('red', 'love');
        +   *   myRadio.option('yellow', 'joy');
        +   *   myRadio.option('blue', 'trust');
            *
        -   *   describe('The text "Speed: S" on a gray square with media controls beneath it. The number "S" oscillates between 0 and 1 as the music plays.');
        +   *   // Choose a default option.
        +   *   myRadio.selected('yellow');
        +   *
        +   *   describe('A yellow square with three options listed, "love", "joy", and "trust". The square changes color when the user selects a new option.');
            * }
            *
            * function draw() {
        -   *   background(200);
        +   *   // Set the background color using the radio button.
        +   *   let c = myRadio.value();
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let myRadio;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a radio button element and place it
        +   *   // in the top-left corner.
        +   *   myRadio = createRadio();
        +   *   myRadio.position(0, 0);
        +   *   myRadio.size(50);
            *
        -   *   // Produce a number between 0 and 2.
        -   *   let n = sin(frameCount * 0.01) + 1;
        +   *   // Add a few color options.
        +   *   myRadio.option('red');
        +   *   myRadio.option('yellow');
        +   *   myRadio.option('blue');
            *
        -   *   // Use n to set the playback speed.
        -   *   dragon.speed(n);
        +   *   // Choose a default option.
        +   *   myRadio.selected('yellow');
            *
        -   *   // Get the current speed and display it.
        -   *   let s = dragon.speed();
        +   *   // Create a button and place it beneath the canvas.
        +   *   let btn = createButton('disable');
        +   *   btn.position(0, 100);
            *
        -   *   // Round s to 1 decimal place for display.
        -   *   s = round(s, 1);
        +   *   // Call disableRadio() when btn is pressed.
        +   *   btn.mousePressed(disableRadio);
            *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(16);
        +   *   describe('A yellow square with three options listed, "red", "yellow", and "blue". The square changes color when the user selects a new option. A "disable" button beneath the canvas disables the color options when pressed.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Set the background color using the radio button.
        +   *   let c = myRadio.value();
        +   *   background(c);
        +   * }
            *
        -   *   // Display the speed.
        -   *   text(`Speed: ${s}`, 50, 50);
        +   * // Disable myRadio.
        +   * function disableRadio() {
        +   *   myRadio.disable(true);
            * }
            * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method createRadio
        +   * @param {String} [name] name parameter assigned to each option's `&lt;input&gt;&lt;/input&gt;` element.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
        +   */
        +  /**
        +   * @method createRadio
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
            */
        +  //counter for unique names on radio button
        +  let counter = 0;
        +  fn.createRadio = function (...args) {
        +    // Creates a div, adds each option as an individual input inside it.
        +    // If already given with a containerEl, will search for all input[radio]
        +    // it, create a p5.Element out of it, add options to it and return the p5.Element.
        +
        +    let self;
        +    let radioElement;
        +    let name;
        +    const arg0 = args[0];
        +    if (
        +      arg0 instanceof Element &&
        +      (arg0.elt instanceof HTMLDivElement || arg0.elt instanceof HTMLSpanElement)
        +    ) {
        +      // If given argument is p5.Element of div/span type
        +      self = arg0;
        +      this.elt = arg0.elt;
        +    } else if (
        +      // If existing radio Element is provided as argument 0
        +      arg0 instanceof HTMLDivElement ||
        +      arg0 instanceof HTMLSpanElement
        +    ) {
        +      self = addElement(arg0, this);
        +      this.elt = arg0;
        +      radioElement = arg0;
        +      if (typeof args[1] === 'string') name = args[1];
        +    } else {
        +      if (typeof arg0 === 'string') name = arg0;
        +      radioElement = document.createElement('div');
        +      self = addElement(radioElement, this);
        +      this.elt = radioElement;
        +    }
        +    self._name = name || `radioOption_${counter++}`;
        +
        +    // setup member functions
        +    const isRadioInput = el =>
        +      el instanceof HTMLInputElement && el.type === 'radio';
        +    const isLabelElement = el => el instanceof HTMLLabelElement;
        +    const isSpanElement = el => el instanceof HTMLSpanElement;
        +
        +    self._getOptionsArray = function () {
        +      return Array.from(this.elt.children)
        +        .filter(
        +          el =>
        +            isRadioInput(el) ||
        +            (isLabelElement(el) && isRadioInput(el.firstElementChild))
        +        )
        +        .map(el => (isRadioInput(el) ? el : el.firstElementChild));
        +    };
        +
        +    self.option = function (value, label) {
        +      // return an option with this value, create if not exists.
        +      let optionEl;
        +      for (const option of self._getOptionsArray()) {
        +        if (option.value === value) {
        +          optionEl = option;
        +          break;
        +        }
        +      }
        +
        +      // Create a new option, add it to radioElement and return it.
        +      if (optionEl === undefined) {
        +        optionEl = document.createElement('input');
        +        optionEl.setAttribute('type', 'radio');
        +        optionEl.setAttribute('value', value);
        +      }
        +      optionEl.setAttribute('name', self._name);
        +
        +      // Check if label element exists, else create it
        +      let labelElement;
        +      if (!isLabelElement(optionEl.parentElement)) {
        +        labelElement = document.createElement('label');
        +        labelElement.insertAdjacentElement('afterbegin', optionEl);
        +      } else {
        +        labelElement = optionEl.parentElement;
        +      }
        +
        +      // Check if span element exists, else create it
        +      let spanElement;
        +      if (!isSpanElement(labelElement.lastElementChild)) {
        +        spanElement = document.createElement('span');
        +        optionEl.insertAdjacentElement('afterend', spanElement);
        +      } else {
        +        spanElement = labelElement.lastElementChild;
        +      }
        +
        +      // Set the innerHTML of span element as the label text
        +      spanElement.innerHTML = label === undefined ? value : label;
        +
        +      // Append the label element, which includes option element and
        +      // span element to the radio container element
        +      this.elt.appendChild(labelElement);
        +
        +      return optionEl;
        +    };
        +
        +    self.remove = function (value) {
        +      for (const optionEl of self._getOptionsArray()) {
        +        if (optionEl.value === value) {
        +          if (isLabelElement(optionEl.parentElement)) {
        +            // Remove parent label which also removes children elements
        +            optionEl.parentElement.remove();
        +          } else {
        +            // Remove the option input if parent label does not exist
        +            optionEl.remove();
        +          }
        +          return;
        +        }
        +      }
        +    };
        +
        +    self.value = function () {
        +      let result = '';
        +      for (const option of self._getOptionsArray()) {
        +        if (option.checked) {
        +          result = option.value;
        +          break;
        +        }
        +      }
        +      return result;
        +    };
         
        -  /**
        -   * @method speed
        -   * @param {Number} speed  speed multiplier for playback.
        -   * @chainable
        -   */
        -  speed(val) {
        -    if (typeof val === 'undefined') {
        -      return this.presetPlaybackRate || this.elt.playbackRate;
        -    } else {
        -      if (this.loadedmetadata) {
        -        this.elt.playbackRate = val;
        +    self.selected = function (value) {
        +      let result = null;
        +      if (value === undefined) {
        +        for (const option of self._getOptionsArray()) {
        +          if (option.checked) {
        +            result = option;
        +            break;
        +          }
        +        }
               } else {
        -        this.presetPlaybackRate = val;
        +        // forEach loop to uncheck all radio buttons before
        +        // setting any one as checked.
        +        self._getOptionsArray().forEach(option => {
        +          option.checked = false;
        +          option.removeAttribute('checked');
        +        });
        +
        +        for (const option of self._getOptionsArray()) {
        +          if (option.value === value) {
        +            option.setAttribute('checked', true);
        +            option.checked = true;
        +            result = option;
        +          }
        +        }
               }
        -    }
        -  }
        +      return result;
        +    };
        +
        +    self.disable = function (shouldDisable = true) {
        +      for (const radioInput of self._getOptionsArray()) {
        +        radioInput.setAttribute('disabled', shouldDisable);
        +      }
        +    };
        +
        +    return self;
        +  };
         
           /**
        -   * Sets the media element's playback time.
        +   * Creates a color picker element.
            *
        -   * The parameter, `time`, is optional. It's a number that specifies the
        -   * time, in seconds, to jump to when playback begins.
        +   * The parameter, `value`, is optional. If a color string or
        +   * <a href="#/p5.Color">p5.Color</a> object is passed, it will set the default
        +   * color.
            *
        -   * Calling `media.time()` without an argument returns the number of seconds
        -   * the audio/video has played.
        +   * Color pickers extend the <a href="#/p5.Element">p5.Element</a> class with a
        +   * couple of helpful methods for managing colors:
        +   * - `myPicker.value()` returns the current color as a hex string in the format `'#rrggbb'`.
        +   * - `myPicker.color()` returns the current color as a <a href="#/p5.Color">p5.Color</a> object.
            *
        -   * Note: Time resets to 0 when looping media restarts.
        -   *
        -   * @method time
        -   * @return {Number} current time (in seconds).
        +   * @method createColorPicker
        +   * @param {String|p5.Color} [value] default color as a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color" target="_blank">CSS color string</a>.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
            *
            * @example
            * <div>
            * <code>
        -   * let dragon;
        +   * let myPicker;
            *
            * function setup() {
            *   createCanvas(100, 100);
            *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   dragon = createAudio('assets/lucky_dragons.mp3');
        -   *
        -   *   // Show the default media controls.
        -   *   dragon.showControls();
        +   *   // Create a color picker and set its position.
        +   *   myPicker = createColorPicker('deeppink');
        +   *   myPicker.position(0, 100);
            *
        -   *   describe('The text "S seconds" on a gray square with media controls beneath it. The number "S" increases as the song plays.');
        +   *   describe('A pink square with a color picker beneath it. The square changes color when the user picks a new color.');
            * }
            *
            * function draw() {
        -   *   background(200);
        -   *
        -   *   // Get the current playback time.
        -   *   let s = dragon.time();
        -   *
        -   *   // Round s to 1 decimal place for display.
        -   *   s = round(s, 1);
        -   *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(16);
        -   *
        -   *   // Display the playback time.
        -   *   text(`${s} seconds`, 50, 50);
        +   *   // Use the color picker to paint the background.
        +   *   let c = myPicker.color();
        +   *   background(c);
            * }
            * </code>
            * </div>
            *
            * <div>
            * <code>
        -   * let dragon;
        +   * let myPicker;
            *
            * function setup() {
            *   createCanvas(100, 100);
            *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   dragon = createAudio('assets/lucky_dragons.mp3');
        -   *
        -   *   // Show the default media controls.
        -   *   dragon.showControls();
        +   *   // Create a color picker and set its position.
        +   *   myPicker = createColorPicker('deeppink');
        +   *   myPicker.position(0, 100);
            *
        -   *   // Jump to 2 seconds to start.
        -   *   dragon.time(2);
        -   *
        -   *   describe('The text "S seconds" on a gray square with media controls beneath it. The number "S" increases as the song plays.');
        +   *   describe('A number with the format "#rrggbb" is displayed on a pink canvas. The background color and number change when the user picks a new color.');
            * }
            *
            * function draw() {
        -   *   background(200);
        -   *
        -   *   // Get the current playback time.
        -   *   let s = dragon.time();
        -   *
        -   *   // Round s to 1 decimal place for display.
        -   *   s = round(s, 1);
        +   *   // Use the color picker to paint the background.
        +   *   let c = myPicker.value();
        +   *   background(c);
            *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(16);
        -   *
        -   *   // Display the playback time.
        -   *   text(`${s} seconds`, 50, 50);
        +   *   // Display the current color as a hex string.
        +   *   text(c, 25, 55);
            * }
            * </code>
            * </div>
            */
        -  /**
        -   * @method time
        -   * @param {Number} time time to jump to (in seconds).
        -   * @chainable
        -   */
        -  time(val) {
        -    if (typeof val === 'undefined') {
        -      return this.elt.currentTime;
        +  fn.createColorPicker = function (value) {
        +    // p5._validateParameters('createColorPicker', arguments);
        +    // TODO: This implementation needs to be rechecked or reimplemented
        +    // The way it worked with color is a bit too complex
        +    const elt = document.createElement('input');
        +    let self;
        +    elt.type = 'color';
        +    if (value) {
        +      if (value instanceof p5.Color) {
        +        elt.value = value.toString('#rrggbb');
        +      } else {
        +        this.push();
        +        this.colorMode('rgb');
        +        elt.value = this.color(value).toString('#rrggbb');
        +        this.pop();
        +      }
             } else {
        -      this.elt.currentTime = val;
        -      return this;
        +      elt.value = '#000000';
             }
        -  }
        +    self = addElement(elt, this);
        +    // Method to return a p5.Color object for the given color.
        +    const inst = this;
        +    self.color = function () {
        +      inst.push();
        +      if (value) {
        +        if (value.mode) {
        +          inst.colorMode(value.mode, ...value?.maxes[value.mode]);
        +        }
        +      }
        +      const c = inst.color(this.elt.value);
        +      inst.pop();
        +      return c;
        +    };
        +    return self;
        +  };
         
           /**
        -   * Returns the audio/video's duration in seconds.
        +   * Creates a text `&lt;input&gt;&lt;/input&gt;` element.
        +   *
        +   * Call `myInput.size()` to set the length of the text box.
        +   *
        +   * The first parameter, `value`, is optional. It's a string that sets the
        +   * input's default value. The input is blank by default.
            *
        -   * @method duration
        -   * @return {Number} duration (in seconds).
        +   * The second parameter, `type`, is also optional. It's a string that
        +   * specifies the type of text being input. See MDN for a full
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input" target="_blank">list of options</a>.
        +   * The default is `'text'`.
        +   *
        +   * @method createInput
        +   * @param {String} [value] default value of the input box. Defaults to an empty string `''`.
        +   * @param {String} [type] type of input. Defaults to `'text'`.
        +   * @return {p5.Element} new <a href="#/p5.Element">p5.Element</a> object.
            *
            * @example
            * <div>
            * <code>
        -   * let dragon;
        +   * let myInput;
            *
            * function setup() {
            *   createCanvas(100, 100);
            *
        -   *   background(200);
        -   *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   dragon = createAudio('assets/lucky_dragons.mp3');
        -   *
        -   *   // Show the default media controls.
        -   *   dragon.showControls();
        +   *   // Create an input element and place it
        +   *   // beneath the canvas.
        +   *   myInput = createInput();
        +   *   myInput.position(0, 100);
            *
        -   *   describe('The text "S seconds left" on a gray square with media controls beneath it. The number "S" decreases as the song plays.');
        +   *   describe('A gray square with a text box beneath it. The text in the square changes when the user types something new in the input bar.');
            * }
            *
            * function draw() {
            *   background(200);
            *
        -   *   // Calculate the time remaining.
        -   *   let s = dragon.duration() - dragon.time();
        -   *
        -   *   // Round s to 1 decimal place for display.
        -   *   s = round(s, 1);
        -   *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(16);
        -   *
        -   *   // Display the time remaining.
        -   *   text(`${s} seconds left`, 50, 50);
        +   *   // Use the input to display a message.
        +   *   let msg = myInput.value();
        +   *   text(msg, 25, 55);
            * }
            * </code>
            * </div>
        -   */
        -  duration() {
        -    return this.elt.duration;
        -  }
        -  _ensureCanvas() {
        -    if (!this.canvas) {
        -      this.canvas = document.createElement('canvas');
        -      this.drawingContext = this.canvas.getContext('2d');
        -      this.setModified(true);
        -    }
        -
        -    // Don't update the canvas again if we have already updated the canvas with
        -    // the current frame
        -    const needsRedraw = this._frameOnCanvas !== this._pInst.frameCount;
        -    if (this.loadedmetadata && needsRedraw) {
        -      // wait for metadata for w/h
        -      if (this.canvas.width !== this.elt.width) {
        -        this.canvas.width = this.elt.width;
        -        this.canvas.height = this.elt.height;
        -        this.width = this.canvas.width;
        -        this.height = this.canvas.height;
        -      }
        -
        -      this.drawingContext.clearRect(
        -        0, 0, this.canvas.width, this.canvas.height);
        -
        -      if (this.flipped === true) {
        -        this.drawingContext.save();
        -        this.drawingContext.scale(-1, 1);
        -        this.drawingContext.translate(-this.canvas.width, 0);
        -      }
        -
        -      this.drawingContext.drawImage(
        -        this.elt,
        -        0,
        -        0,
        -        this.canvas.width,
        -        this.canvas.height
        -      );
        -
        -      if (this.flipped === true) {
        -        this.drawingContext.restore();
        -      }
        -
        -      this.setModified(true);
        -      this._frameOnCanvas = this._pInst.frameCount;
        -    }
        -  }
        -  loadPixels(...args) {
        -    this._ensureCanvas();
        -    return p5.Renderer2D.prototype.loadPixels.apply(this, args);
        -  }
        -  updatePixels(x, y, w, h) {
        -    if (this.loadedmetadata) {
        -      // wait for metadata
        -      this._ensureCanvas();
        -      p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
        -    }
        -    this.setModified(true);
        -    return this;
        -  }
        -  get(...args) {
        -    this._ensureCanvas();
        -    return p5.Renderer2D.prototype.get.apply(this, args);
        -  }
        -  _getPixel(...args) {
        -    this.loadPixels();
        -    return p5.Renderer2D.prototype._getPixel.apply(this, args);
        -  }
        -
        -  set(x, y, imgOrCol) {
        -    if (this.loadedmetadata) {
        -      // wait for metadata
        -      this._ensureCanvas();
        -      p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
        -      this.setModified(true);
        -    }
        -  }
        -  copy(...args) {
        -    this._ensureCanvas();
        -    p5.prototype.copy.apply(this, args);
        -  }
        -  mask(...args) {
        -    this.loadPixels();
        -    this.setModified(true);
        -    p5.Image.prototype.mask.apply(this, args);
        -  }
        -  /**
        -   * helper method for web GL mode to figure out if the element
        -   * has been modified and might need to be re-uploaded to texture
        -   * memory between frames.
        -   * @method isModified
        -   * @private
        -   * @return {boolean} a boolean indicating whether or not the
        -   * image has been updated or modified since last texture upload.
        -   */
        -  isModified() {
        -    return this._modified;
        -  }
        -  /**
        -   * helper method for web GL mode to indicate that an element has been
        -   * changed or unchanged since last upload. gl texture upload will
        -   * set this value to false after uploading the texture; or might set
        -   * it to true if metadata has become available but there is no actual
        -   * texture data available yet..
        -   * @method setModified
        -   * @param {boolean} val sets whether or not the element has been
        -   * modified.
        -   * @private
        -   */
        -  setModified(value) {
        -    this._modified = value;
        -  }
        -  /**
        -   * Calls a function when the audio/video reaches the end of its playback.
        -   *
        -   * The element is passed as an argument to the callback function.
            *
        -   * Note: The function won't be called if the media is looping.
        -   *
        -   * @method  onended
        -   * @param  {Function} callback function to call when playback ends.
        -   *                             The `p5.MediaElement` is passed as
        -   *                             the argument.
        -   * @chainable
        -   *
        -   * @example
            * <div>
            * <code>
        -   * let beat;
        -   * let isPlaying = false;
        -   * let isDone = false;
        +   * let myInput;
            *
            * function setup() {
            *   createCanvas(100, 100);
            *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   beat = createAudio('assets/beat.mp3');
        -   *
        -   *   // Call handleEnd() when the beat finishes.
        -   *   beat.onended(handleEnd);
        +   *   // Create an input element and place it
        +   *   // beneath the canvas. Set its default
        +   *   // text to "hello!".
        +   *   myInput = createInput('hello!');
        +   *   myInput.position(0, 100);
            *
        -   *   describe('The text "Click to play" written in black on a gray square. A beat plays when the user clicks. The text "Done!" appears when the beat finishes playing.');
        +   *   describe('The text "hello!" written at the center of a gray square. A text box beneath the square also says "hello!". The text in the square changes when the user types something new in the input bar.');
            * }
            *
            * function draw() {
            *   background(200);
            *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(16);
        -   *
        -   *   // Display different messages based on playback.
        -   *   if (isDone === true) {
        -   *     text('Done!', 50, 50);
        -   *   } else if (isPlaying === false) {
        -   *     text('Click to play', 50, 50);
        -   *   } else {
        -   *     text('Playing...', 50, 50);
        -   *   }
        -   * }
        -   *
        -   * // Play the beat when the user presses the mouse.
        -   * function mousePressed() {
        -   *   if (isPlaying === false) {
        -   *     isPlaying = true;
        -   *     beat.play();
        -   *   }
        -   * }
        -   *
        -   * // Set isDone when playback ends.
        -   * function handleEnd() {
        -   *   isDone = false;
        +   *   // Use the input to display a message.
        +   *   let msg = myInput.value();
        +   *   text(msg, 25, 55);
            * }
            * </code>
            * </div>
            */
        -  onended(callback) {
        -    this._onended = callback;
        -    return this;
        -  }
        -
        -  /*** CONNECT TO WEB AUDIO API / p5.sound.js ***/
        -
           /**
        -   * Sends the element's audio to an output.
        -   *
        -   * The parameter, `audioNode`, can be an `AudioNode` or an object from the
        -   * `p5.sound` library.
        -   *
        -   * If no element is provided, as in `myElement.connect()`, the element
        -   * connects to the main output. All connections are removed by the
        -   * `.disconnect()` method.
        -   *
        -   * Note: This method is meant to be used with the p5.sound.js addon library.
        -   *
        -   * @method  connect
        -   * @param  {AudioNode|Object} audioNode AudioNode from the Web Audio API,
        -   * or an object from the p5.sound library
        +   * @method createInput
        +   * @param {String} [value]
        +   * @return {p5.Element}
            */
        -  connect(obj) {
        -    let audioContext, mainOutput;
        -
        -    // if p5.sound exists, same audio context
        -    if (typeof p5.prototype.getAudioContext === 'function') {
        -      audioContext = p5.prototype.getAudioContext();
        -      mainOutput = p5.soundOut.input;
        -    } else {
        -      try {
        -        audioContext = obj.context;
        -        mainOutput = audioContext.destination;
        -      } catch (e) {
        -        throw 'connect() is meant to be used with Web Audio API or p5.sound.js';
        -      }
        -    }
        -
        -    // create a Web Audio MediaElementAudioSourceNode if none already exists
        -    if (!this.audioSourceNode) {
        -      this.audioSourceNode = audioContext.createMediaElementSource(this.elt);
        -
        -      // connect to main output when this method is first called
        -      this.audioSourceNode.connect(mainOutput);
        -    }
        -
        -    // connect to object if provided
        -    if (obj) {
        -      if (obj.input) {
        -        this.audioSourceNode.connect(obj.input);
        -      } else {
        -        this.audioSourceNode.connect(obj);
        -      }
        -    } else {
        -      // otherwise connect to main output of p5.sound / AudioContext
        -      this.audioSourceNode.connect(mainOutput);
        -    }
        -  }
        +  fn.createInput = function (value = '', type = 'text') {
        +    // p5._validateParameters('createInput', arguments);
        +    let elt = document.createElement('input');
        +    elt.setAttribute('value', value);
        +    elt.setAttribute('type', type);
        +    return addElement(elt, this);
        +  };
         
           /**
        -   * Disconnect all Web Audio routing, including to the main output.
        +   * Creates an `&lt;input&gt;&lt;/input&gt;` element of type `'file'`.
            *
        -   * This is useful if you want to re-route the output through audio effects,
        -   * for example.
        +   * `createFileInput()` allows users to select local files for use in a sketch.
        +   * It returns a <a href="#/p5.File">p5.File</a> object.
            *
        -   * @method  disconnect
        -   */
        -  disconnect() {
        -    if (this.audioSourceNode) {
        -      this.audioSourceNode.disconnect();
        -    } else {
        -      throw 'nothing to disconnect';
        -    }
        -  }
        -
        -  /*** SHOW / HIDE CONTROLS ***/
        -
        -  /**
        -   * Show the default
        -   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement" target="_blank">HTMLMediaElement</a>
        -   * controls.
        +   * The first parameter, `callback`, is a function that's called when the file
        +   * loads. The callback function should have one parameter, `file`, that's a
        +   * <a href="#/p5.File">p5.File</a> object.
            *
        -   * Note: The controls vary between web browsers.
        +   * The second parameter, `multiple`, is optional. It's a boolean value that
        +   * allows loading multiple files if set to `true`. If `true`, `callback`
        +   * will be called once per file.
            *
        -   * @method  showControls
        +   * @method createFileInput
        +   * @param  {Function} callback function to call once the file loads.
        +   * @param  {Boolean} [multiple] allow multiple files to be selected.
        +   * @return {p5.File} new <a href="#/p5.File">p5.File</a> object.
            *
            * @example
            * <div>
            * <code>
        +   * // Use the file input to select an image to
        +   * // load and display.
        +   * let input;
        +   * let img;
        +   *
            * function setup() {
            *   createCanvas(100, 100);
            *
        -   *   background('cornflowerblue');
        +   *   // Create a file input and place it beneath
        +   *   // the canvas.
        +   *   input = createFileInput(handleImage);
        +   *   input.position(0, 100);
            *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        -   *   textSize(50);
        -   *
        -   *   // Display a dragon.
        -   *   text('🐉', 50, 50);
        +   *   describe('A gray square with a file input beneath it. If the user selects an image file to load, it is displayed on the square.');
        +   * }
            *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   let dragon = createAudio('assets/lucky_dragons.mp3');
        +   * function draw() {
        +   *   background(200);
            *
        -   *   // Show the default media controls.
        -   *   dragon.showControls();
        +   *   // Draw the image if loaded.
        +   *   if (img) {
        +   *     image(img, 0, 0, width, height);
        +   *   }
        +   * }
            *
        -   *   describe('A dragon emoji, 🐉, drawn in the center of a blue square. A song plays in the background. Audio controls are displayed beneath the canvas.');
        +   * // Create an image if the file is an image.
        +   * function handleImage(file) {
        +   *   if (file.type === 'image') {
        +   *     img = createImg(file.data, '');
        +   *     img.hide();
        +   *   } else {
        +   *     img = null;
        +   *   }
            * }
            * </code>
            * </div>
        -   */
        -  showControls() {
        -    // must set style for the element to show on the page
        -    this.elt.style['text-align'] = 'inherit';
        -    this.elt.controls = true;
        -  }
        -
        -  /**
        -   * Hide the default
        -   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement" target="_blank">HTMLMediaElement</a>
        -   * controls.
            *
        -   * @method hideControls
        -   *
        -   * @example
            * <div>
            * <code>
        -   * let dragon;
        -   * let isHidden = false;
        +   * // Use the file input to select multiple images
        +   * // to load and display.
        +   * let input;
        +   * let images = [];
            *
            * function setup() {
        -   *   createCanvas(100, 100);
        -   *
        -   *   // Create a p5.MediaElement using createAudio().
        -   *   dragon = createAudio('assets/lucky_dragons.mp3');
        -   *
        -   *   // Show the default media controls.
        -   *   dragon.showControls();
        -   *
        -   *   describe('The text "Double-click to hide controls" written in the middle of a gray square. A song plays in the background. Audio controls are displayed beneath the canvas. The controls appear/disappear when the user double-clicks the square.');
        +   *   // Create a file input and place it beneath
        +   *   // the canvas. Allow it to load multiple files.
        +   *   input = createFileInput(handleImage, true);
        +   *   input.position(0, 100);
            * }
            *
            * function draw() {
            *   background(200);
            *
        -   *   // Style the text.
        -   *   textAlign(CENTER);
        +   *   // Draw the images if loaded. Each image
        +   *   // is drawn 20 pixels lower than the
        +   *   // previous image.
        +   *   for (let i = 0; i < images.length; i += 1) {
        +   *     // Calculate the y-coordinate.
        +   *     let y = i * 20;
            *
        -   *   // Display a different message when controls are hidden or shown.
        -   *   if (isHidden === true) {
        -   *     text('Double-click to show controls', 10, 20, 80, 80);
        -   *   } else {
        -   *     text('Double-click to hide controls', 10, 20, 80, 80);
        +   *     // Draw the image.
        +   *     image(img, 0, y, 100, 100);
            *   }
        +   *
        +   *   describe('A gray square with a file input beneath it. If the user selects multiple image files to load, they are displayed on the square.');
            * }
            *
        -   * // Show/hide controls based on a double-click.
        -   * function doubleClicked() {
        -   *   if (isHidden === true) {
        -   *     dragon.showControls();
        -   *     isHidden = false;
        -   *   } else {
        -   *     dragon.hideControls();
        -   *     isHidden = true;
        +   * // Create an image if the file is an image,
        +   * // then add it to the images array.
        +   * function handleImage(file) {
        +   *   if (file.type === 'image') {
        +   *     let img = createImg(file.data, '');
        +   *     img.hide();
        +   *     images.push(img);
            *   }
            * }
            * </code>
            * </div>
            */
        -  hideControls() {
        -    this.elt.controls = false;
        -  }
        -
        -  /**
        - * Schedules a function to call when the audio/video reaches a specific time
        - * during its playback.
        - *
        - * The first parameter, `time`, is the time, in seconds, when the function
        - * should run. This value is passed to `callback` as its first argument.
        - *
        - * The second parameter, `callback`, is the function to call at the specified
        - * cue time.
        - *
        - * The third parameter, `value`, is optional and can be any type of value.
        - * `value` is passed to `callback`.
        - *
        - * Calling `media.addCue()` returns an ID as a string. This is useful for
        - * removing the cue later.
        - *
        - * @method  addCue
        - * @param {Number}   time     cue time to run the callback function.
        - * @param {Function} callback function to call at the cue time.
        - * @param {Object} [value]    object to pass as the argument to
        - *                            `callback`.
        - * @return {Number} id ID of this cue,
        - *                     useful for `media.removeCue(id)`.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.MediaElement using createAudio().
        - *   let beat = createAudio('assets/beat.mp3');
        - *
        - *   // Play the beat in a loop.
        - *   beat.loop();
        - *
        - *   // Schedule a few events.
        - *   beat.addCue(0, changeBackground, 'red');
        - *   beat.addCue(2, changeBackground, 'deeppink');
        - *   beat.addCue(4, changeBackground, 'orchid');
        - *   beat.addCue(6, changeBackground, 'lavender');
        - *
        - *   describe('A red square with a beat playing in the background. Its color changes every 2 seconds while the audio plays.');
        - * }
        - *
        - * // Change the background color.
        - * function changeBackground(c) {
        - *   background(c);
        - * }
        - * </code>
        - * </div>
        - */
        -  addCue(time, callback, val) {
        -    const id = this._cueIDCounter++;
        -
        -    const cue = new Cue(callback, time, id, val);
        -    this._cues.push(cue);
        -
        -    if (!this.elt.ontimeupdate) {
        -      this.elt.ontimeupdate = this._onTimeUpdate.bind(this);
        -    }
        -
        -    return id;
        -  }
        +  fn.createFileInput = function (callback, multiple = false) {
        +    // p5._validateParameters('createFileInput', arguments);
         
        -  /**
        - * Removes a callback based on its ID.
        - *
        - * @method removeCue
        - * @param  {Number} id ID of the cue, created by `media.addCue()`.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let lavenderID;
        - * let isRemoved = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.MediaElement using createAudio().
        - *   let beat = createAudio('assets/beat.mp3');
        - *
        - *   // Play the beat in a loop.
        - *   beat.loop();
        - *
        - *   // Schedule a few events.
        - *   beat.addCue(0, changeBackground, 'red');
        - *   beat.addCue(2, changeBackground, 'deeppink');
        - *   beat.addCue(4, changeBackground, 'orchid');
        - *
        - *   // Record the ID of the "lavender" callback.
        - *   lavenderID = beat.addCue(6, changeBackground, 'lavender');
        - *
        - *   describe('The text "Double-click to remove lavender." written on a red square. The color changes every 2 seconds while the audio plays. The lavender option is removed when the user double-clicks the square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Display different instructions based on the available callbacks.
        - *   if (isRemoved === false) {
        - *     text('Double-click to remove lavender.', 10, 10, 80, 80);
        - *   } else {
        - *     text('No more lavender.', 10, 10, 80, 80);
        - *   }
        - * }
        - *
        - * // Change the background color.
        - * function changeBackground(c) {
        - *   background(c);
        - * }
        - *
        - * // Remove the lavender color-change cue when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isRemoved === false) {
        - *     beat.removeCue(lavenderID);
        - *     isRemoved = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -  removeCue(id) {
        -    for (let i = 0; i < this._cues.length; i++) {
        -      if (this._cues[i].id === id) {
        -        console.log(id);
        -        this._cues.splice(i, 1);
        +    const handleFileSelect = function (event) {
        +      for (const file of event.target.files) {
        +        File._load(file, callback);
               }
        -    }
        -
        -    if (this._cues.length === 0) {
        -      this.elt.ontimeupdate = null;
        -    }
        -  }
        -
        -  /**
        - * Removes all functions scheduled with `media.addCue()`.
        - *
        - * @method  clearCues
        - *
        - * @example
        - * <div>
        - * <code>
        - * let isChanging = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.MediaElement using createAudio().
        - *   let beat = createAudio('assets/beat.mp3');
        - *
        - *   // Play the beat in a loop.
        - *   beat.loop();
        - *
        - *   // Schedule a few events.
        - *   beat.addCue(0, changeBackground, 'red');
        - *   beat.addCue(2, changeBackground, 'deeppink');
        - *   beat.addCue(4, changeBackground, 'orchid');
        - *   beat.addCue(6, changeBackground, 'lavender');
        - *
        - *   describe('The text "Double-click to stop changing." written on a square. The color changes every 2 seconds while the audio plays. The color stops changing when the user double-clicks the square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Display different instructions based on the available callbacks.
        - *   if (isChanging === true) {
        - *     text('Double-click to stop changing.', 10, 10, 80, 80);
        - *   } else {
        - *     text('No more changes.', 10, 10, 80, 80);
        - *   }
        - * }
        - *
        - * // Change the background color.
        - * function changeBackground(c) {
        - *   background(c);
        - * }
        - *
        - * // Remove cued functions and stop changing colors when the user
        - * // double-clicks.
        - * function doubleClicked() {
        - *   if (isChanging === true) {
        - *     beat.clearCues();
        - *     isChanging = false;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -  clearCues() {
        -    this._cues = [];
        -    this.elt.ontimeupdate = null;
        -  }
        -
        -  // private method that checks for cues to be fired if events
        -  // have been scheduled using addCue(callback, time).
        -  _onTimeUpdate() {
        -    const playbackTime = this.time();
        -
        -    for (let i = 0; i < this._cues.length; i++) {
        -      const callbackTime = this._cues[i].time;
        -      const val = this._cues[i].val;
        +    };
         
        -      if (this._prevTime < callbackTime && callbackTime <= playbackTime) {
        -        // pass the scheduled callbackTime as parameter to the callback
        -        this._cues[i].callback(val);
        -      }
        +    // If File API's are not supported, throw Error
        +    if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
        +      console.log(
        +        'The File APIs are not fully supported in this browser. Cannot create element.'
        +      );
        +      return;
             }
         
        -    this._prevTime = playbackTime;
        -  }
        +    const fileInput = document.createElement('input');
        +    fileInput.setAttribute('type', 'file');
        +    if (multiple) fileInput.setAttribute('multiple', true);
        +    fileInput.addEventListener('change', handleFileSelect, false);
        +    return addElement(fileInput, this);
        +  };
         }
         
        -p5.MediaElement = MediaElement;
        -
        -/**
        - * A class to describe a file.
        - *
        - * `p5.File` objects are used by
        - * <a href="#/p5.Element/drop">myElement.drop()</a> and
        - * created by
        - * <a href="#/p5/createFileInput">createFileInput</a>.
        - *
        - * @class p5.File
        - * @constructor
        - * @param {File} file wrapped file.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Use the file input to load a
        - * // file and display its info.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a file input and place it beneath the canvas.
        - *   // Call displayInfo() when the file loads.
        - *   let input = createFileInput(displayInfo);
        - *   input.position(0, 100);
        - *
        - *   describe('A gray square with a file input beneath it. If the user loads a file, its info is written in black.');
        - * }
        - *
        - * // Display the p5.File's info once it loads.
        - * function displayInfo(file) {
        - *   background(200);
        - *
        - *   // Display the p5.File's name.
        - *   text(file.name, 10, 10, 80, 40);
        - *
        - *   // Display the p5.File's type and subtype.
        - *   text(`${file.type}/${file.subtype}`, 10, 70);
        - *
        - *   // Display the p5.File's size in bytes.
        - *   text(file.size, 10, 90);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Use the file input to select an image to
        - * // load and display.
        - * let img;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a file input and place it beneath the canvas.
        - *   // Call handleImage() when the file image loads.
        - *   let input = createFileInput(handleImage);
        - *   input.position(0, 100);
        - *
        - *   describe('A gray square with a file input beneath it. If the user selects an image file to load, it is displayed on the square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the image if it's ready.
        - *   if (img) {
        - *     image(img, 0, 0, width, height);
        - *   }
        - * }
        - *
        - * // Use the p5.File's data once it loads.
        - * function handleImage(file) {
        - *   // Check the p5.File's type.
        - *   if (file.type === 'image') {
        - *     // Create an image using using the p5.File's data.
        - *     img = createImg(file.data, '');
        - *
        - *     // Hide the image element so it doesn't appear twice.
        - *     img.hide();
        - *   } else {
        - *     img = null;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -class File {
        -  constructor(file, pInst) {
        -    /**
        -     * Underlying
        -     * <a href="https://developer.mozilla.org/en-US/docs/Web/API/File" target="_blank">File</a>
        -     * object. All `File` properties and methods are accessible.
        -     *
        -     * @property file
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * // Use the file input to load a
        -     * // file and display its info.
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   background(200);
        -     *
        -     *   // Create a file input and place it beneath the canvas.
        -     *   // Call displayInfo() when the file loads.
        -     *   let input = createFileInput(displayInfo);
        -     *   input.position(0, 100);
        -     *
        -     *   describe('A gray square with a file input beneath it. If the user loads a file, its info is written in black.');
        -     * }
        -     *
        -     * // Use the p5.File once it loads.
        -     * function displayInfo(file) {
        -     *   background(200);
        -     *
        -     *   // Display the p5.File's name.
        -     *   text(file.name, 10, 10, 80, 40);
        -     *
        -     *   // Display the p5.File's type and subtype.
        -     *   text(`${file.type}/${file.subtype}`, 10, 70);
        -     *
        -     *   // Display the p5.File's size in bytes.
        -     *   text(file.size, 10, 90);
        -     * }
        -     * </code>
        -     * </div>
        -     */
        -    this.file = file;
        -
        -    this._pInst = pInst;
        -
        -    // Splitting out the file type into two components
        -    // This makes determining if image or text etc simpler
        -    const typeList = file.type.split('/');
        -    /**
        -     * The file
        -     * <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME type</a>
        -     * as a string.
        -     *
        -     * For example, `'image'` and `'text'` are both MIME types.
        -     *
        -     * @property type
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * // Use the file input to load a file and display its info.
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   background(200);
        -     *
        -     *   // Create a file input and place it beneath the canvas.
        -     *   // Call displayType() when the file loads.
        -     *   let input = createFileInput(displayType);
        -     *   input.position(0, 100);
        -     *
        -     *   describe('A gray square with a file input beneath it. If the user loads a file, its type is written in black.');
        -     * }
        -     *
        -     * // Display the p5.File's type once it loads.
        -     * function displayType(file) {
        -     *   background(200);
        -     *
        -     *   // Display the p5.File's type.
        -     *   text(`This is file's type is: ${file.type}`, 10, 10, 80, 80);
        -     * }
        -     * </code>
        -     * </div>
        -     */
        -    this.type = typeList[0];
        -    /**
        -     * The file subtype as a string.
        -     *
        -     * For example, a file with an `'image'`
        -     * <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME type</a>
        -     * may have a subtype such as ``png`` or ``jpeg``.
        -     *
        -     * @property subtype
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * // Use the file input to load a
        -     * // file and display its info.
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   background(200);
        -     *
        -     *   // Create a file input and place it beneath the canvas.
        -     *   // Call displaySubtype() when the file loads.
        -     *   let input = createFileInput(displaySubtype);
        -     *   input.position(0, 100);
        -     *
        -     *   describe('A gray square with a file input beneath it. If the user loads a file, its subtype is written in black.');
        -     * }
        -     *
        -     * // Display the p5.File's type once it loads.
        -     * function displaySubtype(file) {
        -     *   background(200);
        -     *
        -     *   // Display the p5.File's subtype.
        -     *   text(`This is file's subtype is: ${file.subtype}`, 10, 10, 80, 80);
        -     * }
        -     * </code>
        -     * </div>
        -     */
        -    this.subtype = typeList[1];
        -    /**
        -     * The file name as a string.
        -     *
        -     * @property name
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * // Use the file input to load a
        -     * // file and display its info.
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   background(200);
        -     *
        -     *   // Create a file input and place it beneath the canvas.
        -     *   // Call displayName() when the file loads.
        -     *   let input = createFileInput(displayName);
        -     *   input.position(0, 100);
        -     *
        -     *   describe('A gray square with a file input beneath it. If the user loads a file, its name is written in black.');
        -     * }
        -     *
        -     * // Display the p5.File's name once it loads.
        -     * function displayName(file) {
        -     *   background(200);
        -     *
        -     *   // Display the p5.File's name.
        -     *   text(`This is file's name is: ${file.name}`, 10, 10, 80, 80);
        -     * }
        -     * </code>
        -     * </div>
        -     */
        -    this.name = file.name;
        -    /**
        -     * The number of bytes in the file.
        -     *
        -     * @property size
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * // Use the file input to load a file and display its info.
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   background(200);
        -     *
        -     *   // Create a file input and place it beneath the canvas.
        -     *   // Call displaySize() when the file loads.
        -     *   let input = createFileInput(displaySize);
        -     *   input.position(0, 100);
        -     *
        -     *   describe('A gray square with a file input beneath it. If the user loads a file, its size in bytes is written in black.');
        -     * }
        -     *
        -     * // Display the p5.File's size in bytes once it loads.
        -     * function displaySize(file) {
        -     *   background(200);
        -     *
        -     *   // Display the p5.File's size.
        -     *   text(`This is file has ${file.size} bytes.`, 10, 10, 80, 80);
        -     * }
        -     * </code>
        -     * </div>
        -     */
        -    this.size = file.size;
        -
        -    /**
        -     * A string containing the file's data.
        -     *
        -     * Data can be either image data, text contents, or a parsed object in the
        -     * case of JSON and <a href="#/p5.XML">p5.XML</a> objects.
        -     *
        -     * @property data
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * // Use the file input to load a file and display its info.
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   background(200);
        -     *
        -     *   // Create a file input and place it beneath the canvas.
        -     *   // Call displayData() when the file loads.
        -     *   let input = createFileInput(displayData);
        -     *   input.position(0, 100);
        -     *
        -     *   describe('A gray square with a file input beneath it. If the user loads a file, its data is written in black.');
        -     * }
        -     *
        -     * // Display the p5.File's data once it loads.
        -     * function displayData(file) {
        -     *   background(200);
        -     *
        -     *   // Display the p5.File's data, which looks like a random string of characters.
        -     *   text(file.data, 10, 10, 80, 80);
        -     * }
        -     * </code>
        -     * </div>
        -     */
        -    this.data = undefined;
        -  }
        -
        -
        -  static _createLoader(theFile, callback) {
        -    const reader = new FileReader();
        -    reader.onload = function (e) {
        -      const p5file = new p5.File(theFile);
        -      if (p5file.file.type === 'application/json') {
        -        // Parse JSON and store the result in data
        -        p5file.data = JSON.parse(e.target.result);
        -      } else if (p5file.file.type === 'text/xml') {
        -        // Parse XML, wrap it in p5.XML and store the result in data
        -        const parser = new DOMParser();
        -        const xml = parser.parseFromString(e.target.result, 'text/xml');
        -        p5file.data = new p5.XML(xml.documentElement);
        -      } else {
        -        p5file.data = e.target.result;
        -      }
        -      callback(p5file);
        -    };
        -    return reader;
        -  }
        +export default dom;
         
        -  static _load(f, callback) {
        -    // Text or data?
        -    // This should likely be improved
        -    if (/^text\//.test(f.type) || f.type === 'application/json') {
        -      p5.File._createLoader(f, callback).readAsText(f);
        -    } else if (!/^(video|audio)\//.test(f.type)) {
        -      p5.File._createLoader(f, callback).readAsDataURL(f);
        -    } else {
        -      const file = new p5.File(f);
        -      file.data = URL.createObjectURL(f);
        -      callback(file);
        -    }
        -  }
        +if(typeof p5 !== 'undefined'){
        +  dom(p5, p5.prototype);
         }
        -
        -
        -p5.File = File;
        -
        -export default p5;
        diff --git a/src/dom/index.js b/src/dom/index.js
        new file mode 100644
        index 0000000000..2bb5771c1e
        --- /dev/null
        +++ b/src/dom/index.js
        @@ -0,0 +1,11 @@
        +import dom from './dom';
        +import element from './p5.Element';
        +import media from './p5.MediaElement';
        +import file from './p5.File';
        +
        +export default function(p5){
        +  p5.registerAddon(dom);
        +  p5.registerAddon(element);
        +  p5.registerAddon(media);
        +  p5.registerAddon(file);
        +}
        diff --git a/src/dom/p5.Element.js b/src/dom/p5.Element.js
        new file mode 100644
        index 0000000000..6ecb74f5b6
        --- /dev/null
        +++ b/src/dom/p5.Element.js
        @@ -0,0 +1,2675 @@
        +/**
        + * @module DOM
        + * @submodule DOM
        + * @for p5.Element
        + */
        +
        +import { File } from './p5.File';
        +import { Color } from '../color/p5.Color';
        +import * as constants from '../core/constants';
        +
        +class Element {
        +  /**
        +   * A `Number` property that stores the element's width.
        +   *
        +   * @type {Number}
        +   * @property width
        +   * @for p5.Element
        +   */
        +  width;
        +  /**
        +   * A `Number` property that stores the element's height.
        +   *
        +   * @type {Number}
        +   * @property height
        +   * @for p5.Element
        +   */
        +  height;
        +  /**
        +   * The element's underlying `HTMLElement` object.
        +   *
        +   * The
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" target="_blank">HTMLElement</a>
        +   * object's properties and methods can be used directly.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the border style for the
        +   *   // canvas.
        +   *   cnv.elt.style.border = '5px dashed deeppink';
        +   *
        +   *   describe('A gray square with a pink border drawn with dashed lines.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @property elt
        +   * @for p5.Element
        +   * @name elt
        +   * @readOnly
        +   */
        +  elt;
        +
        +  constructor(elt, pInst) {
        +    this.elt = elt;
        +    this._pInst = this._pixelsState = pInst;
        +    this._events = {};
        +    this.width = this.elt.offsetWidth;
        +    this.height = this.elt.offsetHeight;
        +  }
        +
        +  /**
        +   * Removes the element, stops all audio/video streams, and removes all
        +   * callback functions.
        +   *
        +   * @method remove
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let p;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a paragraph element.
        +   *   p = createP('p5*js');
        +   *   p.position(10, 10);
        +   *
        +   *   describe('The text "p5*js" written at the center of a gray square. ');
        +   * }
        +   *
        +   * // Remove the paragraph when the user double-clicks.
        +   * function doubleClicked() {
        +   *   p.remove();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  remove() {
        +    // stop all audios/videos and detach all devices like microphone/camera etc
        +    // used as input/output for audios/videos.
        +    // if (this instanceof p5.MediaElement) {
        +    if(this.stop){
        +      this.stop();
        +      const sources = this.elt.srcObject;
        +      if (sources !== null) {
        +        const tracks = sources.getTracks();
        +        tracks.forEach(track => {
        +          track.stop();
        +        });
        +      }
        +    }
        +
        +    // delete the reference in this._pInst._elements
        +    const index = this._pInst._elements.indexOf(this);
        +    if (index !== -1) {
        +      this._pInst._elements.splice(index, 1);
        +    }
        +
        +    // deregister events
        +    for (let ev in this._events) {
        +      this.elt.removeEventListener(ev, this._events[ev]);
        +    }
        +    if (this.elt && this.elt.parentNode) {
        +      this.elt.parentNode.removeChild(this.elt);
        +    }
        +  }
        +
        +  /**
        +   * Attaches the element to a parent element.
        +   *
        +   * For example, a `&lt;div&gt;&lt;/div&gt;` element may be used as a box to
        +   * hold two pieces of text, a header and a paragraph. The
        +   * `&lt;div&gt;&lt;/div&gt;` is the parent element of both the header and
        +   * paragraph.
        +   *
        +   * The parameter `parent` can have one of three types. `parent` can be a
        +   * string with the parent element's ID, as in
        +   * `myElement.parent('container')`. It can also be another
        +   * <a href="#/p5.Element">p5.Element</a> object, as in
        +   * `myElement.parent(myDiv)`. Finally, `parent` can be an `HTMLElement`
        +   * object, as in `myElement.parent(anotherElement)`.
        +   *
        +   * Calling `myElement.parent()` without an argument returns the element's
        +   * parent.
        +   *
        +   * @param  {String|p5.Element|Object} parent ID, <a href="#/p5.Element">p5.Element</a>,
        +   *                                           or HTMLElement of desired parent element.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup()  {
        +   *   background(200);
        +   *
        +   *   // Create a div element.
        +   *   let div = createDiv();
        +   *
        +   *   // Place the div in the top-left corner.
        +   *   div.position(10, 20);
        +   *
        +   *   // Set its width and height.
        +   *   div.size(80, 60);
        +   *
        +   *   // Set its background color to white
        +   *   div.style('background-color', 'white');
        +   *
        +   *   // Align any text to the center.
        +   *   div.style('text-align', 'center');
        +   *
        +   *   // Set its ID to "container".
        +   *   div.id('container');
        +   *
        +   *   // Create a paragraph element.
        +   *   let p = createP('p5*js');
        +   *
        +   *   // Make the div its parent
        +   *   // using its ID "container".
        +   *   p.parent('container');
        +   *
        +   *   describe('The text "p5*js" written in black at the center of a white rectangle. The rectangle is inside a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup()  {
        +   *   background(200);
        +   *
        +   *   // Create rectangular div element.
        +   *   let div = createDiv();
        +   *
        +   *   // Place the div in the top-left corner.
        +   *   div.position(10, 20);
        +   *
        +   *   // Set its width and height.
        +   *   div.size(80, 60);
        +   *
        +   *   // Set its background color and align
        +   *   // any text to the center.
        +   *   div.style('background-color', 'white');
        +   *   div.style('text-align', 'center');
        +   *
        +   *   // Create a paragraph element.
        +   *   let p = createP('p5*js');
        +   *
        +   *   // Make the div its parent.
        +   *   p.parent(div);
        +   *
        +   *   describe('The text "p5*js" written in black at the center of a white rectangle. The rectangle is inside a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup()  {
        +   *   background(200);
        +   *
        +   *   // Create rectangular div element.
        +   *   let div = createDiv();
        +   *
        +   *   // Place the div in the top-left corner.
        +   *   div.position(10, 20);
        +   *
        +   *   // Set its width and height.
        +   *   div.size(80, 60);
        +   *
        +   *   // Set its background color and align
        +   *   // any text to the center.
        +   *   div.style('background-color', 'white');
        +   *   div.style('text-align', 'center');
        +   *
        +   *   // Create a paragraph element.
        +   *   let p = createP('p5*js');
        +   *
        +   *   // Make the div its parent
        +   *   // using the underlying
        +   *   // HTMLElement.
        +   *   p.parent(div.elt);
        +   *
        +   *   describe('The text "p5*js" written in black at the center of a white rectangle. The rectangle is inside a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @return {p5.Element}
        +   */
        +  parent(p) {
        +    if (typeof p === 'undefined') {
        +      return this.elt.parentNode;
        +    }
        +
        +    if (typeof p === 'string') {
        +      if (p[0] === '#') {
        +        p = p.substring(1);
        +      }
        +      p = document.getElementById(p);
        +    } else if (p instanceof Element) {
        +      p = p.elt;
        +    }
        +    p.appendChild(this.elt);
        +    return this;
        +  }
        +
        +  /**
        +   * Attaches the element as a child of another element.
        +   *
        +   * `myElement.child()` accepts either a string ID, DOM node, or
        +   * <a href="#/p5.Element">p5.Element</a>. For example,
        +   * `myElement.child(otherElement)`. If no argument is provided, an array of
        +   * children DOM nodes is returned.
        +   *
        +   * @method child
        +   * @returns {Node[]} an array of child nodes.
        +   *
        +   * @example
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create the div elements.
        +   *   let div0 = createDiv('Parent');
        +   *   let div1 = createDiv('Child');
        +   *
        +   *   // Make div1 the child of div0
        +   *   // using the p5.Element.
        +   *   div0.child(div1);
        +   *
        +   *   describe('A gray square with the words "Parent" and "Child" written beneath it.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create the div elements.
        +   *   let div0 = createDiv('Parent');
        +   *   let div1 = createDiv('Child');
        +   *
        +   *   // Give div1 an ID.
        +   *   div1.id('apples');
        +   *
        +   *   // Make div1 the child of div0
        +   *   // using its ID.
        +   *   div0.child('apples');
        +   *
        +   *   describe('A gray square with the words "Parent" and "Child" written beneath it.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender notest'>
        +   * <code>
        +   * // This example assumes there is a div already on the page
        +   * // with id "myChildDiv".
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create the div elements.
        +   *   let div0 = createDiv('Parent');
        +   *
        +   *   // Select the child element by its ID.
        +   *   let elt = document.getElementById('myChildDiv');
        +   *
        +   *   // Make div1 the child of div0
        +   *   // using its HTMLElement object.
        +   *   div0.child(elt);
        +   *
        +   *   describe('A gray square with the words "Parent" and "Child" written beneath it.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method child
        +   * @param  {String|p5.Element} [child] the ID, DOM node, or <a href="#/p5.Element">p5.Element</a>
        +   *                         to add to the current element
        +   * @chainable
        +   */
        +  child(childNode) {
        +    if (typeof childNode === 'undefined') {
        +      return this.elt.childNodes;
        +    }
        +    if (typeof childNode === 'string') {
        +      if (childNode[0] === '#') {
        +        childNode = childNode.substring(1);
        +      }
        +      childNode = document.getElementById(childNode);
        +    } else if (childNode instanceof Element) {
        +      childNode = childNode.elt;
        +    }
        +
        +    if (childNode instanceof HTMLElement) {
        +      this.elt.appendChild(childNode);
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * Sets the inner HTML of the element, replacing any existing HTML.
        +   *
        +   * The second parameter, `append`, is optional. If `true` is passed, as in
        +   * `myElement.html('hi', true)`, the HTML is appended instead of replacing
        +   * existing HTML.
        +   *
        +   * If no arguments are passed, as in `myElement.html()`, the element's inner
        +   * HTML is returned.
        +   *
        +   * @for p5.Element
        +   * @method html
        +   * @returns {String} the inner HTML of the element
        +   *
        +   * @example
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create the div element and set its size.
        +   *   let div = createDiv('');
        +   *   div.size(100, 100);
        +   *
        +   *   // Set the inner HTML to "hi".
        +   *   div.html('hi');
        +   *
        +   *   describe('A gray square with the word "hi" written beneath it.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create the div element and set its size.
        +   *   let div = createDiv('Hello ');
        +   *   div.size(100, 100);
        +   *
        +   *   // Append "World" to the div's HTML.
        +   *   div.html('World', true);
        +   *
        +   *   describe('A gray square with the text "Hello World" written beneath it.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create the div element.
        +   *   let div = createDiv('Hello');
        +   *
        +   *   // Prints "Hello" to the console.
        +   *   print(div.html());
        +   *
        +   *   describe('A gray square with the word "Hello!" written beneath it.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method html
        +   * @param  {String} [html] the HTML to be placed inside the element
        +   * @param  {Boolean} [append] whether to append HTML to existing
        +   * @chainable
        +   */
        +  html(...args) {
        +    if (args.length === 0) {
        +      return this.elt.innerHTML;
        +    } else if (args[1]) {
        +      this.elt.insertAdjacentHTML('beforeend', args[0]);
        +      return this;
        +    } else {
        +      this.elt.innerHTML = args[0];
        +      return this;
        +    }
        +  }
        +
        +  /**
        +   * Sets the element's ID using a given string.
        +   *
        +   * Calling `myElement.id()` without an argument returns its ID as a string.
        +   *
        +   * @param  {String} id ID of the element.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the canvas' ID
        +   *   // to "mycanvas".
        +   *   cnv.id('mycanvas');
        +   *
        +   *   // Get the canvas' ID.
        +   *   let id = cnv.id();
        +   *   text(id, 24, 54);
        +   *
        +   *   describe('The text "mycanvas" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @return {String} ID of the element.
        +   */
        +  id(id) {
        +    if (typeof id === 'undefined') {
        +      return this.elt.id;
        +    }
        +
        +    this.elt.id = id;
        +    this.width = this.elt.offsetWidth;
        +    this.height = this.elt.offsetHeight;
        +    return this;
        +  }
        +
        +  /**
        +   * Adds a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class" target="_blank">class attribute</a>
        +   * to the element using a given string.
        +   *
        +   * Calling `myElement.class()` without an argument returns a string with its current classes.
        +   *
        +   * @param  {String} class class to add.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Add the class "small" to the
        +   *   // canvas element.
        +   *   cnv.class('small');
        +   *
        +   *   // Get the canvas element's class
        +   *   // and display it.
        +   *   let c = cnv.class();
        +   *   text(c, 35, 54);
        +   *
        +   *   describe('The word "small" written in black on a gray canvas.');
        +   *
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @return {String} element's classes, if any.
        +   */
        +  class(c) {
        +    if (typeof c === 'undefined') {
        +      return this.elt.className;
        +    }
        +
        +    this.elt.className = c;
        +    return this;
        +  }
        +
        +  /**
        +   *
        +   * Adds a class to the element.
        +   *
        +   * @for p5.Element
        +   * @method addClass
        +   * @param  {String} class name of class to add.
        +   * @chainable
        +   *
        +   * @example
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a div element.
        +   *   let div = createDiv('div');
        +   *
        +   *   // Add a class to the div.
        +   *   div.addClass('myClass');
        +   *
        +   *   describe('A gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  addClass(c) {
        +    if (this.elt.className) {
        +      if (!this.hasClass(c)) {
        +        this.elt.className = this.elt.className + ' ' + c;
        +      }
        +    } else {
        +      this.elt.className = c;
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * Removes a class from the element.
        +   *
        +   * @method removeClass
        +   * @param  {String} class name of class to remove.
        +   * @chainable
        +   *
        +   * @example
        +   * <div class='norender'>
        +   * <code>
        +   * // In this example, a class is set when the div is created
        +   * // and removed when mouse is pressed. This could link up
        +   * // with a CSS style rule to toggle style properties.
        +   *
        +   * let div;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a div element.
        +   *   div = createDiv('div');
        +   *
        +   *   // Add a class to the div.
        +   *   div.addClass('myClass');
        +   *
        +   *   describe('A gray square.');
        +   * }
        +   *
        +   * // Remove 'myClass' from the div when the user presses the mouse.
        +   * function mousePressed() {
        +   *   div.removeClass('myClass');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  removeClass(c) {
        +    // Note: Removing a class that does not exist does NOT throw an error in classList.remove method
        +    this.elt.classList.remove(c);
        +    return this;
        +  }
        +
        +  /**
        +   * Checks if a class is already applied to element.
        +   *
        +   * @method hasClass
        +   * @returns {boolean} a boolean value if element has specified class.
        +   * @param c {String} name of class to check.
        +   *
        +   * @example
        +   * <div class='norender'>
        +   * <code>
        +   * let div;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a div element.
        +   *   div = createDiv('div');
        +   *
        +   *   // Add the class 'show' to the div.
        +   *   div.addClass('show');
        +   *
        +   *   describe('A gray square.');
        +   * }
        +   *
        +   * // Toggle the class 'show' when the mouse is pressed.
        +   * function mousePressed() {
        +   *   if (div.hasClass('show')) {
        +   *     div.addClass('show');
        +   *   } else {
        +   *     div.removeClass('show');
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  hasClass(c) {
        +    return this.elt.classList.contains(c);
        +  }
        +
        +  /**
        +   * Toggles whether a class is applied to the element.
        +   *
        +   * @method toggleClass
        +   * @param c {String} class name to toggle.
        +   * @chainable
        +   *
        +   * @example
        +   * <div class='norender'>
        +   * <code>
        +   * let div;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a div element.
        +   *   div = createDiv('div');
        +   *
        +   *   // Add the 'show' class to the div.
        +   *   div.addClass('show');
        +   *
        +   *   describe('A gray square.');
        +   * }
        +   *
        +   * // Toggle the 'show' class when the mouse is pressed.
        +   * function mousePressed() {
        +   *   div.toggleClass('show');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  toggleClass(c) {
        +    // classList also has a toggle() method, but we cannot use that yet as support is unclear.
        +    // See https://github.com/processing/p5.js/issues/3631
        +    // this.elt.classList.toggle(c);
        +    if (this.elt.classList.contains(c)) {
        +      this.elt.classList.remove(c);
        +    } else {
        +      this.elt.classList.add(c);
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * Centers the element either vertically, horizontally, or both.
        +   *
        +   * `center()` will center the element relative to its parent or according to
        +   * the page's body if the element has no parent.
        +   *
        +   * If no argument is passed, as in `myElement.center()` the element is aligned
        +   * both vertically and horizontally.
        +   *
        +   * @method center
        +   * @param  {String} [align] passing 'vertical', 'horizontal' aligns element accordingly
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create the div element and style it.
        +   *   let div = createDiv('');
        +   *   div.size(10, 10);
        +   *   div.style('background-color', 'orange');
        +   *
        +   *   // Center the div relative to the page's body.
        +   *   div.center();
        +   *
        +   *   describe('A gray square and an orange rectangle. The rectangle is at the center of the page.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  center(align) {
        +    const style = this.elt.style.display;
        +    const hidden = this.elt.style.display === 'none';
        +    const parentHidden = this.parent().style.display === 'none';
        +    const pos = { x: this.elt.offsetLeft, y: this.elt.offsetTop };
        +
        +    if (hidden) this.show();
        +    if (parentHidden) this.parent().show();
        +    this.elt.style.display = 'block';
        +
        +    this.position(0, 0);
        +    const wOffset = Math.abs(this.parent().offsetWidth - this.elt.offsetWidth);
        +    const hOffset = Math.abs(this.parent().offsetHeight - this.elt.offsetHeight);
        +
        +    if (align === 'both' || align === undefined) {
        +      this.position(
        +        wOffset / 2 + this.parent().offsetLeft,
        +        hOffset / 2 + this.parent().offsetTop
        +      );
        +    } else if (align === 'horizontal') {
        +      this.position(wOffset / 2 + this.parent().offsetLeft, pos.y);
        +    } else if (align === 'vertical') {
        +      this.position(pos.x, hOffset / 2 + this.parent().offsetTop);
        +    }
        +
        +    this.style('display', style);
        +    if (hidden) this.hide();
        +    if (parentHidden) this.parent().hide();
        +
        +    return this;
        +  }
        +
        +  /**
        +   * Sets the element's position.
        +   *
        +   * The first two parameters, `x` and `y`, set the element's position relative
        +   * to the top-left corner of the web page.
        +   *
        +   * The third parameter, `positionType`, is optional. It sets the element's
        +   * <a target="_blank"
        +   * href="https://developer.mozilla.org/en-US/docs/Web/CSS/position">positioning scheme</a>.
        +   * `positionType` is a string that can be either `'static'`, `'fixed'`,
        +   * `'relative'`, `'sticky'`, `'initial'`, or `'inherit'`.
        +   *
        +   * If no arguments passed, as in `myElement.position()`, the method returns
        +   * the element's position in an object, as in `{ x: 0, y: 0 }`.
        +   *
        +   * @method position
        +   * @returns {Object} object of form `{ x: 0, y: 0 }` containing the element's position.
        +   *
        +   * @example
        +   * <div>
        +   * <code class='norender'>
        +   * function setup() {
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Positions the canvas 50px to the right and 100px
        +   *   // below the top-left corner of the window.
        +   *   cnv.position(50, 100);
        +   *
        +   *   describe('A gray square that is 50 pixels to the right and 100 pixels down from the top-left corner of the web page.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code class='norender'>
        +   * function setup() {
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Positions the canvas at the top-left corner
        +   *   // of the window with a 'fixed' position type.
        +   *   cnv.position(0, 0, 'fixed');
        +   *
        +   *   describe('A gray square in the top-left corner of the web page.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method position
        +   * @param  {Number} [x] x-position relative to top-left of window (optional)
        +   * @param  {Number} [y] y-position relative to top-left of window (optional)
        +   * @param  {String} [positionType] it can be static, fixed, relative, sticky, initial or inherit (optional)
        +   * @chainable
        +   */
        +  position(...args) {
        +    if (args.length === 0) {
        +      return { x: this.elt.offsetLeft, y: this.elt.offsetTop };
        +    } else {
        +      let positionType = 'absolute';
        +      if (
        +        args[2] === 'static' ||
        +        args[2] === 'fixed' ||
        +        args[2] === 'relative' ||
        +        args[2] === 'sticky' ||
        +        args[2] === 'initial' ||
        +        args[2] === 'inherit'
        +      ) {
        +        positionType = args[2];
        +      }
        +      this.elt.style.position = positionType;
        +      this.elt.style.left = args[0] + 'px';
        +      this.elt.style.top = args[1] + 'px';
        +      this.x = args[0];
        +      this.y = args[1];
        +      return this;
        +    }
        +  }
        +
        +  /**
        +   * Shows the current element.
        +   *
        +   * @method show
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let p;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a paragraph element and hide it.
        +   *   p = createP('p5*js');
        +   *   p.position(10, 10);
        +   *   p.hide();
        +   *
        +   *   describe('A gray square. The text "p5*js" appears when the user double-clicks the square.');
        +   * }
        +   *
        +   * // Show the paragraph when the user double-clicks.
        +   * function doubleClicked() {
        +   *   p.show();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  show() {
        +    this.elt.style.display = 'block';
        +    return this;
        +  }
        +
        +  /**
        +   * Hides the current element.
        +   *
        +   * @method hide
        +   * @chainable
        +   *
        +   * @example
        +   * let p;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a paragraph element.
        +   *   p = createP('p5*js');
        +   *   p.position(10, 10);
        +   *
        +   *   describe('The text "p5*js" at the center of a gray square. The text disappears when the user double-clicks the square.');
        +   * }
        +   *
        +   * // Hide the paragraph when the user double-clicks.
        +   * function doubleClicked() {
        +   *   p.hide();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  hide() {
        +    this.elt.style.display = 'none';
        +    return this;
        +  }
        +
        +  /**
        +   * Sets the element's width and height.
        +   *
        +   * Calling `myElement.size()` without an argument returns the element's size
        +   * as an object with the properties `width` and `height`. For example,
        +   *  `{ width: 20, height: 10 }`.
        +   *
        +   * The first parameter, `width`, is optional. It's a number used to set the
        +   * element's width. Calling `myElement.size(10)`
        +   *
        +   * The second parameter, 'height`, is also optional. It's a
        +   * number used to set the element's height. For example, calling
        +   * `myElement.size(20, 10)` sets the element's width to 20 pixels and height
        +   * to 10 pixels.
        +   *
        +   * The constant `AUTO` can be used to adjust one dimension at a time while
        +   * maintaining the aspect ratio, which is `width / height`. For example,
        +   * consider an element that's 200 pixels wide and 100 pixels tall. Calling
        +   * `myElement.size(20, AUTO)` sets the width to 20 pixels and height to 10
        +   * pixels.
        +   *
        +   * Note: In the case of elements that need to load data, such as images, wait
        +   * to call `myElement.size()` until after the data loads.
        +   *
        +   * @method size
        +   * @return {Object} width and height of the element in an object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a pink div element and place it at the top-left corner.
        +   *   let div = createDiv();
        +   *   div.position(10, 10);
        +   *   div.style('background-color', 'deeppink');
        +   *
        +   *   // Set the div's width to 80 pixels and height to 20 pixels.
        +   *   div.size(80, 20);
        +   *
        +   *   describe('A gray square with a pink rectangle near its top.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a pink div element and place it at the top-left corner.
        +   *   let div = createDiv();
        +   *   div.position(10, 10);
        +   *   div.style('background-color', 'deeppink');
        +   *
        +   *   // Set the div's width to 80 pixels and height to 40 pixels.
        +   *   div.size(80, 40);
        +   *
        +   *   // Get the div's size as an object.
        +   *   let s = div.size();
        +   *
        +   *   // Display the div's dimensions.
        +   *   div.html(`${s.width} x ${s.height}`);
        +   *
        +   *   describe('A gray square with a pink rectangle near its top. The text "80 x 40" is written within the rectangle.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img1;
        +   * let img2;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Load an image of an astronaut on the moon
        +   *   // and place it at the top-left of the canvas.
        +   *   img1 = createImg(
        +   *     'assets/moonwalk.jpg',
        +   *     'An astronaut walking on the moon',
        +   *     ''
        +   *   );
        +   *   img1.position(0, 0);
        +   *
        +   *   // Load an image of an astronaut on the moon
        +   *   // and place it at the top-left of the canvas.
        +   *   // Resize the image once it's loaded.
        +   *   img2 = createImg(
        +   *     'assets/moonwalk.jpg',
        +   *     'An astronaut walking on the moon',
        +   *     '',
        +   *     resizeImage
        +   *   );
        +   *   img2.position(0, 0);
        +   *
        +   *   describe('A gray square two copies of a space image at the top-left. The copy in front is smaller.');
        +   * }
        +   *
        +   * // Resize img2 and keep its aspect ratio.
        +   * function resizeImage() {
        +   *   img2.size(50, AUTO);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method size
        +   * @param  {(Number|AUTO)} [w]   width of the element, either AUTO, or a number.
        +   * @param  {(Number|AUTO)} [h] height of the element, either AUTO, or a number.
        +   * @chainable
        +   */
        +  size(w, h) {
        +    if (arguments.length === 0) {
        +      return { width: this.elt.offsetWidth, height: this.elt.offsetHeight };
        +    } else {
        +      let aW = w;
        +      let aH = h;
        +      const AUTO = constants.AUTO;
        +      if (aW !== AUTO || aH !== AUTO) {
        +        if (aW === AUTO) {
        +          aW = h * this.width / this.height;
        +        } else if (aH === AUTO) {
        +          aH = w * this.height / this.width;
        +        }
        +        // set diff for cnv vs normal div
        +        if (this.elt instanceof HTMLCanvasElement) {
        +          const j = {};
        +          const k = this.elt.getContext('2d');
        +          let prop;
        +          for (prop in k) {
        +            j[prop] = k[prop];
        +          }
        +          this.elt.setAttribute('width', aW * this._pInst._pixelDensity);
        +          this.elt.setAttribute('height', aH * this._pInst._pixelDensity);
        +          this.elt.style.width = aW + 'px';
        +          this.elt.style.height = aH + 'px';
        +          this._pInst.scale(this._pInst._pixelDensity, this._pInst._pixelDensity);
        +          for (prop in j) {
        +            this.elt.getContext('2d')[prop] = j[prop];
        +          }
        +        } else {
        +          this.elt.style.width = aW + 'px';
        +          this.elt.style.height = aH + 'px';
        +          this.elt.width = aW;
        +          this.elt.height = aH;
        +        }
        +        this.width = aW;
        +        this.height = aH;
        +        if (this._pInst && this._pInst._curElement) {
        +          // main canvas associated with p5 instance
        +          if (this._pInst._curElement.elt === this.elt) {
        +            this._pInst.width = aW;
        +            this._pInst.height = aH;
        +          }
        +        }
        +      }
        +      return this;
        +    }
        +  }
        +
        +  /**
        +   * Applies a style to the element by adding a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax" target="_blank">CSS declaration</a>.
        +   *
        +   * The first parameter, `property`, is a string. If the name of a style
        +   * property is passed, as in `myElement.style('color')`, the method returns
        +   * the current value as a string or `null` if it hasn't been set. If a
        +   * `property:style` string is passed, as in
        +   * `myElement.style('color:deeppink')`, the method sets the style `property`
        +   * to `value`.
        +   *
        +   * The second parameter, `value`, is optional. It sets the property's value.
        +   * `value` can be a string, as in
        +   * `myElement.style('color', 'deeppink')`, or a
        +   * <a href="#/p5.Color">p5.Color</a> object, as in
        +   * `myElement.style('color', myColor)`.
        +   *
        +   * @method style
        +   * @param  {String} property style property to set.
        +   * @returns {String} value of the property.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a paragraph element and set its font color to "deeppink".
        +   *   let p = createP('p5*js');
        +   *   p.position(25, 20);
        +   *   p.style('color', 'deeppink');
        +   *
        +   *   describe('The text p5*js written in pink on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let c = color('deeppink');
        +   *
        +   *   // Create a paragraph element and set its font color using a p5.Color object.
        +   *   let p = createP('p5*js');
        +   *   p.position(25, 20);
        +   *   p.style('color', c);
        +   *
        +   *   describe('The text p5*js written in pink on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a paragraph element and set its font color to "deeppink"
        +   *   // using property:value syntax.
        +   *   let p = createP('p5*js');
        +   *   p.position(25, 20);
        +   *   p.style('color:deeppink');
        +   *
        +   *   describe('The text p5*js written in pink on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an empty paragraph element and set its font color to "deeppink".
        +   *   let p = createP();
        +   *   p.position(5, 5);
        +   *   p.style('color', 'deeppink');
        +   *
        +   *   // Get the element's color as an  RGB color string.
        +   *   let c = p.style('color');
        +   *
        +   *   // Set the element's inner HTML using the RGB color string.
        +   *   p.html(c);
        +   *
        +   *   describe('The text "rgb(255, 20, 147)" written in pink on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method style
        +   * @param  {String} property
        +   * @param  {String|p5.Color} value value to assign to the property.
        +   * @return {String} value of the property.
        +   * @chainable
        +   */
        +  style(prop, val) {
        +    const self = this;
        +
        +    if (val instanceof Color) {
        +      val = val.toString();
        +    }
        +
        +    if (typeof val === 'undefined') {
        +      if (prop.indexOf(':') === -1) {
        +        // no value set, so assume requesting a value
        +        let styles = window.getComputedStyle(self.elt);
        +        let style = styles.getPropertyValue(prop);
        +        return style;
        +      } else {
        +        // value set using `:` in a single line string
        +        const attrs = prop.split(';');
        +        for (let i = 0; i < attrs.length; i++) {
        +          const parts = attrs[i].split(':');
        +          if (parts[0] && parts[1]) {
        +            this.elt.style[parts[0].trim()] = parts[1].trim();
        +          }
        +        }
        +      }
        +    } else {
        +      // input provided as key,val pair
        +      this.elt.style[prop] = val;
        +      if (
        +        prop === 'width' ||
        +        prop === 'height' ||
        +        prop === 'left' ||
        +        prop === 'top'
        +      ) {
        +        let styles = window.getComputedStyle(self.elt);
        +        let styleVal = styles.getPropertyValue(prop);
        +        let numVal = styleVal.replace(/[^\d.]/g, '');
        +        this[prop] = Math.round(parseFloat(numVal, 10));
        +      }
        +    }
        +    return this;
        +  }
        +
        +  /* Helper method called by p5.Element.style() */
        +  _translate(...args) {
        +    this.elt.style.position = 'absolute';
        +    // save out initial non-translate transform styling
        +    let transform = '';
        +    if (this.elt.style.transform) {
        +      transform = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
        +      transform = transform.replace(/translate[X-Z]?\(.*\)/g, '');
        +    }
        +    if (args.length === 2) {
        +      this.elt.style.transform =
        +        'translate(' + args[0] + 'px, ' + args[1] + 'px)';
        +    } else if (args.length > 2) {
        +      this.elt.style.transform =
        +        'translate3d(' +
        +        args[0] +
        +        'px,' +
        +        args[1] +
        +        'px,' +
        +        args[2] +
        +        'px)';
        +      if (args.length === 3) {
        +        this.elt.parentElement.style.perspective = '1000px';
        +      } else {
        +        this.elt.parentElement.style.perspective = args[3] + 'px';
        +      }
        +    }
        +    // add any extra transform styling back on end
        +    this.elt.style.transform += transform;
        +    return this;
        +  }
        +
        +  /* Helper method called by p5.Element.style() */
        +  _rotate(...args) {
        +    // save out initial non-rotate transform styling
        +    let transform = '';
        +    if (this.elt.style.transform) {
        +      transform = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
        +      transform = transform.replace(/rotate[X-Z]?\(.*\)/g, '');
        +    }
        +
        +    if (args.length === 1) {
        +      this.elt.style.transform = 'rotate(' + args[0] + 'deg)';
        +    } else if (args.length === 2) {
        +      this.elt.style.transform =
        +        'rotate(' + args[0] + 'deg, ' + args[1] + 'deg)';
        +    } else if (args.length === 3) {
        +      this.elt.style.transform = 'rotateX(' + args[0] + 'deg)';
        +      this.elt.style.transform += 'rotateY(' + args[1] + 'deg)';
        +      this.elt.style.transform += 'rotateZ(' + args[2] + 'deg)';
        +    }
        +    // add remaining transform back on
        +    this.elt.style.transform += transform;
        +    return this;
        +  }
        +
        +  /**
        +   * Adds an
        +   * <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started#attributes" target="_blank">attribute</a>
        +   * to the element.
        +   *
        +   * This method is useful for advanced tasks. Most commonly-used attributes,
        +   * such as `id`, can be set with their dedicated methods. For example,
        +   * `nextButton.id('next')` sets an element's `id` attribute. Calling
        +   * `nextButton.attribute('id', 'next')` has the same effect.
        +   *
        +   * The first parameter, `attr`, is the attribute's name as a string. Calling
        +   * `myElement.attribute('align')` returns the attribute's current value as a
        +   * string or `null` if it hasn't been set.
        +   *
        +   * The second parameter, `value`, is optional. It's a string used to set the
        +   * attribute's value. For example, calling
        +   * `myElement.attribute('align', 'center')` sets the element's horizontal
        +   * alignment to `center`.
        +   *
        +   * @method attribute
        +   * @return {String} value of the attribute.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a container div element and place it at the top-left corner.
        +   *   let container = createDiv();
        +   *   container.position(0, 0);
        +   *
        +   *   // Create a paragraph element and place it within the container.
        +   *   // Set its horizontal alignment to "left".
        +   *   let p1 = createP('hi');
        +   *   p1.parent(container);
        +   *   p1.attribute('align', 'left');
        +   *
        +   *   // Create a paragraph element and place it within the container.
        +   *   // Set its horizontal alignment to "center".
        +   *   let p2 = createP('hi');
        +   *   p2.parent(container);
        +   *   p2.attribute('align', 'center');
        +   *
        +   *   // Create a paragraph element and place it within the container.
        +   *   // Set its horizontal alignment to "right".
        +   *   let p3 = createP('hi');
        +   *   p3.parent(container);
        +   *   p3.attribute('align', 'right');
        +   *
        +   *   describe('A gray square with the text "hi" written on three separate lines, each placed further to the right.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method attribute
        +   * @param  {String} attr       attribute to set.
        +   * @param  {String} value      value to assign to the attribute.
        +   * @chainable
        +   */
        +  attribute(attr, value) {
        +    //handling for checkboxes and radios to ensure options get
        +    //attributes not divs
        +    if (
        +      this.elt.firstChild != null &&
        +      (this.elt.firstChild.type === 'checkbox' ||
        +        this.elt.firstChild.type === 'radio')
        +    ) {
        +      if (typeof value === 'undefined') {
        +        return this.elt.firstChild.getAttribute(attr);
        +      } else {
        +        for (let i = 0; i < this.elt.childNodes.length; i++) {
        +          this.elt.childNodes[i].setAttribute(attr, value);
        +        }
        +      }
        +    } else if (typeof value === 'undefined') {
        +      return this.elt.getAttribute(attr);
        +    } else {
        +      this.elt.setAttribute(attr, value);
        +      return this;
        +    }
        +  }
        +
        +  /**
        +   * Removes an attribute from the element.
        +   *
        +   * The parameter `attr` is the attribute's name as a string. For example,
        +   * calling `myElement.removeAttribute('align')` removes its `align`
        +   * attribute if it's been set.
        +   *
        +   * @method removeAttribute
        +   * @param  {String} attr       attribute to remove.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let p;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a paragraph element and place it in the center of the canvas.
        +   *   // Set its "align" attribute to "center".
        +   *   p = createP('hi');
        +   *   p.position(0, 20);
        +   *   p.attribute('align', 'center');
        +   *
        +   *   describe('The text "hi" written in black at the center of a gray square. The text moves to the left edge when double-clicked.');
        +   * }
        +   *
        +   * // Remove the 'align' attribute when the user double-clicks the paragraph.
        +   * function doubleClicked() {
        +   *   p.removeAttribute('align');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  removeAttribute(attr) {
        +    if (
        +      this.elt.firstChild != null &&
        +      (this.elt.firstChild.type === 'checkbox' ||
        +        this.elt.firstChild.type === 'radio')
        +    ) {
        +      for (let i = 0; i < this.elt.childNodes.length; i++) {
        +        this.elt.childNodes[i].removeAttribute(attr);
        +      }
        +    }
        +    this.elt.removeAttribute(attr);
        +    return this;
        +  }
        +
        +  /**
        +   * Returns or sets the element's value.
        +   *
        +   * Calling `myElement.value()` returns the element's current value.
        +   *
        +   * The parameter, `value`, is an optional number or string. If provided,
        +   * as in `myElement.value(123)`, it's used to set the element's value.
        +   *
        +   * @method value
        +   * @return {String|Number} value of the element.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let input;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a text input and place it beneath the canvas.
        +   *   // Set its default value to "hello".
        +   *   input = createInput('hello');
        +   *   input.position(0, 100);
        +   *
        +   *   describe('The text from an input box is displayed on a gray square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Use the input's value to display a message.
        +   *   let msg = input.value();
        +   *   text(msg, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let input;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a text input and place it beneath the canvas.
        +   *   // Set its default value to "hello".
        +   *   input = createInput('hello');
        +   *   input.position(0, 100);
        +   *
        +   *   describe('The text from an input box is displayed on a gray square. The text resets to "hello" when the user double-clicks the square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Use the input's value to display a message.
        +   *   let msg = input.value();
        +   *   text(msg, 0, 55);
        +   * }
        +   *
        +   * // Reset the input's value.
        +   * function doubleClicked() {
        +   *   input.value('hello');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method value
        +   * @param  {String|Number}     value
        +   * @chainable
        +   */
        +  value(...args) {
        +    if (args.length > 0) {
        +      this.elt.value = args[0];
        +      return this;
        +    } else {
        +      if (this.elt.type === 'range') {
        +        return parseFloat(this.elt.value);
        +      } else return this.elt.value;
        +    }
        +  }
        +
        +  /**
        +   * Calls a function when the mouse is pressed over the element.
        +   *
        +   * Calling `myElement.mousePressed(false)` disables the function.
        +   *
        +   * Note: Some mobile browsers may also trigger this event when the element
        +   * receives a quick tap.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the mouse is
        +   *                                pressed over the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call randomColor() when the canvas
        +   *   // is pressed.
        +   *   cnv.mousePressed(randomColor);
        +   *
        +   *   describe('A gray square changes color when the mouse is pressed.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  mousePressed(fxn) {
        +    // Prepend the mouse property setters to the event-listener.
        +    // This is required so that mouseButton is set correctly prior to calling the callback (fxn).
        +    // For details, see https://github.com/processing/p5.js/issues/3087.
        +    const eventPrependedFxn = function (event) {
        +      this._pInst.mouseIsPressed = true;
        +      this._pInst._setMouseButton(event);
        +      // Pass along the return-value of the callback:
        +      return fxn.call(this, event);
        +    };
        +    // Pass along the event-prepended form of the callback.
        +    Element._adjustListener('mousedown', eventPrependedFxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the mouse is pressed twice over the element.
        +   *
        +   * Calling `myElement.doubleClicked(false)` disables the function.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the mouse is
        +   *                                double clicked over the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call randomColor() when the
        +   *   // canvas is double-clicked.
        +   *   cnv.doubleClicked(randomColor);
        +   *
        +   *   describe('A gray square changes color when the user double-clicks the canvas.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  doubleClicked(fxn) {
        +    Element._adjustListener('dblclick', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the mouse wheel scrolls over the element.
        +   *
        +   * The callback function, `fxn`, is passed an `event` object. `event` has
        +   * two numeric properties, `deltaY` and `deltaX`. `event.deltaY` is
        +   * negative if the mouse wheel rotates away from the user. It's positive if
        +   * the mouse wheel rotates toward the user. `event.deltaX` is positive if
        +   * the mouse wheel moves to the right. It's negative if the mouse wheel moves
        +   * to the left.
        +   *
        +   * Calling `myElement.mouseWheel(false)` disables the function.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the mouse wheel is
        +   *                                scrolled over the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call randomColor() when the
        +   *   // mouse wheel moves.
        +   *   cnv.mouseWheel(randomColor);
        +   *
        +   *   describe('A gray square changes color when the user scrolls the mouse wheel over the canvas.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call changeBackground() when the
        +   *   // mouse wheel moves.
        +   *   cnv.mouseWheel(changeBackground);
        +   *
        +   *   describe('A gray square. When the mouse wheel scrolls over the square, it changes color and displays shapes.');
        +   * }
        +   *
        +   * function changeBackground(event) {
        +   *   // Change the background color
        +   *   // based on deltaY.
        +   *   if (event.deltaY > 0) {
        +   *     background('deeppink');
        +   *   } else if (event.deltaY < 0) {
        +   *     background('cornflowerblue');
        +   *   } else {
        +   *     background(200);
        +   *   }
        +   *
        +   *   // Draw a shape based on deltaX.
        +   *   if (event.deltaX > 0) {
        +   *     circle(50, 50, 20);
        +   *   } else if (event.deltaX < 0) {
        +   *     square(40, 40, 20);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  mouseWheel(fxn) {
        +    Element._adjustListener('wheel', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the mouse is released over the element.
        +   *
        +   * Calling `myElement.mouseReleased(false)` disables the function.
        +   *
        +   * Note: Some mobile browsers may also trigger this event when the element
        +   * receives a quick tap.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the mouse is
        +   *                                pressed over the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call randomColor() when a
        +   *   // mouse press ends.
        +   *   cnv.mouseReleased(randomColor);
        +   *
        +   *   describe('A gray square changes color when the user releases a mouse press.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  mouseReleased(fxn) {
        +    Element._adjustListener('mouseup', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the mouse is pressed and released over the element.
        +   *
        +   * Calling `myElement.mouseReleased(false)` disables the function.
        +   *
        +   * Note: Some mobile browsers may also trigger this event when the element
        +   * receives a quick tap.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the mouse is
        +   *                                pressed and released over the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call randomColor() when a
        +   *   // mouse press ends.
        +   *   cnv.mouseClicked(randomColor);
        +   *
        +   *   describe('A gray square changes color when the user releases a mouse press.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  mouseClicked(fxn) {
        +    Element._adjustListener('click', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the mouse moves over the element.
        +   *
        +   * Calling `myElement.mouseMoved(false)` disables the function.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the mouse
        +   *                                moves over the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call randomColor() when the
        +   *   // mouse moves.
        +   *   cnv.mouseMoved(randomColor);
        +   *
        +   *   describe('A gray square changes color when the mouse moves over the canvas.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  mouseMoved(fxn) {
        +    Element._adjustListener('mousemove', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the mouse moves onto the element.
        +   *
        +   * Calling `myElement.mouseOver(false)` disables the function.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the mouse
        +   *                                moves onto the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call randomColor() when the
        +   *   // mouse moves onto the canvas.
        +   *   cnv.mouseOver(randomColor);
        +   *
        +   *   describe('A gray square changes color when the mouse moves onto the canvas.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  mouseOver(fxn) {
        +    Element._adjustListener('mouseover', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the mouse moves off the element.
        +   *
        +   * Calling `myElement.mouseOut(false)` disables the function.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the mouse
        +   *                                moves off the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call randomColor() when the
        +   *   // mouse moves off the canvas.
        +   *   cnv.mouseOut(randomColor);
        +   *
        +   *   describe('A gray square changes color when the mouse moves off the canvas.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  mouseOut(fxn) {
        +    Element._adjustListener('mouseout', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the element is touched.
        +   *
        +   * Calling `myElement.touchStarted(false)` disables the function.
        +   *
        +   * Note: Touch functions only work on mobile devices.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the touch
        +   *                                starts.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call randomColor() when the
        +   *   // user touches the canvas.
        +   *   cnv.touchStarted(randomColor);
        +   *
        +   *   describe('A gray square changes color when the user touches the canvas.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  touchStarted(fxn) {
        +    Element._adjustListener('touchstart', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the user touches the element and moves.
        +   *
        +   * Calling `myElement.touchMoved(false)` disables the function.
        +   *
        +   * Note: Touch functions only work on mobile devices.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the touch
        +   *                                moves over the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call randomColor() when the
        +   *   // user touches the canvas
        +   *   // and moves.
        +   *   cnv.touchMoved(randomColor);
        +   *
        +   *   describe('A gray square changes color when the user touches the canvas and moves.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  touchMoved(fxn) {
        +    Element._adjustListener('touchmove', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the user stops touching the element.
        +   *
        +   * Calling `myElement.touchMoved(false)` disables the function.
        +   *
        +   * Note: Touch functions only work on mobile devices.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the touch
        +   *                                ends.
        +   *                                `false` disables the function.
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call randomColor() when the
        +   *   // user touches the canvas,
        +   *   // then lifts their finger.
        +   *   cnv.touchEnded(randomColor);
        +   *
        +   *   describe('A gray square changes color when the user touches the canvas, then lifts their finger.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  touchEnded(fxn) {
        +    Element._adjustListener('touchend', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when a file is dragged over the element.
        +   *
        +   * Calling `myElement.dragOver(false)` disables the function.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the file is
        +   *                                dragged over the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Drag a file over the canvas to test.
        +   *
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call helloFile() when a
        +   *   // file is dragged over
        +   *   // the canvas.
        +   *   cnv.dragOver(helloFile);
        +   *
        +   *   describe('A gray square. The text "hello, file" appears when a file is dragged over the square.');
        +   * }
        +   *
        +   * function helloFile() {
        +   *   text('hello, file', 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  dragOver(fxn) {
        +    Element._adjustListener('dragover', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when a file is dragged off the element.
        +   *
        +   * Calling `myElement.dragLeave(false)` disables the function.
        +   *
        +   * @param  {Function|Boolean} fxn function to call when the file is
        +   *                                dragged off the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Drag a file over, then off
        +   * // the canvas to test.
        +   *
        +   * function setup() {
        +   *   // Create a canvas element and
        +   *   // assign it to cnv.
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call byeFile() when a
        +   *   // file is dragged over,
        +   *   // then off the canvas.
        +   *   cnv.dragLeave(byeFile);
        +   *
        +   *   describe('A gray square. The text "bye, file" appears when a file is dragged over, then off the square.');
        +   * }
        +   *
        +   * function byeFile() {
        +   *   text('bye, file', 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  dragLeave(fxn) {
        +    Element._adjustListener('dragleave', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the element changes.
        +   *
        +   * Calling `myElement.changed(false)` disables the function.
        +   *
        +   * @method changed
        +   * @param  {Function|Boolean} fxn function to call when the element changes.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let dropdown;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a dropdown menu and add a few color options.
        +   *   dropdown = createSelect();
        +   *   dropdown.position(0, 0);
        +   *   dropdown.option('red');
        +   *   dropdown.option('green');
        +   *   dropdown.option('blue');
        +   *
        +   *   // Call paintBackground() when the color option changes.
        +   *   dropdown.changed(paintBackground);
        +   *
        +   *   describe('A gray square with a dropdown menu at the top. The square changes color when an option is selected.');
        +   * }
        +   *
        +   * // Paint the background with the selected color.
        +   * function paintBackground() {
        +   *   let c = dropdown.value();
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let checkbox;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a checkbox and place it beneath the canvas.
        +   *   checkbox = createCheckbox(' circle');
        +   *   checkbox.position(0, 100);
        +   *
        +   *   // Call repaint() when the checkbox changes.
        +   *   checkbox.changed(repaint);
        +   *
        +   *   describe('A gray square with a checkbox underneath it that says "circle". A white circle appears when the box is checked and disappears otherwise.');
        +   * }
        +   *
        +   * // Paint the background gray and determine whether to draw a circle.
        +   * function repaint() {
        +   *   background(200);
        +   *   if (checkbox.checked() === true) {
        +   *     circle(50, 50, 30);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  changed(fxn) {
        +    Element._adjustListener('change', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the element receives input.
        +   *
        +   * `myElement.input()` is often used to with text inputs and sliders. Calling
        +   * `myElement.input(false)` disables the function.
        +   *
        +   * @method input
        +   * @param  {Function|Boolean} fxn function to call when input is detected within
        +   *                                the element.
        +   *                                `false` disables the function.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let slider;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a slider and place it beneath the canvas.
        +   *   slider = createSlider(0, 255, 200);
        +   *   slider.position(0, 100);
        +   *
        +   *   // Call repaint() when the slider changes.
        +   *   slider.input(repaint);
        +   *
        +   *   describe('A gray square with a range slider underneath it. The background changes shades of gray when the slider is moved.');
        +   * }
        +   *
        +   * // Paint the background using slider's value.
        +   * function repaint() {
        +   *   let g = slider.value();
        +   *   background(g);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let input;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an input and place it beneath the canvas.
        +   *   input = createInput('');
        +   *   input.position(0, 100);
        +   *
        +   *   // Call repaint() when input is detected.
        +   *   input.input(repaint);
        +   *
        +   *   describe('A gray square with a text input bar beneath it. Any text written in the input appears in the middle of the square.');
        +   * }
        +   *
        +   * // Paint the background gray and display the input's value.
        +   * function repaint() {
        +   *   background(200);
        +   *   let msg = input.value();
        +   *   text(msg, 5, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  input(fxn) {
        +    Element._adjustListener('input', fxn, this);
        +    return this;
        +  }
        +
        +  /**
        +   * Calls a function when the user drops a file on the element.
        +   *
        +   * The first parameter, `callback`, is a function to call once the file loads.
        +   * The callback function should have one parameter, `file`, that's a
        +   * <a href="#/p5.File">p5.File</a> object. If the user drops multiple files on
        +   * the element, `callback`, is called once for each file.
        +   *
        +   * The second parameter, `fxn`, is a function to call when the browser detects
        +   * one or more dropped files. The callback function should have one
        +   * parameter, `event`, that's a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent">DragEvent</a>.
        +   *
        +   * @method drop
        +   * @param  {Function} callback  called when a file loads. Called once for each file dropped.
        +   * @param  {Function} [fxn]     called once when any files are dropped.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Drop an image on the canvas to view
        +   * // this example.
        +   * let img;
        +   *
        +   * function setup() {
        +   *   let c = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call handleFile() when a file that's dropped on the canvas has loaded.
        +   *   c.drop(handleFile);
        +   *
        +   *   describe('A gray square. When the user drops an image on the square, it is displayed.');
        +   * }
        +   *
        +   * // Remove the existing image and display the new one.
        +   * function handleFile(file) {
        +   *   // Remove the current image, if any.
        +   *   if (img) {
        +   *     img.remove();
        +   *   }
        +   *
        +   *   // Create an <img> element with the
        +   *   // dropped file.
        +   *   img = createImg(file.data, '');
        +   *   img.hide();
        +   *
        +   *   // Draw the image.
        +   *   image(img, 0, 0, width, height);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Drop an image on the canvas to view
        +   * // this example.
        +   * let img;
        +   * let msg;
        +   *
        +   * function setup() {
        +   *   let c = createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Call functions when the user drops a file on the canvas
        +   *   // and when the file loads.
        +   *   c.drop(handleFile, handleDrop);
        +   *
        +   *   describe('A gray square. When the user drops an image on the square, it is displayed. The id attribute of canvas element is also displayed.');
        +   * }
        +   *
        +   * // Display the image when it loads.
        +   * function handleFile(file) {
        +   *   // Remove the current image, if any.
        +   *   if (img) {
        +   *     img.remove();
        +   *   }
        +   *
        +   *   // Create an img element with the dropped file.
        +   *   img = createImg(file.data, '');
        +   *   img.hide();
        +   *
        +   *   // Draw the image.
        +   *   image(img, 0, 0, width, height);
        +   * }
        +   *
        +   * // Display the file's name when it loads.
        +   * function handleDrop(event) {
        +   *   // Remove current paragraph, if any.
        +   *   if (msg) {
        +   *     msg.remove();
        +   *   }
        +   *
        +   *   // Use event to get the drop target's id.
        +   *   let id = event.target.id;
        +   *
        +   *   // Write the canvas' id beneath it.
        +   *   msg = createP(id);
        +   *   msg.position(0, 100);
        +   *
        +   *   // Set the font color randomly for each drop.
        +   *   let c = random(['red', 'green', 'blue']);
        +   *   msg.style('color', c);
        +   *   msg.style('font-size', '12px');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  drop(callback, fxn) {
        +    // Is the file stuff supported?
        +    if (window.File && window.FileReader && window.FileList && window.Blob) {
        +      if (!this._dragDisabled) {
        +        this._dragDisabled = true;
        +
        +        const preventDefault = function (evt) {
        +          evt.preventDefault();
        +        };
        +
        +        // If you want to be able to drop you've got to turn off
        +        // a lot of default behavior.
        +        // avoid `attachListener` here, since it overrides other handlers.
        +        this.elt.addEventListener('dragover', preventDefault);
        +
        +        // If this is a drag area we need to turn off the default behavior
        +        this.elt.addEventListener('dragleave', preventDefault);
        +      }
        +
        +      // Deal with the files
        +      Element._attachListener(
        +        'drop',
        +        function (evt) {
        +          evt.preventDefault();
        +          // Call the second argument as a callback that receives the raw drop event
        +          if (typeof fxn === 'function') {
        +            fxn.call(this, evt);
        +          }
        +          // A FileList
        +          const files = evt.dataTransfer.files;
        +
        +          // Load each one and trigger the callback
        +          for (const f of files) {
        +            File._load(f, callback);
        +          }
        +        },
        +        this
        +      );
        +    } else {
        +      console.log('The File APIs are not fully supported in this browser.');
        +    }
        +
        +    return this;
        +  }
        +
        +  /**
        +   * Makes the element draggable.
        +   *
        +   * The parameter, `elmnt`, is optional. If another
        +   * <a href="#/p5.Element">p5.Element</a> object is passed, as in
        +   * `myElement.draggable(otherElement)`, the other element will become draggable.
        +   *
        +   * @method draggable
        +   * @param  {p5.Element} [elmnt]  another <a href="#/p5.Element">p5.Element</a>.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let stickyNote;
        +   * let textInput;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a div element and style it.
        +   *   stickyNote = createDiv('Note');
        +   *   stickyNote.position(5, 5);
        +   *   stickyNote.size(80, 20);
        +   *   stickyNote.style('font-size', '16px');
        +   *   stickyNote.style('font-family', 'Comic Sans MS');
        +   *   stickyNote.style('background', 'orchid');
        +   *   stickyNote.style('padding', '5px');
        +   *
        +   *   // Make the note draggable.
        +   *   stickyNote.draggable();
        +   *
        +   *   // Create a panel div and style it.
        +   *   let panel = createDiv('');
        +   *   panel.position(5, 40);
        +   *   panel.size(80, 50);
        +   *   panel.style('background', 'orchid');
        +   *   panel.style('font-size', '16px');
        +   *   panel.style('padding', '5px');
        +   *   panel.style('text-align', 'center');
        +   *
        +   *   // Make the panel draggable.
        +   *   panel.draggable();
        +   *
        +   *   // Create a text input and style it.
        +   *   textInput = createInput('Note');
        +   *   textInput.size(70);
        +   *
        +   *   // Add the input to the panel.
        +   *   textInput.parent(panel);
        +   *
        +   *   // Call handleInput() when text is input.
        +   *   textInput.input(handleInput);
        +   *
        +   *   describe(
        +   *     'A gray square with two purple rectangles that move when dragged. The top rectangle displays the text that is typed into the bottom rectangle.'
        +   *   );
        +   * }
        +   *
        +   * // Update stickyNote's HTML when text is input.
        +   * function handleInput() {
        +   *   stickyNote.html(textInput.value());
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  draggable(elmMove) {
        +    let isTouch = 'ontouchstart' in window;
        +
        +    let x = 0,
        +      y = 0,
        +      px = 0,
        +      py = 0,
        +      elmDrag,
        +      dragMouseDownEvt = isTouch ? 'touchstart' : 'mousedown',
        +      closeDragElementEvt = isTouch ? 'touchend' : 'mouseup',
        +      elementDragEvt = isTouch ? 'touchmove' : 'mousemove';
        +
        +    if (elmMove === undefined) {
        +      elmMove = this.elt;
        +      elmDrag = elmMove;
        +    } else if (elmMove !== this.elt && elmMove.elt !== this.elt) {
        +      elmMove = elmMove.elt;
        +      elmDrag = this.elt;
        +    }
        +
        +    elmDrag.addEventListener(dragMouseDownEvt, dragMouseDown, false);
        +    elmDrag.style.cursor = 'move';
        +
        +    function dragMouseDown(e) {
        +      e = e || window.event;
        +
        +      if (isTouch) {
        +        const touches = e.changedTouches;
        +        px = parseInt(touches[0].clientX);
        +        py = parseInt(touches[0].clientY);
        +      } else {
        +        px = parseInt(e.clientX);
        +        py = parseInt(e.clientY);
        +      }
        +
        +      document.addEventListener(closeDragElementEvt, closeDragElement, false);
        +      document.addEventListener(elementDragEvt, elementDrag, false);
        +      return false;
        +    }
        +
        +    function elementDrag(e) {
        +      e = e || window.event;
        +
        +      if (isTouch) {
        +        const touches = e.changedTouches;
        +        x = px - parseInt(touches[0].clientX);
        +        y = py - parseInt(touches[0].clientY);
        +        px = parseInt(touches[0].clientX);
        +        py = parseInt(touches[0].clientY);
        +      } else {
        +        x = px - parseInt(e.clientX);
        +        y = py - parseInt(e.clientY);
        +        px = parseInt(e.clientX);
        +        py = parseInt(e.clientY);
        +      }
        +
        +      elmMove.style.left = elmMove.offsetLeft - x + 'px';
        +      elmMove.style.top = elmMove.offsetTop - y + 'px';
        +    }
        +
        +    function closeDragElement() {
        +      document.removeEventListener(closeDragElementEvt, closeDragElement, false);
        +      document.removeEventListener(elementDragEvt, elementDrag, false);
        +    }
        +
        +    return this;
        +  }
        +
        +  /**
        +   *
        +   * @private
        +   * @static
        +   * @param {String} ev
        +   * @param {Boolean|Function} fxn
        +   * @param {Element} ctx
        +   * @chainable
        +   * @alt
        +   * General handler for event attaching and detaching
        +   */
        +  static _adjustListener(ev, fxn, ctx) {
        +    if (fxn === false) {
        +      Element._detachListener(ev, ctx);
        +    } else {
        +      Element._attachListener(ev, fxn, ctx);
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   *
        +   * @private
        +   * @static
        +   * @param {String} ev
        +   * @param {Function} fxn
        +   * @param {Element} ctx
        +   */
        +  static _attachListener(ev, fxn, ctx) {
        +    // detach the old listener if there was one
        +    if (ctx._events[ev]) {
        +      Element._detachListener(ev, ctx);
        +    }
        +    const f = fxn.bind(ctx);
        +    ctx.elt.addEventListener(ev, f, false);
        +    ctx._events[ev] = f;
        +  }
        +
        +  /**
        +   *
        +   * @private
        +   * @static
        +   * @param {String} ev
        +   * @param {Element} ctx
        +   */
        +  static _detachListener(ev, ctx) {
        +    const f = ctx._events[ev];
        +    ctx.elt.removeEventListener(ev, f, false);
        +    ctx._events[ev] = null;
        +  }
        +};
        +
        +function element(p5, fn){
        +  /**
        +   * A class to describe an
        +   * <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started" target="_blank">HTML element</a>.
        +   *
        +   * Sketches can use many elements. Common elements include the drawing canvas,
        +   * buttons, sliders, webcam feeds, and so on.
        +   *
        +   * All elements share the methods of the `p5.Element` class. They're created
        +   * with functions such as <a href="#/p5/createCanvas">createCanvas()</a> and
        +   * <a href="#/p5/createButton">createButton()</a>.
        +   *
        +   * @class p5.Element
        +   * @param {HTMLElement} elt wrapped DOM element.
        +   * @param {p5} [pInst] pointer to p5 instance.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a button element and
        +   *   // place it beneath the canvas.
        +   *   let btn = createButton('change');
        +   *   btn.position(0, 100);
        +   *
        +   *   // Call randomColor() when
        +   *   // the button is pressed.
        +   *   btn.mousePressed(randomColor);
        +   *
        +   *   describe('A gray square with a button that says "change" beneath it. The square changes color when the user presses the button.');
        +   * }
        +   *
        +   * // Paint the background either
        +   * // red, yellow, blue, or green.
        +   * function randomColor() {
        +   *   let c = random(['red', 'yellow', 'blue', 'green']);
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  p5.Element = Element;
        +}
        +
        +export default element;
        +export { Element };
        +
        +if(typeof p5 !== 'undefined'){
        +  element(p5, p5.prototype);
        +}
        diff --git a/src/dom/p5.File.js b/src/dom/p5.File.js
        new file mode 100644
        index 0000000000..7852d4f435
        --- /dev/null
        +++ b/src/dom/p5.File.js
        @@ -0,0 +1,388 @@
        +/**
        + * @module DOM
        + * @submodule DOM
        + * @for p5.Element
        + */
        +
        +import { XML } from '../io/p5.XML';
        +
        +class File {
        +  constructor(file, pInst) {
        +    this.file = file;
        +
        +    this._pInst = pInst;
        +
        +    // Splitting out the file type into two components
        +    // This makes determining if image or text etc simpler
        +    const typeList = file.type.split('/');
        +    this.type = typeList[0];
        +    this.subtype = typeList[1];
        +    this.name = file.name;
        +    this.size = file.size;
        +    this.data = undefined;
        +  }
        +
        +
        +  static _createLoader(theFile, callback) {
        +    const reader = new FileReader();
        +    reader.onload = function (e) {
        +      const p5file = new File(theFile);
        +      if (p5file.file.type === 'application/json') {
        +        // Parse JSON and store the result in data
        +        p5file.data = JSON.parse(e.target.result);
        +      } else if (p5file.file.type === 'text/xml') {
        +        // Parse XML, wrap it in p5.XML and store the result in data
        +        const parser = new DOMParser();
        +        const xml = parser.parseFromString(e.target.result, 'text/xml');
        +        p5file.data = new XML(xml.documentElement);
        +      } else {
        +        p5file.data = e.target.result;
        +      }
        +      callback(p5file);
        +    };
        +    return reader;
        +  }
        +
        +  static _load(f, callback) {
        +    // Text or data?
        +    // This should likely be improved
        +    if (/^text\//.test(f.type) || f.type === 'application/json') {
        +      File._createLoader(f, callback).readAsText(f);
        +    } else if (!/^(video|audio)\//.test(f.type)) {
        +      File._createLoader(f, callback).readAsDataURL(f);
        +    } else {
        +      const file = new File(f);
        +      file.data = URL.createObjectURL(f);
        +      callback(file);
        +    }
        +  }
        +}
        +
        +function file(p5, fn){
        +  /**
        +   * A class to describe a file.
        +   *
        +   * `p5.File` objects are used by
        +   * <a href="#/p5.Element/drop">myElement.drop()</a> and
        +   * created by
        +   * <a href="#/p5/createFileInput">createFileInput</a>.
        +   *
        +   * @class p5.File
        +   * @param {File} file wrapped file.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Use the file input to load a
        +   * // file and display its info.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a file input and place it beneath the canvas.
        +   *   // Call displayInfo() when the file loads.
        +   *   let input = createFileInput(displayInfo);
        +   *   input.position(0, 100);
        +   *
        +   *   describe('A gray square with a file input beneath it. If the user loads a file, its info is written in black.');
        +   * }
        +   *
        +   * // Display the p5.File's info once it loads.
        +   * function displayInfo(file) {
        +   *   background(200);
        +   *
        +   *   // Display the p5.File's name.
        +   *   text(file.name, 10, 10, 80, 40);
        +   *
        +   *   // Display the p5.File's type and subtype.
        +   *   text(`${file.type}/${file.subtype}`, 10, 70);
        +   *
        +   *   // Display the p5.File's size in bytes.
        +   *   text(file.size, 10, 90);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Use the file input to select an image to
        +   * // load and display.
        +   * let img;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a file input and place it beneath the canvas.
        +   *   // Call handleImage() when the file image loads.
        +   *   let input = createFileInput(handleImage);
        +   *   input.position(0, 100);
        +   *
        +   *   describe('A gray square with a file input beneath it. If the user selects an image file to load, it is displayed on the square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the image if it's ready.
        +   *   if (img) {
        +   *     image(img, 0, 0, width, height);
        +   *   }
        +   * }
        +   *
        +   * // Use the p5.File's data once it loads.
        +   * function handleImage(file) {
        +   *   // Check the p5.File's type.
        +   *   if (file.type === 'image') {
        +   *     // Create an image using using the p5.File's data.
        +   *     img = createImg(file.data, '');
        +   *
        +   *     // Hide the image element so it doesn't appear twice.
        +   *     img.hide();
        +   *   } else {
        +   *     img = null;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  p5.File = File;
        +
        +  /**
        +   * Underlying
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/File" target="_blank">File</a>
        +   * object. All `File` properties and methods are accessible.
        +   *
        +   * @for p5.File
        +   * @property file
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Use the file input to load a
        +   * // file and display its info.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a file input and place it beneath the canvas.
        +   *   // Call displayInfo() when the file loads.
        +   *   let input = createFileInput(displayInfo);
        +   *   input.position(0, 100);
        +   *
        +   *   describe('A gray square with a file input beneath it. If the user loads a file, its info is written in black.');
        +   * }
        +   *
        +   * // Use the p5.File once it loads.
        +   * function displayInfo(file) {
        +   *   background(200);
        +   *
        +   *   // Display the p5.File's name.
        +   *   text(file.name, 10, 10, 80, 40);
        +   *
        +   *   // Display the p5.File's type and subtype.
        +   *   text(`${file.type}/${file.subtype}`, 10, 70);
        +   *
        +   *   // Display the p5.File's size in bytes.
        +   *   text(file.size, 10, 90);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * The file
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME type</a>
        +   * as a string.
        +   *
        +   * For example, `'image'` and `'text'` are both MIME types.
        +   *
        +   * @for p5.File
        +   * @property type
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Use the file input to load a file and display its info.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a file input and place it beneath the canvas.
        +   *   // Call displayType() when the file loads.
        +   *   let input = createFileInput(displayType);
        +   *   input.position(0, 100);
        +   *
        +   *   describe('A gray square with a file input beneath it. If the user loads a file, its type is written in black.');
        +   * }
        +   *
        +   * // Display the p5.File's type once it loads.
        +   * function displayType(file) {
        +   *   background(200);
        +   *
        +   *   // Display the p5.File's type.
        +   *   text(`This is file's type is: ${file.type}`, 10, 10, 80, 80);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * The file subtype as a string.
        +   *
        +   * For example, a file with an `'image'`
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank">MIME type</a>
        +   * may have a subtype such as ``png`` or ``jpeg``.
        +   *
        +   * @property subtype
        +   * @for p5.File
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Use the file input to load a
        +   * // file and display its info.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a file input and place it beneath the canvas.
        +   *   // Call displaySubtype() when the file loads.
        +   *   let input = createFileInput(displaySubtype);
        +   *   input.position(0, 100);
        +   *
        +   *   describe('A gray square with a file input beneath it. If the user loads a file, its subtype is written in black.');
        +   * }
        +   *
        +   * // Display the p5.File's type once it loads.
        +   * function displaySubtype(file) {
        +   *   background(200);
        +   *
        +   *   // Display the p5.File's subtype.
        +   *   text(`This is file's subtype is: ${file.subtype}`, 10, 10, 80, 80);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * The file name as a string.
        +   *
        +   * @property name
        +   * @for p5.File
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Use the file input to load a
        +   * // file and display its info.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a file input and place it beneath the canvas.
        +   *   // Call displayName() when the file loads.
        +   *   let input = createFileInput(displayName);
        +   *   input.position(0, 100);
        +   *
        +   *   describe('A gray square with a file input beneath it. If the user loads a file, its name is written in black.');
        +   * }
        +   *
        +   * // Display the p5.File's name once it loads.
        +   * function displayName(file) {
        +   *   background(200);
        +   *
        +   *   // Display the p5.File's name.
        +   *   text(`This is file's name is: ${file.name}`, 10, 10, 80, 80);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * The number of bytes in the file.
        +   *
        +   * @property size
        +   * @for p5.File
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Use the file input to load a file and display its info.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a file input and place it beneath the canvas.
        +   *   // Call displaySize() when the file loads.
        +   *   let input = createFileInput(displaySize);
        +   *   input.position(0, 100);
        +   *
        +   *   describe('A gray square with a file input beneath it. If the user loads a file, its size in bytes is written in black.');
        +   * }
        +   *
        +   * // Display the p5.File's size in bytes once it loads.
        +   * function displaySize(file) {
        +   *   background(200);
        +   *
        +   *   // Display the p5.File's size.
        +   *   text(`This is file has ${file.size} bytes.`, 10, 10, 80, 80);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * A string containing the file's data.
        +   *
        +   * Data can be either image data, text contents, or a parsed object in the
        +   * case of JSON and <a href="#/p5.XML">p5.XML</a> objects.
        +   *
        +   * @property data
        +   * @for p5.File
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Use the file input to load a file and display its info.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a file input and place it beneath the canvas.
        +   *   // Call displayData() when the file loads.
        +   *   let input = createFileInput(displayData);
        +   *   input.position(0, 100);
        +   *
        +   *   describe('A gray square with a file input beneath it. If the user loads a file, its data is written in black.');
        +   * }
        +   *
        +   * // Display the p5.File's data once it loads.
        +   * function displayData(file) {
        +   *   background(200);
        +   *
        +   *   // Display the p5.File's data, which looks like a random string of characters.
        +   *   text(file.data, 10, 10, 80, 80);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +}
        +
        +export default file;
        +export { File };
        +
        +if(typeof p5 !== 'undefined'){
        +  file(p5, p5.prototype);
        +}
        diff --git a/src/dom/p5.MediaElement.js b/src/dom/p5.MediaElement.js
        new file mode 100644
        index 0000000000..fa68e50cee
        --- /dev/null
        +++ b/src/dom/p5.MediaElement.js
        @@ -0,0 +1,1825 @@
        +/**
        + * @module DOM
        + * @submodule DOM
        + * @for p5.Element
        + */
        +
        +import { Element } from './p5.Element';
        +
        +class MediaElement extends Element {
        +  constructor(elt, pInst) {
        +    super(elt, pInst);
        +
        +    const self = this;
        +    this.elt.crossOrigin = 'anonymous';
        +
        +    this._prevTime = 0;
        +    this._cueIDCounter = 0;
        +    this._cues = [];
        +    this.pixels = [];
        +    this._pixelsState = this;
        +    this._pixelDensity = 1;
        +    this._modified = false;
        +
        +    // Media has an internal canvas that is used when drawing it to the main
        +    // canvas. It will need to be updated each frame as the video itself plays.
        +    // We don't want to update it every time we draw, however, in case the user
        +    // has used load/updatePixels. To handle this, we record the frame drawn to
        +    // the internal canvas so we only update it if the frame has changed.
        +    this._frameOnCanvas = -1;
        +
        +    Object.defineProperty(self, 'src', {
        +      get() {
        +        const firstChildSrc = self.elt.children[0].src;
        +        const srcVal = self.elt.src === window.location.href ? '' : self.elt.src;
        +        const ret =
        +          firstChildSrc === window.location.href ? srcVal : firstChildSrc;
        +        return ret;
        +      },
        +      set(newValue) {
        +        for (let i = 0; i < self.elt.children.length; i++) {
        +          self.elt.removeChild(self.elt.children[i]);
        +        }
        +        const source = document.createElement('source');
        +        source.src = newValue;
        +        elt.appendChild(source);
        +        self.elt.src = newValue;
        +        self.modified = true;
        +      }
        +    });
        +
        +    // private _onended callback, set by the method: onended(callback)
        +    self._onended = function () { };
        +    self.elt.onended = function () {
        +      self._onended(self);
        +    };
        +  }
        +
        +
        +  /**
        +   * Plays audio or video from a media element.
        +   *
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let beat;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display a message.
        +   *   text('Click to play', 50, 50);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   beat = createAudio('assets/beat.mp3');
        +   *
        +   *   describe('The text "Click to play" written in black on a gray background. A beat plays when the user clicks the square.');
        +   * }
        +   *
        +   * // Play the beat when the user presses the mouse.
        +   * function mousePressed() {
        +   *   beat.play();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  play() {
        +    if (this.elt.currentTime === this.elt.duration) {
        +      this.elt.currentTime = 0;
        +    }
        +    let promise;
        +    if (this.elt.readyState > 1) {
        +      promise = this.elt.play();
        +    } else {
        +      // in Chrome, playback cannot resume after being stopped and must reload
        +      this.elt.load();
        +      promise = this.elt.play();
        +    }
        +    if (promise && promise.catch) {
        +      promise.catch(e => {
        +        // if it's an autoplay failure error
        +        if (e.name === 'NotAllowedError') {
        +          if (typeof IS_MINIFIED === 'undefined') {
        +            p5._friendlyAutoplayError(this.src);
        +          } else {
        +            console.error(e);
        +          }
        +        } else {
        +          // any other kind of error
        +          console.error('Media play method encountered an unexpected error', e);
        +        }
        +      });
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * Stops a media element and sets its current time to 0.
        +   *
        +   * Calling `media.play()` will restart playing audio/video from the beginning.
        +   *
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let beat;
        +   * let isStopped = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   beat = createAudio('assets/beat.mp3');
        +   *
        +   *   describe('The text "Click to start" written in black on a gray background. The beat starts or stops when the user presses the mouse.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display different instructions based on playback.
        +   *   if (isStopped === true) {
        +   *     text('Click to start', 50, 50);
        +   *   } else {
        +   *     text('Click to stop', 50, 50);
        +   *   }
        +   * }
        +   *
        +   * // Adjust playback when the user presses the mouse.
        +   * function mousePressed() {
        +   *   if (isStopped === true) {
        +   *     // If the beat is stopped, play it.
        +   *     beat.play();
        +   *     isStopped = false;
        +   *   } else {
        +   *     // If the beat is playing, stop it.
        +   *     beat.stop();
        +   *     isStopped = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  stop() {
        +    this.elt.pause();
        +    this.elt.currentTime = 0;
        +    return this;
        +  }
        +
        +  /**
        +   * Pauses a media element.
        +   *
        +   * Calling `media.play()` will resume playing audio/video from the moment it paused.
        +   *
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let beat;
        +   * let isPaused = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   beat = createAudio('assets/beat.mp3');
        +   *
        +   *   describe('The text "Click to play" written in black on a gray background. The beat plays or pauses when the user clicks the square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display different instructions based on playback.
        +   *   if (isPaused === true) {
        +   *     text('Click to play', 50, 50);
        +   *   } else {
        +   *     text('Click to pause', 50, 50);
        +   *   }
        +   * }
        +   *
        +   * // Adjust playback when the user presses the mouse.
        +   * function mousePressed() {
        +   *   if (isPaused === true) {
        +   *     // If the beat is paused,
        +   *     // play it.
        +   *     beat.play();
        +   *     isPaused = false;
        +   *   } else {
        +   *     // If the beat is playing,
        +   *     // pause it.
        +   *     beat.pause();
        +   *     isPaused = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  pause() {
        +    this.elt.pause();
        +    return this;
        +  }
        +
        +  /**
        +   * Plays the audio/video repeatedly in a loop.
        +   *
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let beat;
        +   * let isLooping = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   beat = createAudio('assets/beat.mp3');
        +   *
        +   *   describe('The text "Click to loop" written in black on a gray background. A beat plays repeatedly in a loop when the user clicks. The beat stops when the user clicks again.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display different instructions based on playback.
        +   *   if (isLooping === true) {
        +   *     text('Click to stop', 50, 50);
        +   *   } else {
        +   *     text('Click to loop', 50, 50);
        +   *   }
        +   * }
        +   *
        +   * // Adjust playback when the user presses the mouse.
        +   * function mousePressed() {
        +   *   if (isLooping === true) {
        +   *     // If the beat is looping, stop it.
        +   *     beat.stop();
        +   *     isLooping = false;
        +   *   } else {
        +   *     // If the beat is stopped, loop it.
        +   *     beat.loop();
        +   *     isLooping = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  loop() {
        +    this.elt.setAttribute('loop', true);
        +    this.play();
        +    return this;
        +  }
        +  /**
        +   * Stops the audio/video from playing in a loop.
        +   *
        +   * The media will stop when it finishes playing.
        +   *
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let beat;
        +   * let isPlaying = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   beat = createAudio('assets/beat.mp3');
        +   *
        +   *   describe('The text "Click to play" written in black on a gray background. A beat plays when the user clicks. The beat stops when the user clicks again.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display different instructions based on playback.
        +   *   if (isPlaying === true) {
        +   *     text('Click to stop', 50, 50);
        +   *   } else {
        +   *     text('Click to play', 50, 50);
        +   *   }
        +   * }
        +   *
        +   * // Adjust playback when the user presses the mouse.
        +   * function mousePressed() {
        +   *   if (isPlaying === true) {
        +   *     // If the beat is playing, stop it.
        +   *     beat.stop();
        +   *     isPlaying = false;
        +   *   } else {
        +   *     // If the beat is stopped, play it.
        +   *     beat.play();
        +   *     isPlaying = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  noLoop() {
        +    this.elt.removeAttribute('loop');
        +    return this;
        +  }
        +
        +  /**
        +   * Sets up logic to check that autoplay succeeded.
        +   *
        +   * @private
        +   */
        +  _setupAutoplayFailDetection() {
        +    const timeout = setTimeout(() => {
        +      if (typeof IS_MINIFIED === 'undefined') {
        +        p5._friendlyAutoplayError(this.src);
        +      } else {
        +        console.error(e);
        +      }
        +    }, 500);
        +    this.elt.addEventListener('play', () => clearTimeout(timeout), {
        +      passive: true,
        +      once: true
        +    });
        +  }
        +
        +  /**
        +   * Sets the audio/video to play once it's loaded.
        +   *
        +   * The parameter, `shouldAutoplay`, is optional. Calling
        +   * `media.autoplay()` without an argument causes the media to play
        +   * automatically. If `true` is passed, as in `media.autoplay(true)`, the
        +   * media will automatically play. If `false` is passed, as in
        +   * `media.autoPlay(false)`, it won't play automatically.
        +   *
        +   * @param {Boolean} [shouldAutoplay] whether the element should autoplay.
        +   * @chainable
        +   *
        +   * @example
        +   * <div class='notest'>
        +   * <code>
        +   * let video;
        +   *
        +   * function setup() {
        +   *   noCanvas();
        +   *
        +   *   // Call handleVideo() once the video loads.
        +   *   video = createVideo('assets/fingers.mov', handleVideo);
        +   *
        +   *   describe('A video of fingers walking on a treadmill.');
        +   * }
        +   *
        +   * // Set the video's size and play it.
        +   * function handleVideo() {
        +   *   video.size(100, 100);
        +   *   video.autoplay();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='notest'>
        +   * <code>
        +   * function setup() {
        +   *   noCanvas();
        +   *
        +   *   // Load a video, but don't play it automatically.
        +   *   let video = createVideo('assets/fingers.mov', handleVideo);
        +   *
        +   *   // Play the video when the user clicks on it.
        +   *   video.mousePressed(handlePress);
        +   *
        +   *   describe('An image of fingers on a treadmill. They start walking when the user double-clicks on them.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * // Set the video's size and playback mode.
        +   * function handleVideo() {
        +   *   video.size(100, 100);
        +   *   video.autoplay(false);
        +   * }
        +   *
        +   * // Play the video.
        +   * function handleClick() {
        +   *   video.play();
        +   * }
        +   */
        +  autoplay(val) {
        +    const oldVal = this.elt.getAttribute('autoplay');
        +    this.elt.setAttribute('autoplay', val);
        +    // if we turned on autoplay
        +    if (val && !oldVal) {
        +      // bind method to this scope
        +      const setupAutoplayFailDetection =
        +        () => this._setupAutoplayFailDetection();
        +      // if media is ready to play, schedule check now
        +      if (this.elt.readyState === 4) {
        +        setupAutoplayFailDetection();
        +      } else {
        +        // otherwise, schedule check whenever it is ready
        +        this.elt.addEventListener('canplay', setupAutoplayFailDetection, {
        +          passive: true,
        +          once: true
        +        });
        +      }
        +    }
        +
        +    return this;
        +  }
        +
        +  /**
        +   * Sets the audio/video volume.
        +   *
        +   * Calling `media.volume()` without an argument returns the current volume
        +   * as a number in the range 0 (off) to 1 (maximum).
        +   *
        +   * The parameter, `val`, is optional. It's a number that sets the volume
        +   * from 0 (off) to 1 (maximum). For example, calling `media.volume(0.5)`
        +   * sets the volume to half of its maximum.
        +   *
        +   * @return {Number} current volume.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let dragon;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   dragon = createAudio('assets/lucky_dragons.mp3');
        +   *
        +   *   // Show the default media controls.
        +   *   dragon.showControls();
        +   *
        +   *   describe('The text "Volume: V" on a gray square with media controls beneath it. The number "V" oscillates between 0 and 1 as the music plays.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Produce a number between 0 and 1.
        +   *   let n = 0.5 * sin(frameCount * 0.01) + 0.5;
        +   *
        +   *   // Use n to set the volume.
        +   *   dragon.volume(n);
        +   *
        +   *   // Get the current volume and display it.
        +   *   let v = dragon.volume();
        +   *
        +   *   // Round v to 1 decimal place for display.
        +   *   v = round(v, 1);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the volume.
        +   *   text(`Volume: ${v}`, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method volume
        +   * @param {Number}            val volume between 0.0 and 1.0.
        +   * @chainable
        +   */
        +  volume(val) {
        +    if (typeof val === 'undefined') {
        +      return this.elt.volume;
        +    } else {
        +      this.elt.volume = val;
        +    }
        +  }
        +
        +  /**
        +   * Sets the audio/video playback speed.
        +   *
        +   * The parameter, `val`, is optional. It's a number that sets the playback
        +   * speed. 1 plays the media at normal speed, 0.5 plays it at half speed, 2
        +   * plays it at double speed, and so on. -1 plays the media at normal speed
        +   * in reverse.
        +   *
        +   * Calling `media.speed()` returns the current speed as a number.
        +   *
        +   * Note: Not all browsers support backward playback. Even if they do,
        +   * playback might not be smooth.
        +   *
        +   * @return {Number} current playback speed.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let dragon;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   dragon = createAudio('assets/lucky_dragons.mp3');
        +   *
        +   *   // Show the default media controls.
        +   *   dragon.showControls();
        +   *
        +   *   describe('The text "Speed: S" on a gray square with media controls beneath it. The number "S" oscillates between 0 and 1 as the music plays.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Produce a number between 0 and 2.
        +   *   let n = sin(frameCount * 0.01) + 1;
        +   *
        +   *   // Use n to set the playback speed.
        +   *   dragon.speed(n);
        +   *
        +   *   // Get the current speed and display it.
        +   *   let s = dragon.speed();
        +   *
        +   *   // Round s to 1 decimal place for display.
        +   *   s = round(s, 1);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the speed.
        +   *   text(`Speed: ${s}`, 50, 50);
        +   * }
        +   * </code>
        +   */
        +  /**
        +   * @param {Number} speed  speed multiplier for playback.
        +   * @chainable
        +   */
        +  speed(val) {
        +    if (typeof val === 'undefined') {
        +      return this.presetPlaybackRate || this.elt.playbackRate;
        +    } else {
        +      if (this.loadedmetadata) {
        +        this.elt.playbackRate = val;
        +      } else {
        +        this.presetPlaybackRate = val;
        +      }
        +    }
        +  }
        +
        +  /**
        +   * Sets the media element's playback time.
        +   *
        +   * The parameter, `time`, is optional. It's a number that specifies the
        +   * time, in seconds, to jump to when playback begins.
        +   *
        +   * Calling `media.time()` without an argument returns the number of seconds
        +   * the audio/video has played.
        +   *
        +   * Note: Time resets to 0 when looping media restarts.
        +   *
        +   * @return {Number} current time (in seconds).
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let dragon;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   dragon = createAudio('assets/lucky_dragons.mp3');
        +   *
        +   *   // Show the default media controls.
        +   *   dragon.showControls();
        +   *
        +   *   describe('The text "S seconds" on a gray square with media controls beneath it. The number "S" increases as the song plays.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Get the current playback time.
        +   *   let s = dragon.time();
        +   *
        +   *   // Round s to 1 decimal place for display.
        +   *   s = round(s, 1);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the playback time.
        +   *   text(`${s} seconds`, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let dragon;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   dragon = createAudio('assets/lucky_dragons.mp3');
        +   *
        +   *   // Show the default media controls.
        +   *   dragon.showControls();
        +   *
        +   *   // Jump to 2 seconds to start.
        +   *   dragon.time(2);
        +   *
        +   *   describe('The text "S seconds" on a gray square with media controls beneath it. The number "S" increases as the song plays.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Get the current playback time.
        +   *   let s = dragon.time();
        +   *
        +   *   // Round s to 1 decimal place for display.
        +   *   s = round(s, 1);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the playback time.
        +   *   text(`${s} seconds`, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @param {Number} time time to jump to (in seconds).
        +   * @chainable
        +   */
        +  time(val) {
        +    if (typeof val === 'undefined') {
        +      return this.elt.currentTime;
        +    } else {
        +      this.elt.currentTime = val;
        +      return this;
        +    }
        +  }
        +
        +  /**
        +   * Returns the audio/video's duration in seconds.
        +   *
        +   * @return {Number} duration (in seconds).
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let dragon;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   dragon = createAudio('assets/lucky_dragons.mp3');
        +   *
        +   *   // Show the default media controls.
        +   *   dragon.showControls();
        +   *
        +   *   describe('The text "S seconds left" on a gray square with media controls beneath it. The number "S" decreases as the song plays.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the time remaining.
        +   *   let s = dragon.duration() - dragon.time();
        +   *
        +   *   // Round s to 1 decimal place for display.
        +   *   s = round(s, 1);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the time remaining.
        +   *   text(`${s} seconds left`, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  duration() {
        +    return this.elt.duration;
        +  }
        +  _ensureCanvas() {
        +    if (!this.canvas) {
        +      this.canvas = document.createElement('canvas');
        +      this.drawingContext = this.canvas.getContext('2d');
        +      this.setModified(true);
        +    }
        +
        +    // Don't update the canvas again if we have already updated the canvas with
        +    // the current frame
        +    const needsRedraw = this._frameOnCanvas !== this._pInst.frameCount;
        +    if (this.loadedmetadata && needsRedraw) {
        +      // wait for metadata for w/h
        +      if (this.canvas.width !== this.elt.width) {
        +        this.canvas.width = this.elt.width;
        +        this.canvas.height = this.elt.height;
        +        this.width = this.canvas.width;
        +        this.height = this.canvas.height;
        +      }
        +
        +      this.drawingContext.clearRect(
        +        0, 0, this.canvas.width, this.canvas.height);
        +
        +      if (this.flipped === true) {
        +        this.drawingContext.save();
        +        this.drawingContext.scale(-1, 1);
        +        this.drawingContext.translate(-this.canvas.width, 0);
        +      }
        +
        +      this.drawingContext.drawImage(
        +        this.elt,
        +        0,
        +        0,
        +        this.canvas.width,
        +        this.canvas.height
        +      );
        +
        +      if (this.flipped === true) {
        +        this.drawingContext.restore();
        +      }
        +
        +      this.setModified(true);
        +      this._frameOnCanvas = this._pInst.frameCount;
        +    }
        +  }
        +  loadPixels(...args) {
        +    this._ensureCanvas();
        +    return p5.Renderer2D.prototype.loadPixels.apply(this, args);
        +  }
        +  updatePixels(x, y, w, h) {
        +    if (this.loadedmetadata) {
        +      // wait for metadata
        +      this._ensureCanvas();
        +      p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
        +    }
        +    this.setModified(true);
        +    return this;
        +  }
        +  get(...args) {
        +    this._ensureCanvas();
        +    return p5.Renderer2D.prototype.get.apply(this, args);
        +  }
        +  _getPixel(...args) {
        +    this.loadPixels();
        +    return p5.Renderer2D.prototype._getPixel.apply(this, args);
        +  }
        +
        +  set(x, y, imgOrCol) {
        +    if (this.loadedmetadata) {
        +      // wait for metadata
        +      this._ensureCanvas();
        +      p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
        +      this.setModified(true);
        +    }
        +  }
        +  copy(...args) {
        +    this._ensureCanvas();
        +    fn.copy.apply(this, args);
        +  }
        +  mask(...args) {
        +    this.loadPixels();
        +    this.setModified(true);
        +    p5.Image.prototype.mask.apply(this, args);
        +  }
        +  /**
        +   * helper method for web GL mode to figure out if the element
        +   * has been modified and might need to be re-uploaded to texture
        +   * memory between frames.
        +   * @private
        +   * @return {boolean} a boolean indicating whether or not the
        +   * image has been updated or modified since last texture upload.
        +   */
        +  isModified() {
        +    return this._modified;
        +  }
        +  /**
        +   * helper method for web GL mode to indicate that an element has been
        +   * changed or unchanged since last upload. gl texture upload will
        +   * set this value to false after uploading the texture; or might set
        +   * it to true if metadata has become available but there is no actual
        +   * texture data available yet..
        +   * @param {Boolean} val sets whether or not the element has been
        +   * modified.
        +   * @private
        +   */
        +  setModified(value) {
        +    this._modified = value;
        +  }
        +  /**
        +   * Calls a function when the audio/video reaches the end of its playback.
        +   *
        +   * The element is passed as an argument to the callback function.
        +   *
        +   * Note: The function won't be called if the media is looping.
        +   *
        +   * @param  {Function} callback function to call when playback ends.
        +   *                             The `p5.MediaElement` is passed as
        +   *                             the argument.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let beat;
        +   * let isPlaying = false;
        +   * let isDone = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   beat = createAudio('assets/beat.mp3');
        +   *
        +   *   // Call handleEnd() when the beat finishes.
        +   *   beat.onended(handleEnd);
        +   *
        +   *   describe('The text "Click to play" written in black on a gray square. A beat plays when the user clicks. The text "Done!" appears when the beat finishes playing.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display different messages based on playback.
        +   *   if (isDone === true) {
        +   *     text('Done!', 50, 50);
        +   *   } else if (isPlaying === false) {
        +   *     text('Click to play', 50, 50);
        +   *   } else {
        +   *     text('Playing...', 50, 50);
        +   *   }
        +   * }
        +   *
        +   * // Play the beat when the user presses the mouse.
        +   * function mousePressed() {
        +   *   if (isPlaying === false) {
        +   *     isPlaying = true;
        +   *     beat.play();
        +   *   }
        +   * }
        +   *
        +   * // Set isDone when playback ends.
        +   * function handleEnd() {
        +   *   isDone = false;
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  onended(callback) {
        +    this._onended = callback;
        +    return this;
        +  }
        +
        +  /*** CONNECT TO WEB AUDIO API / p5.sound.js ***/
        +
        +  /**
        +   * Sends the element's audio to an output.
        +   *
        +   * The parameter, `audioNode`, can be an `AudioNode` or an object from the
        +   * `p5.sound` library.
        +   *
        +   * If no element is provided, as in `myElement.connect()`, the element
        +   * connects to the main output. All connections are removed by the
        +   * `.disconnect()` method.
        +   *
        +   * Note: This method is meant to be used with the p5.sound.js addon library.
        +   *
        +   * @param  {AudioNode|Object} audioNode AudioNode from the Web Audio API,
        +   * or an object from the p5.sound library
        +   */
        +  connect(obj) {
        +    let audioContext, mainOutput;
        +
        +    // if p5.sound exists, same audio context
        +    if (typeof fn.getAudioContext === 'function') {
        +      audioContext = fn.getAudioContext();
        +      mainOutput = p5.soundOut.input;
        +    } else {
        +      try {
        +        audioContext = obj.context;
        +        mainOutput = audioContext.destination;
        +      } catch (e) {
        +        throw 'connect() is meant to be used with Web Audio API or p5.sound.js';
        +      }
        +    }
        +
        +    // create a Web Audio MediaElementAudioSourceNode if none already exists
        +    if (!this.audioSourceNode) {
        +      this.audioSourceNode = audioContext.createMediaElementSource(this.elt);
        +
        +      // connect to main output when this method is first called
        +      this.audioSourceNode.connect(mainOutput);
        +    }
        +
        +    // connect to object if provided
        +    if (obj) {
        +      if (obj.input) {
        +        this.audioSourceNode.connect(obj.input);
        +      } else {
        +        this.audioSourceNode.connect(obj);
        +      }
        +    } else {
        +      // otherwise connect to main output of p5.sound / AudioContext
        +      this.audioSourceNode.connect(mainOutput);
        +    }
        +  }
        +
        +  /**
        +   * Disconnect all Web Audio routing, including to the main output.
        +   *
        +   * This is useful if you want to re-route the output through audio effects,
        +   * for example.
        +   *
        +   */
        +  disconnect() {
        +    if (this.audioSourceNode) {
        +      this.audioSourceNode.disconnect();
        +    } else {
        +      throw 'nothing to disconnect';
        +    }
        +  }
        +
        +  /*** SHOW / HIDE CONTROLS ***/
        +
        +  /**
        +   * Show the default
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement" target="_blank">HTMLMediaElement</a>
        +   * controls.
        +   *
        +   * Note: The controls vary between web browsers.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background('cornflowerblue');
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(50);
        +   *
        +   *   // Display a dragon.
        +   *   text('🐉', 50, 50);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   let dragon = createAudio('assets/lucky_dragons.mp3');
        +   *
        +   *   // Show the default media controls.
        +   *   dragon.showControls();
        +   *
        +   *   describe('A dragon emoji, 🐉, drawn in the center of a blue square. A song plays in the background. Audio controls are displayed beneath the canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  showControls() {
        +    // must set style for the element to show on the page
        +    this.elt.style['text-align'] = 'inherit';
        +    this.elt.controls = true;
        +  }
        +
        +  /**
        +   * Hide the default
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement" target="_blank">HTMLMediaElement</a>
        +   * controls.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let dragon;
        +   * let isHidden = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   dragon = createAudio('assets/lucky_dragons.mp3');
        +   *
        +   *   // Show the default media controls.
        +   *   dragon.showControls();
        +   *
        +   *   describe('The text "Double-click to hide controls" written in the middle of a gray square. A song plays in the background. Audio controls are displayed beneath the canvas. The controls appear/disappear when the user double-clicks the square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *
        +   *   // Display a different message when controls are hidden or shown.
        +   *   if (isHidden === true) {
        +   *     text('Double-click to show controls', 10, 20, 80, 80);
        +   *   } else {
        +   *     text('Double-click to hide controls', 10, 20, 80, 80);
        +   *   }
        +   * }
        +   *
        +   * // Show/hide controls based on a double-click.
        +   * function doubleClicked() {
        +   *   if (isHidden === true) {
        +   *     dragon.showControls();
        +   *     isHidden = false;
        +   *   } else {
        +   *     dragon.hideControls();
        +   *     isHidden = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  hideControls() {
        +    this.elt.controls = false;
        +  }
        +
        +  /**
        +   * Schedules a function to call when the audio/video reaches a specific time
        +   * during its playback.
        +   *
        +   * The first parameter, `time`, is the time, in seconds, when the function
        +   * should run. This value is passed to `callback` as its first argument.
        +   *
        +   * The second parameter, `callback`, is the function to call at the specified
        +   * cue time.
        +   *
        +   * The third parameter, `value`, is optional and can be any type of value.
        +   * `value` is passed to `callback`.
        +   *
        +   * Calling `media.addCue()` returns an ID as a string. This is useful for
        +   * removing the cue later.
        +   *
        +   * @param {Number}   time     cue time to run the callback function.
        +   * @param {Function} callback function to call at the cue time.
        +   * @param {Object} [value]    object to pass as the argument to
        +   *                            `callback`.
        +   * @return {Number} id ID of this cue,
        +   *                     useful for `media.removeCue(id)`.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   let beat = createAudio('assets/beat.mp3');
        +   *
        +   *   // Play the beat in a loop.
        +   *   beat.loop();
        +   *
        +   *   // Schedule a few events.
        +   *   beat.addCue(0, changeBackground, 'red');
        +   *   beat.addCue(2, changeBackground, 'deeppink');
        +   *   beat.addCue(4, changeBackground, 'orchid');
        +   *   beat.addCue(6, changeBackground, 'lavender');
        +   *
        +   *   describe('A red square with a beat playing in the background. Its color changes every 2 seconds while the audio plays.');
        +   * }
        +   *
        +   * // Change the background color.
        +   * function changeBackground(c) {
        +   *   background(c);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  addCue(time, callback, val) {
        +    const id = this._cueIDCounter++;
        +
        +    const cue = new Cue(callback, time, id, val);
        +    this._cues.push(cue);
        +
        +    if (!this.elt.ontimeupdate) {
        +      this.elt.ontimeupdate = this._onTimeUpdate.bind(this);
        +    }
        +
        +    return id;
        +  }
        +
        +  /**
        +   * Removes a callback based on its ID.
        +   *
        +   * @param  {Number} id ID of the cue, created by `media.addCue()`.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let lavenderID;
        +   * let isRemoved = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   let beat = createAudio('assets/beat.mp3');
        +   *
        +   *   // Play the beat in a loop.
        +   *   beat.loop();
        +   *
        +   *   // Schedule a few events.
        +   *   beat.addCue(0, changeBackground, 'red');
        +   *   beat.addCue(2, changeBackground, 'deeppink');
        +   *   beat.addCue(4, changeBackground, 'orchid');
        +   *
        +   *   // Record the ID of the "lavender" callback.
        +   *   lavenderID = beat.addCue(6, changeBackground, 'lavender');
        +   *
        +   *   describe('The text "Double-click to remove lavender." written on a red square. The color changes every 2 seconds while the audio plays. The lavender option is removed when the user double-clicks the square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Display different instructions based on the available callbacks.
        +   *   if (isRemoved === false) {
        +   *     text('Double-click to remove lavender.', 10, 10, 80, 80);
        +   *   } else {
        +   *     text('No more lavender.', 10, 10, 80, 80);
        +   *   }
        +   * }
        +   *
        +   * // Change the background color.
        +   * function changeBackground(c) {
        +   *   background(c);
        +   * }
        +   *
        +   * // Remove the lavender color-change cue when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isRemoved === false) {
        +   *     beat.removeCue(lavenderID);
        +   *     isRemoved = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  removeCue(id) {
        +    for (let i = 0; i < this._cues.length; i++) {
        +      if (this._cues[i].id === id) {
        +        console.log(id);
        +        this._cues.splice(i, 1);
        +      }
        +    }
        +
        +    if (this._cues.length === 0) {
        +      this.elt.ontimeupdate = null;
        +    }
        +  }
        +
        +  /**
        +   * Removes all functions scheduled with `media.addCue()`.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let isChanging = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   let beat = createAudio('assets/beat.mp3');
        +   *
        +   *   // Play the beat in a loop.
        +   *   beat.loop();
        +   *
        +   *   // Schedule a few events.
        +   *   beat.addCue(0, changeBackground, 'red');
        +   *   beat.addCue(2, changeBackground, 'deeppink');
        +   *   beat.addCue(4, changeBackground, 'orchid');
        +   *   beat.addCue(6, changeBackground, 'lavender');
        +   *
        +   *   describe('The text "Double-click to stop changing." written on a square. The color changes every 2 seconds while the audio plays. The color stops changing when the user double-clicks the square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Display different instructions based on the available callbacks.
        +   *   if (isChanging === true) {
        +   *     text('Double-click to stop changing.', 10, 10, 80, 80);
        +   *   } else {
        +   *     text('No more changes.', 10, 10, 80, 80);
        +   *   }
        +   * }
        +   *
        +   * // Change the background color.
        +   * function changeBackground(c) {
        +   *   background(c);
        +   * }
        +   *
        +   * // Remove cued functions and stop changing colors when the user
        +   * // double-clicks.
        +   * function doubleClicked() {
        +   *   if (isChanging === true) {
        +   *     beat.clearCues();
        +   *     isChanging = false;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  clearCues() {
        +    this._cues = [];
        +    this.elt.ontimeupdate = null;
        +  }
        +
        +  // private method that checks for cues to be fired if events
        +  // have been scheduled using addCue(callback, time).
        +  _onTimeUpdate() {
        +    const playbackTime = this.time();
        +
        +    for (let i = 0; i < this._cues.length; i++) {
        +      const callbackTime = this._cues[i].time;
        +      const val = this._cues[i].val;
        +
        +      if (this._prevTime < callbackTime && callbackTime <= playbackTime) {
        +        // pass the scheduled callbackTime as parameter to the callback
        +        this._cues[i].callback(val);
        +      }
        +    }
        +
        +    this._prevTime = playbackTime;
        +  }
        +}
        +
        +// Cue inspired by JavaScript setTimeout, and the
        +// Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org
        +// eslint-disable-next-line no-unused-vars
        +class Cue {
        +  constructor(callback, time, id, val) {
        +    this.callback = callback;
        +    this.time = time;
        +    this.id = id;
        +    this.val = val;
        +  }
        +}
        +
        +function media(p5, fn){
        +  /**
        +   * Helpers for create methods.
        +   */
        +  function addElement(elt, pInst, media) {
        +    const node = pInst._userNode ? pInst._userNode : document.body;
        +    node.appendChild(elt);
        +    const c = media
        +      ? new MediaElement(elt, pInst)
        +      : new Element(elt, pInst);
        +    pInst._elements.push(c);
        +    return c;
        +  }
        +
        +  /** VIDEO STUFF **/
        +
        +  // Helps perform similar tasks for media element methods.
        +  function createMedia(pInst, type, src, callback) {
        +    const elt = document.createElement(type);
        +
        +    // Create source elements from given sources
        +    src = src || '';
        +    if (typeof src === 'string') {
        +      src = [src];
        +    }
        +    for (const mediaSource of src) {
        +      const sourceEl = document.createElement('source');
        +      sourceEl.setAttribute('src', mediaSource);
        +      elt.appendChild(sourceEl);
        +    }
        +
        +    // If callback is provided, attach to element
        +    if (typeof callback === 'function') {
        +      const callbackHandler = () => {
        +        callback();
        +        elt.removeEventListener('canplaythrough', callbackHandler);
        +      };
        +      elt.addEventListener('canplaythrough', callbackHandler);
        +    }
        +
        +    const mediaEl = addElement(elt, pInst, true);
        +    mediaEl.loadedmetadata = false;
        +
        +    // set width and height onload metadata
        +    elt.addEventListener('loadedmetadata', () => {
        +      mediaEl.width = elt.videoWidth;
        +      mediaEl.height = elt.videoHeight;
        +
        +      // set elt width and height if not set
        +      if (mediaEl.elt.width === 0) mediaEl.elt.width = elt.videoWidth;
        +      if (mediaEl.elt.height === 0) mediaEl.elt.height = elt.videoHeight;
        +      if (mediaEl.presetPlaybackRate) {
        +        mediaEl.elt.playbackRate = mediaEl.presetPlaybackRate;
        +        delete mediaEl.presetPlaybackRate;
        +      }
        +      mediaEl.loadedmetadata = true;
        +    });
        +
        +    return mediaEl;
        +  }
        +
        +  /**
        +   * Creates a `&lt;video&gt;` element for simple audio/video playback.
        +   *
        +   * `createVideo()` returns a new
        +   * <a href="#/p5.MediaElement">p5.MediaElement</a> object. Videos are shown by
        +   * default. They can be hidden by calling `video.hide()` and drawn to the
        +   * canvas using <a href="#/p5/image">image()</a>.
        +   *
        +   * The first parameter, `src`, is the path the video. If a single string is
        +   * passed, as in `'assets/topsecret.mp4'`, a single video is loaded. An array
        +   * of strings can be used to load the same video in different formats. For
        +   * example, `['assets/topsecret.mp4', 'assets/topsecret.ogv', 'assets/topsecret.webm']`.
        +   * This is useful for ensuring that the video can play across different browsers with
        +   * different capabilities. See
        +   * <a href='https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats'>MDN</a>
        +   * for more information about supported formats.
        +   *
        +   * The second parameter, `callback`, is optional. It's a function to call once
        +   * the video is ready to play.
        +   *
        +   * @method createVideo
        +   * @param  {String|String[]} src path to a video file, or an array of paths for
        +   *                               supporting different browsers.
        +   * @param  {Function} [callback] function to call once the video is ready to play.
        +   * @return {p5.MediaElement}   new <a href="#/p5.MediaElement">p5.MediaElement</a> object.
        +   *
        +   * @example
        +   * <div class='notest'>
        +   * <code>
        +   * function setup() {
        +   *   noCanvas();
        +   *
        +   *   // Load a video and add it to the page.
        +   *   // Note: this may not work in some browsers.
        +   *   let video = createVideo('assets/small.mp4');
        +   *
        +   *   // Show the default video controls.
        +   *   video.showControls();
        +   *
        +   *   describe('A video of a toy robot with playback controls beneath it.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='notest'>
        +   * <code>
        +   * function setup() {
        +   *   noCanvas();
        +   *
        +   *   // Load a video and add it to the page.
        +   *   // Provide an array options for different file formats.
        +   *   let video = createVideo(
        +   *     ['assets/small.mp4', 'assets/small.ogv', 'assets/small.webm']
        +   *   );
        +   *
        +   *   // Show the default video controls.
        +   *   video.showControls();
        +   *
        +   *   describe('A video of a toy robot with playback controls beneath it.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='notest'>
        +   * <code>
        +   * let video;
        +   *
        +   * function setup() {
        +   *   noCanvas();
        +   *
        +   *   // Load a video and add it to the page.
        +   *   // Provide an array options for different file formats.
        +   *   // Call mute() once the video loads.
        +   *   video = createVideo(
        +   *     ['assets/small.mp4', 'assets/small.ogv', 'assets/small.webm'],
        +   *     muteVideo
        +   *   );
        +   *
        +   *   // Show the default video controls.
        +   *   video.showControls();
        +   *
        +   *   describe('A video of a toy robot with playback controls beneath it.');
        +   * }
        +   *
        +   * // Mute the video once it loads.
        +   * function muteVideo() {
        +   *   video.volume(0);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createVideo = function (src, callback) {
        +    // p5._validateParameters('createVideo', arguments);
        +    return createMedia(this, 'video', src, callback);
        +  };
        +
        +  /** AUDIO STUFF **/
        +
        +  /**
        +   * Creates a hidden `&lt;audio&gt;` element for simple audio playback.
        +   *
        +   * `createAudio()` returns a new
        +   * <a href="#/p5.MediaElement">p5.MediaElement</a> object.
        +   *
        +   * The first parameter, `src`, is the path the video. If a single string is
        +   * passed, as in `'assets/video.mp4'`, a single video is loaded. An array
        +   * of strings can be used to load the same video in different formats. For
        +   * example, `['assets/video.mp4', 'assets/video.ogv', 'assets/video.webm']`.
        +   * This is useful for ensuring that the video can play across different
        +   * browsers with different capabilities. See
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats" target="_blank">MDN</a>
        +   * for more information about supported formats.
        +   *
        +   * The second parameter, `callback`, is optional. It's a function to call once
        +   * the audio is ready to play.
        +   *
        +   * @method createAudio
        +   * @param  {String|String[]} [src] path to an audio file, or an array of paths
        +   *                                 for supporting different browsers.
        +   * @param  {Function} [callback]   function to call once the audio is ready to play.
        +   * @return {p5.MediaElement}       new <a href="#/p5.MediaElement">p5.MediaElement</a> object.
        +   *
        +   * @example
        +   * <div class='notest'>
        +   * <code>
        +   * function setup() {
        +   *   noCanvas();
        +   *
        +   *   // Load the audio.
        +   *   let beat = createAudio('assets/beat.mp3');
        +   *
        +   *   // Show the default audio controls.
        +   *   beat.showControls();
        +   *
        +   *   describe('An audio beat plays when the user double-clicks the square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createAudio = function (src, callback) {
        +    // p5._validateParameters('createAudio', arguments);
        +    return createMedia(this, 'audio', src, callback);
        +  };
        +
        +  /** CAMERA STUFF **/
        +
        +  fn.VIDEO = 'video';
        +
        +  fn.AUDIO = 'audio';
        +
        +  // from: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
        +  // Older browsers might not implement mediaDevices at all, so we set an empty object first
        +  if (navigator.mediaDevices === undefined) {
        +    navigator.mediaDevices = {};
        +  }
        +
        +  // Some browsers partially implement mediaDevices. We can't just assign an object
        +  // with getUserMedia as it would overwrite existing properties.
        +  // Here, we will just add the getUserMedia property if it's missing.
        +  if (navigator.mediaDevices.getUserMedia === undefined) {
        +    navigator.mediaDevices.getUserMedia = function (constraints) {
        +      // First get ahold of the legacy getUserMedia, if present
        +      const getUserMedia =
        +        navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
        +
        +      // Some browsers just don't implement it - return a rejected promise with an error
        +      // to keep a consistent interface
        +      if (!getUserMedia) {
        +        return Promise.reject(
        +          new Error('getUserMedia is not implemented in this browser')
        +        );
        +      }
        +
        +      // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
        +      return new Promise(function (resolve, reject) {
        +        getUserMedia.call(navigator, constraints, resolve, reject);
        +      });
        +    };
        +  }
        +
        +  /**
        +   * Creates a `&lt;video&gt;` element that "captures" the audio/video stream from
        +   * the webcam and microphone.
        +   *
        +   * `createCapture()` returns a new
        +   * <a href="#/p5.MediaElement">p5.MediaElement</a> object. Videos are shown by
        +   * default. They can be hidden by calling `capture.hide()` and drawn to the
        +   * canvas using <a href="#/p5/image">image()</a>.
        +   *
        +   * The first parameter, `type`, is optional. It sets the type of capture to
        +   * use. By default, `createCapture()` captures both audio and video. If `VIDEO`
        +   * is passed, as in `createCapture(VIDEO)`, only video will be captured.
        +   * If `AUDIO` is passed, as in `createCapture(AUDIO)`, only audio will be
        +   * captured. A constraints object can also be passed to customize the stream.
        +   * See the <a href="http://w3c.github.io/mediacapture-main/getusermedia.html#media-track-constraints" target="_blank">
        +   * W3C documentation</a> for possible properties. Different browsers support different
        +   * properties.
        +   *
        +   * The 'flipped' property is an optional property which can be set to `{flipped:true}`
        +   * to mirror the video output.If it is true then it means that video will be mirrored
        +   * or flipped and if nothing is mentioned then by default it will be `false`.
        +   *
        +   * The second parameter,`callback`, is optional. It's a function to call once
        +   * the capture is ready for use. The callback function should have one
        +   * parameter, `stream`, that's a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaStream" target="_blank">MediaStream</a> object.
        +   *
        +   * Note: `createCapture()` only works when running a sketch locally or using HTTPS. Learn more
        +   * <a href="http://stackoverflow.com/questions/34197653/getusermedia-in-chrome-47-without-using-https" target="_blank">here</a>
        +   * and <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia" target="_blank">here</a>.
        +   *
        +   * @method createCapture
        +   * @param  {(AUDIO|VIDEO|Object)}  [type] type of capture, either AUDIO or VIDEO,
        +   *                                   or a constraints object. Both video and audio
        +   *                                   audio streams are captured by default.
        +   * @param  {Object}                  [flipped] flip the capturing video and mirror the output with `{flipped:true}`. By
        +   *                                   default it is false.
        +   * @param  {Function}                [callback] function to call once the stream
        +   *                                   has loaded.
        +   * @return {p5.MediaElement} new <a href="#/p5.MediaElement">p5.MediaElement</a> object.
        +   *
        +   * @example
        +   * <div class='notest'>
        +   * <code>
        +   * function setup() {
        +   *   noCanvas();
        +   *
        +   *   // Create the video capture.
        +   *   createCapture(VIDEO);
        +   *
        +   *   describe('A video stream from the webcam.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='notest'>
        +   * <code>
        +   * let capture;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create the video capture and hide the element.
        +   *   capture = createCapture(VIDEO);
        +   *   capture.hide();
        +   *
        +   *   describe('A video stream from the webcam with inverted colors.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Draw the video capture within the canvas.
        +   *   image(capture, 0, 0, width, width * capture.height / capture.width);
        +   *
        +   *   // Invert the colors in the stream.
        +   *   filter(INVERT);
        +   * }
        +   * </code>
        +   * </div>
        +   * <div class='notest'>
        +   * <code>
        +   * let capture;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create the video capture with mirrored output.
        +   *   capture = createCapture(VIDEO,{ flipped:true });
        +   *   capture.size(100,100);
        +   *
        +   *   describe('A video stream from the webcam with flipped or mirrored output.');
        +   * }
        +   *
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='notest norender'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(480, 120);
        +   *
        +   *   // Create a constraints object.
        +   *   let constraints = {
        +   *     video: {
        +   *       mandatory: {
        +   *         minWidth: 1280,
        +   *         minHeight: 720
        +   *       },
        +   *       optional: [{ maxFrameRate: 10 }]
        +   *     },
        +   *     audio: false
        +   *   };
        +   *
        +   *   // Create the video capture.
        +   *   createCapture(constraints);
        +   *
        +   *   describe('A video stream from the webcam.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createCapture = function (...args) {
        +    // p5._validateParameters('createCapture', args);
        +
        +    // return if getUserMedia is not supported by the browser
        +    if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
        +      throw new DOMException('getUserMedia not supported in this browser');
        +    }
        +
        +    let useVideo = true;
        +    let useAudio = true;
        +    let constraints;
        +    let callback;
        +    let flipped = false;
        +
        +    for (const arg of args) {
        +      if (arg === fn.VIDEO) useAudio = false;
        +      else if (arg === fn.AUDIO) useVideo = false;
        +      else if (typeof arg === 'object') {
        +        if (arg.flipped !== undefined) {
        +          flipped = arg.flipped;
        +          delete arg.flipped;
        +        }
        +        constraints = Object.assign({}, constraints, arg);
        +      }
        +      else if (typeof arg === 'function') {
        +        callback = arg;
        +      }
        +    }
        +
        +    const videoConstraints = { video: useVideo, audio: useAudio };
        +    constraints = Object.assign({}, videoConstraints, constraints);
        +    const domElement = document.createElement('video');
        +    // required to work in iOS 11 & up:
        +    domElement.setAttribute('playsinline', '');
        +    navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
        +      try {
        +        if ('srcObject' in domElement) {
        +          domElement.srcObject = stream;
        +        } else {
        +          domElement.src = window.URL.createObjectURL(stream);
        +        }
        +      }
        +      catch (err) {
        +        domElement.src = stream;
        +      }
        +    }).catch(e => {
        +      if (e.name === 'NotFoundError')
        +        p5._friendlyError('No webcam found on this device', 'createCapture');
        +      if (e.name === 'NotAllowedError')
        +        p5._friendlyError('Access to the camera was denied', 'createCapture');
        +
        +      console.error(e);
        +    });
        +
        +    const videoEl = addElement(domElement, this, true);
        +    videoEl.loadedmetadata = false;
        +    // set width and height onload metadata
        +    domElement.addEventListener('loadedmetadata', function () {
        +      domElement.play();
        +      if (domElement.width) {
        +        videoEl.width = domElement.width;
        +        videoEl.height = domElement.height;
        +        if (flipped) {
        +          videoEl.elt.style.transform = 'scaleX(-1)';
        +        }
        +      } else {
        +        videoEl.width = videoEl.elt.width = domElement.videoWidth;
        +        videoEl.height = videoEl.elt.height = domElement.videoHeight;
        +      }
        +      videoEl.loadedmetadata = true;
        +
        +      if (callback) callback(domElement.srcObject);
        +    });
        +    videoEl.flipped = flipped;
        +    return videoEl;
        +  };
        +
        +  // =============================================================================
        +  //                         p5.MediaElement additions
        +  // =============================================================================
        +
        +  /**
        +   * A class to handle audio and video.
        +   *
        +   * `p5.MediaElement` extends <a href="#/p5.Element">p5.Element</a> with
        +   * methods to handle audio and video. `p5.MediaElement` objects are created by
        +   * calling <a href="#/p5/createVideo">createVideo</a>,
        +   * <a href="#/p5/createAudio">createAudio</a>, and
        +   * <a href="#/p5/createCapture">createCapture</a>.
        +   *
        +   * @class p5.MediaElement
        +   * @param {String} elt DOM node that is wrapped
        +   * @extends p5.Element
        +   *
        +   * @example
        +   * <div class='notest'>
        +   * <code>
        +   * let capture;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createCapture().
        +   *   capture = createCapture(VIDEO);
        +   *   capture.hide();
        +   *
        +   *   describe('A webcam feed with inverted colors.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Display the video stream and invert the colors.
        +   *   image(capture, 0, 0, width, width * capture.height / capture.width);
        +   *   filter(INVERT);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  p5.MediaElement = MediaElement;
        +
        +  /**
        +   * Path to the media element's source as a string.
        +   *
        +   * @for p5.MediaElement
        +   * @property src
        +   * @return {String} src
        +   * @example
        +   * <div>
        +   * <code>
        +   * let beat;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.MediaElement using createAudio().
        +   *   beat = createAudio('assets/beat.mp3');
        +   *
        +   *   describe('The text "https://p5js.org/reference/assets/beat.mp3" written in black on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   textWrap(CHAR);
        +   *   text(beat.src, 10, 10, 80, 80);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +}
        +
        +export default media;
        +export { MediaElement };
        +
        +if(typeof p5 !== 'undefined'){
        +  media(p5, p5.prototype);
        +}
        diff --git a/src/events/acceleration.js b/src/events/acceleration.js
        index 0c574b3d48..37e657dc12 100644
        --- a/src/events/acceleration.js
        +++ b/src/events/acceleration.js
        @@ -6,738 +6,742 @@
          * @main Events
          */
         
        -import p5 from '../core/main';
        -
        -/**
        - * The system variable deviceOrientation always contains the orientation of
        - * the device. The value of this variable will either be set 'landscape'
        - * or 'portrait'. If no data is available it will be set to 'undefined'.
        - * either LANDSCAPE or PORTRAIT.
        - *
        - * @property {Constant} deviceOrientation
        - * @readOnly
        - */
        -p5.prototype.deviceOrientation =
        -  window.innerWidth / window.innerHeight > 1.0 ? 'landscape' : 'portrait';
        -
        -/**
        - * The system variable accelerationX always contains the acceleration of the
        - * device along the x axis. Value is represented as meters per second squared.
        - *
        - * @property {Number} accelerationX
        - * @readOnly
        - * @example
        - * <div>
        - * <code>
        - * // Move a touchscreen device to register
        - * // acceleration changes.
        - * function draw() {
        - *   background(220, 50);
        - *   fill('magenta');
        - *   ellipse(width / 2, height / 2, accelerationX);
        - *   describe('Magnitude of device acceleration is displayed as ellipse size.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.accelerationX = 0;
        -
        -/**
        - * The system variable accelerationY always contains the acceleration of the
        - * device along the y axis. Value is represented as meters per second squared.
        - *
        - * @property {Number} accelerationY
        - * @readOnly
        - * @example
        - * <div>
        - * <code>
        - * // Move a touchscreen device to register
        - * // acceleration changes.
        - * function draw() {
        - *   background(220, 50);
        - *   fill('magenta');
        - *   ellipse(width / 2, height / 2, accelerationY);
        - *   describe('Magnitude of device acceleration is displayed as ellipse size');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.accelerationY = 0;
        -
        -/**
        - * The system variable accelerationZ always contains the acceleration of the
        - * device along the z axis. Value is represented as meters per second squared.
        - *
        - * @property {Number} accelerationZ
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Move a touchscreen device to register
        - * // acceleration changes.
        - * function draw() {
        - *   background(220, 50);
        - *   fill('magenta');
        - *   ellipse(width / 2, height / 2, accelerationZ);
        - *   describe('Magnitude of device acceleration is displayed as ellipse size');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.accelerationZ = 0;
        -
        -/**
        - * The system variable pAccelerationX always contains the acceleration of the
        - * device along the x axis in the frame previous to the current frame. Value
        - * is represented as meters per second squared.
        - *
        - * @property {Number} pAccelerationX
        - * @readOnly
        - */
        -p5.prototype.pAccelerationX = 0;
        -
        -/**
        - * The system variable pAccelerationY always contains the acceleration of the
        - * device along the y axis in the frame previous to the current frame. Value
        - * is represented as meters per second squared.
        - *
        - * @property {Number} pAccelerationY
        - * @readOnly
        - */
        -p5.prototype.pAccelerationY = 0;
        -
        -/**
        - * The system variable pAccelerationZ always contains the acceleration of the
        - * device along the z axis in the frame previous to the current frame. Value
        - * is represented as meters per second squared.
        - *
        - * @property {Number} pAccelerationZ
        - * @readOnly
        - */
        -p5.prototype.pAccelerationZ = 0;
        -
        -/**
        - * _updatePAccelerations updates the pAcceleration values
        - *
        - * @private
        - */
        -p5.prototype._updatePAccelerations = function() {
        -  this._setProperty('pAccelerationX', this.accelerationX);
        -  this._setProperty('pAccelerationY', this.accelerationY);
        -  this._setProperty('pAccelerationZ', this.accelerationZ);
        -};
        -
        -/**
        - * The system variable rotationX always contains the rotation of the
        - * device along the x axis. If the sketch <a href="#/p5/angleMode">
        - * angleMode()</a> is set to DEGREES, the value will be -180 to 180. If
        - * it is set to RADIANS, the value will be -PI to PI.
        - *
        - * Note: The order the rotations are called is important, ie. if used
        - * together, it must be called in the order Z-X-Y or there might be
        - * unexpected behaviour.
        - *
        - * @property {Number} rotationX
        - * @readOnly
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *   //rotateZ(radians(rotationZ));
        - *   rotateX(radians(rotationX));
        - *   //rotateY(radians(rotationY));
        - *   box(200, 200, 200);
        - *   describe(`red horizontal line right, green vertical line bottom.
        - *     black background.`);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.rotationX = 0;
        -
        -/**
        - * The system variable rotationY always contains the rotation of the
        - * device along the y axis. If the sketch <a href="#/p5/angleMode">
        - * angleMode()</a> is set to DEGREES, the value will be -90 to 90. If
        - * it is set to RADIANS, the value will be -PI/2 to PI/2.
        - *
        - * Note: The order the rotations are called is important, ie. if used
        - * together, it must be called in the order Z-X-Y or there might be
        - * unexpected behaviour.
        - *
        - * @property {Number} rotationY
        - * @readOnly
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *   //rotateZ(radians(rotationZ));
        - *   //rotateX(radians(rotationX));
        - *   rotateY(radians(rotationY));
        - *   box(200, 200, 200);
        - *   describe(`red horizontal line right, green vertical line bottom.
        - *     black background.`);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.rotationY = 0;
        -
        -/**
        - * The system variable rotationZ always contains the rotation of the
        - * device along the z axis. If the sketch <a href="#/p5/angleMode">
        - * angleMode()</a> is set to DEGREES, the value will be 0 to 360. If
        - * it is set to RADIANS, the value will be 0 to 2*PI.
        - *
        - * Unlike rotationX and rotationY, this variable is available for devices
        - * with a built-in compass only.
        - *
        - * Note: The order the rotations are called is important, ie. if used
        - * together, it must be called in the order Z-X-Y or there might be
        - * unexpected behaviour.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *   rotateZ(radians(rotationZ));
        - *   //rotateX(radians(rotationX));
        - *   //rotateY(radians(rotationY));
        - *   box(200, 200, 200);
        - *   describe(`red horizontal line right, green vertical line bottom.
        - *     black background.`);
        - * }
        - * </code>
        - * </div>
        - *
        - * @property {Number} rotationZ
        - * @readOnly
        - */
        -p5.prototype.rotationZ = 0;
        -
        -/**
        - * The system variable pRotationX always contains the rotation of the
        - * device along the x axis in the frame previous to the current frame.
        - * If the sketch <a href="#/p5/angleMode"> angleMode()</a> is set to DEGREES,
        - * the value will be -180 to 180. If it is set to RADIANS, the value will
        - * be -PI to PI.
        - *
        - * pRotationX can also be used with rotationX to determine the rotate
        - * direction of the device along the X-axis.
        - * @example
        - * <div class='norender'>
        - * <code>
        - * // A simple if statement looking at whether
        - * // rotationX - pRotationX < 0 is true or not will be
        - * // sufficient for determining the rotate direction
        - * // in most cases.
        - *
        - * // Some extra logic is needed to account for cases where
        - * // the angles wrap around.
        - * let rotateDirection = 'clockwise';
        - *
        - * // Simple range conversion to make things simpler.
        - * // This is not absolutely necessary but the logic
        - * // will be different in that case.
        - *
        - * let rX = rotationX + 180;
        - * let pRX = pRotationX + 180;
        - *
        - * if ((rX - pRX > 0 && rX - pRX < 270) || rX - pRX < -270) {
        - *   rotateDirection = 'clockwise';
        - * } else if (rX - pRX < 0 || rX - pRX > 270) {
        - *   rotateDirection = 'counter-clockwise';
        - * }
        - *
        - * print(rotateDirection);
        - * describe('no image to display.');
        - * </code>
        - * </div>
        - *
        - * @property {Number} pRotationX
        - * @readOnly
        - */
        -p5.prototype.pRotationX = 0;
        -
        -/**
        - * The system variable pRotationY always contains the rotation of the
        - * device along the y axis in the frame previous to the current frame.
        - * If the sketch <a href="#/p5/angleMode"> angleMode()</a> is set to DEGREES,
        - * the value will be -90 to 90. If it is set to RADIANS, the value will
        - * be -PI/2 to PI/2.
        - *
        - * pRotationY can also be used with rotationY to determine the rotate
        - * direction of the device along the Y-axis.
        - * @example
        - * <div class='norender'>
        - * <code>
        - * // A simple if statement looking at whether
        - * // rotationY - pRotationY < 0 is true or not will be
        - * // sufficient for determining the rotate direction
        - * // in most cases.
        - *
        - * // Some extra logic is needed to account for cases where
        - * // the angles wrap around.
        - * let rotateDirection = 'clockwise';
        - *
        - * // Simple range conversion to make things simpler.
        - * // This is not absolutely necessary but the logic
        - * // will be different in that case.
        - *
        - * let rY = rotationY + 180;
        - * let pRY = pRotationY + 180;
        - *
        - * if ((rY - pRY > 0 && rY - pRY < 270) || rY - pRY < -270) {
        - *   rotateDirection = 'clockwise';
        - * } else if (rY - pRY < 0 || rY - pRY > 270) {
        - *   rotateDirection = 'counter-clockwise';
        - * }
        - * print(rotateDirection);
        - * describe('no image to display.');
        - * </code>
        - * </div>
        - *
        - * @property {Number} pRotationY
        - * @readOnly
        - */
        -p5.prototype.pRotationY = 0;
        -
        -/**
        - * The system variable pRotationZ always contains the rotation of the
        - * device along the z axis in the frame previous to the current frame.
        - * If the sketch <a href="#/p5/angleMode"> angleMode()</a> is set to DEGREES,
        - * the value will be 0 to 360. If it is set to RADIANS, the value will
        - * be 0 to 2*PI.
        - *
        - * pRotationZ can also be used with rotationZ to determine the rotate
        - * direction of the device along the Z-axis.
        - * @example
        - * <div class='norender'>
        - * <code>
        - * // A simple if statement looking at whether
        - * // rotationZ - pRotationZ < 0 is true or not will be
        - * // sufficient for determining the rotate direction
        - * // in most cases.
        - *
        - * // Some extra logic is needed to account for cases where
        - * // the angles wrap around.
        - * let rotateDirection = 'clockwise';
        - *
        - * if (
        - *   (rotationZ - pRotationZ > 0 && rotationZ - pRotationZ < 270) ||
        - *   rotationZ - pRotationZ < -270
        - * ) {
        - *   rotateDirection = 'clockwise';
        - * } else if (rotationZ - pRotationZ < 0 || rotationZ - pRotationZ > 270) {
        - *   rotateDirection = 'counter-clockwise';
        - * }
        - * print(rotateDirection);
        - * describe('no image to display.');
        - * </code>
        - * </div>
        - *
        - * @property {Number} pRotationZ
        - * @readOnly
        - */
        -p5.prototype.pRotationZ = 0;
        -
        -let startAngleX = 0;
        -let startAngleY = 0;
        -let startAngleZ = 0;
        -
        -let rotateDirectionX = 'clockwise';
        -let rotateDirectionY = 'clockwise';
        -let rotateDirectionZ = 'clockwise';
        -
        -p5.prototype.pRotateDirectionX = undefined;
        -p5.prototype.pRotateDirectionY = undefined;
        -p5.prototype.pRotateDirectionZ = undefined;
        -
        -p5.prototype._updatePRotations = function() {
        -  this._setProperty('pRotationX', this.rotationX);
        -  this._setProperty('pRotationY', this.rotationY);
        -  this._setProperty('pRotationZ', this.rotationZ);
        -};
        -
        -/**
        - * When a device is rotated, the axis that triggers the <a href="#/p5/deviceTurned">deviceTurned()</a>
        - * method is stored in the turnAxis variable. The turnAxis variable is only defined within
        - * the scope of deviceTurned().
        - * @property {String} turnAxis
        - * @readOnly
        - * @example
        - * <div>
        - * <code>
        - * // Run this example on a mobile device
        - * // Rotate the device by 90 degrees in the
        - * // X-axis to change the value.
        - *
        - * let value = 0;
        - * function draw() {
        - *   fill(value);
        - *   rect(25, 25, 50, 50);
        - *   describe(`50-by-50 black rect in center of canvas.
        - *     turns white on mobile when device turns`);
        - *   describe(`50-by-50 black rect in center of canvas.
        - *     turns white on mobile when x-axis turns`);
        - * }
        - * function deviceTurned() {
        - *   if (turnAxis === 'X') {
        - *     if (value === 0) {
        - *       value = 255;
        - *     } else if (value === 255) {
        - *       value = 0;
        - *     }
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.turnAxis = undefined;
        -
        -let move_threshold = 0.5;
        -let shake_threshold = 30;
        -
        -/**
        - * The <a href="#/p5/setMoveThreshold">setMoveThreshold()</a> function is used to set the movement threshold for
        - * the <a href="#/p5/deviceMoved">deviceMoved()</a> function. The default threshold is set to 0.5.
        - *
        - * @method setMoveThreshold
        - * @param {number} value The threshold value
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Run this example on a mobile device
        - * // You will need to move the device incrementally further
        - * // the closer the square's color gets to white in order to change the value.
        - *
        - * let value = 0;
        - * let threshold = 0.5;
        - * function setup() {
        - *   setMoveThreshold(threshold);
        - * }
        - * function draw() {
        - *   fill(value);
        - *   rect(25, 25, 50, 50);
        - *   describe(`50-by-50 black rect in center of canvas.
        - *     turns white on mobile when device moves`);
        - * }
        - * function deviceMoved() {
        - *   value = value + 5;
        - *   threshold = threshold + 0.1;
        - *   if (value > 255) {
        - *     value = 0;
        - *     threshold = 30;
        - *   }
        - *   setMoveThreshold(threshold);
        - * }
        - * </code>
        - * </div>
        - */
        -
        -p5.prototype.setMoveThreshold = function(val) {
        -  p5._validateParameters('setMoveThreshold', arguments);
        -  move_threshold = val;
        -};
        -
        -/**
        - * The <a href="#/p5/setShakeThreshold">setShakeThreshold()</a> function is used to set the movement threshold for
        - * the <a href="#/p5/deviceShaken">deviceShaken()</a> function. The default threshold is set to 30.
        - *
        - * @method setShakeThreshold
        - * @param {number} value The threshold value
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Run this example on a mobile device
        - * // You will need to shake the device more firmly
        - * // the closer the box's fill gets to white in order to change the value.
        - *
        - * let value = 0;
        - * let threshold = 30;
        - * function setup() {
        - *   setShakeThreshold(threshold);
        - * }
        - * function draw() {
        - *   fill(value);
        - *   rect(25, 25, 50, 50);
        - *   describe(`50-by-50 black rect in center of canvas.
        - *     turns white on mobile when device is being shaked`);
        - * }
        - * function deviceMoved() {
        - *   value = value + 5;
        - *   threshold = threshold + 5;
        - *   if (value > 255) {
        - *     value = 0;
        - *     threshold = 30;
        - *   }
        - *   setShakeThreshold(threshold);
        - * }
        - * </code>
        - * </div>
        - */
        -
        -p5.prototype.setShakeThreshold = function(val) {
        -  p5._validateParameters('setShakeThreshold', arguments);
        -  shake_threshold = val;
        -};
        -
        -/**
        - * The <a href="#/p5/deviceMoved">deviceMoved()</a> function is called when the device is moved by more than
        - * the threshold value along X, Y or Z axis. The default threshold is set to 0.5.
        - * The threshold value can be changed using <a href="#/p5/setMoveThreshold">setMoveThreshold()</a>.
        - *
        - * @method deviceMoved
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Run this example on a mobile device
        - * // Move the device around
        - * // to change the value.
        - *
        - * let value = 0;
        - * function draw() {
        - *   fill(value);
        - *   rect(25, 25, 50, 50);
        - *   describe(`50-by-50 black rect in center of canvas.
        - *     turns white on mobile when device moves`);
        - * }
        - * function deviceMoved() {
        - *   value = value + 5;
        - *   if (value > 255) {
        - *     value = 0;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * The <a href="#/p5/deviceTurned">deviceTurned()</a> function is called when the device rotates by
        - * more than 90 degrees continuously.
        - *
        - * The axis that triggers the <a href="#/p5/deviceTurned">deviceTurned()</a> method is stored in the turnAxis
        - * variable. The <a href="#/p5/deviceTurned">deviceTurned()</a> method can be locked to trigger on any axis:
        - * X, Y or Z by comparing the turnAxis variable to 'X', 'Y' or 'Z'.
        - *
        - * @method deviceTurned
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Run this example on a mobile device
        - * // Rotate the device by 90 degrees
        - * // to change the value.
        - *
        - * let value = 0;
        - * function draw() {
        - *   fill(value);
        - *   rect(25, 25, 50, 50);
        - *   describe(`50-by-50 black rect in center of canvas.
        - *     turns white on mobile when device turns`);
        - * }
        - * function deviceTurned() {
        - *   if (value === 0) {
        - *     value = 255;
        - *   } else if (value === 255) {
        - *     value = 0;
        - *   }
        - * }
        - * </code>
        - * </div>
        - * <div>
        - * <code>
        - * // Run this example on a mobile device
        - * // Rotate the device by 90 degrees in the
        - * // X-axis to change the value.
        - *
        - * let value = 0;
        - * function draw() {
        - *   fill(value);
        - *   rect(25, 25, 50, 50);
        - *   describe(`50-by-50 black rect in center of canvas.
        - *     turns white on mobile when x-axis turns`);
        - * }
        - * function deviceTurned() {
        - *   if (turnAxis === 'X') {
        - *     if (value === 0) {
        - *       value = 255;
        - *     } else if (value === 255) {
        - *       value = 0;
        - *     }
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * The <a href="#/p5/deviceShaken">deviceShaken()</a> function is called when the device total acceleration
        - * changes of accelerationX and accelerationY values is more than
        - * the threshold value. The default threshold is set to 30.
        - * The threshold value can be changed using <a href="#/p5/setShakeThreshold">setShakeThreshold()</a>.
        - *
        - * @method deviceShaken
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Run this example on a mobile device
        - * // Shake the device to change the value.
        - *
        - * let value = 0;
        - * function draw() {
        - *   fill(value);
        - *   rect(25, 25, 50, 50);
        - *   describe(`50-by-50 black rect in center of canvas.
        - *     turns white on mobile when device shakes`);
        - * }
        - * function deviceShaken() {
        - *   value = value + 5;
        - *   if (value > 255) {
        - *     value = 0;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -
        -p5.prototype._ondeviceorientation = function(e) {
        -  this._updatePRotations();
        -
        -  // Convert from degrees into current angle mode
        -  this._setProperty('rotationX', this._fromDegrees(e.beta));
        -  this._setProperty('rotationY', this._fromDegrees(e.gamma));
        -  this._setProperty('rotationZ', this._fromDegrees(e.alpha));
        -  this._handleMotion();
        -};
        -p5.prototype._ondevicemotion = function(e) {
        -  this._updatePAccelerations();
        -  this._setProperty('accelerationX', e.acceleration.x * 2);
        -  this._setProperty('accelerationY', e.acceleration.y * 2);
        -  this._setProperty('accelerationZ', e.acceleration.z * 2);
        -  this._handleMotion();
        -};
        -p5.prototype._handleMotion = function() {
        -  if (window.orientation === 90 || window.orientation === -90) {
        -    this._setProperty('deviceOrientation', 'landscape');
        -  } else if (window.orientation === 0) {
        -    this._setProperty('deviceOrientation', 'portrait');
        -  } else if (window.orientation === undefined) {
        -    this._setProperty('deviceOrientation', 'undefined');
        -  }
        -  const context = this._isGlobal ? window : this;
        -  if (typeof context.deviceMoved === 'function') {
        -    if (
        -      Math.abs(this.accelerationX - this.pAccelerationX) > move_threshold ||
        -      Math.abs(this.accelerationY - this.pAccelerationY) > move_threshold ||
        -      Math.abs(this.accelerationZ - this.pAccelerationZ) > move_threshold
        -    ) {
        -      context.deviceMoved();
        -    }
        -  }
        -
        -  if (typeof context.deviceTurned === 'function') {
        -    // The angles given by rotationX etc is from range [-180 to 180].
        -    // The following will convert them to [0 to 360] for ease of calculation
        -    // of cases when the angles wrapped around.
        -    // _startAngleX will be converted back at the end and updated.
        -
        -    // Rotations are converted to degrees and all calculations are done in degrees
        -    const wRX = this._toDegrees(this.rotationX) + 180;
        -    const wPRX = this._toDegrees(this.pRotationX) + 180;
        -    let wSAX = startAngleX + 180;
        -    if ((wRX - wPRX > 0 && wRX - wPRX < 270) || wRX - wPRX < -270) {
        -      rotateDirectionX = 'clockwise';
        -    } else if (wRX - wPRX < 0 || wRX - wPRX > 270) {
        -      rotateDirectionX = 'counter-clockwise';
        +function acceleration(p5, fn){
        +  /**
        +   * The system variable deviceOrientation always contains the orientation of
        +   * the device. The value of this variable will either be set 'landscape'
        +   * or 'portrait'. If no data is available it will be set to 'undefined'.
        +   * either LANDSCAPE or PORTRAIT.
        +   *
        +   * @property {(LANDSCAPE|PORTRAIT)} deviceOrientation
        +   * @readOnly
        +   */
        +  fn.deviceOrientation =
        +    window.innerWidth / window.innerHeight > 1.0 ? 'landscape' : 'portrait';
        +
        +  /**
        +   * The system variable accelerationX always contains the acceleration of the
        +   * device along the x axis. Value is represented as meters per second squared.
        +   *
        +   * @property {Number} accelerationX
        +   * @readOnly
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Move a touchscreen device to register
        +   * // acceleration changes.
        +   * function draw() {
        +   *   background(220, 50);
        +   *   fill('magenta');
        +   *   ellipse(width / 2, height / 2, accelerationX);
        +   *   describe('Magnitude of device acceleration is displayed as ellipse size.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.accelerationX = 0;
        +
        +  /**
        +   * The system variable accelerationY always contains the acceleration of the
        +   * device along the y axis. Value is represented as meters per second squared.
        +   *
        +   * @property {Number} accelerationY
        +   * @readOnly
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Move a touchscreen device to register
        +   * // acceleration changes.
        +   * function draw() {
        +   *   background(220, 50);
        +   *   fill('magenta');
        +   *   ellipse(width / 2, height / 2, accelerationY);
        +   *   describe('Magnitude of device acceleration is displayed as ellipse size');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.accelerationY = 0;
        +
        +  /**
        +   * The system variable accelerationZ always contains the acceleration of the
        +   * device along the z axis. Value is represented as meters per second squared.
        +   *
        +   * @property {Number} accelerationZ
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Move a touchscreen device to register
        +   * // acceleration changes.
        +   * function draw() {
        +   *   background(220, 50);
        +   *   fill('magenta');
        +   *   ellipse(width / 2, height / 2, accelerationZ);
        +   *   describe('Magnitude of device acceleration is displayed as ellipse size');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.accelerationZ = 0;
        +
        +  /**
        +   * The system variable pAccelerationX always contains the acceleration of the
        +   * device along the x axis in the frame previous to the current frame. Value
        +   * is represented as meters per second squared.
        +   *
        +   * @property {Number} pAccelerationX
        +   * @readOnly
        +   */
        +  fn.pAccelerationX = 0;
        +
        +  /**
        +   * The system variable pAccelerationY always contains the acceleration of the
        +   * device along the y axis in the frame previous to the current frame. Value
        +   * is represented as meters per second squared.
        +   *
        +   * @property {Number} pAccelerationY
        +   * @readOnly
        +   */
        +  fn.pAccelerationY = 0;
        +
        +  /**
        +   * The system variable pAccelerationZ always contains the acceleration of the
        +   * device along the z axis in the frame previous to the current frame. Value
        +   * is represented as meters per second squared.
        +   *
        +   * @property {Number} pAccelerationZ
        +   * @readOnly
        +   */
        +  fn.pAccelerationZ = 0;
        +
        +  /**
        +   * _updatePAccelerations updates the pAcceleration values
        +   *
        +   * @private
        +   */
        +  fn._updatePAccelerations = function () {
        +    this.pAccelerationX = this.accelerationX;
        +    this.pAccelerationY = this.accelerationY;
        +    this.pAccelerationZ = this.accelerationZ;
        +  };
        +
        +  /**
        +   * The system variable rotationX always contains the rotation of the
        +   * device along the x axis. If the sketch <a href="#/p5/angleMode">
        +   * angleMode()</a> is set to DEGREES, the value will be -180 to 180. If
        +   * it is set to RADIANS, the value will be -PI to PI.
        +   *
        +   * Note: The order the rotations are called is important, ie. if used
        +   * together, it must be called in the order Z-X-Y or there might be
        +   * unexpected behaviour.
        +   *
        +   * @property {Number} rotationX
        +   * @readOnly
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *   //rotateZ(radians(rotationZ));
        +   *   rotateX(radians(rotationX));
        +   *   //rotateY(radians(rotationY));
        +   *   box(200, 200, 200);
        +   *   describe(`red horizontal line right, green vertical line bottom.
        +   *     black background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.rotationX = 0;
        +
        +  /**
        +   * The system variable rotationY always contains the rotation of the
        +   * device along the y axis. If the sketch <a href="#/p5/angleMode">
        +   * angleMode()</a> is set to DEGREES, the value will be -90 to 90. If
        +   * it is set to RADIANS, the value will be -PI/2 to PI/2.
        +   *
        +   * Note: The order the rotations are called is important, ie. if used
        +   * together, it must be called in the order Z-X-Y or there might be
        +   * unexpected behaviour.
        +   *
        +   * @property {Number} rotationY
        +   * @readOnly
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *   //rotateZ(radians(rotationZ));
        +   *   //rotateX(radians(rotationX));
        +   *   rotateY(radians(rotationY));
        +   *   box(200, 200, 200);
        +   *   describe(`red horizontal line right, green vertical line bottom.
        +   *     black background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.rotationY = 0;
        +
        +  /**
        +   * The system variable rotationZ always contains the rotation of the
        +   * device along the z axis. If the sketch <a href="#/p5/angleMode">
        +   * angleMode()</a> is set to DEGREES, the value will be 0 to 360. If
        +   * it is set to RADIANS, the value will be 0 to 2*PI.
        +   *
        +   * Unlike rotationX and rotationY, this variable is available for devices
        +   * with a built-in compass only.
        +   *
        +   * Note: The order the rotations are called is important, ie. if used
        +   * together, it must be called in the order Z-X-Y or there might be
        +   * unexpected behaviour.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *   rotateZ(radians(rotationZ));
        +   *   //rotateX(radians(rotationX));
        +   *   //rotateY(radians(rotationY));
        +   *   box(200, 200, 200);
        +   *   describe(`red horizontal line right, green vertical line bottom.
        +   *     black background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @property {Number} rotationZ
        +   * @readOnly
        +   */
        +  fn.rotationZ = 0;
        +
        +  /**
        +   * The system variable pRotationX always contains the rotation of the
        +   * device along the x axis in the frame previous to the current frame.
        +   * If the sketch <a href="#/p5/angleMode"> angleMode()</a> is set to DEGREES,
        +   * the value will be -180 to 180. If it is set to RADIANS, the value will
        +   * be -PI to PI.
        +   *
        +   * pRotationX can also be used with rotationX to determine the rotate
        +   * direction of the device along the X-axis.
        +   * @example
        +   * <div class='norender'>
        +   * <code>
        +   * // A simple if statement looking at whether
        +   * // rotationX - pRotationX < 0 is true or not will be
        +   * // sufficient for determining the rotate direction
        +   * // in most cases.
        +   *
        +   * // Some extra logic is needed to account for cases where
        +   * // the angles wrap around.
        +   * let rotateDirection = 'clockwise';
        +   *
        +   * // Simple range conversion to make things simpler.
        +   * // This is not absolutely necessary but the logic
        +   * // will be different in that case.
        +   *
        +   * let rX = rotationX + 180;
        +   * let pRX = pRotationX + 180;
        +   *
        +   * if ((rX - pRX > 0 && rX - pRX < 270) || rX - pRX < -270) {
        +   *   rotateDirection = 'clockwise';
        +   * } else if (rX - pRX < 0 || rX - pRX > 270) {
        +   *   rotateDirection = 'counter-clockwise';
        +   * }
        +   *
        +   * print(rotateDirection);
        +   * describe('no image to display.');
        +   * </code>
        +   * </div>
        +   *
        +   * @property {Number} pRotationX
        +   * @readOnly
        +   */
        +  fn.pRotationX = 0;
        +
        +  /**
        +   * The system variable pRotationY always contains the rotation of the
        +   * device along the y axis in the frame previous to the current frame.
        +   * If the sketch <a href="#/p5/angleMode"> angleMode()</a> is set to DEGREES,
        +   * the value will be -90 to 90. If it is set to RADIANS, the value will
        +   * be -PI/2 to PI/2.
        +   *
        +   * pRotationY can also be used with rotationY to determine the rotate
        +   * direction of the device along the Y-axis.
        +   * @example
        +   * <div class='norender'>
        +   * <code>
        +   * // A simple if statement looking at whether
        +   * // rotationY - pRotationY < 0 is true or not will be
        +   * // sufficient for determining the rotate direction
        +   * // in most cases.
        +   *
        +   * // Some extra logic is needed to account for cases where
        +   * // the angles wrap around.
        +   * let rotateDirection = 'clockwise';
        +   *
        +   * // Simple range conversion to make things simpler.
        +   * // This is not absolutely necessary but the logic
        +   * // will be different in that case.
        +   *
        +   * let rY = rotationY + 180;
        +   * let pRY = pRotationY + 180;
        +   *
        +   * if ((rY - pRY > 0 && rY - pRY < 270) || rY - pRY < -270) {
        +   *   rotateDirection = 'clockwise';
        +   * } else if (rY - pRY < 0 || rY - pRY > 270) {
        +   *   rotateDirection = 'counter-clockwise';
        +   * }
        +   * print(rotateDirection);
        +   * describe('no image to display.');
        +   * </code>
        +   * </div>
        +   *
        +   * @property {Number} pRotationY
        +   * @readOnly
        +   */
        +  fn.pRotationY = 0;
        +
        +  /**
        +   * The system variable pRotationZ always contains the rotation of the
        +   * device along the z axis in the frame previous to the current frame.
        +   * If the sketch <a href="#/p5/angleMode"> angleMode()</a> is set to DEGREES,
        +   * the value will be 0 to 360. If it is set to RADIANS, the value will
        +   * be 0 to 2*PI.
        +   *
        +   * pRotationZ can also be used with rotationZ to determine the rotate
        +   * direction of the device along the Z-axis.
        +   * @example
        +   * <div class='norender'>
        +   * <code>
        +   * // A simple if statement looking at whether
        +   * // rotationZ - pRotationZ < 0 is true or not will be
        +   * // sufficient for determining the rotate direction
        +   * // in most cases.
        +   *
        +   * // Some extra logic is needed to account for cases where
        +   * // the angles wrap around.
        +   * let rotateDirection = 'clockwise';
        +   *
        +   * if (
        +   *   (rotationZ - pRotationZ > 0 && rotationZ - pRotationZ < 270) ||
        +   *   rotationZ - pRotationZ < -270
        +   * ) {
        +   *   rotateDirection = 'clockwise';
        +   * } else if (rotationZ - pRotationZ < 0 || rotationZ - pRotationZ > 270) {
        +   *   rotateDirection = 'counter-clockwise';
        +   * }
        +   * print(rotateDirection);
        +   * describe('no image to display.');
        +   * </code>
        +   * </div>
        +   *
        +   * @property {Number} pRotationZ
        +   * @readOnly
        +   */
        +  fn.pRotationZ = 0;
        +
        +  let startAngleX = 0;
        +  let startAngleY = 0;
        +  let startAngleZ = 0;
        +
        +  let rotateDirectionX = 'clockwise';
        +  let rotateDirectionY = 'clockwise';
        +  let rotateDirectionZ = 'clockwise';
        +
        +  fn.pRotateDirectionX = undefined;
        +  fn.pRotateDirectionY = undefined;
        +  fn.pRotateDirectionZ = undefined;
        +
        +  fn._updatePRotations = function () {
        +    this.pRotationX = this.rotationX;
        +    this.pRotationY = this.rotationY;
        +    this.pRotationZ = this.rotationZ;
        +  };
        +
        +  /**
        +   * When a device is rotated, the axis that triggers the <a href="#/p5/deviceTurned">deviceTurned()</a>
        +   * method is stored in the turnAxis variable. The turnAxis variable is only defined within
        +   * the scope of deviceTurned().
        +   * @property {String} turnAxis
        +   * @readOnly
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Run this example on a mobile device
        +   * // Rotate the device by 90 degrees in the
        +   * // X-axis to change the value.
        +   *
        +   * let value = 0;
        +   * function draw() {
        +   *   fill(value);
        +   *   rect(25, 25, 50, 50);
        +   *   describe(`50-by-50 black rect in center of canvas.
        +   *     turns white on mobile when device turns`);
        +   *   describe(`50-by-50 black rect in center of canvas.
        +   *     turns white on mobile when x-axis turns`);
        +   * }
        +   * function deviceTurned() {
        +   *   if (turnAxis === 'X') {
        +   *     if (value === 0) {
        +   *       value = 255;
        +   *     } else if (value === 255) {
        +   *       value = 0;
        +   *     }
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.turnAxis = undefined;
        +
        +  let move_threshold = 0.5;
        +  let shake_threshold = 30;
        +
        +  /**
        +   * The <a href="#/p5/setMoveThreshold">setMoveThreshold()</a> function is used to set the movement threshold for
        +   * the <a href="#/p5/deviceMoved">deviceMoved()</a> function. The default threshold is set to 0.5.
        +   *
        +   * @method setMoveThreshold
        +   * @param {Number} value The threshold value
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * // Run this example on a mobile device
        +   * // You will need to move the device incrementally further
        +   * // the closer the square's color gets to white in order to change the value.
        +   *
        +   * let value = 0;
        +   * let threshold = 0.5;
        +   * function setup() {
        +   *   setMoveThreshold(threshold);
        +   * }
        +   * function draw() {
        +   *   fill(value);
        +   *   rect(25, 25, 50, 50);
        +   *   describe(`50-by-50 black rect in center of canvas.
        +   *     turns white on mobile when device moves`);
        +   * }
        +   * function deviceMoved() {
        +   *   value = value + 5;
        +   *   threshold = threshold + 0.1;
        +   *   if (value > 255) {
        +   *     value = 0;
        +   *     threshold = 30;
        +   *   }
        +   *   setMoveThreshold(threshold);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  fn.setMoveThreshold = function (val) {
        +    // p5._validateParameters('setMoveThreshold', arguments);
        +    move_threshold = val;
        +  };
        +
        +  /**
        +   * The <a href="#/p5/setShakeThreshold">setShakeThreshold()</a> function is used to set the movement threshold for
        +   * the <a href="#/p5/deviceShaken">deviceShaken()</a> function. The default threshold is set to 30.
        +   *
        +   * @method setShakeThreshold
        +   * @param {Number} value The threshold value
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * // Run this example on a mobile device
        +   * // You will need to shake the device more firmly
        +   * // the closer the box's fill gets to white in order to change the value.
        +   *
        +   * let value = 0;
        +   * let threshold = 30;
        +   * function setup() {
        +   *   setShakeThreshold(threshold);
        +   * }
        +   * function draw() {
        +   *   fill(value);
        +   *   rect(25, 25, 50, 50);
        +   *   describe(`50-by-50 black rect in center of canvas.
        +   *     turns white on mobile when device is being shaked`);
        +   * }
        +   * function deviceMoved() {
        +   *   value = value + 5;
        +   *   threshold = threshold + 5;
        +   *   if (value > 255) {
        +   *     value = 0;
        +   *     threshold = 30;
        +   *   }
        +   *   setShakeThreshold(threshold);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  fn.setShakeThreshold = function (val) {
        +    // p5._validateParameters('setShakeThreshold', arguments);
        +    shake_threshold = val;
        +  };
        +
        +  /**
        +   * The <a href="#/p5/deviceMoved">deviceMoved()</a> function is called when the device is moved by more than
        +   * the threshold value along X, Y or Z axis. The default threshold is set to 0.5.
        +   * The threshold value can be changed using <a href="https://p5js.org/reference/#/p5/setMoveThreshold">setMoveThreshold()</a>.
        +   *
        +   * @method deviceMoved
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * // Run this example on a mobile device
        +   * // Move the device around
        +   * // to change the value.
        +   *
        +   * let value = 0;
        +   * function draw() {
        +   *   fill(value);
        +   *   rect(25, 25, 50, 50);
        +   *   describe(`50-by-50 black rect in center of canvas.
        +   *     turns white on mobile when device moves`);
        +   * }
        +   * function deviceMoved() {
        +   *   value = value + 5;
        +   *   if (value > 255) {
        +   *     value = 0;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * The <a href="#/p5/deviceTurned">deviceTurned()</a> function is called when the device rotates by
        +   * more than 90 degrees continuously.
        +   *
        +   * The axis that triggers the <a href="#/p5/deviceTurned">deviceTurned()</a> method is stored in the turnAxis
        +   * variable. The <a href="#/p5/deviceTurned">deviceTurned()</a> method can be locked to trigger on any axis:
        +   * X, Y or Z by comparing the turnAxis variable to 'X', 'Y' or 'Z'.
        +   *
        +   * @method deviceTurned
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * // Run this example on a mobile device
        +   * // Rotate the device by 90 degrees
        +   * // to change the value.
        +   *
        +   * let value = 0;
        +   * function draw() {
        +   *   fill(value);
        +   *   rect(25, 25, 50, 50);
        +   *   describe(`50-by-50 black rect in center of canvas.
        +   *     turns white on mobile when device turns`);
        +   * }
        +   * function deviceTurned() {
        +   *   if (value === 0) {
        +   *     value = 255;
        +   *   } else if (value === 255) {
        +   *     value = 0;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   * <div>
        +   * <code>
        +   * // Run this example on a mobile device
        +   * // Rotate the device by 90 degrees in the
        +   * // X-axis to change the value.
        +   *
        +   * let value = 0;
        +   * function draw() {
        +   *   fill(value);
        +   *   rect(25, 25, 50, 50);
        +   *   describe(`50-by-50 black rect in center of canvas.
        +   *     turns white on mobile when x-axis turns`);
        +   * }
        +   * function deviceTurned() {
        +   *   if (turnAxis === 'X') {
        +   *     if (value === 0) {
        +   *       value = 255;
        +   *     } else if (value === 255) {
        +   *       value = 0;
        +   *     }
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * The <a href="#/p5/deviceShaken">deviceShaken()</a> function is called when the device total acceleration
        +   * changes of accelerationX and accelerationY values is more than
        +   * the threshold value. The default threshold is set to 30.
        +   * The threshold value can be changed using <a href="https://p5js.org/reference/#/p5/setShakeThreshold">setShakeThreshold()</a>.
        +   *
        +   * @method deviceShaken
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * // Run this example on a mobile device
        +   * // Shake the device to change the value.
        +   *
        +   * let value = 0;
        +   * function draw() {
        +   *   fill(value);
        +   *   rect(25, 25, 50, 50);
        +   *   describe(`50-by-50 black rect in center of canvas.
        +   *     turns white on mobile when device shakes`);
        +   * }
        +   * function deviceShaken() {
        +   *   value = value + 5;
        +   *   if (value > 255) {
        +   *     value = 0;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  fn._ondeviceorientation = function (e) {
        +    this._updatePRotations();
        +
        +    // Convert from degrees into current angle mode
        +    this.rotationX = this._fromDegrees(e.beta);
        +    this.rotationY = this._fromDegrees(e.gamma);
        +    this.rotationZ = this._fromDegrees(e.alpha);
        +    this._handleMotion();
        +  };
        +  fn._ondevicemotion = function (e) {
        +    this._updatePAccelerations();
        +    this.accelerationX = e.acceleration.x * 2;
        +    this.accelerationY = e.acceleration.y * 2;
        +    this.accelerationZ = e.acceleration.z * 2;
        +    this._handleMotion();
        +  };
        +  fn._handleMotion = function () {
        +    if (window.orientation === 90 || window.orientation === -90) {
        +      this.deviceOrientation = 'landscape';
        +    } else if (window.orientation === 0) {
        +      this.deviceOrientation = 'portrait';
        +    } else if (window.orientation === undefined) {
        +      this.deviceOrientation = 'undefined';
             }
        -    if (rotateDirectionX !== this.pRotateDirectionX) {
        -      wSAX = wRX;
        +    const context = this._isGlobal ? window : this;
        +    if (typeof context.deviceMoved === 'function') {
        +      if (
        +        Math.abs(this.accelerationX - this.pAccelerationX) > move_threshold ||
        +        Math.abs(this.accelerationY - this.pAccelerationY) > move_threshold ||
        +        Math.abs(this.accelerationZ - this.pAccelerationZ) > move_threshold
        +      ) {
        +        context.deviceMoved();
        +      }
             }
        -    if (Math.abs(wRX - wSAX) > 90 && Math.abs(wRX - wSAX) < 270) {
        -      wSAX = wRX;
        -      this._setProperty('turnAxis', 'X');
        -      context.deviceTurned();
        -    }
        -    this.pRotateDirectionX = rotateDirectionX;
        -    startAngleX = wSAX - 180;
        -
        -    // Y-axis is identical to X-axis except for changing some names.
        -    const wRY = this._toDegrees(this.rotationY) + 180;
        -    const wPRY = this._toDegrees(this.pRotationY) + 180;
        -    let wSAY = startAngleY + 180;
        -    if ((wRY - wPRY > 0 && wRY - wPRY < 270) || wRY - wPRY < -270) {
        -      rotateDirectionY = 'clockwise';
        -    } else if (wRY - wPRY < 0 || wRY - this.pRotationY > 270) {
        -      rotateDirectionY = 'counter-clockwise';
        -    }
        -    if (rotateDirectionY !== this.pRotateDirectionY) {
        -      wSAY = wRY;
        -    }
        -    if (Math.abs(wRY - wSAY) > 90 && Math.abs(wRY - wSAY) < 270) {
        -      wSAY = wRY;
        -      this._setProperty('turnAxis', 'Y');
        -      context.deviceTurned();
        -    }
        -    this.pRotateDirectionY = rotateDirectionY;
        -    startAngleY = wSAY - 180;
        -
        -    // Z-axis is already in the range 0 to 360
        -    // so no conversion is needed.
        -    const rotZ = this._toDegrees(this.rotationZ);
        -    const pRotZ = this._toDegrees(this.pRotationZ);
        -    if (
        -      (rotZ - pRotZ > 0 && rotZ - pRotZ < 270) ||
        -      rotZ - pRotZ < -270
        -    ) {
        -      rotateDirectionZ = 'clockwise';
        -    } else if (
        -      rotZ - pRotZ < 0 ||
        -      rotZ - pRotZ > 270
        -    ) {
        -      rotateDirectionZ = 'counter-clockwise';
        -    }
        -    if (rotateDirectionZ !== this.pRotateDirectionZ) {
        -      startAngleZ = rotZ;
        -    }
        -    if (
        -      Math.abs(rotZ - startAngleZ) > 90 &&
        -      Math.abs(rotZ - startAngleZ) < 270
        -    ) {
        -      startAngleZ = rotZ;
        -      this._setProperty('turnAxis', 'Z');
        -      context.deviceTurned();
        -    }
        -    this.pRotateDirectionZ = rotateDirectionZ;
        -    this._setProperty('turnAxis', undefined);
        -  }
        -  if (typeof context.deviceShaken === 'function') {
        -    let accelerationChangeX;
        -    let accelerationChangeY;
        -    // Add accelerationChangeZ if acceleration change on Z is needed
        -    if (this.pAccelerationX !== null) {
        -      accelerationChangeX = Math.abs(this.accelerationX - this.pAccelerationX);
        -      accelerationChangeY = Math.abs(this.accelerationY - this.pAccelerationY);
        +
        +    if (typeof context.deviceTurned === 'function') {
        +      // The angles given by rotationX etc is from range [-180 to 180].
        +      // The following will convert them to [0 to 360] for ease of calculation
        +      // of cases when the angles wrapped around.
        +      // _startAngleX will be converted back at the end and updated.
        +
        +      // Rotations are converted to degrees and all calculations are done in degrees
        +      const wRX = this._toDegrees(this.rotationX) + 180;
        +      const wPRX = this._toDegrees(this.pRotationX) + 180;
        +      let wSAX = startAngleX + 180;
        +      if ((wRX - wPRX > 0 && wRX - wPRX < 270) || wRX - wPRX < -270) {
        +        rotateDirectionX = 'clockwise';
        +      } else if (wRX - wPRX < 0 || wRX - wPRX > 270) {
        +        rotateDirectionX = 'counter-clockwise';
        +      }
        +      if (rotateDirectionX !== this.pRotateDirectionX) {
        +        wSAX = wRX;
        +      }
        +      if (Math.abs(wRX - wSAX) > 90 && Math.abs(wRX - wSAX) < 270) {
        +        wSAX = wRX;
        +        this.turnAxis = 'X';
        +        context.deviceTurned();
        +      }
        +      this.pRotateDirectionX = rotateDirectionX;
        +      startAngleX = wSAX - 180;
        +
        +      // Y-axis is identical to X-axis except for changing some names.
        +      const wRY = this._toDegrees(this.rotationY) + 180;
        +      const wPRY = this._toDegrees(this.pRotationY) + 180;
        +      let wSAY = startAngleY + 180;
        +      if ((wRY - wPRY > 0 && wRY - wPRY < 270) || wRY - wPRY < -270) {
        +        rotateDirectionY = 'clockwise';
        +      } else if (wRY - wPRY < 0 || wRY - this.pRotationY > 270) {
        +        rotateDirectionY = 'counter-clockwise';
        +      }
        +      if (rotateDirectionY !== this.pRotateDirectionY) {
        +        wSAY = wRY;
        +      }
        +      if (Math.abs(wRY - wSAY) > 90 && Math.abs(wRY - wSAY) < 270) {
        +        wSAY = wRY;
        +        this.turnAxis = 'Y';
        +        context.deviceTurned();
        +      }
        +      this.pRotateDirectionY = rotateDirectionY;
        +      startAngleY = wSAY - 180;
        +
        +      // Z-axis is already in the range 0 to 360
        +      // so no conversion is needed.
        +      const rotZ = this._toDegrees(this.rotationZ);
        +      const pRotZ = this._toDegrees(this.pRotationZ);
        +      if (
        +        (rotZ - pRotZ > 0 && rotZ - pRotZ < 270) ||
        +        rotZ - pRotZ < -270
        +      ) {
        +        rotateDirectionZ = 'clockwise';
        +      } else if (
        +        rotZ - pRotZ < 0 ||
        +        rotZ - pRotZ > 270
        +      ) {
        +        rotateDirectionZ = 'counter-clockwise';
        +      }
        +      if (rotateDirectionZ !== this.pRotateDirectionZ) {
        +        startAngleZ = rotZ;
        +      }
        +      if (
        +        Math.abs(rotZ - startAngleZ) > 90 &&
        +        Math.abs(rotZ - startAngleZ) < 270
        +      ) {
        +        startAngleZ = rotZ;
        +        this.turnAxis = 'Z';
        +        context.deviceTurned();
        +      }
        +      this.pRotateDirectionZ = rotateDirectionZ;
        +      this.turnAxis = undefined;
             }
        -    if (accelerationChangeX + accelerationChangeY > shake_threshold) {
        -      context.deviceShaken();
        +    if (typeof context.deviceShaken === 'function') {
        +      let accelerationChangeX;
        +      let accelerationChangeY;
        +      // Add accelerationChangeZ if acceleration change on Z is needed
        +      if (this.pAccelerationX !== null) {
        +        accelerationChangeX = Math.abs(this.accelerationX - this.pAccelerationX);
        +        accelerationChangeY = Math.abs(this.accelerationY - this.pAccelerationY);
        +      }
        +      if (accelerationChangeX + accelerationChangeY > shake_threshold) {
        +        context.deviceShaken();
        +      }
             }
        -  }
        -};
        +  };
        +}
        +
        +export default acceleration;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  acceleration(p5, p5.prototype);
        +}
        diff --git a/src/events/index.js b/src/events/index.js
        new file mode 100644
        index 0000000000..447f3d0d10
        --- /dev/null
        +++ b/src/events/index.js
        @@ -0,0 +1,9 @@
        +import acceleration from './acceleration.js';
        +import keyboard from './keyboard.js';
        +import pointer from './pointer.js';
        +
        +export default function(p5){
        +  p5.registerAddon(acceleration);
        +  p5.registerAddon(keyboard);
        +  p5.registerAddon(pointer);
        +}
        diff --git a/src/events/keyboard.js b/src/events/keyboard.js
        index f6e3b04703..dd3b351e9b 100644
        --- a/src/events/keyboard.js
        +++ b/src/events/keyboard.js
        @@ -5,939 +5,939 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        +function keyboard(p5, fn){
        +  /**
        +   * A `Boolean` system variable that's `true` if any key is currently pressed
        +   * and `false` if not.
        +   *
        +   * @property {Boolean} keyIsPressed
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a white square at its center. The white square turns black when the user presses a key.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   if (keyIsPressed === true) {
        +   *     fill(0);
        +   *   } else {
        +   *     fill(255);
        +   *   }
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a white square at its center. The white square turns black when the user presses a key.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   if (keyIsPressed) {
        +   *     fill(0);
        +   *   } else {
        +   *     fill(255);
        +   *   }
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with the word "false" at its center. The word switches to "true" when the user presses a key.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the value of keyIsPressed.
        +   *   text(keyIsPressed, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.keyIsPressed = false;
         
        -/**
        - * A `Boolean` system variable that's `true` if any key is currently pressed
        - * and `false` if not.
        - *
        - * @property {Boolean} keyIsPressed
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a white square at its center. The white square turns black when the user presses a key.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   if (keyIsPressed === true) {
        - *     fill(0);
        - *   } else {
        - *     fill(255);
        - *   }
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a white square at its center. The white square turns black when the user presses a key.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   if (keyIsPressed) {
        - *     fill(0);
        - *   } else {
        - *     fill(255);
        - *   }
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with the word "false" at its center. The word switches to "true" when the user presses a key.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the value of keyIsPressed.
        - *   text(keyIsPressed, 50, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.isKeyPressed = false;
        -p5.prototype.keyIsPressed = false; // khan
        +  /**
        +   * A `String` system variable that contains the value of the last key typed.
        +   *
        +   * The key variable is helpful for checking whether an
        +   * <a href="https://en.wikipedia.org/wiki/ASCII#Printable_characters" target="_blank">ASCII</a>
        +   * key has been typed. For example, the expression `key === "a"` evaluates to
        +   * `true` if the `a` key was typed and `false` if not. `key` doesn’t update
        +   * for special keys such as `LEFT_ARROW` and `ENTER`. Use keyCode instead for
        +   * special keys. The <a href="#/p5/keyIsDown">keyIsDown()</a> function should
        +   * be used to check for multiple different key presses at the same time.
        +   *
        +   * @property {String} key
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square. The last key pressed is displayed at the center.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the last key pressed.
        +   *   text(key, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * let x = 50;
        +   * let y = 50;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe(
        +   *     'A gray square with a black circle at its center. The circle moves when the user presses the keys "w", "a", "s", or "d". It leaves a trail as it moves.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   // Update x and y if a key is pressed.
        +   *   if (keyIsPressed === true) {
        +   *     if (key === 'w') {
        +   *       y -= 1;
        +   *     } else if (key === 's') {
        +   *       y += 1;
        +   *     } else if (key === 'a') {
        +   *       x -= 1;
        +   *     } else if (key === 'd') {
        +   *       x += 1;
        +   *     }
        +   *   }
        +   *
        +   *   // Style the circle.
        +   *   fill(0);
        +   *
        +   *   // Draw the circle at (x, y).
        +   *   circle(x, y, 5);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.key = '';
         
        -/**
        - * A `String` system variable that contains the value of the last key typed.
        - *
        - * The key variable is helpful for checking whether an
        - * <a href="https://en.wikipedia.org/wiki/ASCII#Printable_characters" target="_blank">ASCII</a>
        - * key has been typed. For example, the expression `key === "a"` evaluates to
        - * `true` if the `a` key was typed and `false` if not. `key` doesn’t update
        - * for special keys such as `LEFT_ARROW` and `ENTER`. Use keyCode instead for
        - * special keys. The <a href="#/p5/keyIsDown">keyIsDown()</a> function should
        - * be used to check for multiple different key presses at the same time.
        - *
        - * @property {String} key
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square. The last key pressed is displayed at the center.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the last key pressed.
        - *   text(key, 50, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * let x = 50;
        - * let y = 50;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe(
        - *     'A gray square with a black circle at its center. The circle moves when the user presses the keys "w", "a", "s", or "d". It leaves a trail as it moves.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   // Update x and y if a key is pressed.
        - *   if (keyIsPressed === true) {
        - *     if (key === 'w') {
        - *       y -= 1;
        - *     } else if (key === 's') {
        - *       y += 1;
        - *     } else if (key === 'a') {
        - *       x -= 1;
        - *     } else if (key === 'd') {
        - *       x += 1;
        - *     }
        - *   }
        - *
        - *   // Style the circle.
        - *   fill(0);
        - *
        - *   // Draw the circle at (x, y).
        - *   circle(x, y, 5);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.key = '';
        +  /**
        +   * A `Number` system variable that contains the code of the last key typed.
        +   *
        +   * All keys have a `keyCode`. For example, the `a` key has the `keyCode` 65.
        +   * The `keyCode` variable is helpful for checking whether a special key has
        +   * been typed. For example, the following conditional checks whether the enter
        +   * key has been typed:
        +   *
        +   * ```js
        +   * if (keyCode === 13) {
        +   *   // Code to run if the enter key was pressed.
        +   * }
        +   * ```
        +   *
        +   * The same code can be written more clearly using the system variable `ENTER`
        +   * which has a value of 13:
        +   *
        +   * ```js
        +   * if (keyCode === ENTER) {
        +   *   // Code to run if the enter key was pressed.
        +   * }
        +   * ```
        +   *
        +   * The system variables `BACKSPACE`, `DELETE`, `ENTER`, `RETURN`, `TAB`,
        +   * `ESCAPE`, `SHIFT`, `CONTROL`, `OPTION`, `ALT`, `UP_ARROW`, `DOWN_ARROW`,
        +   * `LEFT_ARROW`, and `RIGHT_ARROW` are all helpful shorthands the key codes of
        +   * special keys. Key codes can be found on websites such as
        +   * <a href="http://keycode.info/">keycode.info</a>.
        +   *
        +   * @property {Integer} keyCode
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square. The last key pressed and its code are displayed at the center.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the last key pressed and its code.
        +   *   text(`${key} : ${keyCode}`, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * let x = 50;
        +   * let y = 50;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe(
        +   *     'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   // Update x and y if an arrow key is pressed.
        +   *   if (keyIsPressed === true) {
        +   *     if (keyCode === UP_ARROW) {
        +   *       y -= 1;
        +   *     } else if (keyCode === DOWN_ARROW) {
        +   *       y += 1;
        +   *     } else if (keyCode === LEFT_ARROW) {
        +   *       x -= 1;
        +   *     } else if (keyCode === RIGHT_ARROW) {
        +   *       x += 1;
        +   *     }
        +   *   }
        +   *
        +   *   // Style the circle.
        +   *   fill(0);
        +   *
        +   *   // Draw the circle at (x, y).
        +   *   circle(x, y, 5);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.keyCode = 0;
         
        -/**
        - * A `Number` system variable that contains the code of the last key typed.
        - *
        - * All keys have a `keyCode`. For example, the `a` key has the `keyCode` 65.
        - * The `keyCode` variable is helpful for checking whether a special key has
        - * been typed. For example, the following conditional checks whether the enter
        - * key has been typed:
        - *
        - * ```js
        - * if (keyCode === 13) {
        - *   // Code to run if the enter key was pressed.
        - * }
        - * ```
        - *
        - * The same code can be written more clearly using the system variable `ENTER`
        - * which has a value of 13:
        - *
        - * ```js
        - * if (keyCode === ENTER) {
        - *   // Code to run if the enter key was pressed.
        - * }
        - * ```
        - *
        - * The system variables `BACKSPACE`, `DELETE`, `ENTER`, `RETURN`, `TAB`,
        - * `ESCAPE`, `SHIFT`, `CONTROL`, `OPTION`, `ALT`, `UP_ARROW`, `DOWN_ARROW`,
        - * `LEFT_ARROW`, and `RIGHT_ARROW` are all helpful shorthands the key codes of
        - * special keys. Key codes can be found on websites such as
        - * <a href="http://keycode.info/">keycode.info</a>.
        - *
        - * @property {Integer} keyCode
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square. The last key pressed and its code are displayed at the center.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the last key pressed and its code.
        - *   text(`${key} : ${keyCode}`, 50, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * let x = 50;
        - * let y = 50;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe(
        - *     'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   // Update x and y if an arrow key is pressed.
        - *   if (keyIsPressed === true) {
        - *     if (keyCode === UP_ARROW) {
        - *       y -= 1;
        - *     } else if (keyCode === DOWN_ARROW) {
        - *       y += 1;
        - *     } else if (keyCode === LEFT_ARROW) {
        - *       x -= 1;
        - *     } else if (keyCode === RIGHT_ARROW) {
        - *       x += 1;
        - *     }
        - *   }
        - *
        - *   // Style the circle.
        - *   fill(0);
        - *
        - *   // Draw the circle at (x, y).
        - *   circle(x, y, 5);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.keyCode = 0;
        -
        -/**
        - * A function that's called once when any key is pressed.
        - *
        - * Declaring the function `keyPressed()` sets a code block to run once
        - * automatically when the user presses any key:
        - *
        - * ```js
        - * function keyPressed() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The <a href="#/p5/key">key</a> and <a href="#/p5/keyCode">keyCode</a>
        - * variables will be updated with the most recently typed value when
        - * `keyPressed()` is called by p5.js:
        - *
        - * ```js
        - * function keyPressed() {
        - *   if (key === 'c') {
        - *     // Code to run.
        - *   }
        - *
        - *   if (keyCode === ENTER) {
        - *     // Code to run.
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, `event`, is optional. `keyPressed()` is always passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent" target="_blank">KeyboardEvent</a>
        - * object with properties that describe the key press event:
        - *
        - * ```js
        - * function keyPressed(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * Browsers may have default behaviors attached to various key events. For
        - * example, some browsers may jump to the bottom of a web page when the
        - * `SPACE` key is pressed. To prevent any default behavior for this event, add
        - * `return false;` to the end of the function.
        - *
        - * @method keyPressed
        - * @param  {KeyboardEvent} [event] optional `KeyboardEvent` callback argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square changes color when the user presses a key.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Toggle the background color when the user presses a key.
        - * function keyPressed() {
        - *   if (value === 0) {
        - *     value = 255;
        - *   } else {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a white square at its center. The inner square turns black when the user presses the "b" key. It turns white when the user presses the "a" key.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Reassign value when the user presses the 'a' or 'b' key.
        - * function keyPressed() {
        - *   if (key === 'a') {
        - *     value = 255;
        - *   } else if (key === 'b') {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square turns white when the user presses the left arrow key. It turns black when the user presses the right arrow key.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Toggle the background color when the user presses an arrow key.
        - * function keyPressed() {
        - *   if (keyCode === LEFT_ARROW) {
        - *     value = 255;
        - *   } else if (keyCode === RIGHT_ARROW) {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype._onkeydown = function(e) {
        -  if (e.repeat) {
        -    // Ignore repeated key events when holding down a key
        -    return;
        -  }
        +  /**
        +   * A function that's called once when any key is pressed.
        +   *
        +   * Declaring the function `keyPressed()` sets a code block to run once
        +   * automatically when the user presses any key:
        +   *
        +   * ```js
        +   * function keyPressed() {
        +   *   // Code to run.
        +   * }
        +   * ```
        +   *
        +   * The <a href="#/p5/key">key</a> and <a href="#/p5/keyCode">keyCode</a>
        +   * variables will be updated with the most recently typed value when
        +   * `keyPressed()` is called by p5.js:
        +   *
        +   * ```js
        +   * function keyPressed() {
        +   *   if (key === 'c') {
        +   *     // Code to run.
        +   *   }
        +   *
        +   *   if (keyCode === ENTER) {
        +   *     // Code to run.
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * The parameter, `event`, is optional. `keyPressed()` is always passed a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent" target="_blank">KeyboardEvent</a>
        +   * object with properties that describe the key press event:
        +   *
        +   * ```js
        +   * function keyPressed(event) {
        +   *   // Code to run that uses the event.
        +   *   console.log(event);
        +   * }
        +   * ```
        +   *
        +   * Browsers may have default behaviors attached to various key events. For
        +   * example, some browsers may jump to the bottom of a web page when the
        +   * `SPACE` key is pressed. To prevent any default behavior for this event, add
        +   * `return false;` to the end of the function.
        +   *
        +   * @method keyPressed
        +   * @param  {KeyboardEvent} [event] optional `KeyboardEvent` callback argument.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black square at its center. The inner square changes color when the user presses a key.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * // Toggle the background color when the user presses a key.
        +   * function keyPressed() {
        +   *   if (value === 0) {
        +   *     value = 255;
        +   *   } else {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a white square at its center. The inner square turns black when the user presses the "b" key. It turns white when the user presses the "a" key.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * // Reassign value when the user presses the 'a' or 'b' key.
        +   * function keyPressed() {
        +   *   if (key === 'a') {
        +   *     value = 255;
        +   *   } else if (key === 'b') {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black square at its center. The inner square turns white when the user presses the left arrow key. It turns black when the user presses the right arrow key.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * // Toggle the background color when the user presses an arrow key.
        +   * function keyPressed() {
        +   *   if (keyCode === LEFT_ARROW) {
        +   *     value = 255;
        +   *   } else if (keyCode === RIGHT_ARROW) {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn._onkeydown = function(e) {
        +    if (e.repeat) {
        +      // Ignore repeated key events when holding down a key
        +      return;
        +    }
         
        -  this._setProperty('isKeyPressed', true);
        -  this._setProperty('keyIsPressed', true);
        -  this._setProperty('keyCode', e.which);
        -  this._downKeys[e.which] = true;
        -  this._setProperty('key', e.key || String.fromCharCode(e.which) || e.which);
        +    this.keyIsPressed = true;
        +    this.keyCode = e.which;
        +    this._downKeys[e.which] = true;
        +    this.key = e.key || String.fromCharCode(e.which) || e.which;
         
        -  // Track keys pressed with meta key
        -  if (e.metaKey) {
        -    if (!this._metaKeys) {
        -      this._metaKeys = [];
        +    // Track keys pressed with meta key
        +    if (e.metaKey) {
        +      if (!this._metaKeys) {
        +        this._metaKeys = [];
        +      }
        +      this._metaKeys.push(e.which);
             }
        -    this._metaKeys.push(e.which);
        -  }
         
        -  const context = this._isGlobal ? window : this;
        -  if (typeof context.keyPressed === 'function' && !e.charCode) {
        -    const executeDefault = context.keyPressed(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        +    const context = this._isGlobal ? window : this;
        +    if (typeof context.keyPressed === 'function' && !e.charCode) {
        +      const executeDefault = context.keyPressed(e);
        +      if (executeDefault === false) {
        +        e.preventDefault();
        +      }
             }
        -  }
        -};
        -
        -/**
        - * A function that's called once when any key is released.
        - *
        - * Declaring the function `keyReleased()` sets a code block to run once
        - * automatically when the user releases any key:
        - *
        - * ```js
        - * function keyReleased() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The <a href="#/p5/key">key</a> and <a href="#/p5/keyCode">keyCode</a>
        - * variables will be updated with the most recently released value when
        - * `keyReleased()` is called by p5.js:
        - *
        - * ```js
        - * function keyReleased() {
        - *   if (key === 'c') {
        - *     // Code to run.
        - *   }
        - *
        - *   if (keyCode === ENTER) {
        - *     // Code to run.
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, `event`, is optional. `keyReleased()` is always passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent" target="_blank">KeyboardEvent</a>
        - * object with properties that describe the key press event:
        - *
        - * ```js
        - * function keyReleased(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * Browsers may have default behaviors attached to various key events. To
        - * prevent any default behavior for this event, add `return false;` to the end
        - * of the function.
        - *
        - * @method keyReleased
        - * @param  {KeyboardEvent} [event] optional `KeyboardEvent` callback argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square changes color when the user releases a key.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Toggle value when the user releases a key.
        - * function keyReleased() {
        - *   if (value === 0) {
        - *     value = 255;
        - *   } else {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square becomes white when the user releases the "w" key.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Set value to 255 the user releases the 'w' key.
        - * function keyReleased() {
        - *   if (key === 'w') {
        - *     value = 255;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square turns white when the user presses and releases the left arrow key. It turns black when the user presses and releases the right arrow key.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Toggle the background color when the user releases an arrow key.
        - * function keyReleased() {
        - *   if (keyCode === LEFT_ARROW) {
        - *     value = 255;
        - *   } else if (keyCode === RIGHT_ARROW) {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype._onkeyup = function(e) {
        -  this._setProperty('isKeyPressed', false);
        -  this._setProperty('keyIsPressed', false);
        -  this._setProperty('_lastKeyCodePressed', this._keyCode);
        -  this._downKeys[e.which] = false;
        +  };
        +  /**
        +   * A function that's called once when any key is released.
        +   *
        +   * Declaring the function `keyReleased()` sets a code block to run once
        +   * automatically when the user releases any key:
        +   *
        +   * ```js
        +   * function keyReleased() {
        +   *   // Code to run.
        +   * }
        +   * ```
        +   *
        +   * The <a href="#/p5/key">key</a> and <a href="#/p5/keyCode">keyCode</a>
        +   * variables will be updated with the most recently released value when
        +   * `keyReleased()` is called by p5.js:
        +   *
        +   * ```js
        +   * function keyReleased() {
        +   *   if (key === 'c') {
        +   *     // Code to run.
        +   *   }
        +   *
        +   *   if (keyCode === ENTER) {
        +   *     // Code to run.
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * The parameter, `event`, is optional. `keyReleased()` is always passed a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent" target="_blank">KeyboardEvent</a>
        +   * object with properties that describe the key press event:
        +   *
        +   * ```js
        +   * function keyReleased(event) {
        +   *   // Code to run that uses the event.
        +   *   console.log(event);
        +   * }
        +   * ```
        +   *
        +   * Browsers may have default behaviors attached to various key events. To
        +   * prevent any default behavior for this event, add `return false;` to the end
        +   * of the function.
        +   *
        +   * @method keyReleased
        +   * @param  {KeyboardEvent} [event] optional `KeyboardEvent` callback argument.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black square at its center. The inner square changes color when the user releases a key.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * // Toggle value when the user releases a key.
        +   * function keyReleased() {
        +   *   if (value === 0) {
        +   *     value = 255;
        +   *   } else {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black square at its center. The inner square becomes white when the user releases the "w" key.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * // Set value to 255 the user releases the 'w' key.
        +   * function keyReleased() {
        +   *   if (key === 'w') {
        +   *     value = 255;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black square at its center. The inner square turns white when the user presses and releases the left arrow key. It turns black when the user presses and releases the right arrow key.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * // Toggle the background color when the user releases an arrow key.
        +   * function keyReleased() {
        +   *   if (keyCode === LEFT_ARROW) {
        +   *     value = 255;
        +   *   } else if (keyCode === RIGHT_ARROW) {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn._onkeyup = function(e) {
        +    this._downKeys[e.which] = false;
        +    this.keyIsPressed = false;
        +    this._lastKeyCodeTyped = null;
         
        -  if (e.key === 'Meta') { // Meta key codes
        -    // When meta key is released, clear all keys pressed with it
        -    if (this._metaKeys) {
        -      this._metaKeys.forEach(key => {
        -        this._downKeys[key] = false;
        -      });
        -      this._metaKeys = [];
        +    if (e.key === 'Meta') { // Meta key codes
        +      // When meta key is released, clear all keys pressed with it
        +      if (this._metaKeys) {
        +        this._metaKeys.forEach(key => {
        +          this._downKeys[key] = false;
        +        });
        +        this._metaKeys = [];
        +      }
             }
        -  }
         
        -  const context = this._isGlobal ? window : this;
        -  if (typeof context.keyReleased === 'function') {
        -    const executeDefault = context.keyReleased(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        +    const context = this._isGlobal ? window : this;
        +    if (typeof context.keyReleased === 'function') {
        +      const executeDefault = context.keyReleased(e);
        +      if (executeDefault === false) {
        +        e.preventDefault();
        +      }
             }
        -  }
        -};
        +  };
         
        -/**
        - * A function that's called once when keys with printable characters are pressed.
        - *
        - * Declaring the function `keyTyped()` sets a code block to run once
        - * automatically when the user presses any key with a printable character such
        - * as `a` or 1. Modifier keys such as `SHIFT`, `CONTROL`, and the arrow keys
        - * will be ignored:
        - *
        - * ```js
        - * function keyTyped() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The <a href="#/p5/key">key</a> and <a href="#/p5/keyCode">keyCode</a>
        - * variables will be updated with the most recently released value when
        - * `keyTyped()` is called by p5.js:
        - *
        - * ```js
        - * function keyTyped() {
        - *   // Check for the "c" character using key.
        - *   if (key === 'c') {
        - *     // Code to run.
        - *   }
        - *
        - *   // Check for "c" using keyCode.
        - *   if (keyCode === 67) {
        - *     // Code to run.
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, `event`, is optional. `keyTyped()` is always passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent" target="_blank">KeyboardEvent</a>
        - * object with properties that describe the key press event:
        - *
        - * ```js
        - * function keyReleased(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * Note: Use the <a href="#/p5/keyPressed">keyPressed()</a> function and
        - * <a href="#/p5/keyCode">keyCode</a> system variable to respond to modifier
        - * keys such as `ALT`.
        - *
        - * Browsers may have default behaviors attached to various key events. To
        - * prevent any default behavior for this event, add `return false;` to the end
        - * of the function.
        - *
        - * @method keyTyped
        - * @param  {KeyboardEvent} [event] optional `KeyboardEvent` callback argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - * // Note: Pressing special keys such as SPACE have no effect.
        - *
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a white square at its center. The inner square changes color when the user presses a key.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Toggle the square's color when the user types a printable key.
        - * function keyTyped() {
        - *   if (value === 0) {
        - *     value = 255;
        - *   } else {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a white square at its center. The inner square turns black when the user types the "b" key. It turns white when the user types the "a" key.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Reassign value when the user types the 'a' or 'b' key.
        - * function keyTyped() {
        - *   if (key === 'a') {
        - *     value = 255;
        - *   } else if (key === 'b') {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype._onkeypress = function(e) {
        -  if (e.which === this._lastKeyCodeTyped) {
        -    // prevent multiple firings
        -    return;
        -  }
        -  this._setProperty('_lastKeyCodeTyped', e.which); // track last keyCode
        -  this._setProperty('key', e.key || String.fromCharCode(e.which) || e.which);
        +  /**
        +   * A function that's called once when keys with printable characters are pressed.
        +   *
        +   * Declaring the function `keyTyped()` sets a code block to run once
        +   * automatically when the user presses any key with a printable character such
        +   * as `a` or 1. Modifier keys such as `SHIFT`, `CONTROL`, and the arrow keys
        +   * will be ignored:
        +   *
        +   * ```js
        +   * function keyTyped() {
        +   *   // Code to run.
        +   * }
        +   * ```
        +   *
        +   * The <a href="#/p5/key">key</a> and <a href="#/p5/keyCode">keyCode</a>
        +   * variables will be updated with the most recently released value when
        +   * `keyTyped()` is called by p5.js:
        +   *
        +   * ```js
        +   * function keyTyped() {
        +   *   // Check for the "c" character using key.
        +   *   if (key === 'c') {
        +   *     // Code to run.
        +   *   }
        +   *
        +   *   // Check for "c" using keyCode.
        +   *   if (keyCode === 67) {
        +   *     // Code to run.
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * The parameter, `event`, is optional. `keyTyped()` is always passed a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent" target="_blank">KeyboardEvent</a>
        +   * object with properties that describe the key press event:
        +   *
        +   * ```js
        +   * function keyReleased(event) {
        +   *   // Code to run that uses the event.
        +   *   console.log(event);
        +   * }
        +   * ```
        +   *
        +   * Note: Use the <a href="#/p5/keyPressed">keyPressed()</a> function and
        +   * <a href="#/p5/keyCode">keyCode</a> system variable to respond to modifier
        +   * keys such as `ALT`.
        +   *
        +   * Browsers may have default behaviors attached to various key events. To
        +   * prevent any default behavior for this event, add `return false;` to the end
        +   * of the function.
        +   *
        +   * @method keyTyped
        +   * @param  {KeyboardEvent} [event] optional `KeyboardEvent` callback argument.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   * // Note: Pressing special keys such as SPACE have no effect.
        +   *
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a white square at its center. The inner square changes color when the user presses a key.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * // Toggle the square's color when the user types a printable key.
        +   * function keyTyped() {
        +   *   if (value === 0) {
        +   *     value = 255;
        +   *   } else {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a white square at its center. The inner square turns black when the user types the "b" key. It turns white when the user types the "a" key.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * // Reassign value when the user types the 'a' or 'b' key.
        +   * function keyTyped() {
        +   *   if (key === 'a') {
        +   *     value = 255;
        +   *   } else if (key === 'b') {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn._onkeypress = function(e) {
        +    if (e.which === this._lastKeyCodeTyped) {
        +      // prevent multiple firings
        +      return;
        +    }
        +    this._lastKeyCodeTyped = e.which; // track last keyCode
        +    this.key = e.key || String.fromCharCode(e.which) || e.which;
         
        -  const context = this._isGlobal ? window : this;
        -  if (typeof context.keyTyped === 'function') {
        -    const executeDefault = context.keyTyped(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        +    const context = this._isGlobal ? window : this;
        +    if (typeof context.keyTyped === 'function') {
        +      const executeDefault = context.keyTyped(e);
        +      if (executeDefault === false) {
        +        e.preventDefault();
        +      }
             }
        -  }
        -};
        -/**
        - * The onblur function is called when the user is no longer focused
        - * on the p5 element. Because the keyup events will not fire if the user is
        - * not focused on the element we must assume all keys currently down have
        - * been released.
        - */
        -p5.prototype._onblur = function(e) {
        -  this._downKeys = {};
        -};
        +  };
        +  /**
        +   * The onblur function is called when the user is no longer focused
        +   * on the p5 element. Because the keyup events will not fire if the user is
        +   * not focused on the element we must assume all keys currently down have
        +   * been released.
        +   */
        +  fn._onblur = function(e) {
        +    this._downKeys = {};
        +  };
         
        -/**
        - * Returns `true` if the key it’s checking is pressed and `false` if not.
        - *
        - * `keyIsDown()` is helpful when checking for multiple different key presses.
        - * For example, `keyIsDown()` can be used to check if both `LEFT_ARROW` and
        - * `UP_ARROW` are pressed:
        - *
        - * ```js
        - * if (keyIsDown(LEFT_ARROW) && keyIsDown(UP_ARROW)) {
        - *   // Move diagonally.
        - * }
        - * ```
        - *
        - * `keyIsDown()` can check for key presses using
        - * <a href="#/p5/keyCode">keyCode</a> values, as in `keyIsDown(37)` or
        - * `keyIsDown(LEFT_ARROW)`. Key codes can be found on websites such as
        - * <a href="https://keycode.info" target="_blank">keycode.info</a>.
        - *
        - * @method keyIsDown
        - * @param {Number}          code key to check.
        - * @return {Boolean}        whether the key is down or not.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * let x = 50;
        - * let y = 50;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe(
        - *     'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   // Update x and y if an arrow key is pressed.
        - *   if (keyIsDown(LEFT_ARROW) === true) {
        - *     x -= 1;
        - *   }
        - *
        - *   if (keyIsDown(RIGHT_ARROW) === true) {
        - *     x += 1;
        - *   }
        - *
        - *   if (keyIsDown(UP_ARROW) === true) {
        - *     y -= 1;
        - *   }
        - *
        - *   if (keyIsDown(DOWN_ARROW) === true) {
        - *     y += 1;
        - *   }
        - *
        - *   // Style the circle.
        - *   fill(0);
        - *
        - *   // Draw the circle.
        - *   circle(x, y, 5);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click on the canvas to begin detecting key presses.
        - *
        - * let x = 50;
        - * let y = 50;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe(
        - *     'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   // Update x and y if an arrow key is pressed.
        - *   if (keyIsDown(37) === true) {
        - *     x -= 1;
        - *   }
        - *
        - *   if (keyIsDown(39) === true) {
        - *     x += 1;
        - *   }
        - *
        - *   if (keyIsDown(38) === true) {
        - *     y -= 1;
        - *   }
        - *
        - *   if (keyIsDown(40) === true) {
        - *     y += 1;
        - *   }
        - *
        - *   // Style the circle.
        - *   fill(0);
        - *
        - *   // Draw the circle.
        - *   circle(x, y, 5);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.keyIsDown = function(code) {
        -  p5._validateParameters('keyIsDown', arguments);
        -  return this._downKeys[code] || false;
        -};
        +  /**
        +   * Returns `true` if the key it’s checking is pressed and `false` if not.
        +   *
        +   * `keyIsDown()` is helpful when checking for multiple different key presses.
        +   * For example, `keyIsDown()` can be used to check if both `LEFT_ARROW` and
        +   * `UP_ARROW` are pressed:
        +   *
        +   * ```js
        +   * if (keyIsDown(LEFT_ARROW) && keyIsDown(UP_ARROW)) {
        +   *   // Move diagonally.
        +   * }
        +   * ```
        +   *
        +   * `keyIsDown()` can check for key presses using
        +   * <a href="#/p5/keyCode">keyCode</a> values, as in `keyIsDown(37)` or
        +   * `keyIsDown(LEFT_ARROW)`. Key codes can be found on websites such as
        +   * <a href="https://keycode.info" target="_blank">keycode.info</a>.
        +   *
        +   * @method keyIsDown
        +   * @param {Number}          code key to check.
        +   * @return {Boolean}        whether the key is down or not.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * let x = 50;
        +   * let y = 50;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe(
        +   *     'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   // Update x and y if an arrow key is pressed.
        +   *   if (keyIsDown(LEFT_ARROW) === true) {
        +   *     x -= 1;
        +   *   }
        +   *
        +   *   if (keyIsDown(RIGHT_ARROW) === true) {
        +   *     x += 1;
        +   *   }
        +   *
        +   *   if (keyIsDown(UP_ARROW) === true) {
        +   *     y -= 1;
        +   *   }
        +   *
        +   *   if (keyIsDown(DOWN_ARROW) === true) {
        +   *     y += 1;
        +   *   }
        +   *
        +   *   // Style the circle.
        +   *   fill(0);
        +   *
        +   *   // Draw the circle.
        +   *   circle(x, y, 5);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click on the canvas to begin detecting key presses.
        +   *
        +   * let x = 50;
        +   * let y = 50;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe(
        +   *     'A gray square with a black circle at its center. The circle moves when the user presses an arrow key. It leaves a trail as it moves.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   // Update x and y if an arrow key is pressed.
        +   *   if (keyIsDown(37) === true) {
        +   *     x -= 1;
        +   *   }
        +   *
        +   *   if (keyIsDown(39) === true) {
        +   *     x += 1;
        +   *   }
        +   *
        +   *   if (keyIsDown(38) === true) {
        +   *     y -= 1;
        +   *   }
        +   *
        +   *   if (keyIsDown(40) === true) {
        +   *     y += 1;
        +   *   }
        +   *
        +   *   // Style the circle.
        +   *   fill(0);
        +   *
        +   *   // Draw the circle.
        +   *   circle(x, y, 5);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.keyIsDown = function(code) {
        +    // p5._validateParameters('keyIsDown', arguments);
        +    return this._downKeys[code] || false;
        +  };
         
        -/**
        - * The _areDownKeys function returns a boolean true if any keys pressed
        - * and a false if no keys are currently pressed.
        +  /**
        +   * The _areDownKeys function returns a boolean true if any keys pressed
        +   * and a false if no keys are currently pressed.
         
        - * Helps avoid instances where multiple keys are pressed simultaneously and
        - * releasing a single key will then switch the
        - * keyIsPressed property to true.
        - * @private
        -**/
        -p5.prototype._areDownKeys = function() {
        -  for (const key in this._downKeys) {
        -    if (this._downKeys.hasOwnProperty(key) && this._downKeys[key] === true) {
        -      return true;
        +   * Helps avoid instances where multiple keys are pressed simultaneously and
        +   * releasing a single key will then switch the
        +   * keyIsPressed property to true.
        +   * @private
        +  **/
        +  fn._areDownKeys = function() {
        +    for (const key in this._downKeys) {
        +      if (this._downKeys.hasOwnProperty(key) && this._downKeys[key] === true) {
        +        return true;
        +      }
             }
        -  }
        -  return false;
        -};
        +    return false;
        +  };
        +}
        +
        +export default keyboard;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  keyboard(p5, p5.prototype);
        +}
        diff --git a/src/events/mouse.js b/src/events/mouse.js
        deleted file mode 100644
        index c36bdf4ffc..0000000000
        --- a/src/events/mouse.js
        +++ /dev/null
        @@ -1,1992 +0,0 @@
        -/**
        - * @module Events
        - * @submodule Mouse
        - * @for p5
        - * @requires core
        - * @requires constants
        - */
        -
        -import p5 from '../core/main';
        -import * as constants from '../core/constants';
        -
        -/**
        - * A `Number` system variable that tracks the mouse's horizontal movement.
        - *
        - * `movedX` tracks how many pixels the mouse moves left or right between
        - * frames. `movedX` will have a negative value if the mouse moves left between
        - * frames and a positive value if it moves right. `movedX` can be calculated
        - * as `mouseX - pmouseX`.
        - *
        - * Note: `movedX` continues updating even when
        - * <a href="#/p5/requestPointerLock">requestPointerLock()</a> is active.
        - *
        - * @property {Number} movedX
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square. The text ">>" appears when the user moves the mouse to the right. The text "<<" appears when the user moves the mouse to the left.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display >> when movedX is positive and
        - *   // << when it's negative.
        - *   if (movedX > 0) {
        - *     text('>>', 50, 50);
        - *   } else if (movedX < 0) {
        - *     text('<<', 50, 50);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.movedX = 0;
        -
        -/**
        - * A `Number` system variable that tracks the mouse's vertical movement.
        - *
        - * `movedY` tracks how many pixels the mouse moves up or down between
        - * frames. `movedY` will have a negative value if the mouse moves up between
        - * frames and a positive value if it moves down. `movedY` can be calculated
        - * as `mouseY - pmouseY`.
        - *
        - * Note: `movedY` continues updating even when
        - * <a href="#/p5/requestPointerLock">requestPointerLock()</a> is active.
        - *
        - * @property {Number} movedY
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square. The text "▲" appears when the user moves the mouse upward. The text "▼" appears when the user moves the mouse downward.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display ▼ when movedY is positive and
        - *   // ▲ when it's negative.
        - *   if (movedY > 0) {
        - *     text('▼', 50, 50);
        - *   } else if (movedY < 0) {
        - *     text('▲', 50, 50);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.movedY = 0;
        -/*
        - * This is a flag which is false until the first time
        - * we receive a mouse event. The pmouseX and pmouseY
        - * values will match the mouseX and mouseY values until
        - * this interaction takes place.
        - */
        -p5.prototype._hasMouseInteracted = false;
        -
        -/**
        - * A `Number` system variable that tracks the mouse's horizontal position.
        - *
        - * In 2D mode, `mouseX` keeps track of the mouse's position relative to the
        - * top-left corner of the canvas. For example, if the mouse is 50 pixels from
        - * the left edge of the canvas, then `mouseX` will be 50.
        - *
        - * In WebGL mode, `mouseX` keeps track of the mouse's position relative to the
        - * center of the canvas. For example, if the mouse is 50 pixels to the right
        - * of the canvas' center, then `mouseX` will be 50.
        - *
        - * If touch is used instead of the mouse, then `mouseX` will hold the
        - * x-coordinate of the most recent touch point.
        - *
        - * @property {Number} mouseX
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe("A vertical black line moves left and right following the mouse's x-position.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a vertical line that follows the mouse's x-coordinate.
        - *   line(mouseX, 0, mouseX, 100);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the mouse's coordinates.
        - *   text(`x: ${mouseX} y: ${mouseY}`, 50, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe("A vertical black line moves left and right following the mouse's x-position.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Adjust coordinates for WebGL mode.
        - *   // The origin (0, 0) is at the center of the canvas.
        - *   let mx = mouseX - 50;
        - *
        - *   // Draw the line.
        - *   line(mx, -50, mx, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let font;
        - *
        - * // Load a font for WebGL mode.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     "A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Display the mouse's coordinates.
        - *   text(`x: ${mouseX} y: ${mouseY}`, 0, 0);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.mouseX = 0;
        -
        -/**
        - * A `Number` system variable that tracks the mouse's vertical position.
        - *
        - * In 2D mode, `mouseY` keeps track of the mouse's position relative to the
        - * top-left corner of the canvas. For example, if the mouse is 50 pixels from
        - * the top edge of the canvas, then `mouseY` will be 50.
        - *
        - * In WebGL mode, `mouseY` keeps track of the mouse's position relative to the
        - * center of the canvas. For example, if the mouse is 50 pixels below the
        - * canvas' center, then `mouseY` will be 50.
        - *
        - * If touch is used instead of the mouse, then `mouseY` will hold the
        - * y-coordinate of the most recent touch point.
        - *
        - * @property {Number} mouseY
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe("A horizontal black line moves up and down following the mouse's y-position.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a horizontal line that follows the mouse's y-coordinate.
        - *   line(0, mouseY, 100, mouseY);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the mouse's coordinates.
        - *   text(`x: ${mouseX} y: ${mouseY}`, 50, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe("A horizontal black line moves up and down following the mouse's y-position.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Adjust coordinates for WebGL mode.
        - *   // The origin (0, 0) is at the center of the canvas.
        - *   let my = mouseY - 50;
        - *
        - *   // Draw the line.
        - *   line(-50, my, 50, my);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let font;
        - *
        - * // Load a font for WebGL mode.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     "A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Display the mouse's coordinates.
        - *   text(`x: ${mouseX} y: ${mouseY}`, 0, 0);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.mouseY = 0;
        -
        -/**
        - * A `Number` system variable that tracks the mouse's previous horizontal
        - * position.
        - *
        - * In 2D mode, `pmouseX` keeps track of the mouse's position relative to the
        - * top-left corner of the canvas. Its value is
        - * <a href="#/p5/mouseX">mouseX</a> from the previous frame. For example, if
        - * the mouse was 50 pixels from the left edge of the canvas during the last
        - * frame, then `pmouseX` will be 50.
        - *
        - * In WebGL mode, `pmouseX` keeps track of the mouse's position relative to the
        - * center of the canvas. For example, if the mouse was 50 pixels to the right
        - * of the canvas' center during the last frame, then `pmouseX` will be 50.
        - *
        - * If touch is used instead of the mouse, then `pmouseX` will hold the
        - * x-coordinate of the last touch point.
        - *
        - * Note: `pmouseX` is reset to the current <a href="#/p5/mouseX">mouseX</a>
        - * value at the start of each touch event.
        - *
        - * @property {Number} pmouseX
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(10);
        - *
        - *   describe('A line follows the mouse as it moves. The line grows longer with faster movements.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   line(pmouseX, pmouseY, mouseX, mouseY);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A line follows the mouse as it moves. The line grows longer with faster movements.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Adjust coordinates for WebGL mode.
        - *   // The origin (0, 0) is at the center of the canvas.
        - *   let pmx = pmouseX - 50;
        - *   let pmy = pmouseY - 50;
        - *   let mx = mouseX - 50;
        - *   let my = mouseY - 50;
        - *
        - *   // Draw the line.
        - *   line(pmx, pmy, mx, my);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.pmouseX = 0;
        -
        -/**
        - * A `Number` system variable that tracks the mouse's previous vertical
        - * position.
        - *
        - * In 2D mode, `pmouseY` keeps track of the mouse's position relative to the
        - * top-left corner of the canvas. Its value is
        - * <a href="#/p5/mouseY">mouseY</a> from the previous frame. For example, if
        - * the mouse was 50 pixels from the top edge of the canvas during the last
        - * frame, then `pmouseY` will be 50.
        - *
        - * In WebGL mode, `pmouseY` keeps track of the mouse's position relative to the
        - * center of the canvas. For example, if the mouse was 50 pixels below the
        - * canvas' center during the last frame, then `pmouseY` will be 50.
        - *
        - * If touch is used instead of the mouse, then `pmouseY` will hold the
        - * y-coordinate of the last touch point.
        - *
        - * Note: `pmouseY` is reset to the current <a href="#/p5/mouseY">mouseY</a>
        - * value at the start of each touch event.
        - *
        - * @property {Number} pmouseY
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(10);
        - *
        - *   describe('A line follows the mouse as it moves. The line grows longer with faster movements.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   line(pmouseX, pmouseY, mouseX, mouseY);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A line follows the mouse as it moves. The line grows longer with faster movements.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Adjust coordinates for WebGL mode.
        - *   // The origin (0, 0) is at the center of the canvas.
        - *   let pmx = pmouseX - 50;
        - *   let pmy = pmouseY - 50;
        - *   let mx = mouseX - 50;
        - *   let my = mouseY - 50;
        - *
        - *   // Draw the line.
        - *   line(pmx, pmy, mx, my);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.pmouseY = 0;
        -
        -/**
        - * A `Number` variable that tracks the mouse's horizontal position within the
        - * browser.
        - *
        - * `winMouseX` keeps track of the mouse's position relative to the top-left
        - * corner of the browser window. For example, if the mouse is 50 pixels from
        - * the left edge of the browser, then `winMouseX` will be 50.
        - *
        - * On a touchscreen device, `winMouseX` will hold the x-coordinate of the most
        - * recent touch point.
        - *
        - * Note: Use <a href="#/p5/mouseX">mouseX</a> to track the mouse’s
        - * x-coordinate within the canvas.
        - *
        - * @property {Number} winMouseX
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the mouse's coordinates within the browser window.
        - *   text(`x: ${winMouseX} y: ${winMouseY}`, 50, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.winMouseX = 0;
        -
        -/**
        - * A `Number` variable that tracks the mouse's vertical position within the
        - * browser.
        - *
        - * `winMouseY` keeps track of the mouse's position relative to the top-left
        - * corner of the browser window. For example, if the mouse is 50 pixels from
        - * the top edge of the browser, then `winMouseY` will be 50.
        - *
        - * On a touchscreen device, `winMouseY` will hold the y-coordinate of the most
        - * recent touch point.
        - *
        - * Note: Use <a href="#/p5/mouseY">mouseY</a> to track the mouse’s
        - * y-coordinate within the canvas.
        - *
        - * @property {Number} winMouseY
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse.");
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the mouse's coordinates within the browser window.
        - *   text(`x: ${winMouseX} y: ${winMouseY}`, 50, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.winMouseY = 0;
        -
        -/**
        - * A `Number` variable that tracks the mouse's previous horizontal position
        - * within the browser.
        - *
        - * `pwinMouseX` keeps track of the mouse's position relative to the top-left
        - * corner of the browser window. Its value is
        - * <a href="#/p5/winMouseX">winMouseX</a> from the previous frame. For
        - * example, if the mouse was 50 pixels from
        - * the left edge of the browser during the last frame, then `pwinMouseX` will
        - * be 50.
        - *
        - * On a touchscreen device, `pwinMouseX` will hold the x-coordinate of the most
        - * recent touch point. `pwinMouseX` is reset to the current
        - * <a href="#/p5/winMouseX">winMouseX</a> value at the start of each touch
        - * event.
        - *
        - * Note: Use <a href="#/p5/pmouseX">pmouseX</a> to track the mouse’s previous
        - * x-coordinate within the canvas.
        - *
        - * @property {Number} pwinMouseX
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(10);
        - *
        - *   describe('A gray square. A white circle at its center grows larger when the mouse moves horizontally.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the circle's diameter.
        - *   let d = winMouseX - pwinMouseX;
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, d);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Create the canvas and set its position.
        - *   let cnv = createCanvas(100, 100);
        - *   cnv.position(20, 20);
        - *
        - *   describe('A gray square with a number at its center. The number changes as the user moves the mouse vertically.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display pwinMouseX.
        - *   text(pwinMouseX, 50, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.pwinMouseX = 0;
        -
        -/**
        - * A `Number` variable that tracks the mouse's previous vertical position
        - * within the browser.
        - *
        - * `pwinMouseY` keeps track of the mouse's position relative to the top-left
        - * corner of the browser window. Its value is
        - * <a href="#/p5/winMouseY">winMouseY</a> from the previous frame. For
        - * example, if the mouse was 50 pixels from
        - * the top edge of the browser during the last frame, then `pwinMouseY` will
        - * be 50.
        - *
        - * On a touchscreen device, `pwinMouseY` will hold the y-coordinate of the most
        - * recent touch point. `pwinMouseY` is reset to the current
        - * <a href="#/p5/winMouseY">winMouseY</a> value at the start of each touch
        - * event.
        - *
        - * Note: Use <a href="#/p5/pmouseY">pmouseY</a> to track the mouse’s previous
        - * y-coordinate within the canvas.
        - *
        - * @property {Number} pwinMouseY
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(10);
        - *
        - *   describe('A gray square. A white circle at its center grows larger when the mouse moves vertically.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the circle's diameter.
        - *   let d = winMouseY - pwinMouseY;
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, d);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Create the canvas and set its position.
        - *   let cnv = createCanvas(100, 100);
        - *   cnv.position(20, 20);
        - *
        - *   describe('A gray square with a number at its center. The number changes as the user moves the mouse vertically.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display pwinMouseY.
        - *   text(pwinMouseY, 50, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.pwinMouseY = 0;
        -
        -/**
        - * A String system variable that contains the value of the last mouse button
        - * pressed.
        - *
        - * The `mouseButton` variable is either `LEFT`, `RIGHT`, or `CENTER`,
        - * depending on which button was pressed last.
        - *
        - * Note: Different browsers may track `mouseButton` differently. See
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons" target="_blank">MDN</a>
        - * for more information.
        - *
        - * @property {Constant} mouseButton
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with black text at its center. The text changes from 0 to either "left" or "right" when the user clicks a mouse button.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the mouse button.
        - *   text(mouseButton, 50, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     "A gray square. Different shapes appear at its center depending on the mouse button that's clicked."
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   if (mouseIsPressed === true) {
        - *     if (mouseButton === LEFT) {
        - *       circle(50, 50, 50);
        - *     }
        - *     if (mouseButton === RIGHT) {
        - *       square(25, 25, 50);
        - *     }
        - *     if (mouseButton === CENTER) {
        - *       triangle(23, 75, 50, 20, 78, 75);
        - *     }
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.mouseButton = 0;
        -
        -/**
        - * A `Boolean` system variable that's `true` if the mouse is pressed and
        - * `false` if not.
        - *
        - * @property {Boolean} mouseIsPressed
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with the word "false" at its center. The word changes to "true" when the user presses a mouse button.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the mouseIsPressed variable.
        - *   text(mouseIsPressed, 25, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a white square at its center. The inner square turns black when the user presses the mouse.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   if (mouseIsPressed === true) {
        - *     fill(0);
        - *   } else {
        - *     fill(255);
        - *   }
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.mouseIsPressed = false;
        -
        -p5.prototype._updateNextMouseCoords = function(e) {
        -  if (this._curElement !== null && (!e.touches || e.touches.length > 0)) {
        -    const mousePos = getMousePos(
        -      this._curElement.elt,
        -      this.width,
        -      this.height,
        -      e
        -    );
        -    this._setProperty('movedX', e.movementX);
        -    this._setProperty('movedY', e.movementY);
        -    this._setProperty('mouseX', mousePos.x);
        -    this._setProperty('mouseY', mousePos.y);
        -    this._setProperty('winMouseX', mousePos.winX);
        -    this._setProperty('winMouseY', mousePos.winY);
        -  }
        -  if (!this._hasMouseInteracted) {
        -    // For first draw, make previous and next equal
        -    this._updateMouseCoords();
        -    this._setProperty('_hasMouseInteracted', true);
        -  }
        -};
        -
        -p5.prototype._updateMouseCoords = function() {
        -  this._setProperty('pmouseX', this.mouseX);
        -  this._setProperty('pmouseY', this.mouseY);
        -  this._setProperty('pwinMouseX', this.winMouseX);
        -  this._setProperty('pwinMouseY', this.winMouseY);
        -
        -  this._setProperty('_pmouseWheelDeltaY', this._mouseWheelDeltaY);
        -};
        -
        -function getMousePos(canvas, w, h, evt) {
        -  if (evt && !evt.clientX) {
        -    // use touches if touch and not mouse
        -    if (evt.touches) {
        -      evt = evt.touches[0];
        -    } else if (evt.changedTouches) {
        -      evt = evt.changedTouches[0];
        -    }
        -  }
        -  const rect = canvas.getBoundingClientRect();
        -  const sx = canvas.scrollWidth / w || 1;
        -  const sy = canvas.scrollHeight / h || 1;
        -  return {
        -    x: (evt.clientX - rect.left) / sx,
        -    y: (evt.clientY - rect.top) / sy,
        -    winX: evt.clientX,
        -    winY: evt.clientY,
        -    id: evt.identifier
        -  };
        -}
        -
        -p5.prototype._setMouseButton = function(e) {
        -  if (e.button === 1) {
        -    this._setProperty('mouseButton', constants.CENTER);
        -  } else if (e.button === 2) {
        -    this._setProperty('mouseButton', constants.RIGHT);
        -  } else {
        -    this._setProperty('mouseButton', constants.LEFT);
        -  }
        -};
        -
        -/**
        - * A function that's called when the mouse moves.
        - *
        - * Declaring the function `mouseMoved()` sets a code block to run
        - * automatically when the user moves the mouse without clicking any mouse
        - * buttons:
        - *
        - * ```js
        - * function mouseMoved() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        - * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        - * value when `mouseMoved()` is called by p5.js:
        - *
        - * ```js
        - * function mouseMoved() {
        - *   if (mouseX < 50) {
        - *     // Code to run if the mouse is on the left.
        - *   }
        - *
        - *   if (mouseY > 50) {
        - *     // Code to run if the mouse is near the bottom.
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, `event`, is optional. `mouseMoved()` is always passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        - * object with properties that describe the mouse move event:
        - *
        - * ```js
        - * function mouseMoved(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * Browsers may have default behaviors attached to various mouse events. For
        - * example, some browsers highlight text when the user moves the mouse while
        - * pressing a mouse button. To prevent any default behavior for this event,
        - * add `return false;` to the end of the function.
        - *
        - * @method mouseMoved
        - * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square becomes lighter as the mouse moves.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * function mouseMoved() {
        - *   // Update the grayscale value.
        - *   value += 5;
        - *
        - *   // Reset the grayscale value.
        - *   if (value > 255) {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * A function that's called when the mouse moves while a button is pressed.
        - *
        - * Declaring the function `mouseDragged()` sets a code block to run
        - * automatically when the user clicks and drags the mouse:
        - *
        - * ```js
        - * function mouseDragged() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        - * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        - * value when `mouseDragged()` is called by p5.js:
        - *
        - * ```js
        - * function mouseDragged() {
        - *   if (mouseX < 50) {
        - *     // Code to run if the mouse is on the left.
        - *   }
        - *
        - *   if (mouseY > 50) {
        - *     // Code to run if the mouse is near the bottom.
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, `event`, is optional. `mouseDragged()` is always passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        - * object with properties that describe the mouse drag event:
        - *
        - * ```js
        - * function mouseDragged(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * On touchscreen devices, `mouseDragged()` will run when a user moves a touch
        - * point if <a href="#/p5/touchMoved">touchMoved()</a> isn’t declared. If
        - * <a href="#/p5/touchMoved">touchMoved()</a> is declared, then
        - * <a href="#/p5/touchMoved">touchMoved()</a> will run when a user moves a
        - * touch point and `mouseDragged()` won’t.
        - *
        - * Browsers may have default behaviors attached to various mouse events. For
        - * example, some browsers highlight text when the user moves the mouse while
        - * pressing a mouse button. To prevent any default behavior for this event,
        - * add `return false;` to the end of the function.
        - *
        - * @method mouseDragged
        - * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square becomes lighter as the user drags the mouse.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * function mouseDragged() {
        - *   // Update the grayscale value.
        - *   value += 5;
        - *
        - *   // Reset the grayscale value.
        - *   if (value > 255) {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype._onmousemove = function(e) {
        -  const context = this._isGlobal ? window : this;
        -  let executeDefault;
        -  this._updateNextMouseCoords(e);
        -  if (!this.mouseIsPressed) {
        -    if (typeof context.mouseMoved === 'function') {
        -      executeDefault = context.mouseMoved(e);
        -      if (executeDefault === false) {
        -        e.preventDefault();
        -      }
        -    }
        -  } else {
        -    if (typeof context.mouseDragged === 'function') {
        -      executeDefault = context.mouseDragged(e);
        -      if (executeDefault === false) {
        -        e.preventDefault();
        -      }
        -    } else if (typeof context.touchMoved === 'function') {
        -      executeDefault = context.touchMoved(e);
        -      if (executeDefault === false) {
        -        e.preventDefault();
        -      }
        -    }
        -  }
        -};
        -
        -/**
        - * A function that's called once when a mouse button is pressed.
        - *
        - * Declaring the function `mousePressed()` sets a code block to run
        - * automatically when the user presses a mouse button:
        - *
        - * ```js
        - * function mousePressed() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        - * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        - * value when `mousePressed()` is called by p5.js:
        - *
        - * ```js
        - * function mousePressed() {
        - *   if (mouseX < 50) {
        - *     // Code to run if the mouse is on the left.
        - *   }
        - *
        - *   if (mouseY > 50) {
        - *     // Code to run if the mouse is near the bottom.
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, `event`, is optional. `mousePressed()` is always passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        - * object with properties that describe the mouse press event:
        - *
        - * ```js
        - * function mousePressed(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * On touchscreen devices, `mousePressed()` will run when a user’s touch
        - * begins if <a href="#/p5/touchStarted">touchStarted()</a> isn’t declared. If
        - * <a href="#/p5/touchStarted">touchStarted()</a> is declared, then
        - * <a href="#/p5/touchStarted">touchStarted()</a> will run when a user’s touch
        - * begins and `mousePressed()` won’t.
        - *
        - * Browsers may have default behaviors attached to various mouse events. For
        - * example, some browsers highlight text when the user moves the mouse while
        - * pressing a mouse button. To prevent any default behavior for this event,
        - * add `return false;` to the end of the function.
        - *
        - * Note: `mousePressed()`, <a href="#/p5/mouseReleased">mouseReleased()</a>,
        - * and <a href="#/p5/mouseClicked">mouseClicked()</a> are all related.
        - * `mousePressed()` runs as soon as the user clicks the mouse.
        - * <a href="#/p5/mouseReleased">mouseReleased()</a> runs as soon as the user
        - * releases the mouse click. <a href="#/p5/mouseClicked">mouseClicked()</a>
        - * runs immediately after <a href="#/p5/mouseReleased">mouseReleased()</a>.
        - *
        - * @method mousePressed
        - * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square becomes lighter when the user presses a mouse button.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * function mousePressed() {
        - *   // Update the grayscale value.
        - *   value += 5;
        - *
        - *   // Reset the grayscale value.
        - *   if (value > 255) {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Style the circle.
        - *   fill('orange');
        - *   stroke('royalblue');
        - *   strokeWeight(10);
        - *
        - *   describe(
        - *     'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(220);
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, 20);
        - * }
        - *
        - * // Set the stroke color and weight as soon as the user clicks.
        - * function mousePressed() {
        - *   stroke('deeppink');
        - *   strokeWeight(3);
        - * }
        - *
        - * // Set the stroke and fill colors as soon as the user releases
        - * // the mouse.
        - * function mouseReleased() {
        - *   stroke('royalblue');
        - *
        - *   // This is never visible because fill() is called
        - *   // in mouseClicked() which runs immediately after
        - *   // mouseReleased();
        - *   fill('limegreen');
        - * }
        - *
        - * // Set the fill color and stroke weight after
        - * // mousePressed() and mouseReleased() are called.
        - * function mouseClicked() {
        - *   fill('orange');
        - *   strokeWeight(10);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype._onmousedown = function(e) {
        -  const context = this._isGlobal ? window : this;
        -  let executeDefault;
        -  this._setProperty('mouseIsPressed', true);
        -  this._setMouseButton(e);
        -  this._updateNextMouseCoords(e);
        -
        -  // _ontouchstart triggers first and sets this.touchstart
        -  if (this.touchstart) {
        -    return;
        -  }
        -
        -  if (typeof context.mousePressed === 'function') {
        -    executeDefault = context.mousePressed(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        -    }
        -  } else if (typeof context.touchStarted === 'function') {
        -    executeDefault = context.touchStarted(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        -    }
        -  }
        -
        -  this.touchstart = false;
        -};
        -
        -/**
        - * A function that's called once when a mouse button is released.
        - *
        - * Declaring the function `mouseReleased()` sets a code block to run
        - * automatically when the user releases a mouse button after having pressed
        - * it:
        - *
        - * ```js
        - * function mouseReleased() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        - * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        - * value when `mouseReleased()` is called by p5.js:
        - *
        - * ```js
        - * function mouseReleased() {
        - *   if (mouseX < 50) {
        - *     // Code to run if the mouse is on the left.
        - *   }
        - *
        - *   if (mouseY > 50) {
        - *     // Code to run if the mouse is near the bottom.
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, `event`, is optional. `mouseReleased()` is always passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        - * object with properties that describe the mouse release event:
        - *
        - * ```js
        - * function mouseReleased(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * On touchscreen devices, `mouseReleased()` will run when a user’s touch
        - * ends if <a href="#/p5/touchEnded">touchEnded()</a> isn’t declared. If
        - * <a href="#/p5/touchEnded">touchEnded()</a> is declared, then
        - * <a href="#/p5/touchEnded">touchEnded()</a> will run when a user’s touch
        - * ends and `mouseReleased()` won’t.
        - *
        - * Browsers may have default behaviors attached to various mouse events. For
        - * example, some browsers highlight text when the user moves the mouse while
        - * pressing a mouse button. To prevent any default behavior for this event,
        - * add `return false;` to the end of the function.
        - *
        - * Note: <a href="#/p5/mousePressed">mousePressed()</a>, `mouseReleased()`,
        - * and <a href="#/p5/mouseClicked">mouseClicked()</a> are all related.
        - * <a href="#/p5/mousePressed">mousePressed()</a> runs as soon as the user
        - * clicks the mouse. `mouseReleased()` runs as soon as the user releases the
        - * mouse click. <a href="#/p5/mouseClicked">mouseClicked()</a> runs
        - * immediately after `mouseReleased()`.
        - *
        - * @method mouseReleased
        - * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square becomes lighter when the user presses and releases a mouse button.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * function mouseReleased() {
        - *   // Update the grayscale value.
        - *   value += 5;
        - *
        - *   // Reset the grayscale value.
        - *   if (value > 255) {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Style the circle.
        - *   fill('orange');
        - *   stroke('royalblue');
        - *   strokeWeight(10);
        - *
        - *   describe(
        - *     'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(220);
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, 20);
        - * }
        - *
        - * // Set the stroke color and weight as soon as the user clicks.
        - * function mousePressed() {
        - *   stroke('deeppink');
        - *   strokeWeight(3);
        - * }
        - *
        - * // Set the stroke and fill colors as soon as the user releases
        - * // the mouse.
        - * function mouseReleased() {
        - *   stroke('royalblue');
        - *
        - *   // This is never visible because fill() is called
        - *   // in mouseClicked() which runs immediately after
        - *   // mouseReleased();
        - *   fill('limegreen');
        - * }
        - *
        - * // Set the fill color and stroke weight after
        - * // mousePressed() and mouseReleased() are called.
        - * function mouseClicked() {
        - *   fill('orange');
        - *   strokeWeight(10);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype._onmouseup = function(e) {
        -  const context = this._isGlobal ? window : this;
        -  let executeDefault;
        -  this._setProperty('mouseIsPressed', false);
        -
        -  // _ontouchend triggers first and sets this.touchend
        -  if (this.touchend) {
        -    return;
        -  }
        -
        -  if (typeof context.mouseReleased === 'function') {
        -    executeDefault = context.mouseReleased(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        -    }
        -  } else if (typeof context.touchEnded === 'function') {
        -    executeDefault = context.touchEnded(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        -    }
        -  }
        -  this.touchend = false;
        -};
        -
        -p5.prototype._ondragend = p5.prototype._onmouseup;
        -p5.prototype._ondragover = p5.prototype._onmousemove;
        -
        -/**
        - * A function that's called once after a mouse button is pressed and released.
        - *
        - * Declaring the function `mouseClicked()` sets a code block to run
        - * automatically when the user releases a mouse button after having pressed
        - * it:
        - *
        - * ```js
        - * function mouseClicked() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        - * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        - * value when `mouseClicked()` is called by p5.js:
        - *
        - * ```js
        - * function mouseClicked() {
        - *   if (mouseX < 50) {
        - *     // Code to run if the mouse is on the left.
        - *   }
        - *
        - *   if (mouseY > 50) {
        - *     // Code to run if the mouse is near the bottom.
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, `event`, is optional. `mouseClicked()` is always passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        - * object with properties that describe the mouse click event:
        - *
        - * ```js
        - * function mouseClicked(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * On touchscreen devices, `mouseClicked()` will run when a user’s touch
        - * ends if <a href="#/p5/touchEnded">touchEnded()</a> isn’t declared. If
        - * <a href="#/p5/touchEnded">touchEnded()</a> is declared, then
        - * <a href="#/p5/touchEnded">touchEnded()</a> will run when a user’s touch
        - * ends and `mouseClicked()` won’t.
        - *
        - * Browsers may have default behaviors attached to various mouse events. For
        - * example, some browsers highlight text when the user moves the mouse while
        - * pressing a mouse button. To prevent any default behavior for this event,
        - * add `return false;` to the end of the function.
        - *
        - * Note: <a href="#/p5/mousePressed">mousePressed()</a>,
        - * <a href="#/p5/mouseReleased">mouseReleased()</a>,
        - * and `mouseClicked()` are all related.
        - * <a href="#/p5/mousePressed">mousePressed()</a> runs as soon as the user
        - * clicks the mouse. <a href="#/p5/mouseReleased">mouseReleased()</a> runs as
        - * soon as the user releases the mouse click. `mouseClicked()` runs
        - * immediately after <a href="#/p5/mouseReleased">mouseReleased()</a>.
        - *
        - * @method mouseClicked
        - * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square changes color when the user presses and releases a mouse button.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Toggle the square's color when the user clicks.
        - * function mouseClicked() {
        - *   if (value === 0) {
        - *     value = 255;
        - *   } else {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Style the circle.
        - *   fill('orange');
        - *   stroke('royalblue');
        - *   strokeWeight(10);
        - *
        - *   describe(
        - *     'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(220);
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, 20);
        - * }
        - *
        - * // Set the stroke color and weight as soon as the user clicks.
        - * function mousePressed() {
        - *   stroke('deeppink');
        - *   strokeWeight(3);
        - * }
        - *
        - * // Set the stroke and fill colors as soon as the user releases
        - * // the mouse.
        - * function mouseReleased() {
        - *   stroke('royalblue');
        - *
        - *   // This is never visible because fill() is called
        - *   // in mouseClicked() which runs immediately after
        - *   // mouseReleased();
        - *   fill('limegreen');
        - * }
        - *
        - * // Set the fill color and stroke weight after
        - * // mousePressed() and mouseReleased() are called.
        - * function mouseClicked() {
        - *   fill('orange');
        - *   strokeWeight(10);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype._onclick = function(e) {
        -  const context = this._isGlobal ? window : this;
        -  if (typeof context.mouseClicked === 'function') {
        -    const executeDefault = context.mouseClicked(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        -    }
        -  }
        -};
        -
        -/**
        - * A function that's called once when a mouse button is clicked twice quickly.
        - *
        - * Declaring the function `doubleClicked()` sets a code block to run
        - * automatically when the user presses and releases the mouse button twice
        - * quickly:
        - *
        - * ```js
        - * function doubleClicked() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        - * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        - * value when `doubleClicked()` is called by p5.js:
        - *
        - * ```js
        - * function doubleClicked() {
        - *   if (mouseX < 50) {
        - *     // Code to run if the mouse is on the left.
        - *   }
        - *
        - *   if (mouseY > 50) {
        - *     // Code to run if the mouse is near the bottom.
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, `event`, is optional. `doubleClicked()` is always passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        - * object with properties that describe the double-click event:
        - *
        - * ```js
        - * function doubleClicked(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * On touchscreen devices, code placed in `doubleClicked()` will run after two
        - * touches that occur within a short time.
        - *
        - * Browsers may have default behaviors attached to various mouse events. For
        - * example, some browsers highlight text when the user moves the mouse while
        - * pressing a mouse button. To prevent any default behavior for this event,
        - * add `return false;` to the end of the function.
        - *
        - * @method doubleClicked
        - * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square changes color when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Toggle the square's color when the user double-clicks.
        - * function doubleClicked() {
        - *   if (value === 0) {
        - *     value = 255;
        - *   } else {
        - *     value = 0;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black circle at its center. When the user double-clicks on the circle, it changes color to white.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the circle.
        - *   fill(value);
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, 80);
        - * }
        - *
        - * // Reassign value to 255 when the user double-clicks on the circle.
        - * function doubleClicked() {
        - *   if (dist(50, 50, mouseX, mouseY) < 40) {
        - *     value = 255;
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - */
        -
        -p5.prototype._ondblclick = function(e) {
        -  const context = this._isGlobal ? window : this;
        -  if (typeof context.doubleClicked === 'function') {
        -    const executeDefault = context.doubleClicked(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        -    }
        -  }
        -};
        -
        -/**
        - * For use with WebGL orbitControl.
        - * @property {Number} _mouseWheelDeltaY
        - * @readOnly
        - * @private
        - */
        -p5.prototype._mouseWheelDeltaY = 0;
        -
        -/**
        - * For use with WebGL orbitControl.
        - * @property {Number} _pmouseWheelDeltaY
        - * @readOnly
        - * @private
        - */
        -p5.prototype._pmouseWheelDeltaY = 0;
        -
        -/**
        - * A function that's called once when the mouse wheel moves.
        - *
        - * Declaring the function `mouseWheel()` sets a code block to run
        - * automatically when the user scrolls with the mouse wheel:
        - *
        - * ```js
        - * function mouseWheel() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        - * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        - * value when `mouseWheel()` is called by p5.js:
        - *
        - * ```js
        - * function mouseWheel() {
        - *   if (mouseX < 50) {
        - *     // Code to run if the mouse is on the left.
        - *   }
        - *
        - *   if (mouseY > 50) {
        - *     // Code to run if the mouse is near the bottom.
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, `event`, is optional. `mouseWheel()` is always passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        - * object with properties that describe the mouse scroll event:
        - *
        - * ```js
        - * function mouseWheel(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * The `event` object has many properties including `delta`, a `Number`
        - * containing the distance that the user scrolled. For example, `event.delta`
        - * might have the value 5 when the user scrolls up. `event.delta` is positive
        - * if the user scrolls up and negative if they scroll down. The signs are
        - * opposite on macOS with "natural" scrolling enabled.
        - *
        - * Browsers may have default behaviors attached to various mouse events. For
        - * example, some browsers highlight text when the user moves the mouse while
        - * pressing a mouse button. To prevent any default behavior for this event,
        - * add `return false;` to the end of the function.
        - *
        - * Note: On Safari, `mouseWheel()` may only work as expected if
        - * `return false;` is added at the end of the function.
        - *
        - * @method mouseWheel
        - * @param  {WheelEvent} [event] optional `WheelEvent` argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let circleSize = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square. A white circle at its center grows up when the user scrolls the mouse wheel.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the circle
        - *   circle(circleSize, 50, 50);
        - * }
        - *
        - * // Increment circleSize when the user scrolls the mouse wheel.
        - * function mouseWheel() {
        - *   circleSize += 1;
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let direction = '';
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square. An arrow at its center points up when the user scrolls up. The arrow points down when the user scrolls down.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Draw an arrow that points where
        - *   // the mouse last scrolled.
        - *   text(direction, 50, 50);
        - * }
        - *
        - * // Change direction when the user scrolls the mouse wheel.
        - * function mouseWheel(event) {
        - *   if (event.delta > 0) {
        - *     direction = '▲';
        - *   } else {
        - *     direction = '▼';
        - *   }
        - *   // Uncomment to prevent any default behavior.
        - *   // return false;
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype._onwheel = function(e) {
        -  const context = this._isGlobal ? window : this;
        -  this._setProperty('_mouseWheelDeltaY', e.deltaY);
        -  if (typeof context.mouseWheel === 'function') {
        -    e.delta = e.deltaY;
        -    const executeDefault = context.mouseWheel(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        -    }
        -  }
        -};
        -
        -/**
        - * Locks the mouse pointer to its current position and makes it invisible.
        - *
        - * `requestPointerLock()` allows the mouse to move forever without leaving the
        - * screen. Calling `requestPointerLock()` locks the values of
        - * <a href="#/p5/mouseX">mouseX</a>, <a href="#/p5/mouseY">mouseY</a>,
        - * <a href="#/p5/pmouseX">pmouseX</a>, and <a href="#/p5/pmouseY">pmouseY</a>.
        - * <a href="#/p5/movedX">movedX</a> and <a href="#/p5/movedY">movedY</a>
        - * continue updating and can be used to get the distance the mouse moved since
        - * the last frame was drawn. Calling
        - * <a href="#/p5/exitPointerLock">exitPointerLock()</a> resumes updating the
        - * mouse system variables.
        - *
        - * Note: Most browsers require an input, such as a click, before calling
        - * `requestPointerLock()`. It’s recommended to call `requestPointerLock()` in
        - * an event function such as <a href="#/p5/doubleClicked">doubleClicked()</a>.
        - *
        - * @method requestPointerLock
        - *
        - * @example
        - * <div>
        - * <code>
        - * let score = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with the text "Score: X" at its center. The score increases when the user moves the mouse upward. It decreases when the user moves the mouse downward.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Update the score.
        - *   score -= movedY;
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the score.
        - *   text(`Score: ${score}`, 50, 50);
        - * }
        - *
        - * // Lock the pointer when the user double-clicks.
        - * function doubleClicked() {
        - *   requestPointerLock();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.requestPointerLock = function() {
        -  // pointer lock object forking for cross browser
        -  const canvas = this._curElement.elt;
        -  canvas.requestPointerLock =
        -    canvas.requestPointerLock || canvas.mozRequestPointerLock;
        -  if (!canvas.requestPointerLock) {
        -    console.log('requestPointerLock is not implemented in this browser');
        -    return false;
        -  }
        -  canvas.requestPointerLock();
        -  return true;
        -};
        -
        -/**
        - * Exits a pointer lock started with
        - * <a href="#/p5/requestPointerLock">requestPointerLock</a>.
        - *
        - * Calling `requestPointerLock()` locks the values of
        - * <a href="#/p5/mouseX">mouseX</a>, <a href="#/p5/mouseY">mouseY</a>,
        - * <a href="#/p5/pmouseX">pmouseX</a>, and <a href="#/p5/pmouseY">pmouseY</a>.
        - * Calling `exitPointerLock()` resumes updating the mouse system variables.
        - *
        - * Note: Most browsers require an input, such as a click, before calling
        - * `requestPointerLock()`. It’s recommended to call `requestPointerLock()` in
        - * an event function such as <a href="#/p5/doubleClicked">doubleClicked()</a>.
        - *
        - * @method exitPointerLock
        - *
        - * @example
        - * <div>
        - * <code>
        - * let isLocked = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a word at its center. The word changes between "Unlocked" and "Locked" when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Tell the user whether the pointer is locked.
        - *   if (isLocked === true) {
        - *     text('Locked', 50, 50);
        - *   } else {
        - *     text('Unlocked', 50, 50);
        - *   }
        - * }
        - *
        - * // Toggle the pointer lock when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isLocked === true) {
        - *     exitPointerLock();
        - *     isLocked = false;
        - *   } else {
        - *     requestPointerLock();
        - *     isLocked = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.exitPointerLock = function() {
        -  document.exitPointerLock();
        -};
        -
        -export default p5;
        diff --git a/src/events/pointer.js b/src/events/pointer.js
        new file mode 100644
        index 0000000000..a934118917
        --- /dev/null
        +++ b/src/events/pointer.js
        @@ -0,0 +1,2070 @@
        +/**
        + * @module Events
        + * @submodule Pointer
        + * @for p5
        + * @requires core
        + * @requires constants
        + */
        +
        +import * as constants from '../core/constants';
        +
        +function pointer(p5, fn){
        +  /**
        +   * A `Number` system variable that tracks the mouse's horizontal movement.
        +   *
        +   * `movedX` tracks how many pixels the mouse moves left or right between
        +   * frames. `movedX` will have a negative value if the mouse moves left between
        +   * frames and a positive value if it moves right. `movedX` can be calculated
        +   * as `mouseX - pmouseX`.
        +   *
        +   * Note: `movedX` continues updating even when
        +   * <a href="#/p5/requestPointerLock">requestPointerLock()</a> is active.
        +   *
        +   * @property {Number} movedX
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square. The text ">>" appears when the user moves the mouse to the right. The text "<<" appears when the user moves the mouse to the left.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display >> when movedX is positive and
        +   *   // << when it's negative.
        +   *   if (movedX > 0) {
        +   *     text('>>', 50, 50);
        +   *   } else if (movedX < 0) {
        +   *     text('<<', 50, 50);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.movedX = 0;
        +
        +  /**
        +   * A `Number` system variable that tracks the mouse's vertical movement.
        +   *
        +   * `movedY` tracks how many pixels the mouse moves up or down between
        +   * frames. `movedY` will have a negative value if the mouse moves up between
        +   * frames and a positive value if it moves down. `movedY` can be calculated
        +   * as `mouseY - pmouseY`.
        +   *
        +   * Note: `movedY` continues updating even when
        +   * <a href="#/p5/requestPointerLock">requestPointerLock()</a> is active.
        +   *
        +   * @property {Number} movedY
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square. The text "▲" appears when the user moves the mouse upward. The text "▼" appears when the user moves the mouse downward.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display ▼ when movedY is positive and
        +   *   // ▲ when it's negative.
        +   *   if (movedY > 0) {
        +   *     text('▼', 50, 50);
        +   *   } else if (movedY < 0) {
        +   *     text('▲', 50, 50);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.movedY = 0;
        +  /*
        +   * This is a flag which is false until the first time
        +   * we receive a mouse event. The pmouseX and pmouseY
        +   * values will match the mouseX and mouseY values until
        +   * this interaction takes place.
        +   */
        +  fn._hasMouseInteracted = false;
        +
        +  /**
        +   * A `Number` system variable that tracks the mouse's horizontal position.
        +   *
        +   * In 2D mode, `mouseX` keeps track of the mouse's position relative to the
        +   * top-left corner of the canvas. For example, if the mouse is 50 pixels from
        +   * the left edge of the canvas, then `mouseX` will be 50.
        +   *
        +   * In WebGL mode, `mouseX` keeps track of the mouse's position relative to the
        +   * center of the canvas. For example, if the mouse is 50 pixels to the right
        +   * of the canvas' center, then `mouseX` will be 50.
        +   *
        +   * If touch is used instead of the mouse, then `mouseX` will hold the
        +   * x-coordinate of the most recent touch point.
        +   *
        +   * @property {Number} mouseX
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe("A vertical black line moves left and right following the mouse's x-position.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a vertical line that follows the mouse's x-coordinate.
        +   *   line(mouseX, 0, mouseX, 100);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the mouse's coordinates.
        +   *   text(`x: ${mouseX} y: ${mouseY}`, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe("A vertical black line moves left and right following the mouse's x-position.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Adjust coordinates for WebGL mode.
        +   *   // The origin (0, 0) is at the center of the canvas.
        +   *   let mx = mouseX - 50;
        +   *
        +   *   // Draw the line.
        +   *   line(mx, -50, mx, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let font;
        +   *
        +   * // Load a font for WebGL mode.
        +   * function preload() {
        +   *   font = loadFont('assets/inconsolata.otf');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     "A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Display the mouse's coordinates.
        +   *   text(`x: ${mouseX} y: ${mouseY}`, 0, 0);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.mouseX = 0;
        +
        +  /**
        +   * A `Number` system variable that tracks the mouse's vertical position.
        +   *
        +   * In 2D mode, `mouseY` keeps track of the mouse's position relative to the
        +   * top-left corner of the canvas. For example, if the mouse is 50 pixels from
        +   * the top edge of the canvas, then `mouseY` will be 50.
        +   *
        +   * In WebGL mode, `mouseY` keeps track of the mouse's position relative to the
        +   * center of the canvas. For example, if the mouse is 50 pixels below the
        +   * canvas' center, then `mouseY` will be 50.
        +   *
        +   * If touch is used instead of the mouse, then `mouseY` will hold the
        +   * y-coordinate of the most recent touch point.
        +   *
        +   * @property {Number} mouseY
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe("A horizontal black line moves up and down following the mouse's y-position.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a horizontal line that follows the mouse's y-coordinate.
        +   *   line(0, mouseY, 100, mouseY);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the mouse's coordinates.
        +   *   text(`x: ${mouseX} y: ${mouseY}`, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe("A horizontal black line moves up and down following the mouse's y-position.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Adjust coordinates for WebGL mode.
        +   *   // The origin (0, 0) is at the center of the canvas.
        +   *   let my = mouseY - 50;
        +   *
        +   *   // Draw the line.
        +   *   line(-50, my, 50, my);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let font;
        +   *
        +   * // Load a font for WebGL mode.
        +   * function preload() {
        +   *   font = loadFont('assets/inconsolata.otf');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     "A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse."
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Display the mouse's coordinates.
        +   *   text(`x: ${mouseX} y: ${mouseY}`, 0, 0);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.mouseY = 0;
        +
        +  /**
        +   * A `Number` system variable that tracks the mouse's previous horizontal
        +   * position.
        +   *
        +   * In 2D mode, `pmouseX` keeps track of the mouse's position relative to the
        +   * top-left corner of the canvas. Its value is
        +   * <a href="#/p5/mouseX">mouseX</a> from the previous frame. For example, if
        +   * the mouse was 50 pixels from the left edge of the canvas during the last
        +   * frame, then `pmouseX` will be 50.
        +   *
        +   * In WebGL mode, `pmouseX` keeps track of the mouse's position relative to the
        +   * center of the canvas. For example, if the mouse was 50 pixels to the right
        +   * of the canvas' center during the last frame, then `pmouseX` will be 50.
        +   *
        +   * If touch is used instead of the mouse, then `pmouseX` will hold the
        +   * x-coordinate of the last touch point.
        +   *
        +   * Note: `pmouseX` is reset to the current <a href="#/p5/mouseX">mouseX</a>
        +   * value at the start of each touch event.
        +   *
        +   * @property {Number} pmouseX
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(10);
        +   *
        +   *   describe('A line follows the mouse as it moves. The line grows longer with faster movements.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   line(pmouseX, pmouseY, mouseX, mouseY);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A line follows the mouse as it moves. The line grows longer with faster movements.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Adjust coordinates for WebGL mode.
        +   *   // The origin (0, 0) is at the center of the canvas.
        +   *   let pmx = pmouseX - 50;
        +   *   let pmy = pmouseY - 50;
        +   *   let mx = mouseX - 50;
        +   *   let my = mouseY - 50;
        +   *
        +   *   // Draw the line.
        +   *   line(pmx, pmy, mx, my);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.pmouseX = 0;
        +
        +  /**
        +   * A `Number` system variable that tracks the mouse's previous vertical
        +   * position.
        +   *
        +   * In 2D mode, `pmouseY` keeps track of the mouse's position relative to the
        +   * top-left corner of the canvas. Its value is
        +   * <a href="#/p5/mouseY">mouseY</a> from the previous frame. For example, if
        +   * the mouse was 50 pixels from the top edge of the canvas during the last
        +   * frame, then `pmouseY` will be 50.
        +   *
        +   * In WebGL mode, `pmouseY` keeps track of the mouse's position relative to the
        +   * center of the canvas. For example, if the mouse was 50 pixels below the
        +   * canvas' center during the last frame, then `pmouseY` will be 50.
        +   *
        +   * If touch is used instead of the mouse, then `pmouseY` will hold the
        +   * y-coordinate of the last touch point.
        +   *
        +   * Note: `pmouseY` is reset to the current <a href="#/p5/mouseY">mouseY</a>
        +   * value at the start of each touch event.
        +   *
        +   * @property {Number} pmouseY
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(10);
        +   *
        +   *   describe('A line follows the mouse as it moves. The line grows longer with faster movements.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   line(pmouseX, pmouseY, mouseX, mouseY);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A line follows the mouse as it moves. The line grows longer with faster movements.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Adjust coordinates for WebGL mode.
        +   *   // The origin (0, 0) is at the center of the canvas.
        +   *   let pmx = pmouseX - 50;
        +   *   let pmy = pmouseY - 50;
        +   *   let mx = mouseX - 50;
        +   *   let my = mouseY - 50;
        +   *
        +   *   // Draw the line.
        +   *   line(pmx, pmy, mx, my);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.pmouseY = 0;
        +
        +  /**
        +   * A `Number` variable that tracks the mouse's horizontal position within the
        +   * browser.
        +   *
        +   * `winMouseX` keeps track of the mouse's position relative to the top-left
        +   * corner of the browser window. For example, if the mouse is 50 pixels from
        +   * the left edge of the browser, then `winMouseX` will be 50.
        +   *
        +   * On a touchscreen device, `winMouseX` will hold the x-coordinate of the most
        +   * recent touch point.
        +   *
        +   * Note: Use <a href="#/p5/mouseX">mouseX</a> to track the mouse’s
        +   * x-coordinate within the canvas.
        +   *
        +   * @property {Number} winMouseX
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the mouse's coordinates within the browser window.
        +   *   text(`x: ${winMouseX} y: ${winMouseY}`, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.winMouseX = 0;
        +
        +  /**
        +   * A `Number` variable that tracks the mouse's vertical position within the
        +   * browser.
        +   *
        +   * `winMouseY` keeps track of the mouse's position relative to the top-left
        +   * corner of the browser window. For example, if the mouse is 50 pixels from
        +   * the top edge of the browser, then `winMouseY` will be 50.
        +   *
        +   * On a touchscreen device, `winMouseY` will hold the y-coordinate of the most
        +   * recent touch point.
        +   *
        +   * Note: Use <a href="#/p5/mouseY">mouseY</a> to track the mouse’s
        +   * y-coordinate within the canvas.
        +   *
        +   * @property {Number} winMouseY
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe("A gray square. The mouse's x- and y-coordinates are displayed as the user moves the mouse.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the mouse's coordinates within the browser window.
        +   *   text(`x: ${winMouseX} y: ${winMouseY}`, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.winMouseY = 0;
        +
        +  /**
        +   * A `Number` variable that tracks the mouse's previous horizontal position
        +   * within the browser.
        +   *
        +   * `pwinMouseX` keeps track of the mouse's position relative to the top-left
        +   * corner of the browser window. Its value is
        +   * <a href="#/p5/winMouseX">winMouseX</a> from the previous frame. For
        +   * example, if the mouse was 50 pixels from
        +   * the left edge of the browser during the last frame, then `pwinMouseX` will
        +   * be 50.
        +   *
        +   * On a touchscreen device, `pwinMouseX` will hold the x-coordinate of the most
        +   * recent touch point. `pwinMouseX` is reset to the current
        +   * <a href="#/p5/winMouseX">winMouseX</a> value at the start of each touch
        +   * event.
        +   *
        +   * Note: Use <a href="#/p5/pmouseX">pmouseX</a> to track the mouse’s previous
        +   * x-coordinate within the canvas.
        +   *
        +   * @property {Number} pwinMouseX
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(10);
        +   *
        +   *   describe('A gray square. A white circle at its center grows larger when the mouse moves horizontally.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the circle's diameter.
        +   *   let d = winMouseX - pwinMouseX;
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, d);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create the canvas and set its position.
        +   *   let cnv = createCanvas(100, 100);
        +   *   cnv.position(20, 20);
        +   *
        +   *   describe('A gray square with a number at its center. The number changes as the user moves the mouse vertically.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display pwinMouseX.
        +   *   text(pwinMouseX, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.pwinMouseX = 0;
        +
        +  /**
        +   * A `Number` variable that tracks the mouse's previous vertical position
        +   * within the browser.
        +   *
        +   * `pwinMouseY` keeps track of the mouse's position relative to the top-left
        +   * corner of the browser window. Its value is
        +   * <a href="#/p5/winMouseY">winMouseY</a> from the previous frame. For
        +   * example, if the mouse was 50 pixels from
        +   * the top edge of the browser during the last frame, then `pwinMouseY` will
        +   * be 50.
        +   *
        +   * On a touchscreen device, `pwinMouseY` will hold the y-coordinate of the most
        +   * recent touch point. `pwinMouseY` is reset to the current
        +   * <a href="#/p5/winMouseY">winMouseY</a> value at the start of each touch
        +   * event.
        +   *
        +   * Note: Use <a href="#/p5/pmouseY">pmouseY</a> to track the mouse’s previous
        +   * y-coordinate within the canvas.
        +   *
        +   * @property {Number} pwinMouseY
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(10);
        +   *
        +   *   describe('A gray square. A white circle at its center grows larger when the mouse moves vertically.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the circle's diameter.
        +   *   let d = winMouseY - pwinMouseY;
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, d);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Create the canvas and set its position.
        +   *   let cnv = createCanvas(100, 100);
        +   *   cnv.position(20, 20);
        +   *
        +   *   describe('A gray square with a number at its center. The number changes as the user moves the mouse vertically.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display pwinMouseY.
        +   *   text(pwinMouseY, 50, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.pwinMouseY = 0;
        +
        +  /**
        +   * An object that tracks the current state of mouse buttons, showing which
        +   * buttons are pressed at any given moment.
        +   *
        +   * The `mouseButton` object has three properties:
        +   * - `left`: A boolean indicating whether the left mouse button is pressed.
        +   * - `right`: A boolean indicating whether the right mouse button is pressed.
        +   * - `center`: A boolean indicating whether the middle mouse button (scroll wheel button) is pressed.
        +   *
        +   * Note: Different browsers may track `mouseButton` differently. See
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons" target="_blank">MDN</a>
        +   * for more information.
        +   *
        +   * @property {Object} mouseButton
        +   * @property {boolean} mouseButton.left - Whether the left mouse button is pressed.
        +   * @property {boolean} mouseButton.right - Whether the right mouse button is pressed.
        +   * @property {boolean} mouseButton.center - Whether the middle mouse button is pressed.
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(200, 200);
        +   *
        +   *   describe(
        +   *     'A gray square with black text at its center. The text changes from 0 to either "left" or "right" when the user clicks a mouse button.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the mouse button.
        +   *   text(`Left: ${mouseButton.left}`, width / 2, height / 2 - 20);
        +   *   text(`Right: ${mouseButton.right}`, width / 2, height / 2);
        +   *   text(`Center: ${mouseButton.center}`, width / 2, height / 2 + 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     "A gray square. Different shapes appear at its center depending on the mouse button that's clicked."
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   if (mouseIsPressed === true) {
        +   *     if (mouseButton.left) {
        +   *       circle(50, 50, 50);
        +   *     }
        +   *     if (mouseButton.right) {
        +   *       square(25, 25, 50);
        +   *     }
        +   *     if (mouseButton.center) {
        +   *       triangle(23, 75, 50, 20, 78, 75);
        +   *     }
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.mouseButton = {
        +    left: false,
        +    right: false,
        +    center: false
        +  };
        +
        +   /**
        +   * An `Array` of all the current touch points on a touchscreen device.
        +   *
        +   * The `touches` array is empty by default. When the user touches their
        +   * screen, a new touch point is tracked and added to the array. Touch points
        +   * are `Objects` with the following properties:
        +   *
        +   * ```js
        +   * // Iterate over the touches array.
        +   * for (let touch of touches) {
        +   *   // x-coordinate relative to the top-left
        +   *   // corner of the canvas.
        +   *   console.log(touch.x);
        +   *
        +   *   // y-coordinate relative to the top-left
        +   *   // corner of the canvas.
        +   *   console.log(touch.y);
        +   *
        +   *   // x-coordinate relative to the top-left
        +   *   // corner of the browser.
        +   *   console.log(touch.winX);
        +   *
        +   *   // y-coordinate relative to the top-left
        +   *   // corner of the browser.
        +   *   console.log(touch.winY);
        +   *
        +   *   // ID number
        +   *   console.log(touch.id);
        +   * }
        +   * ```
        +   *
        +   * @property {Object[]} touches
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // On a touchscreen device, touch the canvas using one or more fingers
        +   * // at the same time.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square. White circles appear where the user touches the square.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a circle at each touch point.
        +   *   for (let touch of touches) {
        +   *     circle(touch.x, touch.y, 40);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // On a touchscreen device, touch the canvas using one or more fingers
        +   * // at the same time.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square. Labels appear where the user touches the square, displaying the coordinates.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a label above each touch point.
        +   *   for (let touch of touches) {
        +   *     text(`${touch.x}, ${touch.y}`, touch.x, touch.y - 40);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +   fn.touches = [];
        +   fn._activePointers = new Map();
        +
        +  /**
        +   * A `Boolean` system variable that's `true` if the mouse is pressed and
        +   * `false` if not.
        +   *
        +   * @property {Boolean} mouseIsPressed
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with the word "false" at its center. The word changes to "true" when the user presses a mouse button.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the mouseIsPressed variable.
        +   *   text(mouseIsPressed, 25, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a white square at its center. The inner square turns black when the user presses the mouse.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   if (mouseIsPressed === true) {
        +   *     fill(0);
        +   *   } else {
        +   *     fill(255);
        +   *   }
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.mouseIsPressed = false;
        +
        +  fn._updatePointerCoords = function (e) {
        +    if (this._curElement !== null) {
        +       const canvas = this._curElement.elt;
        +       const sx = canvas.scrollWidth / this.width || 1;
        +       const sy = canvas.scrollHeight / this.height || 1;
        +
        +       if (e.pointerType == 'touch') {
        +          const touches = [];
        +          for (const touch of this._activePointers.values()) {
        +             touches.push(getTouchInfo(canvas, sx, sy, touch));
        +          }
        +          this.touches = touches;
        +       } else {
        +          const mousePos = getMouseInfo(canvas, sx, sy, e);
        +          this.movedX = e.movementX || 0;
        +          this.movedY = e.movementY || 0;
        +          this.mouseX = mousePos.x;
        +          this.mouseY = mousePos.y;
        +          this.winMouseX = mousePos.winX;
        +          this.winMouseY = mousePos.winY;
        +       }
        +
        +       if (!this._hasMouseInteracted) {
        +          this._updateMouseCoords();
        +          this._hasMouseInteracted = true;
        +       }
        +    }
        + };
        +
        +  fn._updateMouseCoords = function() {
        +    this.pmouseX = this.mouseX;
        +    this.pmouseY = this.mouseY;
        +    this.pwinMouseX = this.winMouseX;
        +    this.pwinMouseY = this.winMouseY;
        +    this._pmouseWheelDeltaY = this._mouseWheelDeltaY;
        +  };
        +
        +  function getMouseInfo(canvas, sx, sy, evt) {
        +    const rect = canvas.getBoundingClientRect();
        +    return {
        +       x: (evt.clientX - rect.left) / sx,
        +       y: (evt.clientY - rect.top) / sy,
        +       winX: evt.clientX,
        +       winY: evt.clientY,
        +    };
        + }
        +
        + function getTouchInfo(canvas, sx, sy, touch) {
        +  const rect = canvas.getBoundingClientRect();
        +  return {
        +     x: (touch.clientX - rect.left) / sx,
        +     y: (touch.clientY - rect.top) / sy,
        +     winX: touch.clientX,
        +     winY: touch.clientY,
        +     id: touch.pointerId,
        +  };
        +}
        +
        +fn._setMouseButton = function(e) {
        +  // Check all active touches to determine button states
        +  this.mouseButton.left = Array.from(this._activePointers.values()).some(touch => 
        +    (touch.buttons & 1) !== 0
        +  );
        +  this.mouseButton.center = Array.from(this._activePointers.values()).some(touch =>
        +    (touch.buttons & 4) !== 0
        +  );
        +  this.mouseButton.right = Array.from(this._activePointers.values()).some(touch =>
        +    (touch.buttons & 2) !== 0
        +  );
        +};
        +
        +  /**
        +   * A function that's called when the mouse moves.
        +   *
        +   * Declaring the function `mouseMoved()` sets a code block to run
        +   * automatically when the user moves the mouse without clicking any mouse
        +   * buttons:
        +   *
        +   * ```js
        +   * function mouseMoved() {
        +   *   // Code to run.
        +   * }
        +   * ```
        +   *
        +   * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        +   * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        +   * value when `mouseMoved()` is called by p5.js:
        +   *
        +   * ```js
        +   * function mouseMoved() {
        +   *   if (mouseX < 50) {
        +   *     // Code to run if the mouse is on the left.
        +   *   }
        +   *
        +   *   if (mouseY > 50) {
        +   *     // Code to run if the mouse is near the bottom.
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * The parameter, `event`, is optional. `mouseMoved()` is always passed a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        +   * object with properties that describe the mouse move event:
        +   *
        +   * ```js
        +   * function mouseMoved(event) {
        +   *   // Code to run that uses the event.
        +   *   console.log(event);
        +   * }
        +   * ```
        +   *
        +   * Browsers may have default behaviors attached to various mouse events. For
        +   * example, some browsers highlight text when the user moves the mouse while
        +   * pressing a mouse button. To prevent any default behavior for this event,
        +   * add `return false;` to the end of the function.
        +   *
        +   * @method mouseMoved
        +   * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black square at its center. The inner square becomes lighter as the mouse moves.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * function mouseMoved() {
        +   *   // Update the grayscale value.
        +   *   value += 5;
        +   *
        +   *   // Reset the grayscale value.
        +   *   if (value > 255) {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * A function that's called when the mouse moves while a button is pressed.
        +   *
        +   * Declaring the function `mouseDragged()` sets a code block to run
        +   * automatically when the user clicks and drags the mouse:
        +   *
        +   * ```js
        +   * function mouseDragged() {
        +   *   // Code to run.
        +   * }
        +   * ```
        +   *
        +   * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        +   * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        +   * value when `mouseDragged()` is called by p5.js:
        +   *
        +   * ```js
        +   * function mouseDragged() {
        +   *   if (mouseX < 50) {
        +   *     // Code to run if the mouse is on the left.
        +   *   }
        +   *
        +   *   if (mouseY > 50) {
        +   *     // Code to run if the mouse is near the bottom.
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * The parameter, `event`, is optional. `mouseDragged()` is always passed a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        +   * object with properties that describe the mouse drag event:
        +   *
        +   * ```js
        +   * function mouseDragged(event) {
        +   *   // Code to run that uses the event.
        +   *   console.log(event);
        +   * }
        +   * ```
        +   *
        +   * On touchscreen devices, `mouseDragged()` will run when a user moves a touch
        +   * point.
        +   *
        +   * Browsers may have default behaviors attached to various mouse events. For
        +   * example, some browsers highlight text when the user moves the mouse while
        +   * pressing a mouse button. To prevent any default behavior for this event,
        +   * add `return false;` to the end of the function.
        +   *
        +   * @method mouseDragged
        +   * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black square at its center. The inner square becomes lighter as the user drags the mouse.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * function mouseDragged() {
        +   *   // Update the grayscale value.
        +   *   value += 5;
        +   *
        +   *   // Reset the grayscale value.
        +   *   if (value > 255) {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn._onpointermove = function(e) {
        +    const context = this._isGlobal ? window : this;
        +    let executeDefault;
        +    this._updatePointerCoords(e);
        +    this._activePointers.set(e.pointerId, e);
        +    this._setMouseButton(e);
        +    
        +
        +      if (!this.mouseIsPressed && typeof context.mouseMoved === 'function') {
        +        executeDefault = context.mouseMoved(e);
        +        if (executeDefault === false) {
        +          e.preventDefault();
        +        }
        +      } else if (this.mouseIsPressed && typeof context.mouseDragged === 'function') {
        +        executeDefault = context.mouseDragged(e);
        +        if (executeDefault === false) {
        +          e.preventDefault();
        +        }
        +      }
        +  };
        +
        +  /**
        +   * A function that's called once when a mouse button is pressed.
        +   *
        +   * Declaring the function `mousePressed()` sets a code block to run
        +   * automatically when the user presses a mouse button:
        +   *
        +   * ```js
        +   * function mousePressed() {
        +   *   // Code to run.
        +   * }
        +   * ```
        +   *
        +   * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        +   * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        +   * value when `mousePressed()` is called by p5.js:
        +   *
        +   * ```js
        +   * function mousePressed() {
        +   *   if (mouseX < 50) {
        +   *     // Code to run if the mouse is on the left.
        +   *   }
        +   *
        +   *   if (mouseY > 50) {
        +   *     // Code to run if the mouse is near the bottom.
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * The parameter, `event`, is optional. `mousePressed()` is always passed a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        +   * object with properties that describe the mouse press event:
        +   *
        +   * ```js
        +   * function mousePressed(event) {
        +   *   // Code to run that uses the event.
        +   *   console.log(event);
        +   * }
        +   * ```
        +   *
        +   * On touchscreen devices, `mousePressed()` will run when a user’s touch
        +   * begins.
        +   *
        +   * Browsers may have default behaviors attached to various mouse events. For
        +   * example, some browsers highlight text when the user moves the mouse while
        +   * pressing a mouse button. To prevent any default behavior for this event,
        +   * add `return false;` to the end of the function.
        +   *
        +   * Note: `mousePressed()`, <a href="#/p5/mouseReleased">mouseReleased()</a>,
        +   * and <a href="#/p5/mouseClicked">mouseClicked()</a> are all related.
        +   * `mousePressed()` runs as soon as the user clicks the mouse.
        +   * <a href="#/p5/mouseReleased">mouseReleased()</a> runs as soon as the user
        +   * releases the mouse click. <a href="#/p5/mouseClicked">mouseClicked()</a>
        +   * runs immediately after <a href="#/p5/mouseReleased">mouseReleased()</a>.
        +   *
        +   * @method mousePressed
        +   * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black square at its center. The inner square becomes lighter when the user presses a mouse button.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * function mousePressed() {
        +   *   // Update the grayscale value.
        +   *   value += 5;
        +   *
        +   *   // Reset the grayscale value.
        +   *   if (value > 255) {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Style the circle.
        +   *   fill('orange');
        +   *   stroke('royalblue');
        +   *   strokeWeight(10);
        +   *
        +   *   describe(
        +   *     'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(220);
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, 20);
        +   * }
        +   *
        +   * // Set the stroke color and weight as soon as the user clicks.
        +   * function mousePressed() {
        +   *   stroke('deeppink');
        +   *   strokeWeight(3);
        +   * }
        +   *
        +   * // Set the stroke and fill colors as soon as the user releases
        +   * // the mouse.
        +   * function mouseReleased() {
        +   *   stroke('royalblue');
        +   *
        +   *   // This is never visible because fill() is called
        +   *   // in mouseClicked() which runs immediately after
        +   *   // mouseReleased();
        +   *   fill('limegreen');
        +   * }
        +   *
        +   * // Set the fill color and stroke weight after
        +   * // mousePressed() and mouseReleased() are called.
        +   * function mouseClicked() {
        +   *   fill('orange');
        +   *   strokeWeight(10);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn._onpointerdown = function(e) {
        +    const context = this._isGlobal ? window : this;
        +    let executeDefault;
        +    this.mouseIsPressed = true;
        +
        +    this._activePointers.set(e.pointerId, e);
        +    this._setMouseButton(e);
        +    this._updatePointerCoords(e);
        +
        +    if (typeof context.mousePressed === 'function') {
        +      executeDefault = context.mousePressed(e);
        +      if (executeDefault === false) {
        +        e.preventDefault();
        +      }
        +    } 
        +  };
        +
        +  /**
        +   * A function that's called once when a mouse button is released.
        +   *
        +   * Declaring the function `mouseReleased()` sets a code block to run
        +   * automatically when the user releases a mouse button after having pressed
        +   * it:
        +   *
        +   * ```js
        +   * function mouseReleased() {
        +   *   // Code to run.
        +   * }
        +   * ```
        +   *
        +   * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        +   * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        +   * value when `mouseReleased()` is called by p5.js:
        +   *
        +   * ```js
        +   * function mouseReleased() {
        +   *   if (mouseX < 50) {
        +   *     // Code to run if the mouse is on the left.
        +   *   }
        +   *
        +   *   if (mouseY > 50) {
        +   *     // Code to run if the mouse is near the bottom.
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * The parameter, `event`, is optional. `mouseReleased()` is always passed a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        +   * object with properties that describe the mouse release event:
        +   *
        +   * ```js
        +   * function mouseReleased(event) {
        +   *   // Code to run that uses the event.
        +   *   console.log(event);
        +   * }
        +   * ```
        +   *
        +   * On touchscreen devices, `mouseReleased()` will run when a user’s touch
        +   * ends.
        +   *
        +   * Browsers may have default behaviors attached to various mouse events. For
        +   * example, some browsers highlight text when the user moves the mouse while
        +   * pressing a mouse button. To prevent any default behavior for this event,
        +   * add `return false;` to the end of the function.
        +   *
        +   * Note: <a href="#/p5/mousePressed">mousePressed()</a>, `mouseReleased()`,
        +   * and <a href="#/p5/mouseClicked">mouseClicked()</a> are all related.
        +   * <a href="#/p5/mousePressed">mousePressed()</a> runs as soon as the user
        +   * clicks the mouse. `mouseReleased()` runs as soon as the user releases the
        +   * mouse click. <a href="#/p5/mouseClicked">mouseClicked()</a> runs
        +   * immediately after `mouseReleased()`.
        +   *
        +   * @method mouseReleased
        +   * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black square at its center. The inner square becomes lighter when the user presses and releases a mouse button.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * function mouseReleased() {
        +   *   // Update the grayscale value.
        +   *   value += 5;
        +   *
        +   *   // Reset the grayscale value.
        +   *   if (value > 255) {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Style the circle.
        +   *   fill('orange');
        +   *   stroke('royalblue');
        +   *   strokeWeight(10);
        +   *
        +   *   describe(
        +   *     'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(220);
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, 20);
        +   * }
        +   *
        +   * // Set the stroke color and weight as soon as the user clicks.
        +   * function mousePressed() {
        +   *   stroke('deeppink');
        +   *   strokeWeight(3);
        +   * }
        +   *
        +   * // Set the stroke and fill colors as soon as the user releases
        +   * // the mouse.
        +   * function mouseReleased() {
        +   *   stroke('royalblue');
        +   *
        +   *   // This is never visible because fill() is called
        +   *   // in mouseClicked() which runs immediately after
        +   *   // mouseReleased();
        +   *   fill('limegreen');
        +   * }
        +   *
        +   * // Set the fill color and stroke weight after
        +   * // mousePressed() and mouseReleased() are called.
        +   * function mouseClicked() {
        +   *   fill('orange');
        +   *   strokeWeight(10);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn._onpointerup = function(e) {
        +    const context = this._isGlobal ? window : this;
        +    let executeDefault;
        +    this.mouseIsPressed = false;
        +
        +    this._activePointers.delete(e.pointerId);
        +    this._setMouseButton(e);
        +
        +    this._updatePointerCoords(e);
        +   
        +    if (typeof context.mouseReleased === 'function') {
        +      executeDefault = context.mouseReleased(e);
        +      if (executeDefault === false) {
        +        e.preventDefault();
        +      }
        +    }
        +  };
        +
        +  fn._ondragend = fn._onpointerup;
        +  fn._ondragover = fn._onpointermove;
        +
        +  /**
        +   * A function that's called once after a mouse button is pressed and released.
        +   *
        +   * Declaring the function `mouseClicked()` sets a code block to run
        +   * automatically when the user releases a mouse button after having pressed
        +   * it:
        +   *
        +   * ```js
        +   * function mouseClicked() {
        +   *   // Code to run.
        +   * }
        +   * ```
        +   *
        +   * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        +   * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        +   * value when `mouseClicked()` is called by p5.js:
        +   *
        +   * ```js
        +   * function mouseClicked() {
        +   *   if (mouseX < 50) {
        +   *     // Code to run if the mouse is on the left.
        +   *   }
        +   *
        +   *   if (mouseY > 50) {
        +   *     // Code to run if the mouse is near the bottom.
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * The parameter, `event`, is optional. `mouseClicked()` is always passed a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        +   * object with properties that describe the mouse click event:
        +   *
        +   * ```js
        +   * function mouseClicked(event) {
        +   *   // Code to run that uses the event.
        +   *   console.log(event);
        +   * }
        +   * ```
        +   *
        +   * On touchscreen devices, `mouseClicked()` will run when a user’s touch
        +   * ends.
        +   *
        +   * Browsers may have default behaviors attached to various mouse events. For
        +   * example, some browsers highlight text when the user moves the mouse while
        +   * pressing a mouse button. To prevent any default behavior for this event,
        +   * add `return false;` to the end of the function.
        +   *
        +   * Note: <a href="#/p5/mousePressed">mousePressed()</a>,
        +   * <a href="#/p5/mouseReleased">mouseReleased()</a>,
        +   * and `mouseClicked()` are all related.
        +   * <a href="#/p5/mousePressed">mousePressed()</a> runs as soon as the user
        +   * clicks the mouse. <a href="#/p5/mouseReleased">mouseReleased()</a> runs as
        +   * soon as the user releases the mouse click. `mouseClicked()` runs
        +   * immediately after <a href="#/p5/mouseReleased">mouseReleased()</a>.
        +   *
        +   * @method mouseClicked
        +   * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black square at its center. The inner square changes color when the user presses and releases a mouse button.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * // Toggle the square's color when the user clicks.
        +   * function mouseClicked() {
        +   *   if (value === 0) {
        +   *     value = 255;
        +   *   } else {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Style the circle.
        +   *   fill('orange');
        +   *   stroke('royalblue');
        +   *   strokeWeight(10);
        +   *
        +   *   describe(
        +   *     'An orange circle with a thick, blue border drawn on a gray background. When the user presses and holds the mouse, the border becomes thin and pink. When the user releases the mouse, the border becomes thicker and changes color to blue.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(220);
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, 20);
        +   * }
        +   *
        +   * // Set the stroke color and weight as soon as the user clicks.
        +   * function mousePressed() {
        +   *   stroke('deeppink');
        +   *   strokeWeight(3);
        +   * }
        +   *
        +   * // Set the stroke and fill colors as soon as the user releases
        +   * // the mouse.
        +   * function mouseReleased() {
        +   *   stroke('royalblue');
        +   *
        +   *   // This is never visible because fill() is called
        +   *   // in mouseClicked() which runs immediately after
        +   *   // mouseReleased();
        +   *   fill('limegreen');
        +   * }
        +   *
        +   * // Set the fill color and stroke weight after
        +   * // mousePressed() and mouseReleased() are called.
        +   * function mouseClicked() {
        +   *   fill('orange');
        +   *   strokeWeight(10);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn._onclick = function(e) {
        +    const context = this._isGlobal ? window : this;
        +    if (typeof context.mouseClicked === 'function') {
        +      const executeDefault = context.mouseClicked(e);
        +      if (executeDefault === false) {
        +        e.preventDefault();
        +      }
        +    }
        +  };
        +
        +  /**
        +   * A function that's called once when a mouse button is clicked twice quickly.
        +   *
        +   * Declaring the function `doubleClicked()` sets a code block to run
        +   * automatically when the user presses and releases the mouse button twice
        +   * quickly:
        +   *
        +   * ```js
        +   * function doubleClicked() {
        +   *   // Code to run.
        +   * }
        +   * ```
        +   *
        +   * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        +   * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        +   * value when `doubleClicked()` is called by p5.js:
        +   *
        +   * ```js
        +   * function doubleClicked() {
        +   *   if (mouseX < 50) {
        +   *     // Code to run if the mouse is on the left.
        +   *   }
        +   *
        +   *   if (mouseY > 50) {
        +   *     // Code to run if the mouse is near the bottom.
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * The parameter, `event`, is optional. `doubleClicked()` is always passed a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        +   * object with properties that describe the double-click event:
        +   *
        +   * ```js
        +   * function doubleClicked(event) {
        +   *   // Code to run that uses the event.
        +   *   console.log(event);
        +   * }
        +   * ```
        +   *
        +   * On touchscreen devices, code placed in `doubleClicked()` will run after two
        +   * touches that occur within a short time.
        +   *
        +   * Browsers may have default behaviors attached to various mouse events. For
        +   * example, some browsers highlight text when the user moves the mouse while
        +   * pressing a mouse button. To prevent any default behavior for this event,
        +   * add `return false;` to the end of the function.
        +   *
        +   * @method doubleClicked
        +   * @param  {MouseEvent} [event] optional `MouseEvent` argument.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black square at its center. The inner square changes color when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the square.
        +   *   fill(value);
        +   *
        +   *   // Draw the square.
        +   *   square(25, 25, 50);
        +   * }
        +   *
        +   * // Toggle the square's color when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (value === 0) {
        +   *     value = 255;
        +   *   } else {
        +   *     value = 0;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let value = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a black circle at its center. When the user double-clicks on the circle, it changes color to white.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the circle.
        +   *   fill(value);
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, 80);
        +   * }
        +   *
        +   * // Reassign value to 255 when the user double-clicks on the circle.
        +   * function doubleClicked() {
        +   *   if (dist(50, 50, mouseX, mouseY) < 40) {
        +   *     value = 255;
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  fn._ondblclick = function(e) {
        +    const context = this._isGlobal ? window : this;
        +    if (typeof context.doubleClicked === 'function') {
        +      const executeDefault = context.doubleClicked(e);
        +      if (executeDefault === false) {
        +        e.preventDefault();
        +      }
        +    }
        +  };
        +
        +  /**
        +   * For use with WebGL orbitControl.
        +   * @property {Number} _mouseWheelDeltaY
        +   * @readOnly
        +   * @private
        +   */
        +  fn._mouseWheelDeltaY = 0;
        +
        +  /**
        +   * For use with WebGL orbitControl.
        +   * @property {Number} _pmouseWheelDeltaY
        +   * @readOnly
        +   * @private
        +   */
        +  fn._pmouseWheelDeltaY = 0;
        +
        +  /**
        +   * A function that's called once when the mouse wheel moves.
        +   *
        +   * Declaring the function `mouseWheel()` sets a code block to run
        +   * automatically when the user scrolls with the mouse wheel:
        +   *
        +   * ```js
        +   * function mouseWheel() {
        +   *   // Code to run.
        +   * }
        +   * ```
        +   *
        +   * The mouse system variables, such as <a href="#/p5/mouseX">mouseX</a> and
        +   * <a href="#/p5/mouseY">mouseY</a>, will be updated with their most recent
        +   * value when `mouseWheel()` is called by p5.js:
        +   *
        +   * ```js
        +   * function mouseWheel() {
        +   *   if (mouseX < 50) {
        +   *     // Code to run if the mouse is on the left.
        +   *   }
        +   *
        +   *   if (mouseY > 50) {
        +   *     // Code to run if the mouse is near the bottom.
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * The parameter, `event`, is optional. `mouseWheel()` is always passed a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" target="_blank">MouseEvent</a>
        +   * object with properties that describe the mouse scroll event:
        +   *
        +   * ```js
        +   * function mouseWheel(event) {
        +   *   // Code to run that uses the event.
        +   *   console.log(event);
        +   * }
        +   * ```
        +   *
        +   * The `event` object has many properties including `delta`, a `Number`
        +   * containing the distance that the user scrolled. For example, `event.delta`
        +   * might have the value 5 when the user scrolls up. `event.delta` is positive
        +   * if the user scrolls up and negative if they scroll down. The signs are
        +   * opposite on macOS with "natural" scrolling enabled.
        +   *
        +   * Browsers may have default behaviors attached to various mouse events. For
        +   * example, some browsers highlight text when the user moves the mouse while
        +   * pressing a mouse button. To prevent any default behavior for this event,
        +   * add `return false;` to the end of the function.
        +   *
        +   * Note: On Safari, `mouseWheel()` may only work as expected if
        +   * `return false;` is added at the end of the function.
        +   *
        +   * @method mouseWheel
        +   * @param  {WheelEvent} [event] optional `WheelEvent` argument.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let circleSize = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square. A white circle at its center grows up when the user scrolls the mouse wheel.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the circle
        +   *   circle(circleSize, 50, 50);
        +   * }
        +   *
        +   * // Increment circleSize when the user scrolls the mouse wheel.
        +   * function mouseWheel() {
        +   *   circleSize += 1;
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let direction = '';
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square. An arrow at its center points up when the user scrolls up. The arrow points down when the user scrolls down.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Draw an arrow that points where
        +   *   // the mouse last scrolled.
        +   *   text(direction, 50, 50);
        +   * }
        +   *
        +   * // Change direction when the user scrolls the mouse wheel.
        +   * function mouseWheel(event) {
        +   *   if (event.delta > 0) {
        +   *     direction = '▲';
        +   *   } else {
        +   *     direction = '▼';
        +   *   }
        +   *   // Uncomment to prevent any default behavior.
        +   *   // return false;
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn._onwheel = function(e) {
        +    const context = this._isGlobal ? window : this;
        +    this._mouseWheelDeltaY = e.deltaY;
        +    if (typeof context.mouseWheel === 'function') {
        +      e.delta = e.deltaY;
        +      const executeDefault = context.mouseWheel(e);
        +      if (executeDefault === false) {
        +        e.preventDefault();
        +      }
        +    }
        +  };
        +
        +  /**
        +   * Locks the mouse pointer to its current position and makes it invisible.
        +   *
        +   * `requestPointerLock()` allows the mouse to move forever without leaving the
        +   * screen. Calling `requestPointerLock()` locks the values of
        +   * <a href="#/p5/mouseX">mouseX</a>, <a href="#/p5/mouseY">mouseY</a>,
        +   * <a href="#/p5/pmouseX">pmouseX</a>, and <a href="#/p5/pmouseY">pmouseY</a>.
        +   * <a href="#/p5/movedX">movedX</a> and <a href="#/p5/movedY">movedY</a>
        +   * continue updating and can be used to get the distance the mouse moved since
        +   * the last frame was drawn. Calling
        +   * <a href="#/p5/exitPointerLock">exitPointerLock()</a> resumes updating the
        +   * mouse system variables.
        +   *
        +   * Note: Most browsers require an input, such as a click, before calling
        +   * `requestPointerLock()`. It’s recommended to call `requestPointerLock()` in
        +   * an event function such as <a href="#/p5/doubleClicked">doubleClicked()</a>.
        +   *
        +   * @method requestPointerLock
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let score = 0;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with the text "Score: X" at its center. The score increases when the user moves the mouse upward. It decreases when the user moves the mouse downward.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Update the score.
        +   *   score -= movedY;
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the score.
        +   *   text(`Score: ${score}`, 50, 50);
        +   * }
        +   *
        +   * // Lock the pointer when the user double-clicks.
        +   * function doubleClicked() {
        +   *   requestPointerLock();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.requestPointerLock = function() {
        +    // pointer lock object forking for cross browser
        +    const canvas = this._curElement.elt;
        +    canvas.requestPointerLock =
        +      canvas.requestPointerLock || canvas.mozRequestPointerLock;
        +    if (!canvas.requestPointerLock) {
        +      console.log('requestPointerLock is not implemented in this browser');
        +      return false;
        +    }
        +    canvas.requestPointerLock();
        +    return true;
        +  };
        +
        +  /**
        +   * Exits a pointer lock started with
        +   * <a href="#/p5/requestPointerLock">requestPointerLock</a>.
        +   *
        +   * Calling `requestPointerLock()` locks the values of
        +   * <a href="#/p5/mouseX">mouseX</a>, <a href="#/p5/mouseY">mouseY</a>,
        +   * <a href="#/p5/pmouseX">pmouseX</a>, and <a href="#/p5/pmouseY">pmouseY</a>.
        +   * Calling `exitPointerLock()` resumes updating the mouse system variables.
        +   *
        +   * Note: Most browsers require an input, such as a click, before calling
        +   * `requestPointerLock()`. It’s recommended to call `requestPointerLock()` in
        +   * an event function such as <a href="#/p5/doubleClicked">doubleClicked()</a>.
        +   *
        +   * @method exitPointerLock
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let isLocked = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a word at its center. The word changes between "Unlocked" and "Locked" when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Tell the user whether the pointer is locked.
        +   *   if (isLocked === true) {
        +   *     text('Locked', 50, 50);
        +   *   } else {
        +   *     text('Unlocked', 50, 50);
        +   *   }
        +   * }
        +   *
        +   * // Toggle the pointer lock when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isLocked === true) {
        +   *     exitPointerLock();
        +   *     isLocked = false;
        +   *   } else {
        +   *     requestPointerLock();
        +   *     isLocked = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.exitPointerLock = function() {
        +    document.exitPointerLock();
        +  };
        +}
        +
        +export default pointer;
        +
        +if(typeof p5 !== 'undefined'){
        +  pointer(p5, p5.prototype);
        +}
        diff --git a/src/events/touch.js b/src/events/touch.js
        deleted file mode 100644
        index ff2587f1bc..0000000000
        --- a/src/events/touch.js
        +++ /dev/null
        @@ -1,637 +0,0 @@
        -/**
        - * @module Events
        - * @submodule Touch
        - * @for p5
        - * @requires core
        - */
        -
        -import p5 from '../core/main';
        -
        -/**
        - * An `Array` of all the current touch points on a touchscreen device.
        - *
        - * The `touches` array is empty by default. When the user touches their
        - * screen, a new touch point is tracked and added to the array. Touch points
        - * are `Objects` with the following properties:
        - *
        - * ```js
        - * // Iterate over the touches array.
        - * for (let touch of touches) {
        - *   // x-coordinate relative to the top-left
        - *   // corner of the canvas.
        - *   console.log(touch.x);
        - *
        - *   // y-coordinate relative to the top-left
        - *   // corner of the canvas.
        - *   console.log(touch.y);
        - *
        - *   // x-coordinate relative to the top-left
        - *   // corner of the browser.
        - *   console.log(touch.winX);
        - *
        - *   // y-coordinate relative to the top-left
        - *   // corner of the browser.
        - *   console.log(touch.winY);
        - *
        - *   // ID number
        - *   console.log(touch.id);
        - * }
        - * ```
        - *
        - * @property {Object[]} touches
        - * @readOnly
        - *
        - * @example
        - * <div>
        - * <code>
        - * // On a touchscreen device, touch the canvas using one or more fingers
        - * // at the same time.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square. White circles appear where the user touches the square.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a circle at each touch point.
        - *   for (let touch of touches) {
        - *     circle(touch.x, touch.y, 40);
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // On a touchscreen device, touch the canvas using one or more fingers
        - * // at the same time.
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square. Labels appear where the user touches the square, displaying the coordinates.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a label above each touch point.
        - *   for (let touch of touches) {
        - *     text(`${touch.x}, ${touch.y}`, touch.x, touch.y - 40);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.touches = [];
        -
        -p5.prototype._updateTouchCoords = function(e) {
        -  if (this._curElement !== null) {
        -    const touches = [];
        -    for (let i = 0; i < e.touches.length; i++) {
        -      touches[i] = getTouchInfo(
        -        this._curElement.elt,
        -        this.width,
        -        this.height,
        -        e,
        -        i
        -      );
        -    }
        -    this._setProperty('touches', touches);
        -  }
        -};
        -
        -function getTouchInfo(canvas, w, h, e, i = 0) {
        -  const rect = canvas.getBoundingClientRect();
        -  const sx = canvas.scrollWidth / w || 1;
        -  const sy = canvas.scrollHeight / h || 1;
        -  const touch = e.touches[i] || e.changedTouches[i];
        -  return {
        -    x: (touch.clientX - rect.left) / sx,
        -    y: (touch.clientY - rect.top) / sy,
        -    winX: touch.clientX,
        -    winY: touch.clientY,
        -    id: touch.identifier
        -  };
        -}
        -
        -/**
        - * A function that's called once each time the user touches the screen.
        - *
        - * Declaring a function called `touchStarted()` sets a code block to run
        - * automatically each time the user begins touching a touchscreen device:
        - *
        - * ```js
        - * function touchStarted() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The <a href="#/p5/touches">touches</a> array will be updated with the most
        - * recent touch points when `touchStarted()` is called by p5.js:
        - *
        - * ```js
        - * function touchStarted() {
        - *   // Paint over the background.
        - *   background(200);
        - *
        - *   // Mark each touch point once with a circle.
        - *   for (let touch of touches) {
        - *     circle(touch.x, touch.y, 40);
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, event, is optional. `touchStarted()` will be passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent" target="_blank">TouchEvent</a>
        - * object with properties that describe the touch event:
        - *
        - * ```js
        - * function touchStarted(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * On touchscreen devices, <a href="#/p5/mousePressed">mousePressed()</a> will
        - * run when a user’s touch starts if `touchStarted()` isn’t declared. If
        - * `touchStarted()` is declared, then `touchStarted()` will run when a user’s
        - * touch starts and <a href="#/p5/mousePressed">mousePressed()</a> won’t.
        - *
        - * Note: `touchStarted()`, <a href="#/p5/touchEnded">touchEnded()</a>, and
        - * <a href="#/p5/touchMoved">touchMoved()</a> are all related.
        - * `touchStarted()` runs as soon as the user touches a touchscreen device.
        - * <a href="#/p5/touchEnded">touchEnded()</a> runs as soon as the user ends a
        - * touch. <a href="#/p5/touchMoved">touchMoved()</a> runs repeatedly as the
        - * user moves any touch points.
        - *
        - * @method touchStarted
        - * @param  {TouchEvent} [event] optional `TouchEvent` argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // On a touchscreen device, touch the canvas using one or more fingers
        - * // at the same time.
        - *
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square switches color between black and white each time the user touches the screen.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Toggle colors with each touch.
        - * function touchStarted() {
        - *   if (value === 0) {
        - *     value = 255;
        - *   } else {
        - *     value = 0;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // On a touchscreen device, touch the canvas using one or more fingers
        - * // at the same time.
        - *
        - * let bgColor = 50;
        - * let fillColor = 255;
        - * let borderWidth = 0.5;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with the number 0 at the top-center. The number tracks the number of places the user is touching the screen. Circles appear at each touch point and change style in response to events.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(bgColor);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   fill(0);
        - *   noStroke();
        - *
        - *   // Display the number of touch points.
        - *   text(touches.length, 50, 20);
        - *
        - *   // Style the touch points.
        - *   fill(fillColor);
        - *   stroke(0);
        - *   strokeWeight(borderWidth);
        - *
        - *   // Display the touch points as circles.
        - *   for (let touch of touches) {
        - *     circle(touch.x, touch.y, 40);
        - *   }
        - * }
        - *
        - * // Set the background color to a random grayscale value.
        - * function touchStarted() {
        - *   bgColor = random(80, 255);
        - * }
        - *
        - * // Set the fill color to a random grayscale value.
        - * function touchEnded() {
        - *   fillColor = random(0, 255);
        - * }
        - *
        - * // Set the stroke weight.
        - * function touchMoved() {
        - *   // Increment the border width.
        - *   borderWidth += 0.1;
        - *
        - *   // Reset the border width once it's too thick.
        - *   if (borderWidth > 20) {
        - *     borderWidth = 0.5;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype._ontouchstart = function(e) {
        -  const context = this._isGlobal ? window : this;
        -  let executeDefault;
        -  this._setProperty('mouseIsPressed', true);
        -  this._updateTouchCoords(e);
        -  this._updateNextMouseCoords(e);
        -  this._updateMouseCoords(); // reset pmouseXY at the start of each touch event
        -
        -  if (typeof context.touchStarted === 'function') {
        -    executeDefault = context.touchStarted(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        -    }
        -    this.touchstart = true;
        -  }
        -};
        -
        -/**
        - * A function that's called when the user touches the screen and moves.
        - *
        - * Declaring the function `touchMoved()` sets a code block to run
        - * automatically when the user touches a touchscreen device and moves:
        - *
        - * ```js
        - * function touchMoved() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The <a href="#/p5/touches">touches</a> array will be updated with the most
        - * recent touch points when `touchMoved()` is called by p5.js:
        - *
        - * ```js
        - * function touchMoved() {
        - *   // Paint over the background.
        - *   background(200);
        - *
        - *   // Mark each touch point while the user moves.
        - *   for (let touch of touches) {
        - *     circle(touch.x, touch.y, 40);
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, event, is optional. `touchMoved()` will be passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent" target="_blank">TouchEvent</a>
        - * object with properties that describe the touch event:
        - *
        - * ```js
        - * function touchMoved(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * On touchscreen devices, <a href="#/p5/mouseDragged">mouseDragged()</a> will
        - * run when the user’s touch points move if `touchMoved()` isn’t declared. If
        - * `touchMoved()` is declared, then `touchMoved()` will run when a user’s
        - * touch points move and <a href="#/p5/mouseDragged">mouseDragged()</a> won’t.
        - *
        - * Note: <a href="#/p5/touchStarted">touchStarted()</a>,
        - * <a href="#/p5/touchEnded">touchEnded()</a>, and
        - * `touchMoved()` are all related.
        - * <a href="#/p5/touchStarted">touchStarted()</a> runs as soon as the user
        - * touches a touchscreen device. <a href="#/p5/touchEnded">touchEnded()</a>
        - * runs as soon as the user ends a touch. `touchMoved()` runs repeatedly as
        - * the user moves any touch points.
        - *
        - * @method touchMoved
        - * @param  {TouchEvent} [event] optional TouchEvent argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // On a touchscreen device, touch the canvas using one or more fingers
        - * // at the same time.
        - *
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square becomes lighter when the user touches the screen and moves.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * function touchMoved() {
        - *   // Update the grayscale value.
        - *   value += 5;
        - *
        - *   // Reset the grayscale value.
        - *   if (value > 255) {
        - *     value = 0;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // On a touchscreen device, touch the canvas using one or more fingers
        - * // at the same time.
        - *
        - * let bgColor = 50;
        - * let fillColor = 255;
        - * let borderWidth = 0.5;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with the number 0 at the top-center. The number tracks the number of places the user is touching the screen. Circles appear at each touch point and change style in response to events.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(bgColor);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   fill(0);
        - *   noStroke();
        - *
        - *   // Display the number of touch points.
        - *   text(touches.length, 50, 20);
        - *
        - *   // Style the touch points.
        - *   fill(fillColor);
        - *   stroke(0);
        - *   strokeWeight(borderWidth);
        - *
        - *   // Display the touch points as circles.
        - *   for (let touch of touches) {
        - *     circle(touch.x, touch.y, 40);
        - *   }
        - * }
        - *
        - * // Set the background color to a random grayscale value.
        - * function touchStarted() {
        - *   bgColor = random(80, 255);
        - * }
        - *
        - * // Set the fill color to a random grayscale value.
        - * function touchEnded() {
        - *   fillColor = random(0, 255);
        - * }
        - *
        - * // Set the stroke weight.
        - * function touchMoved() {
        - *   // Increment the border width.
        - *   borderWidth += 0.1;
        - *
        - *   // Reset the border width once it's too thick.
        - *   if (borderWidth > 20) {
        - *     borderWidth = 0.5;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype._ontouchmove = function(e) {
        -  const context = this._isGlobal ? window : this;
        -  let executeDefault;
        -  this._updateTouchCoords(e);
        -  this._updateNextMouseCoords(e);
        -  if (typeof context.touchMoved === 'function') {
        -    executeDefault = context.touchMoved(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        -    }
        -  } else if (typeof context.mouseDragged === 'function') {
        -    executeDefault = context.mouseDragged(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        -    }
        -  }
        -};
        -
        -/**
        - * A function that's called once each time a screen touch ends.
        - *
        - * Declaring the function `touchEnded()` sets a code block to run
        - * automatically when the user stops touching a touchscreen device:
        - *
        - * ```js
        - * function touchEnded() {
        - *   // Code to run.
        - * }
        - * ```
        - *
        - * The <a href="#/p5/touches">touches</a> array will be updated with the most
        - * recent touch points when `touchEnded()` is called by p5.js:
        - *
        - * ```js
        - * function touchEnded() {
        - *   // Paint over the background.
        - *   background(200);
        - *
        - *   // Mark each remaining touch point when the user stops
        - *   // a touch.
        - *   for (let touch of touches) {
        - *     circle(touch.x, touch.y, 40);
        - *   }
        - * }
        - * ```
        - *
        - * The parameter, event, is optional. `touchEnded()` will be passed a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent" target="_blank">TouchEvent</a>
        - * object with properties that describe the touch event:
        - *
        - * ```js
        - * function touchEnded(event) {
        - *   // Code to run that uses the event.
        - *   console.log(event);
        - * }
        - * ```
        - *
        - * On touchscreen devices, <a href="#/p5/mouseReleased">mouseReleased()</a> will
        - * run when the user’s touch ends if `touchEnded()` isn’t declared. If
        - * `touchEnded()` is declared, then `touchEnded()` will run when a user’s
        - * touch ends and <a href="#/p5/mouseReleased">mouseReleased()</a> won’t.
        - *
        - * Note: <a href="#/p5/touchStarted">touchStarted()</a>,
        - * `touchEnded()`, and <a href="#/p5/touchMoved">touchMoved()</a> are all
        - * related. <a href="#/p5/touchStarted">touchStarted()</a> runs as soon as the
        - * user touches a touchscreen device. `touchEnded()` runs as soon as the user
        - * ends a touch. <a href="#/p5/touchMoved">touchMoved()</a> runs repeatedly as
        - * the user moves any touch points.
        - *
        - * @method touchEnded
        - * @param  {TouchEvent} [event] optional `TouchEvent` argument.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // On a touchscreen device, touch the canvas using one or more fingers
        - * // at the same time.
        - *
        - * let value = 0;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with a black square at its center. The inner square switches color between black and white each time the user stops touching the screen.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the square.
        - *   fill(value);
        - *
        - *   // Draw the square.
        - *   square(25, 25, 50);
        - * }
        - *
        - * // Toggle colors when a touch ends.
        - * function touchEnded() {
        - *   if (value === 0) {
        - *     value = 255;
        - *   } else {
        - *     value = 0;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // On a touchscreen device, touch the canvas using one or more fingers
        - * // at the same time.
        - *
        - * let bgColor = 50;
        - * let fillColor = 255;
        - * let borderWidth = 0.5;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe(
        - *     'A gray square with the number 0 at the top-center. The number tracks the number of places the user is touching the screen. Circles appear at each touch point and change style in response to events.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(bgColor);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   fill(0);
        - *   noStroke();
        - *
        - *   // Display the number of touch points.
        - *   text(touches.length, 50, 20);
        - *
        - *   // Style the touch points.
        - *   fill(fillColor);
        - *   stroke(0);
        - *   strokeWeight(borderWidth);
        - *
        - *   // Display the touch points as circles.
        - *   for (let touch of touches) {
        - *     circle(touch.x, touch.y, 40);
        - *   }
        - * }
        - *
        - * // Set the background color to a random grayscale value.
        - * function touchStarted() {
        - *   bgColor = random(80, 255);
        - * }
        - *
        - * // Set the fill color to a random grayscale value.
        - * function touchEnded() {
        - *   fillColor = random(0, 255);
        - * }
        - *
        - * // Set the stroke weight.
        - * function touchMoved() {
        - *   // Increment the border width.
        - *   borderWidth += 0.1;
        - *
        - *   // Reset the border width once it's too thick.
        - *   if (borderWidth > 20) {
        - *     borderWidth = 0.5;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype._ontouchend = function(e) {
        -  this._setProperty('mouseIsPressed', false);
        -  this._updateTouchCoords(e);
        -  this._updateNextMouseCoords(e);
        -  const context = this._isGlobal ? window : this;
        -  let executeDefault;
        -  if (typeof context.touchEnded === 'function') {
        -    executeDefault = context.touchEnded(e);
        -    if (executeDefault === false) {
        -      e.preventDefault();
        -    }
        -    this.touchend = true;
        -  }
        -};
        -
        -export default p5;
        diff --git a/src/image/const.js b/src/image/const.js
        new file mode 100644
        index 0000000000..535ce66c31
        --- /dev/null
        +++ b/src/image/const.js
        @@ -0,0 +1,6 @@
        +import * as constants from '../core/constants';
        +export const filterParamDefaults = {
        +  [constants.BLUR]: 3,
        +  [constants.POSTERIZE]: 4,
        +  [constants.THRESHOLD]: 0.5,
        +};
        diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js
        new file mode 100644
        index 0000000000..6a3f194466
        --- /dev/null
        +++ b/src/image/filterRenderer2D.js
        @@ -0,0 +1,258 @@
        +import { Shader } from "../webgl/p5.Shader";
        +import { Texture } from "../webgl/p5.Texture";
        +import { Image } from "./p5.Image";
        +import * as constants from '../core/constants';
        +
        +import filterGrayFrag from '../webgl/shaders/filters/gray.frag';
        +import filterErodeFrag from '../webgl/shaders/filters/erode.frag';
        +import filterDilateFrag from '../webgl/shaders/filters/dilate.frag';
        +import filterBlurFrag from '../webgl/shaders/filters/blur.frag';
        +import filterPosterizeFrag from '../webgl/shaders/filters/posterize.frag';
        +import filterOpaqueFrag from '../webgl/shaders/filters/opaque.frag';
        +import filterInvertFrag from '../webgl/shaders/filters/invert.frag';
        +import filterThresholdFrag from '../webgl/shaders/filters/threshold.frag';
        +import filterShaderVert from '../webgl/shaders/filters/default.vert';
        +import { filterParamDefaults } from "./const";
        +
        +class FilterRenderer2D {
        +  /**
        +   * Creates a new FilterRenderer2D instance.
        +   * @param {p5} pInst - The p5.js instance.
        +   */
        +  constructor(pInst) {
        +    this.pInst = pInst;
        +    // Create a canvas for applying WebGL-based filters
        +    this.canvas = document.createElement('canvas');
        +    this.canvas.width = pInst.width;
        +    this.canvas.height = pInst.height;
        +
        +    // Initialize the WebGL context
        +    this.gl = this.canvas.getContext('webgl');
        +    if (!this.gl) {
        +      console.error("WebGL not supported, cannot apply filter.");
        +      return;
        +    }
        +    // Minimal renderer object required by p5.Shader and p5.Texture
        +    this._renderer = {
        +      GL: this.gl,
        +      registerEnabled: new Set(),
        +      _curShader: null,
        +      _emptyTexture: null,
        +      webglVersion: 'WEBGL',
        +      states: {
        +        textureWrapX: this.gl.CLAMP_TO_EDGE,
        +        textureWrapY: this.gl.CLAMP_TO_EDGE,
        +      },
        +      _arraysEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b),
        +      _getEmptyTexture: () => {
        +        if (!this._emptyTexture) {
        +          const im = new Image(1, 1);
        +          im.set(0, 0, 255);
        +          this._emptyTexture = new Texture(this._renderer, im);
        +        }
        +        return this._emptyTexture;
        +      },
        +    };
        +
        +    // Store the fragment shader sources
        +    this.filterShaderSources = {
        +      [constants.BLUR]: filterBlurFrag,
        +      [constants.INVERT]: filterInvertFrag,
        +      [constants.THRESHOLD]: filterThresholdFrag,
        +      [constants.ERODE]: filterErodeFrag,
        +      [constants.GRAY]: filterGrayFrag,
        +      [constants.DILATE]: filterDilateFrag,
        +      [constants.POSTERIZE]: filterPosterizeFrag,
        +      [constants.OPAQUE]: filterOpaqueFrag,
        +    };
        +
        +    // Store initialized shaders for each operation
        +    this.filterShaders = {};
        +
        +    // These will be set by setOperation
        +    this.operation = null;
        +    this.filterParameter = 1;
        +    this.customShader = null;
        +    this._shader = null;
        +
        +    // Create buffers once
        +    this.vertexBuffer = this.gl.createBuffer();
        +    this.texcoordBuffer = this.gl.createBuffer();
        +
        +    // Set up the vertices and texture coordinates for a full-screen quad
        +    this.vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
        +    this.texcoords = new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]);
        +
        +    // Upload vertex data once
        +    this._bindBufferData(this.vertexBuffer, this.gl.ARRAY_BUFFER, this.vertices);
        +
        +    // Upload texcoord data once
        +    this._bindBufferData(this.texcoordBuffer, this.gl.ARRAY_BUFFER, this.texcoords);
        +  }
        +
        +  /**
        +   * Set the current filter operation and parameter. If a customShader is provided,
        +   * that overrides the operation-based shader.
        +   * @param {string} operation - The filter operation type (e.g., constants.BLUR).
        +   * @param {number} filterParameter - The strength of the filter.
        +   * @param {p5.Shader} customShader - Optional custom shader.
        +   */
        +  setOperation(operation, filterParameter, customShader = null) {
        +    this.operation = operation;
        +    this.filterParameter = filterParameter;
        +
        +    let useDefaultParam = operation in filterParamDefaults && filterParameter === undefined;
        +    if (useDefaultParam) {
        +      this.filterParameter = filterParamDefaults[operation];
        +    }
        +
        +    this.customShader = customShader;
        +    this._initializeShader();
        +  }
        +
        +  /**
        +   * Initializes or retrieves the shader program for the current operation.
        +   * If a customShader is provided, that is used.
        +   * Otherwise, returns a cached shader if available, or creates a new one, caches it, and sets it as current.
        +   */
        +  _initializeShader() {
        +    if (this.customShader) {
        +      this._shader = this.customShader;
        +      return;
        +    }
        +
        +    if (!this.operation) {
        +      console.error("No operation set for FilterRenderer2D, cannot initialize shader.");
        +      return;
        +    }
        +
        +    // If we already have a compiled shader for this operation, reuse it
        +    if (this.filterShaders[this.operation]) {
        +      this._shader = this.filterShaders[this.operation];
        +      return;
        +    }
        +
        +    const fragShaderSrc = this.filterShaderSources[this.operation];
        +    if (!fragShaderSrc) {
        +      console.error("No shader available for this operation:", this.operation);
        +      return;
        +    }
        +
        +    // Create and store the new shader
        +    const newShader = new Shader(this._renderer, filterShaderVert, fragShaderSrc);
        +    this.filterShaders[this.operation] = newShader;
        +    this._shader = newShader;
        +  }
        +
        +  /**
        +   * Binds a buffer to the drawing context
        +   * when passed more than two arguments it also updates or initializes
        +   * the data associated with the buffer
        +   */
        +  _bindBufferData(buffer, target, values) {
        +    const gl = this.gl;
        +    gl.bindBuffer(target, buffer);
        +    gl.bufferData(target, values, gl.STATIC_DRAW);
        +  }
        +
        +  get canvasTexture() {
        +    if (!this._canvasTexture) {
        +      this._canvasTexture = new Texture(this._renderer, this.pInst.wrappedElt);
        +    }
        +    return this._canvasTexture;
        +  }
        +
        +  /**
        +   * Prepares and runs the full-screen quad draw call.
        +   */
        +  _renderPass() {
        +    const gl = this.gl;
        +    this._shader.bindShader();
        +    const pixelDensity = this.pInst.pixelDensity ? this.pInst.pixelDensity() : 1;
        +
        +    const texelSize = [
        +      1 / (this.pInst.width * pixelDensity),
        +      1 / (this.pInst.height * pixelDensity)
        +    ];
        +
        +    const canvasTexture = this.canvasTexture;
        +
        +    // Set uniforms for the shader
        +    this._shader.setUniform('tex0', canvasTexture);
        +    this._shader.setUniform('texelSize', texelSize);
        +    this._shader.setUniform('canvasSize', [this.pInst.width, this.pInst.height]);
        +    this._shader.setUniform('radius', Math.max(1, this.filterParameter));
        +    this._shader.setUniform('filterParameter', this.filterParameter);
        +
        +    this.pInst.states.rectMode = constants.CORNER;
        +    this.pInst.states.imageMode = constants.CORNER;
        +    this.pInst.blendMode(constants.BLEND);
        +    this.pInst.resetMatrix();
        +
        +
        +    const identityMatrix = [1, 0, 0, 0,
        +                            0, 1, 0, 0,
        +                            0, 0, 1, 0,
        +                            0, 0, 0, 1];
        +    this._shader.setUniform('uModelViewMatrix', identityMatrix);
        +    this._shader.setUniform('uProjectionMatrix', identityMatrix);
        +
        +    // Bind and enable vertex attributes
        +    gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
        +    this._shader.enableAttrib(this._shader.attributes.aPosition, 2);
        +
        +    gl.bindBuffer(gl.ARRAY_BUFFER, this.texcoordBuffer);
        +    this._shader.enableAttrib(this._shader.attributes.aTexCoord, 2);
        +
        +    this._shader.bindTextures();
        +    this._shader.disableRemainingAttributes();
        +
        +    // Draw the quad
        +    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
        +    // Unbind the shader
        +    this._shader.unbindShader();
        +  }
        +
        +  /**
        +   * Applies the current filter operation. If the filter requires multiple passes (e.g. blur),
        +   * it handles those internally. Make sure setOperation() has been called before applyFilter().
        +   */
        +  applyFilter() {
        +    if (!this._shader) {
        +      console.error("Cannot apply filter: shader not initialized.");
        +      return;
        +    }
        +    this.pInst.push();
        +    this.pInst.resetMatrix();
        +    // For blur, we typically do two passes: one horizontal, one vertical.
        +    if (this.operation === constants.BLUR && !this.customShader) {
        +      // Horizontal pass
        +      this._shader.setUniform('direction', [1, 0]);
        +      this._renderPass();
        +
        +      // Draw the result onto itself
        +      this.pInst.clear();
        +      this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height);
        +
        +      // Vertical pass
        +      this._shader.setUniform('direction', [0, 1]);
        +      this._renderPass();
        +
        +      this.pInst.clear();
        +      this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height);
        +    } else {
        +      // Single-pass filters
        +
        +      this._renderPass();
        +      this.pInst.clear();
        +      // con
        +      this.pInst.blendMode(constants.BLEND);
        +
        +
        +      this.pInst.drawingContext.drawImage(this.canvas, 0, 0, this.pInst.width, this.pInst.height);
        +    }
        +    this.pInst.pop();
        +  }
        +}
        +
        +export default FilterRenderer2D;
        diff --git a/src/image/filters.js b/src/image/filters.js
        index 6c746df32e..0531fcc124 100644
        --- a/src/image/filters.js
        +++ b/src/image/filters.js
        @@ -1,4 +1,4 @@
        -/**
        +/*
          * This module defines the filters for use with image buffers.
          *
          * This module is basically a collection of functions stored in an object
        @@ -11,6 +11,8 @@
          * A number of functions are borrowed/adapted from
          * http://www.html5rocks.com/en/tutorials/canvas/imagefilters/
          * or the java processing implementation.
        + *
        + * @private
          */
         
         const Filters = {
        diff --git a/src/image/image.js b/src/image/image.js
        index 2ef00ab2b2..6a3bbbc368 100644
        --- a/src/image/image.js
        +++ b/src/image/image.js
        @@ -9,718 +9,724 @@
          * This module defines the p5 methods for the <a href="#/p5.Image">p5.Image</a> class
          * for drawing images to the main display canvas.
          */
        -import p5 from '../core/main';
        -import omggif from 'omggif';
        -
        -/**
        - * Creates a new <a href="#/p5.Image">p5.Image</a> object.
        - *
        - * `createImage()` uses the `width` and `height` parameters to set the new
        - * <a href="#/p5.Image">p5.Image</a> object's dimensions in pixels. The new
        - * <a href="#/p5.Image">p5.Image</a> can be modified by updating its
        - * <a href="#/p5.Image/pixels">pixels</a> array or by calling its
        - * <a href="#/p5.Image/get">get()</a> and
        - * <a href="#/p5.Image/set">set()</a> methods. The
        - * <a href="#/p5.Image/loadPixels">loadPixels()</a> method must be called
        - * before reading or modifying pixel values. The
        - * <a href="#/p5.Image/updatePixels">updatePixels()</a> method must be called
        - * for updates to take effect.
        - *
        - * Note: The new <a href="#/p5.Image">p5.Image</a> object is transparent by
        - * default.
        - *
        - * @method createImage
        - * @param  {Integer} width  width in pixels.
        - * @param  {Integer} height height in pixels.
        - * @return {p5.Image}       new <a href="#/p5.Image">p5.Image</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Image object.
        - *   let img = createImage(66, 66);
        - *
        - *   // Load the image's pixels into memory.
        - *   img.loadPixels();
        - *
        - *   // Set all the image's pixels to black.
        - *   for (let x = 0; x < img.width; x += 1) {
        - *     for (let y = 0; y < img.height; y += 1) {
        - *       img.set(x, y, 0);
        - *     }
        - *   }
        - *
        - *   // Update the image's pixel values.
        - *   img.updatePixels();
        - *
        - *   // Draw the image.
        - *   image(img, 17, 17);
        - *
        - *   describe('A black square drawn in the middle of a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Image object.
        - *   let img = createImage(66, 66);
        - *
        - *   // Load the image's pixels into memory.
        - *   img.loadPixels();
        - *
        - *   // Create a color gradient.
        - *   for (let x = 0; x < img.width; x += 1) {
        - *     for (let y = 0; y < img.height; y += 1) {
        - *       // Calculate the transparency.
        - *       let a = map(x, 0, img.width, 0, 255);
        - *
        - *       // Create a p5.Color object.
        - *       let c = color(0, a);
        - *
        - *       // Set the pixel's color.
        - *       img.set(x, y, c);
        - *     }
        - *   }
        - *
        - *   // Update the image's pixels.
        - *   img.updatePixels();
        - *
        - *   // Display the image.
        - *   image(img, 17, 17);
        - *
        - *   describe('A square with a horizontal color gradient that transitions from gray to black.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Image object.
        - *   let img = createImage(66, 66);
        - *
        - *   // Load the pixels into memory.
        - *   img.loadPixels();
        - *   // Get the current pixel density.
        - *   let d = pixelDensity();
        - *
        - *   // Calculate the pixel that is halfway through the image's pixel array.
        - *   let halfImage = 4 * (d * img.width) * (d * img.height / 2);
        - *
        - *   // Set half of the image's pixels to black.
        - *   for (let i = 0; i < halfImage; i += 4) {
        - *     // Red.
        - *     img.pixels[i] = 0;
        - *     // Green.
        - *     img.pixels[i + 1] = 0;
        - *     // Blue.
        - *     img.pixels[i + 2] = 0;
        - *     // Alpha.
        - *     img.pixels[i + 3] = 255;
        - *   }
        - *
        - *   // Update the image's pixels.
        - *   img.updatePixels();
        - *
        - *   // Display the image.
        - *   image(img, 17, 17);
        - *
        - *   describe('A black square drawn in the middle of a gray square.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createImage = function(width, height) {
        -  p5._validateParameters('createImage', arguments);
        -  return new p5.Image(width, height);
        -};
        +import * as omggif from 'omggif';
        +import { Element } from '../dom/p5.Element';
        +import { Framebuffer } from '../webgl/p5.Framebuffer';
        +
        +function image(p5, fn){
        +  /**
        +   * Creates a new <a href="#/p5.Image">p5.Image</a> object.
        +   *
        +   * `createImage()` uses the `width` and `height` parameters to set the new
        +   * <a href="#/p5.Image">p5.Image</a> object's dimensions in pixels. The new
        +   * <a href="#/p5.Image">p5.Image</a> can be modified by updating its
        +   * <a href="#/p5.Image/pixels">pixels</a> array or by calling its
        +   * <a href="#/p5.Image/get">get()</a> and
        +   * <a href="#/p5.Image/set">set()</a> methods. The
        +   * <a href="#/p5.Image/loadPixels">loadPixels()</a> method must be called
        +   * before reading or modifying pixel values. The
        +   * <a href="#/p5.Image/updatePixels">updatePixels()</a> method must be called
        +   * for updates to take effect.
        +   *
        +   * Note: The new <a href="#/p5.Image">p5.Image</a> object is transparent by
        +   * default.
        +   *
        +   * @method createImage
        +   * @param  {Integer} width  width in pixels.
        +   * @param  {Integer} height height in pixels.
        +   * @return {p5.Image}       new <a href="#/p5.Image">p5.Image</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Image object.
        +   *   let img = createImage(66, 66);
        +   *
        +   *   // Load the image's pixels into memory.
        +   *   img.loadPixels();
        +   *
        +   *   // Set all the image's pixels to black.
        +   *   for (let x = 0; x < img.width; x += 1) {
        +   *     for (let y = 0; y < img.height; y += 1) {
        +   *       img.set(x, y, 0);
        +   *     }
        +   *   }
        +   *
        +   *   // Update the image's pixel values.
        +   *   img.updatePixels();
        +   *
        +   *   // Draw the image.
        +   *   image(img, 17, 17);
        +   *
        +   *   describe('A black square drawn in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Image object.
        +   *   let img = createImage(66, 66);
        +   *
        +   *   // Load the image's pixels into memory.
        +   *   img.loadPixels();
        +   *
        +   *   // Create a color gradient.
        +   *   for (let x = 0; x < img.width; x += 1) {
        +   *     for (let y = 0; y < img.height; y += 1) {
        +   *       // Calculate the transparency.
        +   *       let a = map(x, 0, img.width, 0, 255);
        +   *
        +   *       // Create a p5.Color object.
        +   *       let c = color(0, a);
        +   *
        +   *       // Set the pixel's color.
        +   *       img.set(x, y, c);
        +   *     }
        +   *   }
        +   *
        +   *   // Update the image's pixels.
        +   *   img.updatePixels();
        +   *
        +   *   // Display the image.
        +   *   image(img, 17, 17);
        +   *
        +   *   describe('A square with a horizontal color gradient that transitions from gray to black.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Image object.
        +   *   let img = createImage(66, 66);
        +   *
        +   *   // Load the pixels into memory.
        +   *   img.loadPixels();
        +   *   // Get the current pixel density.
        +   *   let d = pixelDensity();
        +   *
        +   *   // Calculate the pixel that is halfway through the image's pixel array.
        +   *   let halfImage = 4 * (d * img.width) * (d * img.height / 2);
        +   *
        +   *   // Set half of the image's pixels to black.
        +   *   for (let i = 0; i < halfImage; i += 4) {
        +   *     // Red.
        +   *     img.pixels[i] = 0;
        +   *     // Green.
        +   *     img.pixels[i + 1] = 0;
        +   *     // Blue.
        +   *     img.pixels[i + 2] = 0;
        +   *     // Alpha.
        +   *     img.pixels[i + 3] = 255;
        +   *   }
        +   *
        +   *   // Update the image's pixels.
        +   *   img.updatePixels();
        +   *
        +   *   // Display the image.
        +   *   image(img, 17, 17);
        +   *
        +   *   describe('A black square drawn in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createImage = function(width, height) {
        +    // p5._validateParameters('createImage', arguments);
        +    return new p5.Image(width, height);
        +  };
         
        -/**
        - * Saves the current canvas as an image.
        - *
        - * By default, `saveCanvas()` saves the canvas as a PNG image called
        - * `untitled.png`.
        - *
        - * The first parameter, `filename`, is optional. It's a string that sets the
        - * file's name. If a file extension is included, as in
        - * `saveCanvas('drawing.png')`, then the image will be saved using that
        - * format.
        - *
        - * The second parameter, `extension`, is also optional. It sets the files format.
        - * Either `'png'`, `'webp'`, or `'jpg'` can be used. For example, `saveCanvas('drawing', 'jpg')`
        - * saves the canvas to a file called `drawing.jpg`.
        - *
        - * Note: The browser will either save the file immediately or prompt the user
        - * with a dialogue window.
        - *
        - *  @method saveCanvas
        - *  @param  {p5.Framebuffer|p5.Element|HTMLCanvasElement} selectedCanvas   reference to a
        - *                                                          specific HTML5 canvas element.
        - *  @param  {String} [filename]  file name. Defaults to 'untitled'.
        - *  @param  {String} [extension] file extension, either 'png', 'webp', or 'jpg'. Defaults to 'png'.
        - *
        - *  @example
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *   background(255);
        - *
        - *   // Save the canvas to 'untitled.png'.
        - *   saveCanvas();
        - *
        - *   describe('A white square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(255);
        - *
        - *   // Save the canvas to 'myCanvas.jpg'.
        - *   saveCanvas('myCanvas.jpg');
        - *
        - *   describe('A white square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(255);
        - *
        - *   // Save the canvas to 'myCanvas.jpg'.
        - *   saveCanvas('myCanvas', 'jpg');
        - *
        - *   describe('A white square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   let cnv = createCanvas(100, 100);
        - *
        - *   background(255);
        - *
        - *   // Save the canvas to 'untitled.png'.
        - *   saveCanvas(cnv);
        - *
        - *   describe('A white square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   let cnv = createCanvas(100, 100);
        - *
        - *   background(255);
        - *
        - *   // Save the canvas to 'myCanvas.jpg'.
        - *   saveCanvas(cnv, 'myCanvas.jpg');
        - *
        - *   describe('A white square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='norender'>
        - * <code>
        - * function setup() {
        - *   let cnv = createCanvas(100, 100);
        - *
        - *   background(255);
        - *
        - *   // Save the canvas to 'myCanvas.jpg'.
        - *   saveCanvas(cnv, 'myCanvas', 'jpg');
        - *
        - *   describe('A white square.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - *  @method saveCanvas
        - *  @param  {String} [filename]
        - *  @param  {String} [extension]
        - */
        -p5.prototype.saveCanvas = function(...args) {
        -  p5._validateParameters('saveCanvas', args);
        -
        -  // copy arguments to array
        -  let htmlCanvas, filename, extension, temporaryGraphics;
        -
        -  if (args[0] instanceof HTMLCanvasElement) {
        -    htmlCanvas = args[0];
        -    args.shift();
        -  } else if (args[0] instanceof p5.Element) {
        -    htmlCanvas = args[0].elt;
        -    args.shift();
        -  } else if (args[0] instanceof p5.Framebuffer) {
        -    const framebuffer = args[0];
        -    temporaryGraphics = this.createGraphics(framebuffer.width,
        -      framebuffer.height);
        -    temporaryGraphics.pixelDensity(pixelDensity());
        -    framebuffer.loadPixels();
        -    temporaryGraphics.loadPixels();
        -    temporaryGraphics.pixels.set(framebuffer.pixels);
        -    temporaryGraphics.updatePixels();
        -
        -    htmlCanvas = temporaryGraphics.elt;
        -    args.shift();
        -  } else {
        -    htmlCanvas = this._curElement && this._curElement.elt;
        -  }
        -
        -  if (args.length >= 1) {
        -    filename = args[0];
        -  }
        -  if (args.length >= 2) {
        -    extension = args[1];
        -  }
        -
        -  extension =
        -    extension ||
        -    p5.prototype._checkFileExtension(filename, extension)[1] ||
        -    'png';
        -
        -  let mimeType;
        -  switch (extension) {
        -    default:
        -      //case 'png':
        -      mimeType = 'image/png';
        -      break;
        -    case 'webp':
        -      mimeType = 'image/webp';
        -      break;
        -    case 'jpeg':
        -    case 'jpg':
        -      mimeType = 'image/jpeg';
        -      break;
        -  }
        -
        -  htmlCanvas.toBlob(blob => {
        -    p5.prototype.downloadFile(blob, filename, extension);
        -    if(temporaryGraphics) temporaryGraphics.remove();
        -  }, mimeType);
        -};
        -
        -// this is the old saveGif, left here for compatibility purposes
        -// the only place I found it being used was on image/p5.Image.js, on the
        -// save function. that has been changed to use this function.
        -p5.prototype.encodeAndDownloadGif = function(pImg, filename) {
        -  const props = pImg.gifProperties;
        -
        -  //convert loopLimit back into Netscape Block formatting
        -  let loopLimit = props.loopLimit;
        -  if (loopLimit === 1) {
        -    loopLimit = null;
        -  } else if (loopLimit === null) {
        -    loopLimit = 0;
        -  }
        -  const buffer = new Uint8Array(pImg.width * pImg.height * props.numFrames);
        -
        -  const allFramesPixelColors = [];
        -
        -  // Used to determine the occurrence of unique palettes and the frames
        -  // which use them
        -  const paletteFreqsAndFrames = {};
        -
        -  // Pass 1:
        -  //loop over frames and get the frequency of each palette
        -  for (let i = 0; i < props.numFrames; i++) {
        -    const paletteSet = new Set();
        -    const data = props.frames[i].image.data;
        -    const dataLength = data.length;
        -    // The color for each pixel in this frame ( for easier lookup later )
        -    const pixelColors = new Uint32Array(pImg.width * pImg.height);
        -    for (let j = 0, k = 0; j < dataLength; j += 4, k++) {
        -      const r = data[j + 0];
        -      const g = data[j + 1];
        -      const b = data[j + 2];
        -      const color = (r << 16) | (g << 8) | (b << 0);
        -      paletteSet.add(color);
        -
        -      // What color does this pixel have in this frame ?
        -      pixelColors[k] = color;
        +  /**
        +   * Saves the current canvas as an image.
        +   *
        +   * By default, `saveCanvas()` saves the canvas as a PNG image called
        +   * `untitled.png`.
        +   *
        +   * The first parameter, `filename`, is optional. It's a string that sets the
        +   * file's name. If a file extension is included, as in
        +   * `saveCanvas('drawing.png')`, then the image will be saved using that
        +   * format.
        +   *
        +   * The second parameter, `extension`, is also optional. It sets the files format.
        +   * Either `'png'`, `'webp'`, or `'jpg'` can be used. For example, `saveCanvas('drawing', 'jpg')`
        +   * saves the canvas to a file called `drawing.jpg`.
        +   *
        +   * Note: The browser will either save the file immediately or prompt the user
        +   * with a dialogue window.
        +   *
        +   *  @method saveCanvas
        +   *  @param  {p5.Framebuffer|p5.Element|HTMLCanvasElement} selectedCanvas   reference to a
        +   *                                                          specific HTML5 canvas element.
        +   *  @param  {String} [filename]  file name. Defaults to 'untitled'.
        +   *  @param  {String} [extension] file extension, either 'png', 'webp', or 'jpg'. Defaults to 'png'.
        +   *
        +   *  @example
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *   background(255);
        +   *
        +   *   // Save the canvas to 'untitled.png'.
        +   *   saveCanvas();
        +   *
        +   *   describe('A white square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(255);
        +   *
        +   *   // Save the canvas to 'myCanvas.jpg'.
        +   *   saveCanvas('myCanvas.jpg');
        +   *
        +   *   describe('A white square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(255);
        +   *
        +   *   // Save the canvas to 'myCanvas.jpg'.
        +   *   saveCanvas('myCanvas', 'jpg');
        +   *
        +   *   describe('A white square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(255);
        +   *
        +   *   // Save the canvas to 'untitled.png'.
        +   *   saveCanvas(cnv);
        +   *
        +   *   describe('A white square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(255);
        +   *
        +   *   // Save the canvas to 'myCanvas.jpg'.
        +   *   saveCanvas(cnv, 'myCanvas.jpg');
        +   *
        +   *   describe('A white square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='norender'>
        +   * <code>
        +   * function setup() {
        +   *   let cnv = createCanvas(100, 100);
        +   *
        +   *   background(255);
        +   *
        +   *   // Save the canvas to 'myCanvas.jpg'.
        +   *   saveCanvas(cnv, 'myCanvas', 'jpg');
        +   *
        +   *   describe('A white square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   *  @method saveCanvas
        +   *  @param  {String} [filename]
        +   *  @param  {String} [extension]
        +   */
        +  fn.saveCanvas = function(...args) {
        +    // p5._validateParameters('saveCanvas', args);
        +
        +    // copy arguments to array
        +    let htmlCanvas, filename, extension, temporaryGraphics;
        +
        +    if (args[0] instanceof HTMLCanvasElement) {
        +      htmlCanvas = args[0];
        +      args.shift();
        +    } else if (args[0] instanceof Element) {
        +      htmlCanvas = args[0].elt;
        +      args.shift();
        +    } else if (args[0] instanceof Framebuffer) {
        +      const framebuffer = args[0];
        +      temporaryGraphics = this.createGraphics(framebuffer.width,
        +        framebuffer.height);
        +      temporaryGraphics.pixelDensity(pixelDensity());
        +      framebuffer.loadPixels();
        +      temporaryGraphics.loadPixels();
        +      temporaryGraphics.pixels.set(framebuffer.pixels);
        +      temporaryGraphics.updatePixels();
        +      htmlCanvas = temporaryGraphics.elt;
        +      args.shift();
        +    } else {
        +      htmlCanvas = this._curElement && this._curElement.elt;
             }
         
        -    // A way to put use the entire palette as an object key
        -    const paletteStr = [...paletteSet].sort().toString();
        -    if (paletteFreqsAndFrames[paletteStr] === undefined) {
        -      paletteFreqsAndFrames[paletteStr] = { freq: 1, frames: [i] };
        -    } else {
        -      paletteFreqsAndFrames[paletteStr].freq += 1;
        -      paletteFreqsAndFrames[paletteStr].frames.push(i);
        +    if (args.length >= 1) {
        +      filename = args[0];
        +    }
        +    if (args.length >= 2) {
        +      extension = args[1];
             }
         
        -    allFramesPixelColors.push(pixelColors);
        -  }
        -
        -  let framesUsingGlobalPalette = [];
        -
        -  // Now to build the global palette
        -  // Sort all the unique palettes in descending order of their occurrence
        -  const palettesSortedByFreq = Object.keys(paletteFreqsAndFrames).sort(function(
        -    a,
        -    b
        -  ) {
        -    return paletteFreqsAndFrames[b].freq - paletteFreqsAndFrames[a].freq;
        -  });
        -
        -  // The initial global palette is the one with the most occurrence
        -  const globalPalette = palettesSortedByFreq[0]
        -    .split(',')
        -    .map(a => parseInt(a));
        -
        -  framesUsingGlobalPalette = framesUsingGlobalPalette.concat(
        -    paletteFreqsAndFrames[globalPalette].frames
        -  );
        -
        -  const globalPaletteSet = new Set(globalPalette);
        -
        -  // Build a more complete global palette
        -  // Iterate over the remaining palettes in the order of
        -  // their occurrence and see if the colors in this palette which are
        -  // not in the global palette can be added there, while keeping the length
        -  // of the global palette <= 256
        -  for (let i = 1; i < palettesSortedByFreq.length; i++) {
        -    const palette = palettesSortedByFreq[i].split(',').map(a => parseInt(a));
        -
        -    const difference = palette.filter(x => !globalPaletteSet.has(x));
        -    if (globalPalette.length + difference.length <= 256) {
        -      for (let j = 0; j < difference.length; j++) {
        -        globalPalette.push(difference[j]);
        -        globalPaletteSet.add(difference[j]);
        -      }
        +    extension =
        +      extension ||
        +      fn._checkFileExtension(filename, extension)[1] ||
        +      'png';
         
        -      // All frames using this palette now use the global palette
        -      framesUsingGlobalPalette = framesUsingGlobalPalette.concat(
        -        paletteFreqsAndFrames[palettesSortedByFreq[i]].frames
        -      );
        +    let mimeType;
        +    switch (extension) {
        +      default:
        +        //case 'png':
        +        mimeType = 'image/png';
        +        break;
        +      case 'webp':
        +        mimeType = 'image/webp';
        +        break;
        +      case 'jpeg':
        +      case 'jpg':
        +        mimeType = 'image/jpeg';
        +        break;
             }
        -  }
         
        -  framesUsingGlobalPalette = new Set(framesUsingGlobalPalette);
        +    htmlCanvas.toBlob(blob => {
        +      fn.downloadFile(blob, filename, extension);
        +      if(temporaryGraphics) temporaryGraphics.remove();
        +    }, mimeType);
        +  };
         
        -  // Build a lookup table of the index of each color in the global palette
        -  // Maps a color to its index
        -  const globalIndicesLookup = {};
        -  for (let i = 0; i < globalPalette.length; i++) {
        -    if (!globalIndicesLookup[globalPalette[i]]) {
        -      globalIndicesLookup[globalPalette[i]] = i;
        +  // this is the old saveGif, left here for compatibility purposes
        +  // the only place I found it being used was on image/p5.Image.js, on the
        +  // save function. that has been changed to use this function.
        +  fn.encodeAndDownloadGif = function(pImg, filename) {
        +    const props = pImg.gifProperties;
        +
        +    //convert loopLimit back into Netscape Block formatting
        +    let loopLimit = props.loopLimit;
        +    if (loopLimit === 1) {
        +      loopLimit = null;
        +    } else if (loopLimit === null) {
        +      loopLimit = 0;
             }
        -  }
        -
        -  // force palette to be power of 2
        -  let powof2 = 1;
        -  while (powof2 < globalPalette.length) {
        -    powof2 <<= 1;
        -  }
        -  globalPalette.length = powof2;
        -
        -  // global opts
        -  const opts = {
        -    loop: loopLimit,
        -    palette: new Uint32Array(globalPalette)
        -  };
        -  const gifWriter = new omggif.GifWriter(buffer, pImg.width, pImg.height, opts);
        -  let previousFrame = {};
        -
        -  // Pass 2
        -  // Determine if the frame needs a local palette
        -  // Also apply transparency optimization. This function will often blow up
        -  // the size of a GIF if not for transparency. If a pixel in one frame has
        -  // the same color in the previous frame, that pixel can be marked as
        -  // transparent. We decide one particular color as transparent and make all
        -  // transparent pixels take this color. This helps in later in compression.
        -  for (let i = 0; i < props.numFrames; i++) {
        -    const localPaletteRequired = !framesUsingGlobalPalette.has(i);
        -    const palette = localPaletteRequired ? [] : globalPalette;
        -    const pixelPaletteIndex = new Uint8Array(pImg.width * pImg.height);
        -
        -    // Lookup table mapping color to its indices
        -    const colorIndicesLookup = {};
        -
        -    // All the colors that cannot be marked transparent in this frame
        -    const cannotBeTransparent = new Set();
        -
        -    allFramesPixelColors[i].forEach((color, k) => {
        -      if (localPaletteRequired) {
        -        if (colorIndicesLookup[color] === undefined) {
        -          colorIndicesLookup[color] = palette.length;
        -          palette.push(color);
        -        }
        -        pixelPaletteIndex[k] = colorIndicesLookup[color];
        -      } else {
        -        pixelPaletteIndex[k] = globalIndicesLookup[color];
        +    const buffer = new Uint8Array(pImg.width * pImg.height * props.numFrames);
        +
        +    const allFramesPixelColors = [];
        +
        +    // Used to determine the occurrence of unique palettes and the frames
        +    // which use them
        +    const paletteFreqsAndFrames = {};
        +
        +    // Pass 1:
        +    //loop over frames and get the frequency of each palette
        +    for (let i = 0; i < props.numFrames; i++) {
        +      const paletteSet = new Set();
        +      const data = props.frames[i].image.data;
        +      const dataLength = data.length;
        +      // The color for each pixel in this frame ( for easier lookup later )
        +      const pixelColors = new Uint32Array(pImg.width * pImg.height);
        +      for (let j = 0, k = 0; j < dataLength; j += 4, k++) {
        +        const r = data[j + 0];
        +        const g = data[j + 1];
        +        const b = data[j + 2];
        +        const color = (r << 16) | (g << 8) | (b << 0);
        +        paletteSet.add(color);
        +
        +        // What color does this pixel have in this frame ?
        +        pixelColors[k] = color;
               }
         
        -      if (i > 0) {
        -        // If even one pixel of this color has changed in this frame
        -        // from the previous frame, we cannot mark it as transparent
        -        if (allFramesPixelColors[i - 1][k] !== color) {
        -          cannotBeTransparent.add(color);
        -        }
        +      // A way to put use the entire palette as an object key
        +      const paletteStr = [...paletteSet].sort().toString();
        +      if (paletteFreqsAndFrames[paletteStr] === undefined) {
        +        paletteFreqsAndFrames[paletteStr] = { freq: 1, frames: [i] };
        +      } else {
        +        paletteFreqsAndFrames[paletteStr].freq += 1;
        +        paletteFreqsAndFrames[paletteStr].frames.push(i);
               }
        -    });
         
        -    const frameOpts = {};
        +      allFramesPixelColors.push(pixelColors);
        +    }
         
        -    // Transparency optimization
        -    const canBeTransparent = palette.filter(a => !cannotBeTransparent.has(a));
        -    if (canBeTransparent.length > 0) {
        -      // Select a color to mark as transparent
        -      const transparent = canBeTransparent[0];
        -      const transparentIndex = localPaletteRequired
        -        ? colorIndicesLookup[transparent]
        -        : globalIndicesLookup[transparent];
        -      if (i > 0) {
        -        for (let k = 0; k < allFramesPixelColors[i].length; k++) {
        -          // If this pixel in this frame has the same color in previous frame
        -          if (allFramesPixelColors[i - 1][k] === allFramesPixelColors[i][k]) {
        -            pixelPaletteIndex[k] = transparentIndex;
        -          }
        +    let framesUsingGlobalPalette = [];
        +
        +    // Now to build the global palette
        +    // Sort all the unique palettes in descending order of their occurrence
        +    const palettesSortedByFreq = Object.keys(paletteFreqsAndFrames).sort(function(
        +      a,
        +      b
        +    ) {
        +      return paletteFreqsAndFrames[b].freq - paletteFreqsAndFrames[a].freq;
        +    });
        +
        +    // The initial global palette is the one with the most occurrence
        +    const globalPalette = palettesSortedByFreq[0]
        +      .split(',')
        +      .map(a => parseInt(a));
        +
        +    framesUsingGlobalPalette = framesUsingGlobalPalette.concat(
        +      paletteFreqsAndFrames[globalPalette].frames
        +    );
        +
        +    const globalPaletteSet = new Set(globalPalette);
        +
        +    // Build a more complete global palette
        +    // Iterate over the remaining palettes in the order of
        +    // their occurrence and see if the colors in this palette which are
        +    // not in the global palette can be added there, while keeping the length
        +    // of the global palette <= 256
        +    for (let i = 1; i < palettesSortedByFreq.length; i++) {
        +      const palette = palettesSortedByFreq[i].split(',').map(a => parseInt(a));
        +
        +      const difference = palette.filter(x => !globalPaletteSet.has(x));
        +      if (globalPalette.length + difference.length <= 256) {
        +        for (let j = 0; j < difference.length; j++) {
        +          globalPalette.push(difference[j]);
        +          globalPaletteSet.add(difference[j]);
                 }
        -        frameOpts.transparent = transparentIndex;
        -        // If this frame has any transparency, do not dispose the previous frame
        -        previousFrame.frameOpts.disposal = 1;
        +
        +        // All frames using this palette now use the global palette
        +        framesUsingGlobalPalette = framesUsingGlobalPalette.concat(
        +          paletteFreqsAndFrames[palettesSortedByFreq[i]].frames
        +        );
               }
             }
        -    frameOpts.delay = props.frames[i].delay / 10; // Move timing back into GIF formatting
        -    if (localPaletteRequired) {
        -      // force palette to be power of 2
        -      let powof2 = 1;
        -      while (powof2 < palette.length) {
        -        powof2 <<= 1;
        +
        +    framesUsingGlobalPalette = new Set(framesUsingGlobalPalette);
        +
        +    // Build a lookup table of the index of each color in the global palette
        +    // Maps a color to its index
        +    const globalIndicesLookup = {};
        +    for (let i = 0; i < globalPalette.length; i++) {
        +      if (!globalIndicesLookup[globalPalette[i]]) {
        +        globalIndicesLookup[globalPalette[i]] = i;
               }
        -      palette.length = powof2;
        -      frameOpts.palette = new Uint32Array(palette);
             }
        -    if (i > 0) {
        -      // add the frame that came before the current one
        -      gifWriter.addFrame(
        -        0,
        -        0,
        -        pImg.width,
        -        pImg.height,
        -        previousFrame.pixelPaletteIndex,
        -        previousFrame.frameOpts
        -      );
        +
        +    // force palette to be power of 2
        +    let powof2 = 1;
        +    while (powof2 < globalPalette.length) {
        +      powof2 <<= 1;
             }
        -    // previous frame object should now have details of this frame
        -    previousFrame = {
        -      pixelPaletteIndex,
        -      frameOpts
        +    globalPalette.length = powof2;
        +
        +    // global opts
        +    const opts = {
        +      loop: loopLimit,
        +      palette: new Uint32Array(globalPalette)
             };
        -  }
        -
        -  previousFrame.frameOpts.disposal = 1;
        -  // add the last frame
        -  gifWriter.addFrame(
        -    0,
        -    0,
        -    pImg.width,
        -    pImg.height,
        -    previousFrame.pixelPaletteIndex,
        -    previousFrame.frameOpts
        -  );
        -
        -  const extension = 'gif';
        -  const blob = new Blob([buffer.slice(0, gifWriter.end())], {
        -    type: 'image/gif'
        -  });
        -  p5.prototype.downloadFile(blob, filename, extension);
        -};
        +    const gifWriter = new omggif.GifWriter(buffer, pImg.width, pImg.height, opts);
        +    let previousFrame = {};
        +
        +    // Pass 2
        +    // Determine if the frame needs a local palette
        +    // Also apply transparency optimization. This function will often blow up
        +    // the size of a GIF if not for transparency. If a pixel in one frame has
        +    // the same color in the previous frame, that pixel can be marked as
        +    // transparent. We decide one particular color as transparent and make all
        +    // transparent pixels take this color. This helps in later in compression.
        +    for (let i = 0; i < props.numFrames; i++) {
        +      const localPaletteRequired = !framesUsingGlobalPalette.has(i);
        +      const palette = localPaletteRequired ? [] : globalPalette;
        +      const pixelPaletteIndex = new Uint8Array(pImg.width * pImg.height);
        +
        +      // Lookup table mapping color to its indices
        +      const colorIndicesLookup = {};
        +
        +      // All the colors that cannot be marked transparent in this frame
        +      const cannotBeTransparent = new Set();
        +
        +      allFramesPixelColors[i].forEach((color, k) => {
        +        if (localPaletteRequired) {
        +          if (colorIndicesLookup[color] === undefined) {
        +            colorIndicesLookup[color] = palette.length;
        +            palette.push(color);
        +          }
        +          pixelPaletteIndex[k] = colorIndicesLookup[color];
        +        } else {
        +          pixelPaletteIndex[k] = globalIndicesLookup[color];
        +        }
         
        -/**
        - * Captures a sequence of frames from the canvas that can be saved as images.
        - *
        - * `saveFrames()` creates an array of frame objects. Each frame is stored as
        - * an object with its file type, file name, and image data as a string. For
        - * example, the first saved frame might have the following properties:
        - *
        - * `{ ext: 'png', filename: 'frame0', imageData: 'data:image/octet-stream;base64, abc123' }`.
        - *
        - * The first parameter, `filename`, sets the prefix for the file names. For
        - * example, setting the prefix to `'frame'` would generate the image files
        - * `frame0.png`, `frame1.png`, and so on.
        - *
        - * The second parameter, `extension`, sets the file type to either `'png'` or
        - * `'jpg'`.
        - *
        - * The third parameter, `duration`, sets the duration to record in seconds.
        - * The maximum duration is 15 seconds.
        - *
        - * The fourth parameter, `framerate`, sets the number of frames to record per
        - * second. The maximum frame rate value is 22. Limits are placed on `duration`
        - * and `framerate` to avoid using too much memory. Recording large canvases
        - * can easily crash sketches or even web browsers.
        - *
        - * The fifth parameter, `callback`, is optional. If a function is passed,
        - * image files won't be saved by default. The callback function can be used
        - * to process an array containing the data for each captured frame. The array
        - * of image data contains a sequence of objects with three properties for each
        - * frame: `imageData`, `filename`, and `extension`.
        - *
        - * Note: Frames are downloaded as individual image files by default.
        - *
        - * @method saveFrames
        - * @param  {String}   filename  prefix of file name.
        - * @param  {String}   extension file extension, either 'jpg' or 'png'.
        - * @param  {Number}   duration  duration in seconds to record. This parameter will be constrained to be less or equal to 15.
        - * @param  {Number}   framerate number of frames to save per second. This parameter will be constrained to be less or equal to 22.
        - * @param  {function(Array)} [callback] callback function that will be executed
        -                                  to handle the image data. This function
        -                                  should accept an array as argument. The
        -                                  array will contain the specified number of
        -                                  frames of objects. Each object has three
        -                                  properties: `imageData`, `filename`, and `extension`.
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A square repeatedly changes color from blue to pink.');
        - * }
        - *
        - * function draw() {
        - *   let r = frameCount % 255;
        - *   let g = 50;
        - *   let b = 100;
        - *   background(r, g, b);
        - * }
        - *
        - * // Save the frames when the user presses the 's' key.
        - * function keyPressed() {
        - *   if (key === 's') {
        - *     saveFrames('frame', 'png', 1, 5);
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A square repeatedly changes color from blue to pink.');
        - * }
        - *
        - * function draw() {
        - *   let r = frameCount % 255;
        - *   let g = 50;
        - *   let b = 100;
        - *   background(r, g, b);
        - * }
        - *
        - * // Print 5 frames when the user presses the mouse.
        - * function mousePressed() {
        - *   saveFrames('frame', 'png', 1, 5, printFrames);
        - * }
        - *
        - * // Prints an array of objects containing raw image data, filenames, and extensions.
        - * function printFrames(frames) {
        - *   for (let frame of frames) {
        - *     print(frame);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.saveFrames = function(fName, ext, _duration, _fps, callback) {
        -  p5._validateParameters('saveFrames', arguments);
        -  let duration = _duration || 3;
        -  duration = p5.prototype.constrain(duration, 0, 15);
        -  duration = duration * 1000;
        -  let fps = _fps || 15;
        -  fps = p5.prototype.constrain(fps, 0, 22);
        -  let count = 0;
        -
        -  const makeFrame = p5.prototype._makeFrame;
        -  const cnv = this._curElement.elt;
        -  let frames = [];
        -  const frameFactory = setInterval(() => {
        -    frames.push(makeFrame(fName + count, ext, cnv));
        -    count++;
        -  }, 1000 / fps);
        -
        -  setTimeout(() => {
        -    clearInterval(frameFactory);
        -    if (callback) {
        -      callback(frames);
        -    } else {
        -      for (const f of frames) {
        -        p5.prototype.downloadFile(f.imageData, f.filename, f.ext);
        +        if (i > 0) {
        +          // If even one pixel of this color has changed in this frame
        +          // from the previous frame, we cannot mark it as transparent
        +          if (allFramesPixelColors[i - 1][k] !== color) {
        +            cannotBeTransparent.add(color);
        +          }
        +        }
        +      });
        +
        +      const frameOpts = {};
        +
        +      // Transparency optimization
        +      const canBeTransparent = palette.filter(a => !cannotBeTransparent.has(a));
        +      if (canBeTransparent.length > 0) {
        +        // Select a color to mark as transparent
        +        const transparent = canBeTransparent[0];
        +        const transparentIndex = localPaletteRequired
        +          ? colorIndicesLookup[transparent]
        +          : globalIndicesLookup[transparent];
        +        if (i > 0) {
        +          for (let k = 0; k < allFramesPixelColors[i].length; k++) {
        +            // If this pixel in this frame has the same color in previous frame
        +            if (allFramesPixelColors[i - 1][k] === allFramesPixelColors[i][k]) {
        +              pixelPaletteIndex[k] = transparentIndex;
        +            }
        +          }
        +          frameOpts.transparent = transparentIndex;
        +          // If this frame has any transparency, do not dispose the previous frame
        +          previousFrame.frameOpts.disposal = 1;
        +        }
        +      }
        +      frameOpts.delay = props.frames[i].delay / 10; // Move timing back into GIF formatting
        +      if (localPaletteRequired) {
        +        // force palette to be power of 2
        +        let powof2 = 1;
        +        while (powof2 < palette.length) {
        +          powof2 <<= 1;
        +        }
        +        palette.length = powof2;
        +        frameOpts.palette = new Uint32Array(palette);
               }
        +      if (i > 0) {
        +        // add the frame that came before the current one
        +        gifWriter.addFrame(
        +          0,
        +          0,
        +          pImg.width,
        +          pImg.height,
        +          previousFrame.pixelPaletteIndex,
        +          previousFrame.frameOpts
        +        );
        +      }
        +      // previous frame object should now have details of this frame
        +      previousFrame = {
        +        pixelPaletteIndex,
        +        frameOpts
        +      };
             }
        -    frames = []; // clear frames
        -  }, duration + 0.01);
        -};
        -
        -p5.prototype._makeFrame = function(filename, extension, _cnv) {
        -  let cnv;
        -  if (this) {
        -    cnv = this._curElement.elt;
        -  } else {
        -    cnv = _cnv;
        -  }
        -  let mimeType;
        -  if (!extension) {
        -    extension = 'png';
        -    mimeType = 'image/png';
        -  } else {
        -    switch (extension.toLowerCase()) {
        -      case 'png':
        -        mimeType = 'image/png';
        -        break;
        -      case 'jpeg':
        -        mimeType = 'image/jpeg';
        -        break;
        -      case 'jpg':
        -        mimeType = 'image/jpeg';
        -        break;
        -      default:
        -        mimeType = 'image/png';
        -        break;
        +
        +    previousFrame.frameOpts.disposal = 1;
        +    // add the last frame
        +    gifWriter.addFrame(
        +      0,
        +      0,
        +      pImg.width,
        +      pImg.height,
        +      previousFrame.pixelPaletteIndex,
        +      previousFrame.frameOpts
        +    );
        +
        +    const extension = 'gif';
        +    const blob = new Blob([buffer.slice(0, gifWriter.end())], {
        +      type: 'image/gif'
        +    });
        +    fn.downloadFile(blob, filename, extension);
        +  };
        +
        +  /**
        +   * Captures a sequence of frames from the canvas that can be saved as images.
        +   *
        +   * `saveFrames()` creates an array of frame objects. Each frame is stored as
        +   * an object with its file type, file name, and image data as a string. For
        +   * example, the first saved frame might have the following properties:
        +   *
        +   * `{ ext: 'png', filenmame: 'frame0', imageData: 'data:image/octet-stream;base64, abc123' }`.
        +   *
        +   * The first parameter, `filename`, sets the prefix for the file names. For
        +   * example, setting the prefix to `'frame'` would generate the image files
        +   * `frame0.png`, `frame1.png`, and so on.
        +   *
        +   * The second parameter, `extension`, sets the file type to either `'png'` or
        +   * `'jpg'`.
        +   *
        +   * The third parameter, `duration`, sets the duration to record in seconds.
        +   * The maximum duration is 15 seconds.
        +   *
        +   * The fourth parameter, `framerate`, sets the number of frames to record per
        +   * second. The maximum frame rate value is 22. Limits are placed on `duration`
        +   * and `framerate` to avoid using too much memory. Recording large canvases
        +   * can easily crash sketches or even web browsers.
        +   *
        +   * The fifth parameter, `callback`, is optional. If a function is passed,
        +   * image files won't be saved by default. The callback function can be used
        +   * to process an array containing the data for each captured frame. The array
        +   * of image data contains a sequence of objects with three properties for each
        +   * frame: `imageData`, `filename`, and `extension`.
        +   *
        +   * Note: Frames are downloaded as individual image files by default.
        +   *
        +   * @method saveFrames
        +   * @param  {String}   filename  prefix of file name.
        +   * @param  {String}   extension file extension, either 'jpg' or 'png'.
        +   * @param  {Number}   duration  duration in seconds to record. This parameter will be constrained to be less or equal to 15.
        +   * @param  {Number}   framerate number of frames to save per second. This parameter will be constrained to be less or equal to 22.
        +   * @param  {function(Array)} [callback] callback function that will be executed
        +                                    to handle the image data. This function
        +                                    should accept an array as argument. The
        +                                    array will contain the specified number of
        +                                    frames of objects. Each object has three
        +                                    properties: `imageData`, `filename`, and `extension`.
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A square repeatedly changes color from blue to pink.');
        +   * }
        +   *
        +   * function draw() {
        +   *   let r = frameCount % 255;
        +   *   let g = 50;
        +   *   let b = 100;
        +   *   background(r, g, b);
        +   * }
        +   *
        +   * // Save the frames when the user presses the 's' key.
        +   * function keyPressed() {
        +   *   if (key === 's') {
        +   *     saveFrames('frame', 'png', 1, 5);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A square repeatedly changes color from blue to pink.');
        +   * }
        +   *
        +   * function draw() {
        +   *   let r = frameCount % 255;
        +   *   let g = 50;
        +   *   let b = 100;
        +   *   background(r, g, b);
        +   * }
        +   *
        +   * // Print 5 frames when the user presses the mouse.
        +   * function mousePressed() {
        +   *   saveFrames('frame', 'png', 1, 5, printFrames);
        +   * }
        +   *
        +   * // Prints an array of objects containing raw image data, filenames, and extensions.
        +   * function printFrames(frames) {
        +   *   for (let frame of frames) {
        +   *     print(frame);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.saveFrames = function(fName, ext, _duration, _fps, callback) {
        +    // p5._validateParameters('saveFrames', arguments);
        +    let duration = _duration || 3;
        +    duration = Math.max(Math.min(duration, 15), 0);
        +    duration = duration * 1000;
        +    let fps = _fps || 15;
        +    fps = Math.max(Math.min(fps, 22), 0);
        +    let count = 0;
        +
        +    const makeFrame = fn._makeFrame;
        +    const cnv = this._curElement.elt;
        +    let frames = [];
        +    const frameFactory = setInterval(() => {
        +      frames.push(makeFrame(fName + count, ext, cnv));
        +      count++;
        +    }, 1000 / fps);
        +
        +    setTimeout(() => {
        +      clearInterval(frameFactory);
        +      if (callback) {
        +        callback(frames);
        +      } else {
        +        for (const f of frames) {
        +          fn.downloadFile(f.imageData, f.filename, f.ext);
        +        }
        +      }
        +      frames = []; // clear frames
        +    }, duration + 0.01);
        +  };
        +
        +  fn._makeFrame = function(filename, extension, _cnv) {
        +    let cnv;
        +    if (this) {
        +      cnv = this._curElement.elt;
        +    } else {
        +      cnv = _cnv;
        +    }
        +    let mimeType;
        +    if (!extension) {
        +      extension = 'png';
        +      mimeType = 'image/png';
        +    } else {
        +      switch (extension.toLowerCase()) {
        +        case 'png':
        +          mimeType = 'image/png';
        +          break;
        +        case 'jpeg':
        +          mimeType = 'image/jpeg';
        +          break;
        +        case 'jpg':
        +          mimeType = 'image/jpeg';
        +          break;
        +        default:
        +          mimeType = 'image/png';
        +          break;
        +      }
             }
        -  }
        -  const downloadMime = 'image/octet-stream';
        -  let imageData = cnv.toDataURL(mimeType);
        -  imageData = imageData.replace(mimeType, downloadMime);
        -
        -  const thisFrame = {};
        -  thisFrame.imageData = imageData;
        -  thisFrame.filename = filename;
        -  thisFrame.ext = extension;
        -  return thisFrame;
        -};
        -
        -export default p5;
        +    const downloadMime = 'image/octet-stream';
        +    let imageData = cnv.toDataURL(mimeType);
        +    imageData = imageData.replace(mimeType, downloadMime);
        +
        +    const thisFrame = {};
        +    thisFrame.imageData = imageData;
        +    thisFrame.filename = filename;
        +    thisFrame.ext = extension;
        +    return thisFrame;
        +  };
        +}
        +
        +export default image;
        +
        +if(typeof p5 !== 'undefined'){
        +  image(p5, p5.prototype);
        +}
        diff --git a/src/image/index.js b/src/image/index.js
        new file mode 100644
        index 0000000000..ce63a52bd4
        --- /dev/null
        +++ b/src/image/index.js
        @@ -0,0 +1,15 @@
        +import image from './image.js';
        +import loadingDisplaying from './loading_displaying.js';
        +import p5image from './p5.Image.js';
        +import pixels from './pixels.js';
        +import shader from '../webgl/p5.Shader.js';
        +import texture from '../webgl/p5.Texture.js';
        +
        +export default function(p5){
        +  p5.registerAddon(image);
        +  p5.registerAddon(loadingDisplaying);
        +  p5.registerAddon(p5image);
        +  p5.registerAddon(pixels);
        +  p5.registerAddon(shader);
        +  p5.registerAddon(texture);
        +}
        diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js
        index c0ce679117..a95efdcf0c 100644
        --- a/src/image/loading_displaying.js
        +++ b/src/image/loading_displaying.js
        @@ -5,1500 +5,1459 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
         import canvas from '../core/helpers';
         import * as constants from '../core/constants';
        -import omggif from 'omggif';
        +import { request } from '../io/files';
        +import * as omggif from 'omggif';
         import { GIFEncoder, quantize, nearestColorIndex } from 'gifenc';
         
        -import '../core/friendly_errors/validate_params';
        -import '../core/friendly_errors/file_errors';
        -import '../core/friendly_errors/fes_core';
        +function loadingDisplaying(p5, fn){
        +  /**
        +   * Loads an image to create a <a href="#/p5.Image">p5.Image</a> object.
        +   *
        +   * `loadImage()` interprets the first parameter one of three ways. If the path
        +   * to an image file is provided, `loadImage()` will load it. Paths to local
        +   * files should be relative, such as `'assets/thundercat.jpg'`. URLs such as
        +   * `'https://example.com/thundercat.jpg'` may be blocked due to browser
        +   * security. Raw image data can also be passed as a base64 encoded image in
        +   * the form `'data:image/png;base64,arandomsequenceofcharacters'`. The `path`
        +   * parameter can also be defined as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
        +   * object for more advanced usage.
        +   *
        +   * The second parameter is optional. If a function is passed, it will be
        +   * called once the image has loaded. The callback function can optionally use
        +   * the new <a href="#/p5.Image">p5.Image</a> object. The return value of the
        +   * function will be used as the final return value of `loadImage()`.
        +   *
        +   * The third parameter is also optional. If a function is passed, it will be
        +   * called if the image fails to load. The callback function can optionally use
        +   * the event error. The return value of the function will be used as the final
        +   * return value of `loadImage()`.
        +   *
        +   * This function returns a `Promise` and should be used in an `async` setup with
        +   * `await`. See the examples for the usage syntax.
        +   *
        +   * @method loadImage
        +   * @param  {String|Request}      path  path of the image to be loaded or base64 encoded image.
        +   * @param  {function(p5.Image)} [successCallback] function called with
        +   *                               <a href="#/p5.Image">p5.Image</a> once it
        +   *                               loads.
        +   * @param  {function(Event)}    [failureCallback] function called with event
        +   *                               error if the image fails to load.
        +   * @return {Promise<p5.Image>}   the <a href="#/p5.Image">p5.Image</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image and create a p5.Image object.
        +   * async function setup() {
        +   *   img = await loadImage('assets/laDefense.jpg');
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Draw the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   describe('Image of the underside of a white umbrella and a gridded ceiling.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Call handleImage() once the image loads.
        +   *   loadImage('assets/laDefense.jpg', handleImage);
        +   *
        +   *   describe('Image of the underside of a white umbrella and a gridded ceiling.');
        +   * }
        +   *
        +   * // Display the image.
        +   * function handleImage(img) {
        +   *   image(img, 0, 0);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   // Call handleImage() once the image loads or
        +   *   // call handleError() if an error occurs.
        +   *   loadImage('assets/laDefense.jpg', handleImage, handleError);
        +   * }
        +   *
        +   * // Display the image.
        +   * function handleImage(img) {
        +   *   image(img, 0, 0);
        +   *
        +   *   describe('Image of the underside of a white umbrella and a gridded ceiling.');
        +   * }
        +   *
        +   * // Log the error.
        +   * function handleError(event) {
        +   *   console.error('Oops!', event);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.loadImage = async function(
        +    path,
        +    successCallback,
        +    failureCallback
        +  ) {
        +    // p5._validateParameters('loadImage', arguments);
        +
        +    try{
        +      let pImg = new p5.Image(1, 1, this);
        +
        +      const req = new Request(path, {
        +        method: 'GET',
        +        mode: 'cors'
        +      });
        +
        +      const { data, headers } = await request(req, 'bytes');
         
        -/**
        - * Loads an image to create a <a href="#/p5.Image">p5.Image</a> object.
        - *
        - * `loadImage()` interprets the first parameter one of three ways. If the path
        - * to an image file is provided, `loadImage()` will load it. Paths to local
        - * files should be relative, such as `'assets/thundercat.jpg'`. URLs such as
        - * `'https://example.com/thundercat.jpg'` may be blocked due to browser
        - * security. Raw image data can also be passed as a base64 encoded image in
        - * the form `'data:image/png;base64,arandomsequenceofcharacters'`.
        - *
        - * The second parameter is optional. If a function is passed, it will be
        - * called once the image has loaded. The callback function can optionally use
        - * the new <a href="#/p5.Image">p5.Image</a> object.
        - *
        - * The third parameter is also optional. If a function is passed, it will be
        - * called if the image fails to load. The callback function can optionally use
        - * the event error.
        - *
        - * Images can take time to load. Calling `loadImage()` in
        - * <a href="#/p5/preload">preload()</a> ensures images load before they're
        - * used in <a href="#/p5/setup">setup()</a> or <a href="#/p5/draw">draw()</a>.
        - *
        - * @method loadImage
        - * @param  {String} path path of the image to be loaded or base64 encoded image.
        - * @param  {function(p5.Image)} [successCallback] function called with
        - *                               <a href="#/p5.Image">p5.Image</a> once it
        - *                               loads.
        - * @param  {function(Event)}    [failureCallback] function called with event
        - *                               error if the image fails to load.
        - * @return {p5.Image}            the <a href="#/p5.Image">p5.Image</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image and create a p5.Image object.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Draw the image.
        - *   image(img, 0, 0);
        - *
        - *   describe('Image of the underside of a white umbrella and a gridded ceiling.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Call handleImage() once the image loads.
        - *   loadImage('assets/laDefense.jpg', handleImage);
        - *
        - *   describe('Image of the underside of a white umbrella and a gridded ceiling.');
        - * }
        - *
        - * // Display the image.
        - * function handleImage(img) {
        - *   image(img, 0, 0);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   // Call handleImage() once the image loads or
        - *   // call handleError() if an error occurs.
        - *   loadImage('assets/laDefense.jpg', handleImage, handleError);
        - * }
        - *
        - * // Display the image.
        - * function handleImage(img) {
        - *   image(img, 0, 0);
        - *
        - *   describe('Image of the underside of a white umbrella and a gridded ceiling.');
        - * }
        - *
        - * // Log the error.
        - * function handleError(event) {
        - *   console.error('Oops!', event);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.loadImage = function(path, successCallback, failureCallback) {
        -  p5._validateParameters('loadImage', arguments);
        -  const pImg = new p5.Image(1, 1, this);
        -  const self = this;
        -
        -  const req = new Request(path, {
        -    method: 'GET',
        -    mode: 'cors'
        -  });
        -
        -  fetch(path, req)
        -    .then(response => {
               // GIF section
        -      const contentType = response.headers.get('content-type');
        +      const contentType = headers.get('content-type');
        +
               if (contentType === null) {
                 console.warn(
                   'The image you loaded does not have a Content-Type header. If you are using the online editor consider reuploading the asset.'
                 );
               }
        +
               if (contentType && contentType.includes('image/gif')) {
        -        response.arrayBuffer().then(
        -          arrayBuffer => {
        -            if (arrayBuffer) {
        -              const byteArray = new Uint8Array(arrayBuffer);
        -              _createGif(
        -                byteArray,
        -                pImg,
        -                successCallback,
        -                failureCallback,
        -                (pImg => {
        -                  self._decrementPreload();
        -                }).bind(self)
        -              );
        -            }
        -          },
        -          e => {
        -            if (typeof failureCallback === 'function') {
        -              failureCallback(e);
        -              self._decrementPreload();
        -            } else {
        -              console.error(e);
        -            }
        -          }
        +        await _createGif(
        +          data,
        +          pImg
                 );
        +
               } else {
                 // Non-GIF Section
        -        const img = new Image();
        +        const blob = new Blob([data]);
        +        const img = await createImageBitmap(blob);
         
        -        img.onload = () => {
        -          pImg.width = pImg.canvas.width = img.width;
        -          pImg.height = pImg.canvas.height = img.height;
        +        pImg.width = pImg.canvas.width = img.width;
        +        pImg.height = pImg.canvas.height = img.height;
         
        -          // Draw the image into the backing canvas of the p5.Image
        -          pImg.drawingContext.drawImage(img, 0, 0);
        -          pImg.modified = true;
        -          if (typeof successCallback === 'function') {
        -            successCallback(pImg);
        -          }
        -          self._decrementPreload();
        -        };
        -
        -        img.onerror = e => {
        -          p5._friendlyFileLoadError(0, img.src);
        -          if (typeof failureCallback === 'function') {
        -            failureCallback(e);
        -            self._decrementPreload();
        -          } else {
        -            console.error(e);
        -          }
        -        };
        -
        -        // Set crossOrigin in case image is served with CORS headers.
        -        // This will let us draw to the canvas without tainting it.
        -        // See https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image
        -        // When using data-uris the file will be loaded locally
        -        // so we don't need to worry about crossOrigin with base64 file types.
        -        if (path.indexOf('data:image/') !== 0) {
        -          img.crossOrigin = 'Anonymous';
        -        }
        -        // start loading the image
        -        img.src = path;
        +        // Draw the image into the backing canvas of the p5.Image
        +        pImg.drawingContext.drawImage(img, 0, 0);
               }
        +
               pImg.modified = true;
        -    })
        -    .catch(e => {
        +
        +      if(successCallback){
        +        return successCallback(pImg);
        +      }else{
        +        return pImg;
        +      }
        +
        +    } catch(err) {
               p5._friendlyFileLoadError(0, path);
               if (typeof failureCallback === 'function') {
        -        failureCallback(e);
        -        self._decrementPreload();
        +        return failureCallback(err);
               } else {
        -        console.error(e);
        +        throw err;
               }
        -    });
        -  return pImg;
        -};
        -
        -/**
        - * Generates a gif from a sketch and saves it to a file.
        - *
        - * `saveGif()` may be called in <a href="#/p5/setup">setup()</a> or at any
        - * point while a sketch is running.
        - *
        - * The first parameter, `fileName`, sets the gif's file name.
        - *
        - * The second parameter, `duration`, sets the gif's duration in seconds.
        - *
        - * The third parameter, `options`, is optional. If an object is passed,
        - * `saveGif()` will use its properties to customize the gif. `saveGif()`
        - * recognizes the properties `delay`, `units`, `silent`,
        - * `notificationDuration`, and `notificationID`.
        - *
        - * @method saveGif
        - * @param  {String} filename file name of gif.
        - * @param  {Number} duration duration in seconds to capture from the sketch.
        - * @param  {Object} [options] an object that can contain five more properties:
        - *                  `delay`, a Number specifying how much time to wait before recording;
        - *                  `units`, a String that can be either 'seconds' or 'frames'. By default it's 'seconds’;
        - *                  `silent`, a Boolean that defines presence of progress notifications. By default it’s `false`;
        - *                  `notificationDuration`, a Number that defines how long in seconds the final notification
        - *                  will live. By default it's `0`, meaning the notification will never be removed;
        - *                  `notificationID`, a String that specifies the id of the notification's DOM element. By default it’s `'progressBar’`.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A circle drawn in the middle of a gray square. The circle changes color from black to white, then repeats.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the circle.
        - *   let c = frameCount % 255;
        - *   fill(c);
        - *
        - *   // Display the circle.
        - *   circle(50, 50, 25);
        - * }
        - *
        - * // Save a 5-second gif when the user presses the 's' key.
        - * function keyPressed() {
        - *   if (key === 's') {
        - *     saveGif('mySketch', 5);
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A circle drawn in the middle of a gray square. The circle changes color from black to white, then repeats.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the circle.
        - *   let c = frameCount % 255;
        - *   fill(c);
        - *
        - *   // Display the circle.
        - *   circle(50, 50, 25);
        - * }
        - *
        - * // Save a 5-second gif when the user presses the 's' key.
        - * // Wait 1 second after the key press before recording.
        - * function keyPressed() {
        - *   if (key === 's') {
        - *     saveGif('mySketch', 5, { delay: 1 });
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.saveGif = async function(
        -  fileName,
        -  duration,
        -  options = {
        -    delay: 0,
        -    units: 'seconds',
        -    silent: false,
        -    notificationDuration: 0,
        -    notificationID: 'progressBar'
        -  }
        -) {
        -  // validate parameters
        -  if (typeof fileName !== 'string') {
        -    throw TypeError('fileName parameter must be a string');
        -  }
        -  if (typeof duration !== 'number') {
        -    throw TypeError('Duration parameter must be a number');
        -  }
        -
        -  // extract variables for more comfortable use
        -  const delay = (options && options.delay) || 0;  // in seconds
        -  const units = (options && options.units) || 'seconds';  // either 'seconds' or 'frames'
        -  const silent = (options && options.silent) || false;
        -  const notificationDuration = (options && options.notificationDuration) || 0;
        -  const notificationID = (options && options.notificationID) || 'progressBar';
        +    }
        +  };
         
        -  // if arguments in the options object are not correct, cancel operation
        -  if (typeof delay !== 'number') {
        -    throw TypeError('Delay parameter must be a number');
        -  }
        -  // if units is not seconds nor frames, throw error
        -  if (units !== 'seconds' && units !== 'frames') {
        -    throw TypeError('Units parameter must be either "frames" or "seconds"');
        -  }
        +  /**
        +   * Generates a gif from a sketch and saves it to a file.
        +   *
        +   * `saveGif()` may be called in <a href="#/p5/setup">setup()</a> or at any
        +   * point while a sketch is running.
        +   *
        +   * The first parameter, `fileName`, sets the gif's file name.
        +   *
        +   * The second parameter, `duration`, sets the gif's duration in seconds.
        +   *
        +   * The third parameter, `options`, is optional. If an object is passed,
        +   * `saveGif()` will use its properties to customize the gif. `saveGif()`
        +   * recognizes the properties `delay`, `units`, `silent`,
        +   * `notificationDuration`, and `notificationID`.
        +   *
        +   * @method saveGif
        +   * @param  {String} filename file name of gif.
        +   * @param  {Number} duration duration in seconds to capture from the sketch.
        +   * @param  {Object} [options] an object that can contain five more properties:
        +   *                  `delay`, a Number specifying how much time to wait before recording;
        +   *                  `units`, a String that can be either 'seconds' or 'frames'. By default it's 'seconds’;
        +   *                  `silent`, a Boolean that defines presence of progress notifications. By default it’s `false`;
        +   *                  `notificationDuration`, a Number that defines how long in seconds the final notification
        +   *                  will live. By default it's `0`, meaning the notification will never be removed;
        +   *                  `notificationID`, a String that specifies the id of the notification's DOM element. By default it’s `'progressBar’`.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A circle drawn in the middle of a gray square. The circle changes color from black to white, then repeats.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the circle.
        +   *   let c = frameCount % 255;
        +   *   fill(c);
        +   *
        +   *   // Display the circle.
        +   *   circle(50, 50, 25);
        +   * }
        +   *
        +   * // Save a 5-second gif when the user presses the 's' key.
        +   * function keyPressed() {
        +   *   if (key === 's') {
        +   *     saveGif('mySketch', 5);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A circle drawn in the middle of a gray square. The circle changes color from black to white, then repeats.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the circle.
        +   *   let c = frameCount % 255;
        +   *   fill(c);
        +   *
        +   *   // Display the circle.
        +   *   circle(50, 50, 25);
        +   * }
        +   *
        +   * // Save a 5-second gif when the user presses the 's' key.
        +   * // Wait 1 second after the key press before recording.
        +   * function keyPressed() {
        +   *   if (key === 's') {
        +   *     saveGif('mySketch', 5, { delay: 1 });
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.saveGif = async function(
        +    fileName,
        +    duration,
        +    options = {
        +      delay: 0,
        +      units: 'seconds',
        +      silent: false,
        +      notificationDuration: 0,
        +      notificationID: 'progressBar'
        +    }
        +  ) {
        +    // validate parameters
        +    if (typeof fileName !== 'string') {
        +      throw TypeError('fileName parameter must be a string');
        +    }
        +    if (typeof duration !== 'number') {
        +      throw TypeError('Duration parameter must be a number');
        +    }
         
        -  if (typeof silent !== 'boolean') {
        -    throw TypeError('Silent parameter must be a boolean');
        -  }
        +    // extract variables for more comfortable use
        +    const delay = (options && options.delay) || 0;  // in seconds
        +    const units = (options && options.units) || 'seconds';  // either 'seconds' or 'frames'
        +    const silent = (options && options.silent) || false;
        +    const notificationDuration = (options && options.notificationDuration) || 0;
        +    const notificationID = (options && options.notificationID) || 'progressBar';
         
        -  if (typeof notificationDuration !== 'number') {
        -    throw TypeError('Notification duration parameter must be a number');
        -  }
        +    // if arguments in the options object are not correct, cancel operation
        +    if (typeof delay !== 'number') {
        +      throw TypeError('Delay parameter must be a number');
        +    }
        +    // if units is not seconds nor frames, throw error
        +    if (units !== 'seconds' && units !== 'frames') {
        +      throw TypeError('Units parameter must be either "frames" or "seconds"');
        +    }
         
        -  if (typeof notificationID !== 'string') {
        -    throw TypeError('Notification ID parameter must be a string');
        -  }
        +    if (typeof silent !== 'boolean') {
        +      throw TypeError('Silent parameter must be a boolean');
        +    }
         
        -  this._recording = true;
        +    if (typeof notificationDuration !== 'number') {
        +      throw TypeError('Notification duration parameter must be a number');
        +    }
         
        -  // get the project's framerate
        -  let _frameRate = this._targetFrameRate;
        -  // if it is undefined or some non useful value, assume it's 60
        -  if (_frameRate === Infinity || _frameRate === undefined || _frameRate === 0) {
        -    _frameRate = 60;
        -  }
        +    if (typeof notificationID !== 'string') {
        +      throw TypeError('Notification ID parameter must be a string');
        +    }
         
        -  // calculate frame delay based on frameRate
        -
        -  // this delay has nothing to do with the
        -  // delay in options, but rather is the delay
        -  // we have to specify to the gif encoder between frames.
        -  let gifFrameDelay = 1 / _frameRate * 1000;
        -
        -  // constrain it to be always greater than 20,
        -  // otherwise it won't work in some browsers and systems
        -  // reference: https://stackoverflow.com/questions/64473278/gif-frame-duration-seems-slower-than-expected
        -  gifFrameDelay = gifFrameDelay < 20 ? 20 : gifFrameDelay;
        -
        -  // check the mode we are in and how many frames
        -  // that duration translates to
        -  const nFrames = units === 'seconds' ? duration * _frameRate : duration;
        -  const nFramesDelay = units === 'seconds' ? delay * _frameRate : delay;
        -  const totalNumberOfFrames = nFrames + nFramesDelay;
        -
        -  // initialize variables for the frames processing
        -  let frameIterator = nFramesDelay;
        -  this.frameCount = frameIterator;
        -
        -  const lastPixelDensity = this._pixelDensity;
        -  this.pixelDensity(1);
        -
        -  // We first take every frame that we are going to use for the animation
        -  let frames = [];
        -
        -  if (document.getElementById(notificationID) !== null)
        -    document.getElementById(notificationID).remove();
        -
        -  let p;
        -  if (!silent){
        -    p = this.createP('');
        -    p.id(notificationID);
        -    p.style('font-size', '16px');
        -    p.style('font-family', 'Montserrat');
        -    p.style('background-color', '#ffffffa0');
        -    p.style('padding', '8px');
        -    p.style('border-radius', '10px');
        -    p.position(0, 0);
        -  }
        +    this._recording = true;
         
        -  let pixels;
        -  let gl;
        -  if (this._renderer instanceof p5.RendererGL) {
        -    // if we have a WEBGL context, initialize the pixels array
        -    // and the gl context to use them inside the loop
        -    gl = this.drawingContext;
        -    pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4);
        -  }
        +    // get the project's framerate
        +    let _frameRate = this._targetFrameRate;
        +    // if it is undefined or some non useful value, assume it's 60
        +    if (_frameRate === Infinity || _frameRate === undefined || _frameRate === 0) {
        +      _frameRate = 60;
        +    }
         
        -  // stop the loop since we are going to manually redraw
        -  this.noLoop();
        -
        -  // Defer execution until the rest of the call stack finishes, allowing the
        -  // rest of `setup` to be called (and, importantly, canvases hidden in setup
        -  // to be unhidden.)
        -  //
        -  // Waiting on this empty promise means we'll continue as soon as setup
        -  // finishes without waiting for another frame.
        -  await Promise.resolve();
        -
        -  while (frameIterator < totalNumberOfFrames) {
        -    /*
        -      we draw the next frame. this is important, since
        -      busy sketches or low end devices might take longer
        -      to render some frames. So we just wait for the frame
        -      to be drawn and immediately save it to a buffer and continue
        -    */
        -    this.redraw();
        -
        -    // depending on the context we'll extract the pixels one way
        -    // or another
        -    let data = undefined;
        +    // calculate frame delay based on frameRate
        +
        +    // this delay has nothing to do with the
        +    // delay in options, but rather is the delay
        +    // we have to specify to the gif encoder between frames.
        +    let gifFrameDelay = 1 / _frameRate * 1000;
        +
        +    // constrain it to be always greater than 20,
        +    // otherwise it won't work in some browsers and systems
        +    // reference: https://stackoverflow.com/questions/64473278/gif-frame-duration-seems-slower-than-expected
        +    gifFrameDelay = gifFrameDelay < 20 ? 20 : gifFrameDelay;
        +
        +    // check the mode we are in and how many frames
        +    // that duration translates to
        +    const nFrames = units === 'seconds' ? duration * _frameRate : duration;
        +    const nFramesDelay = units === 'seconds' ? delay * _frameRate : delay;
        +    const totalNumberOfFrames = nFrames + nFramesDelay;
        +
        +    // initialize variables for the frames processing
        +    let frameIterator = nFramesDelay;
        +    this.frameCount = frameIterator;
        +
        +    const lastPixelDensity = this._renderer._pixelDensity;
        +    this.pixelDensity(1);
        +
        +    // We first take every frame that we are going to use for the animation
        +    let frames = [];
        +
        +    if (document.getElementById(notificationID) !== null)
        +      document.getElementById(notificationID).remove();
        +
        +    let p;
        +    if (!silent){
        +      p = this.createP('');
        +      p.id(notificationID);
        +      p.style('font-size', '16px');
        +      p.style('font-family', 'Montserrat');
        +      p.style('background-color', '#ffffffa0');
        +      p.style('padding', '8px');
        +      p.style('border-radius', '10px');
        +      p.position(0, 0);
        +    }
         
        +    let pixels;
        +    let gl;
             if (this._renderer instanceof p5.RendererGL) {
        -      pixels = new Uint8Array(
        -        gl.drawingBufferWidth * gl.drawingBufferHeight * 4
        -      );
        -      gl.readPixels(
        -        0,
        -        0,
        -        gl.drawingBufferWidth,
        -        gl.drawingBufferHeight,
        -        gl.RGBA,
        -        gl.UNSIGNED_BYTE,
        -        pixels
        -      );
        -
        -      data = _flipPixels(pixels, this.width, this.height);
        -    } else {
        -      data = this.drawingContext.getImageData(0, 0, this.width, this.height)
        -        .data;
        +      // if we have a WEBGL context, initialize the pixels array
        +      // and the gl context to use them inside the loop
        +      gl = this.drawingContext;
        +      pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4);
             }
         
        -    frames.push(data);
        -    frameIterator++;
        +    // stop the loop since we are going to manually redraw
        +    this.noLoop();
         
        -    if (!silent) {
        -      p.html(
        -        'Saved frame <b>' +
        -        frames.length.toString() +
        -        '</b> out of ' +
        -        nFrames.toString()
        -      );
        -    }
        -    await new Promise(resolve => setTimeout(resolve, 0));
        -  }
        -  if (!silent) p.html('Frames processed, generating color palette...');
        -
        -  this.loop();
        -  this.pixelDensity(lastPixelDensity);
        -
        -  // create the gif encoder and the colorspace format
        -  const gif = GIFEncoder();
        -
        -  // calculate the global palette for this set of frames
        -  const globalPalette = _generateGlobalPalette(frames);
        -
        -  // Rather than using applyPalette() from the gifenc library, we use our
        -  // own function to map frame pixels to a palette color. This way, we can
        -  // cache palette color mappings between frames for extra performance, and
        -  // use our own caching mechanism to avoid flickering colors from cache
        -  // key collisions.
        -  const paletteCache = {};
        -  const getIndexedFrame = frame => {
        -    const length = frame.length / 4;
        -    const index = new Uint8Array(length);
        -    for (let i = 0; i < length; i++) {
        -      const key =
        -        (frame[i * 4] << 24) |
        -        (frame[i * 4 + 1] << 16) |
        -        (frame[i * 4 + 2] << 8) |
        -        frame[i * 4 + 3];
        -      if (paletteCache[key] === undefined) {
        -        paletteCache[key] = nearestColorIndex(
        -          globalPalette,
        -          frame.slice(i * 4, (i + 1) * 4)
        +    // Defer execution until the rest of the call stack finishes, allowing the
        +    // rest of `setup` to be called (and, importantly, canvases hidden in setup
        +    // to be unhidden.)
        +    //
        +    // Waiting on this empty promise means we'll continue as soon as setup
        +    // finishes without waiting for another frame.
        +    await Promise.resolve();
        +
        +    while (frameIterator < totalNumberOfFrames) {
        +      /*
        +        we draw the next frame. this is important, since
        +        busy sketches or low end devices might take longer
        +        to render some frames. So we just wait for the frame
        +        to be drawn and immediately save it to a buffer and continue
        +      */
        +      this.redraw();
        +
        +      // depending on the context we'll extract the pixels one way
        +      // or another
        +      let data = undefined;
        +
        +      if (this._renderer instanceof p5.RendererGL) {
        +        pixels = new Uint8Array(
        +          gl.drawingBufferWidth * gl.drawingBufferHeight * 4
        +        );
        +        gl.readPixels(
        +          0,
        +          0,
        +          gl.drawingBufferWidth,
        +          gl.drawingBufferHeight,
        +          gl.RGBA,
        +          gl.UNSIGNED_BYTE,
        +          pixels
                 );
        -      }
        -      index[i] = paletteCache[key];
        -    }
        -    return index;
        -  };
        -
        -  // the way we designed the palette means we always take the last index for transparency
        -  const transparentIndex = globalPalette.length - 1;
         
        -  // we are going to iterate the frames in pairs, n-1 and n
        -  let prevIndexedFrame = [];
        -  for (let i = 0; i < frames.length; i++) {
        -    //const indexedFrame = applyPalette(frames[i], globalPaletteWithoutAlpha, 'rgba565');
        -    const indexedFrame = getIndexedFrame(frames[i]);
        +        data = _flipPixels(pixels, this.width, this.height);
        +      } else {
        +        data = this.drawingContext.getImageData(0, 0, this.width, this.height)
        +          .data;
        +      }
         
        -    // Make a copy of the palette-applied frame before editing the original
        -    // to use transparent pixels
        -    const originalIndexedFrame = indexedFrame.slice();
        +      frames.push(data);
        +      frameIterator++;
         
        -    if (i === 0) {
        -      gif.writeFrame(indexedFrame, this.width, this.height, {
        -        palette: globalPalette,
        -        delay: gifFrameDelay,
        -        dispose: 1
        -      });
        -    } else {
        -      // Matching pixels between frames can be set to full transparency,
        -      // allowing the previous frame's pixels to show through. We only do
        -      // this for pixels that get mapped to the same quantized color so that
        -      // the resulting image would be the same.
        -      for (let i = 0; i < indexedFrame.length; i++) {
        -        if (indexedFrame[i] === prevIndexedFrame[i]) {
        -          indexedFrame[i] = transparentIndex;
        -        }
        +      if (!silent) {
        +        p.html(
        +          'Saved frame <b>' +
        +          frames.length.toString() +
        +          '</b> out of ' +
        +          nFrames.toString()
        +        );
               }
        -
        -      // Write frame into the encoder
        -      gif.writeFrame(indexedFrame, this.width, this.height, {
        -        delay: gifFrameDelay,
        -        transparent: true,
        -        transparentIndex,
        -        dispose: 1
        -      });
        +      await new Promise(resolve => setTimeout(resolve, 0));
             }
        +    if (!silent) p.html('Frames processed, generating color palette...');
        +
        +    this.loop();
        +    this.pixelDensity(lastPixelDensity);
        +
        +    // create the gif encoder and the colorspace format
        +    const gif = GIFEncoder();
        +
        +    // calculate the global palette for this set of frames
        +    const globalPalette = _generateGlobalPalette(frames);
        +
        +    // Rather than using applyPalette() from the gifenc library, we use our
        +    // own function to map frame pixels to a palette color. This way, we can
        +    // cache palette color mappings between frames for extra performance, and
        +    // use our own caching mechanism to avoid flickering colors from cache
        +    // key collisions.
        +    const paletteCache = {};
        +    const getIndexedFrame = frame => {
        +      const length = frame.length / 4;
        +      const index = new Uint8Array(length);
        +      for (let i = 0; i < length; i++) {
        +        const key =
        +          (frame[i * 4] << 24) |
        +          (frame[i * 4 + 1] << 16) |
        +          (frame[i * 4 + 2] << 8) |
        +          frame[i * 4 + 3];
        +        if (paletteCache[key] === undefined) {
        +          paletteCache[key] = nearestColorIndex(
        +            globalPalette,
        +            frame.slice(i * 4, (i + 1) * 4)
        +          );
        +        }
        +        index[i] = paletteCache[key];
        +      }
        +      return index;
        +    };
         
        -    prevIndexedFrame = originalIndexedFrame;
        +    // the way we designed the palette means we always take the last index for transparency
        +    const transparentIndex = globalPalette.length - 1;
        +
        +    // we are going to iterate the frames in pairs, n-1 and n
        +    let prevIndexedFrame = [];
        +    for (let i = 0; i < frames.length; i++) {
        +      //const indexedFrame = applyPalette(frames[i], globalPaletteWithoutAlpha, 'rgba565');
        +      const indexedFrame = getIndexedFrame(frames[i]);
        +
        +      // Make a copy of the palette-applied frame before editing the original
        +      // to use transparent pixels
        +      const originalIndexedFrame = indexedFrame.slice();
        +
        +      if (i === 0) {
        +        gif.writeFrame(indexedFrame, this.width, this.height, {
        +          palette: globalPalette,
        +          delay: gifFrameDelay,
        +          dispose: 1
        +        });
        +      } else {
        +        // Matching pixels between frames can be set to full transparency,
        +        // allowing the previous frame's pixels to show through. We only do
        +        // this for pixels that get mapped to the same quantized color so that
        +        // the resulting image would be the same.
        +        for (let i = 0; i < indexedFrame.length; i++) {
        +          if (indexedFrame[i] === prevIndexedFrame[i]) {
        +            indexedFrame[i] = transparentIndex;
        +          }
        +        }
         
        -    if (!silent) {
        -      p.html(
        -        'Rendered frame <b>' + i.toString() + '</b> out of ' + nFrames.toString()
        -      );
        -    }
        +        // Write frame into the encoder
        +        gif.writeFrame(indexedFrame, this.width, this.height, {
        +          delay: gifFrameDelay,
        +          transparent: true,
        +          transparentIndex,
        +          dispose: 1
        +        });
        +      }
         
        +      prevIndexedFrame = originalIndexedFrame;
         
        -    // this just makes the process asynchronous, preventing
        -    // that the encoding locks up the browser
        -    await new Promise(resolve => setTimeout(resolve, 0));
        -  }
        +      if (!silent) {
        +        p.html(
        +          'Rendered frame <b>' + i.toString() + '</b> out of ' + nFrames.toString()
        +        );
        +      }
         
        -  gif.finish();
         
        -  // Get a direct typed array view into the buffer to avoid copying it
        -  const buffer = gif.bytesView();
        -  const extension = 'gif';
        +      // this just makes the process asynchronous, preventing
        +      // that the encoding locks up the browser
        +      await new Promise(resolve => setTimeout(resolve, 0));
        +    }
         
        -  const blob = new Blob([buffer], {
        -    type: 'image/gif'
        -  });
        +    gif.finish();
         
        -  frames = [];
        -  this._recording = false;
        -  this.loop();
        +    // Get a direct typed array view into the buffer to avoid copying it
        +    const buffer = gif.bytesView();
        +    const extension = 'gif';
         
        -  if (!silent){
        -    p.html('Done. Downloading your gif!🌸');
        -    if(notificationDuration > 0)
        -      setTimeout(() => p.remove(), notificationDuration * 1000);
        -  }
        +    const blob = new Blob([buffer], {
        +      type: 'image/gif'
        +    });
         
        -  p5.prototype.downloadFile(blob, fileName, extension);
        -};
        +    frames = [];
        +    this._recording = false;
        +    this.loop();
         
        -function _flipPixels(pixels, width, height) {
        -  // extracting the pixels using readPixels returns
        -  // an upside down image. we have to flip it back
        -  // first. this solution is proposed by gman on
        -  // this stack overflow answer:
        -  // https://stackoverflow.com/questions/41969562/how-can-i-flip-the-result-of-webglrenderingcontext-readpixels
        +    if (!silent){
        +      p.html('Done. Downloading your gif!🌸');
        +      if(notificationDuration > 0)
        +        setTimeout(() => p.remove(), notificationDuration * 1000);
        +    }
         
        -  const halfHeight = parseInt(height / 2);
        -  const bytesPerRow = width * 4;
        +    fn.downloadFile(blob, fileName, extension);
        +  };
         
        -  // make a temp buffer to hold one row
        -  const temp = new Uint8Array(width * 4);
        -  for (let y = 0; y < halfHeight; ++y) {
        -    const topOffset = y * bytesPerRow;
        -    const bottomOffset = (height - y - 1) * bytesPerRow;
        +  function _flipPixels(pixels, width, height) {
        +    // extracting the pixels using readPixels returns
        +    // an upside down image. we have to flip it back
        +    // first. this solution is proposed by gman on
        +    // this stack overflow answer:
        +    // https://stackoverflow.com/questions/41969562/how-can-i-flip-the-result-of-webglrenderingcontext-readpixels
         
        -    // make copy of a row on the top half
        -    temp.set(pixels.subarray(topOffset, topOffset + bytesPerRow));
        +    const halfHeight = parseInt(height / 2);
        +    const bytesPerRow = width * 4;
         
        -    // copy a row from the bottom half to the top
        -    pixels.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);
        +    // make a temp buffer to hold one row
        +    const temp = new Uint8Array(width * 4);
        +    for (let y = 0; y < halfHeight; ++y) {
        +      const topOffset = y * bytesPerRow;
        +      const bottomOffset = (height - y - 1) * bytesPerRow;
         
        -    // copy the copy of the top half row to the bottom half
        -    pixels.set(temp, bottomOffset);
        -  }
        -  return pixels;
        -}
        +      // make copy of a row on the top half
        +      temp.set(pixels.subarray(topOffset, topOffset + bytesPerRow));
         
        -function _generateGlobalPalette(frames) {
        -  // make an array the size of every possible color in every possible frame
        -  // that is: width * height * frames.
        -  let allColors = new Uint8Array(frames.length * frames[0].length);
        +      // copy a row from the bottom half to the top
        +      pixels.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);
         
        -  // put every frame one after the other in sequence.
        -  // this array will hold absolutely every pixel from the animation.
        -  // the set function on the Uint8Array works super fast tho!
        -  for (let f = 0; f < frames.length; f++) {
        -    allColors.set(frames[f], f * frames[0].length);
        +      // copy the copy of the top half row to the bottom half
        +      pixels.set(temp, bottomOffset);
        +    }
        +    return pixels;
           }
         
        -  // quantize this massive array into 256 colors and return it!
        -  let colorPalette = quantize(allColors, 256, {
        -    format: 'rgba4444',
        -    oneBitAlpha: true
        -  });
        -
        -  // when generating the palette, we have to leave space for 1 of the
        -  // indices to be a random color that does not appear anywhere in our
        -  // animation to use for transparency purposes. So, if the palette is full
        -  // (has 256 colors), we overwrite the last one with a random, fully transparent
        -  // color. Otherwise, we just push a new color into the palette the same way.
        -
        -  // this guarantees that when using the transparency index, there are no matches
        -  // between some colors of the animation and the "holes" we want to dig on them,
        -  // which would cause pieces of some frames to be transparent and thus look glitchy.
        -  if (colorPalette.length === 256) {
        -    colorPalette[colorPalette.length - 1] = [
        -      Math.random() * 255,
        -      Math.random() * 255,
        -      Math.random() * 255,
        -      0
        -    ];
        -  } else {
        -    colorPalette.push([
        -      Math.random() * 255,
        -      Math.random() * 255,
        -      Math.random() * 255,
        -      0
        -    ]);
        -  }
        -  return colorPalette;
        -}
        +  function _generateGlobalPalette(frames) {
        +    // make an array the size of every possible color in every possible frame
        +    // that is: width * height * frames.
        +    let allColors = new Uint8Array(frames.length * frames[0].length);
         
        -/**
        - * Helper function for loading GIF-based images
        - */
        -function _createGif(
        -  arrayBuffer,
        -  pImg,
        -  successCallback,
        -  failureCallback,
        -  finishCallback
        -) {
        -  const gifReader = new omggif.GifReader(arrayBuffer);
        -  pImg.width = pImg.canvas.width = gifReader.width;
        -  pImg.height = pImg.canvas.height = gifReader.height;
        -  const frames = [];
        -  const numFrames = gifReader.numFrames();
        -  let framePixels = new Uint8ClampedArray(pImg.width * pImg.height * 4);
        -  const loadGIFFrameIntoImage = (frameNum, gifReader) => {
        -    try {
        -      gifReader.decodeAndBlitFrameRGBA(frameNum, framePixels);
        -    } catch (e) {
        -      p5._friendlyFileLoadError(8, pImg.src);
        -      if (typeof failureCallback === 'function') {
        -        failureCallback(e);
        -      } else {
        -        console.error(e);
        -      }
        -    }
        -  };
        -  for (let j = 0; j < numFrames; j++) {
        -    const frameInfo = gifReader.frameInfo(j);
        -    const prevFrameData = pImg.drawingContext.getImageData(
        -      0,
        -      0,
        -      pImg.width,
        -      pImg.height
        -    );
        -    framePixels = prevFrameData.data.slice();
        -    loadGIFFrameIntoImage(j, gifReader);
        -    const imageData = new ImageData(framePixels, pImg.width, pImg.height);
        -    pImg.drawingContext.putImageData(imageData, 0, 0);
        -    let frameDelay = frameInfo.delay;
        -    // To maintain the default of 10FPS when frameInfo.delay equals to 0
        -    if (frameDelay === 0) {
        -      frameDelay = 10;
        +    // put every frame one after the other in sequence.
        +    // this array will hold absolutely every pixel from the animation.
        +    // the set function on the Uint8Array works super fast tho!
        +    for (let f = 0; f < frames.length; f++) {
        +      allColors.set(frames[f], f * frames[0].length);
             }
        -    frames.push({
        -      image: pImg.drawingContext.getImageData(0, 0, pImg.width, pImg.height),
        -      delay: frameDelay * 10 //GIF stores delay in one-hundredth of a second, shift to ms
        +
        +    // quantize this massive array into 256 colors and return it!
        +    let colorPalette = quantize(allColors, 256, {
        +      format: 'rgba4444',
        +      oneBitAlpha: true
             });
         
        -    // Some GIFs are encoded so that they expect the previous frame
        -    // to be under the current frame. This can occur at a sub-frame level
        -    //
        -    // Values :    0 -   No disposal specified. The decoder is
        -    //                   not required to take any action.
        -    //             1 -   Do not dispose. The graphic is to be left
        -    //                   in place.
        -    //             2 -   Restore to background color. The area used by the
        -    //                   graphic must be restored to the background color.
        -    //             3 -   Restore to previous. The decoder is required to
        -    //                   restore the area overwritten by the graphic with
        -    //                   what was there prior to rendering the graphic.
        -    //          4-7 -    To be defined.
        -    if (frameInfo.disposal === 2) {
        -      // Restore background color
        -      pImg.drawingContext.clearRect(
        -        frameInfo.x,
        -        frameInfo.y,
        -        frameInfo.width,
        -        frameInfo.height
        -      );
        -    } else if (frameInfo.disposal === 3) {
        -      // Restore previous
        -      pImg.drawingContext.putImageData(
        -        prevFrameData,
        -        0,
        -        0,
        -        frameInfo.x,
        -        frameInfo.y,
        -        frameInfo.width,
        -        frameInfo.height
        -      );
        +    // when generating the palette, we have to leave space for 1 of the
        +    // indices to be a random color that does not appear anywhere in our
        +    // animation to use for transparency purposes. So, if the palette is full
        +    // (has 256 colors), we overwrite the last one with a random, fully transparent
        +    // color. Otherwise, we just push a new color into the palette the same way.
        +
        +    // this guarantees that when using the transparency index, there are no matches
        +    // between some colors of the animation and the "holes" we want to dig on them,
        +    // which would cause pieces of some frames to be transparent and thus look glitchy.
        +    if (colorPalette.length === 256) {
        +      colorPalette[colorPalette.length - 1] = [
        +        Math.random() * 255,
        +        Math.random() * 255,
        +        Math.random() * 255,
        +        0
        +      ];
        +    } else {
        +      colorPalette.push([
        +        Math.random() * 255,
        +        Math.random() * 255,
        +        Math.random() * 255,
        +        0
        +      ]);
             }
        +    return colorPalette;
           }
         
        -  //Uses Netscape block encoding
        -  //to repeat forever, this will be 0
        -  //to repeat just once, this will be null
        -  //to repeat N times (1<N), should contain integer for loop number
        -  //this is changed to more usable values for us
        -  //to repeat forever, loopCount = null
        -  //everything else is just the number of loops
        -  let loopLimit = gifReader.loopCount();
        -  if (loopLimit === null) {
        -    loopLimit = 1;
        -  } else if (loopLimit === 0) {
        -    loopLimit = null;
        -  }
        -
        -  // we used the pImg for painting and saving during load
        -  // so we have to reset it to the first frame
        -  pImg.drawingContext.putImageData(frames[0].image, 0, 0);
        -
        -  if (frames.length > 1) {
        -    pImg.gifProperties = {
        -      displayIndex: 0,
        -      loopLimit,
        -      loopCount: 0,
        -      frames,
        -      numFrames,
        -      playing: true,
        -      timeDisplayed: 0,
        -      lastChangeTime: 0
        +  /**
        +   * Helper function for loading GIF-based images
        +   */
        +  async function _createGif(arrayBuffer, pImg) {
        +    // TODO: Replace with ImageDecoder once it is widely available
        +    // https://developer.mozilla.org/en-US/docs/Web/API/ImageDecoder
        +    const gifReader = new omggif.GifReader(arrayBuffer);
        +    pImg.width = pImg.canvas.width = gifReader.width;
        +    pImg.height = pImg.canvas.height = gifReader.height;
        +    const frames = [];
        +    const numFrames = gifReader.numFrames();
        +    let framePixels = new Uint8ClampedArray(pImg.width * pImg.height * 4);
        +
        +    const loadGIFFrameIntoImage = (frameNum, gifReader) => {
        +      try {
        +        gifReader.decodeAndBlitFrameRGBA(frameNum, framePixels);
        +      } catch (e) {
        +        p5._friendlyFileLoadError(8, pImg.src);
        +        throw e;
        +      }
             };
        -  }
         
        -  if (typeof successCallback === 'function') {
        -    successCallback(pImg);
        -  }
        -  finishCallback();
        -}
        +    for (let j = 0; j < numFrames; j++) {
        +      const frameInfo = gifReader.frameInfo(j);
        +      const prevFrameData = pImg.drawingContext.getImageData(
        +        0,
        +        0,
        +        pImg.width,
        +        pImg.height
        +      );
        +      framePixels = prevFrameData.data.slice();
        +      loadGIFFrameIntoImage(j, gifReader);
        +      const imageData = new ImageData(framePixels, pImg.width, pImg.height);
        +      pImg.drawingContext.putImageData(imageData, 0, 0);
        +      let frameDelay = frameInfo.delay;
        +      // To maintain the default of 10FPS when frameInfo.delay equals to 0
        +      if (frameDelay === 0) {
        +        frameDelay = 10;
        +      }
        +      frames.push({
        +        image: pImg.drawingContext.getImageData(0, 0, pImg.width, pImg.height),
        +        delay: frameDelay * 10 //GIF stores delay in one-hundredth of a second, shift to ms
        +      });
         
        -/**
        - * @private
        - * @param {Constant} xAlign either LEFT, RIGHT or CENTER
        - * @param {Constant} yAlign either TOP, BOTTOM or CENTER
        - * @param {Number} dx
        - * @param {Number} dy
        - * @param {Number} dw
        - * @param {Number} dh
        - * @param {Number} sw
        - * @param {Number} sh
        - * @returns {Object}
        - */
        +      // Some GIFs are encoded so that they expect the previous frame
        +      // to be under the current frame. This can occur at a sub-frame level
        +      //
        +      // Values :    0 -   No disposal specified. The decoder is
        +      //                   not required to take any action.
        +      //             1 -   Do not dispose. The graphic is to be left
        +      //                   in place.
        +      //             2 -   Restore to background color. The area used by the
        +      //                   graphic must be restored to the background color.
        +      //             3 -   Restore to previous. The decoder is required to
        +      //                   restore the area overwritten by the graphic with
        +      //                   what was there prior to rendering the graphic.
        +      //          4-7 -    To be defined.
        +      if (frameInfo.disposal === 2) {
        +        // Restore background color
        +        pImg.drawingContext.clearRect(
        +          frameInfo.x,
        +          frameInfo.y,
        +          frameInfo.width,
        +          frameInfo.height
        +        );
        +      } else if (frameInfo.disposal === 3) {
        +        // Restore previous
        +        pImg.drawingContext.putImageData(
        +          prevFrameData,
        +          0,
        +          0,
        +          frameInfo.x,
        +          frameInfo.y,
        +          frameInfo.width,
        +          frameInfo.height
        +        );
        +      }
        +    }
         
        -function _imageContain(xAlign, yAlign, dx, dy, dw, dh, sw, sh) {
        -  const r = Math.max(sw / dw, sh / dh);
        -  const [adjusted_dw, adjusted_dh] = [sw / r, sh / r];
        -  let x = dx;
        -  let y = dy;
        +    //Uses Netscape block encoding
        +    //to repeat forever, this will be 0
        +    //to repeat just once, this will be null
        +    //to repeat N times (1<N), should contain integer for loop number
        +    //this is changed to more usable values for us
        +    //to repeat forever, loopCount = null
        +    //everything else is just the number of loops
        +    let loopLimit = gifReader.loopCount();
        +    if (loopLimit === null) {
        +      loopLimit = 1;
        +    } else if (loopLimit === 0) {
        +      loopLimit = null;
        +    }
         
        -  if (xAlign === constants.CENTER) {
        -    x += (dw - adjusted_dw) / 2;
        -  } else if (xAlign === constants.RIGHT) {
        -    x += dw - adjusted_dw;
        -  }
        +    // we used the pImg for painting and saving during load
        +    // so we have to reset it to the first frame
        +    pImg.drawingContext.putImageData(frames[0].image, 0, 0);
        +
        +    if (frames.length > 1) {
        +      pImg.gifProperties = {
        +        displayIndex: 0,
        +        loopLimit,
        +        loopCount: 0,
        +        frames,
        +        numFrames,
        +        playing: true,
        +        timeDisplayed: 0,
        +        lastChangeTime: 0
        +      };
        +    }
         
        -  if (yAlign === constants.CENTER) {
        -    y += (dh - adjusted_dh) / 2;
        -  } else if (yAlign === constants.BOTTOM) {
        -    y += dh - adjusted_dh;
        +    return pImg;
           }
        -  return { x, y, w: adjusted_dw, h: adjusted_dh };
        -}
        -
        -/**
        - * @private
        - * @param {Constant} xAlign either LEFT, RIGHT or CENTER
        - * @param {Constant} yAlign either TOP, BOTTOM or CENTER
        - * @param {Number} dw
        - * @param {Number} dh
        - * @param {Number} sx
        - * @param {Number} sy
        - * @param {Number} sw
        - * @param {Number} sh
        - * @returns {Object}
        - */
        -function _imageCover(xAlign, yAlign, dw, dh, sx, sy, sw, sh) {
        -  const r = Math.max(dw / sw, dh / sh);
        -  const [adjusted_sw, adjusted_sh] = [dw / r, dh / r];
        -
        -  let x = sx;
        -  let y = sy;
         
        -  if (xAlign === constants.CENTER) {
        -    x += (sw - adjusted_sw) / 2;
        -  } else if (xAlign === constants.RIGHT) {
        -    x += sw - adjusted_sw;
        -  }
        +  /**
        +   * @private
        +   * @param {(LEFT|RIGHT|CENTER)} xAlign either LEFT, RIGHT or CENTER
        +   * @param {(TOP|BOTTOM|CENTER)} yAlign either TOP, BOTTOM or CENTER
        +   * @param {Number} dx
        +   * @param {Number} dy
        +   * @param {Number} dw
        +   * @param {Number} dh
        +   * @param {Number} sw
        +   * @param {Number} sh
        +   * @returns {Object}
        +   */
        +
        +  function _imageContain(xAlign, yAlign, dx, dy, dw, dh, sw, sh) {
        +    const r = Math.max(sw / dw, sh / dh);
        +    const [adjusted_dw, adjusted_dh] = [sw / r, sh / r];
        +    let x = dx;
        +    let y = dy;
        +
        +    if (xAlign === constants.CENTER) {
        +      x += (dw - adjusted_dw) / 2;
        +    } else if (xAlign === constants.RIGHT) {
        +      x += dw - adjusted_dw;
        +    }
         
        -  if (yAlign === constants.CENTER) {
        -    y += (sh - adjusted_sh) / 2;
        -  } else if (yAlign === constants.BOTTOM) {
        -    y += sh - adjusted_sh;
        +    if (yAlign === constants.CENTER) {
        +      y += (dh - adjusted_dh) / 2;
        +    } else if (yAlign === constants.BOTTOM) {
        +      y += dh - adjusted_dh;
        +    }
        +    return { x, y, w: adjusted_dw, h: adjusted_dh };
           }
         
        -  return { x, y, w: adjusted_sw, h: adjusted_sh };
        -}
        +  /**
        +   * @private
        +   * @param {(LEFT|RIGHT|CENTER)} xAlign either LEFT, RIGHT or CENTER
        +   * @param {(TOP|BOTTOM|CENTER)} yAlign either TOP, BOTTOM or CENTER
        +   * @param {Number} dw
        +   * @param {Number} dh
        +   * @param {Number} sx
        +   * @param {Number} sy
        +   * @param {Number} sw
        +   * @param {Number} sh
        +   * @returns {Object}
        +   */
        +  function _imageCover(xAlign, yAlign, dw, dh, sx, sy, sw, sh) {
        +    const r = Math.max(dw / sw, dh / sh);
        +    const [adjusted_sw, adjusted_sh] = [dw / r, dh / r];
        +
        +    let x = sx;
        +    let y = sy;
        +
        +    if (xAlign === constants.CENTER) {
        +      x += (sw - adjusted_sw) / 2;
        +    } else if (xAlign === constants.RIGHT) {
        +      x += sw - adjusted_sw;
        +    }
         
        -/**
        - * @private
        - * @param {Constant} [fit] either CONTAIN or COVER
        - * @param {Constant} xAlign either LEFT, RIGHT or CENTER
        - * @param {Constant} yAlign either TOP, BOTTOM or CENTER
        - * @param {Number} dx
        - * @param {Number} dy
        - * @param {Number} dw
        - * @param {Number} dh
        - * @param {Number} sx
        - * @param {Number} sy
        - * @param {Number} sw
        - * @param {Number} sh
        - * @returns {Object}
        - */
        -function _imageFit(fit, xAlign, yAlign, dx, dy, dw, dh, sx, sy, sw, sh) {
        -  if (fit === constants.COVER) {
        -    const { x, y, w, h } = _imageCover(xAlign, yAlign, dw, dh, sx, sy, sw, sh);
        -    sx = x;
        -    sy = y;
        -    sw = w;
        -    sh = h;
        -  }
        +    if (yAlign === constants.CENTER) {
        +      y += (sh - adjusted_sh) / 2;
        +    } else if (yAlign === constants.BOTTOM) {
        +      y += sh - adjusted_sh;
        +    }
         
        -  if (fit === constants.CONTAIN) {
        -    const { x, y, w, h } = _imageContain(
        -      xAlign,
        -      yAlign,
        -      dx,
        -      dy,
        -      dw,
        -      dh,
        -      sw,
        -      sh
        -    );
        -    dx = x;
        -    dy = y;
        -    dw = w;
        -    dh = h;
        +    return { x, y, w: adjusted_sw, h: adjusted_sh };
           }
        -  return { sx, sy, sw, sh, dx, dy, dw, dh };
        -}
         
        -/**
        - * Validates clipping params. Per drawImage spec sWidth and sHight cannot be
        - * negative or greater than image intrinsic width and height
        - * @private
        - * @param {Number} sVal
        - * @param {Number} iVal
        - * @returns {Number}
        - * @private
        - */
        -function _sAssign(sVal, iVal) {
        -  if (sVal > 0 && sVal < iVal) {
        -    return sVal;
        -  } else {
        -    return iVal;
        -  }
        -}
        +  /**
        +   * @private
        +   * @param {(CONTAIN|COVER)} [fit] either CONTAIN or COVER
        +   * @param {(LEFT|RIGHT|CENTER)} xAlign either LEFT, RIGHT or CENTER
        +   * @param {(TOP|BOTTOM|CENTER)} yAlign either TOP, BOTTOM or CENTER
        +   * @param {Number} dx
        +   * @param {Number} dy
        +   * @param {Number} dw
        +   * @param {Number} dh
        +   * @param {Number} sx
        +   * @param {Number} sy
        +   * @param {Number} sw
        +   * @param {Number} sh
        +   * @returns {Object}
        +   */
        +  function _imageFit(fit, xAlign, yAlign, dx, dy, dw, dh, sx, sy, sw, sh) {
        +    if (fit === constants.COVER) {
        +      const { x, y, w, h } = _imageCover(xAlign, yAlign, dw, dh, sx, sy, sw, sh);
        +      sx = x;
        +      sy = y;
        +      sw = w;
        +      sh = h;
        +    }
         
        -/**
        - * Draws an image to the canvas.
        - *
        - * The first parameter, `img`, is the source image to be drawn. `img` can be
        - * any of the following objects:
        - * - <a href="#/p5.Image">p5.Image</a>
        - * - <a href="#/p5.Element">p5.Element</a>
        - * - <a href="#/p5.Texture">p5.Texture</a>
        - * - <a href="#/p5.Framebuffer">p5.Framebuffer</a>
        - * - <a href="#/p5.FramebufferTexture">p5.FramebufferTexture</a>
        - *
        - * The second and third parameters, `dx` and `dy`, set the coordinates of the
        - * destination image's top left corner. See
        - * <a href="#/p5/imageMode">imageMode()</a> for other ways to position images.
        - *
        - * Here's a diagram that explains how optional parameters work in `image()`:
        - *
        - * <img src="assets/drawImage.png"></img>
        - *
        - * The fourth and fifth parameters, `dw` and `dh`, are optional. They set the
        - * the width and height to draw the destination image. By default, `image()`
        - * draws the full source image at its original size.
        - *
        - * The sixth and seventh parameters, `sx` and `sy`, are also optional.
        - * These coordinates define the top left corner of a subsection to draw from
        - * the source image.
        - *
        - * The eighth and ninth parameters, `sw` and `sh`, are also optional.
        - * They define the width and height of a subsection to draw from the source
        - * image. By default, `image()` draws the full subsection that begins at
        - * `(sx, sy)` and extends to the edges of the source image.
        - *
        - * The ninth parameter, `fit`, is also optional. It enables a subsection of
        - * the source image to be drawn without affecting its aspect ratio. If
        - * `CONTAIN` is passed, the full subsection will appear within the destination
        - * rectangle. If `COVER` is passed, the subsection will completely cover the
        - * destination rectangle. This may have the effect of zooming into the
        - * subsection.
        - *
        - * The tenth and eleventh paremeters, `xAlign` and `yAlign`, are also
        - * optional. They determine how to align the fitted subsection. `xAlign` can
        - * be set to either `LEFT`, `RIGHT`, or `CENTER`. `yAlign` can be set to
        - * either `TOP`, `BOTTOM`, or `CENTER`. By default, both `xAlign` and `yAlign`
        - * are set to `CENTER`.
        - *
        - * @method image
        - * @param  {p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture} img image to display.
        - * @param  {Number}   x x-coordinate of the top-left corner of the image.
        - * @param  {Number}   y y-coordinate of the top-left corner of the image.
        - * @param  {Number}   [width]  width to draw the image.
        - * @param  {Number}   [height] height to draw the image.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Draw the image.
        - *   image(img, 0, 0);
        - *
        - *   describe('An image of the underside of a white umbrella with a gridded ceiling above.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Draw the image.
        - *   image(img, 10, 10);
        - *
        - *   describe('An image of the underside of a white umbrella with a gridded ceiling above. The image has dark gray borders on its left and top.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Draw the image 50x50.
        - *   image(img, 0, 0, 50, 50);
        - *
        - *   describe('An image of the underside of a white umbrella with a gridded ceiling above. The image is drawn in the top left corner of a dark gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Draw the center of the image.
        - *   image(img, 25, 25, 50, 50, 25, 25, 50, 50);
        - *
        - *   describe('An image of a gridded ceiling drawn in the center of a dark gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/moonwalk.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Draw the image and scale it to fit within the canvas.
        - *   image(img, 0, 0, width, height, 0, 0, img.width, img.height, CONTAIN);
        - *
        - *   describe('An image of an astronaut on the moon. The top and bottom borders of the image are dark gray.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   // Image is 50 x 50 pixels.
        - *   img = loadImage('assets/laDefense50.png');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Draw the image and scale it to cover the canvas.
        - *   image(img, 0, 0, width, height, 0, 0, img.width, img.height, COVER);
        - *
        - *   describe('A pixelated image of the underside of a white umbrella with a gridded ceiling above.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method image
        - * @param  {p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture} img
        - * @param  {Number}   dx     the x-coordinate of the destination
        - *                           rectangle in which to draw the source image
        - * @param  {Number}   dy     the y-coordinate of the destination
        - *                           rectangle in which to draw the source image
        - * @param  {Number}   dWidth  the width of the destination rectangle
        - * @param  {Number}   dHeight the height of the destination rectangle
        - * @param  {Number}   sx     the x-coordinate of the subsection of the source
        - * image to draw into the destination rectangle
        - * @param  {Number}   sy     the y-coordinate of the subsection of the source
        - * image to draw into the destination rectangle
        - * @param {Number}    [sWidth] the width of the subsection of the
        - *                           source image to draw into the destination
        - *                           rectangle
        - * @param {Number}    [sHeight] the height of the subsection of the
        - *                            source image to draw into the destination rectangle
        - * @param {Constant} [fit] either CONTAIN or COVER
        - * @param {Constant} [xAlign] either LEFT, RIGHT or CENTER default is CENTER
        - * @param {Constant} [yAlign] either TOP, BOTTOM or CENTER default is CENTER
        - */
        -p5.prototype.image = function(
        -  img,
        -  dx,
        -  dy,
        -  dWidth,
        -  dHeight,
        -  sx,
        -  sy,
        -  sWidth,
        -  sHeight,
        -  fit,
        -  xAlign,
        -  yAlign
        -) {
        -  // set defaults per spec: https://goo.gl/3ykfOq
        -
        -  p5._validateParameters('image', arguments);
        -
        -  let defW = img.width;
        -  let defH = img.height;
        -  yAlign = yAlign || constants.CENTER;
        -  xAlign = xAlign || constants.CENTER;
        -
        -  if (img.elt) {
        -    defW = defW !== undefined ? defW : img.elt.width;
        -    defH = defH !== undefined ? defH : img.elt.height;
        -  }
        -  if (img.elt && img.elt.videoWidth && !img.canvas) {
        -    // video no canvas
        -    defW = defW !== undefined ? defW : img.elt.videoWidth;
        -    defH = defH !== undefined ? defH : img.elt.videoHeight;
        +    if (fit === constants.CONTAIN) {
        +      const { x, y, w, h } = _imageContain(
        +        xAlign,
        +        yAlign,
        +        dx,
        +        dy,
        +        dw,
        +        dh,
        +        sw,
        +        sh
        +      );
        +      dx = x;
        +      dy = y;
        +      dw = w;
        +      dh = h;
        +    }
        +    return { sx, sy, sw, sh, dx, dy, dw, dh };
           }
         
        -  let _dx = dx;
        -  let _dy = dy;
        -  let _dw = dWidth || defW;
        -  let _dh = dHeight || defH;
        -  let _sx = sx || 0;
        -  let _sy = sy || 0;
        -  let _sw = sWidth !== undefined ? sWidth : defW;
        -  let _sh = sHeight !== undefined ? sHeight : defH;
        -
        -  _sw = _sAssign(_sw, defW);
        -  _sh = _sAssign(_sh, defH);
        -
        -  // This part needs cleanup and unit tests
        -  // see issues https://github.com/processing/p5.js/issues/1741
        -  // and https://github.com/processing/p5.js/issues/1673
        -  let pd = 1;
        -
        -  if (img.elt && !img.canvas && img.elt.style.width) {
        -    //if img is video and img.elt.size() has been used and
        -    //no width passed to image()
        -    if (img.elt.videoWidth && !dWidth) {
        -      pd = img.elt.videoWidth;
        +  /**
        +   * Validates clipping params. Per drawImage spec sWidth and sHight cannot be
        +   * negative or greater than image intrinsic width and height
        +   * @private
        +   * @param {Number} sVal
        +   * @param {Number} iVal
        +   * @returns {Number}
        +   * @private
        +   */
        +  function _sAssign(sVal, iVal) {
        +    if (sVal > 0 && sVal < iVal) {
        +      return sVal;
             } else {
        -      //all other cases
        -      pd = img.elt.width;
        +      return iVal;
             }
        -    pd /= parseInt(img.elt.style.width, 10);
           }
         
        -  _sx *= pd;
        -  _sy *= pd;
        -  _sh *= pd;
        -  _sw *= pd;
        -
        -  let vals = canvas.modeAdjust(_dx, _dy, _dw, _dh, this._renderer._imageMode);
        -  vals = _imageFit(
        +  /**
        +   * Draws an image to the canvas.
        +   *
        +   * The first parameter, `img`, is the source image to be drawn. `img` can be
        +   * any of the following objects:
        +   * - <a href="#/p5.Image">p5.Image</a>
        +   * - <a href="#/p5.Element">p5.Element</a>
        +   * - <a href="#/p5.Texture">p5.Texture</a>
        +   * - <a href="#/p5.Framebuffer">p5.Framebuffer</a>
        +   * - <a href="#/p5.FramebufferTexture">p5.FramebufferTexture</a>
        +   *
        +   * The second and third parameters, `dx` and `dy`, set the coordinates of the
        +   * destination image's top left corner. See
        +   * <a href="#/p5/imageMode">imageMode()</a> for other ways to position images.
        +   *
        +   * Here's a diagram that explains how optional parameters work in `image()`:
        +   *
        +   * <img src="assets/drawImage.png"></img>
        +   *
        +   * The fourth and fifth parameters, `dw` and `dh`, are optional. They set the
        +   * the width and height to draw the destination image. By default, `image()`
        +   * draws the full source image at its original size.
        +   *
        +   * The sixth and seventh parameters, `sx` and `sy`, are also optional.
        +   * These coordinates define the top left corner of a subsection to draw from
        +   * the source image.
        +   *
        +   * The eighth and ninth parameters, `sw` and `sh`, are also optional.
        +   * They define the width and height of a subsection to draw from the source
        +   * image. By default, `image()` draws the full subsection that begins at
        +   * `(sx, sy)` and extends to the edges of the source image.
        +   *
        +   * The ninth parameter, `fit`, is also optional. It enables a subsection of
        +   * the source image to be drawn without affecting its aspect ratio. If
        +   * `CONTAIN` is passed, the full subsection will appear within the destination
        +   * rectangle. If `COVER` is passed, the subsection will completely cover the
        +   * destination rectangle. This may have the effect of zooming into the
        +   * subsection.
        +   *
        +   * The tenth and eleventh paremeters, `xAlign` and `yAlign`, are also
        +   * optional. They determine how to align the fitted subsection. `xAlign` can
        +   * be set to either `LEFT`, `RIGHT`, or `CENTER`. `yAlign` can be set to
        +   * either `TOP`, `BOTTOM`, or `CENTER`. By default, both `xAlign` and `yAlign`
        +   * are set to `CENTER`.
        +   *
        +   * @method image
        +   * @param  {p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture} img image to display.
        +   * @param  {Number}   x x-coordinate of the top-left corner of the image.
        +   * @param  {Number}   y y-coordinate of the top-left corner of the image.
        +   * @param  {Number}   [width]  width to draw the image.
        +   * @param  {Number}   [height] height to draw the image.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Draw the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   describe('An image of the underside of a white umbrella with a gridded ceiling above.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Draw the image.
        +   *   image(img, 10, 10);
        +   *
        +   *   describe('An image of the underside of a white umbrella with a gridded ceiling above. The image has dark gray borders on its left and top.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Draw the image 50x50.
        +   *   image(img, 0, 0, 50, 50);
        +   *
        +   *   describe('An image of the underside of a white umbrella with a gridded ceiling above. The image is drawn in the top left corner of a dark gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Draw the center of the image.
        +   *   image(img, 25, 25, 50, 50, 25, 25, 50, 50);
        +   *
        +   *   describe('An image of a gridded ceiling drawn in the center of a dark gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/moonwalk.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Draw the image and scale it to fit within the canvas.
        +   *   image(img, 0, 0, width, height, 0, 0, img.width, img.height, CONTAIN);
        +   *
        +   *   describe('An image of an astronaut on the moon. The top and bottom borders of the image are dark gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   // Image is 50 x 50 pixels.
        +   *   img = loadImage('assets/laDefense50.png');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Draw the image and scale it to cover the canvas.
        +   *   image(img, 0, 0, width, height, 0, 0, img.width, img.height, COVER);
        +   *
        +   *   describe('A pixelated image of the underside of a white umbrella with a gridded ceiling above.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method image
        +   * @param  {p5.Image|p5.Element|p5.Texture|p5.Framebuffer|p5.FramebufferTexture} img
        +   * @param  {Number}   dx     the x-coordinate of the destination
        +   *                           rectangle in which to draw the source image
        +   * @param  {Number}   dy     the y-coordinate of the destination
        +   *                           rectangle in which to draw the source image
        +   * @param  {Number}   dWidth  the width of the destination rectangle
        +   * @param  {Number}   dHeight the height of the destination rectangle
        +   * @param  {Number}   sx     the x-coordinate of the subsection of the source
        +   * image to draw into the destination rectangle
        +   * @param  {Number}   sy     the y-coordinate of the subsection of the source
        +   * image to draw into the destination rectangle
        +   * @param {Number}    [sWidth] the width of the subsection of the
        +   *                           source image to draw into the destination
        +   *                           rectangle
        +   * @param {Number}    [sHeight] the height of the subsection of the
        +   *                            source image to draw into the destination rectangle
        +   * @param {(CONTAIN|COVER)} [fit] either CONTAIN or COVER
        +   * @param {(LEFT|RIGHT|CENTER)} [xAlign=CENTER] either LEFT, RIGHT or CENTER default is CENTER
        +   * @param {(TOP|BOTTOM|CENTER)} [yAlign=CENTER] either TOP, BOTTOM or CENTER default is CENTER
        +   */
        +  fn.image = function(
        +    img,
        +    dx,
        +    dy,
        +    dWidth,
        +    dHeight,
        +    sx,
        +    sy,
        +    sWidth,
        +    sHeight,
             fit,
             xAlign,
        -    yAlign,
        -    vals.x,
        -    vals.y,
        -    vals.w,
        -    vals.h,
        -    _sx,
        -    _sy,
        -    _sw,
        -    _sh
        -  );
        -
        -  // tint the image if there is a tint
        -  this._renderer.image(
        -    img,
        -    vals.sx,
        -    vals.sy,
        -    vals.sw,
        -    vals.sh,
        -    vals.dx,
        -    vals.dy,
        -    vals.dw,
        -    vals.dh
        -  );
        -};
        +    yAlign
        +  ) {
        +    // set defaults per spec: https://goo.gl/3ykfOq
         
        -/**
        - * Tints images using a color.
        - *
        - * The version of `tint()` with one parameter interprets it one of four ways.
        - * If the parameter is a number, it's interpreted as a grayscale value. If the
        - * parameter is a string, it's interpreted as a CSS color string. An array of
        - * `[R, G, B, A]` values or a <a href="#/p5.Color">p5.Color</a> object can
        - * also be used to set the tint color.
        - *
        - * The version of `tint()` with two parameters uses the first one as a
        - * grayscale value and the second as an alpha value. For example, calling
        - * `tint(255, 128)` will make an image 50% transparent.
        - *
        - * The version of `tint()` with three parameters interprets them as RGB or
        - * HSB values, depending on the current
        - * <a href="#/p5/colorMode">colorMode()</a>. The optional fourth parameter
        - * sets the alpha value. For example, `tint(255, 0, 0, 100)` will give images
        - * a red tint and make them transparent.
        - *
        - * @method tint
        - * @param  {Number}        v1      red or hue value.
        - * @param  {Number}        v2      green or saturation value.
        - * @param  {Number}        v3      blue or brightness.
        - * @param  {Number}        [alpha]
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Left image.
        - *   image(img, 0, 0);
        - *
        - *   // Right image.
        - *   // Tint with a CSS color string.
        - *   tint('red');
        - *   image(img, 50, 0);
        - *
        - *   describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a red tint.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Left image.
        - *   image(img, 0, 0);
        - *
        - *   // Right image.
        - *   // Tint with RGB values.
        - *   tint(255, 0, 0);
        - *   image(img, 50, 0);
        - *
        - *   describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a red tint.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Left.
        - *   image(img, 0, 0);
        - *
        - *   // Right.
        - *   // Tint with RGBA values.
        - *   tint(255, 0, 0, 100);
        - *   image(img, 50, 0);
        - *
        - *   describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a transparent red tint.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Left.
        - *   image(img, 0, 0);
        - *
        - *   // Right.
        - *   // Tint with grayscale and alpha values.
        - *   tint(255, 180);
        - *   image(img, 50, 0);
        - *
        - *   describe('Two images of an umbrella and a ceiling side-by-side. The image on the right is transparent.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method tint
        - * @param  {String}        value   CSS color string.
        - */
        +    // p5._validateParameters('image', arguments);
         
        -/**
        - * @method tint
        - * @param  {Number}        gray   grayscale value.
        - * @param  {Number}        [alpha]
        - */
        +    let defW = img.width;
        +    let defH = img.height;
        +    yAlign = yAlign || constants.CENTER;
        +    xAlign = xAlign || constants.CENTER;
         
        -/**
        - * @method tint
        - * @param  {Number[]}      values  array containing the red, green, blue &
        - *                                 alpha components of the color.
        - */
        +    if (img.elt) {
        +      defW = defW !== undefined ? defW : img.elt.width;
        +      defH = defH !== undefined ? defH : img.elt.height;
        +    }
        +    if (img.elt && img.elt.videoWidth && !img.canvas) {
        +      // video no canvas
        +      defW = defW !== undefined ? defW : img.elt.videoWidth;
        +      defH = defH !== undefined ? defH : img.elt.videoHeight;
        +    }
         
        -/**
        - * @method tint
        - * @param  {p5.Color}      color   the tint color
        - */
        -p5.prototype.tint = function(...args) {
        -  p5._validateParameters('tint', args);
        -  const c = this.color(...args);
        -  this._renderer._tint = c.levels;
        -};
        +    let _dx = dx;
        +    let _dy = dy;
        +    let _dw = dWidth || defW;
        +    let _dh = dHeight || defH;
        +    let _sx = sx || 0;
        +    let _sy = sy || 0;
        +    let _sw = sWidth !== undefined ? sWidth : defW;
        +    let _sh = sHeight !== undefined ? sHeight : defH;
        +
        +    _sw = _sAssign(_sw, defW);
        +    _sh = _sAssign(_sh, defH);
        +
        +    // This part needs cleanup and unit tests
        +    // see issues https://github.com/processing/p5.js/issues/1741
        +    // and https://github.com/processing/p5.js/issues/1673
        +    let pd = 1;
        +
        +    if (img.elt && !img.canvas && img.elt.style.width) {
        +      //if img is video and img.elt.size() has been used and
        +      //no width passed to image()
        +      if (img.elt.videoWidth && !dWidth) {
        +        pd = img.elt.videoWidth;
        +      } else {
        +        //all other cases
        +        pd = img.elt.width;
        +      }
        +      pd /= parseInt(img.elt.style.width, 10);
        +    }
         
        -/**
        - * Removes the current tint set by <a href="#/p5/tint">tint()</a>.
        - *
        - * `noTint()` restores images to their original colors.
        - *
        - * @method noTint
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Left.
        - *   // Tint with a CSS color string.
        - *   tint('red');
        - *   image(img, 0, 0);
        - *
        - *   // Right.
        - *   // Remove the tint.
        - *   noTint();
        - *   image(img, 50, 0);
        - *
        - *   describe('Two images of an umbrella and a ceiling side-by-side. The image on the left has a red tint.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noTint = function() {
        -  this._renderer._tint = null;
        -};
        +    _sx *= pd;
        +    _sy *= pd;
        +    _sh *= pd;
        +    _sw *= pd;
         
        -/**
        - * Apply the current tint color to the input image, return the resulting
        - * canvas.
        - *
        - * @private
        - * @param {p5.Image} The image to be tinted
        - * @return {canvas} The resulting tinted canvas
        - */
        -p5.prototype._getTintedImageCanvas =
        -  p5.Renderer2D.prototype._getTintedImageCanvas;
        +    let vals = canvas.modeAdjust(_dx, _dy, _dw, _dh, this._renderer.states.imageMode);
        +    vals = _imageFit(
        +      fit,
        +      xAlign,
        +      yAlign,
        +      vals.x,
        +      vals.y,
        +      vals.w,
        +      vals.h,
        +      _sx,
        +      _sy,
        +      _sw,
        +      _sh
        +    );
         
        -/**
        - * Changes the location from which images are drawn when
        - * <a href="#/p5/image">image()</a> is called.
        - *
        - * By default, the first
        - * two parameters of <a href="#/p5/image">image()</a> are the x- and
        - * y-coordinates of the image's upper-left corner. The next parameters are
        - * its width and height. This is the same as calling `imageMode(CORNER)`.
        - *
        - * `imageMode(CORNERS)` also uses the first two parameters of
        - * <a href="#/p5/image">image()</a> as the x- and y-coordinates of the image's
        - * top-left corner. The third and fourth parameters are the coordinates of its
        - * bottom-right corner.
        - *
        - * `imageMode(CENTER)` uses the first two parameters of
        - * <a href="#/p5/image">image()</a> as the x- and y-coordinates of the image's
        - * center. The next parameters are its width and height.
        - *
        - * @method imageMode
        - * @param {Constant} mode either CORNER, CORNERS, or CENTER.
        - *
        - * @example
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use CORNER mode.
        - *   imageMode(CORNER);
        - *
        - *   // Display the image.
        - *   image(img, 10, 10, 50, 50);
        - *
        - *   describe('A square image of a brick wall is drawn at the top left of a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use CORNERS mode.
        - *   imageMode(CORNERS);
        - *
        - *   // Display the image.
        - *   image(img, 10, 10, 90, 40);
        - *
        - *   describe('An image of a brick wall is drawn on a gray square. The image is squeezed into a small rectangular area.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use CENTER mode.
        - *   imageMode(CENTER);
        - *
        - *   // Display the image.
        - *   image(img, 50, 50, 80, 80);
        - *
        - *   describe('A square image of a brick wall is drawn on a gray square.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.imageMode = function(m) {
        -  p5._validateParameters('imageMode', arguments);
        -  if (
        -    m === constants.CORNER ||
        -    m === constants.CORNERS ||
        -    m === constants.CENTER
        -  ) {
        -    this._renderer._imageMode = m;
        -  }
        -};
        +    // tint the image if there is a tint
        +    this._renderer.image(
        +      img,
        +      vals.sx,
        +      vals.sy,
        +      vals.sw,
        +      vals.sh,
        +      vals.dx,
        +      vals.dy,
        +      vals.dw,
        +      vals.dh
        +    );
        +  };
        +
        +  /**
        +   * Tints images using a color.
        +   *
        +   * The version of `tint()` with one parameter interprets it one of four ways.
        +   * If the parameter is a number, it's interpreted as a grayscale value. If the
        +   * parameter is a string, it's interpreted as a CSS color string. An array of
        +   * `[R, G, B, A]` values or a <a href="#/p5.Color">p5.Color</a> object can
        +   * also be used to set the tint color.
        +   *
        +   * The version of `tint()` with two parameters uses the first one as a
        +   * grayscale value and the second as an alpha value. For example, calling
        +   * `tint(255, 128)` will make an image 50% transparent.
        +   *
        +   * The version of `tint()` with three parameters interprets them as RGB or
        +   * HSB values, depending on the current
        +   * <a href="#/p5/colorMode">colorMode()</a>. The optional fourth parameter
        +   * sets the alpha value. For example, `tint(255, 0, 0, 100)` will give images
        +   * a red tint and make them transparent.
        +   *
        +   * @method tint
        +   * @param  {Number}        v1      red or hue value.
        +   * @param  {Number}        v2      green or saturation value.
        +   * @param  {Number}        v3      blue or brightness.
        +   * @param  {Number}        [alpha]
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Left image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Right image.
        +   *   // Tint with a CSS color string.
        +   *   tint('red');
        +   *   image(img, 50, 0);
        +   *
        +   *   describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a red tint.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Left image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Right image.
        +   *   // Tint with RGB values.
        +   *   tint(255, 0, 0);
        +   *   image(img, 50, 0);
        +   *
        +   *   describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a red tint.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Left.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Right.
        +   *   // Tint with RGBA values.
        +   *   tint(255, 0, 0, 100);
        +   *   image(img, 50, 0);
        +   *
        +   *   describe('Two images of an umbrella and a ceiling side-by-side. The image on the right has a transparent red tint.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Left.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Right.
        +   *   // Tint with grayscale and alpha values.
        +   *   tint(255, 180);
        +   *   image(img, 50, 0);
        +   *
        +   *   describe('Two images of an umbrella and a ceiling side-by-side. The image on the right is transparent.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method tint
        +   * @param  {String}        value   CSS color string.
        +   */
        +
        +  /**
        +   * @method tint
        +   * @param  {Number}        gray   grayscale value.
        +   * @param  {Number}        [alpha]
        +   */
        +
        +  /**
        +   * @method tint
        +   * @param  {Number[]}      values  array containing the red, green, blue &
        +   *                                 alpha components of the color.
        +   */
        +
        +  /**
        +   * @method tint
        +   * @param  {p5.Color}      color   the tint color
        +   */
        +  fn.tint = function(...args) {
        +    // p5._validateParameters('tint', args);
        +    const c = this.color(...args);
        +    this._renderer.states.tint = c._getRGBA([255, 255, 255, 255]);
        +  };
        +
        +  /**
        +   * Removes the current tint set by <a href="#/p5/tint">tint()</a>.
        +   *
        +   * `noTint()` restores images to their original colors.
        +   *
        +   * @method noTint
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Left.
        +   *   // Tint with a CSS color string.
        +   *   tint('red');
        +   *   image(img, 0, 0);
        +   *
        +   *   // Right.
        +   *   // Remove the tint.
        +   *   noTint();
        +   *   image(img, 50, 0);
        +   *
        +   *   describe('Two images of an umbrella and a ceiling side-by-side. The image on the left has a red tint.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noTint = function() {
        +    this._renderer.states.tint = null;
        +  };
         
        -export default p5;
        +  /**
        +   * Apply the current tint color to the input image, return the resulting
        +   * canvas.
        +   *
        +   * @private
        +   * @param {p5.Image} The image to be tinted
        +   * @return {canvas} The resulting tinted canvas
        +   */
        +  // fn._getTintedImageCanvas =
        +  //   p5.Renderer2D.prototype._getTintedImageCanvas;
        +
        +  /**
        +   * Changes the location from which images are drawn when
        +   * <a href="#/p5/image">image()</a> is called.
        +   *
        +   * By default, the first
        +   * two parameters of <a href="#/p5/image">image()</a> are the x- and
        +   * y-coordinates of the image's upper-left corner. The next parameters are
        +   * its width and height. This is the same as calling `imageMode(CORNER)`.
        +   *
        +   * `imageMode(CORNERS)` also uses the first two parameters of
        +   * <a href="#/p5/image">image()</a> as the x- and y-coordinates of the image's
        +   * top-left corner. The third and fourth parameters are the coordinates of its
        +   * bottom-right corner.
        +   *
        +   * `imageMode(CENTER)` uses the first two parameters of
        +   * <a href="#/p5/image">image()</a> as the x- and y-coordinates of the image's
        +   * center. The next parameters are its width and height.
        +   *
        +   * @method imageMode
        +   * @param {(CORNER|CORNERS|CENTER)} mode either CORNER, CORNERS, or CENTER.
        +   *
        +   * @example
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use CORNER mode.
        +   *   imageMode(CORNER);
        +   *
        +   *   // Display the image.
        +   *   image(img, 10, 10, 50, 50);
        +   *
        +   *   describe('A square image of a brick wall is drawn at the top left of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use CORNERS mode.
        +   *   imageMode(CORNERS);
        +   *
        +   *   // Display the image.
        +   *   image(img, 10, 10, 90, 40);
        +   *
        +   *   describe('An image of a brick wall is drawn on a gray square. The image is squeezed into a small rectangular area.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use CENTER mode.
        +   *   imageMode(CENTER);
        +   *
        +   *   // Display the image.
        +   *   image(img, 50, 50, 80, 80);
        +   *
        +   *   describe('A square image of a brick wall is drawn on a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.imageMode = function(m) {
        +    // p5._validateParameters('imageMode', arguments);
        +    if (
        +      m === constants.CORNER ||
        +      m === constants.CORNERS ||
        +      m === constants.CENTER
        +    ) {
        +      this._renderer.states.imageMode = m;
        +    }
        +  };
        +}
        +
        +export default loadingDisplaying;
        +
        +if(typeof p5 !== 'undefined'){
        +  loadingDisplaying(p5, p5.prototype);
        +}
        diff --git a/src/image/p5.Image.js b/src/image/p5.Image.js
        index 8ab3175afc..e550501e03 100644
        --- a/src/image/p5.Image.js
        +++ b/src/image/p5.Image.js
        @@ -10,180 +10,12 @@
          * This module defines the <a href="#/p5.Image">p5.Image</a> class and P5 methods for
          * drawing images to the main display canvas.
          */
        -
        -import p5 from '../core/main';
         import Filters from './filters';
        +import { Renderer } from '../core/p5.Renderer';
         
        -/*
        - * Class methods
        - */
        -
        -/**
        - * A class to describe an image.
        - *
        - * Images are rectangular grids of pixels that can be displayed and modified.
        - *
        - * Existing images can be loaded by calling
        - * <a href="#/p5/loadImage">loadImage()</a>. Blank images can be created by
        - * calling <a href="#/p5/createImage">createImage()</a>. `p5.Image` objects
        - * have methods for common tasks such as applying filters and modifying
        - * pixel values.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   describe('An image of a brick wall.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Apply the GRAY filter.
        - *   img.filter(GRAY);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   describe('A grayscale image of a brick wall.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Image object.
        - *   let img = createImage(66, 66);
        - *
        - *   // Load the image's pixels.
        - *   img.loadPixels();
        - *
        - *   // Set the pixels to black.
        - *   for (let x = 0; x < img.width; x += 1) {
        - *     for (let y = 0; y < img.height; y += 1) {
        - *       img.set(x, y, 0);
        - *     }
        - *   }
        - *
        - *   // Update the image.
        - *   img.updatePixels();
        - *
        - *   // Display the image.
        - *   image(img, 17, 17);
        - *
        - *   describe('A black square drawn in the middle of a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * @class p5.Image
        - * @constructor
        - * @param {Number} width
        - * @param {Number} height
        - */
        -p5.Image = class {
        +class Image {
           constructor(width, height) {
        -    /**
        -     * The image's width in pixels.
        -     *
        -     * @type {Number}
        -     * @property {Number} width
        -     * @name width
        -     * @readOnly
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * let img;
        -     *
        -     * // Load the image.
        -     * function preload() {
        -     *   img = loadImage('assets/rockies.jpg');
        -     * }
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   // Display the image.
        -     *   image(img, 0, 0);
        -     *
        -     *   // Calculate the center coordinates.
        -     *   let x = img.width / 2;
        -     *   let y = img.height / 2;
        -     *
        -     *   // Draw a circle at the image's center.
        -     *   circle(x, y, 20);
        -     *
        -     *   describe('An image of a mountain landscape with a white circle drawn in the middle.');
        -     * }
        -     * </code>
        -     * </div>
        -     */
             this.width = width;
        -    /**
        -     * The image's height in pixels.
        -     *
        -     * @type {Number}
        -     * @property height
        -     * @name height
        -     * @readOnly
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * let img;
        -     *
        -     * // Load the image.
        -     * function preload() {
        -     *   img = loadImage('assets/rockies.jpg');
        -     * }
        -     *
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   // Display the image.
        -     *   image(img, 0, 0);
        -     *
        -     *   // Calculate the center coordinates.
        -     *   let x = img.width / 2;
        -     *   let y = img.height / 2;
        -     *
        -     *   // Draw a circle at the image's center.
        -     *   circle(x, y, 20);
        -     *
        -     *   describe('An image of a mountain landscape with a white circle drawn in the middle.');
        -     * }
        -     * </code>
        -     * </div>
        -     */
             this.height = height;
             this.canvas = document.createElement('canvas');
             this.canvas.width = this.width;
        @@ -195,121 +27,22 @@ p5.Image = class {
             this.gifProperties = null;
             //For WebGL Texturing only: used to determine whether to reupload texture to GPU
             this._modified = false;
        -    /**
        -     * An array containing the color of each pixel in the image.
        -     *
        -     * Colors are stored as numbers representing red, green, blue, and alpha
        -     * (RGBA) values. `img.pixels` is a one-dimensional array for performance
        -     * reasons.
        -     *
        -     * Each pixel occupies four elements in the pixels array, one for each
        -     * RGBA value. For example, the pixel at coordinates (0, 0) stores its
        -     * RGBA values at `img.pixels[0]`, `img.pixels[1]`, `img.pixels[2]`,
        -     * and `img.pixels[3]`, respectively. The next pixel at coordinates (1, 0)
        -     * stores its RGBA values at `img.pixels[4]`, `img.pixels[5]`,
        -     * `img.pixels[6]`, and `img.pixels[7]`. And so on. The `img.pixels` array
        -     * for a 100×100 <a href="#/p5.Image">p5.Image</a> object has
        -     * 100 × 100 × 4 = 40,000 elements.
        -     *
        -     * Accessing the RGBA values for a pixel in the image requires a little
        -     * math as shown in the examples below. The
        -     * <a href="#/p5.Image/loadPixels">img.loadPixels()</a>
        -     * method must be called before accessing the `img.pixels` array. The
        -     * <a href="#/p5.Image/updatePixels">img.updatePixels()</a> method must be
        -     * called after any changes are made.
        -     *
        -     * @property {Number[]} pixels
        -     * @name pixels
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   background(200);
        -     *
        -     *   // Create a p5.Image object.
        -     *   let img = createImage(66, 66);
        -     *
        -     *   // Load the image's pixels.
        -     *   img.loadPixels();
        -     *
        -     *   for (let i = 0; i < img.pixels.length; i += 4) {
        -     *     // Red.
        -     *     img.pixels[i] = 0;
        -     *     // Green.
        -     *     img.pixels[i + 1] = 0;
        -     *     // Blue.
        -     *     img.pixels[i + 2] = 0;
        -     *     // Alpha.
        -     *     img.pixels[i + 3] = 255;
        -     *   }
        -     *
        -     *   // Update the image.
        -     *   img.updatePixels();
        -     *
        -     *   // Display the image.
        -     *   image(img, 17, 17);
        -     *
        -     *   describe('A black square drawn in the middle of a gray square.');
        -     * }
        -     * </code>
        -     * </div>
        -     *
        -     * <div>
        -     * <code>
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   background(200);
        -     *
        -     *   // Create a p5.Image object.
        -     *   let img = createImage(66, 66);
        -     *
        -     *   // Load the image's pixels.
        -     *   img.loadPixels();
        -     *
        -     *   // Set the pixels to red.
        -     *   for (let i = 0; i < img.pixels.length; i += 4) {
        -     *     // Red.
        -     *     img.pixels[i] = 255;
        -     *     // Green.
        -     *     img.pixels[i + 1] = 0;
        -     *     // Blue.
        -     *     img.pixels[i + 2] = 0;
        -     *     // Alpha.
        -     *     img.pixels[i + 3] = 255;
        -     *   }
        -     *
        -     *   // Update the image.
        -     *   img.updatePixels();
        -     *
        -     *   // Display the image.
        -     *   image(img, 17, 17);
        -     *
        -     *   describe('A red square drawn in the middle of a gray square.');
        -     * }
        -     * </code>
        -     * </div>
        -     */
             this.pixels = [];
           }
         
           /**
        - * Gets or sets the pixel density for high pixel density displays.
        - *
        - * By default, the density will be set to 1.
        - *
        - * Call this method with no arguments to get the default density, or pass
        - * in a number to set the density. If a non-positive number is provided,
        - * it defaults to 1.
        - *
        - * @method pixelDensity
        - * @param {Number} [density] A scaling factor for the number of pixels per
        - * side
        - * @returns {Number} The current density if called without arguments, or the instance for chaining if setting density.
        - */
        +   * Gets or sets the pixel density for high pixel density displays.
        +   *
        +   * By default, the density will be set to 1.
        +   *
        +   * Call this method with no arguments to get the default density, or pass
        +   * in a number to set the density. If a non-positive number is provided,
        +   * it defaults to 1.
        +   *
        +   * @param {Number} [density] A scaling factor for the number of pixels per
        +   * side
        +   * @returns {Number} The current density if called without arguments, or the instance for chaining if setting density.
        +   */
           pixelDensity(density) {
             if (typeof density !== 'undefined') {
             // Setter: set the density and handle resize
        @@ -320,7 +53,7 @@ p5.Image = class {
                   position: 1
                 };
         
        -        p5._friendlyParamError(errorObj, 'pixelDensity');
        +        // p5._friendlyParamError(errorObj, 'pixelDensity');
         
                 // Default to 1 in case of an invalid value
                 density = 1;
        @@ -370,14 +103,6 @@ p5.Image = class {
             }
           }
         
        -  /**
        -   * Helper fxn for sharing pixel methods
        -   */
        -  _setProperty(prop, value) {
        -    this[prop] = value;
        -    this.setModified(true);
        -  }
        -
           /**
            * Loads the current value of each pixel in the image into the `img.pixels`
            * array.
        @@ -385,8 +110,6 @@ p5.Image = class {
            * `img.loadPixels()` must be called before reading or modifying pixel
            * values.
            *
        -   * @method loadPixels
        -   *
            * @example
            * <div>
            * <code>
        @@ -420,42 +143,51 @@ p5.Image = class {
            * </div>
            *
            * <div>
        -     * <code>
        -     * function setup() {
        -     *   createCanvas(100, 100);
        -     *
        -     *   background(200);
        -     *
        -     *   // Create a p5.Image object.
        -     *   let img = createImage(66, 66);
        -     *
        -     *   // Load the image's pixels.
        -     *   img.loadPixels();
        -     *
        -     *   for (let i = 0; i < img.pixels.length; i += 4) {
        -     *     // Red.
        -     *     img.pixels[i] = 0;
        -     *     // Green.
        -     *     img.pixels[i + 1] = 0;
        -     *     // Blue.
        -     *     img.pixels[i + 2] = 0;
        -     *     // Alpha.
        -     *     img.pixels[i + 3] = 255;
        -     *   }
        -     *
        -     *   // Update the image.
        -     *   img.updatePixels();
        -     *
        -     *   // Display the image.
        -     *   image(img, 17, 17);
        -     *
        -     *   describe('A black square drawn in the middle of a gray square.');
        -     * }
        -     * </code>
        -     * </div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Image object.
        +   *   let img = createImage(66, 66);
        +   *
        +   *   // Load the image's pixels.
        +   *   img.loadPixels();
        +   *
        +   *   for (let i = 0; i < img.pixels.length; i += 4) {
        +   *     // Red.
        +   *     img.pixels[i] = 0;
        +   *     // Green.
        +   *     img.pixels[i + 1] = 0;
        +   *     // Blue.
        +   *     img.pixels[i + 2] = 0;
        +   *     // Alpha.
        +   *     img.pixels[i + 3] = 255;
        +   *   }
        +   *
        +   *   // Update the image.
        +   *   img.updatePixels();
        +   *
        +   *   // Display the image.
        +   *   image(img, 17, 17);
        +   *
        +   *   describe('A black square drawn in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
            */
           loadPixels() {
        -    p5.Renderer2D.prototype.loadPixels.call(this);
        +    // Renderer2D.prototype.loadPixels.call(this);
        +    const pixelsState = this._pixelsState;
        +    const pd = this._pixelDensity;
        +    const w = this.width * pd;
        +    const h = this.height * pd;
        +    const imageData = this.drawingContext.getImageData(0, 0, w, h);
        +    // @todo this should actually set pixels per object, so diff buffers can
        +    // have diff pixel arrays.
        +    pixelsState.imageData = imageData;
        +    this.pixels = pixelsState.pixels = imageData.data;
             this.setModified(true);
           }
         
        @@ -476,7 +208,6 @@ p5.Image = class {
            * If the image was loaded from a GIF, then calling `img.updatePixels()`
            * will update the pixels in current frame.
            *
        -   * @method updatePixels
            * @param {Integer} x x-coordinate of the upper-left corner
            *                    of the subsection to update.
            * @param {Integer} y y-coordinate of the upper-left corner
        @@ -552,11 +283,32 @@ p5.Image = class {
            * </code>
            * </div>
            */
        -  /**
        -   * @method updatePixels
        -   */
           updatePixels(x, y, w, h) {
        -    p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
        +    // Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
        +    const pixelsState = this._pixelsState;
        +    const pd = this._pixelDensity;
        +    if (
        +      x === undefined &&
        +      y === undefined &&
        +      w === undefined &&
        +      h === undefined
        +    ) {
        +      x = 0;
        +      y = 0;
        +      w = this.width;
        +      h = this.height;
        +    }
        +    x *= pd;
        +    y *= pd;
        +    w *= pd;
        +    h *= pd;
        +
        +    if (this.gifProperties) {
        +      this.gifProperties.frames[this.gifProperties.displayIndex].image =
        +        pixelsState.imageData;
        +    }
        +
        +    this.drawingContext.putImageData(pixelsState.imageData, x, y, 0, 0, w, h);
             this.setModified(true);
           }
         
        @@ -583,7 +335,6 @@ p5.Image = class {
            * Use `img.get()` instead of <a href="#/p5/get">get()</a> to work directly
            * with images.
            *
        -   * @method get
            * @param  {Number}               x x-coordinate of the pixel.
            * @param  {Number}               y y-coordinate of the pixel.
            * @param  {Number}               w width of the subsection to be returned.
        @@ -676,22 +427,60 @@ p5.Image = class {
            * </div>
            */
           /**
        -   * @method get
            * @return {p5.Image}      whole <a href="#/p5.Image">p5.Image</a>
            */
           /**
        -   * @method get
            * @param  {Number}        x
            * @param  {Number}        y
            * @return {Number[]}      color of the pixel at (x, y) in array format `[R, G, B, A]`.
            */
           get(x, y, w, h) {
        -    p5._validateParameters('p5.Image.get', arguments);
        -    return p5.Renderer2D.prototype.get.apply(this, arguments);
        +    // p5._validateParameters('p5.Image.get', arguments);
        +    // return Renderer2D.prototype.get.apply(this, arguments);
        +    const pixelsState = this._pixelsState;
        +    const pd = this._pixelDensity;
        +    const canvas = this.canvas;
        +
        +    if (typeof x === 'undefined' && typeof y === 'undefined') {
        +    // get()
        +      x = y = 0;
        +      w = pixelsState.width;
        +      h = pixelsState.height;
        +    } else {
        +      x *= pd;
        +      y *= pd;
        +
        +      if (typeof w === 'undefined' && typeof h === 'undefined') {
        +      // get(x,y)
        +        if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) {
        +          return [0, 0, 0, 0];
        +        }
        +
        +        return this._getPixel(x, y);
        +      }
        +    // get(x,y,w,h)
        +    }
        +
        +    const region = new Image(w*pd, h*pd);
        +    region.pixelDensity(pd);
        +    region.canvas
        +      .getContext('2d')
        +      .drawImage(canvas, x, y, w * pd, h * pd, 0, 0, w*pd, h*pd);
        +
        +    return region;
           }
         
        -  _getPixel(...args) {
        -    return p5.Renderer2D.prototype._getPixel.apply(this, args);
        +  _getPixel(x, y) {
        +    let imageData, index;
        +    imageData = this.drawingContext.getImageData(x, y, 1, 1).data;
        +    index = 0;
        +    return [
        +      imageData[index + 0],
        +      imageData[index + 1],
        +      imageData[index + 2],
        +      imageData[index + 3]
        +    ];
        +    // return Renderer2D.prototype._getPixel.apply(this, args);
           }
         
           /**
        @@ -709,7 +498,6 @@ p5.Image = class {
            * <a href="#/p5.Image/updatePixels">img.updatePixels()</a> must be called
            * after using `img.set()` for changes to appear.
            *
        -   * @method set
            * @param {Number}              x x-coordinate of the pixel.
            * @param {Number}              y y-coordinate of the pixel.
            * @param {Number|Number[]|Object}   a grayscale value | pixel array |
        @@ -830,7 +618,77 @@ p5.Image = class {
            * </div>
            */
           set(x, y, imgOrCol) {
        -    p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
        +    // Renderer2D.prototype.set.call(this, x, y, imgOrCol);
        +    // round down to get integer numbers
        +    x = Math.floor(x);
        +    y = Math.floor(y);
        +    const pixelsState = this._pixelsState;
        +    if (imgOrCol instanceof Image) {
        +      this.drawingContext.save();
        +      this.drawingContext.setTransform(1, 0, 0, 1, 0, 0);
        +      this.drawingContext.scale(
        +        this._pixelDensity,
        +        this._pixelDensity
        +      );
        +      this.drawingContext.clearRect(x, y, imgOrCol.width, imgOrCol.height);
        +      this.drawingContext.drawImage(imgOrCol.canvas, x, y);
        +      this.drawingContext.restore();
        +    } else {
        +      let r = 0,
        +        g = 0,
        +        b = 0,
        +        a = 0;
        +      let idx =
        +        4 *
        +        (y *
        +          this._pixelDensity *
        +          (this.width * this._pixelDensity) +
        +          x * this._pixelDensity);
        +      if (!pixelsState.imageData) {
        +        pixelsState.loadPixels();
        +      }
        +      if (typeof imgOrCol === 'number') {
        +        if (idx < pixelsState.pixels.length) {
        +          r = imgOrCol;
        +          g = imgOrCol;
        +          b = imgOrCol;
        +          a = 255;
        +          //this.updatePixels.call(this);
        +        }
        +      } else if (Array.isArray(imgOrCol)) {
        +        if (imgOrCol.length < 4) {
        +          throw new Error('pixel array must be of the form [R, G, B, A]');
        +        }
        +        if (idx < pixelsState.pixels.length) {
        +          r = imgOrCol[0];
        +          g = imgOrCol[1];
        +          b = imgOrCol[2];
        +          a = imgOrCol[3];
        +          //this.updatePixels.call(this);
        +        }
        +      } else if (imgOrCol instanceof p5.Color) {
        +        if (idx < pixelsState.pixels.length) {
        +          [r, g, b, a] = imgOrCol._getRGBA([255, 255, 255, 255]);
        +          //this.updatePixels.call(this);
        +        }
        +      }
        +      // loop over pixelDensity * pixelDensity
        +      for (let i = 0; i < this._pixelDensity; i++) {
        +        for (let j = 0; j < this._pixelDensity; j++) {
        +          // loop over
        +          idx =
        +            4 *
        +            ((y * this._pixelDensity + j) *
        +              this.width *
        +              this._pixelDensity +
        +              (x * this._pixelDensity + i));
        +          pixelsState.pixels[idx] = r;
        +          pixelsState.pixels[idx + 1] = g;
        +          pixelsState.pixels[idx + 2] = b;
        +          pixelsState.pixels[idx + 3] = a;
        +        }
        +      }
        +    }
             this.setModified(true);
           }
         
        @@ -841,7 +699,6 @@ p5.Image = class {
            * `width` or `height`. For example, calling `img.resize(50, 0)` on an image
            * that was 500 &times; 300 pixels will resize it to 50 &times; 30 pixels.
            *
        -   * @method resize
            * @param {Number} width resized image width.
            * @param {Number} height resized image height.
            *
        @@ -1022,7 +879,6 @@ p5.Image = class {
            * Calling `img.copy()` will scale pixels from the source region if it isn't
            * the same size as the destination region.
            *
        -   * @method copy
            * @param  {p5.Image|p5.Element} srcImage source image.
            * @param  {Integer} sx x-coordinate of the source's upper-left corner.
            * @param  {Integer} sy y-coordinate of the source's upper-left corner.
        @@ -1092,7 +948,6 @@ p5.Image = class {
            * </div>
            */
           /**
        -   * @method copy
            * @param  {Integer} sx
            * @param  {Integer} sy
            * @param  {Integer} sw
        @@ -1103,7 +958,7 @@ p5.Image = class {
            * @param  {Integer} dh
            */
           copy(...args) {
        -    p5.prototype.copy.apply(this, args);
        +    fn.copy.apply(this, args);
           }
         
           /**
        @@ -1114,7 +969,6 @@ p5.Image = class {
            * and can't be removed once applied. If the mask has a different
            * pixel density from this image, the mask will be scaled.
            *
        -   * @method mask
            * @param {p5.Image} srcImage source image.
            *
            * @example
        @@ -1152,8 +1006,8 @@ p5.Image = class {
         
             let imgScaleFactor = this._pixelDensity;
             let maskScaleFactor = 1;
        -    if (p5Image instanceof p5.Renderer) {
        -      maskScaleFactor = p5Image._pInst._pixelDensity;
        +    if (p5Image instanceof Renderer) {
        +      maskScaleFactor = p5Image._pInst._renderer._pixelDensity;
             }
         
             const copyArgs = [
        @@ -1232,8 +1086,7 @@ p5.Image = class {
            * `DILATE`
            * Increases the light areas. No parameter is used.
            *
        -   * @method filter
        -   * @param  {Constant} filterType  either THRESHOLD, GRAY, OPAQUE, INVERT,
        +   * @param  {(THRESHOLD|GRAY|OPAQUE|INVERT|POSTERIZE|ERODE|DILATE|BLUR)} filterType  either THRESHOLD, GRAY, OPAQUE, INVERT,
            *                                POSTERIZE, ERODE, DILATE or BLUR.
            * @param  {Number} [filterParam] parameter unique to each filter.
            *
        @@ -1446,7 +1299,6 @@ p5.Image = class {
            * `MULTIPLY`, `EXCLUSION`, `SCREEN`, `REPLACE`, `OVERLAY`, `HARD_LIGHT`,
            * `SOFT_LIGHT`, `DODGE`, `BURN`, `ADD`, or `NORMAL`.
            *
        -   * @method blend
            * @param  {p5.Image} srcImage source image
            * @param  {Integer} sx x-coordinate of the source's upper-left corner.
            * @param  {Integer} sy y-coordinate of the source's upper-left corner.
        @@ -1456,7 +1308,7 @@ p5.Image = class {
            * @param  {Integer} dy y-coordinate of the destination's upper-left corner.
            * @param  {Integer} dw destination image width.
            * @param  {Integer} dh destination image height.
        -   * @param  {Constant} blendMode the blend mode. either
        +   * @param  {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode the blend mode. either
            *     BLEND, DARKEST, LIGHTEST, DIFFERENCE,
            *     MULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,
            *     SOFT_LIGHT, DODGE, BURN, ADD or NORMAL.
        @@ -1554,7 +1406,6 @@ p5.Image = class {
            * </div>
            */
           /**
        -   * @method blend
            * @param  {Integer} sx
            * @param  {Integer} sy
            * @param  {Integer} sw
        @@ -1563,11 +1414,11 @@ p5.Image = class {
            * @param  {Integer} dy
            * @param  {Integer} dw
            * @param  {Integer} dh
        -   * @param  {Constant} blendMode
        +   * @param  {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode
            */
           blend(...args) {
        -    p5._validateParameters('p5.Image.blend', arguments);
        -    p5.prototype.blend.apply(this, args);
        +    // p5._validateParameters('p5.Image.blend', arguments);
        +    fn.blend.apply(this, args);
             this.setModified(true);
           }
         
        @@ -1575,7 +1426,6 @@ p5.Image = class {
            * helper method for web GL mode to indicate that an image has been
            * changed or unchanged since last upload. gl texture upload will
            * set this value to false after uploading the texture.
        -   * @method setModified
            * @param {boolean} val sets whether or not the image has been
            * modified.
            * @private
        @@ -1588,7 +1438,6 @@ p5.Image = class {
            * helper method for web GL mode to figure out if the image
            * has been modified and might need to be re-uploaded to texture
            * memory between frames.
        -   * @method isModified
            * @private
            * @return {boolean} a boolean indicating whether or not the
            * image has been updated or modified since last texture upload.
        @@ -1600,7 +1449,7 @@ p5.Image = class {
           /**
            * Saves the image to a file.
            *
        -    * By default, `img.save()` saves the image as a PNG image called
        +   * By default, `img.save()` saves the image as a PNG image called
            * `untitled.png`.
            *
            * The first parameter, `filename`, is optional. It's a string that sets the
        @@ -1619,7 +1468,6 @@ p5.Image = class {
            * from a GIF file. See <a href="#/p5/saveGif">saveGif()</a> to create new
            * GIFs.
            *
        -   * @method save
            * @param {String} filename filename. Defaults to 'untitled'.
            * @param  {String} [extension] file extension, either 'png' or 'jpg'.
            *                            Defaults to 'png'.
        @@ -1658,18 +1506,22 @@ p5.Image = class {
            */
           save(filename, extension) {
             if (this.gifProperties) {
        -      p5.prototype.encodeAndDownloadGif(this, filename);
        +      fn.encodeAndDownloadGif(this, filename);
             } else {
        -      p5.prototype.saveCanvas(this.canvas, filename, extension);
        +      fn.saveCanvas(this.canvas, filename, extension);
             }
           }
         
        +  async toBlob() {
        +    return new Promise(resolve => {
        +      this.canvas.toBlob(resolve);
        +    });
        +  }
        +
           // GIF Section
           /**
            * Restarts an animated GIF at its first frame.
            *
        -   * @method reset
        -   *
            * @example
            * <div>
            * <code>
        @@ -1716,7 +1568,6 @@ p5.Image = class {
           /**
            * Gets the index of the current frame in an animated GIF.
            *
        -   * @method getCurrentFrame
            * @return {Number}       index of the GIF's current frame.
            *
            * @example
        @@ -1758,7 +1609,6 @@ p5.Image = class {
           /**
            * Sets the current frame in an animated GIF.
            *
        -   * @method setFrame
            * @param {Number} index index of the frame to display.
            *
            * @example
        @@ -1818,7 +1668,6 @@ p5.Image = class {
           /**
            * Returns the number of frames in an animated GIF.
            *
        -   * @method numFrames
            * @return {Number} number of frames in the GIF.
            *
            * @example
        @@ -1859,8 +1708,6 @@ p5.Image = class {
            * Plays an animated GIF that was paused with
            * <a href="#/p5.Image/pause">img.pause()</a>.
            *
        -   * @method play
        -   *
            * @example
            * <div>
            * <code>
        @@ -1906,8 +1753,6 @@ p5.Image = class {
            * The GIF can be resumed by calling
            * <a href="#/p5.Image/play">img.play()</a>.
            *
        -   * @method pause
        -   *
            * @example
            * <div>
            * <code>
        @@ -1958,7 +1803,6 @@ p5.Image = class {
            * at `index` will have its delay modified. All other frames will keep
            * their default delay.
            *
        -   * @method delay
            * @param {Number} d delay in milliseconds between switching frames.
            * @param {Number} [index] index of the frame that will have its delay modified.
            *
        @@ -2037,4 +1881,280 @@ p5.Image = class {
             }
           }
         };
        -export default p5.Image;
        +
        +function image(p5, fn){
        +  /**
        +   * A class to describe an image.
        +   *
        +   * Images are rectangular grids of pixels that can be displayed and modified.
        +   *
        +   * Existing images can be loaded by calling
        +   * <a href="#/p5/loadImage">loadImage()</a>. Blank images can be created by
        +   * calling <a href="#/p5/createImage">createImage()</a>. `p5.Image` objects
        +   * have methods for common tasks such as applying filters and modifying
        +   * pixel values.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   describe('An image of a brick wall.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Apply the GRAY filter.
        +   *   img.filter(GRAY);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   describe('A grayscale image of a brick wall.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Image object.
        +   *   let img = createImage(66, 66);
        +   *
        +   *   // Load the image's pixels.
        +   *   img.loadPixels();
        +   *
        +   *   // Set the pixels to black.
        +   *   for (let x = 0; x < img.width; x += 1) {
        +   *     for (let y = 0; y < img.height; y += 1) {
        +   *       img.set(x, y, 0);
        +   *     }
        +   *   }
        +   *
        +   *   // Update the image.
        +   *   img.updatePixels();
        +   *
        +   *   // Display the image.
        +   *   image(img, 17, 17);
        +   *
        +   *   describe('A black square drawn in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @class p5.Image
        +   * @param {Number} width
        +   * @param {Number} height
        +   */
        +  p5.Image = Image;
        +
        +  /**
        +   * The image's width in pixels.
        +   *
        +   * @type {Number}
        +   * @property {Number} width
        +   * @for p5.Image
        +   * @name width
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/rockies.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Calculate the center coordinates.
        +   *   let x = img.width / 2;
        +   *   let y = img.height / 2;
        +   *
        +   *   // Draw a circle at the image's center.
        +   *   circle(x, y, 20);
        +   *
        +   *   describe('An image of a mountain landscape with a white circle drawn in the middle.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * The image's height in pixels.
        +   *
        +   * @type {Number}
        +   * @property height
        +   * @for p5.Image
        +   * @name height
        +   * @readOnly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/rockies.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Calculate the center coordinates.
        +   *   let x = img.width / 2;
        +   *   let y = img.height / 2;
        +   *
        +   *   // Draw a circle at the image's center.
        +   *   circle(x, y, 20);
        +   *
        +   *   describe('An image of a mountain landscape with a white circle drawn in the middle.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * An array containing the color of each pixel in the image.
        +   *
        +   * Colors are stored as numbers representing red, green, blue, and alpha
        +   * (RGBA) values. `img.pixels` is a one-dimensional array for performance
        +   * reasons.
        +   *
        +   * Each pixel occupies four elements in the pixels array, one for each
        +   * RGBA value. For example, the pixel at coordinates (0, 0) stores its
        +   * RGBA values at `img.pixels[0]`, `img.pixels[1]`, `img.pixels[2]`,
        +   * and `img.pixels[3]`, respectively. The next pixel at coordinates (1, 0)
        +   * stores its RGBA values at `img.pixels[4]`, `img.pixels[5]`,
        +   * `img.pixels[6]`, and `img.pixels[7]`. And so on. The `img.pixels` array
        +   * for a 100×100 <a href="#/p5.Image">p5.Image</a> object has
        +   * 100 × 100 × 4 = 40,000 elements.
        +   *
        +   * Accessing the RGBA values for a pixel in the image requires a little
        +   * math as shown in the examples below. The
        +   * <a href="#/p5.Image/loadPixels">img.loadPixels()</a>
        +   * method must be called before accessing the `img.pixels` array. The
        +   * <a href="#/p5.Image/updatePixels">img.updatePixels()</a> method must be
        +   * called after any changes are made.
        +   *
        +   * @property {Number[]} pixels
        +   * @for p5.Image
        +   * @name pixels
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Image object.
        +   *   let img = createImage(66, 66);
        +   *
        +   *   // Load the image's pixels.
        +   *   img.loadPixels();
        +   *
        +   *   for (let i = 0; i < img.pixels.length; i += 4) {
        +   *     // Red.
        +   *     img.pixels[i] = 0;
        +   *     // Green.
        +   *     img.pixels[i + 1] = 0;
        +   *     // Blue.
        +   *     img.pixels[i + 2] = 0;
        +   *     // Alpha.
        +   *     img.pixels[i + 3] = 255;
        +   *   }
        +   *
        +   *   // Update the image.
        +   *   img.updatePixels();
        +   *
        +   *   // Display the image.
        +   *   image(img, 17, 17);
        +   *
        +   *   describe('A black square drawn in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Image object.
        +   *   let img = createImage(66, 66);
        +   *
        +   *   // Load the image's pixels.
        +   *   img.loadPixels();
        +   *
        +   *   // Set the pixels to red.
        +   *   for (let i = 0; i < img.pixels.length; i += 4) {
        +   *     // Red.
        +   *     img.pixels[i] = 255;
        +   *     // Green.
        +   *     img.pixels[i + 1] = 0;
        +   *     // Blue.
        +   *     img.pixels[i + 2] = 0;
        +   *     // Alpha.
        +   *     img.pixels[i + 3] = 255;
        +   *   }
        +   *
        +   *   // Update the image.
        +   *   img.updatePixels();
        +   *
        +   *   // Display the image.
        +   *   image(img, 17, 17);
        +   *
        +   *   describe('A red square drawn in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +}
        +
        +export default image;
        +export { Image };
        +
        +if(typeof p5 !== 'undefined'){
        +  image(p5, p5.prototype);
        +}
        diff --git a/src/image/pixels.js b/src/image/pixels.js
        index effda98199..770cfede47 100644
        --- a/src/image/pixels.js
        +++ b/src/image/pixels.js
        @@ -5,1178 +5,1160 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
         import Filters from './filters';
        -import '../color/p5.Color';
         
        -/**
        - * An array containing the color of each pixel on the canvas.
        - *
        - * Colors are stored as numbers representing red, green, blue, and alpha
        - * (RGBA) values. `pixels` is a one-dimensional array for performance reasons.
        - *
        - * Each pixel occupies four elements in the `pixels` array, one for each RGBA
        - * value. For example, the pixel at coordinates (0, 0) stores its RGBA values
        - * at `pixels[0]`, `pixels[1]`, `pixels[2]`, and `pixels[3]`, respectively.
        - * The next pixel at coordinates (1, 0) stores its RGBA values at `pixels[4]`,
        - * `pixels[5]`, `pixels[6]`, and `pixels[7]`. And so on. The `pixels` array
        - * for a 100&times;100 canvas has 100 &times; 100 &times; 4 = 40,000 elements.
        - *
        - * Some displays use several smaller pixels to set the color at a single
        - * point. The <a href="#/p5/pixelDensity">pixelDensity()</a> function returns
        - * the pixel density of the canvas. High density displays often have a
        - * <a href="#/p5/pixelDensity">pixelDensity()</a> of 2. On such a display, the
        - * `pixels` array for a 100&times;100 canvas has 200 &times; 200 &times; 4 =
        - * 160,000 elements.
        - *
        - * Accessing the RGBA values for a point on the canvas requires a little math
        - * as shown below. The <a href="#/p5/loadPixels">loadPixels()</a> function
        - * must be called before accessing the `pixels` array. The
        - * <a href="#/p5/updatePixels">updatePixels()</a> function must be called
        - * after any changes are made.
        - *
        - * @property {Number[]} pixels
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Load the pixels array.
        - *   loadPixels();
        - *
        - *   // Set the dot's coordinates.
        - *   let x = 50;
        - *   let y = 50;
        - *
        - *   // Get the pixel density.
        - *   let d = pixelDensity();
        - *
        - *   // Set the pixel(s) at the center of the canvas black.
        - *   for (let i = 0; i < d; i += 1) {
        - *     for (let j = 0; j < d; j += 1) {
        - *       let index = 4 * ((y * d + j) * width * d + (x * d + i));
        - *       // Red.
        - *       pixels[index] = 0;
        - *       // Green.
        - *       pixels[index + 1] = 0;
        - *       // Blue.
        - *       pixels[index + 2] = 0;
        - *       // Alpha.
        - *       pixels[index + 3] = 255;
        - *     }
        - *   }
        - *
        - *   // Update the canvas.
        - *   updatePixels();
        - *
        - *   describe('A black dot in the middle of a gray rectangle.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Load the pixels array.
        - *   loadPixels();
        - *
        - *   // Get the pixel density.
        - *   let d = pixelDensity();
        - *
        - *   // Calculate the halfway index in the pixels array.
        - *   let halfImage = 4 * (d * width) * (d * height / 2);
        - *
        - *   // Make the top half of the canvas red.
        - *   for (let i = 0; i < halfImage; i += 4) {
        - *     // Red.
        - *     pixels[i] = 255;
        - *     // Green.
        - *     pixels[i + 1] = 0;
        - *     // Blue.
        - *     pixels[i + 2] = 0;
        - *     // Alpha.
        - *     pixels[i + 3] = 255;
        - *   }
        - *
        - *   // Update the canvas.
        - *   updatePixels();
        - *
        - *   describe('A red rectangle drawn above a gray rectangle.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create a p5.Color object.
        - *   let pink = color(255, 102, 204);
        - *
        - *   // Load the pixels array.
        - *   loadPixels();
        - *
        - *   // Get the pixel density.
        - *   let d = pixelDensity();
        - *
        - *   // Calculate the halfway index in the pixels array.
        - *   let halfImage = 4 * (d * width) * (d * height / 2);
        - *
        - *   // Make the top half of the canvas red.
        - *   for (let i = 0; i < halfImage; i += 4) {
        - *     pixels[i] = red(pink);
        - *     pixels[i + 1] = green(pink);
        - *     pixels[i + 2] = blue(pink);
        - *     pixels[i + 3] = alpha(pink);
        - *   }
        - *
        - *   // Update the canvas.
        - *   updatePixels();
        - *
        - *   describe('A pink rectangle drawn above a gray rectangle.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.pixels = [];
        +function pixels(p5, fn){
        +  /**
        +   * An array containing the color of each pixel on the canvas.
        +   *
        +   * Colors are stored as numbers representing red, green, blue, and alpha
        +   * (RGBA) values. `pixels` is a one-dimensional array for performance reasons.
        +   *
        +   * Each pixel occupies four elements in the `pixels` array, one for each RGBA
        +   * value. For example, the pixel at coordinates (0, 0) stores its RGBA values
        +   * at `pixels[0]`, `pixels[1]`, `pixels[2]`, and `pixels[3]`, respectively.
        +   * The next pixel at coordinates (1, 0) stores its RGBA values at `pixels[4]`,
        +   * `pixels[5]`, `pixels[6]`, and `pixels[7]`. And so on. The `pixels` array
        +   * for a 100&times;100 canvas has 100 &times; 100 &times; 4 = 40,000 elements.
        +   *
        +   * Some displays use several smaller pixels to set the color at a single
        +   * point. The <a href="#/p5/pixelDensity">pixelDensity()</a> function returns
        +   * the pixel density of the canvas. High density displays often have a
        +   * <a href="#/p5/pixelDensity">pixelDensity()</a> of 2. On such a display, the
        +   * `pixels` array for a 100&times;100 canvas has 200 &times; 200 &times; 4 =
        +   * 160,000 elements.
        +   *
        +   * Accessing the RGBA values for a point on the canvas requires a little math
        +   * as shown below. The <a href="#/p5/loadPixels">loadPixels()</a> function
        +   * must be called before accessing the `pixels` array. The
        +   * <a href="#/p5/updatePixels">updatePixels()</a> function must be called
        +   * after any changes are made.
        +   *
        +   * @property {Number[]} pixels
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Load the pixels array.
        +   *   loadPixels();
        +   *
        +   *   // Set the dot's coordinates.
        +   *   let x = 50;
        +   *   let y = 50;
        +   *
        +   *   // Get the pixel density.
        +   *   let d = pixelDensity();
        +   *
        +   *   // Set the pixel(s) at the center of the canvas black.
        +   *   for (let i = 0; i < d; i += 1) {
        +   *     for (let j = 0; j < d; j += 1) {
        +   *       let index = 4 * ((y * d + j) * width * d + (x * d + i));
        +   *       // Red.
        +   *       pixels[index] = 0;
        +   *       // Green.
        +   *       pixels[index + 1] = 0;
        +   *       // Blue.
        +   *       pixels[index + 2] = 0;
        +   *       // Alpha.
        +   *       pixels[index + 3] = 255;
        +   *     }
        +   *   }
        +   *
        +   *   // Update the canvas.
        +   *   updatePixels();
        +   *
        +   *   describe('A black dot in the middle of a gray rectangle.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Load the pixels array.
        +   *   loadPixels();
        +   *
        +   *   // Get the pixel density.
        +   *   let d = pixelDensity();
        +   *
        +   *   // Calculate the halfway index in the pixels array.
        +   *   let halfImage = 4 * (d * width) * (d * height / 2);
        +   *
        +   *   // Make the top half of the canvas red.
        +   *   for (let i = 0; i < halfImage; i += 4) {
        +   *     // Red.
        +   *     pixels[i] = 255;
        +   *     // Green.
        +   *     pixels[i + 1] = 0;
        +   *     // Blue.
        +   *     pixels[i + 2] = 0;
        +   *     // Alpha.
        +   *     pixels[i + 3] = 255;
        +   *   }
        +   *
        +   *   // Update the canvas.
        +   *   updatePixels();
        +   *
        +   *   describe('A red rectangle drawn above a gray rectangle.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let pink = color(255, 102, 204);
        +   *
        +   *   // Load the pixels array.
        +   *   loadPixels();
        +   *
        +   *   // Get the pixel density.
        +   *   let d = pixelDensity();
        +   *
        +   *   // Calculate the halfway index in the pixels array.
        +   *   let halfImage = 4 * (d * width) * (d * height / 2);
        +   *
        +   *   // Make the top half of the canvas red.
        +   *   for (let i = 0; i < halfImage; i += 4) {
        +   *     pixels[i] = red(pink);
        +   *     pixels[i + 1] = green(pink);
        +   *     pixels[i + 2] = blue(pink);
        +   *     pixels[i + 3] = alpha(pink);
        +   *   }
        +   *
        +   *   // Update the canvas.
        +   *   updatePixels();
        +   *
        +   *   describe('A pink rectangle drawn above a gray rectangle.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -/**
        - * Copies a region of pixels from one image to another.
        - *
        - * The first parameter, `srcImage`, is the
        - * <a href="#/p5.Image">p5.Image</a> object to blend.
        - *
        - * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region
        - * to blend from the source image. `(sx, sy)` is the top-left corner of the
        - * region. `sw` and `sh` are the regions width and height.
        - *
        - * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region
        - * of the canvas to blend into. `(dx, dy)` is the top-left corner of the
        - * region. `dw` and `dh` are the regions width and height.
        - *
        - * The tenth parameter, `blendMode`, sets the effect used to blend the images'
        - * colors. The options are `BLEND`, `DARKEST`, `LIGHTEST`, `DIFFERENCE`,
        - * `MULTIPLY`, `EXCLUSION`, `SCREEN`, `REPLACE`, `OVERLAY`, `HARD_LIGHT`,
        - * `SOFT_LIGHT`, `DODGE`, `BURN`, `ADD`, or `NORMAL`
        - *
        - * @method blend
        - * @param  {p5.Image} srcImage source image.
        - * @param  {Integer} sx x-coordinate of the source's upper-left corner.
        - * @param  {Integer} sy y-coordinate of the source's upper-left corner.
        - * @param  {Integer} sw source image width.
        - * @param  {Integer} sh source image height.
        - * @param  {Integer} dx x-coordinate of the destination's upper-left corner.
        - * @param  {Integer} dy y-coordinate of the destination's upper-left corner.
        - * @param  {Integer} dw destination image width.
        - * @param  {Integer} dh destination image height.
        - * @param  {Constant} blendMode the blend mode. either
        - *     BLEND, DARKEST, LIGHTEST, DIFFERENCE,
        - *     MULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,
        - *     SOFT_LIGHT, DODGE, BURN, ADD or NORMAL.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img0;
        - * let img1;
        - *
        - * // Load the images.
        - * function preload() {
        - *   img0 = loadImage('assets/rockies.jpg');
        - *   img1 = loadImage('assets/bricks_third.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use the mountains as the background.
        - *   background(img0);
        - *
        - *   // Display the bricks.
        - *   image(img1, 0, 0);
        - *
        - *   // Display the bricks faded into the landscape.
        - *   blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST);
        - *
        - *   describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears faded on the right of the image.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img0;
        - * let img1;
        - *
        - * // Load the images.
        - * function preload() {
        - *   img0 = loadImage('assets/rockies.jpg');
        - *   img1 = loadImage('assets/bricks_third.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use the mountains as the background.
        - *   background(img0);
        - *
        - *   // Display the bricks.
        - *   image(img1, 0, 0);
        - *
        - *   // Display the bricks partially transparent.
        - *   blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST);
        - *
        - *   describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears transparent on the right of the image.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img0;
        - * let img1;
        - *
        - * // Load the images.
        - * function preload() {
        - *   img0 = loadImage('assets/rockies.jpg');
        - *   img1 = loadImage('assets/bricks_third.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use the mountains as the background.
        - *   background(img0);
        - *
        - *   // Display the bricks.
        - *   image(img1, 0, 0);
        - *
        - *   // Display the bricks washed out into the landscape.
        - *   blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, ADD);
        - *
        - *   describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears washed out on the right of the image.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method blend
        - * @param  {Integer} sx
        - * @param  {Integer} sy
        - * @param  {Integer} sw
        - * @param  {Integer} sh
        - * @param  {Integer} dx
        - * @param  {Integer} dy
        - * @param  {Integer} dw
        - * @param  {Integer} dh
        - * @param  {Constant} blendMode
        - */
        -p5.prototype.blend = function(...args) {
        -  p5._validateParameters('blend', args);
        -  if (this._renderer) {
        -    this._renderer.blend(...args);
        -  } else {
        -    p5.Renderer2D.prototype.blend.apply(this, args);
        -  }
        -};
        +  /**
        +   * Copies a region of pixels from one image to another.
        +   *
        +   * The first parameter, `srcImage`, is the
        +   * <a href="#/p5.Image">p5.Image</a> object to blend.
        +   *
        +   * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region
        +   * to blend from the source image. `(sx, sy)` is the top-left corner of the
        +   * region. `sw` and `sh` are the regions width and height.
        +   *
        +   * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region
        +   * of the canvas to blend into. `(dx, dy)` is the top-left corner of the
        +   * region. `dw` and `dh` are the regions width and height.
        +   *
        +   * The tenth parameter, `blendMode`, sets the effect used to blend the images'
        +   * colors. The options are `BLEND`, `DARKEST`, `LIGHTEST`, `DIFFERENCE`,
        +   * `MULTIPLY`, `EXCLUSION`, `SCREEN`, `REPLACE`, `OVERLAY`, `HARD_LIGHT`,
        +   * `SOFT_LIGHT`, `DODGE`, `BURN`, `ADD`, or `NORMAL`
        +   *
        +   * @method blend
        +   * @param  {p5.Image} srcImage source image.
        +   * @param  {Integer} sx x-coordinate of the source's upper-left corner.
        +   * @param  {Integer} sy y-coordinate of the source's upper-left corner.
        +   * @param  {Integer} sw source image width.
        +   * @param  {Integer} sh source image height.
        +   * @param  {Integer} dx x-coordinate of the destination's upper-left corner.
        +   * @param  {Integer} dy y-coordinate of the destination's upper-left corner.
        +   * @param  {Integer} dw destination image width.
        +   * @param  {Integer} dh destination image height.
        +   * @param  {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode the blend mode. either
        +   *     BLEND, DARKEST, LIGHTEST, DIFFERENCE,
        +   *     MULTIPLY, EXCLUSION, SCREEN, REPLACE, OVERLAY, HARD_LIGHT,
        +   *     SOFT_LIGHT, DODGE, BURN, ADD or NORMAL.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img0;
        +   * let img1;
        +   *
        +   * // Load the images.
        +   * function preload() {
        +   *   img0 = loadImage('assets/rockies.jpg');
        +   *   img1 = loadImage('assets/bricks_third.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use the mountains as the background.
        +   *   background(img0);
        +   *
        +   *   // Display the bricks.
        +   *   image(img1, 0, 0);
        +   *
        +   *   // Display the bricks faded into the landscape.
        +   *   blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST);
        +   *
        +   *   describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears faded on the right of the image.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img0;
        +   * let img1;
        +   *
        +   * // Load the images.
        +   * function preload() {
        +   *   img0 = loadImage('assets/rockies.jpg');
        +   *   img1 = loadImage('assets/bricks_third.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use the mountains as the background.
        +   *   background(img0);
        +   *
        +   *   // Display the bricks.
        +   *   image(img1, 0, 0);
        +   *
        +   *   // Display the bricks partially transparent.
        +   *   blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST);
        +   *
        +   *   describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears transparent on the right of the image.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img0;
        +   * let img1;
        +   *
        +   * // Load the images.
        +   * function preload() {
        +   *   img0 = loadImage('assets/rockies.jpg');
        +   *   img1 = loadImage('assets/bricks_third.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use the mountains as the background.
        +   *   background(img0);
        +   *
        +   *   // Display the bricks.
        +   *   image(img1, 0, 0);
        +   *
        +   *   // Display the bricks washed out into the landscape.
        +   *   blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, ADD);
        +   *
        +   *   describe('A wall of bricks in front of a mountain landscape. The same wall of bricks appears washed out on the right of the image.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method blend
        +   * @param  {Integer} sx
        +   * @param  {Integer} sy
        +   * @param  {Integer} sw
        +   * @param  {Integer} sh
        +   * @param  {Integer} dx
        +   * @param  {Integer} dy
        +   * @param  {Integer} dw
        +   * @param  {Integer} dh
        +   * @param  {(BLEND|DARKEST|LIGHTEST|DIFFERENCE|MULTIPLY|EXCLUSION|SCREEN|REPLACE|OVERLAY|HARD_LIGHT|SOFT_LIGHT|DODGE|BURN|ADD|NORMAL)} blendMode
        +   */
        +  fn.blend = function(...args) {
        +    // p5._validateParameters('blend', args);
        +    if (this._renderer) {
        +      this._renderer.blend(...args);
        +    } else {
        +      p5.Renderer2D.prototype.blend.apply(this, args);
        +    }
        +  };
         
        -/**
        - * Copies pixels from a source image to a region of the canvas.
        - *
        - * The first parameter, `srcImage`, is the
        - * <a href="#/p5.Image">p5.Image</a> object to blend. The source image can be
        - * the canvas itself or a
        - * <a href="#/p5.Image">p5.Image</a> object. `copy()` will scale pixels from
        - * the source region if it isn't the same size as the destination region.
        - *
        - * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region
        - * to copy from the source image. `(sx, sy)` is the top-left corner of the
        - * region. `sw` and `sh` are the region's width and height.
        - *
        - * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region
        - * of the canvas to copy into. `(dx, dy)` is the top-left corner of the
        - * region. `dw` and `dh` are the region's width and height.
        - *
        - * @method copy
        - * @param  {p5.Image|p5.Element} srcImage source image.
        - * @param  {Integer} sx x-coordinate of the source's upper-left corner.
        - * @param  {Integer} sy y-coordinate of the source's upper-left corner.
        - * @param  {Integer} sw source image width.
        - * @param  {Integer} sh source image height.
        - * @param  {Integer} dx x-coordinate of the destination's upper-left corner.
        - * @param  {Integer} dy y-coordinate of the destination's upper-left corner.
        - * @param  {Integer} dw destination image width.
        - * @param  {Integer} dh destination image height.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/rockies.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use the mountains as the background.
        - *   background(img);
        - *
        - *   // Copy a region of pixels to another spot.
        - *   copy(img, 7, 22, 10, 10, 35, 25, 50, 50);
        - *
        - *   // Outline the copied region.
        - *   stroke(255);
        - *   noFill();
        - *   square(7, 22, 10);
        - *
        - *   describe('An image of a mountain landscape. A square region is outlined in white. A larger square contains a pixelated view of the outlined region.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method copy
        - * @param  {Integer} sx
        - * @param  {Integer} sy
        - * @param  {Integer} sw
        - * @param  {Integer} sh
        - * @param  {Integer} dx
        - * @param  {Integer} dy
        - * @param  {Integer} dw
        - * @param  {Integer} dh
        - */
        -p5.prototype.copy = function(...args) {
        -  p5._validateParameters('copy', args);
        +  /**
        +   * Copies pixels from a source image to a region of the canvas.
        +   *
        +   * The first parameter, `srcImage`, is the
        +   * <a href="#/p5.Image">p5.Image</a> object to blend. The source image can be
        +   * the canvas itself or a
        +   * <a href="#/p5.Image">p5.Image</a> object. `copy()` will scale pixels from
        +   * the source region if it isn't the same size as the destination region.
        +   *
        +   * The next four parameters, `sx`, `sy`, `sw`, and `sh` determine the region
        +   * to copy from the source image. `(sx, sy)` is the top-left corner of the
        +   * region. `sw` and `sh` are the region's width and height.
        +   *
        +   * The next four parameters, `dx`, `dy`, `dw`, and `dh` determine the region
        +   * of the canvas to copy into. `(dx, dy)` is the top-left corner of the
        +   * region. `dw` and `dh` are the region's width and height.
        +   *
        +   * @method copy
        +   * @param  {p5.Image|p5.Element} srcImage source image.
        +   * @param  {Integer} sx x-coordinate of the source's upper-left corner.
        +   * @param  {Integer} sy y-coordinate of the source's upper-left corner.
        +   * @param  {Integer} sw source image width.
        +   * @param  {Integer} sh source image height.
        +   * @param  {Integer} dx x-coordinate of the destination's upper-left corner.
        +   * @param  {Integer} dy y-coordinate of the destination's upper-left corner.
        +   * @param  {Integer} dw destination image width.
        +   * @param  {Integer} dh destination image height.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/rockies.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use the mountains as the background.
        +   *   background(img);
        +   *
        +   *   // Copy a region of pixels to another spot.
        +   *   copy(img, 7, 22, 10, 10, 35, 25, 50, 50);
        +   *
        +   *   // Outline the copied region.
        +   *   stroke(255);
        +   *   noFill();
        +   *   square(7, 22, 10);
        +   *
        +   *   describe('An image of a mountain landscape. A square region is outlined in white. A larger square contains a pixelated view of the outlined region.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method copy
        +   * @param  {Integer} sx
        +   * @param  {Integer} sy
        +   * @param  {Integer} sw
        +   * @param  {Integer} sh
        +   * @param  {Integer} dx
        +   * @param  {Integer} dy
        +   * @param  {Integer} dw
        +   * @param  {Integer} dh
        +   */
        +  fn.copy = function(...args) {
        +    // p5._validateParameters('copy', args);
         
        -  let srcImage, sx, sy, sw, sh, dx, dy, dw, dh;
        -  if (args.length === 9) {
        -    srcImage = args[0];
        -    sx = args[1];
        -    sy = args[2];
        -    sw = args[3];
        -    sh = args[4];
        -    dx = args[5];
        -    dy = args[6];
        -    dw = args[7];
        -    dh = args[8];
        -  } else if (args.length === 8) {
        -    srcImage = this;
        -    sx = args[0];
        -    sy = args[1];
        -    sw = args[2];
        -    sh = args[3];
        -    dx = args[4];
        -    dy = args[5];
        -    dw = args[6];
        -    dh = args[7];
        -  } else {
        -    throw new Error('Signature not supported');
        -  }
        +    let srcImage, sx, sy, sw, sh, dx, dy, dw, dh;
        +    if (args.length === 9) {
        +      srcImage = args[0];
        +      sx = args[1];
        +      sy = args[2];
        +      sw = args[3];
        +      sh = args[4];
        +      dx = args[5];
        +      dy = args[6];
        +      dw = args[7];
        +      dh = args[8];
        +    } else if (args.length === 8) {
        +      srcImage = this;
        +      sx = args[0];
        +      sy = args[1];
        +      sw = args[2];
        +      sh = args[3];
        +      dx = args[4];
        +      dy = args[5];
        +      dw = args[6];
        +      dh = args[7];
        +    } else {
        +      throw new Error('Signature not supported');
        +    }
         
        -  p5.prototype._copyHelper(this, srcImage, sx, sy, sw, sh, dx, dy, dw, dh);
        -};
        +    fn._copyHelper(this, srcImage, sx, sy, sw, sh, dx, dy, dw, dh);
        +  };
         
        -p5.prototype._copyHelper = (
        -  dstImage,
        -  srcImage,
        -  sx,
        -  sy,
        -  sw,
        -  sh,
        -  dx,
        -  dy,
        -  dw,
        -  dh
        -) => {
        -  const s = srcImage.canvas.width / srcImage.width;
        -  // adjust coord system for 3D when renderer
        -  // ie top-left = -width/2, -height/2
        -  let sxMod = 0;
        -  let syMod = 0;
        -  if (srcImage._renderer && srcImage._renderer.isP3D) {
        -    sxMod = srcImage.width / 2;
        -    syMod = srcImage.height / 2;
        -  }
        -  if (dstImage._renderer && dstImage._renderer.isP3D) {
        -    dstImage.push();
        -    dstImage.resetMatrix();
        -    dstImage.noLights();
        -    dstImage.blendMode(dstImage.BLEND);
        -    dstImage.imageMode(dstImage.CORNER);
        -    p5.RendererGL.prototype.image.call(
        -      dstImage._renderer,
        -      srcImage,
        -      sx + sxMod,
        -      sy + syMod,
        -      sw,
        -      sh,
        -      dx,
        -      dy,
        -      dw,
        -      dh
        -    );
        -    dstImage.pop();
        -  } else {
        -    dstImage.drawingContext.drawImage(
        -      srcImage.canvas,
        -      s * (sx + sxMod),
        -      s * (sy + syMod),
        -      s * sw,
        -      s * sh,
        -      dx,
        -      dy,
        -      dw,
        -      dh
        -    );
        -  }
        -};
        +  fn._copyHelper = (
        +    dstImage,
        +    srcImage,
        +    sx,
        +    sy,
        +    sw,
        +    sh,
        +    dx,
        +    dy,
        +    dw,
        +    dh
        +  ) => {
        +    const s = srcImage.canvas.width / srcImage.width;
        +    // adjust coord system for 3D when renderer
        +    // ie top-left = -width/2, -height/2
        +    let sxMod = 0;
        +    let syMod = 0;
        +    if (srcImage._renderer && srcImage._renderer.isP3D) {
        +      sxMod = srcImage.width / 2;
        +      syMod = srcImage.height / 2;
        +    }
        +    if (dstImage._renderer && dstImage._renderer.isP3D) {
        +      dstImage.push();
        +      dstImage.resetMatrix();
        +      dstImage.noLights();
        +      dstImage.blendMode(dstImage.BLEND);
        +      dstImage.imageMode(dstImage.CORNER);
        +      dstImage._renderer.image(
        +        srcImage,
        +        sx + sxMod,
        +        sy + syMod,
        +        sw,
        +        sh,
        +        dx,
        +        dy,
        +        dw,
        +        dh
        +      );
        +      dstImage.pop();
        +    } else {
        +      dstImage.drawingContext.drawImage(
        +        srcImage.canvas,
        +        s * (sx + sxMod),
        +        s * (sy + syMod),
        +        s * sw,
        +        s * sh,
        +        dx,
        +        dy,
        +        dw,
        +        dh
        +      );
        +    }
        +  };
         
        -/**
        - * Applies an image filter to the canvas.
        - *
        - * The preset options are:
        - *
        - * `INVERT`
        - * Inverts the colors in the image. No parameter is used.
        - *
        - * `GRAY`
        - * Converts the image to grayscale. No parameter is used.
        - *
        - * `THRESHOLD`
        - * Converts the image to black and white. Pixels with a grayscale value
        - * above a given threshold are converted to white. The rest are converted to
        - * black. The threshold must be between 0.0 (black) and 1.0 (white). If no
        - * value is specified, 0.5 is used.
        - *
        - * `OPAQUE`
        - * Sets the alpha channel to entirely opaque. No parameter is used.
        - *
        - * `POSTERIZE`
        - * Limits the number of colors in the image. Each color channel is limited to
        - * the number of colors specified. Values between 2 and 255 are valid, but
        - * results are most noticeable with lower values. The default value is 4.
        - *
        - * `BLUR`
        - * Blurs the image. The level of blurring is specified by a blur radius. Larger
        - * values increase the blur. The default value is 4. A gaussian blur is used
        - * in `P2D` mode. A box blur is used in `WEBGL` mode.
        - *
        - * `ERODE`
        - * Reduces the light areas. No parameter is used.
        - *
        - * `DILATE`
        - * Increases the light areas. No parameter is used.
        - *
        - * `filter()` uses WebGL in the background by default because it's faster.
        - * This can be disabled in `P2D` mode by adding a `false` argument, as in
        - * `filter(BLUR, false)`. This may be useful to keep computation off the GPU
        - * or to work around a lack of WebGL support.
        - *
        - * In WebgL mode, `filter()` can also use custom shaders. See
        - * <a href="#/p5/createFilterShader">createFilterShader()</a> for more
        - * information.
        - *
        - *
        - * @method filter
        - * @param  {Constant} filterType  either THRESHOLD, GRAY, OPAQUE, INVERT,
        - *                                POSTERIZE, BLUR, ERODE, DILATE or BLUR.
        - * @param  {Number} [filterParam] parameter unique to each filter.
        - * @param  {Boolean} [useWebGL]   flag to control whether to use fast
        - *                                WebGL filters (GPU) or original image
        - *                                filters (CPU); defaults to `true`.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Apply the INVERT filter.
        - *   filter(INVERT);
        - *
        - *   describe('A blue brick wall.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Apply the GRAY filter.
        - *   filter(GRAY);
        - *
        - *   describe('A brick wall drawn in grayscale.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Apply the THRESHOLD filter.
        - *   filter(THRESHOLD);
        - *
        - *   describe('A brick wall drawn in black and white.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Apply the OPAQUE filter.
        - *   filter(OPAQUE);
        - *
        - *   describe('A red brick wall.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Apply the POSTERIZE filter.
        - *   filter(POSTERIZE, 3);
        - *
        - *   describe('An image of a red brick wall drawn with limited color palette.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Apply the BLUR filter.
        - *   filter(BLUR, 3);
        - *
        - *   describe('A blurry image of a red brick wall.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Apply the DILATE filter.
        - *   filter(DILATE);
        - *
        - *   describe('A red brick wall with bright lines between each brick.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Apply the ERODE filter.
        - *   filter(ERODE);
        - *
        - *   describe('A red brick wall with faint lines between each brick.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Apply the BLUR filter.
        - *   // Don't use WebGL.
        - *   filter(BLUR, 3, false);
        - *
        - *   describe('A blurry image of a red brick wall.');
        - * }
        - * </code>
        - * </div>
        - */
        +  /**
        +   * Applies an image filter to the canvas.
        +   *
        +   * The preset options are:
        +   *
        +   * `INVERT`
        +   * Inverts the colors in the image. No parameter is used.
        +   *
        +   * `GRAY`
        +   * Converts the image to grayscale. No parameter is used.
        +   *
        +   * `THRESHOLD`
        +   * Converts the image to black and white. Pixels with a grayscale value
        +   * above a given threshold are converted to white. The rest are converted to
        +   * black. The threshold must be between 0.0 (black) and 1.0 (white). If no
        +   * value is specified, 0.5 is used.
        +   *
        +   * `OPAQUE`
        +   * Sets the alpha channel to entirely opaque. No parameter is used.
        +   *
        +   * `POSTERIZE`
        +   * Limits the number of colors in the image. Each color channel is limited to
        +   * the number of colors specified. Values between 2 and 255 are valid, but
        +   * results are most noticeable with lower values. The default value is 4.
        +   *
        +   * `BLUR`
        +   * Blurs the image. The level of blurring is specified by a blur radius. Larger
        +   * values increase the blur. The default value is 4. A gaussian blur is used
        +   * in `P2D` mode. A box blur is used in `WEBGL` mode.
        +   *
        +   * `ERODE`
        +   * Reduces the light areas. No parameter is used.
        +   *
        +   * `DILATE`
        +   * Increases the light areas. No parameter is used.
        +   *
        +   * `filter()` uses WebGL in the background by default because it's faster.
        +   * This can be disabled in `P2D` mode by adding a `false` argument, as in
        +   * `filter(BLUR, false)`. This may be useful to keep computation off the GPU
        +   * or to work around a lack of WebGL support.
        +   *
        +   * In WebgL mode, `filter()` can also use custom shaders. See
        +   * <a href="#/p5/createFilterShader">createFilterShader()</a> for more
        +   * information.
        +   *
        +   *
        +   * @method filter
        +   * @param  {(THRESHOLD|GRAY|OPAQUE|INVERT|POSTERIZE|BLUR|ERODE|DILATE|BLUR)} filterType  either THRESHOLD, GRAY, OPAQUE, INVERT,
        +   *                                POSTERIZE, BLUR, ERODE, DILATE or BLUR.
        +   * @param  {Number} [filterParam] parameter unique to each filter.
        +   * @param  {Boolean} [useWebGL=true]   flag to control whether to use fast
        +   *                                WebGL filters (GPU) or original image
        +   *                                filters (CPU); defaults to `true`.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Apply the INVERT filter.
        +   *   filter(INVERT);
        +   *
        +   *   describe('A blue brick wall.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Apply the GRAY filter.
        +   *   filter(GRAY);
        +   *
        +   *   describe('A brick wall drawn in grayscale.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Apply the THRESHOLD filter.
        +   *   filter(THRESHOLD);
        +   *
        +   *   describe('A brick wall drawn in black and white.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Apply the OPAQUE filter.
        +   *   filter(OPAQUE);
        +   *
        +   *   describe('A red brick wall.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Apply the POSTERIZE filter.
        +   *   filter(POSTERIZE, 3);
        +   *
        +   *   describe('An image of a red brick wall drawn with limited color palette.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Apply the BLUR filter.
        +   *   filter(BLUR, 3);
        +   *
        +   *   describe('A blurry image of a red brick wall.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Apply the DILATE filter.
        +   *   filter(DILATE);
        +   *
        +   *   describe('A red brick wall with bright lines between each brick.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Apply the ERODE filter.
        +   *   filter(ERODE);
        +   *
        +   *   describe('A red brick wall with faint lines between each brick.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/bricks.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Apply the BLUR filter.
        +   *   // Don't use WebGL.
        +   *   filter(BLUR, 3, false);
        +   *
        +   *   describe('A blurry image of a red brick wall.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -/**
        - * @method getFilterGraphicsLayer
        - * @private
        - * @returns {p5.Graphics}
        - */
        -p5.prototype.getFilterGraphicsLayer = function() {
        -  return this._renderer.getFilterGraphicsLayer();
        -};
        +  /**
        +   * @method getFilterGraphicsLayer
        +   * @private
        +   * @returns {p5.Graphics}
        +   */
        +  fn.getFilterGraphicsLayer = function() {
        +    return this._renderer.getFilterGraphicsLayer();
        +  };
         
        -/**
        - * @method filter
        - * @param  {Constant} filterType
        - * @param  {Boolean} [useWebGL]
        - */
        -/**
        - * @method filter
        - * @param {p5.Shader}  shaderFilter  shader that's been loaded, with the
        - *                                   frag shader using a `tex0` uniform.
        - */
        -p5.prototype.filter = function(...args) {
        -  p5._validateParameters('filter', args);
        +  /**
        +   * @method filter
        +   * @param  {(THRESHOLD|GRAY|OPAQUE|INVERT|POSTERIZE|BLUR|ERODE|DILATE|BLUR)} filterType
        +   * @param  {Number} [filterParam]
        +   * @param  {Boolean} [useWebGL=true]
        +   */
        +  /**
        +   * @method filter
        +   * @param {p5.Shader}  shaderFilter  shader that's been loaded, with the
        +   *                                   frag shader using a `tex0` uniform.
        +   */
        +  fn.filter = function(...args) {
        +    // p5._validateParameters('filter', args);
         
        -  let { shader, operation, value, useWebGL } = parseFilterArgs(...args);
        +    let { shader, operation, value, useWebGL } = parseFilterArgs(...args);
         
        -  // when passed a shader, use it directly
        -  if (this._renderer.isP3D && shader) {
        -    p5.RendererGL.prototype.filter.call(this._renderer, shader);
        -    return;
        -  }
        +    // when passed a shader, use it directly
        +    if (this._renderer.isP3D && shader) {
        +      this._renderer.filter(shader);
        +      return;
        +    }
         
        -  // when opting out of webgl, use old pixels method
        -  if (!useWebGL && !this._renderer.isP3D) {
        -    if (this.canvas !== undefined) {
        -      Filters.apply(this.canvas, Filters[operation], value);
        -    } else {
        -      Filters.apply(this.elt, Filters[operation], value);
        +    // when opting out of webgl, use old pixels method
        +    if (!useWebGL && !this._renderer.isP3D) {
        +      if (this.canvas !== undefined) {
        +        Filters.apply(this.canvas, Filters[operation], value);
        +      } else {
        +        Filters.apply(this.elt, Filters[operation], value);
        +      }
        +      return;
             }
        -    return;
        -  }
         
        -  if(!useWebGL && this._renderer.isP3D) {
        -    console.warn('filter() with useWebGL=false is not supported in WEBGL');
        -  }
        +    if(!useWebGL && this._renderer.isP3D) {
        +      console.warn('filter() with useWebGL=false is not supported in WEBGL');
        +    }
         
        -  // when this is a webgl renderer, apply constant shader filter
        -  if (this._renderer.isP3D) {
        -    p5.RendererGL.prototype.filter.call(this._renderer, operation, value);
        -  }
        +    // when this is a webgl renderer, apply constant shader filter
        +    if (this._renderer.isP3D) {
        +      this._renderer.filter(operation, value);
        +    }
         
        -  // when this is P2D renderer, create/use hidden webgl renderer
        -  else {
        -    const filterGraphicsLayer = this.getFilterGraphicsLayer();
        +    // when this is P2D renderer, create/use hidden webgl renderer
        +    else {
         
        -    // copy p2d canvas contents to secondary webgl renderer
        -    // dest
        -    filterGraphicsLayer.copy(
        -      // src
        -      this._renderer,
        -      // src coods
        -      0, 0, this.width, this.height,
        -      // dest coords
        -      -this.width/2, -this.height/2, this.width, this.height
        -    );
        -    //clearing the main canvas
        -    this._renderer.clear();
        +      if (shader) {
        +        this._renderer.filterRenderer.setOperation(operation, value, shader);
        +      } else {
        +        this._renderer.filterRenderer.setOperation(operation, value);
        +      }
         
        -    this._renderer.resetMatrix();
        -    // filter it with shaders
        -    filterGraphicsLayer.filter(...args);
        +      this._renderer.filterRenderer.applyFilter();
        +    }
        +  };
         
        -    // copy secondary webgl renderer back to original p2d canvas
        -    this.copy(
        -      // src
        -      filterGraphicsLayer._renderer,
        -      // src coods
        -      0, 0, this.width, this.height,
        -      // dest coords
        -      0, 0, this.width, this.height
        -    );
        -    filterGraphicsLayer.clear(); // prevent feedback effects on p2d canvas
        -  }
        -};
        +  function parseFilterArgs(...args) {
        +    // args could be:
        +    // - operation, value, [useWebGL]
        +    // - operation, [useWebGL]
        +    // - shader
         
        -function parseFilterArgs(...args) {
        -  // args could be:
        -  // - operation, value, [useWebGL]
        -  // - operation, [useWebGL]
        -  // - shader
        +    let result = {
        +      shader: undefined,
        +      operation: undefined,
        +      value: undefined,
        +      useWebGL: true
        +    };
         
        -  let result = {
        -    shader: undefined,
        -    operation: undefined,
        -    value: undefined,
        -    useWebGL: true
        -  };
        +    if (args[0] instanceof p5.Shader) {
        +      result.shader = args[0];
        +      return result;
        +    }
        +    else {
        +      result.operation = args[0];
        +    }
         
        -  if (args[0] instanceof p5.Shader) {
        -    result.shader = args[0];
        -    return result;
        -  }
        -  else {
        -    result.operation = args[0];
        -  }
        +    if (args.length > 1 && typeof args[1] === 'number') {
        +      result.value = args[1];
        +    }
         
        -  if (args.length > 1 && typeof args[1] === 'number') {
        -    result.value = args[1];
        +    if (args[args.length-1] === false) {
        +      result.useWebGL = false;
        +    }
        +    return result;
           }
         
        -  if (args[args.length-1] === false) {
        -    result.useWebGL = false;
        -  }
        -  return result;
        -}
        +  /**
        +   * Gets a pixel or a region of pixels from the canvas.
        +   *
        +   * `get()` is easy to use but it's not as fast as
        +   * <a href="#/p5/pixels">pixels</a>. Use <a href="#/p5/pixels">pixels</a>
        +   * to read many pixel values.
        +   *
        +   * The version of `get()` with no parameters returns the entire canvas.
        +   *
        +   * The version of `get()` with two parameters interprets them as
        +   * coordinates. It returns an array with the `[R, G, B, A]` values of the
        +   * pixel at the given point.
        +   *
        +   * The version of `get()` with four parameters interprets them as coordinates
        +   * and dimensions. It returns a subsection of the canvas as a
        +   * <a href="#/p5.Image">p5.Image</a> object. The first two parameters are the
        +   * coordinates for the upper-left corner of the subsection. The last two
        +   * parameters are the width and height of the subsection.
        +   *
        +   * Use <a href="#/p5.Image/get">p5.Image.get()</a> to work directly with
        +   * <a href="#/p5.Image">p5.Image</a> objects.
        +   *
        +   * @method get
        +   * @param  {Number}         x x-coordinate of the pixel.
        +   * @param  {Number}         y y-coordinate of the pixel.
        +   * @param  {Number}         w width of the subsection to be returned.
        +   * @param  {Number}         h height of the subsection to be returned.
        +   * @return {p5.Image}       subsection as a <a href="#/p5.Image">p5.Image</a> object.
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/rockies.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Get the entire canvas.
        +   *   let c = get();
        +   *
        +   *   // Display half the canvas.
        +   *   image(c, 50, 0);
        +   *
        +   *   describe('Two identical mountain landscapes shown side-by-side.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/rockies.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Get the color of a pixel.
        +   *   let c = get(50, 90);
        +   *
        +   *   // Style the square with the pixel's color.
        +   *   fill(c);
        +   *   noStroke();
        +   *
        +   *   // Display the square.
        +   *   square(25, 25, 50);
        +   *
        +   *   describe('A mountain landscape with an olive green square in its center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/rockies.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0);
        +   *
        +   *   // Get a region of the image.
        +   *   let c = get(0, 0, 50, 50);
        +   *
        +   *   // Display the region.
        +   *   image(c, 50, 50);
        +   *
        +   *   describe('A mountain landscape drawn on top of another mountain landscape.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method get
        +   * @return {p5.Image}      whole canvas as a <a href="#/p5.Image">p5.Image</a>.
        +   */
        +  /**
        +   * @method get
        +   * @param  {Number}        x
        +   * @param  {Number}        y
        +   * @return {Number[]}      color of the pixel at (x, y) in array format `[R, G, B, A]`.
        +   */
        +  fn.get = function(x, y, w, h) {
        +    // p5._validateParameters('get', arguments);
        +    return this._renderer.get(...arguments);
        +  };
         
        -/**
        - * Gets a pixel or a region of pixels from the canvas.
        - *
        - * `get()` is easy to use but it's not as fast as
        - * <a href="#/p5/pixels">pixels</a>. Use <a href="#/p5/pixels">pixels</a>
        - * to read many pixel values.
        - *
        - * The version of `get()` with no parameters returns the entire canvas.
        - *
        - * The version of `get()` with two parameters interprets them as
        - * coordinates. It returns an array with the `[R, G, B, A]` values of the
        - * pixel at the given point.
        - *
        - * The version of `get()` with four parameters interprets them as coordinates
        - * and dimensions. It returns a subsection of the canvas as a
        - * <a href="#/p5.Image">p5.Image</a> object. The first two parameters are the
        - * coordinates for the upper-left corner of the subsection. The last two
        - * parameters are the width and height of the subsection.
        - *
        - * Use <a href="#/p5.Image/get">p5.Image.get()</a> to work directly with
        - * <a href="#/p5.Image">p5.Image</a> objects.
        - *
        - * @method get
        - * @param  {Number}         x x-coordinate of the pixel.
        - * @param  {Number}         y y-coordinate of the pixel.
        - * @param  {Number}         w width of the subsection to be returned.
        - * @param  {Number}         h height of the subsection to be returned.
        - * @return {p5.Image}       subsection as a <a href="#/p5.Image">p5.Image</a> object.
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/rockies.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Get the entire canvas.
        - *   let c = get();
        - *
        - *   // Display half the canvas.
        - *   image(c, 50, 0);
        - *
        - *   describe('Two identical mountain landscapes shown side-by-side.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/rockies.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Get the color of a pixel.
        - *   let c = get(50, 90);
        - *
        - *   // Style the square with the pixel's color.
        - *   fill(c);
        - *   noStroke();
        - *
        - *   // Display the square.
        - *   square(25, 25, 50);
        - *
        - *   describe('A mountain landscape with an olive green square in its center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/rockies.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0);
        - *
        - *   // Get a region of the image.
        - *   let c = get(0, 0, 50, 50);
        - *
        - *   // Display the region.
        - *   image(c, 50, 50);
        - *
        - *   describe('A mountain landscape drawn on top of another mountain landscape.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method get
        - * @return {p5.Image}      whole canvas as a <a href="#/p5.Image">p5.Image</a>.
        - */
        -/**
        - * @method get
        - * @param  {Number}        x
        - * @param  {Number}        y
        - * @return {Number[]}      color of the pixel at (x, y) in array format `[R, G, B, A]`.
        - */
        -p5.prototype.get = function(x, y, w, h) {
        -  p5._validateParameters('get', arguments);
        -  return this._renderer.get(...arguments);
        -};
        +  /**
        +   * Loads the current value of each pixel on the canvas into the
        +   * <a href="#/p5/pixels">pixels</a> array.
        +   *
        +   * `loadPixels()` must be called before reading from or writing to
        +   * <a href="#/p5/pixels">pixels</a>.
        +   *
        +   * @method loadPixels
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/rockies.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0, 100, 100);
        +   *
        +   *   // Get the pixel density.
        +   *   let d = pixelDensity();
        +   *
        +   *   // Calculate the halfway index in the pixels array.
        +   *   let halfImage = 4 * (d * width) * (d * height / 2);
        +   *
        +   *   // Load the pixels array.
        +   *   loadPixels();
        +   *
        +   *   // Copy the top half of the canvas to the bottom.
        +   *   for (let i = 0; i < halfImage; i += 1) {
        +   *     pixels[i + halfImage] = pixels[i];
        +   *   }
        +   *
        +   *   // Update the canvas.
        +   *   updatePixels();
        +   *
        +   *   describe('Two identical images of mountain landscapes, one on top of the other.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.loadPixels = function(...args) {
        +    // p5._validateParameters('loadPixels', args);
        +    this._renderer.loadPixels();
        +  };
         
        -/**
        - * Loads the current value of each pixel on the canvas into the
        - * <a href="#/p5/pixels">pixels</a> array.
        - *
        - * `loadPixels()` must be called before reading from or writing to
        - * <a href="#/p5/pixels">pixels</a>.
        - *
        - * @method loadPixels
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/rockies.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0, 100, 100);
        - *
        - *   // Get the pixel density.
        - *   let d = pixelDensity();
        - *
        - *   // Calculate the halfway index in the pixels array.
        - *   let halfImage = 4 * (d * width) * (d * height / 2);
        - *
        - *   // Load the pixels array.
        - *   loadPixels();
        - *
        - *   // Copy the top half of the canvas to the bottom.
        - *   for (let i = 0; i < halfImage; i += 1) {
        - *     pixels[i + halfImage] = pixels[i];
        - *   }
        - *
        - *   // Update the canvas.
        - *   updatePixels();
        - *
        - *   describe('Two identical images of mountain landscapes, one on top of the other.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.loadPixels = function(...args) {
        -  p5._validateParameters('loadPixels', args);
        -  this._renderer.loadPixels();
        -};
        +  /**
        +   * Sets the color of a pixel or draws an image to the canvas.
        +   *
        +   * `set()` is easy to use but it's not as fast as
        +   * <a href="#/p5/pixels">pixels</a>. Use <a href="#/p5/pixels">pixels</a>
        +   * to set many pixel values.
        +   *
        +   * `set()` interprets the first two parameters as x- and y-coordinates. It
        +   * interprets the last parameter as a grayscale value, a `[R, G, B, A]` pixel
        +   * array, a <a href="#/p5.Color">p5.Color</a> object, or a
        +   * <a href="#/p5.Image">p5.Image</a> object. If an image is passed, the first
        +   * two parameters set the coordinates for the image's upper-left corner,
        +   * regardless of the current <a href="#/p5/imageMode">imageMode()</a>.
        +   *
        +   * <a href="#/p5/updatePixels">updatePixels()</a> must be called after using
        +   * `set()` for changes to appear.
        +   *
        +   * @method set
        +   * @param {Number}              x x-coordinate of the pixel.
        +   * @param {Number}              y y-coordinate of the pixel.
        +   * @param {Number|Number[]|Object} c grayscale value | pixel array |
        +   *                                <a href="#/p5.Color">p5.Color</a> object | <a href="#/p5.Image">p5.Image</a> to copy.
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set four pixels to black.
        +   *   set(30, 20, 0);
        +   *   set(85, 20, 0);
        +   *   set(85, 75, 0);
        +   *   set(30, 75, 0);
        +   *
        +   *   // Update the canvas.
        +   *   updatePixels();
        +   *
        +   *   describe('Four black dots arranged in a square drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object.
        +   *   let black = color(0);
        +   *
        +   *   // Set four pixels to black.
        +   *   set(30, 20, black);
        +   *   set(85, 20, black);
        +   *   set(85, 75, black);
        +   *   set(30, 75, black);
        +   *
        +   *   // Update the canvas.
        +   *   updatePixels();
        +   *
        +   *   describe('Four black dots arranged in a square drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(255);
        +   *
        +   *   // Draw a horizontal color gradient.
        +   *   for (let x = 0; x < 100; x += 1) {
        +   *     for (let y = 0; y < 100; y += 1) {
        +   *       // Calculate the grayscale value.
        +   *       let c = map(x, 0, 100, 0, 255);
        +   *
        +   *       // Set the pixel using the grayscale value.
        +   *       set(x, y, c);
        +   *     }
        +   *   }
        +   *
        +   *   // Update the canvas.
        +   *   updatePixels();
        +   *
        +   *   describe('A horiztonal color gradient from black to white.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/rockies.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use the image to set all pixels.
        +   *   set(0, 0, img);
        +   *
        +   *   // Update the canvas.
        +   *   updatePixels();
        +   *
        +   *   describe('An image of a mountain landscape.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.set = function(x, y, imgOrCol) {
        +    this._renderer.set(x, y, imgOrCol);
        +  };
         
        -/**
        - * Sets the color of a pixel or draws an image to the canvas.
        - *
        - * `set()` is easy to use but it's not as fast as
        - * <a href="#/p5/pixels">pixels</a>. Use <a href="#/p5/pixels">pixels</a>
        - * to set many pixel values.
        - *
        - * `set()` interprets the first two parameters as x- and y-coordinates. It
        - * interprets the last parameter as a grayscale value, a `[R, G, B, A]` pixel
        - * array, a <a href="#/p5.Color">p5.Color</a> object, or a
        - * <a href="#/p5.Image">p5.Image</a> object. If an image is passed, the first
        - * two parameters set the coordinates for the image's upper-left corner,
        - * regardless of the current <a href="#/p5/imageMode">imageMode()</a>.
        - *
        - * <a href="#/p5/updatePixels">updatePixels()</a> must be called after using
        - * `set()` for changes to appear.
        - *
        - * @method set
        - * @param {Number}              x x-coordinate of the pixel.
        - * @param {Number}              y y-coordinate of the pixel.
        - * @param {Number|Number[]|Object} c grayscale value | pixel array |
        - *                                <a href="#/p5.Color">p5.Color</a> object | <a href="#/p5.Image">p5.Image</a> to copy.
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Set four pixels to black.
        - *   set(30, 20, 0);
        - *   set(85, 20, 0);
        - *   set(85, 75, 0);
        - *   set(30, 75, 0);
        - *
        - *   // Update the canvas.
        - *   updatePixels();
        - *
        - *   describe('Four black dots arranged in a square drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object.
        - *   let black = color(0);
        - *
        - *   // Set four pixels to black.
        - *   set(30, 20, black);
        - *   set(85, 20, black);
        - *   set(85, 75, black);
        - *   set(30, 75, black);
        - *
        - *   // Update the canvas.
        - *   updatePixels();
        - *
        - *   describe('Four black dots arranged in a square drawn on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(255);
        - *
        - *   // Draw a horizontal color gradient.
        - *   for (let x = 0; x < 100; x += 1) {
        - *     for (let y = 0; y < 100; y += 1) {
        - *       // Calculate the grayscale value.
        - *       let c = map(x, 0, 100, 0, 255);
        - *
        - *       // Set the pixel using the grayscale value.
        - *       set(x, y, c);
        - *     }
        - *   }
        - *
        - *   // Update the canvas.
        - *   updatePixels();
        - *
        - *   describe('A horiztonal color gradient from black to white.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/rockies.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use the image to set all pixels.
        - *   set(0, 0, img);
        - *
        - *   // Update the canvas.
        - *   updatePixels();
        - *
        - *   describe('An image of a mountain landscape.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.set = function(x, y, imgOrCol) {
        -  this._renderer.set(x, y, imgOrCol);
        -};
        +  /**
        +   * Updates the canvas with the RGBA values in the
        +   * <a href="#/p5/pixels">pixels</a> array.
        +   *
        +   * `updatePixels()` only needs to be called after changing values in the
        +   * <a href="#/p5/pixels">pixels</a> array. Such changes can be made directly
        +   * after calling <a href="#/p5/loadPixels">loadPixels()</a> or by calling
        +   * <a href="#/p5/set">set()</a>.
        +   *
        +   * @method updatePixels
        +   * @param  {Number} [x]    x-coordinate of the upper-left corner of region
        +   *                         to update.
        +   * @param  {Number} [y]    y-coordinate of the upper-left corner of region
        +   *                         to update.
        +   * @param  {Number} [w]    width of region to update.
        +   * @param  {Number} [h]    height of region to update.
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load the image.
        +   * function preload() {
        +   *   img = loadImage('assets/rockies.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Display the image.
        +   *   image(img, 0, 0, 100, 100);
        +   *
        +   *   // Get the pixel density.
        +   *   let d = pixelDensity();
        +   *
        +   *   // Calculate the halfway index in the pixels array.
        +   *   let halfImage = 4 * (d * width) * (d * height / 2);
        +   *
        +   *   // Load the pixels array.
        +   *   loadPixels();
        +   *
        +   *   // Copy the top half of the canvas to the bottom.
        +   *   for (let i = 0; i < halfImage; i += 1) {
        +   *     pixels[i + halfImage] = pixels[i];
        +   *   }
        +   *
        +   *   // Update the canvas.
        +   *   updatePixels();
        +   *
        +   *   describe('Two identical images of mountain landscapes, one on top of the other.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.updatePixels = function(x, y, w, h) {
        +    // p5._validateParameters('updatePixels', arguments);
        +    // graceful fail - if loadPixels() or set() has not been called, pixel
        +    // array will be empty, ignore call to updatePixels()
        +    if (this.pixels.length === 0) {
        +      return;
        +    }
        +    this._renderer.updatePixels(x, y, w, h);
        +  };
        +}
         
        -/**
        - * Updates the canvas with the RGBA values in the
        - * <a href="#/p5/pixels">pixels</a> array.
        - *
        - * `updatePixels()` only needs to be called after changing values in the
        - * <a href="#/p5/pixels">pixels</a> array. Such changes can be made directly
        - * after calling <a href="#/p5/loadPixels">loadPixels()</a> or by calling
        - * <a href="#/p5/set">set()</a>.
        - *
        - * @method updatePixels
        - * @param  {Number} [x]    x-coordinate of the upper-left corner of region
        - *                         to update.
        - * @param  {Number} [y]    y-coordinate of the upper-left corner of region
        - *                         to update.
        - * @param  {Number} [w]    width of region to update.
        - * @param  {Number} [h]    height of region to update.
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load the image.
        - * function preload() {
        - *   img = loadImage('assets/rockies.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Display the image.
        - *   image(img, 0, 0, 100, 100);
        - *
        - *   // Get the pixel density.
        - *   let d = pixelDensity();
        - *
        - *   // Calculate the halfway index in the pixels array.
        - *   let halfImage = 4 * (d * width) * (d * height / 2);
        - *
        - *   // Load the pixels array.
        - *   loadPixels();
        - *
        - *   // Copy the top half of the canvas to the bottom.
        - *   for (let i = 0; i < halfImage; i += 1) {
        - *     pixels[i + halfImage] = pixels[i];
        - *   }
        - *
        - *   // Update the canvas.
        - *   updatePixels();
        - *
        - *   describe('Two identical images of mountain landscapes, one on top of the other.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.updatePixels = function(x, y, w, h) {
        -  p5._validateParameters('updatePixels', arguments);
        -  // graceful fail - if loadPixels() or set() has not been called, pixel
        -  // array will be empty, ignore call to updatePixels()
        -  if (this.pixels.length === 0) {
        -    return;
        -  }
        -  this._renderer.updatePixels(x, y, w, h);
        -};
        +export default pixels;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  pixels(p5, p5.prototype);
        +}
        diff --git a/src/io/csv.js b/src/io/csv.js
        new file mode 100644
        index 0000000000..4b1c2924fb
        --- /dev/null
        +++ b/src/io/csv.js
        @@ -0,0 +1,211 @@
        +/*
        +The MIT License (MIT)
        +
        +Copyright (c) 2019 Evan Plaice <evanplaice@gmail.com>
        +
        +Permission is hereby granted, free of charge, to any person obtaining
        +a copy of this software and associated documentation files (the
        +'Software'), to deal in the Software without restriction, including
        +without limitation the rights to use, copy, modify, merge, publish,
        +distribute, sublicense, and/or sell copies of the Software, and to
        +permit persons to whom the Software is furnished to do so, subject to
        +the following conditions:
        +
        +The above copyright notice and this permission notice shall be
        +included in all copies or substantial portions of the Software.
        +
        +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
        +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
        +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
        +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
        +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
        +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
        +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
        +*/
        +export function parse (csv, options, reviver = v => v) {
        +  const ctx = Object.create(null)
        +  ctx.options = options || {}
        +  ctx.reviver = reviver
        +  ctx.value = ''
        +  ctx.entry = []
        +  ctx.output = []
        +  ctx.col = 1
        +  ctx.row = 1
        +
        +  ctx.options.delimiter = ctx.options.delimiter === undefined ? '"' : options.delimiter;
        +  if(ctx.options.delimiter.length > 1 || ctx.options.delimiter.length === 0)
        +    throw Error(`CSVError: delimiter must be one character [${ctx.options.separator}]`)
        +
        +  ctx.options.separator = ctx.options.separator === undefined ? ',' : options.separator;
        +  if(ctx.options.separator.length > 1 || ctx.options.separator.length === 0)
        +    throw Error(`CSVError: separator must be one character [${ctx.options.separator}]`)
        +
        +  const lexer = new RegExp(`${escapeRegExp(ctx.options.delimiter)}|${escapeRegExp(ctx.options.separator)}|\r\n|\n|\r|[^${escapeRegExp(ctx.options.delimiter)}${escapeRegExp(ctx.options.separator)}\r\n]+`, 'y')
        +  const isNewline = /^(\r\n|\n|\r)$/
        +
        +  let matches = []
        +  let match = ''
        +  let state = 0
        +
        +  while ((matches = lexer.exec(csv)) !== null) {
        +    match = matches[0]
        +
        +    switch (state) {
        +      case 0: // start of entry
        +        switch (true) {
        +          case match === ctx.options.delimiter:
        +            state = 3
        +            break
        +          case match === ctx.options.separator:
        +            state = 0
        +            valueEnd(ctx)
        +            break
        +          case isNewline.test(match):
        +            state = 0
        +            valueEnd(ctx)
        +            entryEnd(ctx)
        +            break
        +          default:
        +            ctx.value += match
        +            state = 2
        +            break
        +        }
        +        break
        +      case 2: // un-delimited input
        +        switch (true) {
        +          case match === ctx.options.separator:
        +            state = 0
        +            valueEnd(ctx)
        +            break
        +          case isNewline.test(match):
        +            state = 0
        +            valueEnd(ctx)
        +            entryEnd(ctx)
        +            break
        +          default:
        +            state = 4
        +            throw Error(`CSVError: Illegal state [row:${ctx.row}, col:${ctx.col}]`)
        +        }
        +        break
        +      case 3: // delimited input
        +        switch (true) {
        +          case match === ctx.options.delimiter:
        +            state = 4
        +            break
        +          default:
        +            state = 3
        +            ctx.value += match
        +            break
        +        }
        +        break
        +      case 4: // escaped or closing delimiter
        +        switch (true) {
        +          case match === ctx.options.delimiter:
        +            state = 3
        +            ctx.value += match
        +            break
        +          case match === ctx.options.separator:
        +            state = 0
        +            valueEnd(ctx)
        +            break
        +          case isNewline.test(match):
        +            state = 0
        +            valueEnd(ctx)
        +            entryEnd(ctx)
        +            break
        +          default:
        +            throw Error(`CSVError: Illegal state [row:${ctx.row}, col:${ctx.col}]`)
        +        }
        +        break
        +    }
        +  }
        +
        +  // flush the last value
        +  if (ctx.entry.length !== 0) {
        +    valueEnd(ctx)
        +    entryEnd(ctx)
        +  }
        +
        +  return ctx.output
        +}
        +
        +export function stringify (array, options = {}, replacer = v => v) {
        +  const ctx = Object.create(null)
        +  ctx.options = options
        +  ctx.options.eof = ctx.options.eof !== undefined ? ctx.options.eof : true
        +  ctx.row = 1
        +  ctx.col = 1
        +  ctx.output = ''
        +
        +  ctx.options.delimiter = ctx.options.delimiter === undefined ? '"' : options.delimiter;
        +  if(ctx.options.delimiter.length > 1 || ctx.options.delimiter.length === 0)
        +    throw Error(`CSVError: delimiter must be one character [${ctx.options.separator}]`)
        +
        +  ctx.options.separator = ctx.options.separator === undefined ? ',' : options.separator;
        +  if(ctx.options.separator.length > 1 || ctx.options.separator.length === 0)
        +    throw Error(`CSVError: separator must be one character [${ctx.options.separator}]`)
        +
        +  const needsDelimiters = new RegExp(`${escapeRegExp(ctx.options.delimiter)}|${escapeRegExp(ctx.options.separator)}|\r\n|\n|\r`)
        +
        +  array.forEach((row, rIdx) => {
        +    let entry = ''
        +    ctx.col = 1
        +    row.forEach((col, cIdx) => {
        +      if (typeof col === 'string') {
        +        col = col.replace(new RegExp(ctx.options.delimiter, 'g'), `${ctx.options.delimiter}${ctx.options.delimiter}`)
        +        col = needsDelimiters.test(col) ? `${ctx.options.delimiter}${col}${ctx.options.delimiter}` : col
        +      }
        +      entry += replacer(col, ctx.row, ctx.col)
        +      if (cIdx !== row.length - 1) {
        +        entry += ctx.options.separator
        +      }
        +      ctx.col++
        +    })
        +    switch (true) {
        +      case ctx.options.eof:
        +      case !ctx.options.eof && rIdx !== array.length - 1:
        +        ctx.output += `${entry}\n`
        +        break
        +      default:
        +        ctx.output += `${entry}`
        +        break
        +    }
        +    ctx.row++
        +  })
        +
        +  return ctx.output
        +}
        +
        +function valueEnd (ctx) {
        +  const value = ctx.options.typed ? inferType(ctx.value) : ctx.value
        +  ctx.entry.push(ctx.reviver(value, ctx.row, ctx.col))
        +  ctx.value = ''
        +  ctx.col++
        +}
        +
        +function entryEnd (ctx) {
        +  ctx.output.push(ctx.entry)
        +  ctx.entry = []
        +  ctx.row++
        +  ctx.col = 1
        +}
        +
        +function inferType (value) {
        +  const isNumber = /.\./
        +
        +  switch (true) {
        +    case value === 'true':
        +    case value === 'false':
        +      return value === 'true'
        +    case isNumber.test(value):
        +      return parseFloat(value)
        +    case isFinite(value):
        +      return parseInt(value)
        +    default:
        +      return value
        +  }
        +}
        +
        +function escapeRegExp(str) {
        +  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
        +}
        diff --git a/src/io/files.js b/src/io/files.js
        index 64c82d3171..a73a169daf 100644
        --- a/src/io/files.js
        +++ b/src/io/files.js
        @@ -5,1631 +5,1287 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        -import 'whatwg-fetch';
        -import 'es6-promise/auto';
        -import fetchJsonp from 'fetch-jsonp';
        -import fileSaver from 'file-saver';
        -import '../core/friendly_errors/validate_params';
        -import '../core/friendly_errors/file_errors';
        -import '../core/friendly_errors/fes_core';
        +import * as fileSaver from 'file-saver';
        +import { Renderer } from '../core/p5.Renderer';
        +import { Graphics } from '../core/p5.Graphics';
        +import { parse } from './csv';
        +
        +class HTTPError extends Error {
        +  status;
        +  response;
        +  ok;
        +}
         
        -/**
        - * Loads a JSON file to create an `Object`.
        - *
        - * JavaScript Object Notation
        - * (<a href="https://developer.mozilla.org/en-US/docs/Glossary/JSON" target="_blank">JSON</a>)
        - * is a standard format for sending data between applications. The format is
        - * based on JavaScript objects which have keys and values. JSON files store
        - * data in an object with strings as keys. Values can be strings, numbers,
        - * Booleans, arrays, `null`, or other objects.
        - *
        - * The first parameter, `path`, is always a string with the path to the file.
        - * Paths to local files should be relative, as in
        - * `loadJSON('assets/data.json')`. URLs such as
        - * `'https://example.com/data.json'` may be blocked due to browser security.
        - *
        - * The second parameter, `successCallback`, is optional. If a function is
        - * passed, as in `loadJSON('assets/data.json', handleData)`, then the
        - * `handleData()` function will be called once the data loads. The object
        - * created from the JSON data will be passed to `handleData()` as its only argument.
        - *
        - * The third parameter, `failureCallback`, is also optional. If a function is
        - * passed, as in `loadJSON('assets/data.json', handleData, handleFailure)`,
        - * then the `handleFailure()` function will be called if an error occurs while
        - * loading. The `Error` object will be passed to `handleFailure()` as its only
        - * argument.
        - *
        - * Note: Data can take time to load. Calling `loadJSON()` within
        - * <a href="#/p5/preload">preload()</a> ensures data loads before it's used in
        - * <a href="#/p5/setup">setup()</a> or <a href="#/p5/draw">draw()</a>.
        - *
        - * @method loadJSON
        - * @param  {String} path path of the JSON file to be loaded.
        - * @param  {function} [successCallback] function to call once the data is loaded. Will be passed the object.
        - * @param  {function} [errorCallback] function to call if the data fails to load. Will be passed an `Error` event object.
        - * @return {Object} object containing the loaded data.
        - *
        - * @example
        - *
        - * <div>
        - * <code>
        - * let myData;
        - *
        - * // Load the JSON and create an object.
        - * function preload() {
        - *   myData = loadJSON('assets/data.json');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the circle.
        - *   fill(myData.color);
        - *   noStroke();
        - *
        - *   // Draw the circle.
        - *   circle(myData.x, myData.y, myData.d);
        - *
        - *   describe('A pink circle on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myData;
        - *
        - * // Load the JSON and create an object.
        - * function preload() {
        - *   myData = loadJSON('assets/data.json');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Color object and make it transparent.
        - *   let c = color(myData.color);
        - *   c.setAlpha(80);
        - *
        - *   // Style the circles.
        - *   fill(c);
        - *   noStroke();
        - *
        - *   // Iterate over the myData.bubbles array.
        - *   for (let b of myData.bubbles) {
        - *     // Draw a circle for each bubble.
        - *     circle(b.x, b.y, b.d);
        - *   }
        - *
        - *   describe('Several pink bubbles floating in a blue sky.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myData;
        - *
        - * // Load the GeoJSON and create an object.
        - * function preload() {
        - *   myData = loadJSON('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get data about the most recent earthquake.
        - *   let quake = myData.features[0].properties;
        - *
        - *   // Draw a circle based on the earthquake's magnitude.
        - *   circle(50, 50, quake.mag * 10);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(11);
        - *
        - *   // Display the earthquake's location.
        - *   text(quake.place, 5, 80, 100);
        - *
        - *   describe(`A white circle on a gray background. The text "${quake.place}" is written beneath the circle.`);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let bigQuake;
        - *
        - * // Load the GeoJSON and preprocess it.
        - * function preload() {
        - *   loadJSON(
        - *     'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson',
        - *     handleData
        - *   );
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw a circle based on the earthquake's magnitude.
        - *   circle(50, 50, bigQuake.mag * 10);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(11);
        - *
        - *   // Display the earthquake's location.
        - *   text(bigQuake.place, 5, 80, 100);
        - *
        - *   describe(`A white circle on a gray background. The text "${bigQuake.place}" is written beneath the circle.`);
        - * }
        - *
        - * // Find the biggest recent earthquake.
        - * function handleData(data) {
        - *   let maxMag = 0;
        - *   // Iterate over the earthquakes array.
        - *   for (let quake of data.features) {
        - *     // Reassign bigQuake if a larger
        - *     // magnitude quake is found.
        - *     if (quake.properties.mag > maxMag) {
        - *       bigQuake = quake.properties;
        - *     }
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let bigQuake;
        - *
        - * // Load the GeoJSON and preprocess it.
        - * function preload() {
        - *   loadJSON(
        - *     'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson',
        - *     handleData,
        - *     handleError
        - *   );
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw a circle based on the earthquake's magnitude.
        - *   circle(50, 50, bigQuake.mag * 10);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(11);
        - *
        - *   // Display the earthquake's location.
        - *   text(bigQuake.place, 5, 80, 100);
        - *
        - *   describe(`A white circle on a gray background. The text "${bigQuake.place}" is written beneath the circle.`);
        - * }
        - *
        - * // Find the biggest recent earthquake.
        - * function handleData(data) {
        - *   let maxMag = 0;
        - *   // Iterate over the earthquakes array.
        - *   for (let quake of data.features) {
        - *     // Reassign bigQuake if a larger
        - *     // magnitude quake is found.
        - *     if (quake.properties.mag > maxMag) {
        - *       bigQuake = quake.properties;
        - *     }
        - *   }
        - * }
        - *
        - * // Log any errors to the console.
        - * function handleError(error) {
        - *   console.log('Oops!', error);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.loadJSON = function(...args) {
        -  p5._validateParameters('loadJSON', args);
        -  const path = args[0];
        -  let callback;
        -  let errorCallback;
        -  let options;
        -
        -  const ret = {}; // object needed for preload
        -  let t = 'json';
        -
        -  // check for explicit data type argument
        -  for (let i = 1; i < args.length; i++) {
        -    const arg = args[i];
        -    if (typeof arg === 'string') {
        -      if (arg === 'jsonp' || arg === 'json') {
        -        t = arg;
        +export async function request(path, type){
        +  try {
        +    const res = await fetch(path);
        +
        +    if (res.ok) {
        +      let data;
        +      switch(type) {
        +        case 'json':
        +          data = await res.json();
        +          break;
        +        case 'text':
        +          data = await res.text();
        +          break;
        +        case 'arrayBuffer':
        +          data = await res.arrayBuffer();
        +          break;
        +        case 'blob':
        +          data = await res.blob();
        +          break;
        +        case 'bytes':
        +          // TODO: Chrome does not implement res.bytes() yet
        +          if(res.bytes){
        +            data = await res.bytes();
        +          }else{
        +            const d = await res.arrayBuffer();
        +            data = new Uint8Array(d);
        +          }
        +          break;
        +        default:
        +          throw new Error('Unsupported response type');
        +      }
        +
        +      return { data, headers: res.headers };
        +
        +    } else {
        +      const err = new HTTPError(res.statusText);
        +      err.status = res.status;
        +      err.response = res;
        +      err.ok = false;
        +
        +      throw err;
        +    }
        +
        +  } catch(err) {
        +    // Handle both fetch error and HTTP error
        +    if (err instanceof TypeError) {
        +      console.log('You may have encountered a CORS error');
        +    } else if (err instanceof HTTPError) {
        +      console.log('You have encountered a HTTP error');
        +    } else if (err instanceof SyntaxError) {
        +      console.log('There is an error parsing the response to requested data structure');
        +    }
        +
        +    throw err;
        +  }
        +}
        +
        +function files(p5, fn){
        +  /**
        +   * Loads a JSON file to create an `Object`.
        +   *
        +   * JavaScript Object Notation
        +   * (<a href="https://developer.mozilla.org/en-US/docs/Glossary/JSON" target="_blank">JSON</a>)
        +   * is a standard format for sending data between applications. The format is
        +   * based on JavaScript objects which have keys and values. JSON files store
        +   * data in an object with strings as keys. Values can be strings, numbers,
        +   * Booleans, arrays, `null`, or other objects.
        +   *
        +   * The first parameter, `path`, is a string with the path to the file.
        +   * Paths to local files should be relative, as in
        +   * `loadJSON('assets/data.json')`. URLs such as
        +   * `'https://example.com/data.json'` may be blocked due to browser security.
        +   * The `path` parameter can also be defined as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
        +   * object for more advanced usage.
        +   *
        +   * The second parameter, `successCallback`, is optional. If a function is
        +   * passed, as in `loadJSON('assets/data.json', handleData)`, then the
        +   * `handleData()` function will be called once the data loads. The object
        +   * created from the JSON data will be passed to `handleData()` as its only argument.
        +   * The return value of the `handleData()` function will be used as the final return
        +   * value of `loadJSON('assets/data.json', handleData)`.
        +   *
        +   * The third parameter, `failureCallback`, is also optional. If a function is
        +   * passed, as in `loadJSON('assets/data.json', handleData, handleFailure)`,
        +   * then the `handleFailure()` function will be called if an error occurs while
        +   * loading. The `Error` object will be passed to `handleFailure()` as its only
        +   * argument. The return value of the `handleFailure()` function will be used as the
        +   * final return value of `loadJSON('assets/data.json', handleData, handleFailure)`.
        +   *
        +   * This function returns a `Promise` and should be used in an `async` setup with
        +   * `await`. See the examples for the usage syntax.
        +   *
        +   * @method loadJSON
        +   * @param  {String|Request} path path of the JSON file to be loaded.
        +   * @param  {Function} [successCallback] function to call once the data is loaded. Will be passed the object.
        +   * @param  {Function} [errorCallback] function to call if the data fails to load. Will be passed an `Error` event object.
        +   * @return {Promise<Object>} object containing the loaded data.
        +   *
        +   * @example
        +   *
        +   * <div>
        +   * <code>
        +   * let myData;
        +   *
        +   * async function setup() {
        +   *   myData = await loadJSON('assets/data.json');
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the circle.
        +   *   fill(myData.color);
        +   *   noStroke();
        +   *
        +   *   // Draw the circle.
        +   *   circle(myData.x, myData.y, myData.d);
        +   *
        +   *   describe('A pink circle on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let myData;
        +   *
        +   * async function setup() {
        +   *   myData = await loadJSON('assets/data.json');
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Color object and make it transparent.
        +   *   let c = color(myData.color);
        +   *   c.setAlpha(80);
        +   *
        +   *   // Style the circles.
        +   *   fill(c);
        +   *   noStroke();
        +   *
        +   *   // Iterate over the myData.bubbles array.
        +   *   for (let b of myData.bubbles) {
        +   *     // Draw a circle for each bubble.
        +   *     circle(b.x, b.y, b.d);
        +   *   }
        +   *
        +   *   describe('Several pink bubbles floating in a blue sky.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let myData;
        +   *
        +   * async function setup() {
        +   *   myData = await loadJSON('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson');
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get data about the most recent earthquake.
        +   *   let quake = myData.features[0].properties;
        +   *
        +   *   // Draw a circle based on the earthquake's magnitude.
        +   *   circle(50, 50, quake.mag * 10);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(11);
        +   *
        +   *   // Display the earthquake's location.
        +   *   text(quake.place, 5, 80, 100);
        +   *
        +   *   describe(`A white circle on a gray background. The text "${quake.place}" is written beneath the circle.`);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let bigQuake;
        +   *
        +   * // Load the GeoJSON and preprocess it.
        +   * async function setup() {
        +   *   await loadJSON(
        +   *     'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson',
        +   *     handleData
        +   *   );
        +   *
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw a circle based on the earthquake's magnitude.
        +   *   circle(50, 50, bigQuake.mag * 10);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(11);
        +   *
        +   *   // Display the earthquake's location.
        +   *   text(bigQuake.place, 5, 80, 100);
        +   *
        +   *   describe(`A white circle on a gray background. The text "${bigQuake.place}" is written beneath the circle.`);
        +   * }
        +   *
        +   * // Find the biggest recent earthquake.
        +   * function handleData(data) {
        +   *   let maxMag = 0;
        +   *   // Iterate over the earthquakes array.
        +   *   for (let quake of data.features) {
        +   *     // Reassign bigQuake if a larger
        +   *     // magnitude quake is found.
        +   *     if (quake.properties.mag > maxMag) {
        +   *       bigQuake = quake.properties;
        +   *     }
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let bigQuake;
        +   *
        +   * // Load the GeoJSON and preprocess it.
        +   * async function setup() {
        +   *   await loadJSON(
        +   *     'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson',
        +   *     handleData,
        +   *     handleError
        +   *   );
        +   *
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw a circle based on the earthquake's magnitude.
        +   *   circle(50, 50, bigQuake.mag * 10);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(11);
        +   *
        +   *   // Display the earthquake's location.
        +   *   text(bigQuake.place, 5, 80, 100);
        +   *
        +   *   describe(`A white circle on a gray background. The text "${bigQuake.place}" is written beneath the circle.`);
        +   * }
        +   *
        +   * // Find the biggest recent earthquake.
        +   * function handleData(data) {
        +   *   let maxMag = 0;
        +   *   // Iterate over the earthquakes array.
        +   *   for (let quake of data.features) {
        +   *     // Reassign bigQuake if a larger
        +   *     // magnitude quake is found.
        +   *     if (quake.properties.mag > maxMag) {
        +   *       bigQuake = quake.properties;
        +   *     }
        +   *   }
        +   * }
        +   *
        +   * // Log any errors to the console.
        +   * function handleError(error) {
        +   *   console.log('Oops!', error);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.loadJSON = async function (path, successCallback, errorCallback) {
        +    // p5._validateParameters('loadJSON', arguments);
        +
        +    try{
        +      const { data } = await request(path, 'json');
        +      if (successCallback) successCallback(data);
        +      return data;
        +    } catch(err) {
        +      p5._friendlyFileLoadError(5, path);
        +      if(errorCallback) {
        +        return errorCallback(err);
        +      } else {
        +        throw err;
               }
        -    } else if (typeof arg === 'function') {
        -      if (!callback) {
        -        callback = arg;
        +    }
        +  };
        +
        +  /**
        +   * Loads a text file to create an `Array`.
        +   *
        +   * The first parameter, `path`, is always a string with the path to the file.
        +   * Paths to local files should be relative, as in
        +   * `loadStrings('assets/data.txt')`. URLs such as
        +   * `'https://example.com/data.txt'` may be blocked due to browser security.
        +   * The `path` parameter can also be defined as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
        +   * object for more advanced usage.
        +   *
        +   * The second parameter, `successCallback`, is optional. If a function is
        +   * passed, as in `loadStrings('assets/data.txt', handleData)`, then the
        +   * `handleData()` function will be called once the data loads. The array
        +   * created from the text data will be passed to `handleData()` as its only
        +   * argument. The return value of the `handleData()` function will be used as
        +   * the final return value of `loadStrings('assets/data.txt', handleData)`.
        +   *
        +   * The third parameter, `failureCallback`, is also optional. If a function is
        +   * passed, as in `loadStrings('assets/data.txt', handleData, handleFailure)`,
        +   * then the `handleFailure()` function will be called if an error occurs while
        +   * loading. The `Error` object will be passed to `handleFailure()` as its only
        +   * argument. The return value of the `handleFailure()` function will be used as
        +   * the final return value of `loadStrings('assets/data.txt', handleData, handleFailure)`.
        +   *
        +   * This function returns a `Promise` and should be used in an `async` setup with
        +   * `await`. See the examples for the usage syntax.
        +   *
        +   * @method loadStrings
        +   * @param  {String|Request} path path of the text file to be loaded.
        +   * @param  {Function} [successCallback] function to call once the data is
        +   *                                      loaded. Will be passed the array.
        +   * @param  {Function} [errorCallback] function to call if the data fails to
        +   *                                    load. Will be passed an `Error` event
        +   *                                    object.
        +   * @return {Promise<String[]>} new array containing the loaded text.
        +   *
        +   * @example
        +   *
        +   * <div>
        +   * <code>
        +   * let myData;
        +   *
        +   * async function setup() {
        +   *   myData = await loadStrings('assets/test.txt');
        +   *
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Select a random line from the text.
        +   *   let phrase = random(myData);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
        +   *
        +   *   // Display the text.
        +   *   text(phrase, 10, 50, 90);
        +   *
        +   *   describe(`The text "${phrase}" written in black on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let lastLine;
        +   *
        +   * // Load the text and preprocess it.
        +   * async function setup() {
        +   *   await loadStrings('assets/test.txt', handleData);
        +   *
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
        +   *
        +   *   // Display the text.
        +   *   text(lastLine, 10, 50, 90);
        +   *
        +   *   describe('The text "I talk like an orange" written in black on a gray background.');
        +   * }
        +   *
        +   * // Select the last line from the text.
        +   * function handleData(data) {
        +   *   lastLine = data[data.length - 1];
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let lastLine;
        +   *
        +   * // Load the text and preprocess it.
        +   * async function setup() {
        +   *   await loadStrings('assets/test.txt', handleData, handleError);
        +   *
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
        +   *
        +   *   // Display the text.
        +   *   text(lastLine, 10, 50, 90);
        +   *
        +   *   describe('The text "I talk like an orange" written in black on a gray background.');
        +   * }
        +   *
        +   * // Select the last line from the text.
        +   * function handleData(data) {
        +   *   lastLine = data[data.length - 1];
        +   * }
        +   *
        +   * // Log any errors to the console.
        +   * function handleError(error) {
        +   *   console.error('Oops!', error);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.loadStrings = async function (path, successCallback, errorCallback) {
        +    // p5._validateParameters('loadStrings', arguments);
        +
        +    try{
        +      let { data } = await request(path, 'text');
        +      data = data.split(/\r?\n/);
        +
        +      if (successCallback) successCallback(data);
        +      return data;
        +    } catch(err) {
        +      p5._friendlyFileLoadError(3, path);
        +      if(errorCallback) {
        +        errorCallback(err);
        +      } else {
        +        throw err;
        +      }
        +    }
        +  };
        +
        +  /**
        +   * Reads the contents of a file or URL and creates a <a href="#/p5.Table">p5.Table</a> object with
        +   * its values. If a file is specified, it must be located in the sketch's
        +   * "data" folder. The filename parameter can also be a URL to a file found
        +   * online. By default, the file is assumed to be comma-separated (in CSV
        +   * format). Table only looks for a header row if the 'header' option is
        +   * included.
        +   *
        +   * This function returns a `Promise` and should be used in an `async` setup with
        +   * `await`. See the examples for the usage syntax.
        +   *
        +   * All files loaded and saved use UTF-8 encoding. This method is suitable for fetching files up to size of 64MB.
        +   *
        +   * @method loadTable
        +   * @param  {String|Request} filename    name of the file or URL to load
        +   * @param  {String}         [separator] the separator character used by the file, defaults to `','`
        +   * @param  {String}         [header]    "header" to indicate table has header row
        +   * @param  {Function}       [callback]  function to be executed after
        +   *                                      <a href="#/p5/loadTable">loadTable()</a> completes. On success, the
        +   *                                      <a href="#/p5.Table">Table</a> object is passed in as the
        +   *                                      first argument.
        +   * @param  {Function}  [errorCallback]  function to be executed if
        +   *                                      there is an error, response is passed
        +   *                                      in as first argument
        +   * @return {Promise<Object>}            <a href="#/p5.Table">Table</a> object containing data
        +   *
        +   * @example
        +   * <div class='norender'>
        +   * <code>
        +   * // Given the following CSV file called "mammals.csv"
        +   * // located in the project's "assets" folder:
        +   * //
        +   * // id,species,name
        +   * // 0,Capra hircus,Goat
        +   * // 1,Panthera pardus,Leopard
        +   * // 2,Equus zebra,Zebra
        +   *
        +   * let table;
        +   *
        +   * async function setup() {
        +   *   table = await loadTable('assets/mammals.csv', 'csv', 'header');
        +   *
        +   *   //count the columns
        +   *   print(table.getRowCount() + ' total rows in table');
        +   *   print(table.getColumnCount() + ' total columns in table');
        +   *
        +   *   print(table.getColumn('name'));
        +   *   //["Goat", "Leopard", "Zebra"]
        +   *
        +   *   //cycle through the table
        +   *   for (let r = 0; r < table.getRowCount(); r++)
        +   *     for (let c = 0; c < table.getColumnCount(); c++) {
        +   *       print(table.getString(r, c));
        +   *     }
        +   *   describe(`randomly generated text from a file,
        +   *     for example "i smell like butter"`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.loadTable = async function (path, separator, header, successCallback, errorCallback) {
        +    if(typeof arguments[arguments.length-1] === 'function'){
        +      if(typeof arguments[arguments.length-2] === 'function'){
        +        successCallback = arguments[arguments.length-2];
        +        errorCallback = arguments[arguments.length-1];
        +      }else{
        +        successCallback = arguments[arguments.length-1];
        +      }
        +    }
        +
        +    if(typeof separator !== 'string') separator = ',';
        +    if(typeof header === 'function') header = false;
        +
        +    try{
        +      let { data } = await request(path, 'text');
        +
        +      let ret = new p5.Table();
        +      data = parse(data, {
        +        separator
        +      });
        +
        +      if(header){
        +        ret.columns = data.shift();
        +      }else{
        +        ret.columns = Array(data[0].length).fill(null);
        +      }
        +
        +      data.forEach((line) => {
        +        const row = new p5.TableRow(line);
        +        ret.addRow(row);
        +      });
        +
        +      if (successCallback) {
        +        successCallback(ret);
        +      } else {
        +        return ret;
        +      }
        +    } catch(err) {
        +      p5._friendlyFileLoadError(2, path);
        +      if(errorCallback) {
        +        return errorCallback(err);
        +      } else {
        +        throw err;
        +      }
        +    }
        +  };
        +
        +  /**
        +   * Loads an XML file to create a <a href="#/p5.XML">p5.XML</a> object.
        +   *
        +   * Extensible Markup Language
        +   * (<a href="https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction" target="_blank">XML</a>)
        +   * is a standard format for sending data between applications. Like HTML, the
        +   * XML format is based on tags and attributes, as in
        +   * `&lt;time units="s"&gt;1234&lt;/time&gt;`.
        +   *
        +   * The first parameter, `path`, is always a string with the path to the file.
        +   * Paths to local files should be relative, as in
        +   * `loadXML('assets/data.xml')`. URLs such as `'https://example.com/data.xml'`
        +   * may be blocked due to browser security. The `path` parameter can also be defined
        +   * as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
        +   * object for more advanced usage.
        +   *
        +   * The second parameter, `successCallback`, is optional. If a function is
        +   * passed, as in `loadXML('assets/data.xml', handleData)`, then the
        +   * `handleData()` function will be called once the data loads. The
        +   * <a href="#/p5.XML">p5.XML</a> object created from the data will be passed
        +   * to `handleData()` as its only argument. The return value of the `handleData()`
        +   * function will be used as the final return value of `loadXML('assets/data.xml', handleData)`.
        +   *
        +   * The third parameter, `failureCallback`, is also optional. If a function is
        +   * passed, as in `loadXML('assets/data.xml', handleData, handleFailure)`, then
        +   * the `handleFailure()` function will be called if an error occurs while
        +   * loading. The `Error` object will be passed to `handleFailure()` as its only
        +   * argument. The return value of the `handleFailure()` function will be used as the
        +   * final return value of `loadXML('assets/data.xml', handleData, handleFailure)`.
        +   *
        +   * This function returns a `Promise` and should be used in an `async` setup with
        +   * `await`. See the examples for the usage syntax.
        +   *
        +   * @method loadXML
        +   * @param  {String|Request} path        path of the XML file to be loaded.
        +   * @param  {Function} [successCallback] function to call once the data is
        +   *                                      loaded. Will be passed the
        +   *                                      <a href="#/p5.XML">p5.XML</a> object.
        +   * @param  {Function} [errorCallback] function to call if the data fails to
        +   *                                    load. Will be passed an `Error` event
        +   *                                    object.
        +   * @return {Promise<p5.XML>} XML data loaded into a <a href="#/p5.XML">p5.XML</a>
        +   *                  object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * async function setup() {
        +   *   myXML = await loadXML('assets/animals.xml');
        +   *
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get an array with all mammal tags.
        +   *   let mammals = myXML.getChildren('mammal');
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Iterate over the mammals array.
        +   *   for (let i = 0; i < mammals.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 25;
        +   *
        +   *     // Get the mammal's common name.
        +   *     let name = mammals[i].getContent();
        +   *
        +   *     // Display the mammal's name.
        +   *     text(name, 20, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The words "Goat", "Leopard", and "Zebra" written on three separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let lastMammal;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * async function setup() {
        +   *   await loadXML('assets/animals.xml', handleData);
        +   *
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(16);
        +   *
        +   *   // Display the content of the last mammal element.
        +   *   text(lastMammal, 50, 50);
        +   *
        +   *   describe('The word "Zebra" written in black on a gray background.');
        +   * }
        +   *
        +   * // Get the content of the last mammal element.
        +   * function handleData(data) {
        +   *   // Get an array with all mammal elements.
        +   *   let mammals = data.getChildren('mammal');
        +   *
        +   *   // Get the content of the last mammal.
        +   *   lastMammal = mammals[mammals.length - 1].getContent();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let lastMammal;
        +   *
        +   * // Load the XML and preprocess it.
        +   * async function setup() {
        +   *   await loadXML('assets/animals.xml', handleData, handleError);
        +   *
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(16);
        +   *
        +   *   // Display the content of the last mammal element.
        +   *   text(lastMammal, 50, 50);
        +   *
        +   *   describe('The word "Zebra" written in black on a gray background.');
        +   * }
        +   *
        +   * // Get the content of the last mammal element.
        +   * function handleData(data) {
        +   *   // Get an array with all mammal elements.
        +   *   let mammals = data.getChildren('mammal');
        +   *
        +   *   // Get the content of the last mammal.
        +   *   lastMammal = mammals[mammals.length - 1].getContent();
        +   * }
        +   *
        +   * // Log any errors to the console.
        +   * function handleError(error) {
        +   *   console.error('Oops!', error);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.loadXML = async function (path, successCallback, errorCallback) {
        +    try{
        +      const parser = new DOMParser();
        +
        +      let { data } = await request(path, 'text');
        +      const parsedDOM = parser.parseFromString(data, 'application/xml');
        +      data = new p5.XML(parsedDOM);
        +
        +      if (successCallback) successCallback(data);
        +      return data;
        +    } catch(err) {
        +      p5._friendlyFileLoadError(1, path);
        +      if(errorCallback) {
        +        errorCallback(err);
               } else {
        -        errorCallback = arg;
        +        throw err;
               }
        -    } else if (
        -      typeof arg === 'object' &&
        -      (arg.hasOwnProperty('jsonpCallback') ||
        -        arg.hasOwnProperty('jsonpCallbackFunction'))
        -    ) {
        -      t = 'jsonp';
        -      options = arg;
             }
        -  }
        -
        -  const self = this;
        -  this.httpDo(
        -    path,
        -    'GET',
        -    options,
        -    t,
        -    resp => {
        -      for (const k in resp) {
        -        ret[k] = resp[k];
        -      }
        -      if (typeof callback !== 'undefined') {
        -        callback(resp);
        -      }
        -
        -      self._decrementPreload();
        -    },
        -    err => {
        -      // Error handling
        -      p5._friendlyFileLoadError(5, path);
        +  };
         
        -      if (errorCallback) {
        +  /**
        +   * This method is suitable for fetching files up to size of 64MB.
        +   *
        +   * @method loadBytes
        +   * @param {String|Request}   file            name of the file or URL to load
        +   * @param {Function} [callback]      function to be executed after <a href="#/p5/loadBytes">loadBytes()</a>
        +   *                                    completes
        +   * @param {Function} [errorCallback] function to be executed if there
        +   *                                    is an error
        +   * @returns {Promise<Object>} an object whose 'bytes' property will be the loaded buffer
        +   *
        +   * @example
        +   * <div class='norender'><code>
        +   * let data;
        +   *
        +   * async function setup() {
        +   *   data = await loadBytes('assets/mammals.xml');
        +   *
        +   *   for (let i = 0; i < 5; i++) {
        +   *     console.log(data.bytes[i].toString(16));
        +   *   }
        +   *   describe('no image displayed');
        +   * }
        +   * </code></div>
        +   */
        +  fn.loadBytes = async function (path, successCallback, errorCallback) {
        +    try{
        +      let { data } = await request(path, 'arrayBuffer');
        +      data = new Uint8Array(data);
        +      if (successCallback) successCallback(data);
        +      return data;
        +    } catch(err) {
        +      p5._friendlyFileLoadError(6, path);
        +      if(errorCallback) {
                 errorCallback(err);
               } else {
                 throw err;
               }
             }
        -  );
        -
        -  return ret;
        -};
        -
        -/**
        - * Loads a text file to create an `Array`.
        - *
        - * The first parameter, `path`, is always a string with the path to the file.
        - * Paths to local files should be relative, as in
        - * `loadStrings('assets/data.txt')`. URLs such as
        - * `'https://example.com/data.txt'` may be blocked due to browser security.
        - *
        - * The second parameter, `successCallback`, is optional. If a function is
        - * passed, as in `loadStrings('assets/data.txt', handleData)`, then the
        - * `handleData()` function will be called once the data loads. The array
        - * created from the text data will be passed to `handleData()` as its only
        - * argument.
        - *
        - * The third parameter, `failureCallback`, is also optional. If a function is
        - * passed, as in `loadStrings('assets/data.txt', handleData, handleFailure)`,
        - * then the `handleFailure()` function will be called if an error occurs while
        - * loading. The `Error` object will be passed to `handleFailure()` as its only
        - * argument.
        - *
        - * Note: Data can take time to load. Calling `loadStrings()` within
        - * <a href="#/p5/preload">preload()</a> ensures data loads before it's used in
        - * <a href="#/p5/setup">setup()</a> or <a href="#/p5/draw">draw()</a>.
        - *
        - * @method loadStrings
        - * @param  {String} path path of the text file to be loaded.
        - * @param  {function} [successCallback] function to call once the data is
        - *                                      loaded. Will be passed the array.
        - * @param  {function} [errorCallback] function to call if the data fails to
        - *                                    load. Will be passed an `Error` event
        - *                                    object.
        - * @return {String[]} new array containing the loaded text.
        - *
        - * @example
        - *
        - * <div>
        - * <code>
        - * let myData;
        - *
        - * // Load the text and create an array.
        - * function preload() {
        - *   myData = loadStrings('assets/test.txt');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Select a random line from the text.
        - *   let phrase = random(myData);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display the text.
        - *   text(phrase, 10, 50, 90);
        - *
        - *   describe(`The text "${phrase}" written in black on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let lastLine;
        - *
        - * // Load the text and preprocess it.
        - * function preload() {
        - *   loadStrings('assets/test.txt', handleData);
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display the text.
        - *   text(lastLine, 10, 50, 90);
        - *
        - *   describe('The text "I talk like an orange" written in black on a gray background.');
        - * }
        - *
        - * // Select the last line from the text.
        - * function handleData(data) {
        - *   lastLine = data[data.length - 1];
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let lastLine;
        - *
        - * // Load the text and preprocess it.
        - * function preload() {
        - *   loadStrings('assets/test.txt', handleData, handleError);
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display the text.
        - *   text(lastLine, 10, 50, 90);
        - *
        - *   describe('The text "I talk like an orange" written in black on a gray background.');
        - * }
        - *
        - * // Select the last line from the text.
        - * function handleData(data) {
        - *   lastLine = data[data.length - 1];
        - * }
        - *
        - * // Log any errors to the console.
        - * function handleError(error) {
        - *   console.error('Oops!', error);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.loadStrings = function(...args) {
        -  p5._validateParameters('loadStrings', args);
        -
        -  const ret = [];
        -  let callback, errorCallback;
        -
        -  for (let i = 1; i < args.length; i++) {
        -    const arg = args[i];
        -    if (typeof arg === 'function') {
        -      if (typeof callback === 'undefined') {
        -        callback = arg;
        -      } else if (typeof errorCallback === 'undefined') {
        -        errorCallback = arg;
        -      }
        -    }
        -  }
        -
        -  const self = this;
        -  p5.prototype.httpDo.call(
        -    this,
        -    args[0],
        -    'GET',
        -    'text',
        -    data => {
        -      // split lines handling mac/windows/linux endings
        -      const lines = data
        -        .replace(/\r\n/g, '\r')
        -        .replace(/\n/g, '\r')
        -        .split(/\r/);
        -
        -      // safe insert approach which will not blow up stack when inserting
        -      // >100k lines, but still be faster than iterating line-by-line. based on
        -      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Examples
        -      const QUANTUM = 32768;
        -      for (let i = 0, len = lines.length; i < len; i += QUANTUM) {
        -        Array.prototype.push.apply(
        -          ret,
        -          lines.slice(i, Math.min(i + QUANTUM, len))
        -        );
        -      }
        -
        -      if (typeof callback !== 'undefined') {
        -        callback(ret);
        -      }
        -
        -      self._decrementPreload();
        -    },
        -    function(err) {
        -      // Error handling
        -      p5._friendlyFileLoadError(3, arguments[0]);
        +  };
         
        -      if (errorCallback) {
        +  fn.loadBlob = async function(path, successCallback, errorCallback) {
        +    try{
        +      const { data } = await request(path, 'blob');
        +      if (successCallback) successCallback(data);
        +      return data;
        +    } catch(err) {
        +      if(errorCallback) {
                 errorCallback(err);
               } else {
                 throw err;
               }
             }
        -  );
        -
        -  return ret;
        -};
        +  };
         
        -/**
        - * Reads the contents of a file or URL and creates a <a href="#/p5.Table">p5.Table</a> object with
        - * its values. If a file is specified, it must be located in the sketch's
        - * "data" folder. The filename parameter can also be a URL to a file found
        - * online. By default, the file is assumed to be comma-separated (in CSV
        - * format). Table only looks for a header row if the 'header' option is
        - * included.
        - *
        - * This method is asynchronous, meaning it may not finish before the next
        - * line in your sketch is executed. Calling <a href="#/p5/loadTable">loadTable()</a> inside <a href="#/p5/preload">preload()</a>
        - * guarantees to complete the operation before <a href="#/p5/setup">setup()</a> and <a href="#/p5/draw">draw()</a> are called.
        - * Outside of <a href="#/p5/preload">preload()</a>, you may supply a callback function to handle the
        - * object:
        - *
        - * All files loaded and saved use UTF-8 encoding. This method is suitable for fetching files up to size of 64MB.
        - * @method loadTable
        - * @param  {String}         filename    name of the file or URL to load
        - * @param  {String}         [extension] parse the table by comma-separated values "csv", semicolon-separated
        - *                                      values "ssv", or tab-separated values "tsv"
        - * @param  {String}         [header]    "header" to indicate table has header row
        - * @param  {function}       [callback]  function to be executed after
        - *                                      <a href="#/p5/loadTable">loadTable()</a> completes. On success, the
        - *                                      <a href="#/p5.Table">Table</a> object is passed in as the
        - *                                      first argument.
        - * @param  {function}  [errorCallback]  function to be executed if
        - *                                      there is an error, response is passed
        - *                                      in as first argument
        - * @return {Object}                     <a href="#/p5.Table">Table</a> object containing data
        - *
        - * @example
        - * <div class='norender'>
        - * <code>
        - * // Given the following CSV file called "mammals.csv"
        - * // located in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - *   //the file can be remote
        - *   //table = loadTable("http://p5js.org/reference/assets/mammals.csv",
        - *   //                  "csv", "header");
        - * }
        - *
        - * function setup() {
        - *   //count the columns
        - *   print(table.getRowCount() + ' total rows in table');
        - *   print(table.getColumnCount() + ' total columns in table');
        - *
        - *   print(table.getColumn('name'));
        - *   //["Goat", "Leopard", "Zebra"]
        - *
        - *   //cycle through the table
        - *   for (let r = 0; r < table.getRowCount(); r++)
        - *     for (let c = 0; c < table.getColumnCount(); c++) {
        - *       print(table.getString(r, c));
        - *     }
        - *   describe(`randomly generated text from a file,
        - *     for example "i smell like butter"`);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.loadTable = function(path) {
        -  // p5._validateParameters('loadTable', arguments);
        -  let callback;
        -  let errorCallback;
        -  const options = [];
        -  let header = false;
        -  const ext = path.substring(path.lastIndexOf('.') + 1, path.length);
        -
        -  let sep;
        -  if (ext === 'csv') {
        -    sep = ',';
        -  } else if (ext === 'ssv') {
        -    sep = ';';
        -  } else if (ext === 'tsv') {
        -    sep = '\t';
        -  }
        +  /**
        +   * Method for executing an HTTP GET request. If data type is not specified,
        +   * it will default to `'text'`. This is equivalent to
        +   * calling <code>httpDo(path, 'GET')</code>. The 'binary' datatype will return
        +   * a Blob object, and the 'arrayBuffer' datatype will return an ArrayBuffer
        +   * which can be used to initialize typed arrays (such as Uint8Array).
        +   *
        +   * @method httpGet
        +   * @param  {String|Request}        path       name of the file or url to load
        +   * @param  {String}        [datatype] "json", "jsonp", "binary", "arrayBuffer",
        +   *                                    "xml", or "text"
        +   * @param  {Function}      [callback] function to be executed after
        +   *                                    <a href="#/p5/httpGet">httpGet()</a> completes, data is passed in
        +   *                                    as first argument
        +   * @param  {Function}      [errorCallback] function to be executed if
        +   *                                    there is an error, response is passed
        +   *                                    in as first argument
        +   * @return {Promise} A promise that resolves with the data when the operation
        +   *                   completes successfully or rejects with the error after
        +   *                   one occurs.
        +   * @example
        +   * <div class='norender'><code>
        +   * // Examples use USGS Earthquake API:
        +   * //   https://earthquake.usgs.gov/fdsnws/event/1/#methods
        +   * let earthquakes;
        +   * async function setup() {
        +   *   // Get the most recent earthquake in the database
        +   *   let url =
        +      'https://earthquake.usgs.gov/fdsnws/event/1/query?' +
        +   *     'format=geojson&limit=1&orderby=time';
        +   *   earthquakes = await httpGet(url, 'json');
        +   * }
        +   *
        +   * function draw() {
        +   *   if (!earthquakes) {
        +   *     // Wait until the earthquake data has loaded before drawing.
        +   *     return;
        +   *   }
        +   *   background(200);
        +   *   // Get the magnitude and name of the earthquake out of the loaded JSON
        +   *   let earthquakeMag = earthquakes.features[0].properties.mag;
        +   *   let earthquakeName = earthquakes.features[0].properties.place;
        +   *   ellipse(width / 2, height / 2, earthquakeMag * 10, earthquakeMag * 10);
        +   *   textAlign(CENTER);
        +   *   text(earthquakeName, 0, height - 30, width, 30);
        +   *   noLoop();
        +   * }
        +   * </code></div>
        +   */
        +  /**
        +   * @method httpGet
        +   * @param  {String|Request}  path
        +   * @param  {Function}        callback
        +   * @param  {Function}        [errorCallback]
        +   * @return {Promise}
        +   */
        +  fn.httpGet = async function (path, datatype='text', successCallback, errorCallback) {
        +    // p5._validateParameters('httpGet', arguments);
         
        -  for (let i = 1; i < arguments.length; i++) {
        -    if (typeof arguments[i] === 'function') {
        -      if (typeof callback === 'undefined') {
        -        callback = arguments[i];
        -      } else if (typeof errorCallback === 'undefined') {
        -        errorCallback = arguments[i];
        -      }
        -    } else if (typeof arguments[i] === 'string') {
        -      options.push(arguments[i]);
        -      if (arguments[i] === 'header') {
        -        header = true;
        -      }
        -      if (arguments[i] === 'csv') {
        -        sep = ',';
        -      } else if (arguments[i] === 'ssv') {
        -        sep = ';';
        -      } else if (arguments[i] === 'tsv') {
        -        sep = '\t';
        -      }
        +    if (typeof datatype === 'function') {
        +      errorCallback = successCallback;
        +      successCallback = datatype;
        +      datatype = 'text';
             }
        -  }
         
        -  const t = new p5.Table();
        -
        -  const self = this;
        -  this.httpDo(
        -    path,
        -    'GET',
        -    'table',
        -    resp => {
        -      const state = {};
        -
        -      // define constants
        -      const PRE_TOKEN = 0,
        -        MID_TOKEN = 1,
        -        POST_TOKEN = 2,
        -        POST_RECORD = 4;
        -
        -      const QUOTE = '"',
        -        CR = '\r',
        -        LF = '\n';
        -
        -      const records = [];
        -      let offset = 0;
        -      let currentRecord = null;
        -      let currentChar;
        -
        -      const tokenBegin = () => {
        -        state.currentState = PRE_TOKEN;
        -        state.token = '';
        -      };
        -
        -      const tokenEnd = () => {
        -        currentRecord.push(state.token);
        -        tokenBegin();
        -      };
        -
        -      const recordBegin = () => {
        -        state.escaped = false;
        -        currentRecord = [];
        -        tokenBegin();
        -      };
        -
        -      const recordEnd = () => {
        -        state.currentState = POST_RECORD;
        -        records.push(currentRecord);
        -        currentRecord = null;
        -      };
        -
        -      for (;;) {
        -        currentChar = resp[offset++];
        -
        -        // EOF
        -        if (currentChar == null) {
        -          if (state.escaped) {
        -            throw new Error('Unclosed quote in file.');
        -          }
        -          if (currentRecord) {
        -            tokenEnd();
        -            recordEnd();
        -            break;
        -          }
        -        }
        -        if (currentRecord === null) {
        -          recordBegin();
        -        }
        +    // This is like a more primitive version of the other load functions.
        +    // If the user wanted to customize more behavior, pass in Request to path.
         
        -        // Handle opening quote
        -        if (state.currentState === PRE_TOKEN) {
        -          if (currentChar === QUOTE) {
        -            state.escaped = true;
        -            state.currentState = MID_TOKEN;
        -            continue;
        -          }
        -          state.currentState = MID_TOKEN;
        -        }
        +    return this.httpDo(path, 'GET', datatype, successCallback, errorCallback);
        +  };
         
        -        // mid-token and escaped, look for sequences and end quote
        -        if (state.currentState === MID_TOKEN && state.escaped) {
        -          if (currentChar === QUOTE) {
        -            if (resp[offset] === QUOTE) {
        -              state.token += QUOTE;
        -              offset++;
        -            } else {
        -              state.escaped = false;
        -              state.currentState = POST_TOKEN;
        -            }
        -          } else if (currentChar === CR) {
        -            continue;
        -          } else {
        -            state.token += currentChar;
        -          }
        -          continue;
        -        }
        +  /**
        +   * Method for executing an HTTP POST request. If data type is not specified,
        +   * it will default to `'text'`. This is equivalent to
        +   * calling <code>httpDo(path, 'POST')</code>.
        +   *
        +   * @method httpPost
        +   * @param  {String|Request} path       name of the file or url to load
        +   * @param  {Object|Boolean} [data]     param data passed sent with request
        +   * @param  {String}         [datatype] "json", "jsonp", "xml", or "text".
        +   *                                    If omitted, <a href="#/p5/httpPost">httpPost()</a> will guess.
        +   * @param  {Function}       [callback] function to be executed after
        +   *                                     <a href="#/p5/httpPost">httpPost()</a> completes, data is passed in
        +   *                                     as first argument
        +   * @param  {Function}       [errorCallback] function to be executed if
        +   *                                          there is an error, response is passed
        +   *                                          in as first argument
        +   * @return {Promise} A promise that resolves with the data when the operation
        +   *                   completes successfully or rejects with the error after
        +   *                   one occurs.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Examples use jsonplaceholder.typicode.com for a Mock Data API
        +   *
        +   * let url = 'https://jsonplaceholder.typicode.com/posts';
        +   * let postData = { userId: 1, title: 'p5 Clicked!', body: 'p5.js is very cool.' };
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *   background(200);
        +   * }
        +   *
        +   * function mousePressed() {
        +   *   httpPost(url, 'json', postData, function(result) {
        +   *     strokeWeight(2);
        +   *     text(result.body, mouseX, mouseY);
        +   *   });
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div><code>
        +   * let url = 'ttps://invalidURL'; // A bad URL that will cause errors
        +   * let postData = { title: 'p5 Clicked!', body: 'p5.js is very cool.' };
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *   background(200);
        +   * }
        +   *
        +   * function mousePressed() {
        +   *   httpPost(
        +   *     url,
        +   *     'json',
        +   *     postData,
        +   *     function(result) {
        +   *       // ... won't be called
        +   *     },
        +   *     function(error) {
        +   *       strokeWeight(2);
        +   *       text(error.toString(), mouseX, mouseY);
        +   *     }
        +   *   );
        +   * }
        +   * </code></div>
        +   */
        +  /**
        +   * @method httpPost
        +   * @param  {String|Request}    path
        +   * @param  {Object|Boolean}    data
        +   * @param  {Function}         [callback]
        +   * @param  {Function}         [errorCallback]
        +   * @return {Promise}
        +   */
        +  /**
        +   * @method httpPost
        +   * @param  {String|Request}    path
        +   * @param  {Function}         [callback]
        +   * @param  {Function}         [errorCallback]
        +   * @return {Promise}
        +   */
        +  fn.httpPost = async function (path, data, datatype='text', successCallback, errorCallback) {
        +    // p5._validateParameters('httpPost', arguments);
        +
        +    // This behave similarly to httpGet and additional options should be passed
        +    // as a `Request`` to path. Both method and body will be overridden.
        +    // Will try to infer correct Content-Type for given data.
        +
        +    if (typeof data === 'function') {
        +      // Assume both data and datatype are functions as data should not be function
        +      successCallback = data;
        +      errorCallback = datatype;
        +      data = undefined;
        +      datatype = 'text';
        +
        +    } else if (typeof datatype === 'function') {
        +      // Data is provided but not datatype\
        +      errorCallback = successCallback;
        +      successCallback = datatype;
        +      datatype = 'text';
        +    }
         
        -        // fall-through: mid-token or post-token, not escaped
        -        if (currentChar === CR) {
        -          if (resp[offset] === LF) {
        -            offset++;
        -          }
        -          tokenEnd();
        -          recordEnd();
        -        } else if (currentChar === LF) {
        -          tokenEnd();
        -          recordEnd();
        -        } else if (currentChar === sep) {
        -          tokenEnd();
        -        } else if (state.currentState === MID_TOKEN) {
        -          state.token += currentChar;
        -        }
        -      }
        +    let reqData = data;
        +    let contentType = 'text/plain';
        +    // Normalize data
        +    if(data instanceof p5.XML) {
        +      reqData = data.serialize();
        +      contentType = 'application/xml';
         
        -      // set up column names
        -      if (header) {
        -        t.columns = records.shift();
        -      } else {
        -        for (let i = 0; i < records[0].length; i++) {
        -          t.columns[i] = 'null';
        -        }
        -      }
        -      let row;
        -      for (let i = 0; i < records.length; i++) {
        -        //Handles row of 'undefined' at end of some CSVs
        -        if (records[i].length === 1) {
        -          if (records[i][0] === 'undefined' || records[i][0] === '') {
        -            continue;
        -          }
        -        }
        -        row = new p5.TableRow();
        -        row.arr = records[i];
        -        row.obj = makeObject(records[i], t.columns);
        -        t.addRow(row);
        -      }
        -      if (typeof callback === 'function') {
        -        callback(t);
        -      }
        +    } else if(data instanceof p5.Image) {
        +      reqData = await data.toBlob();
        +      contentType = 'image/png';
         
        -      self._decrementPreload();
        -    },
        -    err => {
        -      // Error handling
        -      p5._friendlyFileLoadError(2, path);
        +    } else if (typeof data === 'object') {
        +      reqData = JSON.stringify(data);
        +      contentType = 'application/json';
        +    }
         
        -      if (errorCallback) {
        -        errorCallback(err);
        -      } else {
        -        console.error(err);
        +    const requestOptions = {
        +      method: 'POST',
        +      body: reqData,
        +      headers: {
        +        'Content-Type': contentType
               }
        +    };
        +
        +    if (reqData) {
        +      requestOptions.body = reqData;
             }
        -  );
         
        -  return t;
        -};
        +    const req = new Request(path, requestOptions);
         
        -// helper function to turn a row into a JSON object
        -function makeObject(row, headers) {
        -  headers = headers || [];
        -  if (typeof headers === 'undefined') {
        -    for (let j = 0; j < row.length; j++) {
        -      headers[j.toString()] = j;
        -    }
        -  }
        -  return Object.fromEntries(
        -    headers
        -      .map((key,i) => [key, row[i]])
        -  );
        -}
        +    return this.httpDo(req, 'POST', datatype, successCallback, errorCallback);
        +  };
         
        -/**
        - * Loads an XML file to create a <a href="#/p5.XML">p5.XML</a> object.
        - *
        - * Extensible Markup Language
        - * (<a href="https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction" target="_blank">XML</a>)
        - * is a standard format for sending data between applications. Like HTML, the
        - * XML format is based on tags and attributes, as in
        - * `&lt;time units="s"&gt;1234&lt;/time&gt;`.
        - *
        - * The first parameter, `path`, is always a string with the path to the file.
        - * Paths to local files should be relative, as in
        - * `loadXML('assets/data.xml')`. URLs such as `'https://example.com/data.xml'`
        - * may be blocked due to browser security.
        - *
        - * The second parameter, `successCallback`, is optional. If a function is
        - * passed, as in `loadXML('assets/data.xml', handleData)`, then the
        - * `handleData()` function will be called once the data loads. The
        - * <a href="#/p5.XML">p5.XML</a> object created from the data will be passed
        - * to `handleData()` as its only argument.
        - *
        - * The third parameter, `failureCallback`, is also optional. If a function is
        - * passed, as in `loadXML('assets/data.xml', handleData, handleFailure)`, then
        - * the `handleFailure()` function will be called if an error occurs while
        - * loading. The `Error` object will be passed to `handleFailure()` as its only
        - * argument.
        - *
        - * Note: Data can take time to load. Calling `loadXML()` within
        - * <a href="#/p5/preload">preload()</a> ensures data loads before it's used in
        - * <a href="#/p5/setup">setup()</a> or <a href="#/p5/draw">draw()</a>.
        - *
        - * @method loadXML
        - * @param  {String} path path of the XML file to be loaded.
        - * @param  {function} [successCallback] function to call once the data is
        - *                                      loaded. Will be passed the
        - *                                      <a href="#/p5.XML">p5.XML</a> object.
        - * @param  {function} [errorCallback] function to call if the data fails to
        - *                                    load. Will be passed an `Error` event
        - *                                    object.
        - * @return {p5.XML} XML data loaded into a <a href="#/p5.XML">p5.XML</a>
        - *                  object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get an array with all mammal tags.
        - *   let mammals = myXML.getChildren('mammal');
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Iterate over the mammals array.
        - *   for (let i = 0; i < mammals.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 25;
        - *
        - *     // Get the mammal's common name.
        - *     let name = mammals[i].getContent();
        - *
        - *     // Display the mammal's name.
        - *     text(name, 20, y);
        - *   }
        - *
        - *   describe(
        - *     'The words "Goat", "Leopard", and "Zebra" written on three separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let lastMammal;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   loadXML('assets/animals.xml', handleData);
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(16);
        - *
        - *   // Display the content of the last mammal element.
        - *   text(lastMammal, 50, 50);
        - *
        - *   describe('The word "Zebra" written in black on a gray background.');
        - * }
        - *
        - * // Get the content of the last mammal element.
        - * function handleData(data) {
        - *   // Get an array with all mammal elements.
        - *   let mammals = data.getChildren('mammal');
        - *
        - *   // Get the content of the last mammal.
        - *   lastMammal = mammals[mammals.length - 1].getContent();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let lastMammal;
        - *
        - * // Load the XML and preprocess it.
        - * function preload() {
        - *   loadXML('assets/animals.xml', handleData, handleError);
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(16);
        - *
        - *   // Display the content of the last mammal element.
        - *   text(lastMammal, 50, 50);
        - *
        - *   describe('The word "Zebra" written in black on a gray background.');
        - * }
        - *
        - * // Get the content of the last mammal element.
        - * function handleData(data) {
        - *   // Get an array with all mammal elements.
        - *   let mammals = data.getChildren('mammal');
        - *
        - *   // Get the content of the last mammal.
        - *   lastMammal = mammals[mammals.length - 1].getContent();
        - * }
        - *
        - * // Log any errors to the console.
        - * function handleError(error) {
        - *   console.error('Oops!', error);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.loadXML = function(...args) {
        -  const ret = new p5.XML();
        -  let callback, errorCallback;
        -
        -  for (let i = 1; i < args.length; i++) {
        -    const arg = args[i];
        -    if (typeof arg === 'function') {
        -      if (typeof callback === 'undefined') {
        -        callback = arg;
        -      } else if (typeof errorCallback === 'undefined') {
        -        errorCallback = arg;
        -      }
        +  /**
        +   * Method for executing an HTTP request. If data type is not specified,
        +   * it will default to `'text'`.
        +   *
        +   * This function is meant for more advanced usage of HTTP requests in p5.js. It is
        +   * best used when a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
        +   * object is passed to the `path` parameter.
        +   *
        +   * This method is suitable for fetching files up to size of 64MB when "GET" is used.
        +   *
        +   * @method httpDo
        +   * @param  {String|Request}   path      name of the file or url to load
        +   * @param  {String}           [method]    either "GET", "POST", "PUT", "DELETE",
        +   *                                      or other HTTP request methods
        +   * @param  {String}          [datatype] "json", "jsonp", "xml", or "text"
        +   * @param  {Object}          [data]     param data passed sent with request
        +   * @param  {Function}        [callback] function to be executed after
        +   *                                      <a href="#/p5/httpGet">httpGet()</a> completes, data is passed in
        +   *                                      as first argument
        +   * @param  {Function}        [errorCallback] function to be executed if
        +   *                                      there is an error, response is passed
        +   *                                      in as first argument
        +   * @return {Promise} A promise that resolves with the data when the operation
        +   *                   completes successfully or rejects with the error after
        +   *                   one occurs.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Examples use USGS Earthquake API:
        +   * // https://earthquake.usgs.gov/fdsnws/event/1/#methods
        +   *
        +   * // displays an animation of all USGS earthquakes
        +   * let earthquakes;
        +   * let eqFeatureIndex = 0;
        +   *
        +   * function setup() {
        +   *   let url = 'https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson';
        +   *   httpDo(
        +   *     url,
        +   *     {
        +   *       method: 'GET',
        +   *       // Other Request options, like special headers for apis
        +   *       headers: { authorization: 'Bearer secretKey' }
        +   *     },
        +   *     function(res) {
        +   *       earthquakes = res;
        +   *     }
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   // wait until the data is loaded
        +   *   if (!earthquakes || !earthquakes.features[eqFeatureIndex]) {
        +   *     return;
        +   *   }
        +   *   clear();
        +   *
        +   *   let feature = earthquakes.features[eqFeatureIndex];
        +   *   let mag = feature.properties.mag;
        +   *   let rad = mag / 11 * ((width + height) / 2);
        +   *   fill(255, 0, 0, 100);
        +   *   ellipse(width / 2 + random(-2, 2), height / 2 + random(-2, 2), rad, rad);
        +   *
        +   *   if (eqFeatureIndex >= earthquakes.features.length) {
        +   *     eqFeatureIndex = 0;
        +   *   } else {
        +   *     eqFeatureIndex += 1;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method httpDo
        +   * @param  {String|Request}    path
        +   * @param  {Function}         [callback]
        +   * @param  {Function}         [errorCallback]
        +   * @return {Promise}
        +   */
        +  fn.httpDo = async function (path, method, datatype, successCallback, errorCallback) {
        +    // This behave similarly to httpGet but even more primitive. The user
        +    // will most likely want to pass in a Request to path, the only convenience
        +    // is that datatype will be taken into account to parse the response.
        +
        +    if(typeof datatype === 'function'){
        +      errorCallback = successCallback;
        +      successCallback = datatype;
        +      datatype = undefined;
             }
        -  }
         
        -  const self = this;
        -  this.httpDo(
        -    args[0],
        -    'GET',
        -    'xml',
        -    xml => {
        -      for (const key in xml) {
        -        ret[key] = xml[key];
        -      }
        -      if (typeof callback !== 'undefined') {
        -        callback(ret);
        -      }
        +    // Try to infer data type if it is defined
        +    if(!datatype){
        +      const extension = typeof path === 'string' ?
        +        path.split(".").pop() :
        +        path.url.split(".").pop();
        +      switch(extension) {
        +        case 'json':
        +          datatype = 'json';
        +          break;
         
        -      self._decrementPreload();
        -    },
        -    function(err) {
        -      // Error handling
        -      p5._friendlyFileLoadError(1, arguments[0]);
        +        case 'jpg':
        +        case 'jpeg':
        +        case 'png':
        +        case 'webp':
        +        case 'gif':
        +          datatype = 'blob';
        +          break;
         
        -      if (errorCallback) {
        -        errorCallback(err);
        -      } else {
        -        throw err;
        +        case 'xml':
        +          // NOTE: still need to normalize type handling/mapping
        +          // datatype = 'xml';
        +        case 'txt':
        +        default:
        +          datatype = 'text';
               }
             }
        -  );
         
        -  return ret;
        -};
        +    const req = new Request(path, {
        +      method
        +    });
         
        -/**
        - * This method is suitable for fetching files up to size of 64MB.
        - * @method loadBytes
        - * @param {string}   file            name of the file or URL to load
        - * @param {function} [callback]      function to be executed after <a href="#/p5/loadBytes">loadBytes()</a>
        - *                                    completes
        - * @param {function} [errorCallback] function to be executed if there
        - *                                    is an error
        - * @returns {Object} an object whose 'bytes' property will be the loaded buffer
        - *
        - * @example
        - * <div class='norender'><code>
        - * let data;
        - *
        - * function preload() {
        - *   data = loadBytes('assets/mammals.xml');
        - * }
        - *
        - * function setup() {
        - *   for (let i = 0; i < 5; i++) {
        - *     console.log(data.bytes[i].toString(16));
        - *   }
        - *   describe('no image displayed');
        - * }
        - * </code></div>
        - */
        -p5.prototype.loadBytes = function(file, callback, errorCallback) {
        -  const ret = {};
        -
        -  const self = this;
        -  this.httpDo(
        -    file,
        -    'GET',
        -    'arrayBuffer',
        -    arrayBuffer => {
        -      ret.bytes = new Uint8Array(arrayBuffer);
        -
        -      if (typeof callback === 'function') {
        -        callback(ret);
        +    try{
        +      const { data } = await request(req, datatype);
        +      if (successCallback) {
        +        return successCallback(data);
        +      } else {
        +        return data;
               }
        -
        -      self._decrementPreload();
        -    },
        -    err => {
        -      // Error handling
        -      p5._friendlyFileLoadError(6, file);
        -
        -      if (errorCallback) {
        -        errorCallback(err);
        +    } catch(err) {
        +      if(errorCallback) {
        +        return errorCallback(err);
               } else {
                 throw err;
               }
             }
        -  );
        -  return ret;
        -};
        -
        -/**
        - * Method for executing an HTTP GET request. If data type is not specified,
        - * p5 will try to guess based on the URL, defaulting to text. This is equivalent to
        - * calling <code>httpDo(path, 'GET')</code>. The 'binary' datatype will return
        - * a Blob object, and the 'arrayBuffer' datatype will return an ArrayBuffer
        - * which can be used to initialize typed arrays (such as Uint8Array).
        - *
        - * @method httpGet
        - * @param  {String}        path       name of the file or url to load
        - * @param  {String}        [datatype] "json", "jsonp", "binary", "arrayBuffer",
        - *                                    "xml", or "text"
        - * @param  {Object|Boolean} [data]    param data passed sent with request
        - * @param  {function}      [callback] function to be executed after
        - *                                    <a href="#/p5/httpGet">httpGet()</a> completes, data is passed in
        - *                                    as first argument
        - * @param  {function}      [errorCallback] function to be executed if
        - *                                    there is an error, response is passed
        - *                                    in as first argument
        - * @return {Promise} A promise that resolves with the data when the operation
        - *                   completes successfully or rejects with the error after
        - *                   one occurs.
        - * @example
        - * <div class='norender'><code>
        - * // Examples use USGS Earthquake API:
        - * //   https://earthquake.usgs.gov/fdsnws/event/1/#methods
        - * let earthquakes;
        - * function preload() {
        - *   // Get the most recent earthquake in the database
        - *   let url =
        -    'https://earthquake.usgs.gov/fdsnws/event/1/query?' +
        - *     'format=geojson&limit=1&orderby=time';
        - *   httpGet(url, 'jsonp', false, function(response) {
        - *     // when the HTTP request completes, populate the variable that holds the
        - *     // earthquake data used in the visualization.
        - *     earthquakes = response;
        - *   });
        - * }
        - *
        - * function draw() {
        - *   if (!earthquakes) {
        - *     // Wait until the earthquake data has loaded before drawing.
        - *     return;
        - *   }
        - *   background(200);
        - *   // Get the magnitude and name of the earthquake out of the loaded JSON
        - *   let earthquakeMag = earthquakes.features[0].properties.mag;
        - *   let earthquakeName = earthquakes.features[0].properties.place;
        - *   ellipse(width / 2, height / 2, earthquakeMag * 10, earthquakeMag * 10);
        - *   textAlign(CENTER);
        - *   text(earthquakeName, 0, height - 30, width, 30);
        - *   noLoop();
        - * }
        - * </code></div>
        - */
        -/**
        - * @method httpGet
        - * @param  {String}        path
        - * @param  {Object|Boolean} data
        - * @param  {function}      [callback]
        - * @param  {function}      [errorCallback]
        - * @return {Promise}
        - */
        -/**
        - * @method httpGet
        - * @param  {String}        path
        - * @param  {function}      callback
        - * @param  {function}      [errorCallback]
        - * @return {Promise}
        - */
        -p5.prototype.httpGet = function(...args) {
        -  p5._validateParameters('httpGet', args);
        -
        -  args.splice(1, 0, 'GET');
        -  return p5.prototype.httpDo.apply(this, args);
        -};
        -
        -/**
        - * Method for executing an HTTP POST request. If data type is not specified,
        - * p5 will try to guess based on the URL, defaulting to text. This is equivalent to
        - * calling <code>httpDo(path, 'POST')</code>.
        - *
        - * @method httpPost
        - * @param  {String}        path       name of the file or url to load
        - * @param  {String}        [datatype] "json", "jsonp", "xml", or "text".
        - *                                    If omitted, <a href="#/p5/httpPost">httpPost()</a> will guess.
        - * @param  {Object|Boolean} [data]    param data passed sent with request
        - * @param  {function}      [callback] function to be executed after
        - *                                    <a href="#/p5/httpPost">httpPost()</a> completes, data is passed in
        - *                                    as first argument
        - * @param  {function}      [errorCallback] function to be executed if
        - *                                    there is an error, response is passed
        - *                                    in as first argument
        - * @return {Promise} A promise that resolves with the data when the operation
        - *                   completes successfully or rejects with the error after
        - *                   one occurs.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Examples use jsonplaceholder.typicode.com for a Mock Data API
        - *
        - * let url = 'https://jsonplaceholder.typicode.com/posts';
        - * let postData = { userId: 1, title: 'p5 Clicked!', body: 'p5.js is very cool.' };
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *   background(200);
        - * }
        - *
        - * function mousePressed() {
        - *   httpPost(url, 'json', postData, function(result) {
        - *     strokeWeight(2);
        - *     text(result.body, mouseX, mouseY);
        - *   });
        - * }
        - * </code>
        - * </div>
        - *
        - * <div><code>
        - * let url = 'ttps://invalidURL'; // A bad URL that will cause errors
        - * let postData = { title: 'p5 Clicked!', body: 'p5.js is very cool.' };
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *   background(200);
        - * }
        - *
        - * function mousePressed() {
        - *   httpPost(
        - *     url,
        - *     'json',
        - *     postData,
        - *     function(result) {
        - *       // ... won't be called
        - *     },
        - *     function(error) {
        - *       strokeWeight(2);
        - *       text(error.toString(), mouseX, mouseY);
        - *     }
        - *   );
        - * }
        - * </code></div>
        - */
        -/**
        - * @method httpPost
        - * @param  {String}        path
        - * @param  {Object|Boolean} data
        - * @param  {function}      [callback]
        - * @param  {function}      [errorCallback]
        - * @return {Promise}
        - */
        -/**
        - * @method httpPost
        - * @param  {String}        path
        - * @param  {function}      callback
        - * @param  {function}      [errorCallback]
        - * @return {Promise}
        - */
        -p5.prototype.httpPost = function(...args) {
        -  p5._validateParameters('httpPost', args);
        -
        -  args.splice(1, 0, 'POST');
        -  return p5.prototype.httpDo.apply(this, args);
        -};
        -
        -/**
        - * Method for executing an HTTP request. If data type is not specified,
        - * p5 will try to guess based on the URL, defaulting to text.<br><br>
        - * For more advanced use, you may also pass in the path as the first argument
        - * and a object as the second argument, the signature follows the one specified
        - * in the Fetch API specification.
        - * This method is suitable for fetching files up to size of 64MB when "GET" is used.
        - *
        - * @method httpDo
        - * @param  {String}        path       name of the file or url to load
        - * @param  {String}        [method]   either "GET", "POST", or "PUT",
        - *                                    defaults to "GET"
        - * @param  {String}        [datatype] "json", "jsonp", "xml", or "text"
        - * @param  {Object}        [data]     param data passed sent with request
        - * @param  {function}      [callback] function to be executed after
        - *                                    <a href="#/p5/httpGet">httpGet()</a> completes, data is passed in
        - *                                    as first argument
        - * @param  {function}      [errorCallback] function to be executed if
        - *                                    there is an error, response is passed
        - *                                    in as first argument
        - * @return {Promise} A promise that resolves with the data when the operation
        - *                   completes successfully or rejects with the error after
        - *                   one occurs.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Examples use USGS Earthquake API:
        - * // https://earthquake.usgs.gov/fdsnws/event/1/#methods
        - *
        - * // displays an animation of all USGS earthquakes
        - * let earthquakes;
        - * let eqFeatureIndex = 0;
        - *
        - * function preload() {
        - *   let url = 'https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson';
        - *   httpDo(
        - *     url,
        - *     {
        - *       method: 'GET',
        - *       // Other Request options, like special headers for apis
        - *       headers: { authorization: 'Bearer secretKey' }
        - *     },
        - *     function(res) {
        - *       earthquakes = res;
        - *     }
        - *   );
        - * }
        - *
        - * function draw() {
        - *   // wait until the data is loaded
        - *   if (!earthquakes || !earthquakes.features[eqFeatureIndex]) {
        - *     return;
        - *   }
        - *   clear();
        - *
        - *   let feature = earthquakes.features[eqFeatureIndex];
        - *   let mag = feature.properties.mag;
        - *   let rad = mag / 11 * ((width + height) / 2);
        - *   fill(255, 0, 0, 100);
        - *   ellipse(width / 2 + random(-2, 2), height / 2 + random(-2, 2), rad, rad);
        - *
        - *   if (eqFeatureIndex >= earthquakes.features.length) {
        - *     eqFeatureIndex = 0;
        - *   } else {
        - *     eqFeatureIndex += 1;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method httpDo
        - * @param  {String}        path
        - * @param  {Object}        options   Request object options as documented in the
        - *                                    "fetch" API
        - * <a href="https://developer.mozilla.org/en/docs/Web/API/Fetch_API">reference</a>
        - * @param  {function}      [callback]
        - * @param  {function}      [errorCallback]
        - * @return {Promise}
        - */
        -p5.prototype.httpDo = function(...args) {
        -  let type;
        -  let callback;
        -  let errorCallback;
        -  let request;
        -  let promise;
        -  const jsonpOptions = {};
        -  let cbCount = 0;
        -  let contentType = 'text/plain';
        -  // Trim the callbacks off the end to get an idea of how many arguments are passed
        -  for (let i = args.length - 1; i > 0; i--) {
        -    if (typeof args[i] === 'function') {
        -      cbCount++;
        -    } else {
        -      break;
        -    }
        -  }
        -  // The number of arguments minus callbacks
        -  const argsCount = args.length - cbCount;
        -  const path = args[0];
        -  if (
        -    argsCount === 2 &&
        -    typeof path === 'string' &&
        -    typeof args[1] === 'object'
        -  ) {
        -    // Intended for more advanced use, pass in Request parameters directly
        -    request = new Request(path, args[1]);
        -    callback = args[2];
        -    errorCallback = args[3];
        -  } else {
        -    // Provided with arguments
        -    let method = 'GET';
        -    let data;
        -
        -    for (let j = 1; j < args.length; j++) {
        -      const a = args[j];
        -      if (typeof a === 'string') {
        -        if (a === 'GET' || a === 'POST' || a === 'PUT' || a === 'DELETE') {
        -          method = a;
        -        } else if (
        -          a === 'json' ||
        -          a === 'jsonp' ||
        -          a === 'binary' ||
        -          a === 'arrayBuffer' ||
        -          a === 'xml' ||
        -          a === 'text' ||
        -          a === 'table'
        -        ) {
        -          type = a;
        -        } else {
        -          data = a;
        -        }
        -      } else if (typeof a === 'number') {
        -        data = a.toString();
        -      } else if (typeof a === 'object') {
        -        if (
        -          a.hasOwnProperty('jsonpCallback') ||
        -          a.hasOwnProperty('jsonpCallbackFunction')
        -        ) {
        -          for (const attr in a) {
        -            jsonpOptions[attr] = a[attr];
        -          }
        -        } else if (a instanceof p5.XML) {
        -          data = a.serialize();
        -          contentType = 'application/xml';
        -        } else {
        -          data = JSON.stringify(a);
        -          contentType = 'application/json';
        -        }
        -      } else if (typeof a === 'function') {
        -        if (!callback) {
        -          callback = a;
        -        } else {
        -          errorCallback = a;
        -        }
        -      }
        -    }
        -
        -    let headers =
        -      method === 'GET'
        -        ? new Headers()
        -        : new Headers({ 'Content-Type': contentType });
        +  };
         
        -    request = new Request(path, {
        -      method,
        -      mode: 'cors',
        -      body: data,
        -      headers
        -    });
        -  }
        -  // do some sort of smart type checking
        -  if (!type) {
        -    if (path.includes('json')) {
        -      type = 'json';
        -    } else if (path.includes('xml')) {
        -      type = 'xml';
        -    } else {
        -      type = 'text';
        -    }
        -  }
        +  /**
        +   * @module IO
        +   * @submodule Output
        +   * @for p5
        +   */
        +  // private array of p5.PrintWriter objects
        +  fn._pWriters = [];
         
        -  if (type === 'jsonp') {
        -    promise = fetchJsonp(path, jsonpOptions);
        -  } else {
        -    promise = fetch(request);
        -  }
        -  promise = promise.then(res => {
        -    if (!res.ok) {
        -      const err = new Error(res.body);
        -      err.status = res.status;
        -      err.ok = false;
        -      throw err;
        -    } else {
        -      let fileSize = 0;
        -      if (type !== 'jsonp') {
        -        fileSize = res.headers.get('content-length');
        -      }
        -      if (fileSize && fileSize > 64000000) {
        -        p5._friendlyFileLoadError(7, path);
        -      }
        -      switch (type) {
        -        case 'json':
        -        case 'jsonp':
        -          return res.json();
        -        case 'binary':
        -          return res.blob();
        -        case 'arrayBuffer':
        -          return res.arrayBuffer();
        -        case 'xml':
        -          return res.text().then(text => {
        -            const parser = new DOMParser();
        -            const xml = parser.parseFromString(text, 'text/xml');
        -            return new p5.XML(xml.documentElement);
        -          });
        -        default:
        -          return res.text();
        +  /**
        +   * Creates a new <a href="#/p5.PrintWriter">p5.PrintWriter</a> object.
        +   *
        +   * <a href="#/p5.PrintWriter">p5.PrintWriter</a> objects provide a way to
        +   * save a sequence of text data, called the *print stream*, to the user's
        +   * computer. They're low-level objects that enable precise control of text
        +   * output. Functions such as
        +   * <a href="#/p5/saveStrings">saveStrings()</a> and
        +   * <a href="#/p5/saveJSON">saveJSON()</a> are easier to use for simple file
        +   * saving.
        +   *
        +   * The first parameter, `filename`, is the name of the file to be written. If
        +   * a string is passed, as in `createWriter('words.txt')`, a new
        +   * <a href="#/p5.PrintWriter">p5.PrintWriter</a> object will be created that
        +   * writes to a file named `words.txt`.
        +   *
        +   * The second parameter, `extension`, is optional. If a string is passed, as
        +   * in `createWriter('words', 'csv')`, the first parameter will be interpreted
        +   * as the file name and the second parameter as the extension.
        +   *
        +   * @method createWriter
        +   * @param {String} name name of the file to create.
        +   * @param {String} [extension] format to use for the file.
        +   * @return {p5.PrintWriter} stream for writing data.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
        +   *
        +   *   // Display instructions.
        +   *   text('Double-click to save', 5, 50, 90);
        +   *
        +   *   describe('The text "Double-click to save" written in black on a gray background.');
        +   * }
        +   *
        +   * // Save the file when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        +   *     // Create a p5.PrintWriter object.
        +   *     let myWriter = createWriter('xo.txt');
        +   *
        +   *     // Add some lines to the print stream.
        +   *     myWriter.print('XOO');
        +   *     myWriter.print('OXO');
        +   *     myWriter.print('OOX');
        +   *
        +   *     // Save the file and close the print stream.
        +   *     myWriter.close();
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
        +   *
        +   *   // Display instructions.
        +   *   text('Double-click to save', 5, 50, 90);
        +   *
        +   *   describe('The text "Double-click to save" written in black on a gray background.');
        +   * }
        +   *
        +   * // Save the file when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        +   *     // Create a p5.PrintWriter object.
        +   *     // Use the file format .csv.
        +   *     let myWriter = createWriter('mauna_loa_co2', 'csv');
        +   *
        +   *     // Add some lines to the print stream.
        +   *     myWriter.print('date,ppm_co2');
        +   *     myWriter.print('1960-01-01,316.43');
        +   *     myWriter.print('1970-01-01,325.06');
        +   *     myWriter.print('1980-01-01,337.9');
        +   *     myWriter.print('1990-01-01,353.86');
        +   *     myWriter.print('2000-01-01,369.45');
        +   *     myWriter.print('2020-01-01,413.61');
        +   *
        +   *     // Save the file and close the print stream.
        +   *     myWriter.close();
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createWriter = function (name, extension) {
        +    let newPW;
        +    // check that it doesn't already exist
        +    for (const i in fn._pWriters) {
        +      if (fn._pWriters[i].name === name) {
        +        // if a p5.PrintWriter w/ this name already exists...
        +        // return fn._pWriters[i]; // return it w/ contents intact.
        +        // or, could return a new, empty one with a unique name:
        +        newPW = new p5.PrintWriter(name + this.millis(), extension);
        +        fn._pWriters.push(newPW);
        +        return newPW;
               }
             }
        -  });
        -  promise.then(callback || (() => {}));
        -  promise.catch(errorCallback || console.error);
        -  return promise;
        -};
        -
        -/**
        - * @module IO
        - * @submodule Output
        - * @for p5
        - */
        -
        -window.URL = window.URL || window.webkitURL;
        -
        -// private array of p5.PrintWriter objects
        -p5.prototype._pWriters = [];
        -
        -/**
        - * Creates a new <a href="#/p5.PrintWriter">p5.PrintWriter</a> object.
        - *
        - * <a href="#/p5.PrintWriter">p5.PrintWriter</a> objects provide a way to
        - * save a sequence of text data, called the *print stream*, to the user's
        - * computer. They're low-level objects that enable precise control of text
        - * output. Functions such as
        - * <a href="#/p5/saveStrings">saveStrings()</a> and
        - * <a href="#/p5/saveJSON">saveJSON()</a> are easier to use for simple file
        - * saving.
        - *
        - * The first parameter, `filename`, is the name of the file to be written. If
        - * a string is passed, as in `createWriter('words.txt')`, a new
        - * <a href="#/p5.PrintWriter">p5.PrintWriter</a> object will be created that
        - * writes to a file named `words.txt`.
        - *
        - * The second parameter, `extension`, is optional. If a string is passed, as
        - * in `createWriter('words', 'csv')`, the first parameter will be interpreted
        - * as the file name and the second parameter as the extension.
        - *
        - * @method createWriter
        - * @param {String} name name of the file to create.
        - * @param {String} [extension] format to use for the file.
        - * @return {p5.PrintWriter} stream for writing data.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display instructions.
        - *   text('Double-click to save', 5, 50, 90);
        - *
        - *   describe('The text "Double-click to save" written in black on a gray background.');
        - * }
        - *
        - * // Save the file when the user double-clicks.
        - * function doubleClicked() {
        - *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        - *     // Create a p5.PrintWriter object.
        - *     let myWriter = createWriter('xo.txt');
        - *
        - *     // Add some lines to the print stream.
        - *     myWriter.print('XOO');
        - *     myWriter.print('OXO');
        - *     myWriter.print('OOX');
        - *
        - *     // Save the file and close the print stream.
        - *     myWriter.close();
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display instructions.
        - *   text('Double-click to save', 5, 50, 90);
        - *
        - *   describe('The text "Double-click to save" written in black on a gray background.');
        - * }
        - *
        - * // Save the file when the user double-clicks.
        - * function doubleClicked() {
        - *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        - *     // Create a p5.PrintWriter object.
        - *     // Use the file format .csv.
        - *     let myWriter = createWriter('mauna_loa_co2', 'csv');
        - *
        - *     // Add some lines to the print stream.
        - *     myWriter.print('date,ppm_co2');
        - *     myWriter.print('1960-01-01,316.43');
        - *     myWriter.print('1970-01-01,325.06');
        - *     myWriter.print('1980-01-01,337.9');
        - *     myWriter.print('1990-01-01,353.86');
        - *     myWriter.print('2000-01-01,369.45');
        - *     myWriter.print('2020-01-01,413.61');
        - *
        - *     // Save the file and close the print stream.
        - *     myWriter.close();
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createWriter = function(name, extension) {
        -  let newPW;
        -  // check that it doesn't already exist
        -  for (const i in p5.prototype._pWriters) {
        -    if (p5.prototype._pWriters[i].name === name) {
        -      // if a p5.PrintWriter w/ this name already exists...
        -      // return p5.prototype._pWriters[i]; // return it w/ contents intact.
        -      // or, could return a new, empty one with a unique name:
        -      newPW = new p5.PrintWriter(name + this.millis(), extension);
        -      p5.prototype._pWriters.push(newPW);
        -      return newPW;
        -    }
        -  }
        -  newPW = new p5.PrintWriter(name, extension);
        -  p5.prototype._pWriters.push(newPW);
        -  return newPW;
        -};
        -
        -/**
        - * A class to describe a print stream.
        - *
        - * Each `p5.PrintWriter` object provides a way to save a sequence of text
        - * data, called the *print stream*, to the user's computer. It's a low-level
        - * object that enables precise control of text output. Functions such as
        - * <a href="#/p5/saveStrings">saveStrings()</a> and
        - * <a href="#/p5/saveJSON">saveJSON()</a> are easier to use for simple file
        - * saving.
        - *
        - * Note: <a href="#/p5/createWriter">createWriter()</a> is the recommended way
        - * to make an instance of this class.
        - *
        - * @class p5.PrintWriter
        - * @param  {String} filename name of the file to create.
        - * @param  {String} [extension] format to use for the file.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display instructions.
        - *   text('Double-click to save', 5, 50, 90);
        - *
        - *   describe('The text "Double-click to save" written in black on a gray background.');
        - * }
        - *
        - * // Save the file when the user double-clicks.
        - * function doubleClicked() {
        - *   // Create a p5.PrintWriter object.
        - *   let myWriter = createWriter('xo.txt');
        - *
        - *   // Add some lines to the print stream.
        - *   myWriter.print('XOO');
        - *   myWriter.print('OXO');
        - *   myWriter.print('OOX');
        - *
        - *   // Save the file and close the print stream.
        - *   myWriter.close();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.PrintWriter = function(filename, extension) {
        -  let self = this;
        -  this.name = filename;
        -  this.content = '';
        +    newPW = new p5.PrintWriter(name, extension);
        +    fn._pWriters.push(newPW);
        +    return newPW;
        +  };
         
           /**
        -   * Writes data to the print stream without adding new lines.
        +   * A class to describe a print stream.
            *
        -   * The parameter, `data`, is the data to write. `data` can be a number or
        -   * string, as in `myWriter.write('hi')`, or an array of numbers and strings,
        -   * as in `myWriter.write([1, 2, 3])`. A comma will be inserted between array
        -   * array elements when they're added to the print stream.
        +   * Each `p5.PrintWriter` object provides a way to save a sequence of text
        +   * data, called the *print stream*, to the user's computer. It's a low-level
        +   * object that enables precise control of text output. Functions such as
        +   * <a href="#/p5/saveStrings">saveStrings()</a> and
        +   * <a href="#/p5/saveJSON">saveJSON()</a> are easier to use for simple file
        +   * saving.
            *
        -   * @method write
        -   * @param {String|Number|Array} data data to be written as a string, number,
        -   *                                   or array of strings and numbers.
        +   * Note: <a href="#/p5/createWriter">createWriter()</a> is the recommended way
        +   * to make an instance of this class.
        +   *
        +   * @class p5.PrintWriter
        +   * @param  {String} filename name of the file to create.
        +   * @param  {String} [extension] format to use for the file.
            *
            * @example
            * <div>
        @@ -1653,11 +1309,12 @@ p5.PrintWriter = function(filename, extension) {
            * // Save the file when the user double-clicks.
            * function doubleClicked() {
            *   // Create a p5.PrintWriter object.
        -   *   let myWriter = createWriter('numbers.txt');
        +   *   let myWriter = createWriter('xo.txt');
            *
        -   *   // Add some data to the print stream.
        -   *   myWriter.write('1,2,3,');
        -   *   myWriter.write(['4', '5', '6']);
        +   *   // Add some lines to the print stream.
        +   *   myWriter.print('XOO');
        +   *   myWriter.print('OXO');
        +   *   myWriter.print('OOX');
            *
            *   // Save the file and close the print stream.
            *   myWriter.close();
        @@ -1665,21 +1322,400 @@ p5.PrintWriter = function(filename, extension) {
            * </code>
            * </div>
            */
        -  this.write = function(data) {
        -    this.content += data;
        +  p5.PrintWriter = function (filename, extension) {
        +    let self = this;
        +    this.name = filename;
        +    this.content = '';
        +
        +    /**
        +     * Writes data to the print stream without adding new lines.
        +     *
        +     * The parameter, `data`, is the data to write. `data` can be a number or
        +     * string, as in `myWriter.write('hi')`, or an array of numbers and strings,
        +     * as in `myWriter.write([1, 2, 3])`. A comma will be inserted between array
        +     * array elements when they're added to the print stream.
        +     *
        +     * @method write
        +     * @param {String|Number|Array} data data to be written as a string, number,
        +     *                                   or array of strings and numbers.
        +     *
        +     * @example
        +     * <div>
        +     * <code>
        +     * function setup() {
        +     *   createCanvas(100, 100);
        +     *
        +     *   background(200);
        +     *
        +     *   // Style the text.
        +     *   textAlign(LEFT, CENTER);
        +     *   textFont('Courier New');
        +     *   textSize(12);
        +     *
        +     *   // Display instructions.
        +     *   text('Double-click to save', 5, 50, 90);
        +     *
        +     *   describe('The text "Double-click to save" written in black on a gray background.');
        +     * }
        +     *
        +     * // Save the file when the user double-clicks.
        +     * function doubleClicked() {
        +     *   // Create a p5.PrintWriter object.
        +     *   let myWriter = createWriter('numbers.txt');
        +     *
        +     *   // Add some data to the print stream.
        +     *   myWriter.write('1,2,3,');
        +     *   myWriter.write(['4', '5', '6']);
        +     *
        +     *   // Save the file and close the print stream.
        +     *   myWriter.close();
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    this.write = function (data) {
        +      this.content += data;
        +    };
        +
        +    /**
        +     * Writes data to the print stream with new lines added.
        +     *
        +     * The parameter, `data`, is the data to write. `data` can be a number or
        +     * string, as in `myWriter.print('hi')`, or an array of numbers and strings,
        +     * as in `myWriter.print([1, 2, 3])`. A comma will be inserted between array
        +     * array elements when they're added to the print stream.
        +     *
        +     * @method print
        +     * @param {String|Number|Array} data data to be written as a string, number,
        +     *                                   or array of strings and numbers.
        +     *
        +     * @example
        +     * <div>
        +     * <code>
        +     * function setup() {
        +     *   createCanvas(100, 100);
        +     *
        +     *   background(200);
        +     *
        +     *   // Style the text.
        +     *   textAlign(LEFT, CENTER);
        +     *   textFont('Courier New');
        +     *   textSize(12);
        +     *
        +     *   // Display instructions.
        +     *   text('Double-click to save', 5, 50, 90);
        +     *
        +     *   describe('The text "Double-click to save" written in black on a gray background.');
        +     * }
        +     *
        +     * // Save the file when the user double-clicks.
        +     * function doubleClicked() {
        +     *   // Create a p5.PrintWriter object.
        +     *   let myWriter = createWriter('numbers.txt');
        +     *
        +     *   // Add some data to the print stream.
        +     *   myWriter.print('1,2,3,');
        +     *   myWriter.print(['4', '5', '6']);
        +     *
        +     *   // Save the file and close the print stream.
        +     *   myWriter.close();
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    this.print = function (data) {
        +      this.content += `${data}\n`;
        +    };
        +
        +    /**
        +     * Clears all data from the print stream.
        +     *
        +     * @method clear
        +     *
        +     * @example
        +     * <div>
        +     * <code>
        +     * function setup() {
        +     *   createCanvas(100, 100);
        +     *
        +     *   background(200);
        +     *
        +     *   // Style the text.
        +     *   textAlign(LEFT, CENTER);
        +     *   textFont('Courier New');
        +     *   textSize(12);
        +     *
        +     *   // Display instructions.
        +     *   text('Double-click to save', 5, 50, 90);
        +     *
        +     *   describe('The text "Double-click to save" written in black on a gray background.');
        +     * }
        +     *
        +     * // Save the file when the user double-clicks.
        +     * function doubleClicked() {
        +     *   // Create a p5.PrintWriter object.
        +     *   let myWriter = createWriter('numbers.txt');
        +     *
        +     *   // Add some data to the print stream.
        +     *   myWriter.print('Hello p5*js!');
        +     *
        +     *   // Clear the print stream.
        +     *   myWriter.clear();
        +     *
        +     *   // Save the file and close the print stream.
        +     *   myWriter.close();
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    this.clear = function () {
        +      this.content = '';
        +    };
        +
        +    /**
        +     * Saves the file and closes the print stream.
        +     *
        +     * @method close
        +     *
        +     * @example
        +     * <div>
        +     * <code>
        +     * function setup() {
        +     *   createCanvas(100, 100);
        +     *
        +     *   background(200);
        +     *
        +     *   // Style the text.
        +     *   textAlign(LEFT, CENTER);
        +     *   textFont('Courier New');
        +     *   textSize(12);
        +     *
        +     *   // Display instructions.
        +     *   text('Double-click to save', 5, 50, 90);
        +     *
        +     *   describe('The text "Double-click to save" written in black on a gray background.');
        +     * }
        +     *
        +     * // Save the file when the user double-clicks.
        +     * function doubleClicked() {
        +     *   // Create a p5.PrintWriter object.
        +     *   let myWriter = createWriter('cat.txt');
        +     *
        +     *   // Add some data to the print stream.
        +     *   // ASCII art courtesy Wikipedia:
        +     *   // https://en.wikipedia.org/wiki/ASCII_art
        +     *   myWriter.print(' (\\_/) ');
        +     *   myWriter.print("(='.'=)");
        +     *   myWriter.print('(")_(")');
        +     *
        +     *   // Save the file and close the print stream.
        +     *   myWriter.close();
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    this.close = function () {
        +      // convert String to Array for the writeFile Blob
        +      const arr = [];
        +      arr.push(this.content);
        +      fn.writeFile(arr, filename, extension);
        +      // remove from _pWriters array and delete self
        +      for (const i in fn._pWriters) {
        +        if (fn._pWriters[i].name === this.name) {
        +          // remove from _pWriters array
        +          fn._pWriters.splice(i, 1);
        +        }
        +      }
        +      self.clear();
        +      self = {};
        +    };
        +  };
        +
        +  /**
        +   * @module IO
        +   * @submodule Output
        +   * @for p5
        +   */
        +
        +  // object, filename, options --> saveJSON, saveStrings,
        +  // filename, [extension] [canvas] --> saveImage
        +
        +  /**
        +   *  Saves a given element(image, text, json, csv, wav, or html) to the client's
        +   *  computer. The first parameter can be a pointer to element we want to save.
        +   *  The element can be one of <a href="#/p5.Element">p5.Element</a>,an Array of
        +   *  Strings, an Array of JSON, a JSON object, a <a href="#/p5.Table">p5.Table
        +   *  </a>, a <a href="#/p5.Image">p5.Image</a>, or a p5.SoundFile (requires
        +   *  p5.sound). The second parameter is a filename (including extension).The
        +   *  third parameter is for options specific to this type of object. This method
        +   *  will save a file that fits the given parameters.
        +   *  If it is called without specifying an element, by default it will save the
        +   *  whole canvas as an image file. You can optionally specify a filename as
        +   *  the first parameter in such a case.
        +   *  **Note that it is not recommended to
        +   *  call this method within draw, as it will open a new save dialog on every
        +   *  render.**
        +   *
        +   * @method save
        +   * @param  {Object|String} [objectOrFilename]  If filename is provided, will
        +   *                                             save canvas as an image with
        +   *                                             either png or jpg extension
        +   *                                             depending on the filename.
        +   *                                             If object is provided, will
        +   *                                             save depending on the object
        +   *                                             and filename (see examples
        +   *                                             above).
        +   * @param  {String} [filename] If an object is provided as the first
        +   *                               parameter, then the second parameter
        +   *                               indicates the filename,
        +   *                               and should include an appropriate
        +   *                               file extension (see examples above).
        +   * @param  {Boolean|String} [options]  Additional options depend on
        +   *                            filetype. For example, when saving JSON,
        +   *                            <code>true</code> indicates that the
        +   *                            output will be optimized for filesize,
        +   *                            rather than readability.
        +   *
        +   * @example
        +   * <div class="norender"><code>
        +   * // Saves the canvas as an image
        +   * cnv = createCanvas(300, 300);
        +   * save(cnv, 'myCanvas.jpg');
        +   *
        +   * // Saves the canvas as an image by default
        +   * save('myCanvas.jpg');
        +   * describe('An example for saving a canvas as an image.');
        +   * </code></div>
        +   *
        +   * <div class="norender"><code>
        +   * // Saves p5.Image as an image
        +   * img = createImage(10, 10);
        +   * save(img, 'myImage.png');
        +   * describe('An example for saving a p5.Image element as an image.');
        +   * </code></div>
        +   *
        +   * <div class="norender"><code>
        +   * // Saves p5.Renderer object as an image
        +   * obj = createGraphics(100, 100);
        +   * save(obj, 'myObject.png');
        +   * describe('An example for saving a p5.Renderer element.');
        +   * </code></div>
        +   *
        +   * <div class="norender"><code>
        +   * let myTable = new p5.Table();
        +   * // Saves table as html file
        +   * save(myTable, 'myTable.html');
        +   *
        +   * // Comma Separated Values
        +   * save(myTable, 'myTable.csv');
        +   *
        +   * // Tab Separated Values
        +   * save(myTable, 'myTable.tsv');
        +   *
        +   * describe(`An example showing how to save a table in formats of
        +   *   HTML, CSV and TSV.`);
        +   * </code></div>
        +   *
        +   * <div class="norender"><code>
        +   * let myJSON = { a: 1, b: true };
        +   *
        +   * // Saves pretty JSON
        +   * save(myJSON, 'my.json');
        +   *
        +   * // Optimizes JSON filesize
        +   * save(myJSON, 'my.json', true);
        +   *
        +   * describe('An example for saving JSON to a txt file with some extra arguments.');
        +   * </code></div>
        +   *
        +   * <div class="norender"><code>
        +   * // Saves array of strings to text file with line breaks after each item
        +   * let arrayOfStrings = ['a', 'b'];
        +   * save(arrayOfStrings, 'my.txt');
        +   * describe(`An example for saving an array of strings to text file
        +   *   with line breaks.`);
        +   * </code></div>
        +   */
        +  fn.save = function (object, _filename, _options) {
        +    // TODO: parameters is not used correctly
        +    // parse the arguments and figure out which things we are saving
        +    const args = arguments;
        +    // =================================================
        +    // OPTION 1: saveCanvas...
        +
        +    // if no arguments are provided, save canvas
        +    const cnv = this._curElement ? this._curElement.elt : this.elt;
        +    if (args.length === 0) {
        +      fn.saveCanvas(cnv);
        +      return;
        +
        +    } else if (args[0] instanceof Renderer || args[0] instanceof Graphics) {
        +      // otherwise, parse the arguments
        +      // if first param is a p5Graphics, then saveCanvas
        +      fn.saveCanvas(args[0].canvas, args[1], args[2]);
        +      return;
        +
        +    } else if (args.length === 1 && typeof args[0] === 'string') {
        +      // if 1st param is String and only one arg, assume it is canvas filename
        +      fn.saveCanvas(cnv, args[0]);
        +
        +    } else {
        +      // =================================================
        +      // OPTION 2: extension clarifies saveStrings vs. saveJSON
        +      const extension = _checkFileExtension(args[1], args[2])[1];
        +      switch (extension) {
        +        case 'json':
        +          fn.saveJSON(args[0], args[1], args[2]);
        +          return;
        +        case 'txt':
        +          fn.saveStrings(args[0], args[1], args[2]);
        +          return;
        +        // =================================================
        +        // OPTION 3: decide based on object...
        +        default:
        +          if (args[0] instanceof Array) {
        +            fn.saveStrings(args[0], args[1], args[2]);
        +          } else if (args[0] instanceof p5.Table) {
        +            fn.saveTable(args[0], args[1], args[2]);
        +          } else if (args[0] instanceof p5.Image) {
        +            fn.saveCanvas(args[0].canvas, args[1]);
        +          } else if (args[0] instanceof p5.SoundFile) {
        +            fn.saveSound(args[0], args[1], args[2], args[3]);
        +          }
        +      }
        +    }
           };
         
           /**
        -   * Writes data to the print stream with new lines added.
        +   * Saves an `Object` or `Array` to a JSON file.
        +   *
        +   * JavaScript Object Notation
        +   * (<a href="https://developer.mozilla.org/en-US/docs/Glossary/JSON" target="_blank">JSON</a>)
        +   * is a standard format for sending data between applications. The format is
        +   * based on JavaScript objects which have keys and values. JSON files store
        +   * data in an object with strings as keys. Values can be strings, numbers,
        +   * Booleans, arrays, `null`, or other objects.
        +   *
        +   * The first parameter, `json`, is the data to save. The data can be an array,
        +   * as in `[1, 2, 3]`, or an object, as in
        +   * `{ x: 50, y: 50, color: 'deeppink' }`.
        +   *
        +   * The second parameter, `filename`, is a string that sets the file's name.
        +   * For example, calling `saveJSON([1, 2, 3], 'data.json')` saves the array
        +   * `[1, 2, 3]` to a file called `data.json` on the user's computer.
        +   *
        +   * The third parameter, `optimize`, is optional. If `true` is passed, as in
        +   * `saveJSON([1, 2, 3], 'data.json', true)`, then all unneeded whitespace will
        +   * be removed to reduce the file size.
            *
        -   * The parameter, `data`, is the data to write. `data` can be a number or
        -   * string, as in `myWriter.print('hi')`, or an array of numbers and strings,
        -   * as in `myWriter.print([1, 2, 3])`. A comma will be inserted between array
        -   * array elements when they're added to the print stream.
        +   * Note: The browser will either save the file immediately or prompt the user
        +   * with a dialogue window.
            *
        -   * @method print
        -   * @param {String|Number|Array} data data to be written as a string, number,
        -   *                                   or array of strings and numbers.
        +   * @method saveJSON
        +   * @param  {Array|Object} json data to save.
        +   * @param  {String} filename name of the file to be saved.
        +   * @param  {Boolean} [optimize] whether to trim unneeded whitespace. Defaults
        +   *                              to `true`.
            *
            * @example
            * <div>
        @@ -1702,29 +1738,17 @@ p5.PrintWriter = function(filename, extension) {
            *
            * // Save the file when the user double-clicks.
            * function doubleClicked() {
        -   *   // Create a p5.PrintWriter object.
        -   *   let myWriter = createWriter('numbers.txt');
        -   *
        -   *   // Add some data to the print stream.
        -   *   myWriter.print('1,2,3,');
        -   *   myWriter.print(['4', '5', '6']);
        +   *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        +   *     // Create an array.
        +   *     let data = [1, 2, 3];
            *
        -   *   // Save the file and close the print stream.
        -   *   myWriter.close();
        +   *     // Save the JSON file.
        +   *     saveJSON(data, 'numbers.json');
        +   *   }
            * }
            * </code>
            * </div>
        -   */
        -  this.print = function(data) {
        -    this.content += `${data}\n`;
        -  };
        -
        -  /**
        -   * Clears all data from the print stream.
            *
        -   * @method clear
        -   *
        -   * @example
            * <div>
            * <code>
            * function setup() {
        @@ -1745,29 +1769,89 @@ p5.PrintWriter = function(filename, extension) {
            *
            * // Save the file when the user double-clicks.
            * function doubleClicked() {
        -   *   // Create a p5.PrintWriter object.
        -   *   let myWriter = createWriter('numbers.txt');
        +   *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        +   *     // Create an object.
        +   *     let data = { x: mouseX, y: mouseY };
        +   *
        +   *     // Save the JSON file.
        +   *     saveJSON(data, 'state.json');
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
            *
        -   *   // Add some data to the print stream.
        -   *   myWriter.print('Hello p5*js!');
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
            *
        -   *   // Clear the print stream.
        -   *   myWriter.clear();
        +   *   // Display instructions.
        +   *   text('Double-click to save', 5, 50, 90);
            *
        -   *   // Save the file and close the print stream.
        -   *   myWriter.close();
        +   *   describe('The text "Double-click to save" written in black on a gray background.');
        +   * }
        +   *
        +   * // Save the file when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        +   *     // Create an object.
        +   *     let data = { x: mouseX, y: mouseY };
        +   *
        +   *     // Save the JSON file and reduce its size.
        +   *     saveJSON(data, 'state.json', true);
        +   *   }
            * }
            * </code>
            * </div>
            */
        -  this.clear = function() {
        -    this.content = '';
        +  fn.saveJSON = function (json, filename, optimize) {
        +    // p5._validateParameters('saveJSON', arguments);
        +    let stringify;
        +    if (optimize) {
        +      stringify = JSON.stringify(json);
        +    } else {
        +      stringify = JSON.stringify(json, undefined, 2);
        +    }
        +    this.saveStrings(stringify.split('\n'), filename, 'json');
           };
         
           /**
        -   * Saves the file and closes the print stream.
        +   * Saves an `Array` of `String`s to a file, one per line.
        +   *
        +   * The first parameter, `list`, is an array with the strings to save.
            *
        -   * @method close
        +   * The second parameter, `filename`, is a string that sets the file's name.
        +   * For example, calling `saveStrings(['0', '01', '011'], 'data.txt')` saves
        +   * the array `['0', '01', '011']` to a file called `data.txt` on the user's
        +   * computer.
        +   *
        +   * The third parameter, `extension`, is optional. If a string is passed, as in
        +   * `saveStrings(['0', '01', '0`1'], 'data', 'txt')`, the second parameter will
        +   * be interpreted as the file name and the third parameter as the extension.
        +   *
        +   * The fourth parameter, `isCRLF`, is also optional, If `true` is passed, as
        +   * in `saveStrings(['0', '01', '011'], 'data', 'txt', true)`, then two
        +   * characters, `\r\n` , will be added to the end of each string to create new
        +   * lines in the saved file. `\r` is a carriage return (CR) and `\n` is a line
        +   * feed (LF). By default, only `\n` (line feed) is added to each string in
        +   * order to create new lines.
        +   *
        +   * Note: The browser will either save the file immediately or prompt the user
        +   * with a dialogue window.
        +   *
        +   *  @method saveStrings
        +   *  @param  {String[]} list data to save.
        +   *  @param  {String} filename name of file to be saved.
        +   *  @param  {String} [extension] format to use for the file.
        +   *  @param  {Boolean} [isCRLF] whether to add `\r\n` to the end of each
        +   *                             string. Defaults to `false`.
            *
            * @example
            * <div>
        @@ -1790,741 +1874,317 @@ p5.PrintWriter = function(filename, extension) {
            *
            * // Save the file when the user double-clicks.
            * function doubleClicked() {
        -   *   // Create a p5.PrintWriter object.
        -   *   let myWriter = createWriter('cat.txt');
        +   *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        +   *     // Create an array.
        +   *     let data = ['0', '01', '011'];
        +   *
        +   *     // Save the text file.
        +   *     saveStrings(data, 'data.txt');
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
            *
        -   *   // Add some data to the print stream.
        -   *   // ASCII art courtesy Wikipedia:
        -   *   // https://en.wikipedia.org/wiki/ASCII_art
        -   *   myWriter.print(' (\\_/) ');
        -   *   myWriter.print("(='.'=)");
        -   *   myWriter.print('(")_(")');
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
            *
        -   *   // Save the file and close the print stream.
        -   *   myWriter.close();
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
        +   *
        +   *   // Display instructions.
        +   *   text('Double-click to save', 5, 50, 90);
        +   *
        +   *   describe('The text "Double-click to save" written in black on a gray background.');
        +   * }
        +   *
        +   * // Save the file when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        +   *     // Create an array.
        +   *     // ASCII art courtesy Wikipedia:
        +   *     // https://en.wikipedia.org/wiki/ASCII_art
        +   *     let data = [' (\\_/) ', "(='.'=)", '(")_(")'];
        +   *
        +   *     // Save the text file.
        +   *     saveStrings(data, 'cat', 'txt');
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
        +   *
        +   *   // Display instructions.
        +   *   text('Double-click to save', 5, 50, 90);
        +   *
        +   *   describe('The text "Double-click to save" written in black on a gray background.');
        +   * }
        +   *
        +   * // Save the file when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        +   *     // Create an array.
        +   *     //   +--+
        +   *     //  /  /|
        +   *     // +--+ +
        +   *     // |  |/
        +   *     // +--+
        +   *     let data = ['  +--+', ' /  /|', '+--+ +', '|  |/', '+--+'];
        +   *
        +   *     // Save the text file.
        +   *     // Use CRLF for line endings.
        +   *     saveStrings(data, 'box', 'txt', true);
        +   *   }
            * }
            * </code>
            * </div>
            */
        -  this.close = function() {
        -    // convert String to Array for the writeFile Blob
        -    const arr = [];
        -    arr.push(this.content);
        -    p5.prototype.writeFile(arr, filename, extension);
        -    // remove from _pWriters array and delete self
        -    for (const i in p5.prototype._pWriters) {
        -      if (p5.prototype._pWriters[i].name === this.name) {
        -        // remove from _pWriters array
        -        p5.prototype._pWriters.splice(i, 1);
        -      }
        +  fn.saveStrings = function (list, filename, extension, isCRLF) {
        +    // p5._validateParameters('saveStrings', arguments);
        +    const ext = extension || 'txt';
        +    const pWriter = new p5.PrintWriter(filename, ext);
        +    for (let item of list) {
        +      isCRLF ? pWriter.write(item + '\r\n') : pWriter.write(item + '\n');
             }
        -    self.clear();
        -    self = {};
        +    pWriter.close();
        +    pWriter.clear();
           };
        -};
        -
        -/**
        - * @module IO
        - * @submodule Output
        - * @for p5
        - */
        -
        -// object, filename, options --> saveJSON, saveStrings,
        -// filename, [extension] [canvas] --> saveImage
        -
        -/**
        - *  Saves a given element(image, text, json, csv, wav, or html) to the client's
        - *  computer. The first parameter can be a pointer to element we want to save.
        - *  The element can be one of <a href="#/p5.Element">p5.Element</a>,an Array of
        - *  Strings, an Array of JSON, a JSON object, a <a href="#/p5.Table">p5.Table
        - *  </a>, a <a href="#/p5.Image">p5.Image</a>, or a p5.SoundFile (requires
        - *  p5.sound). The second parameter is a filename (including extension).The
        - *  third parameter is for options specific to this type of object. This method
        - *  will save a file that fits the given parameters.
        - *  If it is called without specifying an element, by default it will save the
        - *  whole canvas as an image file. You can optionally specify a filename as
        - *  the first parameter in such a case.
        - *  **Note that it is not recommended to
        - *  call this method within draw, as it will open a new save dialog on every
        - *  render.**
        - *
        - * @method save
        - * @param  {Object|String} [objectOrFilename]  If filename is provided, will
        - *                                             save canvas as an image with
        - *                                             either png or jpg extension
        - *                                             depending on the filename.
        - *                                             If object is provided, will
        - *                                             save depending on the object
        - *                                             and filename (see examples
        - *                                             above).
        - * @param  {String} [filename] If an object is provided as the first
        - *                               parameter, then the second parameter
        - *                               indicates the filename,
        - *                               and should include an appropriate
        - *                               file extension (see examples above).
        - * @param  {Boolean|String} [options]  Additional options depend on
        - *                            filetype. For example, when saving JSON,
        - *                            <code>true</code> indicates that the
        - *                            output will be optimized for filesize,
        - *                            rather than readability.
        - *
        - * @example
        - * <div class="norender"><code>
        - * // Saves the canvas as an image
        - * cnv = createCanvas(300, 300);
        - * save(cnv, 'myCanvas.jpg');
        - *
        - * // Saves the canvas as an image by default
        - * save('myCanvas.jpg');
        - * describe('An example for saving a canvas as an image.');
        - * </code></div>
        - *
        - * <div class="norender"><code>
        - * // Saves p5.Image as an image
        - * img = createImage(10, 10);
        - * save(img, 'myImage.png');
        - * describe('An example for saving a p5.Image element as an image.');
        - * </code></div>
        - *
        - * <div class="norender"><code>
        - * // Saves p5.Renderer object as an image
        - * obj = createGraphics(100, 100);
        - * save(obj, 'myObject.png');
        - * describe('An example for saving a p5.Renderer element.');
        - * </code></div>
        - *
        - * <div class="norender"><code>
        - * let myTable = new p5.Table();
        - * // Saves table as html file
        - * save(myTable, 'myTable.html');
        - *
        - * // Comma Separated Values
        - * save(myTable, 'myTable.csv');
        - *
        - * // Tab Separated Values
        - * save(myTable, 'myTable.tsv');
        - *
        - * describe(`An example showing how to save a table in formats of
        - *   HTML, CSV and TSV.`);
        - * </code></div>
        - *
        - * <div class="norender"><code>
        - * let myJSON = { a: 1, b: true };
        - *
        - * // Saves pretty JSON
        - * save(myJSON, 'my.json');
        - *
        - * // Optimizes JSON filesize
        - * save(myJSON, 'my.json', true);
        - *
        - * describe('An example for saving JSON to a txt file with some extra arguments.');
        - * </code></div>
        - *
        - * <div class="norender"><code>
        - * // Saves array of strings to text file with line breaks after each item
        - * let arrayOfStrings = ['a', 'b'];
        - * save(arrayOfStrings, 'my.txt');
        - * describe(`An example for saving an array of strings to text file
        - *   with line breaks.`);
        - * </code></div>
        - */
        -
        -p5.prototype.save = function(object, _filename, _options) {
        -  // parse the arguments and figure out which things we are saving
        -  const args = arguments;
        -  // =================================================
        -  // OPTION 1: saveCanvas...
        -
        -  // if no arguments are provided, save canvas
        -  const cnv = this._curElement ? this._curElement.elt : this.elt;
        -  if (args.length === 0) {
        -    p5.prototype.saveCanvas(cnv);
        -    return;
        -  } else if (args[0] instanceof p5.Renderer || args[0] instanceof p5.Graphics) {
        -    // otherwise, parse the arguments
        -
        -    // if first param is a p5Graphics, then saveCanvas
        -    p5.prototype.saveCanvas(args[0].elt, args[1], args[2]);
        -    return;
        -  } else if (args.length === 1 && typeof args[0] === 'string') {
        -    // if 1st param is String and only one arg, assume it is canvas filename
        -    p5.prototype.saveCanvas(cnv, args[0]);
        -  } else {
        -    // =================================================
        -    // OPTION 2: extension clarifies saveStrings vs. saveJSON
        -    const extension = _checkFileExtension(args[1], args[2])[1];
        -    switch (extension) {
        -      case 'json':
        -        p5.prototype.saveJSON(args[0], args[1], args[2]);
        -        return;
        -      case 'txt':
        -        p5.prototype.saveStrings(args[0], args[1], args[2]);
        -        return;
        -      // =================================================
        -      // OPTION 3: decide based on object...
        -      default:
        -        if (args[0] instanceof Array) {
        -          p5.prototype.saveStrings(args[0], args[1], args[2]);
        -        } else if (args[0] instanceof p5.Table) {
        -          p5.prototype.saveTable(args[0], args[1], args[2]);
        -        } else if (args[0] instanceof p5.Image) {
        -          p5.prototype.saveCanvas(args[0].canvas, args[1]);
        -        } else if (args[0] instanceof p5.SoundFile) {
        -          p5.prototype.saveSound(args[0], args[1], args[2], args[3]);
        -        }
        -    }
        -  }
        -};
         
        -/**
        - * Saves an `Object` or `Array` to a JSON file.
        - *
        - * JavaScript Object Notation
        - * (<a href="https://developer.mozilla.org/en-US/docs/Glossary/JSON" target="_blank">JSON</a>)
        - * is a standard format for sending data between applications. The format is
        - * based on JavaScript objects which have keys and values. JSON files store
        - * data in an object with strings as keys. Values can be strings, numbers,
        - * Booleans, arrays, `null`, or other objects.
        - *
        - * The first parameter, `json`, is the data to save. The data can be an array,
        - * as in `[1, 2, 3]`, or an object, as in
        - * `{ x: 50, y: 50, color: 'deeppink' }`.
        - *
        - * The second parameter, `filename`, is a string that sets the file's name.
        - * For example, calling `saveJSON([1, 2, 3], 'data.json')` saves the array
        - * `[1, 2, 3]` to a file called `data.json` on the user's computer.
        - *
        - * The third parameter, `optimize`, is optional. If `true` is passed, as in
        - * `saveJSON([1, 2, 3], 'data.json', true)`, then all unneeded whitespace will
        - * be removed to reduce the file size.
        - *
        - * Note: The browser will either save the file immediately or prompt the user
        - * with a dialogue window.
        - *
        - * @method saveJSON
        - * @param  {Array|Object} json data to save.
        - * @param  {String} filename name of the file to be saved.
        - * @param  {Boolean} [optimize] whether to trim unneeded whitespace. Defaults
        - *                              to `true`.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display instructions.
        - *   text('Double-click to save', 5, 50, 90);
        - *
        - *   describe('The text "Double-click to save" written in black on a gray background.');
        - * }
        - *
        - * // Save the file when the user double-clicks.
        - * function doubleClicked() {
        - *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        - *     // Create an array.
        - *     let data = [1, 2, 3];
        - *
        - *     // Save the JSON file.
        - *     saveJSON(data, 'numbers.json');
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display instructions.
        - *   text('Double-click to save', 5, 50, 90);
        - *
        - *   describe('The text "Double-click to save" written in black on a gray background.');
        - * }
        - *
        - * // Save the file when the user double-clicks.
        - * function doubleClicked() {
        - *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        - *     // Create an object.
        - *     let data = { x: mouseX, y: mouseY };
        - *
        - *     // Save the JSON file.
        - *     saveJSON(data, 'state.json');
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display instructions.
        - *   text('Double-click to save', 5, 50, 90);
        - *
        - *   describe('The text "Double-click to save" written in black on a gray background.');
        - * }
        - *
        - * // Save the file when the user double-clicks.
        - * function doubleClicked() {
        - *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        - *     // Create an object.
        - *     let data = { x: mouseX, y: mouseY };
        - *
        - *     // Save the JSON file and reduce its size.
        - *     saveJSON(data, 'state.json', true);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.saveJSON = function(json, filename, opt) {
        -  p5._validateParameters('saveJSON', arguments);
        -  let stringify;
        -  if (opt) {
        -    stringify = JSON.stringify(json);
        -  } else {
        -    stringify = JSON.stringify(json, undefined, 2);
        -  }
        -  this.saveStrings(stringify.split('\n'), filename, 'json');
        -};
        -
        -p5.prototype.saveJSONObject = p5.prototype.saveJSON;
        -p5.prototype.saveJSONArray = p5.prototype.saveJSON;
        -
        -/**
        - * Saves an `Array` of `String`s to a file, one per line.
        - *
        - * The first parameter, `list`, is an array with the strings to save.
        - *
        - * The second parameter, `filename`, is a string that sets the file's name.
        - * For example, calling `saveStrings(['0', '01', '011'], 'data.txt')` saves
        - * the array `['0', '01', '011']` to a file called `data.txt` on the user's
        - * computer.
        - *
        - * The third parameter, `extension`, is optional. If a string is passed, as in
        - * `saveStrings(['0', '01', '0`1'], 'data', 'txt')`, the second parameter will
        - * be interpreted as the file name and the third parameter as the extension.
        - *
        - * The fourth parameter, `isCRLF`, is also optional, If `true` is passed, as
        - * in `saveStrings(['0', '01', '011'], 'data', 'txt', true)`, then two
        - * characters, `\r\n` , will be added to the end of each string to create new
        - * lines in the saved file. `\r` is a carriage return (CR) and `\n` is a line
        - * feed (LF). By default, only `\n` (line feed) is added to each string in
        - * order to create new lines.
        - *
        - * Note: The browser will either save the file immediately or prompt the user
        - * with a dialogue window.
        - *
        - *  @method saveStrings
        - *  @param  {String[]} list data to save.
        - *  @param  {String} filename name of file to be saved.
        - *  @param  {String} [extension] format to use for the file.
        - *  @param  {Boolean} [isCRLF] whether to add `\r\n` to the end of each
        - *                             string. Defaults to `false`.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display instructions.
        - *   text('Double-click to save', 5, 50, 90);
        - *
        - *   describe('The text "Double-click to save" written in black on a gray background.');
        - * }
        - *
        - * // Save the file when the user double-clicks.
        - * function doubleClicked() {
        - *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        - *     // Create an array.
        - *     let data = ['0', '01', '011'];
        - *
        - *     // Save the text file.
        - *     saveStrings(data, 'data.txt');
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display instructions.
        - *   text('Double-click to save', 5, 50, 90);
        - *
        - *   describe('The text "Double-click to save" written in black on a gray background.');
        - * }
        - *
        - * // Save the file when the user double-clicks.
        - * function doubleClicked() {
        - *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        - *     // Create an array.
        - *     // ASCII art courtesy Wikipedia:
        - *     // https://en.wikipedia.org/wiki/ASCII_art
        - *     let data = [' (\\_/) ', "(='.'=)", '(")_(")'];
        - *
        - *     // Save the text file.
        - *     saveStrings(data, 'cat', 'txt');
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display instructions.
        - *   text('Double-click to save', 5, 50, 90);
        - *
        - *   describe('The text "Double-click to save" written in black on a gray background.');
        - * }
        - *
        - * // Save the file when the user double-clicks.
        - * function doubleClicked() {
        - *   if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) {
        - *     // Create an array.
        - *     //   +--+
        - *     //  /  /|
        - *     // +--+ +
        - *     // |  |/
        - *     // +--+
        - *     let data = ['  +--+', ' /  /|', '+--+ +', '|  |/', '+--+'];
        - *
        - *     // Save the text file.
        - *     // Use CRLF for line endings.
        - *     saveStrings(data, 'box', 'txt', true);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.saveStrings = function(list, filename, extension, isCRLF) {
        -  p5._validateParameters('saveStrings', arguments);
        -  const ext = extension || 'txt';
        -  const pWriter = this.createWriter(filename, ext);
        -  for (let i = 0; i < list.length; i++) {
        -    isCRLF ? pWriter.write(list[i] + '\r\n') : pWriter.write(list[i] + '\n');
        +  // =======
        +  // HELPERS
        +  // =======
        +
        +  function escapeHelper(content) {
        +    return content
        +      .replace(/&/g, '&amp;')
        +      .replace(/</g, '&lt;')
        +      .replace(/>/g, '&gt;')
        +      .replace(/"/g, '&quot;')
        +      .replace(/'/g, '&#039;');
           }
        -  pWriter.close();
        -  pWriter.clear();
        -};
        -
        -// =======
        -// HELPERS
        -// =======
        -
        -function escapeHelper(content) {
        -  return content
        -    .replace(/&/g, '&amp;')
        -    .replace(/</g, '&lt;')
        -    .replace(/>/g, '&gt;')
        -    .replace(/"/g, '&quot;')
        -    .replace(/'/g, '&#039;');
        -}
         
        -/**
        - *  Writes the contents of a <a href="#/p5.Table">Table</a> object to a file. Defaults to a
        - *  text file with comma-separated-values ('csv') but can also
        - *  use tab separation ('tsv'), or generate an HTML table ('html').
        - *  The file saving process and location of the saved file will
        - *  vary between web browsers.
        - *
        - *  @method saveTable
        - *  @param  {p5.Table} Table  the <a href="#/p5.Table">Table</a> object to save to a file
        - *  @param  {String} filename the filename to which the Table should be saved
        - *  @param  {String} [options]  can be one of "tsv", "csv", or "html"
        - *  @example
        - *  <div><code>
        - * let table;
        - *
        - * function setup() {
        - *   table = new p5.Table();
        - *
        - *   table.addColumn('id');
        - *   table.addColumn('species');
        - *   table.addColumn('name');
        - *
        - *   let newRow = table.addRow();
        - *   newRow.setNum('id', table.getRowCount() - 1);
        - *   newRow.setString('species', 'Panthera leo');
        - *   newRow.setString('name', 'Lion');
        - *
        - *   // To save, un-comment next line then click 'run'
        - *   // saveTable(table, 'new.csv');
        - *
        - *   describe('no image displayed');
        - * }
        - *
        - * // Saves the following to a file called 'new.csv':
        - * // id,species,name
        - * // 0,Panthera leo,Lion
        - * </code></div>
        - */
        -p5.prototype.saveTable = function(table, filename, options) {
        -  p5._validateParameters('saveTable', arguments);
        -  let ext;
        -  if (options === undefined) {
        -    ext = filename.substring(filename.lastIndexOf('.') + 1, filename.length);
        -  } else {
        -    ext = options;
        -  }
        -  const pWriter = this.createWriter(filename, ext);
        +  /**
        +   *  Writes the contents of a <a href="#/p5.Table">Table</a> object to a file. Defaults to a
        +   *  text file with comma-separated-values ('csv') but can also
        +   *  use tab separation ('tsv'), or generate an HTML table ('html').
        +   *  The file saving process and location of the saved file will
        +   *  vary between web browsers.
        +   *
        +   *  @method saveTable
        +   *  @param  {p5.Table} Table  the <a href="#/p5.Table">Table</a> object to save to a file
        +   *  @param  {String} filename the filename to which the Table should be saved
        +   *  @param  {String} [options]  can be one of "tsv", "csv", or "html"
        +   *  @example
        +   *  <div><code>
        +   * let table;
        +   *
        +   * function setup() {
        +   *   table = new p5.Table();
        +   *
        +   *   table.addColumn('id');
        +   *   table.addColumn('species');
        +   *   table.addColumn('name');
        +   *
        +   *   let newRow = table.addRow();
        +   *   newRow.setNum('id', table.getRowCount() - 1);
        +   *   newRow.setString('species', 'Panthera leo');
        +   *   newRow.setString('name', 'Lion');
        +   *
        +   *   // To save, un-comment next line then click 'run'
        +   *   // saveTable(table, 'new.csv');
        +   *
        +   *   describe('no image displayed');
        +   * }
        +   *
        +   * // Saves the following to a file called 'new.csv':
        +   * // id,species,name
        +   * // 0,Panthera leo,Lion
        +   * </code></div>
        +   */
        +  fn.saveTable = function (table, filename, options) {
        +    // p5._validateParameters('saveTable', arguments);
        +    let ext;
        +    if (options === undefined) {
        +      ext = filename.substring(filename.lastIndexOf('.') + 1, filename.length);
        +      if(ext === filename) ext = 'csv';
        +    } else {
        +      ext = options;
        +    }
        +    const pWriter = this.createWriter(filename, ext);
         
        -  const header = table.columns;
        +    const header = table.columns;
         
        -  let sep = ','; // default to CSV
        -  if (ext === 'tsv') {
        -    sep = '\t';
        -  }
        -  if (ext !== 'html') {
        -    // make header if it has values
        -    if (header[0] !== '0') {
        -      for (let h = 0; h < header.length; h++) {
        -        if (h < header.length - 1) {
        -          pWriter.write(header[h] + sep);
        -        } else {
        -          pWriter.write(header[h]);
        +    let sep = ','; // default to CSV
        +    if (ext === 'tsv') {
        +      sep = '\t';
        +    }
        +    if (ext !== 'html') {
        +      const output = table.toString(sep);
        +      pWriter.write(output);
        +    } else {
        +      // otherwise, make HTML
        +      pWriter.print('<html>');
        +      pWriter.print('<head>');
        +      let str = '  <meta http-equiv="content-type" content';
        +      str += '="text/html;charset=utf-8" />';
        +      pWriter.print(str);
        +      pWriter.print('</head>');
        +
        +      pWriter.print('<body>');
        +      pWriter.print('  <table>');
        +
        +      // make header if it has values
        +      if (header[0] !== '0') {
        +        pWriter.print('    <tr>');
        +        for (let k = 0; k < header.length; k++) {
        +          const e = escapeHelper(header[k]);
        +          pWriter.print(`      <td>${e}`);
        +          pWriter.print('      </td>');
                 }
        +        pWriter.print('    </tr>');
               }
        -      pWriter.write('\n');
        -    }
         
        -    // make rows
        -    for (let i = 0; i < table.rows.length; i++) {
        -      let j;
        -      for (j = 0; j < table.rows[i].arr.length; j++) {
        -        if (j < table.rows[i].arr.length - 1) {
        -          //double quotes should be inserted in csv only if contains comma separated single value
        -          if (ext === 'csv' && String(table.rows[i].arr[j]).includes(',')) {
        -            pWriter.write('"' + table.rows[i].arr[j] + '"' + sep);
        -          } else {
        -            pWriter.write(table.rows[i].arr[j] + sep);
        -          }
        -        } else {
        -          //double quotes should be inserted in csv only if contains comma separated single value
        -          if (ext === 'csv' && String(table.rows[i].arr[j]).includes(',')) {
        -            pWriter.write('"' + table.rows[i].arr[j] + '"');
        -          } else {
        -            pWriter.write(table.rows[i].arr[j]);
        -          }
        +      // make rows
        +      for (let row = 0; row < table.rows.length; row++) {
        +        pWriter.print('    <tr>');
        +        for (let col = 0; col < table.columns.length; col++) {
        +          const entry = table.rows[row].getString(col);
        +          const htmlEntry = escapeHelper(entry);
        +          pWriter.print(`      <td>${htmlEntry}`);
        +          pWriter.print('      </td>');
                 }
        +        pWriter.print('    </tr>');
               }
        -      pWriter.write('\n');
        -    }
        -  } else {
        -    // otherwise, make HTML
        -    pWriter.print('<html>');
        -    pWriter.print('<head>');
        -    let str = '  <meta http-equiv="content-type" content';
        -    str += '="text/html;charset=utf-8" />';
        -    pWriter.print(str);
        -    pWriter.print('</head>');
        -
        -    pWriter.print('<body>');
        -    pWriter.print('  <table>');
        -
        -    // make header if it has values
        -    if (header[0] !== '0') {
        -      pWriter.print('    <tr>');
        -      for (let k = 0; k < header.length; k++) {
        -        const e = escapeHelper(header[k]);
        -        pWriter.print(`      <td>${e}`);
        -        pWriter.print('      </td>');
        -      }
        -      pWriter.print('    </tr>');
        +      pWriter.print('  </table>');
        +      pWriter.print('</body>');
        +      pWriter.print('</html>');
             }
        +    // close and clear the pWriter
        +    pWriter.close();
        +    pWriter.clear();
        +  }; // end saveTable()
         
        -    // make rows
        -    for (let row = 0; row < table.rows.length; row++) {
        -      pWriter.print('    <tr>');
        -      for (let col = 0; col < table.columns.length; col++) {
        -        const entry = table.rows[row].getString(col);
        -        const htmlEntry = escapeHelper(entry);
        -        pWriter.print(`      <td>${htmlEntry}`);
        -        pWriter.print('      </td>');
        -      }
        -      pWriter.print('    </tr>');
        +  /**
        +   *  Generate a blob of file data as a url to prepare for download.
        +   *  Accepts an array of data, a filename, and an extension (optional).
        +   *  This is a private function because it does not do any formatting,
        +   *  but it is used by <a href="#/p5/saveStrings">saveStrings</a>, <a href="#/p5/saveJSON">saveJSON</a>, <a href="#/p5/saveTable">saveTable</a> etc.
        +   *
        +   *  @param  {Array} dataToDownload
        +   *  @param  {String} filename
        +   *  @param  {String} [extension]
        +   *  @private
        +   */
        +  fn.writeFile = function (dataToDownload, filename, extension) {
        +    let type = 'application/octet-stream';
        +    if (fn._isSafari()) {
        +      type = 'text/plain';
             }
        -    pWriter.print('  </table>');
        -    pWriter.print('</body>');
        -    pWriter.print('</html>');
        -  }
        -  // close and clear the pWriter
        -  pWriter.close();
        -  pWriter.clear();
        -}; // end saveTable()
        -
        -/**
        - *  Generate a blob of file data as a url to prepare for download.
        - *  Accepts an array of data, a filename, and an extension (optional).
        - *  This is a private function because it does not do any formatting,
        - *  but it is used by <a href="#/p5/saveStrings">saveStrings</a>, <a href="#/p5/saveJSON">saveJSON</a>, <a href="#/p5/saveTable">saveTable</a> etc.
        - *
        - *  @param  {Array} dataToDownload
        - *  @param  {String} filename
        - *  @param  {String} [extension]
        - *  @private
        - */
        -p5.prototype.writeFile = function(dataToDownload, filename, extension) {
        -  let type = 'application/octet-stream';
        -  if (p5.prototype._isSafari()) {
        -    type = 'text/plain';
        -  }
        -  const blob = new Blob(dataToDownload, {
        -    type
        -  });
        -  p5.prototype.downloadFile(blob, filename, extension);
        -};
        -
        -/**
        - *  Forces download. Accepts a url to filedata/blob, a filename,
        - *  and an extension (optional).
        - *  This is a private function because it does not do any formatting,
        - *  but it is used by <a href="#/p5/saveStrings">saveStrings</a>, <a href="#/p5/saveJSON">saveJSON</a>, <a href="#/p5/saveTable">saveTable</a> etc.
        - *
        - *  @method downloadFile
        - *  @private
        - *  @param  {String|Blob} data    either an href generated by createObjectURL,
        - *                                or a Blob object containing the data
        - *  @param  {String} [filename]
        - *  @param  {String} [extension]
        - */
        -p5.prototype.downloadFile = function(data, fName, extension) {
        -  const fx = _checkFileExtension(fName, extension);
        -  const filename = fx[0];
        +    const blob = new Blob(dataToDownload, {
        +      type
        +    });
        +    fn.downloadFile(blob, filename, extension);
        +  };
         
        -  if (data instanceof Blob) {
        -    fileSaver.saveAs(data, filename);
        -    return;
        -  }
        +  /**
        +   *  Forces download. Accepts a url to filedata/blob, a filename,
        +   *  and an extension (optional).
        +   *  This is a private function because it does not do any formatting,
        +   *  but it is used by <a href="#/p5/saveStrings">saveStrings</a>, <a href="#/p5/saveJSON">saveJSON</a>, <a href="#/p5/saveTable">saveTable</a> etc.
        +   *
        +   *  @method downloadFile
        +   *  @private
        +   *  @param  {String|Blob} data    either an href generated by createObjectURL,
        +   *                                or a Blob object containing the data
        +   *  @param  {String} [filename]
        +   *  @param  {String} [extension]
        +   */
        +  fn.downloadFile = function (data, fName, extension) {
        +    const fx = _checkFileExtension(fName, extension);
        +    const filename = fx[0];
        +    let saveData = data;
         
        -  const a = document.createElement('a');
        -  a.href = data;
        -  a.download = filename;
        +    if (!(saveData instanceof Blob)) {
        +      saveData = new Blob([data]);
        +    }
         
        -  // Firefox requires the link to be added to the DOM before click()
        -  a.onclick = e => {
        -    destroyClickedElement(e);
        -    e.stopPropagation();
        +    fileSaver.saveAs(saveData, filename);
           };
         
        -  a.style.display = 'none';
        -  document.body.appendChild(a);
        -
        -  // Safari will open this file in the same page as a confusing Blob.
        -  if (p5.prototype._isSafari()) {
        -    let aText = 'Hello, Safari user! To download this file...\n';
        -    aText += '1. Go to File --> Save As.\n';
        -    aText += '2. Choose "Page Source" as the Format.\n';
        -    aText += `3. Name it with this extension: ."${fx[1]}"`;
        -    alert(aText);
        +  /**
        +   *  Returns a file extension, or another string
        +   *  if the provided parameter has no extension.
        +   *
        +   *  @param   {String} filename
        +   *  @param   {String} [extension]
        +   *  @return  {String[]} [fileName, fileExtension]
        +   *
        +   *  @private
        +   */
        +  function _checkFileExtension(filename, extension) {
        +    if (!extension || extension === true || extension === 'true') {
        +      extension = '';
        +    }
        +    if (!filename) {
        +      filename = 'untitled';
        +    }
        +    let ext = '';
        +    // make sure the file will have a name, see if filename needs extension
        +    if (filename && filename.includes('.')) {
        +      ext = filename.split('.').pop();
        +    }
        +    // append extension if it doesn't exist
        +    if (extension) {
        +      if (ext !== extension) {
        +        ext = extension;
        +        filename = `${filename}.${ext}`;
        +      }
        +    }
        +    return [filename, ext];
           }
        -  a.click();
        -};
        +  fn._checkFileExtension = _checkFileExtension;
         
        -/**
        - *  Returns a file extension, or another string
        - *  if the provided parameter has no extension.
        - *
        - *  @param   {String} filename
        - *  @param   {String} [extension]
        - *  @return  {String[]} [fileName, fileExtension]
        - *
        - *  @private
        - */
        -function _checkFileExtension(filename, extension) {
        -  if (!extension || extension === true || extension === 'true') {
        -    extension = '';
        -  }
        -  if (!filename) {
        -    filename = 'untitled';
        -  }
        -  let ext = '';
        -  // make sure the file will have a name, see if filename needs extension
        -  if (filename && filename.includes('.')) {
        -    ext = filename.split('.').pop();
        -  }
        -  // append extension if it doesn't exist
        -  if (extension) {
        -    if (ext !== extension) {
        -      ext = extension;
        -      filename = `${filename}.${ext}`;
        -    }
        +  /**
        +   *  Returns true if the browser is Safari, false if not.
        +   *  Safari makes trouble for downloading files.
        +   *
        +   *  @return  {Boolean} [description]
        +   *  @private
        +   */
        +  fn._isSafari = function () {
        +    return window.HTMLElement.toString().includes('Constructor');
        +  };
        +
        +  /**
        +   *  Helper function, a callback for download that deletes
        +   *  an invisible anchor element from the DOM once the file
        +   *  has been automatically downloaded.
        +   *
        +   *  @private
        +   */
        +  function destroyClickedElement(event) {
        +    document.body.removeChild(event.target);
           }
        -  return [filename, ext];
         }
        -p5.prototype._checkFileExtension = _checkFileExtension;
         
        -/**
        - *  Returns true if the browser is Safari, false if not.
        - *  Safari makes trouble for downloading files.
        - *
        - *  @return  {Boolean} [description]
        - *  @private
        - */
        -p5.prototype._isSafari = function() {
        -  return window.HTMLElement.toString().includes('Constructor');
        -};
        +export default files;
         
        -/**
        - *  Helper function, a callback for download that deletes
        - *  an invisible anchor element from the DOM once the file
        - *  has been automatically downloaded.
        - *
        - *  @private
        - */
        -function destroyClickedElement(event) {
        -  document.body.removeChild(event.target);
        +if(typeof p5 !== 'undefined'){
        +  files(p5, p5.prototype);
         }
        -
        -export default p5;
        diff --git a/src/io/index.js b/src/io/index.js
        new file mode 100644
        index 0000000000..474d035cd9
        --- /dev/null
        +++ b/src/io/index.js
        @@ -0,0 +1,11 @@
        +import files from './files.js';
        +import table from './p5.Table.js';
        +import tableRow from './p5.TableRow.js';
        +import xml from './p5.XML.js';
        +
        +export default function(p5){
        +  p5.registerAddon(files);
        +  p5.registerAddon(table);
        +  p5.registerAddon(tableRow);
        +  p5.registerAddon(xml);
        +}
        diff --git a/src/io/p5.Table.js b/src/io/p5.Table.js
        index 4e05615bee..38c09df201 100644
        --- a/src/io/p5.Table.js
        +++ b/src/io/p5.Table.js
        @@ -4,1325 +4,1320 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        +import { stringify } from './csv';
         
        -/**
        - *  Table Options
        - *  Generic class for handling tabular data, typically from a
        - *  CSV, TSV, or other sort of spreadsheet file.
        - *  CSV files are
        - *  <a href="http://en.wikipedia.org/wiki/Comma-separated_values">
        - *  comma separated values</a>, often with the data in quotes. TSV
        - *  files use tabs as separators, and usually don't bother with the
        - *  quotes.
        - *  File names should end with .csv if they're comma separated.
        - *  A rough "spec" for CSV can be found
        - *  <a href="http://tools.ietf.org/html/rfc4180">here</a>.
        - *  To load files, use the <a href="#/p5/loadTable">loadTable</a> method.
        - *  To save tables to your computer, use the <a href="#/p5/save">save</a> method
        - *   or the <a href="#/p5/saveTable">saveTable</a> method.
        - *
        - *  Possible options include:
        - *  <ul>
        - *  <li>csv - parse the table as comma-separated values
        - *  <li>tsv - parse the table as tab-separated values
        - *  <li>header - this table has a header (title) row
        - *  </ul>
        - */
        -
        -/**
        - *  <a href="#/p5.Table">Table</a> objects store data with multiple rows and columns, much
        - *  like in a traditional spreadsheet. Tables can be generated from
        - *  scratch, dynamically, or using data from an existing file.
        - *
        - *  @class p5.Table
        - *  @constructor
        - *  @param  {p5.TableRow[]}     [rows] An array of p5.TableRow objects
        - */
        -p5.Table = class {
        +function table(p5, fn){
           /**
        -   * An array containing the names of the columns in the table, if the "header" the table is
        -   * loaded with the "header" parameter.
        -   * @type {String[]}
        -   * @property columns
        -   * @name columns
        -   * @example
        -   * <div class="norender">
        -   * <code>
        -   * // Given the CSV file "mammals.csv"
        -   * // in the project's "assets" folder:
        -   * //
        -   * // id,species,name
        -   * // 0,Capra hircus,Goat
        -   * // 1,Panthera pardus,Leopard
        -   * // 2,Equus zebra,Zebra
        -   *
        -   * let table;
        -   *
        -   * function preload() {
        -   *   //my table is comma separated value "csv"
        -   *   //and has a header specifying the columns labels
        -   *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        -   * }
        +   *  Table Options
        +   *  Generic class for handling tabular data, typically from a
        +   *  CSV, TSV, or other sort of spreadsheet file.
        +   *  CSV files are
        +   *  <a href="http://en.wikipedia.org/wiki/Comma-separated_values">
        +   *  comma separated values</a>, often with the data in quotes. TSV
        +   *  files use tabs as separators, and usually don't bother with the
        +   *  quotes.
        +   *  File names should end with .csv if they're comma separated.
        +   *  A rough "spec" for CSV can be found
        +   *  <a href="http://tools.ietf.org/html/rfc4180">here</a>.
        +   *  To load files, use the <a href="#/p5/loadTable">loadTable</a> method.
        +   *  To save tables to your computer, use the <a href="#/p5/save">save</a> method
        +   *   or the <a href="#/p5/saveTable">saveTable</a> method.
            *
        -   * function setup() {
        -   *   //print the column names
        -   *   for (let c = 0; c < table.getColumnCount(); c++) {
        -   *     print('column ' + c + ' is named ' + table.columns[c]);
        -   *   }
        -   * }
        -   * </code>
        -   * </div>
        +   *  Possible options include:
        +   *  <ul>
        +   *  <li>csv - parse the table as comma-separated values
        +   *  <li>tsv - parse the table as tab-separated values
        +   *  <li>header - this table has a header (title) row
        +   *  </ul>
            */
        -  constructor(rows = []) {
        -    this.columns = [];
         
        -    /**
        -   * An array containing the <a href="#/p5.Table">p5.TableRow</a> objects that make up the
        -   * rows of the table. The same result as calling <a href="#/p5/getRows">getRows()</a>
        -   * @type {p5.TableRow[]}
        -   * @property rows
        -   * @name rows
        +  /**
        +   *  <a href="#/p5.Table">Table</a> objects store data with multiple rows and columns, much
        +   *  like in a traditional spreadsheet. Tables can be generated from
        +   *  scratch, dynamically, or using data from an existing file.
        +   *
        +   *  @class p5.Table
        +   *  @param  {p5.TableRow[]}     [rows] An array of p5.TableRow objects
            */
        -    this.rows = rows;
        -  }
        +  p5.Table = class Table {
        +    constructor(rows) {
        +      this.columns = [];
        +      this.rows = [];
        +    }
         
        -  /**
        - *  Use <a href="#/p5/addRow">addRow()</a> to add a new row of data to a <a href="#/p5.Table">p5.Table</a> object. By default,
        - *  an empty row is created. Typically, you would store a reference to
        - *  the new row in a TableRow object (see newRow in the example above),
        - *  and then set individual values using <a href="#/p5/set">set()</a>.
        - *
        - *  If a <a href="#/p5.TableRow">p5.TableRow</a> object is included as a parameter, then that row is
        - *  duplicated and added to the table.
        - *
        - *  @method  addRow
        - *  @param   {p5.TableRow} [row] row to be added to the table
        - *  @return  {p5.TableRow} the row that was added
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   //add a row
        - *   let newRow = table.addRow();
        - *   newRow.setString('id', table.getRowCount() - 1);
        - *   newRow.setString('species', 'Canis Lupus');
        - *   newRow.setString('name', 'Wolf');
        - *
        - *   //print the results
        - *   for (let r = 0; r < table.getRowCount(); r++)
        - *     for (let c = 0; c < table.getColumnCount(); c++)
        - *       print(table.getString(r, c));
        - *
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  addRow (row) {
        -  // make sure it is a valid TableRow
        -    const r = row || new p5.TableRow();
        +    toString(separator=',') {
        +      let rows = this.rows.map((row) => row.arr);
        +
        +      if(!this.columns.some((column) => column === null)){
        +        rows = [this.columns, ...rows,]
        +      }
         
        -    if (typeof r.arr === 'undefined' || typeof r.obj === 'undefined') {
        -    //r = new p5.prototype.TableRow(r);
        -      throw new Error(`invalid TableRow: ${r}`);
        +      return stringify(rows, {
        +        separator
        +      });
             }
        -    r.table = this;
        -    this.rows.push(r);
        -    return r;
        -  }
         
        -  /**
        - * Removes a row from the table object.
        - *
        - * @method  removeRow
        - * @param   {Integer} id ID number of the row to remove
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   //remove the first row
        - *   table.removeRow(0);
        - *
        - *   //print the results
        - *   for (let r = 0; r < table.getRowCount(); r++)
        - *     for (let c = 0; c < table.getColumnCount(); c++)
        - *       print(table.getString(r, c));
        - *
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  removeRow (id) {
        -    this.rows[id].table = null; // remove reference to table
        -    const chunk = this.rows.splice(id + 1, this.rows.length);
        -    this.rows.pop();
        -    this.rows = this.rows.concat(chunk);
        -  }
        +    /**
        +     *  Use <a href="#/p5/addRow">addRow()</a> to add a new row of data to a <a href="#/p5.Table">p5.Table</a> object. By default,
        +     *  an empty row is created. Typically, you would store a reference to
        +     *  the new row in a TableRow object (see newRow in the example above),
        +     *  and then set individual values using <a href="#/p5/set">set()</a>.
        +     *
        +     *  If a <a href="#/p5.TableRow">p5.TableRow</a> object is included as a parameter, then that row is
        +     *  duplicated and added to the table.
        +     *
        +     *  @param   {p5.TableRow} [row] row to be added to the table
        +     *  @return  {p5.TableRow} the row that was added
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   //add a row
        +     *   let newRow = table.addRow();
        +     *   newRow.setString('id', table.getRowCount() - 1);
        +     *   newRow.setString('species', 'Canis Lupus');
        +     *   newRow.setString('name', 'Wolf');
        +     *
        +     *   //print the results
        +     *   for (let r = 0; r < table.getRowCount(); r++)
        +     *     for (let c = 0; c < table.getColumnCount(); c++)
        +     *       print(table.getString(r, c));
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    addRow (row) {
        +    // make sure it is a valid TableRow
        +      const r = row || new p5.TableRow();
         
        -  /**
        - * Returns a reference to the specified <a href="#/p5.TableRow">p5.TableRow</a>. The reference
        - * can then be used to get and set values of the selected row.
        - *
        - * @method  getRow
        - * @param  {Integer}   rowID ID number of the row to get
        - * @return {p5.TableRow} <a href="#/p5.TableRow">p5.TableRow</a> object
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   let row = table.getRow(1);
        - *   //print it column by column
        - *   //note: a row is an object, not an array
        - *   for (let c = 0; c < table.getColumnCount(); c++) {
        - *     print(row.getString(c));
        - *   }
        - *
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  getRow (r) {
        -    return this.rows[r];
        -  }
        +      if (typeof r.arr === 'undefined' || typeof r.obj === 'undefined') {
        +      //r = new p5.prototype.TableRow(r);
        +        throw new Error(`invalid TableRow: ${r}`);
        +      }
        +      r.table = this;
        +      this.rows.push(r);
        +      return r;
        +    }
         
        -  /**
        - *  Gets all rows from the table. Returns an array of <a href="#/p5.TableRow">p5.TableRow</a>s.
        - *
        - *  @method  getRows
        - *  @return {p5.TableRow[]}   Array of <a href="#/p5.TableRow">p5.TableRow</a>s
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   let rows = table.getRows();
        - *
        - *   //warning: rows is an array of objects
        - *   for (let r = 0; r < rows.length; r++) {
        - *     rows[r].set('name', 'Unicorn');
        - *   }
        - *
        - *   //print the results
        - *   for (let r = 0; r < table.getRowCount(); r++)
        - *     for (let c = 0; c < table.getColumnCount(); c++)
        - *       print(table.getString(r, c));
        - *
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  getRows () {
        -    return this.rows;
        -  }
        +    /**
        +     * Removes a row from the table object.
        +     *
        +     * @param   {Integer} id ID number of the row to remove
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   //remove the first row
        +     *   table.removeRow(0);
        +     *
        +     *   //print the results
        +     *   for (let r = 0; r < table.getRowCount(); r++)
        +     *     for (let c = 0; c < table.getColumnCount(); c++)
        +     *       print(table.getString(r, c));
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    removeRow (id) {
        +      this.rows[id].table = null; // remove reference to table
        +      const chunk = this.rows.splice(id + 1, this.rows.length);
        +      this.rows.pop();
        +      this.rows = this.rows.concat(chunk);
        +    }
         
        -  /**
        - *  Finds the first row in the Table that contains the value
        - *  provided, and returns a reference to that row. Even if
        - *  multiple rows are possible matches, only the first matching
        - *  row is returned. The column to search may be specified by
        - *  either its ID or title.
        - *
        - *  @method  findRow
        - *  @param  {String} value  The value to match
        - *  @param  {Integer|String} column ID number or title of the
        - *                                 column to search
        - *  @return {p5.TableRow}
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   //find the animal named zebra
        - *   let row = table.findRow('Zebra', 'name');
        - *   //find the corresponding species
        - *   print(row.getString('species'));
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  findRow (value, column) {
        -  // try the Object
        -    if (typeof column === 'string') {
        -      for (let i = 0; i < this.rows.length; i++) {
        -        if (this.rows[i].obj[column] === value) {
        -          return this.rows[i];
        +    /**
        +     * Returns a reference to the specified <a href="#/p5.TableRow">p5.TableRow</a>. The reference
        +     * can then be used to get and set values of the selected row.
        +     *
        +     * @param  {Integer}   rowID ID number of the row to get
        +     * @return {p5.TableRow} <a href="#/p5.TableRow">p5.TableRow</a> object
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   let row = table.getRow(1);
        +     *   //print it column by column
        +     *   //note: a row is an object, not an array
        +     *   for (let c = 0; c < table.getColumnCount(); c++) {
        +     *     print(row.getString(c));
        +     *   }
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    getRow (r) {
        +      return this.rows[r];
        +    }
        +
        +    /**
        +     *  Gets all rows from the table. Returns an array of <a href="#/p5.TableRow">p5.TableRow</a>s.
        +     *
        +     *  @return {p5.TableRow[]}   Array of <a href="#/p5.TableRow">p5.TableRow</a>s
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   let rows = table.getRows();
        +     *
        +     *   //warning: rows is an array of objects
        +     *   for (let r = 0; r < rows.length; r++) {
        +     *     rows[r].set('name', 'Unicorn');
        +     *   }
        +     *
        +     *   //print the results
        +     *   for (let r = 0; r < table.getRowCount(); r++)
        +     *     for (let c = 0; c < table.getColumnCount(); c++)
        +     *       print(table.getString(r, c));
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    getRows () {
        +      return this.rows;
        +    }
        +
        +    /**
        +     *  Finds the first row in the Table that contains the value
        +     *  provided, and returns a reference to that row. Even if
        +     *  multiple rows are possible matches, only the first matching
        +     *  row is returned. The column to search may be specified by
        +     *  either its ID or title.
        +     *
        +     *  @param  {String} value  The value to match
        +     *  @param  {Integer|String} column ID number or title of the
        +     *                                 column to search
        +     *  @return {p5.TableRow}
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   //find the animal named zebra
        +     *   let row = table.findRow('Zebra', 'name');
        +     *   //find the corresponding species
        +     *   print(row.getString('species'));
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    findRow (value, column) {
        +    // try the Object
        +      if (typeof column === 'string') {
        +        for (let i = 0; i < this.rows.length; i++) {
        +          if (this.rows[i].obj[column] === value) {
        +            return this.rows[i];
        +          }
                 }
        -      }
        -    } else {
        -    // try the Array
        -      for (let j = 0; j < this.rows.length; j++) {
        -        if (this.rows[j].arr[column] === value) {
        -          return this.rows[j];
        +      } else {
        +      // try the Array
        +        for (let j = 0; j < this.rows.length; j++) {
        +          if (this.rows[j].arr[column] === value) {
        +            return this.rows[j];
        +          }
                 }
               }
        +      // otherwise...
        +      return null;
             }
        -    // otherwise...
        -    return null;
        -  }
         
        -  /**
        - *  Finds the rows in the Table that contain the value
        - *  provided, and returns references to those rows. Returns an
        - *  Array, so for must be used to iterate through all the rows,
        - *  as shown in the example above. The column to search may be
        - *  specified by either its ID or title.
        - *
        - *  @method  findRows
        - *  @param  {String} value  The value to match
        - *  @param  {Integer|String} column ID number or title of the
        - *                                 column to search
        - *  @return {p5.TableRow[]}        An Array of TableRow objects
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   //add another goat
        - *   let newRow = table.addRow();
        - *   newRow.setString('id', table.getRowCount() - 1);
        - *   newRow.setString('species', 'Scape Goat');
        - *   newRow.setString('name', 'Goat');
        - *
        - *   //find the rows containing animals named Goat
        - *   let rows = table.findRows('Goat', 'name');
        - *   print(rows.length + ' Goats found');
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  findRows (value, column) {
        -    const ret = [];
        -    if (typeof column === 'string') {
        -      for (let i = 0; i < this.rows.length; i++) {
        -        if (this.rows[i].obj[column] === value) {
        -          ret.push(this.rows[i]);
        +    /**
        +     *  Finds the rows in the Table that contain the value
        +     *  provided, and returns references to those rows. Returns an
        +     *  Array, so for must be used to iterate through all the rows,
        +     *  as shown in the example above. The column to search may be
        +     *  specified by either its ID or title.
        +     *
        +     *  @param  {String} value  The value to match
        +     *  @param  {Integer|String} column ID number or title of the
        +     *                                 column to search
        +     *  @return {p5.TableRow[]}        An Array of TableRow objects
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   //add another goat
        +     *   let newRow = table.addRow();
        +     *   newRow.setString('id', table.getRowCount() - 1);
        +     *   newRow.setString('species', 'Scape Goat');
        +     *   newRow.setString('name', 'Goat');
        +     *
        +     *   //find the rows containing animals named Goat
        +     *   let rows = table.findRows('Goat', 'name');
        +     *   print(rows.length + ' Goats found');
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    findRows (value, column) {
        +      const ret = [];
        +      if (typeof column === 'string') {
        +        for (let i = 0; i < this.rows.length; i++) {
        +          if (this.rows[i].obj[column] === value) {
        +            ret.push(this.rows[i]);
        +          }
                 }
        -      }
        -    } else {
        -    // try the Array
        -      for (let j = 0; j < this.rows.length; j++) {
        -        if (this.rows[j].arr[column] === value) {
        -          ret.push(this.rows[j]);
        +      } else {
        +      // try the Array
        +        for (let j = 0; j < this.rows.length; j++) {
        +          if (this.rows[j].arr[column] === value) {
        +            ret.push(this.rows[j]);
        +          }
                 }
               }
        +      return ret;
             }
        -    return ret;
        -  }
         
        -  /**
        - * Finds the first row in the Table that matches the regular
        - * expression provided, and returns a reference to that row.
        - * Even if multiple rows are possible matches, only the first
        - * matching row is returned. The column to search may be
        - * specified by either its ID or title.
        - *
        - * @method  matchRow
        - * @param  {String|RegExp} regexp The regular expression to match
        - * @param  {String|Integer} column The column ID (number) or
        - *                                  title (string)
        - * @return {p5.TableRow}        TableRow object
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   //Search using specified regex on a given column, return TableRow object
        - *   let mammal = table.matchRow(new RegExp('ant'), 1);
        - *   print(mammal.getString(1));
        - *   //Output "Panthera pardus"
        - * }
        - * </code>
        - * </div>
        - */
        -  matchRow (regexp, column) {
        -    if (typeof column === 'number') {
        -      for (let j = 0; j < this.rows.length; j++) {
        -        if (this.rows[j].arr[column].match(regexp)) {
        -          return this.rows[j];
        +    /**
        +     * Finds the first row in the Table that matches the regular
        +     * expression provided, and returns a reference to that row.
        +     * Even if multiple rows are possible matches, only the first
        +     * matching row is returned. The column to search may be
        +     * specified by either its ID or title.
        +     *
        +     * @param  {String|RegExp} regexp The regular expression to match
        +     * @param  {String|Integer} column The column ID (number) or
        +     *                                  title (string)
        +     * @return {p5.TableRow}        TableRow object
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   //Search using specified regex on a given column, return TableRow object
        +     *   let mammal = table.matchRow(new RegExp('ant'), 1);
        +     *   print(mammal.getString(1));
        +     *   //Output "Panthera pardus"
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    matchRow (regexp, column) {
        +      if (typeof column === 'number') {
        +        for (let j = 0; j < this.rows.length; j++) {
        +          if (this.rows[j].arr[column].match(regexp)) {
        +            return this.rows[j];
        +          }
                 }
        -      }
        -    } else {
        -      for (let i = 0; i < this.rows.length; i++) {
        -        if (this.rows[i].obj[column].match(regexp)) {
        -          return this.rows[i];
        +      } else {
        +        for (let i = 0; i < this.rows.length; i++) {
        +          if (this.rows[i].obj[column].match(regexp)) {
        +            return this.rows[i];
        +          }
                 }
               }
        +      return null;
             }
        -    return null;
        -  }
         
        -  /**
        - * Finds the rows in the Table that match the regular expression provided,
        - * and returns references to those rows. Returns an array, so for must be
        - * used to iterate through all the rows, as shown in the example. The
        - * column to search may be specified by either its ID or title.
        - *
        - * @method  matchRows
        - * @param  {String} regexp The regular expression to match
        - * @param  {String|Integer} [column] The column ID (number) or
        - *                                  title (string)
        - * @return {p5.TableRow[]}          An Array of TableRow objects
        - * @example
        - * <div class="norender">
        - * <code>
        - * let table;
        - *
        - * function setup() {
        - *   table = new p5.Table();
        - *
        - *   table.addColumn('name');
        - *   table.addColumn('type');
        - *
        - *   let newRow = table.addRow();
        - *   newRow.setString('name', 'Lion');
        - *   newRow.setString('type', 'Mammal');
        - *
        - *   newRow = table.addRow();
        - *   newRow.setString('name', 'Snake');
        - *   newRow.setString('type', 'Reptile');
        - *
        - *   newRow = table.addRow();
        - *   newRow.setString('name', 'Mosquito');
        - *   newRow.setString('type', 'Insect');
        - *
        - *   newRow = table.addRow();
        - *   newRow.setString('name', 'Lizard');
        - *   newRow.setString('type', 'Reptile');
        - *
        - *   let rows = table.matchRows('R.*', 'type');
        - *   for (let i = 0; i < rows.length; i++) {
        - *     print(rows[i].getString('name') + ': ' + rows[i].getString('type'));
        - *   }
        - * }
        - * // Sketch prints:
        - * // Snake: Reptile
        - * // Lizard: Reptile
        - * </code>
        - * </div>
        - */
        -  matchRows (regexp, column) {
        -    const ret = [];
        -    if (typeof column === 'number') {
        -      for (let j = 0; j < this.rows.length; j++) {
        -        if (this.rows[j].arr[column].match(regexp)) {
        -          ret.push(this.rows[j]);
        +    /**
        +     * Finds the rows in the Table that match the regular expression provided,
        +     * and returns references to those rows. Returns an array, so for must be
        +     * used to iterate through all the rows, as shown in the example. The
        +     * column to search may be specified by either its ID or title.
        +     *
        +     * @param  {String} regexp The regular expression to match
        +     * @param  {String|Integer} [column] The column ID (number) or
        +     *                                  title (string)
        +     * @return {p5.TableRow[]}          An Array of TableRow objects
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * let table;
        +     *
        +     * function setup() {
        +     *   table = new p5.Table();
        +     *
        +     *   table.addColumn('name');
        +     *   table.addColumn('type');
        +     *
        +     *   let newRow = table.addRow();
        +     *   newRow.setString('name', 'Lion');
        +     *   newRow.setString('type', 'Mammal');
        +     *
        +     *   newRow = table.addRow();
        +     *   newRow.setString('name', 'Snake');
        +     *   newRow.setString('type', 'Reptile');
        +     *
        +     *   newRow = table.addRow();
        +     *   newRow.setString('name', 'Mosquito');
        +     *   newRow.setString('type', 'Insect');
        +     *
        +     *   newRow = table.addRow();
        +     *   newRow.setString('name', 'Lizard');
        +     *   newRow.setString('type', 'Reptile');
        +     *
        +     *   let rows = table.matchRows('R.*', 'type');
        +     *   for (let i = 0; i < rows.length; i++) {
        +     *     print(rows[i].getString('name') + ': ' + rows[i].getString('type'));
        +     *   }
        +     * }
        +     * // Sketch prints:
        +     * // Snake: Reptile
        +     * // Lizard: Reptile
        +     * </code>
        +     * </div>
        +     */
        +    matchRows (regexp, column) {
        +      const ret = [];
        +      if (typeof column === 'number') {
        +        for (let j = 0; j < this.rows.length; j++) {
        +          if (this.rows[j].arr[column].match(regexp)) {
        +            ret.push(this.rows[j]);
        +          }
                 }
        -      }
        -    } else {
        -      for (let i = 0; i < this.rows.length; i++) {
        -        if (this.rows[i].obj[column].match(regexp)) {
        -          ret.push(this.rows[i]);
        +      } else {
        +        for (let i = 0; i < this.rows.length; i++) {
        +          if (this.rows[i].obj[column].match(regexp)) {
        +            ret.push(this.rows[i]);
        +          }
                 }
               }
        +      return ret;
             }
        -    return ret;
        -  }
         
        -  /**
        - *  Retrieves all values in the specified column, and returns them
        - *  as an array. The column may be specified by either its ID or title.
        - *
        - *  @method  getColumn
        - *  @param  {String|Number} column String or Number of the column to return
        - *  @return {Array}       Array of column values
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   //getColumn returns an array that can be printed directly
        - *   print(table.getColumn('species'));
        - *   //outputs ["Capra hircus", "Panthera pardus", "Equus zebra"]
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  getColumn (value) {
        -    const ret = [];
        -    if (typeof value === 'string') {
        -      for (let i = 0; i < this.rows.length; i++) {
        -        ret.push(this.rows[i].obj[value]);
        -      }
        -    } else {
        -      for (let j = 0; j < this.rows.length; j++) {
        -        ret.push(this.rows[j].arr[value]);
        +    /**
        +     *  Retrieves all values in the specified column, and returns them
        +     *  as an array. The column may be specified by either its ID or title.
        +     *
        +     *  @param  {String|Number} column String or Number of the column to return
        +     *  @return {Array}       Array of column values
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   //getColumn returns an array that can be printed directly
        +     *   print(table.getColumn('species'));
        +     *   //outputs ["Capra hircus", "Panthera pardus", "Equus zebra"]
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    getColumn (value) {
        +      const ret = [];
        +      if (typeof value === 'string') {
        +        for (let i = 0; i < this.rows.length; i++) {
        +          ret.push(this.rows[i].obj[value]);
        +        }
        +      } else {
        +        for (let j = 0; j < this.rows.length; j++) {
        +          ret.push(this.rows[j].arr[value]);
        +        }
               }
        +      return ret;
             }
        -    return ret;
        -  }
        -
        -  /**
        - *  Removes all rows from a Table. While all rows are removed,
        - *  columns and column titles are maintained.
        - *
        - *  @method  clearRows
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   table.clearRows();
        - *   print(table.getRowCount() + ' total rows in table');
        - *   print(table.getColumnCount() + ' total columns in table');
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  clearRows () {
        -    delete this.rows;
        -    this.rows = [];
        -  }
         
        -  /**
        - *  Use <a href="#/p5/addColumn">addColumn()</a> to add a new column to a <a href="#/p5.Table">Table</a> object.
        - *  Typically, you will want to specify a title, so the column
        - *  may be easily referenced later by name. (If no title is
        - *  specified, the new column's title will be null.)
        - *
        - *  @method  addColumn
        - *  @param {String} [title] title of the given column
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   table.addColumn('carnivore');
        - *   table.set(0, 'carnivore', 'no');
        - *   table.set(1, 'carnivore', 'yes');
        - *   table.set(2, 'carnivore', 'no');
        - *
        - *   //print the results
        - *   for (let r = 0; r < table.getRowCount(); r++)
        - *     for (let c = 0; c < table.getColumnCount(); c++)
        - *       print(table.getString(r, c));
        - *
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  addColumn (title) {
        -    const t = title || null;
        -    this.columns.push(t);
        -  }
        +    /**
        +     *  Removes all rows from a Table. While all rows are removed,
        +     *  columns and column titles are maintained.
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   table.clearRows();
        +     *   print(table.getRowCount() + ' total rows in table');
        +     *   print(table.getColumnCount() + ' total columns in table');
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    clearRows () {
        +      delete this.rows;
        +      this.rows = [];
        +    }
         
        -  /**
        - *  Returns the total number of columns in a Table.
        - *
        - *  @method  getColumnCount
        - *  @return {Integer} Number of columns in this table
        - * @example
        - * <div>
        - * <code>
        - * // given the cvs file "blobs.csv" in /assets directory
        - * // ID, Name, Flavor, Shape, Color
        - * // Blob1, Blobby, Sweet, Blob, Pink
        - * // Blob2, Saddy, Savory, Blob, Blue
        - *
        - * let table;
        - *
        - * function preload() {
        - *   table = loadTable('assets/blobs.csv');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(200, 100);
        - *   textAlign(CENTER);
        - *   background(255);
        - * }
        - *
        - * function draw() {
        - *   let numOfColumn = table.getColumnCount();
        - *   text('There are ' + numOfColumn + ' columns in the table.', 100, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -  getColumnCount () {
        -    return this.columns.length;
        -  }
        +    /**
        +     *  Use <a href="#/p5/addColumn">addColumn()</a> to add a new column to a <a href="#/p5.Table">Table</a> object.
        +     *  Typically, you will want to specify a title, so the column
        +     *  may be easily referenced later by name. (If no title is
        +     *  specified, the new column's title will be null.)
        +     *
        +     *  @param {String} [title] title of the given column
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   table.addColumn('carnivore');
        +     *   table.set(0, 'carnivore', 'no');
        +     *   table.set(1, 'carnivore', 'yes');
        +     *   table.set(2, 'carnivore', 'no');
        +     *
        +     *   //print the results
        +     *   for (let r = 0; r < table.getRowCount(); r++)
        +     *     for (let c = 0; c < table.getColumnCount(); c++)
        +     *       print(table.getString(r, c));
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    addColumn (title) {
        +      const t = title || null;
        +      this.columns.push(t);
        +    }
         
        -  /**
        - *  Returns the total number of rows in a Table.
        - *
        - *  @method  getRowCount
        - *  @return {Integer} Number of rows in this table
        - * @example
        - * <div>
        - * <code>
        - * // given the cvs file "blobs.csv" in /assets directory
        - * //
        - * // ID, Name, Flavor, Shape, Color
        - * // Blob1, Blobby, Sweet, Blob, Pink
        - * // Blob2, Saddy, Savory, Blob, Blue
        - *
        - * let table;
        - *
        - * function preload() {
        - *   table = loadTable('assets/blobs.csv');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(200, 100);
        - *   textAlign(CENTER);
        - *   background(255);
        - * }
        - *
        - * function draw() {
        - *   text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -  getRowCount () {
        -    return this.rows.length;
        -  }
        +    /**
        +     *  Returns the total number of columns in a Table.
        +     *
        +     *  @return {Integer} Number of columns in this table
        +     * @example
        +     * <div>
        +     * <code>
        +     * // given the cvs file "blobs.csv" in /assets directory
        +     * // ID, Name, Flavor, Shape, Color
        +     * // Blob1, Blobby, Sweet, Blob, Pink
        +     * // Blob2, Saddy, Savory, Blob, Blue
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   table = loadTable('assets/blobs.csv');
        +     * }
        +     *
        +     * function setup() {
        +     *   createCanvas(200, 100);
        +     *   textAlign(CENTER);
        +     *   background(255);
        +     * }
        +     *
        +     * function draw() {
        +     *   let numOfColumn = table.getColumnCount();
        +     *   text('There are ' + numOfColumn + ' columns in the table.', 100, 50);
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    getColumnCount () {
        +      return this.columns.length;
        +    }
         
        -  /**
        - *  Removes any of the specified characters (or "tokens").
        - *
        - *  If no column is specified, then the values in all columns and
        - *  rows are processed. A specific column may be referenced by
        - *  either its ID or title.
        - *
        - *  @method  removeTokens
        - *  @param  {String} chars  String listing characters to be removed
        - *  @param  {String|Integer} [column] Column ID (number)
        - *                                   or name (string)
        - *
        - * @example
        - * <div class="norender"><code>
        - * function setup() {
        - *   let table = new p5.Table();
        - *
        - *   table.addColumn('name');
        - *   table.addColumn('type');
        - *
        - *   let newRow = table.addRow();
        - *   newRow.setString('name', '   $Lion  ,');
        - *   newRow.setString('type', ',,,Mammal');
        - *
        - *   newRow = table.addRow();
        - *   newRow.setString('name', '$Snake  ');
        - *   newRow.setString('type', ',,,Reptile');
        - *
        - *   table.removeTokens(',$ ');
        - *   print(table.getArray());
        - * }
        - *
        - * // prints:
        - * //  0  "Lion"   "Mamal"
        - * //  1  "Snake"  "Reptile"
        - * </code></div>
        - */
        -  removeTokens (chars, column) {
        -    const escape = s => s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
        -    const charArray = [];
        -    for (let i = 0; i < chars.length; i++) {
        -      charArray.push(escape(chars.charAt(i)));
        +    /**
        +     *  Returns the total number of rows in a Table.
        +     *
        +     *  @return {Integer} Number of rows in this table
        +     * @example
        +     * <div>
        +     * <code>
        +     * // given the cvs file "blobs.csv" in /assets directory
        +     * //
        +     * // ID, Name, Flavor, Shape, Color
        +     * // Blob1, Blobby, Sweet, Blob, Pink
        +     * // Blob2, Saddy, Savory, Blob, Blue
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   table = loadTable('assets/blobs.csv');
        +     * }
        +     *
        +     * function setup() {
        +     *   createCanvas(200, 100);
        +     *   textAlign(CENTER);
        +     *   background(255);
        +     * }
        +     *
        +     * function draw() {
        +     *   text('There are ' + table.getRowCount() + ' rows in the table.', 100, 50);
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    getRowCount () {
        +      return this.rows.length;
             }
        -    const regex = new RegExp(charArray.join('|'), 'g');
         
        -    if (typeof column === 'undefined') {
        -      for (let c = 0; c < this.columns.length; c++) {
        -        for (let d = 0; d < this.rows.length; d++) {
        -          let s = this.rows[d].arr[c];
        -          s = s.replace(regex, '');
        -          this.rows[d].arr[c] = s;
        -          this.rows[d].obj[this.columns[c]] = s;
        -        }
        -      }
        -    } else if (typeof column === 'string') {
        -      for (let j = 0; j < this.rows.length; j++) {
        -        let val = this.rows[j].obj[column];
        -        val = val.replace(regex, '');
        -        this.rows[j].obj[column] = val;
        -        const pos = this.columns.indexOf(column);
        -        this.rows[j].arr[pos] = val;
        +    /**
        +     *  Removes any of the specified characters (or "tokens").
        +     *
        +     *  If no column is specified, then the values in all columns and
        +     *  rows are processed. A specific column may be referenced by
        +     *  either its ID or title.
        +     *
        +     *  @param  {String} chars  String listing characters to be removed
        +     *  @param  {String|Integer} [column] Column ID (number)
        +     *                                   or name (string)
        +     *
        +     * @example
        +     * <div class="norender"><code>
        +     * function setup() {
        +     *   let table = new p5.Table();
        +     *
        +     *   table.addColumn('name');
        +     *   table.addColumn('type');
        +     *
        +     *   let newRow = table.addRow();
        +     *   newRow.setString('name', '   $Lion  ,');
        +     *   newRow.setString('type', ',,,Mammal');
        +     *
        +     *   newRow = table.addRow();
        +     *   newRow.setString('name', '$Snake  ');
        +     *   newRow.setString('type', ',,,Reptile');
        +     *
        +     *   table.removeTokens(',$ ');
        +     *   print(table.getArray());
        +     * }
        +     *
        +     * // prints:
        +     * //  0  "Lion"   "Mamal"
        +     * //  1  "Snake"  "Reptile"
        +     * </code></div>
        +     */
        +    removeTokens (chars, column) {
        +      const escape = s => s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
        +      const charArray = [];
        +      for (let i = 0; i < chars.length; i++) {
        +        charArray.push(escape(chars.charAt(i)));
               }
        -    } else {
        -      for (let k = 0; k < this.rows.length; k++) {
        -        let str = this.rows[k].arr[column];
        -        str = str.replace(regex, '');
        -        this.rows[k].arr[column] = str;
        -        this.rows[k].obj[this.columns[column]] = str;
        +      const regex = new RegExp(charArray.join('|'), 'g');
        +
        +      if (typeof column === 'undefined') {
        +        for (let c = 0; c < this.columns.length; c++) {
        +          for (let d = 0; d < this.rows.length; d++) {
        +            let s = this.rows[d].arr[c];
        +            s = s.replace(regex, '');
        +            this.rows[d].arr[c] = s;
        +            this.rows[d].obj[this.columns[c]] = s;
        +          }
        +        }
        +      } else if (typeof column === 'string') {
        +        for (let j = 0; j < this.rows.length; j++) {
        +          let val = this.rows[j].obj[column];
        +          val = val.replace(regex, '');
        +          this.rows[j].obj[column] = val;
        +          const pos = this.columns.indexOf(column);
        +          this.rows[j].arr[pos] = val;
        +        }
        +      } else {
        +        for (let k = 0; k < this.rows.length; k++) {
        +          let str = this.rows[k].arr[column];
        +          str = str.replace(regex, '');
        +          this.rows[k].arr[column] = str;
        +          this.rows[k].obj[this.columns[column]] = str;
        +        }
               }
             }
        -  }
         
        -  /**
        - *  Trims leading and trailing whitespace, such as spaces and tabs,
        - *  from String table values. If no column is specified, then the
        - *  values in all columns and rows are trimmed. A specific column
        - *  may be referenced by either its ID or title.
        - *
        - *  @method  trim
        - *  @param  {String|Integer} [column] Column ID (number)
        - *                                   or name (string)
        - * @example
        - * <div class="norender"><code>
        - * function setup() {
        - *   let table = new p5.Table();
        - *
        - *   table.addColumn('name');
        - *   table.addColumn('type');
        - *
        - *   let newRow = table.addRow();
        - *   newRow.setString('name', '   Lion  ,');
        - *   newRow.setString('type', ' Mammal  ');
        - *
        - *   newRow = table.addRow();
        - *   newRow.setString('name', '  Snake  ');
        - *   newRow.setString('type', '  Reptile  ');
        - *
        - *   table.trim();
        - *   print(table.getArray());
        - * }
        - *
        - * // prints:
        - * //  0  "Lion"   "Mamal"
        - * //  1  "Snake"  "Reptile"
        - * </code></div>
        - */
        -  trim (column) {
        -    const regex = new RegExp(' ', 'g');
        +    /**
        +     *  Trims leading and trailing whitespace, such as spaces and tabs,
        +     *  from String table values. If no column is specified, then the
        +     *  values in all columns and rows are trimmed. A specific column
        +     *  may be referenced by either its ID or title.
        +     *
        +     *  @param  {String|Integer} [column] Column ID (number)
        +     *                                   or name (string)
        +     * @example
        +     * <div class="norender"><code>
        +     * function setup() {
        +     *   let table = new p5.Table();
        +     *
        +     *   table.addColumn('name');
        +     *   table.addColumn('type');
        +     *
        +     *   let newRow = table.addRow();
        +     *   newRow.setString('name', '   Lion  ,');
        +     *   newRow.setString('type', ' Mammal  ');
        +     *
        +     *   newRow = table.addRow();
        +     *   newRow.setString('name', '  Snake  ');
        +     *   newRow.setString('type', '  Reptile  ');
        +     *
        +     *   table.trim();
        +     *   print(table.getArray());
        +     * }
        +     *
        +     * // prints:
        +     * //  0  "Lion"   "Mamal"
        +     * //  1  "Snake"  "Reptile"
        +     * </code></div>
        +     */
        +    trim (column) {
        +      const regex = new RegExp(' ', 'g');
         
        -    if (typeof column === 'undefined') {
        -      for (let c = 0; c < this.columns.length; c++) {
        -        for (let d = 0; d < this.rows.length; d++) {
        -          let s = this.rows[d].arr[c];
        -          s = s.replace(regex, '');
        -          this.rows[d].arr[c] = s;
        -          this.rows[d].obj[this.columns[c]] = s;
        +      if (typeof column === 'undefined') {
        +        for (let c = 0; c < this.columns.length; c++) {
        +          for (let d = 0; d < this.rows.length; d++) {
        +            let s = this.rows[d].arr[c];
        +            s = s.replace(regex, '');
        +            this.rows[d].arr[c] = s;
        +            this.rows[d].obj[this.columns[c]] = s;
        +          }
        +        }
        +      } else if (typeof column === 'string') {
        +        for (let j = 0; j < this.rows.length; j++) {
        +          let val = this.rows[j].obj[column];
        +          val = val.replace(regex, '');
        +          this.rows[j].obj[column] = val;
        +          const pos = this.columns.indexOf(column);
        +          this.rows[j].arr[pos] = val;
        +        }
        +      } else {
        +        for (let k = 0; k < this.rows.length; k++) {
        +          let str = this.rows[k].arr[column];
        +          str = str.replace(regex, '');
        +          this.rows[k].arr[column] = str;
        +          this.rows[k].obj[this.columns[column]] = str;
                 }
        -      }
        -    } else if (typeof column === 'string') {
        -      for (let j = 0; j < this.rows.length; j++) {
        -        let val = this.rows[j].obj[column];
        -        val = val.replace(regex, '');
        -        this.rows[j].obj[column] = val;
        -        const pos = this.columns.indexOf(column);
        -        this.rows[j].arr[pos] = val;
        -      }
        -    } else {
        -      for (let k = 0; k < this.rows.length; k++) {
        -        let str = this.rows[k].arr[column];
        -        str = str.replace(regex, '');
        -        this.rows[k].arr[column] = str;
        -        this.rows[k].obj[this.columns[column]] = str;
               }
             }
        -  }
         
        -  /**
        - *  Use <a href="#/p5/removeColumn">removeColumn()</a> to remove an existing column from a Table
        - *  object. The column to be removed may be identified by either
        - *  its title (a String) or its index value (an int).
        - *  removeColumn(0) would remove the first column, removeColumn(1)
        - *  would remove the second column, and so on.
        - *
        - *  @method  removeColumn
        - *  @param  {String|Integer} column columnName (string) or ID (number)
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   table.removeColumn('id');
        - *   print(table.getColumnCount());
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  removeColumn (c) {
        -    let cString;
        -    let cNumber;
        -    if (typeof c === 'string') {
        -    // find the position of c in the columns
        -      cString = c;
        -      cNumber = this.columns.indexOf(c);
        -    } else {
        -      cNumber = c;
        -      cString = this.columns[c];
        -    }
        +    /**
        +     *  Use <a href="#/p5/removeColumn">removeColumn()</a> to remove an existing column from a Table
        +     *  object. The column to be removed may be identified by either
        +     *  its title (a String) or its index value (an int).
        +     *  removeColumn(0) would remove the first column, removeColumn(1)
        +     *  would remove the second column, and so on.
        +     *
        +     *  @param  {String|Integer} column columnName (string) or ID (number)
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   table.removeColumn('id');
        +     *   print(table.getColumnCount());
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    removeColumn (c) {
        +      let cString;
        +      let cNumber;
        +      if (typeof c === 'string') {
        +      // find the position of c in the columns
        +        cString = c;
        +        cNumber = this.columns.indexOf(c);
        +      } else {
        +        cNumber = c;
        +        cString = this.columns[c];
        +      }
         
        -    const chunk = this.columns.splice(cNumber + 1, this.columns.length);
        -    this.columns.pop();
        -    this.columns = this.columns.concat(chunk);
        +      const chunk = this.columns.splice(cNumber + 1, this.columns.length);
        +      this.columns.pop();
        +      this.columns = this.columns.concat(chunk);
         
        -    for (let i = 0; i < this.rows.length; i++) {
        -      const tempR = this.rows[i].arr;
        -      const chip = tempR.splice(cNumber + 1, tempR.length);
        -      tempR.pop();
        -      this.rows[i].arr = tempR.concat(chip);
        -      delete this.rows[i].obj[cString];
        +      for (let i = 0; i < this.rows.length; i++) {
        +        const tempR = this.rows[i].arr;
        +        const chip = tempR.splice(cNumber + 1, tempR.length);
        +        tempR.pop();
        +        this.rows[i].arr = tempR.concat(chip);
        +        delete this.rows[i].obj[cString];
        +      }
             }
        -  }
         
        -  /**
        - * Stores a value in the Table's specified row and column.
        - * The row is specified by its ID, while the column may be specified
        - * by either its ID or title.
        - *
        - * @method  set
        - * @param {Integer} row row ID
        - * @param {String|Integer} column column ID (Number)
        - *                               or title (String)
        - * @param {String|Number} value  value to assign
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   table.set(0, 'species', 'Canis Lupus');
        - *   table.set(0, 'name', 'Wolf');
        - *
        - *   //print the results
        - *   for (let r = 0; r < table.getRowCount(); r++)
        - *     for (let c = 0; c < table.getColumnCount(); c++)
        - *       print(table.getString(r, c));
        - *
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  set (row, column, value) {
        -    this.rows[row].set(column, value);
        -  }
        -
        -  /**
        - * Stores a Float value in the Table's specified row and column.
        - * The row is specified by its ID, while the column may be specified
        - * by either its ID or title.
        - *
        - * @method setNum
        - * @param {Integer} row row ID
        - * @param {String|Integer} column column ID (Number)
        - *                               or title (String)
        - * @param {Number} value  value to assign
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   table.setNum(1, 'id', 1);
        - *
        - *   print(table.getColumn(0));
        - *   //["0", 1, "2"]
        - *
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  setNum (row, column, value) {
        -    this.rows[row].setNum(column, value);
        -  }
        +    /**
        +     * Stores a value in the Table's specified row and column.
        +     * The row is specified by its ID, while the column may be specified
        +     * by either its ID or title.
        +     *
        +     * @param {Integer} row row ID
        +     * @param {String|Integer} column column ID (Number)
        +     *                               or title (String)
        +     * @param {String|Number} value  value to assign
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   table.set(0, 'species', 'Canis Lupus');
        +     *   table.set(0, 'name', 'Wolf');
        +     *
        +     *   //print the results
        +     *   for (let r = 0; r < table.getRowCount(); r++)
        +     *     for (let c = 0; c < table.getColumnCount(); c++)
        +     *       print(table.getString(r, c));
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    set (row, column, value) {
        +      this.rows[row].set(column, value);
        +    }
         
        -  /**
        - * Stores a String value in the Table's specified row and column.
        - * The row is specified by its ID, while the column may be specified
        - * by either its ID or title.
        - *
        - * @method  setString
        - * @param {Integer} row row ID
        - * @param {String|Integer} column column ID (Number)
        - *                               or title (String)
        - * @param {String} value  value to assign
        - * @example
        - * <div class="norender"><code>
        - * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   //add a row
        - *   let newRow = table.addRow();
        - *   newRow.setString('id', table.getRowCount() - 1);
        - *   newRow.setString('species', 'Canis Lupus');
        - *   newRow.setString('name', 'Wolf');
        - *
        - *   print(table.getArray());
        - *
        - *   describe('no image displayed');
        - * }
        - * </code></div>
        - */
        -  setString (row, column, value) {
        -    this.rows[row].setString(column, value);
        -  }
        +    /**
        +     * Stores a Float value in the Table's specified row and column.
        +     * The row is specified by its ID, while the column may be specified
        +     * by either its ID or title.
        +     *
        +     * @param {Integer} row row ID
        +     * @param {String|Integer} column column ID (Number)
        +     *                               or title (String)
        +     * @param {Number} value  value to assign
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   table.setNum(1, 'id', 1);
        +     *
        +     *   print(table.getColumn(0));
        +     *   //["0", 1, "2"]
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    setNum (row, column, value) {
        +      this.rows[row].setNum(column, value);
        +    }
         
        -  /**
        - * Retrieves a value from the Table's specified row and column.
        - * The row is specified by its ID, while the column may be specified by
        - * either its ID or title.
        - *
        - * @method  get
        - * @param {Integer} row row ID
        - * @param  {String|Integer} column columnName (string) or
        - *                                   ID (number)
        - * @return {String|Number}
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   print(table.get(0, 1));
        - *   //Capra hircus
        - *   print(table.get(0, 'species'));
        - *   //Capra hircus
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  get (row, column) {
        -    return this.rows[row].get(column);
        -  }
        +    /**
        +     * Stores a String value in the Table's specified row and column.
        +     * The row is specified by its ID, while the column may be specified
        +     * by either its ID or title.
        +     *
        +     * @param {Integer} row row ID
        +     * @param {String|Integer} column column ID (Number)
        +     *                               or title (String)
        +     * @param {String} value  value to assign
        +     * @example
        +     * <div class="norender"><code>
        +     * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   //add a row
        +     *   let newRow = table.addRow();
        +     *   newRow.setString('id', table.getRowCount() - 1);
        +     *   newRow.setString('species', 'Canis Lupus');
        +     *   newRow.setString('name', 'Wolf');
        +     *
        +     *   print(table.getArray());
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code></div>
        +     */
        +    setString (row, column, value) {
        +      this.rows[row].setString(column, value);
        +    }
         
        -  /**
        - * Retrieves a Float value from the Table's specified row and column.
        - * The row is specified by its ID, while the column may be specified by
        - * either its ID or title.
        - *
        - * @method  getNum
        - * @param {Integer} row row ID
        - * @param  {String|Integer} column columnName (string) or
        - *                                   ID (number)
        - * @return {Number}
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   print(table.getNum(1, 0) + 100);
        - *   //id 1 + 100 = 101
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  getNum (row, column) {
        -    return this.rows[row].getNum(column);
        -  }
        +    /**
        +     * Retrieves a value from the Table's specified row and column.
        +     * The row is specified by its ID, while the column may be specified by
        +     * either its ID or title.
        +     *
        +     * @param {Integer} row row ID
        +     * @param  {String|Integer} column columnName (string) or
        +     *                                   ID (number)
        +     * @return {String|Number}
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   print(table.get(0, 1));
        +     *   //Capra hircus
        +     *   print(table.get(0, 'species'));
        +     *   //Capra hircus
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    get (row, column) {
        +      return this.rows[row].get(column);
        +    }
         
        -  /**
        - * Retrieves a String value from the Table's specified row and column.
        - * The row is specified by its ID, while the column may be specified by
        - * either its ID or title.
        - *
        - * @method  getString
        - * @param {Integer} row row ID
        - * @param  {String|Integer} column columnName (string) or
        - *                                   ID (number)
        - * @return {String}
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   // table is comma separated value "CSV"
        - *   // and has specifiying header for column labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   print(table.getString(0, 0)); // 0
        - *   print(table.getString(0, 1)); // Capra hircus
        - *   print(table.getString(0, 2)); // Goat
        - *   print(table.getString(1, 0)); // 1
        - *   print(table.getString(1, 1)); // Panthera pardus
        - *   print(table.getString(1, 2)); // Leopard
        - *   print(table.getString(2, 0)); // 2
        - *   print(table.getString(2, 1)); // Equus zebra
        - *   print(table.getString(2, 2)); // Zebra
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        +    /**
        +     * Retrieves a Float value from the Table's specified row and column.
        +     * The row is specified by its ID, while the column may be specified by
        +     * either its ID or title.
        +     *
        +     * @param {Integer} row row ID
        +     * @param  {String|Integer} column columnName (string) or
        +     *                                   ID (number)
        +     * @return {Number}
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   print(table.getNum(1, 0) + 100);
        +     *   //id 1 + 100 = 101
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    getNum (row, column) {
        +      return this.rows[row].getNum(column);
        +    }
         
        -  getString (row, column) {
        -    return this.rows[row].getString(column);
        -  }
        +    /**
        +     * Retrieves a String value from the Table's specified row and column.
        +     * The row is specified by its ID, while the column may be specified by
        +     * either its ID or title.
        +     *
        +     * @param {Integer} row row ID
        +     * @param  {String|Integer} column columnName (string) or
        +     *                                   ID (number)
        +     * @return {String}
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   // table is comma separated value "CSV"
        +     *   // and has specifiying header for column labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   print(table.getString(0, 0)); // 0
        +     *   print(table.getString(0, 1)); // Capra hircus
        +     *   print(table.getString(0, 2)); // Goat
        +     *   print(table.getString(1, 0)); // 1
        +     *   print(table.getString(1, 1)); // Panthera pardus
        +     *   print(table.getString(1, 2)); // Leopard
        +     *   print(table.getString(2, 0)); // 2
        +     *   print(table.getString(2, 1)); // Equus zebra
        +     *   print(table.getString(2, 2)); // Zebra
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    getString (row, column) {
        +      return this.rows[row].getString(column);
        +    }
         
        -  /**
        - * Retrieves all table data and returns as an object. If a column name is
        - * passed in, each row object will be stored with that attribute as its
        - * title.
        - *
        - * @method  getObject
        - * @param {String} [headerColumn] Name of the column which should be used to
        - *                              title each row object (optional)
        - * @return {Object}
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   let tableObject = table.getObject();
        - *
        - *   print(tableObject);
        - *   //outputs an object
        - *
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  getObject (headerColumn) {
        -    const tableObject = {};
        -    let obj, cPos, index;
        +    /**
        +     * Retrieves all table data and returns as an object. If a column name is
        +     * passed in, each row object will be stored with that attribute as its
        +     * title.
        +     *
        +     * @param {String} [headerColumn] Name of the column which should be used to
        +     *                              title each row object (optional)
        +     * @return {Object}
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   let tableObject = table.getObject();
        +     *
        +     *   print(tableObject);
        +     *   //outputs an object
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    getObject (headerColumn) {
        +      const tableObject = {};
        +      let obj, cPos, index;
         
        -    for (let i = 0; i < this.rows.length; i++) {
        -      obj = this.rows[i].obj;
        +      for (let i = 0; i < this.rows.length; i++) {
        +        obj = this.rows[i].obj;
         
        -      if (typeof headerColumn === 'string') {
        -        cPos = this.columns.indexOf(headerColumn); // index of columnID
        -        if (cPos >= 0) {
        -          index = obj[headerColumn];
        -          tableObject[index] = obj;
        +        if (typeof headerColumn === 'string') {
        +          cPos = this.columns.indexOf(headerColumn); // index of columnID
        +          if (cPos >= 0) {
        +            index = obj[headerColumn];
        +            tableObject[index] = obj;
        +          } else {
        +            throw new Error(`This table has no column named "${headerColumn}"`);
        +          }
                 } else {
        -          throw new Error(`This table has no column named "${headerColumn}"`);
        +          tableObject[i] = this.rows[i].obj;
                 }
        -      } else {
        -        tableObject[i] = this.rows[i].obj;
               }
        +      return tableObject;
             }
        -    return tableObject;
        -  }
         
        -  /**
        - * Retrieves all table data and returns it as a multidimensional array.
        - *
        - * @method  getArray
        - * @return {Array}
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * // Given the CSV file "mammals.csv"
        - * // in the project's "assets" folder
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leoperd
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   // table is comma separated value "CSV"
        - *   // and has specifiying header for column labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   let tableArray = table.getArray();
        - *   for (let i = 0; i < tableArray.length; i++) {
        - *     print(tableArray[i]);
        - *   }
        - *   describe('no image displayed');
        - * }
        - * </code>
        - * </div>
        - */
        -  getArray () {
        -    const tableArray = [];
        -    for (let i = 0; i < this.rows.length; i++) {
        -      tableArray.push(this.rows[i].arr);
        +    /**
        +     * Retrieves all table data and returns it as a multidimensional array.
        +     *
        +     * @return {Array}
        +     *
        +     * @example
        +     * <div class="norender">
        +     * <code>
        +     * // Given the CSV file "mammals.csv"
        +     * // in the project's "assets" folder
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leoperd
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   // table is comma separated value "CSV"
        +     *   // and has specifiying header for column labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   let tableArray = table.getArray();
        +     *   for (let i = 0; i < tableArray.length; i++) {
        +     *     print(tableArray[i]);
        +     *   }
        +     *   describe('no image displayed');
        +     * }
        +     * </code>
        +     * </div>
        +     */
        +    getArray () {
        +      const tableArray = [];
        +      for (let i = 0; i < this.rows.length; i++) {
        +        tableArray.push(this.rows[i].arr);
        +      }
        +      return tableArray;
             }
        -    return tableArray;
        -  }
        -};
        -export default p5;
        +  };
        +
        +  /**
        +   * An array containing the names of the columns in the table, if the "header" the table is
        +   * loaded with the "header" parameter.
        +   * @type {String[]}
        +   * @property columns
        +   * @for p5.Table
        +   * @name columns
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * // Given the CSV file "mammals.csv"
        +   * // in the project's "assets" folder:
        +   * //
        +   * // id,species,name
        +   * // 0,Capra hircus,Goat
        +   * // 1,Panthera pardus,Leopard
        +   * // 2,Equus zebra,Zebra
        +   *
        +   * let table;
        +   *
        +   * function preload() {
        +   *   //my table is comma separated value "csv"
        +   *   //and has a header specifying the columns labels
        +   *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +   * }
        +   *
        +   * function setup() {
        +   *   //print the column names
        +   *   for (let c = 0; c < table.getColumnCount(); c++) {
        +   *     print('column ' + c + ' is named ' + table.columns[c]);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * An array containing the <a href="#/p5.Table">p5.TableRow</a> objects that make up the
        +   * rows of the table. The same result as calling <a href="#/p5/getRows">getRows()</a>
        +   * @type {p5.TableRow[]}
        +   * @property rows
        +   * @for p5.Table
        +   * @name rows
        +  */
        +}
        +
        +export default table;
        +
        +if(typeof p5 !== 'undefined'){
        +  table(p5, p5.prototype);
        +}
        diff --git a/src/io/p5.TableRow.js b/src/io/p5.TableRow.js
        index 652c8521d6..f1590e0dcf 100644
        --- a/src/io/p5.TableRow.js
        +++ b/src/io/p5.TableRow.js
        @@ -4,332 +4,331 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        +function tableRow(p5, fn){
        +  /**
        +   *  A TableRow object represents a single row of data values,
        +   *  stored in columns, from a table.
        +   *
        +   *  A Table Row contains both an ordered array, and an unordered
        +   *  JSON object.
        +   *
        +   *  @class p5.TableRow
        +   *  @constructor
        +   *  @param {any[]} row         optional: populate the row with an
        +   *                              array of values
        +   */
        +  p5.TableRow = class {
        +    constructor(row=[]){
        +      let arr = row;
         
        -/**
        - *  A TableRow object represents a single row of data values,
        - *  stored in columns, from a table.
        - *
        - *  A Table Row contains both an ordered array, and an unordered
        - *  JSON object.
        - *
        - *  @class p5.TableRow
        - *  @constructor
        - *  @param {String} [str]       optional: populate the row with a
        - *                              string of values, separated by the
        - *                              separator
        - *  @param {String} [separator] comma separated values (csv) by default
        - */
        -p5.TableRow = class {
        -  constructor(str, separator){
        -    let arr = [];
        -    if (str) {
        -      separator = separator || ',';
        -      arr = str.split(separator);
        +      this.arr = arr;
        +      this.obj = Object.fromEntries(arr.entries());
        +      this.table = null;
             }
         
        -    this.arr = arr;
        -    this.obj = Object.fromEntries(arr.entries());
        -    this.table = null;
        -  }
        -
        -  /**
        - *  Stores a value in the TableRow's specified column.
        - *  The column may be specified by either its ID or title.
        - *
        - *  @method  set
        - *  @param {String|Integer} column Column ID (Number)
        - *                                or Title (String)
        - *  @param {String|Number} value  The value to be stored
        - *
        - * @example
        - * <div class="norender"><code>
        - * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   let rows = table.getRows();
        - *   for (let r = 0; r < rows.length; r++) {
        - *     rows[r].set('name', 'Unicorn');
        - *   }
        - *
        - *   //print the results
        - *   print(table.getArray());
        - *
        - *   describe('no image displayed');
        - * }
        - * </code></div>
        - */
        -  set(column, value) {
        -  // if typeof column is string, use .obj
        -    if (typeof column === 'string') {
        -      const cPos = this.table.columns.indexOf(column); // index of columnID
        -      if (cPos >= 0) {
        -        this.obj[column] = value;
        -        this.arr[cPos] = value;
        -      } else {
        -        throw new Error(`This table has no column named "${column}"`);
        -      }
        -    } else {
        -    // if typeof column is number, use .arr
        -      if (column < this.table.columns.length) {
        -        this.arr[column] = value;
        -        const cTitle = this.table.columns[column];
        -        this.obj[cTitle] = value;
        +    /**
        +     *  Stores a value in the TableRow's specified column.
        +     *  The column may be specified by either its ID or title.
        +     *
        +     *  @method  set
        +     *  @param {String|Integer} column Column ID (Number)
        +     *                                or Title (String)
        +     *  @param {String|Number} value  The value to be stored
        +     *
        +     * @example
        +     * <div class="norender"><code>
        +     * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   let rows = table.getRows();
        +     *   for (let r = 0; r < rows.length; r++) {
        +     *     rows[r].set('name', 'Unicorn');
        +     *   }
        +     *
        +     *   //print the results
        +     *   print(table.getArray());
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code></div>
        +     */
        +    set(column, value) {
        +    // if typeof column is string, use .obj
        +      if (typeof column === 'string') {
        +        const cPos = this.table.columns.indexOf(column); // index of columnID
        +        if (cPos >= 0) {
        +          this.obj[column] = value;
        +          this.arr[cPos] = value;
        +        } else {
        +          throw new Error(`This table has no column named "${column}"`);
        +        }
               } else {
        -        throw new Error(`Column #${column} is out of the range of this table`);
        +      // if typeof column is number, use .arr
        +        if (column < this.table.columns.length) {
        +          this.arr[column] = value;
        +          const cTitle = this.table.columns[column];
        +          this.obj[cTitle] = value;
        +        } else {
        +          throw new Error(`Column #${column} is out of the range of this table`);
        +        }
               }
             }
        -  }
         
        -  /**
        - *  Stores a Float value in the TableRow's specified column.
        - *  The column may be specified by either its ID or title.
        - *
        - *  @method  setNum
        - *  @param {String|Integer} column Column ID (Number)
        - *                                or Title (String)
        - *  @param {Number|String} value  The value to be stored
        - *                                as a Float
        - * @example
        - * <div class="norender"><code>
        - * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   let rows = table.getRows();
        - *   for (let r = 0; r < rows.length; r++) {
        - *     rows[r].setNum('id', r + 10);
        - *   }
        - *
        - *   print(table.getArray());
        - *
        - *   describe('no image displayed');
        - * }
        - * </code></div>
        - */
        -  setNum(column, value) {
        -    const floatVal = parseFloat(value);
        -    this.set(column, floatVal);
        -  }
        -
        -  /**
        - *  Stores a String value in the TableRow's specified column.
        - *  The column may be specified by either its ID or title.
        - *
        - *  @method  setString
        - *  @param {String|Integer} column Column ID (Number)
        - *                                or Title (String)
        - *  @param {String|Number|Boolean|Object} value  The value to be stored
        - *                                as a String
        - * @example
        - * <div class="norender"><code>
        - * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   let rows = table.getRows();
        - *   for (let r = 0; r < rows.length; r++) {
        - *     let name = rows[r].getString('name');
        - *     rows[r].setString('name', 'A ' + name + ' named George');
        - *   }
        - *
        - *   print(table.getArray());
        - *
        - *   describe('no image displayed');
        - * }
        - * </code></div>
        - */
        -  setString(column, value) {
        -    const stringVal = value.toString();
        -    this.set(column, stringVal);
        -  }
        +    /**
        +     *  Stores a Float value in the TableRow's specified column.
        +     *  The column may be specified by either its ID or title.
        +     *
        +     *  @method  setNum
        +     *  @param {String|Integer} column Column ID (Number)
        +     *                                or Title (String)
        +     *  @param {Number|String} value  The value to be stored
        +     *                                as a Float
        +     * @example
        +     * <div class="norender"><code>
        +     * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   let rows = table.getRows();
        +     *   for (let r = 0; r < rows.length; r++) {
        +     *     rows[r].setNum('id', r + 10);
        +     *   }
        +     *
        +     *   print(table.getArray());
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code></div>
        +     */
        +    setNum(column, value) {
        +      const floatVal = parseFloat(value);
        +      this.set(column, floatVal);
        +    }
         
        -  /**
        - *  Retrieves a value from the TableRow's specified column.
        - *  The column may be specified by either its ID or title.
        - *
        - *  @method  get
        - *  @param  {String|Integer} column columnName (string) or
        - *                                   ID (number)
        - *  @return {String|Number}
        - *
        - * @example
        - * <div class="norender"><code>
        - * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   let names = [];
        - *   let rows = table.getRows();
        - *   for (let r = 0; r < rows.length; r++) {
        - *     names.push(rows[r].get('name'));
        - *   }
        - *
        - *   print(names);
        - *
        - *   describe('no image displayed');
        - * }
        - * </code></div>
        - */
        -  get(column) {
        -    if (typeof column === 'string') {
        -      return this.obj[column];
        -    } else {
        -      return this.arr[column];
        +    /**
        +     *  Stores a String value in the TableRow's specified column.
        +     *  The column may be specified by either its ID or title.
        +     *
        +     *  @method  setString
        +     *  @param {String|Integer} column Column ID (Number)
        +     *                                or Title (String)
        +     *  @param {String|Number|Boolean|Object} value  The value to be stored
        +     *                                as a String
        +     * @example
        +     * <div class="norender"><code>
        +     * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   let rows = table.getRows();
        +     *   for (let r = 0; r < rows.length; r++) {
        +     *     let name = rows[r].getString('name');
        +     *     rows[r].setString('name', 'A ' + name + ' named George');
        +     *   }
        +     *
        +     *   print(table.getArray());
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code></div>
        +     */
        +    setString(column, value) {
        +      const stringVal = value.toString();
        +      this.set(column, stringVal);
             }
        -  }
         
        -  /**
        - *  Retrieves a Float value from the TableRow's specified
        - *  column. The column may be specified by either its ID or
        - *  title.
        - *
        - *  @method  getNum
        - *  @param  {String|Integer} column columnName (string) or
        - *                                   ID (number)
        - *  @return {Number}  Float Floating point number
        - * @example
        - * <div class="norender"><code>
        - * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   let rows = table.getRows();
        - *   let minId = Infinity;
        - *   let maxId = -Infinity;
        - *   for (let r = 0; r < rows.length; r++) {
        - *     let id = rows[r].getNum('id');
        - *     minId = min(minId, id);
        - *     maxId = min(maxId, id);
        - *   }
        - *   print('minimum id = ' + minId + ', maximum id = ' + maxId);
        - *   describe('no image displayed');
        - * }
        - * </code></div>
        - */
        -  getNum(column) {
        -    let ret;
        -    if (typeof column === 'string') {
        -      ret = parseFloat(this.obj[column]);
        -    } else {
        -      ret = parseFloat(this.arr[column]);
        +    /**
        +     *  Retrieves a value from the TableRow's specified column.
        +     *  The column may be specified by either its ID or title.
        +     *
        +     *  @method  get
        +     *  @param  {String|Integer} column columnName (string) or
        +     *                                   ID (number)
        +     *  @return {String|Number}
        +     *
        +     * @example
        +     * <div class="norender"><code>
        +     * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   let names = [];
        +     *   let rows = table.getRows();
        +     *   for (let r = 0; r < rows.length; r++) {
        +     *     names.push(rows[r].get('name'));
        +     *   }
        +     *
        +     *   print(names);
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code></div>
        +     */
        +    get(column) {
        +      if (typeof column === 'string') {
        +        return this.obj[column];
        +      } else {
        +        return this.arr[column];
        +      }
             }
         
        -    if (ret.toString() === 'NaN') {
        -      throw `Error: ${this.obj[column]} is NaN (Not a Number)`;
        +    /**
        +     *  Retrieves a Float value from the TableRow's specified
        +     *  column. The column may be specified by either its ID or
        +     *  title.
        +     *
        +     *  @method  getNum
        +     *  @param  {String|Integer} column columnName (string) or
        +     *                                   ID (number)
        +     *  @return {Number}  Float Floating point number
        +     * @example
        +     * <div class="norender"><code>
        +     * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   let rows = table.getRows();
        +     *   let minId = Infinity;
        +     *   let maxId = -Infinity;
        +     *   for (let r = 0; r < rows.length; r++) {
        +     *     let id = rows[r].getNum('id');
        +     *     minId = min(minId, id);
        +     *     maxId = min(maxId, id);
        +     *   }
        +     *   print('minimum id = ' + minId + ', maximum id = ' + maxId);
        +     *   describe('no image displayed');
        +     * }
        +     * </code></div>
        +     */
        +    getNum(column) {
        +      let ret;
        +      if (typeof column === 'string') {
        +        ret = parseFloat(this.obj[column]);
        +      } else {
        +        ret = parseFloat(this.arr[column]);
        +      }
        +
        +      if (ret.toString() === 'NaN') {
        +        throw `Error: ${this.obj[column]} is NaN (Not a Number)`;
        +      }
        +      return ret;
             }
        -    return ret;
        -  }
         
        -  /**
        - *  Retrieves an String value from the TableRow's specified
        - *  column. The column may be specified by either its ID or
        - *  title.
        - *
        - *  @method  getString
        - *  @param  {String|Integer} column columnName (string) or
        - *                                   ID (number)
        - *  @return {String}  String
        - * @example
        - * <div class="norender"><code>
        - * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        - * //
        - * // id,species,name
        - * // 0,Capra hircus,Goat
        - * // 1,Panthera pardus,Leopard
        - * // 2,Equus zebra,Zebra
        - *
        - * let table;
        - *
        - * function preload() {
        - *   //my table is comma separated value "csv"
        - *   //and has a header specifying the columns labels
        - *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        - * }
        - *
        - * function setup() {
        - *   let rows = table.getRows();
        - *   let longest = '';
        - *   for (let r = 0; r < rows.length; r++) {
        - *     let species = rows[r].getString('species');
        - *     if (longest.length < species.length) {
        - *       longest = species;
        - *     }
        - *   }
        - *
        - *   print('longest: ' + longest);
        - *
        - *   describe('no image displayed');
        - * }
        - * </code></div>
        - */
        -  getString(column) {
        -    if (typeof column === 'string') {
        -      return this.obj[column].toString();
        -    } else {
        -      return this.arr[column].toString();
        +    /**
        +     *  Retrieves an String value from the TableRow's specified
        +     *  column. The column may be specified by either its ID or
        +     *  title.
        +     *
        +     *  @method  getString
        +     *  @param  {String|Integer} column columnName (string) or
        +     *                                   ID (number)
        +     *  @return {String}  String
        +     * @example
        +     * <div class="norender"><code>
        +     * // Given the CSV file "mammals.csv" in the project's "assets" folder:
        +     * //
        +     * // id,species,name
        +     * // 0,Capra hircus,Goat
        +     * // 1,Panthera pardus,Leopard
        +     * // 2,Equus zebra,Zebra
        +     *
        +     * let table;
        +     *
        +     * function preload() {
        +     *   //my table is comma separated value "csv"
        +     *   //and has a header specifying the columns labels
        +     *   table = loadTable('assets/mammals.csv', 'csv', 'header');
        +     * }
        +     *
        +     * function setup() {
        +     *   let rows = table.getRows();
        +     *   let longest = '';
        +     *   for (let r = 0; r < rows.length; r++) {
        +     *     let species = rows[r].getString('species');
        +     *     if (longest.length < species.length) {
        +     *       longest = species;
        +     *     }
        +     *   }
        +     *
        +     *   print('longest: ' + longest);
        +     *
        +     *   describe('no image displayed');
        +     * }
        +     * </code></div>
        +     */
        +    getString(column) {
        +      if (typeof column === 'string') {
        +        return this.obj[column].toString();
        +      } else {
        +        return this.arr[column].toString();
        +      }
             }
        -  }
        -};
        -export default p5;
        +  };
        +}
        +
        +export default tableRow;
        +
        +if(typeof p5 !== 'undefined'){
        +  tableRow(p5, p5.prototype);
        +}
        diff --git a/src/io/p5.XML.js b/src/io/p5.XML.js
        index d5450938cc..fc2907c8b7 100644
        --- a/src/io/p5.XML.js
        +++ b/src/io/p5.XML.js
        @@ -4,67 +4,7 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        -
        -/**
        - * A class to describe an XML object.
        - *
        - * Each `p5.XML` object provides an easy way to interact with XML data.
        - * Extensible Markup Language
        - * (<a href="https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction" target="_blank">XML</a>)
        - * is a standard format for sending data between applications. Like HTML, the
        - * XML format is based on tags and attributes, as in
        - * `&lt;time units="s"&gt;1234&lt;/time&gt;`.
        - *
        - * Note: Use <a href="#/p5/loadXML">loadXML()</a> to load external XML files.
        - *
        - * @class p5.XML
        - * @constructor
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get an array with all mammal tags.
        - *   let mammals = myXML.getChildren('mammal');
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Iterate over the mammals array.
        - *   for (let i = 0; i < mammals.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 25;
        - *
        - *     // Get the mammal's common name.
        - *     let name = mammals[i].getContent();
        - *
        - *     // Display the mammal's name.
        - *     text(name, 20, y);
        - *   }
        - *
        - *   describe(
        - *     'The words "Goat", "Leopard", and "Zebra" written on three separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.XML = class  {
        +class XML {
           constructor(DOM){
             if (!DOM) {
               const xmlDoc = document.implementation.createDocument(null, 'doc');
        @@ -75,158 +15,155 @@ p5.XML = class  {
           }
         
           /**
        - * Returns the element's parent element as a new <a href="#/p5.XML">p5.XML</a>
        - * object.
        - *
        - * @method getParent
        - * @return {p5.XML} parent element.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get an array with all mammal elements.
        - *   let mammals = myXML.getChildren('mammal');
        - *
        - *   // Get the first mammal element.
        - *   let firstMammal = mammals[0];
        - *
        - *   // Get the parent element.
        - *   let parent = firstMammal.getParent();
        - *
        - *   // Get the parent element's name.
        - *   let name = parent.getName();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the parent element's name.
        - *   text(name, 50, 50);
        - *
        - *   describe('The word "animals" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        +   * Returns the element's parent element as a new <a href="#/p5.XML">p5.XML</a>
        +   * object.
        +   *
        +   * @return {p5.XML} parent element.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get an array with all mammal elements.
        +   *   let mammals = myXML.getChildren('mammal');
        +   *
        +   *   // Get the first mammal element.
        +   *   let firstMammal = mammals[0];
        +   *
        +   *   // Get the parent element.
        +   *   let parent = firstMammal.getParent();
        +   *
        +   *   // Get the parent element's name.
        +   *   let name = parent.getName();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the parent element's name.
        +   *   text(name, 50, 50);
        +   *
        +   *   describe('The word "animals" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
           getParent() {
        -    return new p5.XML(this.DOM.parentElement);
        +    return new XML(this.DOM.parentElement);
           }
         
           /**
        - * Returns the element's name as a `String`.
        - *
        - * An XML element's name is given by its tag. For example, the element
        - * `&lt;language&gt;JavaScript&lt;/language&gt;` has the name `language`.
        - *
        - * @method getName
        - * @return {String} name of the element.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get an array with all mammal elements.
        - *   let mammals = myXML.getChildren('mammal');
        - *
        - *   // Get the first mammal element.
        - *   let firstMammal = mammals[0];
        - *
        - *   // Get the mammal element's name.
        - *   let name = firstMammal.getName();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the element's name.
        - *   text(name, 50, 50);
        - *
        - *   describe('The word "mammal" written in black on a gray background.');
        - * }
        - * </code>
        -</div>
        - */
        +   * Returns the element's name as a `String`.
        +   *
        +   * An XML element's name is given by its tag. For example, the element
        +   * `&lt;language&gt;JavaScript&lt;/language&gt;` has the name `language`.
        +   *
        +   * @return {String} name of the element.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get an array with all mammal elements.
        +   *   let mammals = myXML.getChildren('mammal');
        +   *
        +   *   // Get the first mammal element.
        +   *   let firstMammal = mammals[0];
        +   *
        +   *   // Get the mammal element's name.
        +   *   let name = firstMammal.getName();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the element's name.
        +   *   text(name, 50, 50);
        +   *
        +   *   describe('The word "mammal" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
           getName() {
             return this.DOM.tagName;
           }
         
           /**
        - * Sets the element's tag name.
        - *
        - * An XML element's name is given by its tag. For example, the element
        - * `&lt;language&gt;JavaScript&lt;/language&gt;` has the name `language`.
        - *
        - * The parameter, `name`, is the element's new name as a string. For example,
        - * calling `myXML.setName('planet')` will make the element's new tag name
        - * `&lt;planet&gt;&lt;/planet&gt;`.
        - *
        - * @method setName
        - * @param {String} name new tag name of the element.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the element's original name.
        - *   let oldName = myXML.getName();
        - *
        - *   // Set the element's name.
        - *   myXML.setName('monsters');
        - *
        - *   // Get the element's new name.
        - *   let newName = myXML.getName();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the element's names.
        - *   text(oldName, 50, 33);
        - *   text(newName, 50, 67);
        - *
        - *   describe(
        - *     'The words "animals" and "monsters" written on separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code></div>
        - */
        +   * Sets the element's tag name.
        +   *
        +   * An XML element's name is given by its tag. For example, the element
        +   * `&lt;language&gt;JavaScript&lt;/language&gt;` has the name `language`.
        +   *
        +   * The parameter, `name`, is the element's new name as a string. For example,
        +   * calling `myXML.setName('planet')` will make the element's new tag name
        +   * `&lt;planet&gt;&lt;/planet&gt;`.
        +   *
        +   * @param {String} name new tag name of the element.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the element's original name.
        +   *   let oldName = myXML.getName();
        +   *
        +   *   // Set the element's name.
        +   *   myXML.setName('monsters');
        +   *
        +   *   // Get the element's new name.
        +   *   let newName = myXML.getName();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the element's names.
        +   *   text(oldName, 50, 33);
        +   *   text(newName, 50, 67);
        +   *
        +   *   describe(
        +   *     'The words "animals" and "monsters" written on separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code></div>
        +   */
           setName(name) {
             const content = this.DOM.innerHTML;
             const attributes = this.DOM.attributes;
        @@ -240,97 +177,95 @@ p5.XML = class  {
           }
         
           /**
        - * Returns `true` if the element has child elements and `false` if not.
        - *
        - * @method hasChildren
        - * @return {boolean} whether the element has children.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Check whether the element has child elements.
        - *   let isParent = myXML.hasChildren();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Style the text.
        - *   if (isParent === true) {
        - *     text('Parent', 50, 50);
        - *   } else {
        - *     text('Not Parent', 50, 50);
        - *   }
        - *
        - *   describe('The word "Parent" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        +   * Returns `true` if the element has child elements and `false` if not.
        +   *
        +   * @return {boolean} whether the element has children.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Check whether the element has child elements.
        +   *   let isParent = myXML.hasChildren();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Style the text.
        +   *   if (isParent === true) {
        +   *     text('Parent', 50, 50);
        +   *   } else {
        +   *     text('Not Parent', 50, 50);
        +   *   }
        +   *
        +   *   describe('The word "Parent" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
           hasChildren() {
             return this.DOM.children.length > 0;
           }
         
           /**
        - * Returns an array with the names of the element's child elements as
        - * `String`s.
        - *
        - * @method listChildren
        - * @return {String[]} names of the child elements.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the names of the element's children as an array.
        - *   let children = myXML.listChildren();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Iterate over the array.
        - *   for (let i = 0; i < children.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 25;
        - *
        - *     // Display the child element's name.
        - *     text(children[i], 10, y);
        - *   }
        - *
        - *   describe(
        - *     'The words "mammal", "mammal", "mammal", and "reptile" written on separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        +   * Returns an array with the names of the element's child elements as
        +   * `String`s.
        +   *
        +   * @return {String[]} names of the child elements.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the names of the element's children as an array.
        +   *   let children = myXML.listChildren();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Iterate over the array.
        +   *   for (let i = 0; i < children.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 25;
        +   *
        +   *     // Display the child element's name.
        +   *     text(children[i], 10, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The words "mammal", "mammal", "mammal", and "reptile" written on separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
           listChildren() {
             const arr = [];
             for (let i = 0; i < this.DOM.childNodes.length; i++) {
        @@ -340,103 +275,102 @@ p5.XML = class  {
           }
         
           /**
        - * Returns an array with the element's child elements as new
        - * <a href="#/p5.XML">p5.XML</a> objects.
        - *
        - * The parameter, `name`, is optional. If a string is passed, as in
        - * `myXML.getChildren('cat')`, then the method will only return child elements
        - * with the tag `&lt;cat&gt;`.
        - *
        - * @method getChildren
        - * @param {String} [name] name of the elements to return.
        - * @return {p5.XML[]} child elements.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get an array of the child elements.
        - *   let children = myXML.getChildren();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Iterate over the array.
        - *   for (let i = 0; i < children.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 20;
        - *
        - *     // Get the child element's content.
        - *     let content = children[i].getContent();
        - *
        - *     // Display the child element's content.
        - *     text(content, 10, y);
        - *   }
        - *
        - *   describe(
        - *     'The words "Goat", "Leopard", "Zebra", and "Turtle" written on separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get an array of the child elements
        - *   // that are mammals.
        - *   let children = myXML.getChildren('mammal');
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Iterate over the array.
        - *   for (let i = 0; i < children.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 20;
        - *
        - *     // Get the child element's content.
        - *     let content = children[i].getContent();
        - *
        - *     // Display the child element's content.
        - *     text(content, 10, y);
        - *   }
        - *
        - *   describe(
        - *     'The words "Goat", "Leopard", and "Zebra" written on separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        +   * Returns an array with the element's child elements as new
        +   * <a href="#/p5.XML">p5.XML</a> objects.
        +   *
        +   * The parameter, `name`, is optional. If a string is passed, as in
        +   * `myXML.getChildren('cat')`, then the method will only return child elements
        +   * with the tag `&lt;cat&gt;`.
        +   *
        +   * @param {String} [name] name of the elements to return.
        +   * @return {p5.XML[]} child elements.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get an array of the child elements.
        +   *   let children = myXML.getChildren();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Iterate over the array.
        +   *   for (let i = 0; i < children.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 20;
        +   *
        +   *     // Get the child element's content.
        +   *     let content = children[i].getContent();
        +   *
        +   *     // Display the child element's content.
        +   *     text(content, 10, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The words "Goat", "Leopard", "Zebra", and "Turtle" written on separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get an array of the child elements
        +   *   // that are mammals.
        +   *   let children = myXML.getChildren('mammal');
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Iterate over the array.
        +   *   for (let i = 0; i < children.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 20;
        +   *
        +   *     // Get the child element's content.
        +   *     let content = children[i].getContent();
        +   *
        +   *     // Display the child element's content.
        +   *     text(content, 10, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The words "Goat", "Leopard", and "Zebra" written on separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
           getChildren(param) {
             if (param) {
               return elementsToP5XML(this.DOM.getElementsByTagName(param));
        @@ -446,154 +380,152 @@ p5.XML = class  {
           }
         
           /**
        - * Returns the first matching child element as a new
        - * <a href="#/p5.XML">p5.XML</a> object.
        - *
        - * The parameter, `name`, is optional. If a string is passed, as in
        - * `myXML.getChild('cat')`, then the first child element with the tag
        - * `&lt;cat&gt;` will be returned. If a number is passed, as in
        - * `myXML.getChild(1)`, then the child element at that index will be returned.
        - *
        - * @method getChild
        - * @param {String|Integer} name element name or index.
        - * @return {p5.XML} child element.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the first child element that is a mammal.
        - *   let goat = myXML.getChild('mammal');
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Get the child element's content.
        - *   let content = goat.getContent();
        - *
        - *   // Display the child element's content.
        - *   text(content, 50, 50);
        - *
        - *   describe('The word "Goat" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the child element at index 1.
        - *   let leopard = myXML.getChild(1);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Get the child element's content.
        - *   let content = leopard.getContent();
        - *
        - *   // Display the child element's content.
        - *   text(content, 50, 50);
        - *
        - *   describe('The word "Leopard" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        +   * Returns the first matching child element as a new
        +   * <a href="#/p5.XML">p5.XML</a> object.
        +   *
        +   * The parameter, `name`, is optional. If a string is passed, as in
        +   * `myXML.getChild('cat')`, then the first child element with the tag
        +   * `&lt;cat&gt;` will be returned. If a number is passed, as in
        +   * `myXML.getChild(1)`, then the child element at that index will be returned.
        +   *
        +   * @param {String|Integer} name element name or index.
        +   * @return {p5.XML} child element.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the first child element that is a mammal.
        +   *   let goat = myXML.getChild('mammal');
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Get the child element's content.
        +   *   let content = goat.getContent();
        +   *
        +   *   // Display the child element's content.
        +   *   text(content, 50, 50);
        +   *
        +   *   describe('The word "Goat" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the child element at index 1.
        +   *   let leopard = myXML.getChild(1);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Get the child element's content.
        +   *   let content = leopard.getContent();
        +   *
        +   *   // Display the child element's content.
        +   *   text(content, 50, 50);
        +   *
        +   *   describe('The word "Leopard" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
           getChild(param) {
             if (typeof param === 'string') {
               for (const child of this.DOM.children) {
        -        if (child.tagName === param) return new p5.XML(child);
        +        if (child.tagName === param) return new XML(child);
               }
             } else {
        -      return new p5.XML(this.DOM.children[param]);
        +      return new XML(this.DOM.children[param]);
             }
           }
         
           /**
        - * Adds a new child element and returns a reference to it.
        - *
        - * The parameter, `child`, is the <a href="#/p5.XML">p5.XML</a> object to add
        - * as a child element. For example, calling `myXML.addChild(otherXML)` inserts
        - * `otherXML` as a child element of `myXML`.
        - *
        - * @method addChild
        - * @param {p5.XML} child child element to add.
        - * @return {p5.XML} added child element.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a new p5.XML object.
        - *   let newAnimal = new p5.XML();
        - *
        - *   // Set its properties.
        - *   newAnimal.setName('hydrozoa');
        - *   newAnimal.setAttribute('id', 4);
        - *   newAnimal.setAttribute('species', 'Physalia physalis');
        - *   newAnimal.setContent('Bluebottle');
        - *
        - *   // Add the child element.
        - *   myXML.addChild(newAnimal);
        - *
        - *   // Get the first child element that is a hydrozoa.
        - *   let blueBottle = myXML.getChild('hydrozoa');
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Get the child element's content.
        - *   let content = blueBottle.getContent();
        - *
        - *   // Display the child element's content.
        - *   text(content, 50, 50);
        - *
        - *   describe('The word "Bluebottle" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        +   * Adds a new child element and returns a reference to it.
        +   *
        +   * The parameter, `child`, is the <a href="#/p5.XML">p5.XML</a> object to add
        +   * as a child element. For example, calling `myXML.addChild(otherXML)` inserts
        +   * `otherXML` as a child element of `myXML`.
        +   *
        +   * @param {p5.XML} child child element to add.
        +   * @return {p5.XML} added child element.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a new p5.XML object.
        +   *   let newAnimal = new p5.XML();
        +   *
        +   *   // Set its properties.
        +   *   newAnimal.setName('hydrozoa');
        +   *   newAnimal.setAttribute('id', 4);
        +   *   newAnimal.setAttribute('species', 'Physalia physalis');
        +   *   newAnimal.setContent('Bluebottle');
        +   *
        +   *   // Add the child element.
        +   *   myXML.addChild(newAnimal);
        +   *
        +   *   // Get the first child element that is a hydrozoa.
        +   *   let blueBottle = myXML.getChild('hydrozoa');
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Get the child element's content.
        +   *   let content = blueBottle.getContent();
        +   *
        +   *   // Display the child element's content.
        +   *   text(content, 50, 50);
        +   *
        +   *   describe('The word "Bluebottle" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
           addChild(node) {
        -    if (node instanceof p5.XML) {
        +    if (node instanceof XML) {
               this.DOM.appendChild(node.DOM);
             } else {
             // PEND
        @@ -601,108 +533,107 @@ p5.XML = class  {
           }
         
           /**
        - * Removes the first matching child element.
        - *
        - * The parameter, `name`, is the child element to remove. If a string is
        - * passed, as in `myXML.removeChild('cat')`, then the first child element
        - * with the tag `&lt;cat&gt;` will be removed. If a number is passed, as in
        - * `myXML.removeChild(1)`, then the child element at that index will be
        - * removed.
        - *
        - * @method removeChild
        - * @param {String|Integer} name name or index of the child element to remove.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Remove the first mammal element.
        - *   myXML.removeChild('mammal');
        - *
        - *   // Get an array of child elements.
        - *   let children = myXML.getChildren();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Iterate over the array.
        - *   for (let i = 0; i < children.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 25;
        - *
        - *     // Get the child element's content.
        - *     let content = children[i].getContent();
        - *
        - *     // Display the child element's content.
        - *     text(content, 10, y);
        - *   }
        - *
        - *   describe(
        - *     'The words "Leopard", "Zebra", and "Turtle" written on separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Remove the element at index 2.
        - *   myXML.removeChild(2);
        - *
        - *   // Get an array of child elements.
        - *   let children = myXML.getChildren();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Iterate over the array.
        - *   for (let i = 0; i < children.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 25;
        - *
        - *     // Get the child element's content.
        - *     let content = children[i].getContent();
        - *
        - *     // Display the child element's content.
        - *     text(content, 10, y);
        - *   }
        - *
        - *   describe(
        - *     'The words "Goat", "Leopard", and "Turtle" written on separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        +   * Removes the first matching child element.
        +   *
        +   * The parameter, `name`, is the child element to remove. If a string is
        +   * passed, as in `myXML.removeChild('cat')`, then the first child element
        +   * with the tag `&lt;cat&gt;` will be removed. If a number is passed, as in
        +   * `myXML.removeChild(1)`, then the child element at that index will be
        +   * removed.
        +   *
        +   * @param {String|Integer} name name or index of the child element to remove.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Remove the first mammal element.
        +   *   myXML.removeChild('mammal');
        +   *
        +   *   // Get an array of child elements.
        +   *   let children = myXML.getChildren();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Iterate over the array.
        +   *   for (let i = 0; i < children.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 25;
        +   *
        +   *     // Get the child element's content.
        +   *     let content = children[i].getContent();
        +   *
        +   *     // Display the child element's content.
        +   *     text(content, 10, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The words "Leopard", "Zebra", and "Turtle" written on separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Remove the element at index 2.
        +   *   myXML.removeChild(2);
        +   *
        +   *   // Get an array of child elements.
        +   *   let children = myXML.getChildren();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Iterate over the array.
        +   *   for (let i = 0; i < children.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 25;
        +   *
        +   *     // Get the child element's content.
        +   *     let content = children[i].getContent();
        +   *
        +   *     // Display the child element's content.
        +   *     text(content, 10, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The words "Goat", "Leopard", and "Turtle" written on separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
           removeChild(param) {
             let ind = -1;
             if (typeof param === 'string') {
        @@ -721,93 +652,91 @@ p5.XML = class  {
           }
         
           /**
        - * Returns the number of attributes the element has.
        - *
        - * @method getAttributeCount
        - * @return {Integer} number of attributes.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the first child element.
        - *   let first = myXML.getChild(0);
        - *
        - *   // Get the number of attributes.
        - *   let numAttributes = first.getAttributeCount();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the number of attributes.
        - *   text(numAttributes, 50, 50);
        - *
        - *   describe('The number "2" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        +   * Returns the number of attributes the element has.
        +   *
        +   * @return {Integer} number of attributes.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the first child element.
        +   *   let first = myXML.getChild(0);
        +   *
        +   *   // Get the number of attributes.
        +   *   let numAttributes = first.getAttributeCount();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the number of attributes.
        +   *   text(numAttributes, 50, 50);
        +   *
        +   *   describe('The number "2" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
           getAttributeCount() {
             return this.DOM.attributes.length;
           }
         
           /**
        - * Returns an `Array` with the names of the element's attributes.
        - *
        - * Note: Use
        - * <a href="#/p5.XML/getString">myXML.getString()</a> or
        - * <a href="#/p5.XML/getNum">myXML.getNum()</a> to return an attribute's value.
        - *
        - * @method listAttributes
        - * @return {String[]} attribute names.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the first child element.
        - *   let first = myXML.getChild(0);
        - *
        - *   // Get the number of attributes.
        - *   let attributes = first.listAttributes();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the element's attributes.
        - *   text(attributes, 50, 50);
        - *
        - *   describe('The text "id,species" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        +   * Returns an `Array` with the names of the element's attributes.
        +   *
        +   * Note: Use
        +   * <a href="#/p5.XML/getString">myXML.getString()</a> or
        +   * <a href="#/p5.XML/getNum">myXML.getNum()</a> to return an attribute's value.
        +   *
        +   * @return {String[]} attribute names.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the first child element.
        +   *   let first = myXML.getChild(0);
        +   *
        +   *   // Get the number of attributes.
        +   *   let attributes = first.listAttributes();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the element's attributes.
        +   *   text(attributes, 50, 50);
        +   *
        +   *   describe('The text "id,species" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
           listAttributes() {
             const arr = [];
         
        @@ -819,58 +748,57 @@ p5.XML = class  {
           }
         
           /**
        - * Returns `true` if the element has a given attribute and `false` if not.
        - *
        - * The parameter, `name`, is a string with the name of the attribute being
        - * checked.
        - *
        - * Note: Use
        - * <a href="#/p5.XML/getString">myXML.getString()</a> or
        - * <a href="#/p5.XML/getNum">myXML.getNum()</a> to return an attribute's value.
        - *
        - * @method hasAttribute
        - * @param {String} name name of the attribute to be checked.
        - * @return {boolean} whether the element has the attribute.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the first mammal child element.
        - *   let mammal = myXML.getChild('mammal');
        - *
        - *   // Check whether the element has an
        - *   // species attribute.
        - *   let hasSpecies = mammal.hasAttribute('species');
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display whether the element has a species attribute.
        - *   if (hasSpecies === true) {
        - *     text('Species', 50, 50);
        - *   } else {
        - *     text('No species', 50, 50);
        - *   }
        - *
        - *   describe('The text "Species" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        +   * Returns `true` if the element has a given attribute and `false` if not.
        +   *
        +   * The parameter, `name`, is a string with the name of the attribute being
        +   * checked.
        +   *
        +   * Note: Use
        +   * <a href="#/p5.XML/getString">myXML.getString()</a> or
        +   * <a href="#/p5.XML/getNum">myXML.getNum()</a> to return an attribute's value.
        +   *
        +   * @param {String} name name of the attribute to be checked.
        +   * @return {boolean} whether the element has the attribute.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the first mammal child element.
        +   *   let mammal = myXML.getChild('mammal');
        +   *
        +   *   // Check whether the element has an
        +   *   // species attribute.
        +   *   let hasSpecies = mammal.hasAttribute('species');
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display whether the element has a species attribute.
        +   *   if (hasSpecies === true) {
        +   *     text('Species', 50, 50);
        +   *   } else {
        +   *     text('No species', 50, 50);
        +   *   }
        +   *
        +   *   describe('The text "Species" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
           hasAttribute(name) {
             const obj = {};
         
        @@ -882,100 +810,99 @@ p5.XML = class  {
           }
         
           /**
        - * Return an attribute's value as a `Number`.
        - *
        - * The first parameter, `name`, is a string with the name of the attribute
        - * being checked. For example, calling `myXML.getNum('id')` returns the
        - * element's `id` attribute as a number.
        - *
        - * The second parameter, `defaultValue`, is optional. If a number is passed,
        - * as in `myXML.getNum('id', -1)`, it will be returned if the attribute
        - * doesn't exist or can't be converted to a number.
        - *
        - * Note: Use
        - * <a href="#/p5.XML/getString">myXML.getString()</a> or
        - * <a href="#/p5.XML/getNum">myXML.getNum()</a> to return an attribute's value.
        - *
        - * @method getNum
        - * @param {String} name name of the attribute to be checked.
        - * @param {Number} [defaultValue] value to return if the attribute doesn't exist.
        - * @return {Number} attribute value as a number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the first reptile child element.
        - *   let reptile = myXML.getChild('reptile');
        - *
        - *   // Get the reptile's content.
        - *   let content = reptile.getContent();
        - *
        - *   // Get the reptile's ID.
        - *   let id = reptile.getNum('id');
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the ID attribute.
        - *   text(`${content} is ${id + 1}th`, 5, 50, 90);
        - *
        - *   describe(`The text "${content} is ${id + 1}th" written in black on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the first reptile child element.
        - *   let reptile = myXML.getChild('reptile');
        - *
        - *   // Get the reptile's content.
        - *   let content = reptile.getContent();
        - *
        - *   // Get the reptile's size.
        - *   let weight = reptile.getNum('weight', 135);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the ID attribute.
        - *   text(`${content} is ${weight}kg`, 5, 50, 90);
        - *
        - *   describe(
        - *     `The text "${content} is ${weight}kg" written in black on a gray background.`
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        +   * Return an attribute's value as a `Number`.
        +   *
        +   * The first parameter, `name`, is a string with the name of the attribute
        +   * being checked. For example, calling `myXML.getNum('id')` returns the
        +   * element's `id` attribute as a number.
        +   *
        +   * The second parameter, `defaultValue`, is optional. If a number is passed,
        +   * as in `myXML.getNum('id', -1)`, it will be returned if the attribute
        +   * doesn't exist or can't be converted to a number.
        +   *
        +   * Note: Use
        +   * <a href="#/p5.XML/getString">myXML.getString()</a> or
        +   * <a href="#/p5.XML/getNum">myXML.getNum()</a> to return an attribute's value.
        +   *
        +   * @param {String} name name of the attribute to be checked.
        +   * @param {Number} [defaultValue] value to return if the attribute doesn't exist.
        +   * @return {Number} attribute value as a number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the first reptile child element.
        +   *   let reptile = myXML.getChild('reptile');
        +   *
        +   *   // Get the reptile's content.
        +   *   let content = reptile.getContent();
        +   *
        +   *   // Get the reptile's ID.
        +   *   let id = reptile.getNum('id');
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the ID attribute.
        +   *   text(`${content} is ${id + 1}th`, 5, 50, 90);
        +   *
        +   *   describe(`The text "${content} is ${id + 1}th" written in black on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the first reptile child element.
        +   *   let reptile = myXML.getChild('reptile');
        +   *
        +   *   // Get the reptile's content.
        +   *   let content = reptile.getContent();
        +   *
        +   *   // Get the reptile's size.
        +   *   let weight = reptile.getNum('weight', 135);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the ID attribute.
        +   *   text(`${content} is ${weight}kg`, 5, 50, 90);
        +   *
        +   *   describe(
        +   *     `The text "${content} is ${weight}kg" written in black on a gray background.`
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
           getNum(name, defaultValue) {
             const obj = {};
         
        @@ -987,99 +914,98 @@ p5.XML = class  {
           }
         
           /**
        - * Return an attribute's value as a string.
        - *
        - * The first parameter, `name`, is a string with the name of the attribute
        - * being checked. For example, calling `myXML.getString('color')` returns the
        - * element's `id` attribute as a string.
        - *
        - * The second parameter, `defaultValue`, is optional. If a string is passed,
        - * as in `myXML.getString('color', 'deeppink')`, it will be returned if the
        - * attribute doesn't exist.
        - *
        - * Note: Use
        - * <a href="#/p5.XML/getString">myXML.getString()</a> or
        - * <a href="#/p5.XML/getNum">myXML.getNum()</a> to return an attribute's value.
        - *
        - * @method getString
        - * @param {String} name name of the attribute to be checked.
        - * @param {Number} [defaultValue] value to return if the attribute doesn't exist.
        - * @return {String} attribute value as a string.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the first reptile child element.
        - *   let reptile = myXML.getChild('reptile');
        - *
        - *   // Get the reptile's content.
        - *   let content = reptile.getContent();
        - *
        - *   // Get the reptile's species.
        - *   let species = reptile.getString('species');
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the species attribute.
        - *   text(`${content}: ${species}`, 5, 50, 90);
        - *
        - *   describe(`The text "${content}: ${species}" written in black on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the first reptile child element.
        - *   let reptile = myXML.getChild('reptile');
        - *
        - *   // Get the reptile's content.
        - *   let content = reptile.getContent();
        - *
        - *   // Get the reptile's color.
        - *   let attribute = reptile.getString('color', 'green');
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *   fill(attribute);
        - *
        - *   // Display the element's content.
        - *   text(content, 50, 50);
        - *
        - *   describe(`The text "${content}" written in green on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - */
        +   * Return an attribute's value as a string.
        +   *
        +   * The first parameter, `name`, is a string with the name of the attribute
        +   * being checked. For example, calling `myXML.getString('color')` returns the
        +   * element's `id` attribute as a string.
        +   *
        +   * The second parameter, `defaultValue`, is optional. If a string is passed,
        +   * as in `myXML.getString('color', 'deeppink')`, it will be returned if the
        +   * attribute doesn't exist.
        +   *
        +   * Note: Use
        +   * <a href="#/p5.XML/getString">myXML.getString()</a> or
        +   * <a href="#/p5.XML/getNum">myXML.getNum()</a> to return an attribute's value.
        +   *
        +   * @param {String} name name of the attribute to be checked.
        +   * @param {Number} [defaultValue] value to return if the attribute doesn't exist.
        +   * @return {String} attribute value as a string.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the first reptile child element.
        +   *   let reptile = myXML.getChild('reptile');
        +   *
        +   *   // Get the reptile's content.
        +   *   let content = reptile.getContent();
        +   *
        +   *   // Get the reptile's species.
        +   *   let species = reptile.getString('species');
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the species attribute.
        +   *   text(`${content}: ${species}`, 5, 50, 90);
        +   *
        +   *   describe(`The text "${content}: ${species}" written in black on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the first reptile child element.
        +   *   let reptile = myXML.getChild('reptile');
        +   *
        +   *   // Get the reptile's content.
        +   *   let content = reptile.getContent();
        +   *
        +   *   // Get the reptile's color.
        +   *   let attribute = reptile.getString('color', 'green');
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *   fill(attribute);
        +   *
        +   *   // Display the element's content.
        +   *   text(content, 50, 50);
        +   *
        +   *   describe(`The text "${content}" written in green on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
           getString(name, defaultValue) {
             const obj = {};
         
        @@ -1091,137 +1017,135 @@ p5.XML = class  {
           }
         
           /**
        - * Sets an attribute to a given value.
        - *
        - * The first parameter, `name`, is a string with the name of the attribute
        - * being set.
        - *
        - * The second parameter, `value`, is the attribute's new value. For example,
        - * calling `myXML.setAttribute('id', 123)` sets the `id` attribute to the
        - * value 123.
        - *
        - * @method setAttribute
        - * @param {String} name name of the attribute to be set.
        - * @param {Number|String|Boolean} value attribute's new value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the first reptile child element.
        - *   let reptile = myXML.getChild('reptile');
        - *
        - *   // Set the reptile's color.
        - *   reptile.setAttribute('color', 'green');
        - *
        - *   // Get the reptile's content.
        - *   let content = reptile.getContent();
        - *
        - *   // Get the reptile's color.
        - *   let attribute = reptile.getString('color');
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the element's content.
        - *   text(`${content} is ${attribute}`, 5, 50, 90);
        - *
        - *   describe(
        - *     `The text "${content} is ${attribute}" written in green on a gray background.`
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        +   * Sets an attribute to a given value.
        +   *
        +   * The first parameter, `name`, is a string with the name of the attribute
        +   * being set.
        +   *
        +   * The second parameter, `value`, is the attribute's new value. For example,
        +   * calling `myXML.setAttribute('id', 123)` sets the `id` attribute to the
        +   * value 123.
        +   *
        +   * @param {String} name name of the attribute to be set.
        +   * @param {Number|String|Boolean} value attribute's new value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the first reptile child element.
        +   *   let reptile = myXML.getChild('reptile');
        +   *
        +   *   // Set the reptile's color.
        +   *   reptile.setAttribute('color', 'green');
        +   *
        +   *   // Get the reptile's content.
        +   *   let content = reptile.getContent();
        +   *
        +   *   // Get the reptile's color.
        +   *   let attribute = reptile.getString('color');
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the element's content.
        +   *   text(`${content} is ${attribute}`, 5, 50, 90);
        +   *
        +   *   describe(
        +   *     `The text "${content} is ${attribute}" written in green on a gray background.`
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
           setAttribute(name, value) {
             this.DOM.setAttribute(name, value);
           }
         
           /**
        - * Returns the element's content as a `String`.
        - *
        - * The parameter, `defaultValue`, is optional. If a string is passed, as in
        - * `myXML.getContent('???')`, it will be returned if the element has no
        - * content.
        - *
        - * @method getContent
        - * @param {String} [defaultValue] value to return if the element has no
        - *                                content.
        - * @return {String} element's content as a string.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the first reptile child element.
        - *   let reptile = myXML.getChild('reptile');
        - *
        - *   // Get the reptile's content.
        - *   let content = reptile.getContent();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the element's content.
        - *   text(content, 5, 50, 90);
        - *
        - *   describe(`The text "${content}" written in green on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.XML object.
        - *   let blankSpace = new p5.XML();
        - *
        - *   // Get the element's content and use a default value.
        - *   let content = blankSpace.getContent('Your name');
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the element's content.
        - *   text(content, 5, 50, 90);
        - *
        - *   describe(`The text "${content}" written in green on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - */
        +   * Returns the element's content as a `String`.
        +   *
        +   * The parameter, `defaultValue`, is optional. If a string is passed, as in
        +   * `myXML.getContent('???')`, it will be returned if the element has no
        +   * content.
        +   *
        +   * @param {String} [defaultValue] value to return if the element has no
        +   *                                content.
        +   * @return {String} element's content as a string.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the first reptile child element.
        +   *   let reptile = myXML.getChild('reptile');
        +   *
        +   *   // Get the reptile's content.
        +   *   let content = reptile.getContent();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the element's content.
        +   *   text(content, 5, 50, 90);
        +   *
        +   *   describe(`The text "${content}" written in green on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.XML object.
        +   *   let blankSpace = new p5.XML();
        +   *
        +   *   // Get the element's content and use a default value.
        +   *   let content = blankSpace.getContent('Your name');
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the element's content.
        +   *   text(content, 5, 50, 90);
        +   *
        +   *   describe(`The text "${content}" written in green on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
           getContent(defaultValue) {
             let str;
             str = this.DOM.textContent;
        @@ -1230,58 +1154,58 @@ p5.XML = class  {
           }
         
           /**
        - * Sets the element's content.
        - *
        - * An element's content is the text between its tags. For example, the element
        - * `&lt;language&gt;JavaScript&lt;/language&gt;` has the content `JavaScript`.
        - *
        - * The parameter, `content`, is a string with the element's new content.
        - *
        - * @method setContent
        - * @param {String} content new content for the element.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the first reptile child element.
        - *   let reptile = myXML.getChild('reptile');
        - *
        - *   // Get the reptile's original content.
        - *   let oldContent = reptile.getContent();
        - *
        - *   // Set the reptile's content.
        - *   reptile.setContent('Loggerhead');
        - *
        - *   // Get the reptile's new content.
        - *   let newContent = reptile.getContent();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(14);
        - *
        - *   // Display the element's old and new content.
        - *   text(`${oldContent}: ${newContent}`, 5, 50, 90);
        - *
        - *   describe(
        - *     `The text "${oldContent}: ${newContent}" written in green on a gray background.`
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        +   * Sets the element's content.
        +   *
        +   * An element's content is the text between its tags. For example, the element
        +   * `&lt;language&gt;JavaScript&lt;/language&gt;` has the content `JavaScript`.
        +   *
        +   * The parameter, `content`, is a string with the element's new content.
        +   *
        +   * @method setContent
        +   * @param {String} content new content for the element.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the first reptile child element.
        +   *   let reptile = myXML.getChild('reptile');
        +   *
        +   *   // Get the reptile's original content.
        +   *   let oldContent = reptile.getContent();
        +   *
        +   *   // Set the reptile's content.
        +   *   reptile.setContent('Loggerhead');
        +   *
        +   *   // Get the reptile's new content.
        +   *   let newContent = reptile.getContent();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Display the element's old and new content.
        +   *   text(`${oldContent}: ${newContent}`, 5, 50, 90);
        +   *
        +   *   describe(
        +   *     `The text "${oldContent}: ${newContent}" written in green on a gray background.`
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
           setContent(content) {
             if (!this.DOM.children.length) {
               this.DOM.textContent = content;
        @@ -1289,70 +1213,134 @@ p5.XML = class  {
           }
         
           /**
        - * Returns the element as a `String`.
        - *
        - * `myXML.serialize()` is useful for sending the element over the network or
        - * saving it to a file.
        - *
        - * @method serialize
        - * @return {String} element as a string.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myXML;
        - *
        - * // Load the XML and create a p5.XML object.
        - * function preload() {
        - *   myXML = loadXML('assets/animals.xml');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Display instructions.
        - *   text('Double-click to save', 5, 50, 90);
        - *
        - *   describe('The text "Double-click to save" written in black on a gray background.');
        - * }
        - *
        - * // Save the file when the user double-clicks.
        - * function doubleClicked() {
        - *   // Create a p5.PrintWriter object.
        - *   // Use the file format .xml.
        - *   let myWriter = createWriter('animals', 'xml');
        - *
        - *   // Serialize the XML data to a string.
        - *   let data = myXML.serialize();
        - *
        - *   // Write the data to the print stream.
        - *   myWriter.write(data);
        - *
        - *   // Save the file and close the print stream.
        - *   myWriter.close();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Returns the element as a `String`.
        +   *
        +   * `myXML.serialize()` is useful for sending the element over the network or
        +   * saving it to a file.
        +   *
        +   * @return {String} element as a string.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
        +   *
        +   *   // Display instructions.
        +   *   text('Double-click to save', 5, 50, 90);
        +   *
        +   *   describe('The text "Double-click to save" written in black on a gray background.');
        +   * }
        +   *
        +   * // Save the file when the user double-clicks.
        +   * function doubleClicked() {
        +   *   // Create a p5.PrintWriter object.
        +   *   // Use the file format .xml.
        +   *   let myWriter = createWriter('animals', 'xml');
        +   *
        +   *   // Serialize the XML data to a string.
        +   *   let data = myXML.serialize();
        +   *
        +   *   // Write the data to the print stream.
        +   *   myWriter.write(data);
        +   *
        +   *   // Save the file and close the print stream.
        +   *   myWriter.close();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           serialize() {
             const xmlSerializer = new XMLSerializer();
             return xmlSerializer.serializeToString(this.DOM);
           }
        -};
        +}
         
         function elementsToP5XML(elements) {
           const arr = [];
           for (let i = 0; i < elements.length; i++) {
        -    arr.push(new p5.XML(elements[i]));
        +    arr.push(new XML(elements[i]));
           }
           return arr;
         }
         
        -export default p5;
        +function xml(p5, fn){
        +  /**
        +   * A class to describe an XML object.
        +   *
        +   * Each `p5.XML` object provides an easy way to interact with XML data.
        +   * Extensible Markup Language
        +   * (<a href="https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction" target="_blank">XML</a>)
        +   * is a standard format for sending data between applications. Like HTML, the
        +   * XML format is based on tags and attributes, as in
        +   * `&lt;time units="s"&gt;1234&lt;/time&gt;`.
        +   *
        +   * Note: Use <a href="#/p5/loadXML">loadXML()</a> to load external XML files.
        +   *
        +   * @class p5.XML
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myXML;
        +   *
        +   * // Load the XML and create a p5.XML object.
        +   * function preload() {
        +   *   myXML = loadXML('assets/animals.xml');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get an array with all mammal tags.
        +   *   let mammals = myXML.getChildren('mammal');
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(14);
        +   *
        +   *   // Iterate over the mammals array.
        +   *   for (let i = 0; i < mammals.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 25;
        +   *
        +   *     // Get the mammal's common name.
        +   *     let name = mammals[i].getContent();
        +   *
        +   *     // Display the mammal's name.
        +   *     text(name, 20, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The words "Goat", "Leopard", and "Zebra" written on three separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  p5.XML = XML;
        +}
        +
        +export default xml;
        +export { XML }
        +
        +if(typeof p5 !== 'undefined'){
        +  xml(p5, p5.prototype);
        +}
        diff --git a/src/math/Matrices/Matrix.js b/src/math/Matrices/Matrix.js
        new file mode 100644
        index 0000000000..58ee51fd26
        --- /dev/null
        +++ b/src/math/Matrices/Matrix.js
        @@ -0,0 +1,1322 @@
        +import { Vector } from "../p5.Vector";
        +import { MatrixInterface } from "./MatrixInterface";
        +
        +const isPerfectSquare = (arr) => {
        +  const sqDimention = Math.sqrt(Array.from(arr).length);
        +  if (sqDimention % 1 !== 0) {
        +    throw new Error("Array length must be a perfect square.");
        +  }
        +  return true;
        +};
        +
        +export let GLMAT_ARRAY_TYPE = Array;
        +export let isMatrixArray = (x) => Array.isArray(x);
        +if (typeof Float32Array !== "undefined") {
        +  GLMAT_ARRAY_TYPE = Float32Array;
        +  isMatrixArray = (x) => Array.isArray(x) || x instanceof Float32Array;
        +}
        +/**
        + * The `Matrix` class represents a mathematical matrix and provides various methods for matrix operations.
        + * 
        + * This class extends the `MatrixInterface` and includes methods for creating, manipulating, and performing
        + * operations on matrices. It supports both 3x3 and 4x4 matrices, as well as general NxN matrices.
        + * 
        + * @class
        + * @extends MatrixInterface
        + * 
        + * @example
        + * // Creating a 3x3 matrix from an array
        + * const matrix = new Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        + * 
        + * // Creating a 4x4 identity matrix
        + * const identityMatrix = new Matrix(4);
        + * 
        + * // Adding two matrices
        + * const matrix1 = new Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        + * const matrix2 = new Matrix([9, 8, 7, 6, 5, 4, 3, 2, 1]);
        + * matrix1.add(matrix2); // matrix1 is now [10, 10, 10, 10, 10, 10, 10, 10, 10]
        + * 
        + * // Setting an element in the matrix
        + * matrix.setElement(0, 10); // matrix is now [10, 2, 3, 4, 5, 6, 7, 8, 9]
        + * 
        + * // Resetting the matrix to an identity matrix
        + * matrix.reset();
        + * 
        + * // Getting the diagonal elements of the matrix
        + * const diagonal = matrix.diagonal(); // [1, 1, 1]
        + * 
        + * // Transposing the matrix
        + * matrix.transpose();
        + * 
        + * // Multiplying two matrices
        + * matrix1.mult(matrix2);
        + * 
        + * // Inverting the matrix
        + * matrix.invert();
        + * 
        + * // Scaling the matrix
        + * matrix.scale(2, 2, 2);
        + * 
        + * // Rotating the matrix around an axis
        + * matrix.rotate4x4(Math.PI / 4, 1, 0, 0);
        + * 
        + * // Applying a perspective transformation
        + * matrix.perspective(Math.PI / 4, 1, 0.1, 100);
        + * 
        + * // Applying an orthographic transformation
        + * matrix.ortho(-1, 1, -1, 1, 0.1, 100);
        + * 
        + * // Multiplying a vector by the matrix
        + * const vector = new Vector(1, 2, 3);
        + * const result = matrix.multiplyPoint(vector);
        + */
        +export class Matrix extends MatrixInterface {
        +  matrix;
        +  #sqDimention;
        +
        +  constructor(...args) {
        +    super(...args);
        +    // This is default behavior when object
        +    // instantiated using createMatrix()
        +    if (isMatrixArray(args[0]) && isPerfectSquare(args[0])) {
        +      const sqDimention = Math.sqrt(Array.from(args[0]).length);
        +      this.#sqDimention = sqDimention;
        +      this.matrix = Array.from(args[0]);
        +    } else if (typeof args[0] === "number") {
        +      this.#sqDimention = Number(args[0]);
        +      this.matrix = this.#createIdentityMatrix(args[0]);
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * Getter for a 3x3 matrix.
        +   * 
        +   * This method returns the matrix if its dimensions are 3x3. 
        +   * If the matrix is not 3x3, it returns `undefined`.
        +   * 
        +   * @returns {Array|undefined} The 3x3 matrix or `undefined` if the matrix is not 3x3.
        +   */
        +  get mat3() {
        +    if (this.#sqDimention === 3) {
        +      return this.matrix;
        +    } else {
        +      return undefined;
        +    }
        +  }
        +
        +  /**
        +   * Getter for a 4x4 matrix.
        +   * 
        +   * This method returns the matrix if its dimensions are 4x4.
        +   * If the matrix is not 4x4, it returns `undefined`.
        +   * 
        +   * @returns {Array|undefined} The 4x4 matrix or `undefined` if the matrix is not 4x4.
        +   */
        +  get mat4() {
        +    if (this.#sqDimention === 4) {
        +      return this.matrix;
        +    } else {
        +      return undefined;
        +    }
        +  }
        +
        +  /**
        +   * Adds the corresponding elements of the given matrix to this matrix.
        +   * 
        +   * @param {Matrix} matrix - The matrix to add to this matrix. It must have the same dimensions as this matrix.
        +   * @returns {Matrix} The resulting matrix after addition.
        +   * @throws {Error} If the matrices do not have the same dimensions.
        +   * 
        +   * @example
        +   * const matrix1 = new Matrix([1, 2, 3]);
        +   * const matrix2 = new Matrix([4, 5, 6]);
        +   * matrix1.add(matrix2); // matrix1 is now [5, 7, 9]
        +   */
        +  add(matrix) {
        +    if (this.matrix.length !== matrix.matrix.length) {
        +      throw new Error("Matrices must be of the same dimension to add.");
        +    }
        +    for (let i = 0; i < this.matrix.length; i++) {
        +      this.matrix[i] += matrix.matrix[i];
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * Sets the value of a specific element in the matrix.
        +   *
        +   * @param {number} index - The position in the matrix where the value should be set. 
        +   *                         Must be a non-negative integer less than the length of the matrix.
        +   * @param {*} value - The new value to be assigned to the specified position in the matrix.
        +   * @returns {Matrix} The current instance of the Matrix, allowing for method chaining.
        +   *
        +   * @example
        +   * // Assuming matrix is an instance of Matrix with initial values [1, 2, 3]
        +   * matrix.setElement(1, 10);
        +   * // Now the matrix values are [1, 10, 3]
        +   */
        +  setElement(index, value) {
        +    if (index >= 0 && index < this.matrix.length) {
        +      this.matrix[index] = value;
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * Resets the current matrix to an identity matrix.
        +   * 
        +   * This method replaces the current matrix with an identity matrix of the same dimensions.
        +   * An identity matrix is a square matrix with ones on the main diagonal and zeros elsewhere.
        +   * 
        +   * @returns {Matrix} The current instance of the Matrix class, allowing for method chaining.
        +   */
        +  reset() {
        +    this.matrix = this.#createIdentityMatrix(this.#sqDimention);
        +    return this;
        +  }
        +
        +  /**
        +   * Replace the entire contents of a NxN matrix.
        +   * If providing an array or a p5.Matrix, the values will be copied without
        +   * referencing the source object.
        +   * Can also provide NxN numbers as individual arguments.
        +   *
        +   * @param {p5.Matrix|Float32Array|Number[]} [inMatrix] the input p5.Matrix or
        +   *                                     an Array of length 16
        +   * @chainable
        +   */
        +  /**
        +   * @param {Number[]} elements 16 numbers passed by value to avoid
        +   *                                     array copying.
        +   * @chainable
        +   */
        +  set(inMatrix) {
        +    let refArray = Array.from([...arguments]);
        +    if (inMatrix instanceof Matrix) {
        +      refArray = inMatrix.matrix;
        +    } else if (isMatrixArray(inMatrix)) {
        +      refArray = inMatrix;
        +    }
        +    if (refArray.length !== this.matrix.length) {
        +      p5._friendlyError(
        +        `Expected same dimentions values but received different ${refArray.length}.`,
        +        "p5.Matrix.set"
        +      );
        +      return this;
        +    }
        +    this.matrix = [...refArray];
        +    return this;
        +  }
        +
        +  /**
        +   * Gets a copy of the vector, returns a p5.Matrix object.
        +   *
        +   * @return {p5.Matrix} the copy of the p5.Matrix object
        +   */
        +  get() {
        +    return new Matrix(this.matrix); // TODO: Pass p5
        +  }
        +
        +  /**
        +   * return a copy of this matrix.
        +   * If this matrix is 4x4, a 4x4 matrix with exactly the same entries will be
        +   * generated. The same is true if this matrix is 3x3.
        +   *
        +   * @return {p5.Matrix}   the result matrix
        +   */
        +  copy() {
        +    return new Matrix(this.matrix);
        +  }
        +
        +  /**
        +   * Creates a copy of the current matrix instance.
        +   * This method is useful when you need a duplicate of the matrix
        +   * without modifying the original one.
        +   *
        +   * @returns {Matrix} A new matrix instance that is a copy of the current matrix.
        +   */
        +  clone() {
        +    return this.copy();
        +  }
        +  /**
        +   * Returns the diagonal elements of the matrix in the form of an array.
        +   * A NxN matrix will return an array of length N.
        +   *
        +   * @return {Number[]} An array obtained by arranging the diagonal elements
        +   *                    of the matrix in ascending order of index
        +   */
        +  diagonal() {
        +    const diagonal = [];
        +    for (let i = 0; i < this.#sqDimention; i++) {
        +      diagonal.push(this.matrix[i * (this.#sqDimention + 1)]);
        +    }
        +    return diagonal;
        +  }
        +
        +  /**
        +   * This function is only for 3x3 matrices.
        +   * A function that returns a row vector of a NxN matrix.
        +   *
        +   * @param {Number} columnIndex matrix column number
        +   * @return {p5.Vector}
        +   */
        +  row(columnIndex) {
        +    const columnVector = [];
        +    for (let i = 0; i < this.#sqDimention; i++) {
        +      columnVector.push(this.matrix[i * this.#sqDimention + columnIndex]);
        +    }
        +    return new Vector(...columnVector);
        +  }
        +
        +  /**
        +   * A function that returns a column vector of a NxN matrix.
        +   *
        +   * @param {Number} rowIndex matrix row number
        +   * @return {p5.Vector}
        +   */
        +  column(rowIndex) {
        +    const rowVector = [];
        +    for (let i = 0; i < this.#sqDimention; i++) {
        +      rowVector.push(this.matrix[rowIndex * this.#sqDimention + i]);
        +    }
        +    return new Vector(...rowVector);
        +  }
        +
        + 
        +
        +
        +  /**
        +   * Transposes the given matrix `a` based on the square dimension of the matrix.
        +   * 
        +   * This method rearranges the elements of the matrix such that the rows become columns
        +   * and the columns become rows. It handles matrices of different dimensions (4x4, 3x3, NxN)
        +   * by delegating to specific transpose methods for each case.
        +   * 
        +   * @param {Array} a - The matrix to be transposed. It should be a 2D array where each sub-array represents a row.
        +   * @returns {Array} - The transposed matrix.
        +   */
        +  transpose(a) {
        +    // TODO: Cristian: What does passing an argument to a transpose mean?
        +    // In the codebase this is never done in any reference
        +    // Actually transposse of a 4x4 is never done dierectly,
        +    // I'm thinking it is incorrect, transpose3x3 is only used for inverseTranspose4x4
        +    if (this.#sqDimention === 4) {
        +      return this.#transpose4x4(a);
        +    } else if (this.#sqDimention === 3) {
        +      return this.#transpose3x3(a);
        +    } else {
        +      return this.#transposeNxN(a);
        +    }
        +  }
        +
        + 
        +  /**
        +   * Multiplies the current matrix with another matrix or matrix-like array.
        +   * 
        +   * This method supports several types of input:
        +   * - Another Matrix instance
        +   * - A matrix-like array (must be a perfect square, e.g., 4x4 or 3x3)
        +   * - Multiple arguments that form a perfect square matrix
        +   * 
        +   * If the input is the same as the current matrix, a copy is made to avoid modifying the original matrix.
        +   * 
        +   * @param {Matrix|Array|...number} multMatrix - The matrix or matrix-like array to multiply with.
        +   * @returns {Matrix|undefined} The resulting matrix after multiplication, or undefined if the input is invalid.
        +   * @chainable
        +   */
        +  mult(multMatrix) {
        +    let _src;
        +    if (multMatrix === this || multMatrix === this.matrix) {
        +      _src = this.copy().matrix; // only need to allocate in this rare case
        +    } else if (multMatrix instanceof Matrix) {
        +      _src = multMatrix.matrix;
        +    } else if (isMatrixArray(multMatrix) && isPerfectSquare(multMatrix)) {
        +      _src = multMatrix;
        +    } else if (isPerfectSquare(arguments)) {
        +      _src = Array.from(arguments);
        +    } else {
        +      return; // nothing to do.
        +    }
        +    if (this.#sqDimention === 4 && _src.length === 16) {
        +      return this.#mult4x4(_src);
        +    } else if (this.#sqDimention === 3 && _src.length === 9) {
        +      return this.#mult3x3(_src);
        +    } else {
        +      return this.#multNxN(_src);
        +    }
        +  }
        +
        +  /**
        +   * This function is only for 3x3 matrices.
        +   * Takes a vector and returns the vector resulting from multiplying to
        +   * that vector by this matrix from left.
        +   *
        +   * @param {p5.Vector} multVector the vector to which this matrix applies
        +   * @param {p5.Vector} [target] The vector to receive the result
        +   * @return {p5.Vector}
        +   */
        +  multiplyVec(multVector, target) {
        +    if (target === undefined) {
        +      target = multVector.copy();
        +    }
        +    for (let i = 0; i < this.#sqDimention; i++) {
        +      target.values[i] = this.row(i).dot(multVector);
        +    }
        +    return target;
        +  }
        +
        +  /**
        +   * Inverts a given matrix.
        +   * 
        +   * This method inverts a matrix based on its dimensions. Currently, it supports
        +   * 3x3 and 4x4 matrices. If the matrix dimension is greater than 4, an error is thrown.
        +   * 
        +   * @param {Array} a - The matrix to be inverted. It should be a 2D array representing the matrix.
        +   * @returns {Array} - The inverted matrix.
        +   * @throws {Error} - Throws an error if the matrix dimension is greater than 4.
        +   */
        +  invert(a) {
        +    if (this.#sqDimention === 4) {
        +      return this.#invert4x4(a);
        +    } else if (this.#sqDimention === 3) {
        +      return this.#invert3x3(a);
        +    } else {
        +      throw new Error(
        +        "Invert is not implemented for N>4 at the moment, we are working on it"
        +      );
        +    }
        +  }
        +
        +  /**
        +   * This function is only for 4x4 matrices.
        +   * Creates a 3x3 matrix whose entries are the top left 3x3 part and returns it.
        +   *
        +   * @return {p5.Matrix}
        +   */
        +  createSubMatrix3x3() {
        +    if (this.#sqDimention === 4) {
        +      const result = new Matrix(3);
        +      result.mat3[0] = this.matrix[0];
        +      result.mat3[1] = this.matrix[1];
        +      result.mat3[2] = this.matrix[2];
        +      result.mat3[3] = this.matrix[4];
        +      result.mat3[4] = this.matrix[5];
        +      result.mat3[5] = this.matrix[6];
        +      result.mat3[6] = this.matrix[8];
        +      result.mat3[7] = this.matrix[9];
        +      result.mat3[8] = this.matrix[10];
        +      return result;
        +    } else {
        +      throw new Error("Matrix dimension must be 4 to create a 3x3 submatrix.");
        +    }
        +  }
        +
        +  /**
        +   * Converts a 4×4 matrix to its 3×3 inverse transform
        +   * commonly used in MVMatrix to NMatrix conversions.
        +   * @param  {p5.Matrix} mat4 the matrix to be based on to invert
        +   * @chainable
        +   * @todo  finish implementation
        +   */
        +  inverseTranspose4x4({ mat4 }) {
        +    if (this.#sqDimention !== 3) {
        +      p5._friendlyError("sorry, this function only works with mat3");
        +    } else {
        +      //convert mat4 -> mat3
        +      this.matrix[0] = mat4[0];
        +      this.matrix[1] = mat4[1];
        +      this.matrix[2] = mat4[2];
        +      this.matrix[3] = mat4[4];
        +      this.matrix[4] = mat4[5];
        +      this.matrix[5] = mat4[6];
        +      this.matrix[6] = mat4[8];
        +      this.matrix[7] = mat4[9];
        +      this.matrix[8] = mat4[10];
        +    }
        +
        +    const inverse = this.invert();
        +    // check inverse succeeded
        +    if (inverse) {
        +      inverse.transpose(this.matrix);
        +    } else {
        +      // in case of singularity, just zero the matrix
        +      for (let i = 0; i < 9; i++) {
        +        this.matrix[i] = 0;
        +      }
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * Applies a transformation matrix to the current matrix.
        +   *
        +   * This method multiplies the current matrix by another matrix, which can be provided
        +   * in several forms: another Matrix instance, an array representing a matrix, or as
        +   * individual arguments representing the elements of a 4x4 matrix.
        +   *
        +   * @param {Matrix|Array|number} multMatrix - The matrix to multiply with. This can be:
        +   *   - An instance of the Matrix class.
        +   *   - An array of 16 numbers representing a 4x4 matrix.
        +   *   - 16 individual numbers representing the elements of a 4x4 matrix.
        +   * @returns {Matrix} The current matrix after applying the transformation.
        +   *
        +   * @example
        +   * // Assuming `matrix` is an instance of Matrix
        +   * const anotherMatrix = new Matrix();
        +   * matrix.apply(anotherMatrix);
        +   *
        +   * @example
        +   * // Applying a transformation using an array
        +   * const matrixArray = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
        +   * matrix.apply(matrixArray);
        +   *
        +   * @example
        +   * // Applying a transformation using individual arguments
        +   * matrix.apply(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
        +   */
        +  apply(multMatrix) {
        +    let _src;
        +
        +    if (multMatrix === this || multMatrix === this.matrix) {
        +      _src = this.copy().matrix; // only need to allocate in this rare case
        +    } else if (multMatrix instanceof Matrix) {
        +      _src = multMatrix.matrix;
        +    } else if (isMatrixArray(multMatrix)) {
        +      _src = multMatrix;
        +    } else if (arguments.length === 16) {
        +      _src = arguments;
        +    } else {
        +      return; // nothing to do.
        +    }
        +
        +    const mat4 = this.matrix;
        +
        +    // each row is used for the multiplier
        +    const m0 = mat4[0];
        +    const m4 = mat4[4];
        +    const m8 = mat4[8];
        +    const m12 = mat4[12];
        +    mat4[0] = _src[0] * m0 + _src[1] * m4 + _src[2] * m8 + _src[3] * m12;
        +    mat4[4] = _src[4] * m0 + _src[5] * m4 + _src[6] * m8 + _src[7] * m12;
        +    mat4[8] = _src[8] * m0 + _src[9] * m4 + _src[10] * m8 + _src[11] * m12;
        +    mat4[12] = _src[12] * m0 + _src[13] * m4 + _src[14] * m8 + _src[15] * m12;
        +
        +    const m1 = mat4[1];
        +    const m5 = mat4[5];
        +    const m9 = mat4[9];
        +    const m13 = mat4[13];
        +    mat4[1] = _src[0] * m1 + _src[1] * m5 + _src[2] * m9 + _src[3] * m13;
        +    mat4[5] = _src[4] * m1 + _src[5] * m5 + _src[6] * m9 + _src[7] * m13;
        +    mat4[9] = _src[8] * m1 + _src[9] * m5 + _src[10] * m9 + _src[11] * m13;
        +    mat4[13] = _src[12] * m1 + _src[13] * m5 + _src[14] * m9 + _src[15] * m13;
        +
        +    const m2 = mat4[2];
        +    const m6 = mat4[6];
        +    const m10 = mat4[10];
        +    const m14 = mat4[14];
        +    mat4[2] = _src[0] * m2 + _src[1] * m6 + _src[2] * m10 + _src[3] * m14;
        +    mat4[6] = _src[4] * m2 + _src[5] * m6 + _src[6] * m10 + _src[7] * m14;
        +    mat4[10] = _src[8] * m2 + _src[9] * m6 + _src[10] * m10 + _src[11] * m14;
        +    mat4[14] = _src[12] * m2 + _src[13] * m6 + _src[14] * m10 + _src[15] * m14;
        +
        +    const m3 = mat4[3];
        +    const m7 = mat4[7];
        +    const m11 = mat4[11];
        +    const m15 = mat4[15];
        +    mat4[3] = _src[0] * m3 + _src[1] * m7 + _src[2] * m11 + _src[3] * m15;
        +    mat4[7] = _src[4] * m3 + _src[5] * m7 + _src[6] * m11 + _src[7] * m15;
        +    mat4[11] = _src[8] * m3 + _src[9] * m7 + _src[10] * m11 + _src[11] * m15;
        +    mat4[15] = _src[12] * m3 + _src[13] * m7 + _src[14] * m11 + _src[15] * m15;
        +
        +    return this;
        +  }
        +
        +  /**
        +   * scales a p5.Matrix by scalars or a vector
        +   * @param  {p5.Vector|Float32Array|Number[]} s vector to scale by
        +   * @chainable
        +   */
        +  scale(x, y, z) {
        +    if (x instanceof Vector) {
        +      // x is a vector, extract the components from it.
        +      y = x.y;
        +      z = x.z;
        +      x = x.x; // must be last
        +    } else if (x instanceof Array) {
        +      // x is an array, extract the components from it.
        +      y = x[1];
        +      z = x[2];
        +      x = x[0]; // must be last
        +    }
        +
        +    this.matrix[0] *= x;
        +    this.matrix[1] *= x;
        +    this.matrix[2] *= x;
        +    this.matrix[3] *= x;
        +    this.matrix[4] *= y;
        +    this.matrix[5] *= y;
        +    this.matrix[6] *= y;
        +    this.matrix[7] *= y;
        +    this.matrix[8] *= z;
        +    this.matrix[9] *= z;
        +    this.matrix[10] *= z;
        +    this.matrix[11] *= z;
        +
        +    return this;
        +  }
        +
        +  /**
        +   * rotate our Matrix around an axis by the given angle.
        +   * @param  {Number} a The angle of rotation in radians
        +   * @param  {p5.Vector|Number[]} axis  the axis(es) to rotate around
        +   * @chainable
        +   * inspired by Toji's gl-matrix lib, mat4 rotation
        +   */
        +  rotate4x4(a, x, y, z) {
        +    if (x instanceof Vector) {
        +      // x is a vector, extract the components from it.
        +      y = x.y;
        +      z = x.z;
        +      x = x.x; //must be last
        +    } else if (x instanceof Array) {
        +      // x is an array, extract the components from it.
        +      y = x[1];
        +      z = x[2];
        +      x = x[0]; //must be last
        +    }
        +
        +    const len = Math.sqrt(x * x + y * y + z * z);
        +    x *= 1 / len;
        +    y *= 1 / len;
        +    z *= 1 / len;
        +
        +    const a00 = this.matrix[0];
        +    const a01 = this.matrix[1];
        +    const a02 = this.matrix[2];
        +    const a03 = this.matrix[3];
        +    const a10 = this.matrix[4];
        +    const a11 = this.matrix[5];
        +    const a12 = this.matrix[6];
        +    const a13 = this.matrix[7];
        +    const a20 = this.matrix[8];
        +    const a21 = this.matrix[9];
        +    const a22 = this.matrix[10];
        +    const a23 = this.matrix[11];
        +
        +    //sin,cos, and tan of respective angle
        +    const sA = Math.sin(a);
        +    const cA = Math.cos(a);
        +    const tA = 1 - cA;
        +    // Construct the elements of the rotation matrix
        +    const b00 = x * x * tA + cA;
        +    const b01 = y * x * tA + z * sA;
        +    const b02 = z * x * tA - y * sA;
        +    const b10 = x * y * tA - z * sA;
        +    const b11 = y * y * tA + cA;
        +    const b12 = z * y * tA + x * sA;
        +    const b20 = x * z * tA + y * sA;
        +    const b21 = y * z * tA - x * sA;
        +    const b22 = z * z * tA + cA;
        +
        +    // rotation-specific matrix multiplication
        +    this.matrix[0] = a00 * b00 + a10 * b01 + a20 * b02;
        +    this.matrix[1] = a01 * b00 + a11 * b01 + a21 * b02;
        +    this.matrix[2] = a02 * b00 + a12 * b01 + a22 * b02;
        +    this.matrix[3] = a03 * b00 + a13 * b01 + a23 * b02;
        +    this.matrix[4] = a00 * b10 + a10 * b11 + a20 * b12;
        +    this.matrix[5] = a01 * b10 + a11 * b11 + a21 * b12;
        +    this.matrix[6] = a02 * b10 + a12 * b11 + a22 * b12;
        +    this.matrix[7] = a03 * b10 + a13 * b11 + a23 * b12;
        +    this.matrix[8] = a00 * b20 + a10 * b21 + a20 * b22;
        +    this.matrix[9] = a01 * b20 + a11 * b21 + a21 * b22;
        +    this.matrix[10] = a02 * b20 + a12 * b21 + a22 * b22;
        +    this.matrix[11] = a03 * b20 + a13 * b21 + a23 * b22;
        +
        +    return this;
        +  }
        +
        +  /**
        +   * @todo  finish implementing this method!
        +   * translates
        +   * @param  {Number[]} v vector to translate by
        +   * @chainable
        +   */
        +  translate(v) {
        +    const x = v[0],
        +      y = v[1],
        +      z = v[2] || 0;
        +    this.matrix[12] +=
        +      this.matrix[0] * x + this.matrix[4] * y + this.matrix[8] * z;
        +    this.matrix[13] +=
        +      this.matrix[1] * x + this.matrix[5] * y + this.matrix[9] * z;
        +    this.matrix[14] +=
        +      this.matrix[2] * x + this.matrix[6] * y + this.matrix[10] * z;
        +    this.matrix[15] +=
        +      this.matrix[3] * x + this.matrix[7] * y + this.matrix[11] * z;
        +  }
        +
        +  /**
        +   * Rotates the matrix around the X-axis by a given angle.
        +   *
        +   * This method modifies the current matrix to apply a rotation transformation
        +   * around the X-axis. The rotation angle is specified in radians.
        +   *
        +   * @param {number} a - The angle in radians to rotate the matrix by.
        +   */
        +  rotateX(a) {
        +    this.rotate4x4(a, 1, 0, 0);
        +  }
        +
        +  /**
        +   * Rotates the matrix around the Y-axis by a given angle.
        +   *
        +   * This method modifies the current matrix to apply a rotation transformation
        +   * around the Y-axis. The rotation is performed in 3D space, and the angle
        +   * is specified in radians.
        +   *
        +   * @param {number} a - The angle in radians to rotate the matrix by. Positive
        +   * values rotate the matrix counterclockwise, and negative values rotate it
        +   * clockwise.
        +   */
        +  rotateY(a) {
        +    this.rotate4x4(a, 0, 1, 0);
        +  }
        +
        +  /**
        +   * Rotates the matrix around the Z-axis by a given angle.
        +   *
        +   * @param {number} a - The angle in radians to rotate the matrix by.
        +   * 
        +   * This method modifies the current matrix to apply a rotation transformation
        +   * around the Z-axis. The rotation is performed in a 4x4 matrix context, which
        +   * is commonly used in 3D graphics to handle transformations.
        +   *
        +   * Example usage:
        +   * ```
        +   * const matrix = new Matrix();
        +   * matrix.rotateZ(Math.PI / 4); // Rotates the matrix 45 degrees around the Z-axis
        +   * ```
        +   */
        +  rotateZ(a) {
        +    this.rotate4x4(a, 0, 0, 1);
        +  }
        +
        +  /**
        +   * sets the perspective matrix
        +   * @param  {Number} fovy   [description]
        +   * @param  {Number} aspect [description]
        +   * @param  {Number} near   near clipping plane
        +   * @param  {Number} far    far clipping plane
        +   * @chainable
        +   */
        +  perspective(fovy, aspect, near, far) {
        +    const f = 1.0 / Math.tan(fovy / 2),
        +      nf = 1 / (near - far);
        +
        +    this.matrix[0] = f / aspect;
        +    this.matrix[1] = 0;
        +    this.matrix[2] = 0;
        +    this.matrix[3] = 0;
        +    this.matrix[4] = 0;
        +    this.matrix[5] = f;
        +    this.matrix[6] = 0;
        +    this.matrix[7] = 0;
        +    this.matrix[8] = 0;
        +    this.matrix[9] = 0;
        +    this.matrix[10] = (far + near) * nf;
        +    this.matrix[11] = -1;
        +    this.matrix[12] = 0;
        +    this.matrix[13] = 0;
        +    this.matrix[14] = 2 * far * near * nf;
        +    this.matrix[15] = 0;
        +
        +    return this;
        +  }
        +
        +  /**
        +   * sets the ortho matrix
        +   * @param  {Number} left   [description]
        +   * @param  {Number} right  [description]
        +   * @param  {Number} bottom [description]
        +   * @param  {Number} top    [description]
        +   * @param  {Number} near   near clipping plane
        +   * @param  {Number} far    far clipping plane
        +   * @chainable
        +   */
        +  ortho(left, right, bottom, top, near, far) {
        +    const lr = 1 / (left - right),
        +      bt = 1 / (bottom - top),
        +      nf = 1 / (near - far);
        +    this.matrix[0] = -2 * lr;
        +    this.matrix[1] = 0;
        +    this.matrix[2] = 0;
        +    this.matrix[3] = 0;
        +    this.matrix[4] = 0;
        +    this.matrix[5] = -2 * bt;
        +    this.matrix[6] = 0;
        +    this.matrix[7] = 0;
        +    this.matrix[8] = 0;
        +    this.matrix[9] = 0;
        +    this.matrix[10] = 2 * nf;
        +    this.matrix[11] = 0;
        +    this.matrix[12] = (left + right) * lr;
        +    this.matrix[13] = (top + bottom) * bt;
        +    this.matrix[14] = (far + near) * nf;
        +    this.matrix[15] = 1;
        +
        +    return this;
        +  }
        +
        +  /**
        +   * apply a matrix to a vector with x,y,z,w components
        +   * get the results in the form of an array
        +   * @param {Number}
        +   * @return {Number[]}
        +   */
        +  multiplyVec4(x, y, z, w) {
        +    const result = new Array(4);
        +    const m = this.matrix;
        +
        +    result[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
        +    result[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
        +    result[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
        +    result[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
        +
        +    return result;
        +  }
        +
        +  /**
        +   * Applies a matrix to a vector.
        +   * The fourth component is set to 1.
        +   * Returns a vector consisting of the first
        +   * through third components of the result.
        +   *
        +   * @param {p5.Vector}
        +   * @return {p5.Vector}
        +   */
        +  multiplyPoint({ x, y, z }) {
        +    const array = this.multiplyVec4(x, y, z, 1);
        +    return new Vector(array[0], array[1], array[2]);
        +  }
        +
        +  /**
        +   * Applies a matrix to a vector.
        +   * The fourth component is set to 1.
        +   * Returns the result of dividing the 1st to 3rd components
        +   * of the result by the 4th component as a vector.
        +   *
        +   * @param {p5.Vector}
        +   * @return {p5.Vector}
        +   */
        +  multiplyAndNormalizePoint({ x, y, z }) {
        +    const array = this.multiplyVec4(x, y, z, 1);
        +    array[0] /= array[3];
        +    array[1] /= array[3];
        +    array[2] /= array[3];
        +    return new Vector(array[0], array[1], array[2]);
        +  }
        +
        +  /**
        +   * Applies a matrix to a vector.
        +   * The fourth component is set to 0.
        +   * Returns a vector consisting of the first
        +   * through third components of the result.
        +   *
        +   * @param {p5.Vector}
        +   * @return {p5.Vector}
        +   */
        +  multiplyDirection({ x, y, z }) {
        +    const array = this.multiplyVec4(x, y, z, 0);
        +    return new Vector(array[0], array[1], array[2]);
        +  }
        +
        +  /**
        +   * This function is only for 3x3 matrices.
        +   * Takes a vector and returns the vector resulting from multiplying to
        +   * that vector by this matrix from left.
        +   *
        +   * @param {p5.Vector} multVector the vector to which this matrix applies
        +   * @param {p5.Vector} [target] The vector to receive the result
        +   * @return {p5.Vector}
        +   */
        +  /**
        +   * This function is only for 3x3 matrices.
        +   * Takes a vector and returns the vector resulting from multiplying to
        +   * that vector by this matrix from left.
        +   *
        +   * @param {p5.Vector} multVector the vector to which this matrix applies
        +   * @param {p5.Vector} [target] The vector to receive the result
        +   * @return {p5.Vector}
        +   */
        +  multiplyVec3(multVector, target) {
        +    if (target === undefined) {
        +      target = multVector.copy();
        +    }
        +    target.x = this.row(0).dot(multVector);
        +    target.y = this.row(1).dot(multVector);
        +    target.z = this.row(2).dot(multVector);
        +    return target;
        +  }
        +
        +  // ====================
        +  // PRIVATE
        +  #createIdentityMatrix(dimension) {
        +    // This it to prevent loops in the most common 3x3 and 4x4 cases
        +    // TODO: check performance if it actually helps
        +    if (dimension === 3)
        +      return new GLMAT_ARRAY_TYPE([1, 0, 0, 0, 1, 0, 0, 0, 1]);
        +    if (dimension === 4)
        +      return new GLMAT_ARRAY_TYPE([
        +        1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
        +      ]);
        +    const identityMatrix = new GLMAT_ARRAY_TYPE(dimension * dimension).fill(0);
        +    for (let i = 0; i < dimension; i++) {
        +      identityMatrix[i * dimension + i] = 1;
        +    }
        +    return identityMatrix;
        +  }
        +
        +  /**
        +   * Multiplies the current 4x4 matrix with another 4x4 matrix.
        +   * This method updates the current matrix with the result of the multiplication.
        +   * 
        +   * @private
        +   * @param {number[]} _src - A 16-element array representing the 4x4 matrix to multiply with.
        +   * 
        +   * @returns {this} The current instance with the updated matrix.
        +   * 
        +   * @example
        +   * // Assuming `matrix` is an instance of the Matrix class
        +   * const srcMatrix = [
        +   *   1, 0, 0, 0,
        +   *   0, 1, 0, 0,
        +   *   0, 0, 1, 0,
        +   *   0, 0, 0, 1
        +   * ];
        +   * matrix.#mult4x4(srcMatrix);
        +   */
        +  #mult4x4(_src) {
        +    // each row is used for the multiplier
        +    let b0 = this.matrix[0],
        +      b1 = this.matrix[1],
        +      b2 = this.matrix[2],
        +      b3 = this.matrix[3];
        +    this.matrix[0] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12];
        +    this.matrix[1] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13];
        +    this.matrix[2] =
        +      b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14];
        +    this.matrix[3] =
        +      b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15];
        +
        +    b0 = this.matrix[4];
        +    b1 = this.matrix[5];
        +    b2 = this.matrix[6];
        +    b3 = this.matrix[7];
        +    this.matrix[4] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12];
        +    this.matrix[5] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13];
        +    this.matrix[6] =
        +      b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14];
        +    this.matrix[7] =
        +      b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15];
        +
        +    b0 = this.matrix[8];
        +    b1 = this.matrix[9];
        +    b2 = this.matrix[10];
        +    b3 = this.matrix[11];
        +    this.matrix[8] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12];
        +    this.matrix[9] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13];
        +    this.matrix[10] =
        +      b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14];
        +    this.matrix[11] =
        +      b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15];
        +
        +    b0 = this.matrix[12];
        +    b1 = this.matrix[13];
        +    b2 = this.matrix[14];
        +    b3 = this.matrix[15];
        +    this.matrix[12] =
        +      b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12];
        +    this.matrix[13] =
        +      b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13];
        +    this.matrix[14] =
        +      b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14];
        +    this.matrix[15] =
        +      b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15];
        +
        +    return this;
        +  }
        +
        +  /**
        +   * @param {p5.Matrix|Float32Array|Number[]} multMatrix The matrix
        +   *                                                we want to multiply by
        +   * @chainable
        +   */
        +  #multNxN(multMatrix) {
        +    if (multMatrix.length !== this.matrix.length) {
        +      throw new Error("Matrices must be of the same dimension to multiply.");
        +    }
        +    const result = new GLMAT_ARRAY_TYPE(this.matrix.length).fill(0);
        +    for (let i = 0; i < this.#sqDimention; i++) {
        +      for (let j = 0; j < this.#sqDimention; j++) {
        +        for (let k = 0; k < this.#sqDimention; k++) {
        +          result[i * this.#sqDimention + j] +=
        +            this.matrix[i * this.#sqDimention + k] *
        +            multMatrix[k * this.#sqDimention + j];
        +        }
        +      }
        +    }
        +    this.matrix = result;
        +    return this;
        +  }
        +
        +  /**
        +   * This function is only for 3x3 matrices.
        +   * multiply two mat3s. It is an operation to multiply the 3x3 matrix of
        +   * the argument from the right. Arguments can be a 3x3 p5.Matrix,
        +   * a Float32Array of length 9, or a javascript array of length 9.
        +   * In addition, it can also be done by enumerating 9 numbers.
        +   *
        +   * @param {p5.Matrix|Float32Array|Number[]} multMatrix The matrix
        +   *                                                we want to multiply by
        +   * @chainable
        +   */
        +  #mult3x3(_src) {
        +    // each row is used for the multiplier
        +    let b0 = this.mat3[0];
        +    let b1 = this.mat3[1];
        +    let b2 = this.mat3[2];
        +    this.mat3[0] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6];
        +    this.mat3[1] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7];
        +    this.mat3[2] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8];
        +
        +    b0 = this.mat3[3];
        +    b1 = this.mat3[4];
        +    b2 = this.mat3[5];
        +    this.mat3[3] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6];
        +    this.mat3[4] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7];
        +    this.mat3[5] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8];
        +
        +    b0 = this.mat3[6];
        +    b1 = this.mat3[7];
        +    b2 = this.mat3[8];
        +    this.mat3[6] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6];
        +    this.mat3[7] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7];
        +    this.mat3[8] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8];
        +
        +    return this;
        +  }
        +
        +  /**
        +   * Transposes a square matrix in place.
        +   * This method swaps the rows and columns of the matrix, effectively flipping it over its diagonal.
        +   * 
        +   * @private
        +   * @returns {Matrix} The current instance of the Matrix, with the transposed values.
        +   */
        +  #transposeNxN() {
        +    const n = this.#sqDimention;
        +    for (let i = 0; i < n; i++) {
        +      for (let j = 0; j < n; j++) {
        +        this.matrix[i * n + j] = this.matrix[j * n + i];
        +      }
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * transpose according to a given matrix
        +   * @param  {p5.Matrix|Float32Array|Number[]} a  the matrix to be
        +   *                                               based on to transpose
        +   * @chainable
        +   */
        +  #transpose4x4(a) {
        +    console.log("====> 4x4");
        +    let a01, a02, a03, a12, a13, a23;
        +    if (a instanceof Matrix) {
        +      a01 = a.matrix[1];
        +      a02 = a.matrix[2];
        +      a03 = a.matrix[3];
        +      a12 = a.matrix[6];
        +      a13 = a.matrix[7];
        +      a23 = a.matrix[11];
        +
        +      this.matrix[0] = a.matrix[0];
        +      this.matrix[1] = a.matrix[4];
        +      this.matrix[2] = a.matrix[8];
        +      this.matrix[3] = a.matrix[12];
        +      this.matrix[4] = a01;
        +      this.matrix[5] = a.matrix[5];
        +      this.matrix[6] = a.matrix[9];
        +      this.matrix[7] = a.matrix[13];
        +      this.matrix[8] = a02;
        +      this.matrix[9] = a12;
        +      this.matrix[10] = a.matrix[10];
        +      this.matrix[11] = a.matrix[14];
        +      this.matrix[12] = a03;
        +      this.matrix[13] = a13;
        +      this.matrix[14] = a23;
        +      this.matrix[15] = a.matrix[15];
        +    } else if (isMatrixArray(a)) {
        +      a01 = a[1];
        +      a02 = a[2];
        +      a03 = a[3];
        +      a12 = a[6];
        +      a13 = a[7];
        +      a23 = a[11];
        +
        +      this.matrix[0] = a[0];
        +      this.matrix[1] = a[4];
        +      this.matrix[2] = a[8];
        +      this.matrix[3] = a[12];
        +      this.matrix[4] = a01;
        +      this.matrix[5] = a[5];
        +      this.matrix[6] = a[9];
        +      this.matrix[7] = a[13];
        +      this.matrix[8] = a02;
        +      this.matrix[9] = a12;
        +      this.matrix[10] = a[10];
        +      this.matrix[11] = a[14];
        +      this.matrix[12] = a03;
        +      this.matrix[13] = a13;
        +      this.matrix[14] = a23;
        +      this.matrix[15] = a[15];
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * This function is only for 3x3 matrices.
        +   * transposes a 3×3 p5.Matrix by a mat3
        +   * If there is an array of arguments, the matrix obtained by transposing
        +   * the 3x3 matrix generated based on that array is set.
        +   * If no arguments, it transposes itself and returns it.
        +   *
        +   * @param  {Number[]} mat3 1-dimensional array
        +   * @chainable
        +   */
        +  #transpose3x3(mat3) {
        +    if (mat3 === undefined) {
        +      mat3 = this.mat3;
        +    }
        +    const a01 = mat3[1];
        +    const a02 = mat3[2];
        +    const a12 = mat3[5];
        +    this.mat3[0] = mat3[0];
        +    this.mat3[1] = mat3[3];
        +    this.mat3[2] = mat3[6];
        +    this.mat3[3] = a01;
        +    this.mat3[4] = mat3[4];
        +    this.mat3[5] = mat3[7];
        +    this.mat3[6] = a02;
        +    this.mat3[7] = a12;
        +    this.mat3[8] = mat3[8];
        +
        +    return this;
        +  }
        +
        +  /**
        +   * Only 4x4 becasuse determinant is only 4x4 currently
        +   * invert  matrix according to a give matrix
        +   * @param  {p5.Matrix|Float32Array|Number[]} a   the matrix to be
        +   *                                                based on to invert
        +   * @chainable
        +   */
        +  #invert4x4(a) {
        +    let a00, a01, a02, a03, a10, a11, a12, a13;
        +    let a20, a21, a22, a23, a30, a31, a32, a33;
        +    if (a instanceof Matrix) {
        +      a00 = a.matrix[0];
        +      a01 = a.matrix[1];
        +      a02 = a.matrix[2];
        +      a03 = a.matrix[3];
        +      a10 = a.matrix[4];
        +      a11 = a.matrix[5];
        +      a12 = a.matrix[6];
        +      a13 = a.matrix[7];
        +      a20 = a.matrix[8];
        +      a21 = a.matrix[9];
        +      a22 = a.matrix[10];
        +      a23 = a.matrix[11];
        +      a30 = a.matrix[12];
        +      a31 = a.matrix[13];
        +      a32 = a.matrix[14];
        +      a33 = a.matrix[15];
        +    } else if (isMatrixArray(a)) {
        +      a00 = a[0];
        +      a01 = a[1];
        +      a02 = a[2];
        +      a03 = a[3];
        +      a10 = a[4];
        +      a11 = a[5];
        +      a12 = a[6];
        +      a13 = a[7];
        +      a20 = a[8];
        +      a21 = a[9];
        +      a22 = a[10];
        +      a23 = a[11];
        +      a30 = a[12];
        +      a31 = a[13];
        +      a32 = a[14];
        +      a33 = a[15];
        +    }
        +    const b00 = a00 * a11 - a01 * a10;
        +    const b01 = a00 * a12 - a02 * a10;
        +    const b02 = a00 * a13 - a03 * a10;
        +    const b03 = a01 * a12 - a02 * a11;
        +    const b04 = a01 * a13 - a03 * a11;
        +    const b05 = a02 * a13 - a03 * a12;
        +    const b06 = a20 * a31 - a21 * a30;
        +    const b07 = a20 * a32 - a22 * a30;
        +    const b08 = a20 * a33 - a23 * a30;
        +    const b09 = a21 * a32 - a22 * a31;
        +    const b10 = a21 * a33 - a23 * a31;
        +    const b11 = a22 * a33 - a23 * a32;
        +
        +    // Calculate the determinant
        +    let det =
        +      b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
        +
        +    if (!det) {
        +      return null;
        +    }
        +    det = 1.0 / det;
        +
        +    this.matrix[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
        +    this.matrix[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
        +    this.matrix[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
        +    this.matrix[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
        +    this.matrix[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
        +    this.matrix[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
        +    this.matrix[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
        +    this.matrix[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
        +    this.matrix[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
        +    this.matrix[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
        +    this.matrix[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
        +    this.matrix[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
        +    this.matrix[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
        +    this.matrix[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
        +    this.matrix[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
        +    this.matrix[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
        +
        +    return this;
        +  }
        +
        +  /**
        +   * Inverts a 3×3 matrix
        +   * @chainable
        +   */
        +  #invert3x3() {
        +    const a00 = this.mat3[0];
        +    const a01 = this.mat3[1];
        +    const a02 = this.mat3[2];
        +    const a10 = this.mat3[3];
        +    const a11 = this.mat3[4];
        +    const a12 = this.mat3[5];
        +    const a20 = this.mat3[6];
        +    const a21 = this.mat3[7];
        +    const a22 = this.mat3[8];
        +    const b01 = a22 * a11 - a12 * a21;
        +    const b11 = -a22 * a10 + a12 * a20;
        +    const b21 = a21 * a10 - a11 * a20;
        +
        +    // Calculate the determinant
        +    let det = a00 * b01 + a01 * b11 + a02 * b21;
        +    if (!det) {
        +      return null;
        +    }
        +    det = 1.0 / det;
        +    this.mat3[0] = b01 * det;
        +    this.mat3[1] = (-a22 * a01 + a02 * a21) * det;
        +    this.mat3[2] = (a12 * a01 - a02 * a11) * det;
        +    this.mat3[3] = b11 * det;
        +    this.mat3[4] = (a22 * a00 - a02 * a20) * det;
        +    this.mat3[5] = (-a12 * a00 + a02 * a10) * det;
        +    this.mat3[6] = b21 * det;
        +    this.mat3[7] = (-a21 * a00 + a01 * a20) * det;
        +    this.mat3[8] = (a11 * a00 - a01 * a10) * det;
        +    return this;
        +  }
        +
        +  /**
        +   * inspired by Toji's mat4 determinant
        +   * @return {Number} Determinant of our 4×4 matrix
        +   */
        +  #determinant4x4() {
        +    if (this.#sqDimention !== 4) {
        +      throw new Error(
        +        "Determinant is only implemented for 4x4 matrices. We are working on it."
        +      );
        +    }
        +
        +    const d00 =
        +        this.matrix[0] * this.matrix[5] - this.matrix[1] * this.matrix[4],
        +      d01 = this.matrix[0] * this.matrix[6] - this.matrix[2] * this.matrix[4],
        +      d02 = this.matrix[0] * this.matrix[7] - this.matrix[3] * this.matrix[4],
        +      d03 = this.matrix[1] * this.matrix[6] - this.matrix[2] * this.matrix[5],
        +      d04 = this.matrix[1] * this.matrix[7] - this.matrix[3] * this.matrix[5],
        +      d05 = this.matrix[2] * this.matrix[7] - this.matrix[3] * this.matrix[6],
        +      d06 = this.matrix[8] * this.matrix[13] - this.matrix[9] * this.matrix[12],
        +      d07 =
        +        this.matrix[8] * this.matrix[14] - this.matrix[10] * this.matrix[12],
        +      d08 =
        +        this.matrix[8] * this.matrix[15] - this.matrix[11] * this.matrix[12],
        +      d09 =
        +        this.matrix[9] * this.matrix[14] - this.matrix[10] * this.matrix[13],
        +      d10 =
        +        this.matrix[9] * this.matrix[15] - this.matrix[11] * this.matrix[13],
        +      d11 =
        +        this.matrix[10] * this.matrix[15] - this.matrix[11] * this.matrix[14];
        +
        +    // Calculate the determinant
        +    return (
        +      d00 * d11 - d01 * d10 + d02 * d09 + d03 * d08 - d04 * d07 + d05 * d06
        +    );
        +  }
        +
        +  /**
        +   * PRIVATE
        +   */
        +  // matrix methods adapted from:
        +  // https://developer.mozilla.org/en-US/docs/Web/WebGL/
        +  // gluPerspective
        +  //
        +  // function _makePerspective(fovy, aspect, znear, zfar){
        +  //    const ymax = znear * Math.tan(fovy * Math.PI / 360.0);
        +  //    const ymin = -ymax;
        +  //    const xmin = ymin * aspect;
        +  //    const xmax = ymax * aspect;
        +  //    return _makeFrustum(xmin, xmax, ymin, ymax, znear, zfar);
        +  //  }
        +
        +  ////
        +  //// glFrustum
        +  ////
        +  //function _makeFrustum(left, right, bottom, top, znear, zfar){
        +  //  const X = 2*znear/(right-left);
        +  //  const Y = 2*znear/(top-bottom);
        +  //  const A = (right+left)/(right-left);
        +  //  const B = (top+bottom)/(top-bottom);
        +  //  const C = -(zfar+znear)/(zfar-znear);
        +  //  const D = -2*zfar*znear/(zfar-znear);
        +  //  const frustrumMatrix =[
        +  //  X, 0, A, 0,
        +  //  0, Y, B, 0,
        +  //  0, 0, C, D,
        +  //  0, 0, -1, 0
        +  //];
        +  //return frustrumMatrix;
        +  // }
        +
        +  // function _setMVPMatrices(){
        +  ////an identity matrix
        +  ////@TODO use the p5.Matrix class to abstract away our MV matrices and
        +  ///other math
        +  //const _mvMatrix =
        +  //[
        +  //  1.0,0.0,0.0,0.0,
        +  //  0.0,1.0,0.0,0.0,
        +  //  0.0,0.0,1.0,0.0,
        +  //  0.0,0.0,0.0,1.0
        +  //];
        +}
        diff --git a/src/math/Matrices/MatrixInterface.js b/src/math/Matrices/MatrixInterface.js
        new file mode 100644
        index 0000000000..4ab4a0b63a
        --- /dev/null
        +++ b/src/math/Matrices/MatrixInterface.js
        @@ -0,0 +1,53 @@
        +export let GLMAT_ARRAY_TYPE = Array;
        +export let isMatrixArray = (x) => Array.isArray(x);
        +if (typeof Float32Array !== "undefined") {
        +  GLMAT_ARRAY_TYPE = Float32Array;
        +  isMatrixArray = (x) => Array.isArray(x) || x instanceof Float32Array;
        +}
        +export class MatrixInterface {
        +  // Private field to store the matrix
        +  #matrix = null;
        +  constructor(...args) {
        +    if (this.constructor === MatrixInterface) {
        +      throw new Error("Class is of abstract type and can't be instantiated");
        +    }
        +    const methods = [
        +      "add",
        +      "setElement",
        +      "reset",
        +      "set",
        +      "get",
        +      "copy",
        +      "clone",
        +      "diagonal",
        +      "row",
        +      "column",
        +      "transpose",
        +      "mult",
        +      "multiplyVec",
        +      "invert",
        +      "createSubMatrix3x3",
        +      "inverseTranspose4x4",
        +      "apply",
        +      "scale",
        +      "rotate4x4",
        +      "translate",
        +      "rotateX",
        +      "rotateY",
        +      "rotateZ",
        +      "perspective",
        +      "ortho",
        +      "multiplyVec4",
        +      "multiplyPoint",
        +      "multiplyAndNormalizePoint",
        +      "multiplyDirection",
        +      "multiplyVec3",
        +    ];
        +
        +    methods.forEach((method) => {
        +      if (this[method] === undefined) {
        +        throw new Error(`${method}() method must be implemented`);
        +      }
        +    });
        +  }
        +}
        diff --git a/src/math/Matrices/MatrixNumjs.js b/src/math/Matrices/MatrixNumjs.js
        new file mode 100644
        index 0000000000..75d731dbee
        --- /dev/null
        +++ b/src/math/Matrices/MatrixNumjs.js
        @@ -0,0 +1,966 @@
        +import nj from "@d4c/numjs/build/module/numjs.min.js";
        +import { Vector } from "../p5.Vector";
        +import { MatrixInterface } from "./MatrixInterface";
        +
        +/**
        + * @requires constants
        + * @todo see methods below needing further implementation.
        + * future consideration: implement SIMD optimizations
        + * when browser compatibility becomes available
        + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/
        + *   Reference/Global_Objects/SIMD
        + */
        +
        +let GLMAT_ARRAY_TYPE = Array;
        +let isMatrixArray = (x) => Array.isArray(x);
        +if (typeof Float32Array !== "undefined") {
        +  GLMAT_ARRAY_TYPE = Float32Array;
        +  isMatrixArray = (x) => Array.isArray(x) || x instanceof Float32Array;
        +}
        +
        +/**
        + * A class to describe a matrix, which can be either a 3×3 or 4×4 matrix,
        + * for various matrix manipulations in the p5.js webgl renderer.
        + * This class provides methods for common matrix operations such as
        + * multiplication, inversion, transposition, and transformation.
        + * It supports both 3×3 matrices, typically used for normal transformations,
        + * and 4×4 matrices, commonly used for model, view, and projection transformations.
        + *
        + * @class MatrixNumjs
        + * @private
        + * @param {Array} [mat4] column-major array literal of our 4×4 matrix
        + * @param {Array} [mat3] column-major array literal of our 3×3 matrix
        + * @example
        + * <div>
        + * <code>
        + * function setup() {
        + *   createCanvas(100, 100);
        + *
        + *   background(200);
        + *
        + *   // Create Vector objects.
        + *   let p1 = createMatrix(1,1,1,1,1,1,1,1,1);
        + *   console.log(p1);
        + * }
        + * </code>
        + * </div>
        + */
        +// const matrixEngine = "numjs";
        +export class MatrixNumjs extends MatrixInterface{
        +  constructor(...args) {
        +    // This is default behavior when object
        +    super(...args)
        +
        +    if (args[0] === 3) {
        +      this._mat3 = Array.isArray(args[1]) ? nj.array(args[1]) : nj.identity(3);
        +    } else {
        +      this._mat4 = Array.isArray(args[0]) ? nj.array(args[0]) : nj.identity(4);
        +    }
        +    return this;
        +  }
        +
        +  get mat3() {
        +    return this._mat3?.flatten().tolist();
        +  }
        +  get mat4() {
        +    return this._mat4?.flatten().tolist();
        +  }
        +  /**
        +   * Resets the matrix to the identity matrix.
        +   *
        +   * If the matrix is a 3x3 matrix (`mat3`), it sets the matrix to:
        +   * [1, 0, 0,
        +   *  0, 1, 0,
        +   *  0, 0, 1]
        +   *
        +   * If the matrix is a 4x4 matrix (`mat4`), it sets the matrix to:
        +   * [1, 0, 0, 0,
        +   *  0, 1, 0, 0,
        +   *  0, 0, 1, 0,
        +   *  0, 0, 0, 1]
        +   *
        +   * @returns {this} The current instance for chaining.
        +   */
        +  reset() {
        +    if (this._mat3) {
        +      this._mat3 = nj.identity(3).flatten();
        +    } else if (this._mat4) {
        +      this._mat4 = nj.identity(4).flatten();
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * Replace the entire contents of a 4x4 matrix.
        +   * If providing an array or a MatrixNumjs, the values will be copied without
        +   * referencing the source object.
        +   * Can also provide 16 numbers as individual arguments.
        +   *
        +   * @param {MatrixNumjs|Float32Array|Number[]} [inMatrix] the input MatrixNumjs or
        +   *                                     an Array of length 16
        +   * @chainable
        +   */
        +  /**
        +   * @param {Number[]} elements 16 numbers passed by value to avoid
        +   *                                     array copying.
        +   * @chainable
        +   */
        +  set(inMatrix) {
        +    let refArray = [...arguments];
        +    if (inMatrix instanceof MatrixNumjs) {
        +      refArray = inMatrix.mat4;
        +    } else if (isMatrixArray(inMatrix)) {
        +      refArray = inMatrix;
        +    }
        +    if (refArray.length !== 16) {
        +      // p5._friendlyError(
        +      //   `Expected 16 values but received ${refArray.length}.`,
        +      //   "MatrixNumjs.set"
        +      // );
        +      return this;
        +    }
        +    this._mat4 = nj.array(refArray);
        +    return this;
        +  }
        +
        +  setElement(index, value) {
        +    if (this._mat3) {
        +      this._mat3.set(index, value);
        +    }
        +    return this;
        +  }
        +
        +
        +  /**
        +   * Gets a copy of the vector, returns a MatrixNumjs object.
        +   *
        +   * @return {MatrixNumjs} the copy of the MatrixNumjs object
        +   */
        +  get() {
        +    let temp = new MatrixNumjs(this.mat4);
        +    return new MatrixNumjs(this.mat4);
        +  }
        +
        +  /**
        +   * return a copy of this matrix.
        +   * If this matrix is 4x4, a 4x4 matrix with exactly the same entries will be
        +   * generated. The same is true if this matrix is 3x3.
        +   *
        +   * @return {MatrixNumjs}   the result matrix
        +   */
        +  copy() {
        +    if (this._mat3 !== undefined) {
        +      const copied3x3 = new MatrixNumjs(3, this._mat3.tolist());
        +      copied3x3._mat3 = copied3x3._mat3.flatten();
        +      return copied3x3;
        +    }
        +    const copied = new MatrixNumjs(this._mat4.tolist());
        +    // copied.set(this);
        +    copied._mat4 = copied._mat4.flatten();
        +    return copied;
        +  }
        +
        +  /**
        +   * Creates a copy of the current matrix.
        +   *
        +   * @returns {MatrixNumjs} A new matrix that is a copy of the current matrix.
        +   */
        +  clone() {
        +    return this.copy();
        +  }
        +
        +  /**
        +   * return an identity matrix
        +   * @return {MatrixNumjs}   the result matrix
        +   */
        +  static identity(pInst) {
        +    return new MatrixNumjs(pInst);
        +  }
        +
        +  /**
        +   * transpose according to a given matrix
        +   * @param  {MatrixNumjs|Float32Array|Number[]} a  the matrix to be
        +   *                                               based on to transpose
        +   * @chainable
        +   */
        +  transpose(a) {
        +    if (a instanceof MatrixNumjs) {
        +      if (a._mat3) {
        +        this._mat3 = nj.array(a.mat3).reshape(3, 3).transpose().flatten();
        +      } else if (a._mat4) {
        +        this._mat4 = nj.array(a.mat4).reshape(4, 4).transpose().flatten();
        +      }
        +    } else if (isMatrixArray(a)) {
        +      if (a.length === 9) {
        +        let temp3 = new MatrixNumjs(3, a);
        +        this._mat3 = nj.array(temp3.mat3).reshape(3, 3).transpose().flatten();
        +      } else if (a.length === 16) {
        +        let temp4 = new MatrixNumjs(a);
        +        this._mat4 = nj.array(temp4.mat4).reshape(4, 4).transpose().flatten();
        +      }
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * invert  matrix according to a give matrix
        +   * @param  {MatrixNumjs|Float32Array|Number[]} a   the matrix to be
        +   *                                                based on to invert
        +   * @chainable
        +   */
        +  invert(a) {
        +    let a00, a01, a02, a03, a10, a11, a12, a13;
        +    let a20, a21, a22, a23, a30, a31, a32, a33;
        +    if (a instanceof MatrixNumjs) {
        +      a00 = a._mat4.get(0);
        +      a01 = a._mat4.get(1);
        +      a02 = a._mat4.get(2);
        +      a03 = a._mat4.get(3);
        +      a10 = a._mat4.get(4);
        +      a11 = a._mat4.get(5);
        +      a12 = a._mat4.get(6);
        +      a13 = a._mat4.get(7);
        +      a20 = a._mat4.get(8);
        +      a21 = a._mat4.get(9);
        +      a22 = a._mat4.get(10);
        +      a23 = a._mat4.get(11);
        +      a30 = a._mat4.get(12);
        +      a31 = a._mat4.get(13);
        +      a32 = a._mat4.get(14);
        +      a33 = a._mat4.get(15);
        +    } else if (isMatrixArray(a)) {
        +      a00 = a[0];
        +      a01 = a[1];
        +      a02 = a[2];
        +      a03 = a[3];
        +      a10 = a[4];
        +      a11 = a[5];
        +      a12 = a[6];
        +      a13 = a[7];
        +      a20 = a[8];
        +      a21 = a[9];
        +      a22 = a[10];
        +      a23 = a[11];
        +      a30 = a[12];
        +      a31 = a[13];
        +      a32 = a[14];
        +      a33 = a[15];
        +    }
        +    const b00 = a00 * a11 - a01 * a10;
        +    const b01 = a00 * a12 - a02 * a10;
        +    const b02 = a00 * a13 - a03 * a10;
        +    const b03 = a01 * a12 - a02 * a11;
        +    const b04 = a01 * a13 - a03 * a11;
        +    const b05 = a02 * a13 - a03 * a12;
        +    const b06 = a20 * a31 - a21 * a30;
        +    const b07 = a20 * a32 - a22 * a30;
        +    const b08 = a20 * a33 - a23 * a30;
        +    const b09 = a21 * a32 - a22 * a31;
        +    const b10 = a21 * a33 - a23 * a31;
        +    const b11 = a22 * a33 - a23 * a32;
        +
        +    // Calculate the determinant
        +    let det =
        +      b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
        +
        +    if (!det) {
        +      return null;
        +    }
        +    det = 1.0 / det;
        +
        +    this._mat4.set(0, (a11 * b11 - a12 * b10 + a13 * b09) * det);
        +    this._mat4.set(1, (a02 * b10 - a01 * b11 - a03 * b09) * det);
        +    this._mat4.set(2, (a31 * b05 - a32 * b04 + a33 * b03) * det);
        +    this._mat4.set(3, (a22 * b04 - a21 * b05 - a23 * b03) * det);
        +    this._mat4.set(4, (a12 * b08 - a10 * b11 - a13 * b07) * det);
        +    this._mat4.set(5, (a00 * b11 - a02 * b08 + a03 * b07) * det);
        +    this._mat4.set(6, (a32 * b02 - a30 * b05 - a33 * b01) * det);
        +    this._mat4.set(7, (a20 * b05 - a22 * b02 + a23 * b01) * det);
        +    this._mat4.set(8, (a10 * b10 - a11 * b08 + a13 * b06) * det);
        +    this._mat4.set(9, (a01 * b08 - a00 * b10 - a03 * b06) * det);
        +    this._mat4.set(10, (a30 * b04 - a31 * b02 + a33 * b00) * det);
        +    this._mat4.set(11, (a21 * b02 - a20 * b04 - a23 * b00) * det);
        +    this._mat4.set(12, (a11 * b07 - a10 * b09 - a12 * b06) * det);
        +    this._mat4.set(13, (a00 * b09 - a01 * b07 + a02 * b06) * det);
        +    this._mat4.set(14, (a31 * b01 - a30 * b03 - a32 * b00) * det);
        +    this._mat4.set(15, (a20 * b03 - a21 * b01 + a22 * b00) * det);
        +
        +    return this;
        +  }
        +
        +  /**
        +   * Inverts a 3×3 matrix
        +   * @chainable
        +   */
        +  invert3x3() {
        +    const a00 = this._mat3.get(0);
        +    const a01 = this._mat3.get(1);
        +    const a02 = this._mat3.get(2);
        +    const a10 = this._mat3.get(3);
        +    const a11 = this._mat3.get(4);
        +    const a12 = this._mat3.get(5);
        +    const a20 = this._mat3.get(6);
        +    const a21 = this._mat3.get(7);
        +    const a22 = this._mat3.get(8);
        +    const b01 = a22 * a11 - a12 * a21;
        +    const b11 = -a22 * a10 + a12 * a20;
        +    const b21 = a21 * a10 - a11 * a20;
        +
        +    // Calculate the determinant
        +    let det = a00 * b01 + a01 * b11 + a02 * b21;
        +    if (!det) {
        +      return null;
        +    }
        +    det = 1.0 / det;
        +    this._mat3.set(0, b01 * det);
        +    this._mat3.set(1, (-a22 * a01 + a02 * a21) * det);
        +    this._mat3.set(2, (a12 * a01 - a02 * a11) * det);
        +    this._mat3.set(3, b11 * det);
        +    this._mat3.set(4, (a22 * a00 - a02 * a20) * det);
        +    this._mat3.set(5, (-a12 * a00 + a02 * a10) * det);
        +    this._mat3.set(6, b21 * det);
        +    this._mat3.set(7, (-a21 * a00 + a01 * a20) * det);
        +    this._mat3.set(8, (a11 * a00 - a01 * a10) * det);
        +    return this;
        +  }
        +
        +  /**
        +   * This function is only for 3x3 matrices.
        +   * transposes a 3×3 MatrixNumjs by a mat3
        +   * If there is an array of arguments, the matrix obtained by transposing
        +   * the 3x3 matrix generated based on that array is set.
        +   * If no arguments, it transposes itself and returns it.
        +   *
        +   * @param  {Number[]} mat3 1-dimensional array
        +   * @chainable
        +   */
        +  transpose3x3(mat3) {
        +    if (mat3 === undefined) {
        +      mat3 = this._mat3;
        +      this._mat3 = this._mat3.reshape(3, 3).transpose().flatten();
        +    } else {
        +      const temp = new MatrixNumjs(3, mat3);
        +      temp._mat3 = temp._mat3.reshape(3, 3).transpose().flatten();
        +      this._mat3 = temp._mat3;
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * converts a 4×4 matrix to its 3×3 inverse transform
        +   * commonly used in MVMatrix to NMatrix conversions.
        +   * @param  {MatrixNumjs} mat4 the matrix to be based on to invert
        +   * @chainable
        +   * @todo  finish implementation
        +   */
        +  inverseTranspose({ mat4 }) {
        +    if (this._mat3 === undefined) {
        +      // p5._friendlyError("sorry, this function only works with mat3");
        +    } else {
        +      //convert mat4 -> mat3
        +      this._mat3 = this._mat3.flatten();
        +      this._mat3.set(0, mat4[0]);
        +      this._mat3.set(1, mat4[1]);
        +      this._mat3.set(2, mat4[2]);
        +      this._mat3.set(3, mat4[4]);
        +      this._mat3.set(4, mat4[5]);
        +      this._mat3.set(5, mat4[6]);
        +      this._mat3.set(6, mat4[8]);
        +      this._mat3.set(7, mat4[9]);
        +      this._mat3.set(8, mat4[10]);
        +    }
        +
        +    const inverse = this.invert3x3();
        +    // check inverse succeeded
        +    if (inverse) {
        +      inverse.transpose3x3(this._mat3);
        +    } else {
        +      // in case of singularity, just zero the matrix
        +      for (let i = 0; i < 9; i++) {
        +        this._mat3.set(i, 0);
        +      }
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * inspired by Toji's mat4 determinant
        +   * @return {Number} Determinant of our 4×4 matrix
        +   */
        +  determinant() {
        +    const d00 =
        +        this._mat4.get(0) * this._mat4.get(5) -
        +        this._mat4.get(1) * this._mat4.get(4),
        +      d01 =
        +        this._mat4.get(0) * this._mat4.get(6) -
        +        this._mat4.get(2) * this._mat4.get(4),
        +      d02 =
        +        this._mat4.get(0) * this._mat4.get(7) -
        +        this._mat4.get(3) * this._mat4.get(4),
        +      d03 =
        +        this._mat4.get(1) * this._mat4.get(6) -
        +        this._mat4.get(2) * this._mat4.get(5),
        +      d04 =
        +        this._mat4.get(1) * this._mat4.get(7) -
        +        this._mat4.get(3) * this._mat4.get(5),
        +      d05 =
        +        this._mat4.get(2) * this._mat4.get(7) -
        +        this._mat4.get(3) * this._mat4.get(6),
        +      d06 =
        +        this._mat4.get(8) * this._mat4.get(13) -
        +        this._mat4.get(9) * this._mat4.get(12),
        +      d07 =
        +        this._mat4.get(8) * this._mat4.get(14) -
        +        this._mat4.get(10) * this._mat4.get(12),
        +      d08 =
        +        this._mat4.get(8) * this._mat4.get(15) -
        +        this._mat4.get(11) * this._mat4.get(12),
        +      d09 =
        +        this._mat4.get(9) * this._mat4.get(14) -
        +        this._mat4.get(10) * this._mat4.get(13),
        +      d10 =
        +        this._mat4.get(9) * this._mat4.get(15) -
        +        this._mat4.get(11) * this._mat4.get(13),
        +      d11 =
        +        this._mat4.get(10) * this._mat4.get(15) -
        +        this._mat4.get(11) * this._mat4.get(14);
        +
        +    // Calculate the determinant
        +    return (
        +      d00 * d11 - d01 * d10 + d02 * d09 + d03 * d08 - d04 * d07 + d05 * d06
        +    );
        +  }
        +
        +  /**
        +   * multiply two mat4s
        +   * @param {MatrixNumjs|Float32Array|Number[]} multMatrix The matrix
        +   *                                                we want to multiply by
        +   * @chainable
        +   */
        +  mult(multMatrix) {
        +    if (isMatrixArray(multMatrix)) {
        +      multMatrix = new MatrixNumjs(multMatrix);
        +    }
        +    if (this._mat3 !== undefined) {
        +      let a = this._mat3.reshape(3, 3);
        +      a = a.dot(multMatrix._mat3?.reshape(3, 3)).flatten();
        +      this._mat3 = a;
        +    } else if (this._mat4 !== undefined) {
        +      let a = this._mat4.reshape(4, 4);
        +      a = a.dot(multMatrix._mat4?.reshape(4, 4)).flatten();
        +      this._mat4 = a;
        +    }
        +    return this;
        +  }
        +
        +  apply(multMatrix) {
        +    let _src;
        +
        +    if (multMatrix === this || multMatrix === this._mat4) {
        +      _src = this.copy().mat4; // only need to allocate in this rare case
        +    } else if (multMatrix instanceof MatrixNumjs) {
        +      _src = multMatrix.mat4;
        +    } else if (isMatrixArray(multMatrix)) {
        +      _src = multMatrix;
        +    } else if (arguments.length === 16) {
        +      _src = arguments;
        +    } else {
        +      return; // nothing to do.
        +    }
        +
        +    const mat4 = this._mat4.tolist();
        +
        +    // each row is used for the multiplier
        +    const m0 = mat4[0];
        +    const m4 = mat4[4];
        +    const m8 = mat4[8];
        +    const m12 = mat4[12];
        +    mat4[0] = _src[0] * m0 + _src[1] * m4 + _src[2] * m8 + _src[3] * m12;
        +    mat4[4] = _src[4] * m0 + _src[5] * m4 + _src[6] * m8 + _src[7] * m12;
        +    mat4[8] = _src[8] * m0 + _src[9] * m4 + _src[10] * m8 + _src[11] * m12;
        +    mat4[12] = _src[12] * m0 + _src[13] * m4 + _src[14] * m8 + _src[15] * m12;
        +
        +    const m1 = mat4[1];
        +    const m5 = mat4[5];
        +    const m9 = mat4[9];
        +    const m13 = mat4[13];
        +    mat4[1] = _src[0] * m1 + _src[1] * m5 + _src[2] * m9 + _src[3] * m13;
        +    mat4[5] = _src[4] * m1 + _src[5] * m5 + _src[6] * m9 + _src[7] * m13;
        +    mat4[9] = _src[8] * m1 + _src[9] * m5 + _src[10] * m9 + _src[11] * m13;
        +    mat4[13] = _src[12] * m1 + _src[13] * m5 + _src[14] * m9 + _src[15] * m13;
        +
        +    const m2 = mat4[2];
        +    const m6 = mat4[6];
        +    const m10 = mat4[10];
        +    const m14 = mat4[14];
        +    mat4[2] = _src[0] * m2 + _src[1] * m6 + _src[2] * m10 + _src[3] * m14;
        +    mat4[6] = _src[4] * m2 + _src[5] * m6 + _src[6] * m10 + _src[7] * m14;
        +    mat4[10] = _src[8] * m2 + _src[9] * m6 + _src[10] * m10 + _src[11] * m14;
        +    mat4[14] = _src[12] * m2 + _src[13] * m6 + _src[14] * m10 + _src[15] * m14;
        +
        +    const m3 = mat4[3];
        +    const m7 = mat4[7];
        +    const m11 = mat4[11];
        +    const m15 = mat4[15];
        +    mat4[3] = _src[0] * m3 + _src[1] * m7 + _src[2] * m11 + _src[3] * m15;
        +    mat4[7] = _src[4] * m3 + _src[5] * m7 + _src[6] * m11 + _src[7] * m15;
        +    mat4[11] = _src[8] * m3 + _src[9] * m7 + _src[10] * m11 + _src[11] * m15;
        +    mat4[15] = _src[12] * m3 + _src[13] * m7 + _src[14] * m11 + _src[15] * m15;
        +    this._mat4 = nj.array(mat4);
        +    return this;
        +  }
        +
        +  /**
        +   * scales a MatrixNumjs by scalars or a vector
        +   * @param  {Vector|Float32Array|Number[]} s vector to scale by
        +   * @chainable
        +   */
        +  scale(x, y, z) {
        +    if (x instanceof Vector) {
        +      // x is a vector, extract the components from it.
        +      y = x.y;
        +      z = x.z;
        +      x = x.x; // must be last
        +    } else if (x instanceof Array) {
        +      // x is an array, extract the components from it.
        +      y = x[1];
        +      z = x[2];
        +      x = x[0]; // must be last
        +    }
        +    this._mat4 = this._mat4.flatten();
        +    const vect = nj.array([x, y, z, 1]);
        +    this._mat4.set(0, x * this._mat4.get(0));
        +    this._mat4.set(1, x * this._mat4.get(1));
        +    this._mat4.set(2, x * this._mat4.get(2));
        +    this._mat4.set(3, x * this._mat4.get(3));
        +    this._mat4.set(4, y * this._mat4.get(4));
        +    this._mat4.set(5, y * this._mat4.get(5));
        +    this._mat4.set(6, y * this._mat4.get(6));
        +    this._mat4.set(7, y * this._mat4.get(7));
        +    this._mat4.set(8, z * this._mat4.get(8));
        +    this._mat4.set(9, z * this._mat4.get(9));
        +    this._mat4.set(10, z * this._mat4.get(10));
        +    this._mat4.set(11, z * this._mat4.get(11));
        +    return this;
        +  }
        +
        +  /**
        +   * rotate our Matrix around an axis by the given angle.
        +   * @param  {Number} a The angle of rotation in radians
        +   * @param  {Vector|Number[]} axis  the axis(es) to rotate around
        +   * @chainable
        +   * inspired by Toji's gl-matrix lib, mat4 rotation
        +   */
        +  rotate(a, x, y, z) {
        +    if (x instanceof Vector) {
        +      // x is a vector, extract the components from it.
        +      y = x.y;
        +      z = x.z;
        +      x = x.x; //must be last
        +    } else if (x instanceof Array) {
        +      // x is an array, extract the components from it.
        +      y = x[1];
        +      z = x[2];
        +      x = x[0]; //must be last
        +    }
        +
        +    const len = Math.sqrt(x * x + y * y + z * z);
        +    x *= 1 / len;
        +    y *= 1 / len;
        +    z *= 1 / len;
        +
        +    // const aMat = this._mat4.reshape(4,4)
        +    this._mat4 = this._mat4.flatten();
        +    const a00 = this._mat4.get(0);
        +    const a01 = this._mat4.get(1);
        +    const a02 = this._mat4.get(2);
        +    const a03 = this._mat4.get(3);
        +    const a10 = this._mat4.get(4);
        +    const a11 = this._mat4.get(5);
        +    const a12 = this._mat4.get(6);
        +    const a13 = this._mat4.get(7);
        +    const a20 = this._mat4.get(8);
        +    const a21 = this._mat4.get(9);
        +    const a22 = this._mat4.get(10);
        +    const a23 = this._mat4.get(11);
        +
        +    //sin,cos, and tan of respective angle
        +    const sA = Math.sin(a);
        +    const cA = Math.cos(a);
        +    const tA = 1 - cA;
        +    // Construct the elements of the rotation matrix
        +    const b00 = x * x * tA + cA;
        +    const b01 = y * x * tA + z * sA;
        +    const b02 = z * x * tA - y * sA;
        +
        +    const b10 = x * y * tA - z * sA;
        +    const b11 = y * y * tA + cA;
        +    const b12 = z * y * tA + x * sA;
        +
        +    const b20 = x * z * tA + y * sA;
        +    const b21 = y * z * tA - x * sA;
        +    const b22 = z * z * tA + cA;
        +
        +    // rotation-specific matrix multiplication
        +    this._mat4.set(0, a00 * b00 + a10 * b01 + a20 * b02);
        +    this._mat4.set(1, a01 * b00 + a11 * b01 + a21 * b02);
        +    this._mat4.set(2, a02 * b00 + a12 * b01 + a22 * b02);
        +    this._mat4.set(3, a03 * b00 + a13 * b01 + a23 * b02);
        +    this._mat4.set(4, a00 * b10 + a10 * b11 + a20 * b12);
        +    this._mat4.set(5, a01 * b10 + a11 * b11 + a21 * b12);
        +    this._mat4.set(6, a02 * b10 + a12 * b11 + a22 * b12);
        +    this._mat4.set(7, a03 * b10 + a13 * b11 + a23 * b12);
        +    this._mat4.set(8, a00 * b20 + a10 * b21 + a20 * b22);
        +    this._mat4.set(9, a01 * b20 + a11 * b21 + a21 * b22);
        +    this._mat4.set(10, a02 * b20 + a12 * b21 + a22 * b22);
        +    this._mat4.set(11, a03 * b20 + a13 * b21 + a23 * b22);
        +    return this;
        +  }
        +
        +  /**
        +   * @todo  finish implementing this method!
        +   * translates
        +   * @param  {Number[]} v vector to translate by
        +   * @chainable
        +   */
        +  translate(v) {
        +    const x = v[0],
        +      y = v[1],
        +      z = v[2] || 0;
        +    this._mat4 = this._mat4.flatten();
        +    this._mat4.set(
        +      12,
        +      this._mat4.get(0) * x +
        +        this._mat4.get(4) * y +
        +        this._mat4.get(8) * z +
        +        this._mat4.get(12)
        +    );
        +    this._mat4.set(
        +      13,
        +      this._mat4.get(1) * x +
        +        this._mat4.get(5) * y +
        +        this._mat4.get(9) * z +
        +        this._mat4.get(13)
        +    );
        +    this._mat4.set(
        +      14,
        +      this._mat4.get(2) * x +
        +        this._mat4.get(6) * y +
        +        this._mat4.get(10) * z +
        +        this._mat4.get(14)
        +    );
        +    this._mat4.set(
        +      15,
        +      this._mat4.get(3) * x +
        +        this._mat4.get(7) * y +
        +        this._mat4.get(11) * z +
        +        this._mat4.get(15)
        +    );
        +  }
        +
        +  rotateX(a) {
        +    this.rotate(a, 1, 0, 0);
        +  }
        +  rotateY(a) {
        +    this.rotate(a, 0, 1, 0);
        +  }
        +  rotateZ(a) {
        +    this.rotate(a, 0, 0, 1);
        +  }
        +
        +  /**
        +   * sets the perspective matrix
        +   * @param  {Number} fovy   [description]
        +   * @param  {Number} aspect [description]
        +   * @param  {Number} near   near clipping plane
        +   * @param  {Number} far    far clipping plane
        +   * @chainable
        +   */
        +  perspective(fovy, aspect, near, far) {
        +    const f = 1.0 / Math.tan(fovy / 2),
        +      nf = 1 / (near - far);
        +
        +    this._mat4 = this._mat4.flatten();
        +    this._mat4.set(0, f / aspect);
        +    this._mat4.set(1, 0);
        +    this._mat4.set(2, 0);
        +    this._mat4.set(3, 0);
        +    this._mat4.set(4, 0);
        +    this._mat4.set(5, f);
        +    this._mat4.set(6, 0);
        +    this._mat4.set(7, 0);
        +    this._mat4.set(8, 0);
        +    this._mat4.set(9, 0);
        +    this._mat4.set(10, (far + near) * nf);
        +    this._mat4.set(11, -1);
        +    this._mat4.set(12, 0);
        +    this._mat4.set(13, 0);
        +    this._mat4.set(14, 2 * far * near * nf);
        +    this._mat4.set(15, 0);
        +
        +    return this;
        +  }
        +
        +  /**
        +   * sets the ortho matrix
        +   * @param  {Number} left   [description]
        +   * @param  {Number} right  [description]
        +   * @param  {Number} bottom [description]
        +   * @param  {Number} top    [description]
        +   * @param  {Number} near   near clipping plane
        +   * @param  {Number} far    far clipping plane
        +   * @chainable
        +   */
        +  ortho(left, right, bottom, top, near, far) {
        +    const lr = 1 / (left - right),
        +      bt = 1 / (bottom - top),
        +      nf = 1 / (near - far);
        +    this._mat4 = this._mat4.flatten();
        +    this._mat4.set(0, -2 * lr);
        +    this._mat4.set(1, 0);
        +    this._mat4.set(2, 0);
        +    this._mat4.set(3, 0);
        +    this._mat4.set(4, 0);
        +    this._mat4.set(5, -2 * bt);
        +    this._mat4.set(6, 0);
        +    this._mat4.set(7, 0);
        +    this._mat4.set(8, 0);
        +    this._mat4.set(9, 0);
        +    this._mat4.set(10, 2 * nf);
        +    this._mat4.set(11, 0);
        +    this._mat4.set(12, (left + right) * lr);
        +    this._mat4.set(13, (top + bottom) * bt);
        +    this._mat4.set(14, (far + near) * nf);
        +    this._mat4.set(15, 1);
        +
        +    return this;
        +  }
        +
        +  /**
        +   * apply a matrix to a vector with x,y,z,w components
        +   * get the results in the form of an array
        +   * @param {Number}
        +   * @return {Number[]}
        +   */
        +  multiplyVec4(x, y, z, w) {
        +    const result = new Array(4);
        +    const m = this._mat4;
        +
        +    result[0] = m.get(0) * x + m.get(4) * y + m.get(8) * z + m.get(12) * w;
        +    result[1] = m.get(1) * x + m.get(5) * y + m.get(9) * z + m.get(13) * w;
        +    result[2] = m.get(2) * x + m.get(6) * y + m.get(10) * z + m.get(14) * w;
        +    result[3] = m.get(3) * x + m.get(7) * y + m.get(11) * z + m.get(15) * w;
        +
        +    return result;
        +  }
        +
        +  /**
        +   * Applies a matrix to a vector.
        +   * The fourth component is set to 1.
        +   * Returns a vector consisting of the first
        +   * through third components of the result.
        +   *
        +   * @param {Vector}
        +   * @return {Vector}
        +   */
        +  multiplyPoint({ x, y, z }) {
        +    const array = this.multiplyVec4(x, y, z, 1);
        +    return new Vector(array[0], array[1], array[2]);
        +  }
        +
        +  /**
        +   * Applies a matrix to a vector.
        +   * The fourth component is set to 1.
        +   * Returns the result of dividing the 1st to 3rd components
        +   * of the result by the 4th component as a vector.
        +   *
        +   * @param {Vector}
        +   * @return {Vector}
        +   */
        +  multiplyAndNormalizePoint({ x, y, z }) {
        +    const array = this.multiplyVec4(x, y, z, 1);
        +    array[0] /= array[3];
        +    array[1] /= array[3];
        +    array[2] /= array[3];
        +    return new Vector(array[0], array[1], array[2]);
        +  }
        +
        +  /**
        +   * Applies a matrix to a vector.
        +   * The fourth component is set to 0.
        +   * Returns a vector consisting of the first
        +   * through third components of the result.
        +   *
        +   * @param {Vector}
        +   * @return {Vector}
        +   */
        +  multiplyDirection({ x, y, z }) {
        +    const array = this.multiplyVec4(x, y, z, 0);
        +    return new Vector(array[0], array[1], array[2]);
        +  }
        +
        +  /**
        +   * This function is only for 3x3 matrices.
        +   * multiply two mat3s. It is an operation to multiply the 3x3 matrix of
        +   * the argument from the right. Arguments can be a 3x3 MatrixNumjs,
        +   * a Float32Array of length 9, or a javascript array of length 9.
        +   * In addition, it can also be done by enumerating 9 numbers.
        +   *
        +   * @param {MatrixNumjs|Float32Array|Number[]} multMatrix The matrix
        +   *                                                we want to multiply by
        +   * @chainable
        +   */
        +  mult3x3(multMatrix) {
        +    let _src;
        +    let tempMatrix = multMatrix;
        +    if (multMatrix === this || multMatrix === this._mat3) {
        +      // mat3; // only need to allocate in this rare case
        +    } else if (multMatrix instanceof MatrixNumjs) {
        +      _src = multMatrix.mat3;
        +    } else if (isMatrixArray(multMatrix)) {
        +      multMatrix._mat3 = nj.array(arguments);
        +    } else if (arguments.length === 9) {
        +      tempMatrix = new MatrixNumjs(3, Array.from(arguments));
        +    } else {
        +      return; // nothing to do.
        +    }
        +    let a = this._mat3.reshape(3, 3);
        +    a = a.dot(tempMatrix._mat3.reshape(3, 3)).flatten();
        +    this._mat3 = a;
        +    return this;
        +  }
        +
        +  /**
        +   * This function is only for 3x3 matrices.
        +   * A function that returns a column vector of a 3x3 matrix.
        +   *
        +   * @param {Number} columnIndex matrix column number
        +   * @return {Vector}
        +   */
        +  column(columnIndex) {
        +    // let temp = this._mat3.reshape(3,3)
        +    let vect = new Vector(
        +      this._mat3.tolist()[3 * columnIndex],
        +      this._mat3.tolist()[3 * columnIndex + 1],
        +      this._mat3.tolist()[3 * columnIndex + 2]
        +    );
        +    return vect;
        +  }
        +
        +  /**
        +   * This function is only for 3x3 matrices.
        +   * A function that returns a row vector of a 3x3 matrix.
        +   *
        +   * @param {Number} rowIndex matrix row number
        +   * @return {Vector}
        +   */
        +  row(rowIndex) {
        +    return new Vector(
        +      this._mat3.tolist()[rowIndex],
        +      this._mat3.tolist()[rowIndex + 3],
        +      this._mat3.tolist()[rowIndex + 6]
        +    );
        +  }
        +
        +  /**
        +   * Returns the diagonal elements of the matrix in the form of an array.
        +   * A 3x3 matrix will return an array of length 3.
        +   * A 4x4 matrix will return an array of length 4.
        +   *
        +   * @return {Number[]} An array obtained by arranging the diagonal elements
        +   *                    of the matrix in ascending order of index
        +   */
        +  diagonal() {
        +    if (this._mat3 !== undefined) {
        +      return this._mat3.reshape(3, 3).diag().tolist();
        +    }
        +    return this._mat4.reshape(4, 4).diag().tolist();
        +  }
        +
        +  /**
        +   * This function is only for 3x3 matrices.
        +   * Takes a vector and returns the vector resulting from multiplying to
        +   * that vector by this matrix from left.
        +   *
        +   * @param {Vector} multVector the vector to which this matrix applies
        +   * @param {Vector} [target] The vector to receive the result
        +   * @return {Vector}
        +   */
        +  multiplyVec3(multVector, target) {
        +    if (target === undefined) {
        +      target = multVector.copy();
        +    }
        +    target.x = this.row(0).dot(multVector);
        +    target.y = this.row(1).dot(multVector);
        +    target.z = this.row(2).dot(multVector);
        +    return target;
        +  }
        +
        +  /**
        +   * This function is only for 4x4 matrices.
        +   * Creates a 3x3 matrix whose entries are the top left 3x3 part and returns it.
        +   *
        +   * @return {MatrixNumjs}
        +   */
        +  createSubMatrix3x3() {
        +    const result = new MatrixNumjs(3);
        +    result._mat3 = result._mat3.flatten();
        +    result._mat3.set(0, this._mat4.get(0));
        +    result._mat3.set(1, this._mat4.get(1));
        +    result._mat3.set(2, this._mat4.get(2));
        +    result._mat3.set(3, this._mat4.get(4));
        +    result._mat3.set(4, this._mat4.get(5));
        +    result._mat3.set(5, this._mat4.get(6));
        +    result._mat3.set(6, this._mat4.get(8));
        +    result._mat3.set(7, this._mat4.get(9));
        +    result._mat3.set(8, this._mat4.get(10));
        +    return result;
        +  }
        +
        +  /**
        +   * PRIVATE
        +   */
        +  // matrix methods adapted from:
        +  // https://developer.mozilla.org/en-US/docs/Web/WebGL/
        +  // gluPerspective
        +  //
        +  // function _makePerspective(fovy, aspect, znear, zfar){
        +  //    const ymax = znear * Math.tan(fovy * Math.PI / 360.0);
        +  //    const ymin = -ymax;
        +  //    const xmin = ymin * aspect;
        +  //    const xmax = ymax * aspect;
        +  //    return _makeFrustum(xmin, xmax, ymin, ymax, znear, zfar);
        +  //  }
        +
        +  ////
        +  //// glFrustum
        +  ////
        +  //function _makeFrustum(left, right, bottom, top, znear, zfar){
        +  //  const X = 2*znear/(right-left);
        +  //  const Y = 2*znear/(top-bottom);
        +  //  const A = (right+left)/(right-left);
        +  //  const B = (top+bottom)/(top-bottom);
        +  //  const C = -(zfar+znear)/(zfar-znear);
        +  //  const D = -2*zfar*znear/(zfar-znear);
        +  //  const frustrumMatrix =[
        +  //  X, 0, A, 0,
        +  //  0, Y, B, 0,
        +  //  0, 0, C, D,
        +  //  0, 0, -1, 0
        +  //];
        +  //return frustrumMatrix;
        +  // }
        +
        +  // function _setMVPMatrices(){
        +  ////an identity matrix
        +  ////@TODO use the MatrixNumjs class to abstract away our MV matrices and
        +  ///other math
        +  //const _mvMatrix =
        +  //[
        +  //  1.0,0.0,0.0,0.0,
        +  //  0.0,1.0,0.0,0.0,
        +  //  0.0,0.0,1.0,0.0,
        +  //  0.0,0.0,0.0,1.0
        +  //];
        +}
        +
        diff --git a/src/math/calculation.js b/src/math/calculation.js
        index 5ed489190b..5075b8c8ad 100644
        --- a/src/math/calculation.js
        +++ b/src/math/calculation.js
        @@ -5,1100 +5,1118 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        +function calculation(p5, fn){
        +  /**
        +   * Calculates the absolute value of a number.
        +   *
        +   * A number's absolute value is its distance from zero on the number line.
        +   * -5 and 5 are both five units away from zero, so calling `abs(-5)` and
        +   * `abs(5)` both return 5. The absolute value of a number is always positive.
        +   *
        +   * @method abs
        +   * @param  {Number} n number to compute.
        +   * @return {Number}   absolute value of given number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A gray square with a vertical black line that divides it in half. A white rectangle gets taller when the user moves the mouse away from the line.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Divide the canvas.
        +   *   line(50, 0, 50, 100);
        +   *
        +   *   // Calculate the mouse's distance from the middle.
        +   *   let h = abs(mouseX - 50);
        +   *
        +   *   // Draw a rectangle based on the mouse's distance
        +   *   // from the middle.
        +   *   rect(0, 100 - h, 100, h);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.abs = Math.abs;
         
        -/**
        - * Calculates the absolute value of a number.
        - *
        - * A number's absolute value is its distance from zero on the number line.
        - * -5 and 5 are both five units away from zero, so calling `abs(-5)` and
        - * `abs(5)` both return 5. The absolute value of a number is always positive.
        - *
        - * @method abs
        - * @param  {Number} n number to compute.
        - * @return {Number}   absolute value of given number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A gray square with a vertical black line that divides it in half. A white rectangle gets taller when the user moves the mouse away from the line.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Divide the canvas.
        - *   line(50, 0, 50, 100);
        - *
        - *   // Calculate the mouse's distance from the middle.
        - *   let h = abs(mouseX - 50);
        - *
        - *   // Draw a rectangle based on the mouse's distance
        - *   // from the middle.
        - *   rect(0, 100 - h, 100, h);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.abs = Math.abs;
        +  /**
        +   * Calculates the closest integer value that is greater than or equal to a
        +   * number.
        +   *
        +   * For example, calling `ceil(9.03)` and `ceil(9.97)` both return the value
        +   * 10.
        +   *
        +   * @method ceil
        +   * @param  {Number} n number to round up.
        +   * @return {Integer}   rounded up number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use RGB color with values from 0 to 1.
        +   *   colorMode(RGB, 1);
        +   *
        +   *   noStroke();
        +   *
        +   *   // Draw the left rectangle.
        +   *   let r = 0.3;
        +   *   fill(r, 0, 0);
        +   *   rect(0, 0, 50, 100);
        +   *
        +   *   // Round r up to 1.
        +   *   r = ceil(r);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(r, 0, 0);
        +   *   rect(50, 0, 50, 100);
        +   *
        +   *   describe('Two rectangles. The one on the left is dark red and the one on the right is bright red.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.ceil = Math.ceil;
         
        -/**
        - * Calculates the closest integer value that is greater than or equal to a
        - * number.
        - *
        - * For example, calling `ceil(9.03)` and `ceil(9.97)` both return the value
        - * 10.
        - *
        - * @method ceil
        - * @param  {Number} n number to round up.
        - * @return {Integer}   rounded up number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use RGB color with values from 0 to 1.
        - *   colorMode(RGB, 1);
        - *
        - *   noStroke();
        - *
        - *   // Draw the left rectangle.
        - *   let r = 0.3;
        - *   fill(r, 0, 0);
        - *   rect(0, 0, 50, 100);
        - *
        - *   // Round r up to 1.
        - *   r = ceil(r);
        - *
        - *   // Draw the right rectangle.
        - *   fill(r, 0, 0);
        - *   rect(50, 0, 50, 100);
        - *
        - *   describe('Two rectangles. The one on the left is dark red and the one on the right is bright red.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.ceil = Math.ceil;
        +  /**
        +   * Constrains a number between a minimum and maximum value.
        +   *
        +   * @method constrain
        +   * @param  {Number} n    number to constrain.
        +   * @param  {Number} low  minimum limit.
        +   * @param  {Number} high maximum limit.
        +   * @return {Number}      constrained number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A black dot drawn on a gray square follows the mouse from left to right. Its movement is constrained to the middle third of the square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   let x = constrain(mouseX, 33, 67);
        +   *   let y = 50;
        +   *
        +   *   strokeWeight(5);
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Two vertical lines. Two circles move horizontally with the mouse. One circle stops at the vertical lines.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Set boundaries and draw them.
        +   *   let leftWall = 25;
        +   *   let rightWall = 75;
        +   *   line(leftWall, 0, leftWall, 100);
        +   *   line(rightWall, 0, rightWall, 100);
        +   *
        +   *   // Draw a circle that follows the mouse freely.
        +   *   fill(255);
        +   *   circle(mouseX, 33, 9);
        +   *
        +   *   // Draw a circle that's constrained.
        +   *   let xc = constrain(mouseX, leftWall, rightWall);
        +   *   fill(0);
        +   *   circle(xc, 67, 9);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.constrain = function(n, low, high) {
        +    // p5._validateParameters('constrain', arguments);
        +    return Math.max(Math.min(n, high), low);
        +  };
         
        -/**
        - * Constrains a number between a minimum and maximum value.
        - *
        - * @method constrain
        - * @param  {Number} n    number to constrain.
        - * @param  {Number} low  minimum limit.
        - * @param  {Number} high maximum limit.
        - * @return {Number}      constrained number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A black dot drawn on a gray square follows the mouse from left to right. Its movement is constrained to the middle third of the square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   let x = constrain(mouseX, 33, 67);
        - *   let y = 50;
        - *
        - *   strokeWeight(5);
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Two vertical lines. Two circles move horizontally with the mouse. One circle stops at the vertical lines.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Set boundaries and draw them.
        - *   let leftWall = 25;
        - *   let rightWall = 75;
        - *   line(leftWall, 0, leftWall, 100);
        - *   line(rightWall, 0, rightWall, 100);
        - *
        - *   // Draw a circle that follows the mouse freely.
        - *   fill(255);
        - *   circle(mouseX, 33, 9);
        - *
        - *   // Draw a circle that's constrained.
        - *   let xc = constrain(mouseX, leftWall, rightWall);
        - *   fill(0);
        - *   circle(xc, 67, 9);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.constrain = function(n, low, high) {
        -  p5._validateParameters('constrain', arguments);
        -  return Math.max(Math.min(n, high), low);
        -};
        +  /**
        +   * Calculates the distance between two points.
        +   *
        +   * The version of `dist()` with four parameters calculates distance in two
        +   * dimensions.
        +   *
        +   * The version of `dist()` with six parameters calculates distance in three
        +   * dimensions.
        +   *
        +   * Use <a href="#/p5.Vector/dist">p5.Vector.dist()</a> to calculate the
        +   * distance between two <a href="#/p5.Vector">p5.Vector</a> objects.
        +   *
        +   * @method dist
        +   * @param  {Number} x1 x-coordinate of the first point.
        +   * @param  {Number} y1 y-coordinate of the first point.
        +   * @param  {Number} x2 x-coordinate of the second point.
        +   * @param  {Number} y2 y-coordinate of the second point.
        +   * @return {Number}    distance between the two points.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the coordinates.
        +   *   let x1 = 10;
        +   *   let y1 = 50;
        +   *   let x2 = 90;
        +   *   let y2 = 50;
        +   *
        +   *   // Draw the points and a line connecting them.
        +   *   line(x1, y1, x2, y2);
        +   *   strokeWeight(5);
        +   *   point(x1, y1);
        +   *   point(x2, y2);
        +   *
        +   *   // Calculate the distance.
        +   *   let d = dist(x1, y1, x2, y2);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the distance.
        +   *   text(d, 43, 40);
        +   *
        +   *   describe('Two dots connected by a horizontal line. The number 80 is written above the center of the line.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method dist
        +   * @param  {Number} x1
        +   * @param  {Number} y1
        +   * @param  {Number} z1 z-coordinate of the first point.
        +   * @param  {Number} x2
        +   * @param  {Number} y2
        +   * @param  {Number} z2 z-coordinate of the second point.
        +   * @return {Number}    distance between the two points.
        +   */
        +  fn.dist = function(...args) {
        +    // p5._validateParameters('dist', args);
        +    if (args.length === 4) {
        +      //2D
        +      return Math.hypot(args[2] - args[0], args[3] - args[1]);
        +    } else if (args.length === 6) {
        +      //3D
        +      return Math.hypot(
        +        args[3] - args[0], args[4] - args[1], args[5] - args[2]
        +      );
        +    }
        +  };
         
        -/**
        - * Calculates the distance between two points.
        - *
        - * The version of `dist()` with four parameters calculates distance in two
        - * dimensions.
        - *
        - * The version of `dist()` with six parameters calculates distance in three
        - * dimensions.
        - *
        - * Use <a href="#/p5.Vector/dist">p5.Vector.dist()</a> to calculate the
        - * distance between two <a href="#/p5.Vector">p5.Vector</a> objects.
        - *
        - * @method dist
        - * @param  {Number} x1 x-coordinate of the first point.
        - * @param  {Number} y1 y-coordinate of the first point.
        - * @param  {Number} x2 x-coordinate of the second point.
        - * @param  {Number} y2 y-coordinate of the second point.
        - * @return {Number}    distance between the two points.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Set the coordinates.
        - *   let x1 = 10;
        - *   let y1 = 50;
        - *   let x2 = 90;
        - *   let y2 = 50;
        - *
        - *   // Draw the points and a line connecting them.
        - *   line(x1, y1, x2, y2);
        - *   strokeWeight(5);
        - *   point(x1, y1);
        - *   point(x2, y2);
        - *
        - *   // Calculate the distance.
        - *   let d = dist(x1, y1, x2, y2);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the distance.
        - *   text(d, 43, 40);
        - *
        - *   describe('Two dots connected by a horizontal line. The number 80 is written above the center of the line.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method dist
        - * @param  {Number} x1
        - * @param  {Number} y1
        - * @param  {Number} z1 z-coordinate of the first point.
        - * @param  {Number} x2
        - * @param  {Number} y2
        - * @param  {Number} z2 z-coordinate of the second point.
        - * @return {Number}    distance between the two points.
        - */
        -p5.prototype.dist = function(...args) {
        -  p5._validateParameters('dist', args);
        -  if (args.length === 4) {
        -    //2D
        -    return Math.hypot(args[2] - args[0], args[3] - args[1]);
        -  } else if (args.length === 6) {
        -    //3D
        -    return Math.hypot(args[3] - args[0], args[4] - args[1], args[5] - args[2]);
        -  }
        -};
        +  /**
        +   * Calculates the value of Euler's number e (2.71828...) raised to the power
        +   * of a number.
        +   *
        +   * @method exp
        +   * @param  {Number} n exponent to raise.
        +   * @return {Number}   e^n
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Top-left.
        +   *   let d = exp(1);
        +   *   circle(10, 10, d);
        +   *
        +   *   // Left-center.
        +   *   d = exp(2);
        +   *   circle(20, 20, d);
        +   *
        +   *   // Right-center.
        +   *   d = exp(3);
        +   *   circle(40, 40, d);
        +   *
        +   *   // Bottom-right.
        +   *   d = exp(4);
        +   *   circle(80, 80, d);
        +   *
        +   *   describe('A series of circles that grow exponentially from top left to bottom right.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A series of black dots that grow exponentially from left to right.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Invert the y-axis.
        +   *   scale(1, -1);
        +   *   translate(0, -100);
        +   *
        +   *   // Calculate the coordinates.
        +   *   let x = frameCount;
        +   *   let y = 0.005 * exp(x * 0.1);
        +   *
        +   *   // Draw a point.
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.exp = Math.exp;
         
        -/**
        - * Calculates the value of Euler's number e (2.71828...) raised to the power
        - * of a number.
        - *
        - * @method exp
        - * @param  {Number} n exponent to raise.
        - * @return {Number}   e^n
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top-left.
        - *   let d = exp(1);
        - *   circle(10, 10, d);
        - *
        - *   // Left-center.
        - *   d = exp(2);
        - *   circle(20, 20, d);
        - *
        - *   // Right-center.
        - *   d = exp(3);
        - *   circle(40, 40, d);
        - *
        - *   // Bottom-right.
        - *   d = exp(4);
        - *   circle(80, 80, d);
        - *
        - *   describe('A series of circles that grow exponentially from top left to bottom right.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A series of black dots that grow exponentially from left to right.');
        - * }
        - *
        - * function draw() {
        - *   // Invert the y-axis.
        - *   scale(1, -1);
        - *   translate(0, -100);
        - *
        - *   // Calculate the coordinates.
        - *   let x = frameCount;
        - *   let y = 0.005 * exp(x * 0.1);
        - *
        - *   // Draw a point.
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.exp = Math.exp;
        +  /**
        +   * Calculates the closest integer value that is less than or equal to the
        +   * value of a number.
        +   *
        +   * @method floor
        +   * @param  {Number} n number to round down.
        +   * @return {Integer}  rounded down number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use RGB color with values from 0 to 1.
        +   *   colorMode(RGB, 1);
        +   *
        +   *   noStroke();
        +   *
        +   *   // Draw the left rectangle.
        +   *   let r = 0.8;
        +   *   fill(r, 0, 0);
        +   *   rect(0, 0, 50, 100);
        +   *
        +   *   // Round r down to 0.
        +   *   r = floor(r);
        +   *
        +   *   // Draw the right rectangle.
        +   *   fill(r, 0, 0);
        +   *   rect(50, 0, 50, 100);
        +   *
        +   *   describe('Two rectangles. The one on the left is bright red and the one on the right is black.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.floor = Math.floor;
         
        -/**
        - * Calculates the closest integer value that is less than or equal to the
        - * value of a number.
        - *
        - * @method floor
        - * @param  {Number} n number to round down.
        - * @return {Integer}  rounded down number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use RGB color with values from 0 to 1.
        - *   colorMode(RGB, 1);
        - *
        - *   noStroke();
        - *
        - *   // Draw the left rectangle.
        - *   let r = 0.8;
        - *   fill(r, 0, 0);
        - *   rect(0, 0, 50, 100);
        - *
        - *   // Round r down to 0.
        - *   r = floor(r);
        - *
        - *   // Draw the right rectangle.
        - *   fill(r, 0, 0);
        - *   rect(50, 0, 50, 100);
        - *
        - *   describe('Two rectangles. The one on the left is bright red and the one on the right is black.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.floor = Math.floor;
        +  /**
        +   * Calculates a number between two numbers at a specific increment.
        +   *
        +   * The `amt` parameter is the amount to interpolate between the two numbers.
        +   * 0.0 is equal to the first number, 0.1 is very near the first number, 0.5 is
        +   * half-way in between, and 1.0 is equal to the second number. The `lerp()`
        +   * function is convenient for creating motion along a straight path and for
        +   * drawing dotted lines.
        +   *
        +   * If the value of `amt` is less than 0 or more than 1, `lerp()` will return a
        +   * number outside of the original interval. For example, calling
        +   * `lerp(0, 10, 1.5)` will return 15.
        +   *
        +   * @method lerp
        +   * @param  {Number} start first value.
        +   * @param  {Number} stop  second value.
        +   * @param  {Number} amt   number.
        +   * @return {Number}       lerped value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Declare variables for coordinates.
        +   *   let a = 20;
        +   *   let b = 80;
        +   *   let c = lerp(a, b, 0.2);
        +   *   let d = lerp(a, b, 0.5);
        +   *   let e = lerp(a, b, 0.8);
        +   *
        +   *   strokeWeight(5);
        +   *
        +   *   // Draw the original points in black.
        +   *   stroke(0);
        +   *   point(a, 50);
        +   *   point(b, 50);
        +   *
        +   *   // Draw the lerped points in gray.
        +   *   stroke(100);
        +   *   point(c, 50);
        +   *   point(d, 50);
        +   *   point(e, 50);
        +   *
        +   *   describe('Five points in a horizontal line. The outer points are black and the inner points are gray.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let x = 50;
        +   * let y = 50;
        +   * let targetX = 50;
        +   * let targetY = 50;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A white circle at the center of a gray canvas. The circle moves to where the user clicks, then moves smoothly back to the center.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(220);
        +   *
        +   *   // Move x and y toward the target.
        +   *   x = lerp(x, targetX, 0.05);
        +   *   y = lerp(y, targetY, 0.05);
        +   *
        +   *   // Draw the circle.
        +   *   circle(x, y, 20);
        +   * }
        +   *
        +   * // Set x and y when the user clicks the mouse.
        +   * function mouseClicked() {
        +   *   x = mouseX;
        +   *   y = mouseY;
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.lerp = function(start, stop, amt) {
        +    // p5._validateParameters('lerp', arguments);
        +    return amt * (stop - start) + start;
        +  };
         
        -/**
        - * Calculates a number between two numbers at a specific increment.
        - *
        - * The `amt` parameter is the amount to interpolate between the two numbers.
        - * 0.0 is equal to the first number, 0.1 is very near the first number, 0.5 is
        - * half-way in between, and 1.0 is equal to the second number. The `lerp()`
        - * function is convenient for creating motion along a straight path and for
        - * drawing dotted lines.
        - *
        - * If the value of `amt` is less than 0 or more than 1, `lerp()` will return a
        - * number outside of the original interval. For example, calling
        - * `lerp(0, 10, 1.5)` will return 15.
        - *
        - * @method lerp
        - * @param  {Number} start first value.
        - * @param  {Number} stop  second value.
        - * @param  {Number} amt   number.
        - * @return {Number}       lerped value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Declare variables for coordinates.
        - *   let a = 20;
        - *   let b = 80;
        - *   let c = lerp(a, b, 0.2);
        - *   let d = lerp(a, b, 0.5);
        - *   let e = lerp(a, b, 0.8);
        - *
        - *   strokeWeight(5);
        - *
        - *   // Draw the original points in black.
        - *   stroke(0);
        - *   point(a, 50);
        - *   point(b, 50);
        - *
        - *   // Draw the lerped points in gray.
        - *   stroke(100);
        - *   point(c, 50);
        - *   point(d, 50);
        - *   point(e, 50);
        - *
        - *   describe('Five points in a horizontal line. The outer points are black and the inner points are gray.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let x = 50;
        - * let y = 50;
        - * let targetX = 50;
        - * let targetY = 50;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A white circle at the center of a gray canvas. The circle moves to where the user clicks, then moves smoothly back to the center.');
        - * }
        - *
        - * function draw() {
        - *   background(220);
        - *
        - *   // Move x and y toward the target.
        - *   x = lerp(x, targetX, 0.05);
        - *   y = lerp(y, targetY, 0.05);
        - *
        - *   // Draw the circle.
        - *   circle(x, y, 20);
        - * }
        - *
        - * // Set x and y when the user clicks the mouse.
        - * function mouseClicked() {
        - *   x = mouseX;
        - *   y = mouseY;
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.lerp = function(start, stop, amt) {
        -  p5._validateParameters('lerp', arguments);
        -  return amt * (stop - start) + start;
        -};
        +  /**
        +   * Calculates the natural logarithm (the base-e logarithm) of a number.
        +   *
        +   * `log()` expects the `n` parameter to be a value greater than 0 because
        +   * the natural logarithm is defined that way.
        +   *
        +   * @method log
        +   * @param  {Number} n number greater than 0.
        +   * @return {Number}   natural logarithm of n.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Top-left.
        +   *   let d = log(50);
        +   *   circle(33, 33, d);
        +   *
        +   *   // Bottom-right.
        +   *   d = log(500000000);
        +   *   circle(67, 67, d);
        +   *
        +   *   describe('Two white circles. The circle at the top-left is small. The circle at the bottom-right is about five times larger.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A series of black dots that get higher slowly from left to right.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Invert the y-axis.
        +   *   scale(1, -1);
        +   *   translate(0, -100);
        +   *
        +   *   // Calculate coordinates.
        +   *   let x = frameCount;
        +   *   let y = 15 * log(x);
        +   *
        +   *   // Draw a point.
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.log = Math.log;
         
        -/**
        - * Calculates the natural logarithm (the base-e logarithm) of a number.
        - *
        - * `log()` expects the `n` parameter to be a value greater than 0 because
        - * the natural logarithm is defined that way.
        - *
        - * @method log
        - * @param  {Number} n number greater than 0.
        - * @return {Number}   natural logarithm of n.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top-left.
        - *   let d = log(50);
        - *   circle(33, 33, d);
        - *
        - *   // Bottom-right.
        - *   d = log(500000000);
        - *   circle(67, 67, d);
        - *
        - *   describe('Two white circles. The circle at the top-left is small. The circle at the bottom-right is about five times larger.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A series of black dots that get higher slowly from left to right.');
        - * }
        - *
        - * function draw() {
        - *   // Invert the y-axis.
        - *   scale(1, -1);
        - *   translate(0, -100);
        - *
        - *   // Calculate coordinates.
        - *   let x = frameCount;
        - *   let y = 15 * log(x);
        - *
        - *   // Draw a point.
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.log = Math.log;
        +  /**
        +   * Calculates the magnitude, or length, of a vector.
        +   *
        +   * A vector can be thought of in different ways. In one view, a vector is a
        +   * point in space. The vector's components, `x` and `y`, are the point's
        +   * coordinates `(x, y)`. A vector's magnitude is the distance from the origin
        +   * `(0, 0)` to `(x, y)`. `mag(x, y)` is a shortcut for calling
        +   * `dist(0, 0, x, y)`.
        +   *
        +   * A vector can also be thought of as an arrow pointing in space. This view is
        +   * helpful for programming motion. See <a href="#/p5.Vector">p5.Vector</a> for
        +   * more details.
        +   *
        +   * Use <a href="#/p5.Vector/mag">p5.Vector.mag()</a> to calculate the
        +   * magnitude of a <a href="#/p5.Vector">p5.Vector</a> object.
        +   *
        +   * @method mag
        +   * @param  {Number} x first component.
        +   * @param  {Number} y second component.
        +   * @return {Number}   magnitude of vector.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the vector's components.
        +   *   let x = 30;
        +   *   let y = 40;
        +   *
        +   *   // Calculate the magnitude.
        +   *   let m = mag(x, y);
        +   *
        +   *   // Style the text.
        +   *   textSize(16);
        +   *
        +   *   // Display the vector and its magnitude.
        +   *   line(0, 0, x, y);
        +   *   text(m, x, y);
        +   *
        +   *   describe('A diagonal line is drawn from the top left of the canvas. The number 50 is written at the end of the line.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.mag = function(x, y) {
        +    // p5._validateParameters('mag', arguments);
        +    return Math.hypot(x, y);
        +  };
         
        -/**
        - * Calculates the magnitude, or length, of a vector.
        - *
        - * A vector can be thought of in different ways. In one view, a vector is a
        - * point in space. The vector's components, `x` and `y`, are the point's
        - * coordinates `(x, y)`. A vector's magnitude is the distance from the origin
        - * `(0, 0)` to `(x, y)`. `mag(x, y)` is a shortcut for calling
        - * `dist(0, 0, x, y)`.
        - *
        - * A vector can also be thought of as an arrow pointing in space. This view is
        - * helpful for programming motion. See <a href="#/p5.Vector">p5.Vector</a> for
        - * more details.
        - *
        - * Use <a href="#/p5.Vector/mag">p5.Vector.mag()</a> to calculate the
        - * magnitude of a <a href="#/p5.Vector">p5.Vector</a> object.
        - *
        - * @method mag
        - * @param  {Number} x first component.
        - * @param  {Number} y second component.
        - * @return {Number}   magnitude of vector.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Set the vector's components.
        - *   let x = 30;
        - *   let y = 40;
        - *
        - *   // Calculate the magnitude.
        - *   let m = mag(x, y);
        - *
        - *   // Style the text.
        - *   textSize(16);
        - *
        - *   // Display the vector and its magnitude.
        - *   line(0, 0, x, y);
        - *   text(m, x, y);
        - *
        - *   describe('A diagonal line is drawn from the top left of the canvas. The number 50 is written at the end of the line.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.mag = function(x, y) {
        -  p5._validateParameters('mag', arguments);
        -  return Math.hypot(x, y);
        -};
        +  /**
        +   * Re-maps a number from one range to another.
        +   *
        +   * For example, calling `map(2, 0, 10, 0, 100)` returns 20. The first three
        +   * arguments set the original value to 2 and the original range from 0 to 10.
        +   * The last two arguments set the target range from 0 to 100. 20's position
        +   * in the target range [0, 100] is proportional to 2's position in the
        +   * original range [0, 10].
        +   *
        +   * The sixth parameter, `withinBounds`, is optional. By default, `map()` can
        +   * return values outside of the target range. For example,
        +   * `map(11, 0, 10, 0, 100)` returns 110. Passing `true` as the sixth parameter
        +   * constrains the remapped value to the target range. For example,
        +   * `map(11, 0, 10, 0, 100, true)` returns 100.
        +   *
        +   * @method map
        +   * @param  {Number} value  the value to be remapped.
        +   * @param  {Number} start1 lower bound of the value's current range.
        +   * @param  {Number} stop1  upper bound of the value's current range.
        +   * @param  {Number} start2 lower bound of the value's target range.
        +   * @param  {Number} stop2  upper bound of the value's target range.
        +   * @param  {Boolean} [withinBounds] constrain the value to the newly mapped range.
        +   * @return {Number}        remapped number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Two horizontal lines. The top line grows horizontally as the mouse moves to the right. The bottom line also grows horizontally but is scaled to stay on the left half of the canvas.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the top line.
        +   *   line(0, 25, mouseX, 25);
        +   *
        +   *   // Remap mouseX from [0, 100] to [0, 50].
        +   *   let x = map(mouseX, 0, 100, 0, 50);
        +   *
        +   *   // Draw the bottom line.
        +   *   line(0, 75, 0, x);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A circle changes color from black to white as the mouse moves from left to right.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Remap mouseX from [0, 100] to [0, 255]
        +   *   let c = map(mouseX, 0, 100, 0, 255);
        +   *
        +   *   // Style the circle.
        +   *   fill(c);
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.map = function(n, start1, stop1, start2, stop2, withinBounds) {
        +    // p5._validateParameters('map', arguments);
        +    const newval = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
        +    if (!withinBounds) {
        +      return newval;
        +    }
        +    if (start2 < stop2) {
        +      return this.constrain(newval, start2, stop2);
        +    } else {
        +      return this.constrain(newval, stop2, start2);
        +    }
        +  };
         
        -/**
        - * Re-maps a number from one range to another.
        - *
        - * For example, calling `map(2, 0, 10, 0, 100)` returns 20. The first three
        - * arguments set the original value to 2 and the original range from 0 to 10.
        - * The last two arguments set the target range from 0 to 100. 20's position
        - * in the target range [0, 100] is proportional to 2's position in the
        - * original range [0, 10].
        - *
        - * The sixth parameter, `withinBounds`, is optional. By default, `map()` can
        - * return values outside of the target range. For example,
        - * `map(11, 0, 10, 0, 100)` returns 110. Passing `true` as the sixth parameter
        - * constrains the remapped value to the target range. For example,
        - * `map(11, 0, 10, 0, 100, true)` returns 100.
        - *
        - * @method map
        - * @param  {Number} value  the value to be remapped.
        - * @param  {Number} start1 lower bound of the value's current range.
        - * @param  {Number} stop1  upper bound of the value's current range.
        - * @param  {Number} start2 lower bound of the value's target range.
        - * @param  {Number} stop2  upper bound of the value's target range.
        - * @param  {Boolean} [withinBounds] constrain the value to the newly mapped range.
        - * @return {Number}        remapped number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Two horizontal lines. The top line grows horizontally as the mouse moves to the right. The bottom line also grows horizontally but is scaled to stay on the left half of the canvas.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the top line.
        - *   line(0, 25, mouseX, 25);
        - *
        - *   // Remap mouseX from [0, 100] to [0, 50].
        - *   let x = map(mouseX, 0, 100, 0, 50);
        - *
        - *   // Draw the bottom line.
        - *   line(0, 75, x, 75);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A circle changes color from black to white as the mouse moves from left to right.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Remap mouseX from [0, 100] to [0, 255]
        - *   let c = map(mouseX, 0, 100, 0, 255);
        - *
        - *   // Style the circle.
        - *   fill(c);
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, 20);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.map = function(n, start1, stop1, start2, stop2, withinBounds) {
        -  p5._validateParameters('map', arguments);
        -  const newval = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
        -  if (!withinBounds) {
        -    return newval;
        -  }
        -  if (start2 < stop2) {
        -    return this.constrain(newval, start2, stop2);
        -  } else {
        -    return this.constrain(newval, stop2, start2);
        -  }
        -};
        +  /**
        +   * Returns the largest value in a sequence of numbers.
        +   *
        +   * The version of `max()` with one parameter interprets it as an array of
        +   * numbers and returns the largest number.
        +   *
        +   * The version of `max()` with two or more parameters interprets them as
        +   * individual numbers and returns the largest number.
        +   *
        +   * @method max
        +   * @param  {Number} n0 first number to compare.
        +   * @param  {Number} n1 second number to compare.
        +   * @return {Number}             maximum number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Calculate the maximum of 10, 5, and 20.
        +   *   let m = max(10, 5, 20);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the max.
        +   *   text(m, 50, 50);
        +   *
        +   *   describe('The number 20 written in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of numbers.
        +   *   let numbers = [10, 5, 20];
        +   *
        +   *   // Calculate the maximum of the array.
        +   *   let m = max(numbers);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the max.
        +   *   text(m, 50, 50);
        +   *
        +   *   describe('The number 20 written in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method max
        +   * @param  {Number[]} nums numbers to compare.
        +   * @return {Number}
        +   */
        +  fn.max = function(...args) {
        +    const findMax = arr => {
        +      let max = -Infinity;
        +      for (let x of arr) {
        +        max = Math.max(max, x);
        +      }
        +      return max;
        +    };
         
        -/**
        - * Returns the largest value in a sequence of numbers.
        - *
        - * The version of `max()` with one parameter interprets it as an array of
        - * numbers and returns the largest number.
        - *
        - * The version of `max()` with two or more parameters interprets them as
        - * individual numbers and returns the largest number.
        - *
        - * @method max
        - * @param  {Number} n0 first number to compare.
        - * @param  {Number} n1 second number to compare.
        - * @return {Number}             maximum number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Calculate the maximum of 10, 5, and 20.
        - *   let m = max(10, 5, 20);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the max.
        - *   text(m, 50, 50);
        - *
        - *   describe('The number 20 written in the middle of a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of numbers.
        - *   let numbers = [10, 5, 20];
        - *
        - *   // Calculate the maximum of the array.
        - *   let m = max(numbers);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the max.
        - *   text(m, 50, 50);
        - *
        - *   describe('The number 20 written in the middle of a gray square.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method max
        - * @param  {Number[]} nums numbers to compare.
        - * @return {Number}
        - */
        -p5.prototype.max = function(...args) {
        -  const findMax = arr => Math.max(...arr);
        +    if (args[0] instanceof Array) {
        +      return findMax(args[0]);
        +    } else {
        +      return findMax(args);
        +    }
        +  };
         
        -  if (Array.isArray(args[0])) {
        -    return findMax(args[0]);
        -  } else {
        -    return findMax(args);
        -  }
        -};
        +  /**
        +   * Returns the smallest value in a sequence of numbers.
        +   *
        +   * The version of `min()` with one parameter interprets it as an array of
        +   * numbers and returns the smallest number.
        +   *
        +   * The version of `min()` with two or more parameters interprets them as
        +   * individual numbers and returns the smallest number.
        +   *
        +   * @method min
        +   * @param  {Number} n0 first number to compare.
        +   * @param  {Number} n1 second number to compare.
        +   * @return {Number}             minimum number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Calculate the minimum of 10, 5, and 20.
        +   *   let m = min(10, 5, 20);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the min.
        +   *   text(m, 50, 50);
        +   *
        +   *   describe('The number 5 written in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of numbers.
        +   *   let numbers = [10, 5, 20];
        +   *
        +   *   // Calculate the minimum of the array.
        +   *   let m = min(numbers);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the min.
        +   *   text(m, 50, 50);
        +   *
        +   *   describe('The number 5 written in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method min
        +   * @param  {Number[]} nums numbers to compare.
        +   * @return {Number}
        +   */
        +  fn.min = function(...args) {
        +    const findMin = arr => {
        +      let min = Infinity;
        +      for (let x of arr) {
        +        min = Math.min(min, x);
        +      }
        +      return min;
        +    };
         
        -/**
        - * Returns the smallest value in a sequence of numbers.
        - *
        - * The version of `min()` with one parameter interprets it as an array of
        - * numbers and returns the smallest number.
        - *
        - * The version of `min()` with two or more parameters interprets them as
        - * individual numbers and returns the smallest number.
        - *
        - * @method min
        - * @param  {Number} n0 first number to compare.
        - * @param  {Number} n1 second number to compare.
        - * @return {Number}             minimum number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Calculate the minimum of 10, 5, and 20.
        - *   let m = min(10, 5, 20);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the min.
        - *   text(m, 50, 50);
        - *
        - *   describe('The number 5 written in the middle of a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of numbers.
        - *   let numbers = [10, 5, 20];
        - *
        - *   // Calculate the minimum of the array.
        - *   let m = min(numbers);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the min.
        - *   text(m, 50, 50);
        - *
        - *   describe('The number 5 written in the middle of a gray square.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method min
        - * @param  {Number[]} nums numbers to compare.
        - * @return {Number}
        - */
        -p5.prototype.min = function(...args) {
        -  const findMin = arr => Math.min(...arr);
        +    if (args[0] instanceof Array) {
        +      return findMin(args[0]);
        +    } else {
        +      return findMin(args);
        +    }
        +  };
         
        -  if (Array.isArray(args[0])) {
        -    return findMin(args[0]);
        -  } else {
        -    return findMin(args);
        -  }
        -};
        +  /**
        +   * Maps a number from one range to a value between 0 and 1.
        +   *
        +   * For example, `norm(2, 0, 10)` returns 0.2. 2's position in the original
        +   * range [0, 10] is proportional to 0.2's position in the range [0, 1]. This
        +   * is the same as calling `map(2, 0, 10, 0, 1)`.
        +   *
        +   * Numbers outside of the original range are not constrained between 0 and 1.
        +   * Out-of-range values are often intentional and useful.
        +   *
        +   * @method norm
        +   * @param  {Number} value incoming value to be normalized.
        +   * @param  {Number} start lower bound of the value's current range.
        +   * @param  {Number} stop  upper bound of the value's current range.
        +   * @return {Number}       normalized number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use RGB color with values from 0 to 1.
        +   *   colorMode(RGB, 1);
        +   *
        +   *   describe('A square changes color from black to red as the mouse moves from left to right.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Calculate the redValue.
        +   *   let redValue = norm(mouseX, 0, 100);
        +   *
        +   *   // Paint the background.
        +   *   background(redValue, 0, 0);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.norm = function(n, start, stop) {
        +    // p5._validateParameters('norm', arguments);
        +    return this.map(n, start, stop, 0, 1);
        +  };
         
        -/**
        - * Maps a number from one range to a value between 0 and 1.
        - *
        - * For example, `norm(2, 0, 10)` returns 0.2. 2's position in the original
        - * range [0, 10] is proportional to 0.2's position in the range [0, 1]. This
        - * is the same as calling `map(2, 0, 10, 0, 1)`.
        - *
        - * Numbers outside of the original range are not constrained between 0 and 1.
        - * Out-of-range values are often intentional and useful.
        - *
        - * @method norm
        - * @param  {Number} value incoming value to be normalized.
        - * @param  {Number} start lower bound of the value's current range.
        - * @param  {Number} stop  upper bound of the value's current range.
        - * @return {Number}       normalized number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use RGB color with values from 0 to 1.
        - *   colorMode(RGB, 1);
        - *
        - *   describe('A square changes color from black to red as the mouse moves from left to right.');
        - * }
        - *
        - * function draw() {
        - *   // Calculate the redValue.
        - *   let redValue = norm(mouseX, 0, 100);
        - *
        - *   // Paint the background.
        - *   background(redValue, 0, 0);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.norm = function(n, start, stop) {
        -  p5._validateParameters('norm', arguments);
        -  return this.map(n, start, stop, 0, 1);
        -};
        +  /**
        +   * Calculates exponential expressions such as <var>2<sup>3</sup></var>.
        +   *
        +   * For example, `pow(2, 3)` evaluates the expression
        +   * 2 &times; 2 &times; 2. `pow(2, -3)` evaluates 1 &#247;
        +   * (2 &times; 2 &times; 2).
        +   *
        +   * @method pow
        +   * @param  {Number} n base of the exponential expression.
        +   * @param  {Number} e power by which to raise the base.
        +   * @return {Number}   n^e.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the base of the exponent.
        +   *   let base = 3;
        +   *
        +   *   // Top-left.
        +   *   let d = pow(base, 1);
        +   *   circle(10, 10, d);
        +   *
        +   *   // Left-center.
        +   *   d = pow(base, 2);
        +   *   circle(20, 20, d);
        +   *
        +   *   // Right-center.
        +   *   d = pow(base, 3);
        +   *   circle(40, 40, d);
        +   *
        +   *   // Bottom-right.
        +   *   d = pow(base, 4);
        +   *   circle(80, 80, d);
        +   *
        +   *   describe('A series of circles that grow exponentially from top left to bottom right.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.pow = Math.pow;
         
        -/**
        - * Calculates exponential expressions such as <var>2<sup>3</sup></var>.
        - *
        - * For example, `pow(2, 3)` evaluates the expression
        - * 2 &times; 2 &times; 2. `pow(2, -3)` evaluates 1 &#247;
        - * (2 &times; 2 &times; 2).
        - *
        - * @method pow
        - * @param  {Number} n base of the exponential expression.
        - * @param  {Number} e power by which to raise the base.
        - * @return {Number}   n^e.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Set the base of the exponent.
        - *   let base = 3;
        - *
        - *   // Top-left.
        - *   let d = pow(base, 1);
        - *   circle(10, 10, d);
        - *
        - *   // Left-center.
        - *   d = pow(base, 2);
        - *   circle(20, 20, d);
        - *
        - *   // Right-center.
        - *   d = pow(base, 3);
        - *   circle(40, 40, d);
        - *
        - *   // Bottom-right.
        - *   d = pow(base, 4);
        - *   circle(80, 80, d);
        - *
        - *   describe('A series of circles that grow exponentially from top left to bottom right.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.pow = Math.pow;
        +  /**
        +   * Calculates the integer closest to a number.
        +   *
        +   * For example, `round(133.8)` returns the value 134.
        +   *
        +   * The second parameter, `decimals`, is optional. It sets the number of
        +   * decimal places to use when rounding. For example, `round(12.34, 1)` returns
        +   * 12.3. `decimals` is 0 by default.
        +   *
        +   * @method round
        +   * @param  {Number} n number to round.
        +   * @param  {Number} [decimals] number of decimal places to round to, default is 0.
        +   * @return {Integer}  rounded number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Round a number.
        +   *   let x = round(4.2);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the rounded number.
        +   *   text(x, 50, 50);
        +   *
        +   *   describe('The number 4 written in middle of the canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Round a number to 2 decimal places.
        +   *   let x = round(12.782383, 2);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the rounded number.
        +   *   text(x, 50, 50);
        +   *
        +   *   describe('The number 12.78 written in middle of canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.round = function(n, decimals) {
        +    if (!decimals) {
        +      return Math.round(n);
        +    }
        +    const multiplier = Math.pow(10, decimals);
        +    return Math.round(n * multiplier) / multiplier;
        +  };
         
        -/**
        - * Calculates the integer closest to a number.
        - *
        - * For example, `round(133.8)` returns the value 134.
        - *
        - * The second parameter, `decimals`, is optional. It sets the number of
        - * decimal places to use when rounding. For example, `round(12.34, 1)` returns
        - * 12.3. `decimals` is 0 by default.
        - *
        - * @method round
        - * @param  {Number} n number to round.
        - * @param  {Number} [decimals] number of decimal places to round to, default is 0.
        - * @return {Integer}  rounded number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Round a number.
        - *   let x = round(4.2);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the rounded number.
        - *   text(x, 50, 50);
        - *
        - *   describe('The number 4 written in middle of the canvas.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Round a number to 2 decimal places.
        - *   let x = round(12.782383, 2);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the rounded number.
        - *   text(x, 50, 50);
        - *
        - *   describe('The number 12.78 written in middle of canvas.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.round = function(n, decimals) {
        -  if (!decimals) {
        -    return Math.round(n);
        -  }
        -  const multiplier = Math.pow(10, decimals);
        -  return Math.round(n * multiplier) / multiplier;
        -};
        +  /**
        +   * Calculates the square of a number.
        +   *
        +   * Squaring a number means multiplying the number by itself. For example,
        +   * `sq(3)` evaluates 3 &times; 3 which is 9. `sq(-3)` evaluates -3 &times; -3
        +   * which is also 9. Multiplying two negative numbers produces a positive
        +   * number. The value returned by `sq()` is always positive.
        +   *
        +   * @method sq
        +   * @param  {Number} n number to square.
        +   * @return {Number}   squared number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Top-left.
        +   *   let d = sq(3);
        +   *   circle(33, 33, d);
        +   *
        +   *   // Bottom-right.
        +   *   d = sq(6);
        +   *   circle(67, 67, d);
        +   *
        +   *   describe('Two white circles. The circle at the top-left is small. The circle at the bottom-right is four times larger.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A series of black dots that get higher quickly from left to right.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Invert the y-axis.
        +   *   scale(1, -1);
        +   *   translate(0, -100);
        +   *
        +   *   // Calculate the coordinates.
        +   *   let x = frameCount;
        +   *   let y = 0.01 * sq(x);
        +   *
        +   *   // Draw the point.
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.sq = n => n * n;
         
        -/**
        - * Calculates the square of a number.
        - *
        - * Squaring a number means multiplying the number by itself. For example,
        - * `sq(3)` evaluates 3 &times; 3 which is 9. `sq(-3)` evaluates -3 &times; -3
        - * which is also 9. Multiplying two negative numbers produces a positive
        - * number. The value returned by `sq()` is always positive.
        - *
        - * @method sq
        - * @param  {Number} n number to square.
        - * @return {Number}   squared number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top-left.
        - *   let d = sq(3);
        - *   circle(33, 33, d);
        - *
        - *   // Bottom-right.
        - *   d = sq(6);
        - *   circle(67, 67, d);
        - *
        - *   describe('Two white circles. The circle at the top-left is small. The circle at the bottom-right is four times larger.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A series of black dots that get higher quickly from left to right.');
        - * }
        - *
        - * function draw() {
        - *   // Invert the y-axis.
        - *   scale(1, -1);
        - *   translate(0, -100);
        - *
        - *   // Calculate the coordinates.
        - *   let x = frameCount;
        - *   let y = 0.01 * sq(x);
        - *
        - *   // Draw the point.
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.sq = n => n * n;
        +  /**
        +   * Calculates the square root of a number.
        +   *
        +   * A number's square root can be multiplied by itself to produce the original
        +   * number. For example, `sqrt(9)` returns 3 because 3 &times; 3 = 9. `sqrt()`
        +   * always returns a positive value. `sqrt()` doesn't work with negative arguments
        +   * such as `sqrt(-9)`.
        +   *
        +   * @method sqrt
        +   * @param  {Number} n non-negative number to square root.
        +   * @return {Number}   square root of number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Top-left.
        +   *   let d = sqrt(16);
        +   *   circle(33, 33, d);
        +   *
        +   *   // Bottom-right.
        +   *   d = sqrt(1600);
        +   *   circle(67, 67, d);
        +   *
        +   *   describe('Two white circles. The circle at the top-left is small. The circle at the bottom-right is ten times larger.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A series of black dots that get higher slowly from left to right.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Invert the y-axis.
        +   *   scale(1, -1);
        +   *   translate(0, -100);
        +   *
        +   *   // Calculate the coordinates.
        +   *   let x = frameCount;
        +   *   let y = 5 * sqrt(x);
        +   *
        +   *   // Draw the point.
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.sqrt = Math.sqrt;
         
        -/**
        - * Calculates the square root of a number.
        - *
        - * A number's square root can be multiplied by itself to produce the original
        - * number. For example, `sqrt(9)` returns 3 because 3 &times; 3 = 9. `sqrt()`
        - * always returns a positive value. `sqrt()` doesn't work with negative arguments
        - * such as `sqrt(-9)`.
        - *
        - * @method sqrt
        - * @param  {Number} n non-negative number to square root.
        - * @return {Number}   square root of number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top-left.
        - *   let d = sqrt(16);
        - *   circle(33, 33, d);
        - *
        - *   // Bottom-right.
        - *   d = sqrt(1600);
        - *   circle(67, 67, d);
        - *
        - *   describe('Two white circles. The circle at the top-left is small. The circle at the bottom-right is ten times larger.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A series of black dots that get higher slowly from left to right.');
        - * }
        - *
        - * function draw() {
        - *   // Invert the y-axis.
        - *   scale(1, -1);
        - *   translate(0, -100);
        - *
        - *   // Calculate the coordinates.
        - *   let x = frameCount;
        - *   let y = 5 * sqrt(x);
        - *
        - *   // Draw the point.
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.sqrt = Math.sqrt;
        +  /**
        +   * Calculates the fractional part of a number.
        +   *
        +   * A number's fractional part includes its decimal values. For example,
        +   * `fract(12.34)` returns 0.34.
        +   *
        +   * @method fract
        +   * @param {Number} n number whose fractional part will be found.
        +   * @returns {Number} fractional part of n.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Original number.
        +   *   let n = 56.78;
        +   *   text(n, 50, 33);
        +   *
        +   *   // Fractional part.
        +   *   let f = fract(n);
        +   *   text(f, 50, 67);
        +   *
        +   *   describe('The number 56.78 written above the number 0.78.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.fract = function(toConvert) {
        +    // p5._validateParameters('fract', arguments);
        +    let sign = 0;
        +    let num = Number(toConvert);
        +    if (isNaN(num) || Math.abs(num) === Infinity) {
        +      return num;
        +    } else if (num < 0) {
        +      num = -num;
        +      sign = 1;
        +    }
        +    if (String(num).includes('.') && !String(num).includes('e')) {
        +      let toFract = String(num);
        +      toFract = Number('0' + toFract.slice(toFract.indexOf('.')));
        +      return Math.abs(sign - toFract);
        +    } else if (num < 1) {
        +      return Math.abs(sign - num);
        +    } else {
        +      return 0;
        +    }
        +  };
        +}
         
        -/**
        - * Calculates the fractional part of a number.
        - *
        - * A number's fractional part includes its decimal values. For example,
        - * `fract(12.34)` returns 0.34.
        - *
        - * @method fract
        - * @param {Number} n number whose fractional part will be found.
        - * @returns {Number} fractional part of n.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Original number.
        - *   let n = 56.78;
        - *   text(n, 50, 33);
        - *
        - *   // Fractional part.
        - *   let f = fract(n);
        - *   text(f, 50, 67);
        - *
        - *   describe('The number 56.78 written above the number 0.78.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.fract = function(toConvert) {
        -  p5._validateParameters('fract', arguments);
        -  let sign = 0;
        -  let num = Number(toConvert);
        -  if (isNaN(num) || Math.abs(num) === Infinity) {
        -    return num;
        -  } else if (num < 0) {
        -    num = -num;
        -    sign = 1;
        -  }
        -  if (String(num).includes('.') && !String(num).includes('e')) {
        -    let toFract = String(num);
        -    toFract = Number('0' + toFract.slice(toFract.indexOf('.')));
        -    return Math.abs(sign - toFract);
        -  } else if (num < 1) {
        -    return Math.abs(sign - num);
        -  } else {
        -    return 0;
        -  }
        -};
        +export default calculation;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  calculation(p5, p5.prototype);
        +}
        diff --git a/src/math/index.js b/src/math/index.js
        new file mode 100644
        index 0000000000..2acb397b21
        --- /dev/null
        +++ b/src/math/index.js
        @@ -0,0 +1,15 @@
        +import calculation from './calculation.js';
        +import noise from './noise.js';
        +import random from './random.js';
        +import trigonometry from './trigonometry.js';
        +import math from './math.js';
        +import vector from './p5.Vector.js';
        +
        +export default function(p5){
        +  p5.registerAddon(calculation);
        +  p5.registerAddon(noise);
        +  p5.registerAddon(random);
        +  p5.registerAddon(trigonometry);
        +  p5.registerAddon(math);
        +  p5.registerAddon(vector);
        +}
        diff --git a/src/math/math.js b/src/math/math.js
        index bfcbeea77f..c3e9103da4 100644
        --- a/src/math/math.js
        +++ b/src/math/math.js
        @@ -5,105 +5,135 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        +function math(p5, fn){
        +  /**
        +   * Creates a new <a href="#/p5.Vector">p5.Vector</a> object.
        +   *
        +   * A vector can be thought of in different ways. In one view, a vector is like
        +   * an arrow pointing in space. Vectors have both magnitude (length) and
        +   * direction. This view is helpful for programming motion.
        +   *
        +   * A vector's components determine its magnitude and direction. For example,
        +   * calling `createVector(3, 4)` creates a new
        +   * <a href="#/p5.Vector">p5.Vector</a> object with an x-component of 3 and a
        +   * y-component of 4. From the origin, this vector's tip is 3 units to the
        +   * right and 4 units down.
        +   *
        +   * <a href="#/p5.Vector">p5.Vector</a> objects are often used to program
        +   * motion because they simplify the math. For example, a moving ball has a
        +   * position and a velocity. Position describes where the ball is in space. The
        +   * ball's position vector extends from the origin to the ball's center.
        +   * Velocity describes the ball's speed and the direction it's moving. If the
        +   * ball is moving straight up, its velocity vector points straight up. Adding
        +   * the ball's velocity vector to its position vector moves it, as in
        +   * `pos.add(vel)`. Vector math relies on methods inside the
        +   * <a href="#/p5.Vector">p5.Vector</a> class.
        +   *
        +   * @method createVector
        +   * @param {Number} [x] x component of the vector.
        +   * @param {Number} [y] y component of the vector.
        +   * @param {Number} [z] z component of the vector.
        +   * @return {p5.Vector} new <a href="#/p5.Vector">p5.Vector</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   let p1 = createVector(25, 25);
        +   *   let p2 = createVector(50, 50);
        +   *   let p3 = createVector(75, 75);
        +   *
        +   *   // Draw the dots.
        +   *   strokeWeight(5);
        +   *   point(p1);
        +   *   point(p2);
        +   *   point(p3);
        +   *
        +   *   describe('Three black dots form a diagonal line from top left to bottom right.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let pos;
        +   * let vel;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   pos = createVector(50, 100);
        +   *   vel = createVector(0, -1);
        +   *
        +   *   describe('A black dot moves from bottom to top on a gray square. The dot reappears at the bottom when it reaches the top.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Add velocity to position.
        +   *   pos.add(vel);
        +   *
        +   *   // If the dot reaches the top of the canvas,
        +   *   // restart from the bottom.
        +   *   if (pos.y < 0) {
        +   *     pos.y = 100;
        +   *   }
        +   *
        +   *   // Draw the dot.
        +   *   strokeWeight(5);
        +   *   point(pos);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createVector = function (x, y, z) {
        +    if (this instanceof p5) {
        +      return new p5.Vector(
        +        this._fromRadians.bind(this),
        +        this._toRadians.bind(this),
        +        ...arguments
        +      );
        +    } else {
        +      return new p5.Vector(x, y, z);
        +    }
        +  };
         
        -/**
        - * Creates a new <a href="#/p5.Vector">p5.Vector</a> object.
        - *
        - * A vector can be thought of in different ways. In one view, a vector is like
        - * an arrow pointing in space. Vectors have both magnitude (length) and
        - * direction. This view is helpful for programming motion.
        - *
        - * A vector's components determine its magnitude and direction. For example,
        - * calling `createVector(3, 4)` creates a new
        - * <a href="#/p5.Vector">p5.Vector</a> object with an x-component of 3 and a
        - * y-component of 4. From the origin, this vector's tip is 3 units to the
        - * right and 4 units down.
        - *
        - * <a href="#/p5.Vector">p5.Vector</a> objects are often used to program
        - * motion because they simplify the math. For example, a moving ball has a
        - * position and a velocity. Position describes where the ball is in space. The
        - * ball's position vector extends from the origin to the ball's center.
        - * Velocity describes the ball's speed and the direction it's moving. If the
        - * ball is moving straight up, its velocity vector points straight up. Adding
        - * the ball's velocity vector to its position vector moves it, as in
        - * `pos.add(vel)`. Vector math relies on methods inside the
        - * <a href="#/p5.Vector">p5.Vector</a> class.
        - *
        - * @method createVector
        - * @param {Number} [x] x component of the vector.
        - * @param {Number} [y] y component of the vector.
        - * @param {Number} [z] z component of the vector.
        - * @return {p5.Vector} new <a href="#/p5.Vector">p5.Vector</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create p5.Vector objects.
        - *   let p1 = createVector(25, 25);
        - *   let p2 = createVector(50, 50);
        - *   let p3 = createVector(75, 75);
        - *
        - *   // Draw the dots.
        - *   strokeWeight(5);
        - *   point(p1);
        - *   point(p2);
        - *   point(p3);
        - *
        - *   describe('Three black dots form a diagonal line from top left to bottom right.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let pos;
        - * let vel;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create p5.Vector objects.
        - *   pos = createVector(50, 100);
        - *   vel = createVector(0, -1);
        - *
        - *   describe('A black dot moves from bottom to top on a gray square. The dot reappears at the bottom when it reaches the top.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Add velocity to position.
        - *   pos.add(vel);
        - *
        - *   // If the dot reaches the top of the canvas,
        - *   // restart from the bottom.
        - *   if (pos.y < 0) {
        - *     pos.y = 100;
        - *   }
        - *
        - *   // Draw the dot.
        - *   strokeWeight(5);
        - *   point(pos);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createVector = function(x, y, z) {
        -  if (this instanceof p5) {
        -    return new p5.Vector(
        -      this._fromRadians.bind(this),
        -      this._toRadians.bind(this),
        -      ...arguments
        -    );
        -  } else {
        -    return new p5.Vector(x, y, z);
        -  }
        -};
        +  /**
        +   * Creates a new <a href="#/p5.Matrix">p5.Matrix</a> object.
        +   *
        +   * A matrix is a mathematical concept that is useful in many fields, including
        +   * computer graphics. In p5.js, matrices are used to perform transformations
        +   * on shapes and images.
        +   *
        +   * @method createMatrix
        +   * @return {p5.Matrix} new <a href="#/p5.Matrix">p5.Matrix</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *   let matrix = createMatrix();
        +   *   console.log(matrix);
        +   *   describe('Logs a new p5.Matrix object to the console.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createMatrix = function (...args) {
        +    return new p5.Matrix(...args);
        +  };
        +}
        +
        +export default math;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  math(p5, p5.prototype);
        +}
        diff --git a/src/math/noise.js b/src/math/noise.js
        index b1383efdd0..4a5b9ce5bc 100644
        --- a/src/math/noise.js
        +++ b/src/math/noise.js
        @@ -17,470 +17,471 @@
          * @for p5
          * @requires core
          */
        +function noise(p5, fn){
        +  const PERLIN_YWRAPB = 4;
        +  const PERLIN_YWRAP = 1 << PERLIN_YWRAPB;
        +  const PERLIN_ZWRAPB = 8;
        +  const PERLIN_ZWRAP = 1 << PERLIN_ZWRAPB;
        +  const PERLIN_SIZE = 4095;
         
        -import p5 from '../core/main';
        +  let perlin_octaves = 4; // default to medium smooth
        +  let perlin_amp_falloff = 0.5; // 50% reduction/octave
         
        -const PERLIN_YWRAPB = 4;
        -const PERLIN_YWRAP = 1 << PERLIN_YWRAPB;
        -const PERLIN_ZWRAPB = 8;
        -const PERLIN_ZWRAP = 1 << PERLIN_ZWRAPB;
        -const PERLIN_SIZE = 4095;
        +  const scaled_cosine = i => 0.5 * (1.0 - Math.cos(i * Math.PI));
         
        -let perlin_octaves = 4; // default to medium smooth
        -let perlin_amp_falloff = 0.5; // 50% reduction/octave
        +  let perlin; // will be initialized lazily by noise() or noiseSeed()
         
        -const scaled_cosine = i => 0.5 * (1.0 - Math.cos(i * Math.PI));
        -
        -let perlin; // will be initialized lazily by noise() or noiseSeed()
        -
        -/**
        - * Returns random numbers that can be tuned to feel organic.
        - *
        - * Values returned by <a href="#/p5/random">random()</a> and
        - * <a href="#/p5/randomGaussian">randomGaussian()</a> can change by large
        - * amounts between function calls. By contrast, values returned by `noise()`
        - * can be made "smooth". Calls to `noise()` with similar inputs will produce
        - * similar outputs. `noise()` is used to create textures, motion, shapes,
        - * terrains, and so on. Ken Perlin invented `noise()` while animating the
        - * original <em>Tron</em> film in the 1980s.
        - *
        - * `noise()` always returns values between 0 and 1. It returns the same value
        - * for a given input while a sketch is running. `noise()` produces different
        - * results each time a sketch runs. The
        - * <a href="#/p5/noiseSeed">noiseSeed()</a> function can be used to generate
        - * the same sequence of Perlin noise values each time a sketch runs.
        - *
        - * The character of the noise can be adjusted in two ways. The first way is to
        - * scale the inputs. `noise()` interprets inputs as coordinates. The sequence
        - * of noise values will be smoother when the input coordinates are closer. The
        - * second way is to use the <a href="#/p5/noiseDetail">noiseDetail()</a>
        - * function.
        - *
        - * The version of `noise()` with one parameter computes noise values in one
        - * dimension. This dimension can be thought of as space, as in `noise(x)`, or
        - * time, as in `noise(t)`.
        - *
        - * The version of `noise()` with two parameters computes noise values in two
        - * dimensions. These dimensions can be thought of as space, as in
        - * `noise(x, y)`, or space and time, as in `noise(x, t)`.
        - *
        - * The version of `noise()` with three parameters computes noise values in
        - * three dimensions. These dimensions can be thought of as space, as in
        - * `noise(x, y, z)`, or space and time, as in `noise(x, y, t)`.
        - *
        - * @method noise
        - * @param  {Number} x   x-coordinate in noise space.
        - * @param  {Number} [y] y-coordinate in noise space.
        - * @param  {Number} [z] z-coordinate in noise space.
        - * @return {Number}     Perlin noise value at specified coordinates.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A black dot moves randomly on a gray square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the coordinates.
        - *   let x = 100 * noise(0.005 * frameCount);
        - *   let y = 100 * noise(0.005 * frameCount + 10000);
        - *
        - *   // Draw the point.
        - *   strokeWeight(5);
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A black dot moves randomly on a gray square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Set the noise level and scale.
        - *   let noiseLevel = 100;
        - *   let noiseScale = 0.005;
        - *
        - *   // Scale the input coordinate.
        - *   let nt = noiseScale * frameCount;
        - *
        - *   // Compute the noise values.
        - *   let x = noiseLevel * noise(nt);
        - *   let y = noiseLevel * noise(nt + 10000);
        - *
        - *   // Draw the point.
        - *   strokeWeight(5);
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A hilly terrain drawn in gray against a black sky.');
        - * }
        - *
        - * function draw() {
        - *   // Set the noise level and scale.
        - *   let noiseLevel = 100;
        - *   let noiseScale = 0.02;
        - *
        - *   // Scale the input coordinate.
        - *   let x = frameCount;
        - *   let nx = noiseScale * x;
        - *
        - *   // Compute the noise value.
        - *   let y = noiseLevel * noise(nx);
        - *
        - *   // Draw the line.
        - *   line(x, 0, x, y);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A calm sea drawn in gray against a black sky.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Set the noise level and scale.
        - *   let noiseLevel = 100;
        - *   let noiseScale = 0.002;
        - *
        - *   // Iterate from left to right.
        - *   for (let x = 0; x < width; x += 1) {
        - *     // Scale the input coordinates.
        - *     let nx = noiseScale * x;
        - *     let nt = noiseScale * frameCount;
        - *
        - *     // Compute the noise value.
        - *     let y = noiseLevel * noise(nx, nt);
        - *
        - *     // Draw the line.
        - *     line(x, 0, x, y);
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Set the noise level and scale.
        - *   let noiseLevel = 255;
        - *   let noiseScale = 0.01;
        - *
        - *   // Iterate from top to bottom.
        - *   for (let y = 0; y < height; y += 1) {
        - *     // Iterate from left to right.
        - *     for (let x = 0; x < width; x += 1) {
        - *       // Scale the input coordinates.
        - *       let nx = noiseScale * x;
        - *       let ny = noiseScale * y;
        - *
        - *       // Compute the noise value.
        - *       let c = noiseLevel * noise(nx, ny);
        - *
        - *       // Draw the point.
        - *       stroke(c);
        - *       point(x, y);
        - *     }
        - *   }
        - *
        - *   describe('A gray cloudy pattern.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A gray cloudy pattern that changes.');
        - * }
        - *
        - * function draw() {
        - *   // Set the noise level and scale.
        - *   let noiseLevel = 255;
        - *   let noiseScale = 0.009;
        - *
        - *   // Iterate from top to bottom.
        - *   for (let y = 0; y < height; y += 1) {
        - *     // Iterate from left to right.
        - *     for (let x = 0; x < width; x += 1) {
        - *       // Scale the input coordinates.
        - *       let nx = noiseScale * x;
        - *       let ny = noiseScale * y;
        - *       let nt = noiseScale * frameCount;
        - *
        - *       // Compute the noise value.
        - *       let c = noiseLevel * noise(nx, ny, nt);
        - *
        - *       // Draw the point.
        - *       stroke(c);
        - *       point(x, y);
        - *     }
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -
        -p5.prototype.noise = function(x, y = 0, z = 0) {
        -  if (perlin == null) {
        -    perlin = new Array(PERLIN_SIZE + 1);
        -    for (let i = 0; i < PERLIN_SIZE + 1; i++) {
        -      perlin[i] = Math.random();
        +  /**
        +   * Returns random numbers that can be tuned to feel organic.
        +   *
        +   * Values returned by <a href="#/p5/random">random()</a> and
        +   * <a href="#/p5/randomGaussian">randomGaussian()</a> can change by large
        +   * amounts between function calls. By contrast, values returned by `noise()`
        +   * can be made "smooth". Calls to `noise()` with similar inputs will produce
        +   * similar outputs. `noise()` is used to create textures, motion, shapes,
        +   * terrains, and so on. Ken Perlin invented `noise()` while animating the
        +   * original <em>Tron</em> film in the 1980s.
        +   *
        +   * `noise()` always returns values between 0 and 1. It returns the same value
        +   * for a given input while a sketch is running. `noise()` produces different
        +   * results each time a sketch runs. The
        +   * <a href="#/p5/noiseSeed">noiseSeed()</a> function can be used to generate
        +   * the same sequence of Perlin noise values each time a sketch runs.
        +   *
        +   * The character of the noise can be adjusted in two ways. The first way is to
        +   * scale the inputs. `noise()` interprets inputs as coordinates. The sequence
        +   * of noise values will be smoother when the input coordinates are closer. The
        +   * second way is to use the <a href="#/p5/noiseDetail">noiseDetail()</a>
        +   * function.
        +   *
        +   * The version of `noise()` with one parameter computes noise values in one
        +   * dimension. This dimension can be thought of as space, as in `noise(x)`, or
        +   * time, as in `noise(t)`.
        +   *
        +   * The version of `noise()` with two parameters computes noise values in two
        +   * dimensions. These dimensions can be thought of as space, as in
        +   * `noise(x, y)`, or space and time, as in `noise(x, t)`.
        +   *
        +   * The version of `noise()` with three parameters computes noise values in
        +   * three dimensions. These dimensions can be thought of as space, as in
        +   * `noise(x, y, z)`, or space and time, as in `noise(x, y, t)`.
        +   *
        +   * @method noise
        +   * @param  {Number} x   x-coordinate in noise space.
        +   * @param  {Number} [y] y-coordinate in noise space.
        +   * @param  {Number} [z] z-coordinate in noise space.
        +   * @return {Number}     Perlin noise value at specified coordinates.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A black dot moves randomly on a gray square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the coordinates.
        +   *   let x = 100 * noise(0.005 * frameCount);
        +   *   let y = 100 * noise(0.005 * frameCount + 10000);
        +   *
        +   *   // Draw the point.
        +   *   strokeWeight(5);
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A black dot moves randomly on a gray square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Set the noise level and scale.
        +   *   let noiseLevel = 100;
        +   *   let noiseScale = 0.005;
        +   *
        +   *   // Scale the input coordinate.
        +   *   let nt = noiseScale * frameCount;
        +   *
        +   *   // Compute the noise values.
        +   *   let x = noiseLevel * noise(nt);
        +   *   let y = noiseLevel * noise(nt + 10000);
        +   *
        +   *   // Draw the point.
        +   *   strokeWeight(5);
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A hilly terrain drawn in gray against a black sky.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Set the noise level and scale.
        +   *   let noiseLevel = 100;
        +   *   let noiseScale = 0.02;
        +   *
        +   *   // Scale the input coordinate.
        +   *   let x = frameCount;
        +   *   let nx = noiseScale * x;
        +   *
        +   *   // Compute the noise value.
        +   *   let y = noiseLevel * noise(nx);
        +   *
        +   *   // Draw the line.
        +   *   line(x, 0, x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A calm sea drawn in gray against a black sky.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Set the noise level and scale.
        +   *   let noiseLevel = 100;
        +   *   let noiseScale = 0.002;
        +   *
        +   *   // Iterate from left to right.
        +   *   for (let x = 0; x < 100; x += 1) {
        +   *     // Scale the input coordinates.
        +   *     let nx = noiseScale * x;
        +   *     let nt = noiseScale * frameCount;
        +   *
        +   *     // Compute the noise value.
        +   *     let y = noiseLevel * noise(nx, nt);
        +   *
        +   *     // Draw the line.
        +   *     line(x, 0, x, y);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the noise level and scale.
        +   *   let noiseLevel = 255;
        +   *   let noiseScale = 0.01;
        +   *
        +   *   // Iterate from top to bottom.
        +   *   for (let y = 0; y < 100; y += 1) {
        +   *     // Iterate from left to right.
        +   *     for (let x = 0; x < 100; x += 1) {
        +   *       // Scale the input coordinates.
        +   *       let nx = noiseScale * x;
        +   *       let ny = noiseScale * y;
        +   *
        +   *       // Compute the noise value.
        +   *       let c = noiseLevel * noise(nx, ny);
        +   *
        +   *       // Draw the point.
        +   *       stroke(c);
        +   *       point(x, y);
        +   *     }
        +   *   }
        +   *
        +   *   describe('A gray cloudy pattern.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A gray cloudy pattern that changes.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Set the noise level and scale.
        +   *   let noiseLevel = 255;
        +   *   let noiseScale = 0.009;
        +   *
        +   *   // Iterate from top to bottom.
        +   *   for (let y = 0; y < 100; y += 1) {
        +   *     // Iterate from left to right.
        +   *     for (let x = 0; x < width; x += 1) {
        +   *       // Scale the input coordinates.
        +   *       let nx = noiseScale * x;
        +   *       let ny = noiseScale * y;
        +   *       let nt = noiseScale * frameCount;
        +   *
        +   *       // Compute the noise value.
        +   *       let c = noiseLevel * noise(nx, ny, nt);
        +   *
        +   *       // Draw the point.
        +   *       stroke(c);
        +   *       point(x, y);
        +   *     }
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noise = function(x, y = 0, z = 0) {
        +    if (perlin == null) {
        +      perlin = new Array(PERLIN_SIZE + 1);
        +      for (let i = 0; i < PERLIN_SIZE + 1; i++) {
        +        perlin[i] = Math.random();
        +      }
             }
        -  }
        -
        -  if (x < 0) {
        -    x = -x;
        -  }
        -  if (y < 0) {
        -    y = -y;
        -  }
        -  if (z < 0) {
        -    z = -z;
        -  }
         
        -  let xi = Math.floor(x),
        -    yi = Math.floor(y),
        -    zi = Math.floor(z);
        -  let xf = x - xi;
        -  let yf = y - yi;
        -  let zf = z - zi;
        -  let rxf, ryf;
        +    if (x < 0) {
        +      x = -x;
        +    }
        +    if (y < 0) {
        +      y = -y;
        +    }
        +    if (z < 0) {
        +      z = -z;
        +    }
         
        -  let r = 0;
        -  let ampl = 0.5;
        +    let xi = Math.floor(x),
        +      yi = Math.floor(y),
        +      zi = Math.floor(z);
        +    let xf = x - xi;
        +    let yf = y - yi;
        +    let zf = z - zi;
        +    let rxf, ryf;
        +    let r = 0;
        +    let ampl = 0.5;
         
        -  let n1, n2, n3;
        +    let n1, n2, n3;
         
        -  for (let o = 0; o < perlin_octaves; o++) {
        -    let of = xi + (yi << PERLIN_YWRAPB) + (zi << PERLIN_ZWRAPB);
        +    for (let o = 0; o < perlin_octaves; o++) {
        +      let of = xi + (yi << PERLIN_YWRAPB) + (zi << PERLIN_ZWRAPB);
         
        -    rxf = scaled_cosine(xf);
        -    ryf = scaled_cosine(yf);
        +      rxf = scaled_cosine(xf);
        +      ryf = scaled_cosine(yf);
         
        -    n1 = perlin[of & PERLIN_SIZE];
        -    n1 += rxf * (perlin[(of + 1) & PERLIN_SIZE] - n1);
        -    n2 = perlin[(of + PERLIN_YWRAP) & PERLIN_SIZE];
        -    n2 += rxf * (perlin[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n2);
        -    n1 += ryf * (n2 - n1);
        +      n1 = perlin[of & PERLIN_SIZE];
        +      n1 += rxf * (perlin[(of + 1) & PERLIN_SIZE] - n1);
        +      n2 = perlin[(of + PERLIN_YWRAP) & PERLIN_SIZE];
        +      n2 += rxf * (perlin[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n2);
        +      n1 += ryf * (n2 - n1);
         
        -    of += PERLIN_ZWRAP;
        -    n2 = perlin[of & PERLIN_SIZE];
        -    n2 += rxf * (perlin[(of + 1) & PERLIN_SIZE] - n2);
        -    n3 = perlin[(of + PERLIN_YWRAP) & PERLIN_SIZE];
        -    n3 += rxf * (perlin[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n3);
        -    n2 += ryf * (n3 - n2);
        +      of += PERLIN_ZWRAP;
        +      n2 = perlin[of & PERLIN_SIZE];
        +      n2 += rxf * (perlin[(of + 1) & PERLIN_SIZE] - n2);
        +      n3 = perlin[(of + PERLIN_YWRAP) & PERLIN_SIZE];
        +      n3 += rxf * (perlin[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n3);
        +      n2 += ryf * (n3 - n2);
         
        -    n1 += scaled_cosine(zf) * (n2 - n1);
        +      n1 += scaled_cosine(zf) * (n2 - n1);
         
        -    r += n1 * ampl;
        -    ampl *= perlin_amp_falloff;
        -    xi <<= 1;
        -    xf *= 2;
        -    yi <<= 1;
        -    yf *= 2;
        -    zi <<= 1;
        -    zf *= 2;
        +      r += n1 * ampl;
        +      ampl *= perlin_amp_falloff;
        +      xi <<= 1;
        +      xf *= 2;
        +      yi <<= 1;
        +      yf *= 2;
        +      zi <<= 1;
        +      zf *= 2;
         
        -    if (xf >= 1.0) {
        -      xi++;
        -      xf--;
        +      if (xf >= 1.0) {
        +        xi++;
        +        xf--;
        +      }
        +      if (yf >= 1.0) {
        +        yi++;
        +        yf--;
        +      }
        +      if (zf >= 1.0) {
        +        zi++;
        +        zf--;
        +      }
             }
        -    if (yf >= 1.0) {
        -      yi++;
        -      yf--;
        +    return r;
        +  };
        +
        +  /**
        +   * Adjusts the character of the noise produced by the
        +   * <a href="#/p5/noise">noise()</a> function.
        +   *
        +   * Perlin noise values are created by adding layers of noise together. The
        +   * noise layers, called octaves, are similar to harmonics in music. Lower
        +   * octaves contribute more to the output signal. They define the overall
        +   * intensity of the noise. Higher octaves create finer-grained details.
        +   *
        +   * By default, noise values are created by combining four octaves. Each higher
        +   * octave contributes half as much (50% less) compared to its predecessor.
        +   * `noiseDetail()` changes the number of octaves and the falloff amount. For
        +   * example, calling `noiseDetail(6, 0.25)` ensures that
        +   * <a href="#/p5/noise">noise()</a> will use six octaves. Each higher octave
        +   * will contribute 25% as much (75% less) compared to its predecessor. Falloff
        +   * values between 0 and 1 are valid. However, falloff values greater than 0.5
        +   * might result in noise values greater than 1.
        +   *
        +   * @method noiseDetail
        +   * @param {Number} lod number of octaves to be used by the noise.
        +   * @param {Number} falloff falloff factor for each octave.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Set the noise level and scale.
        +   *   let noiseLevel = 255;
        +   *   let noiseScale = 0.02;
        +   *
        +   *   // Iterate from top to bottom.
        +   *   for (let y = 0; y < 100; y += 1) {
        +   *     // Iterate from left to right.
        +   *     for (let x = 0; x < 50; x += 1) {
        +   *       // Scale the input coordinates.
        +   *       let nx = noiseScale * x;
        +   *       let ny = noiseScale * y;
        +   *
        +   *       // Compute the noise value with six octaves
        +   *       // and a low falloff factor.
        +   *       noiseDetail(6, 0.25);
        +   *       let c = noiseLevel * noise(nx, ny);
        +   *
        +   *       // Draw the left side.
        +   *       stroke(c);
        +   *       point(x, y);
        +   *
        +   *       // Compute the noise value with four octaves
        +   *       // and a high falloff factor.
        +   *       noiseDetail(4, 0.5);
        +   *       c = noiseLevel * noise(nx, ny);
        +   *
        +   *       // Draw the right side.
        +   *       stroke(c);
        +   *       point(x + 50, y);
        +   *     }
        +   *   }
        +   *
        +   *   describe('Two gray cloudy patterns. The pattern on the right is cloudier than the pattern on the left.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noiseDetail = function(lod, falloff) {
        +    if (lod > 0) {
        +      perlin_octaves = lod;
             }
        -    if (zf >= 1.0) {
        -      zi++;
        -      zf--;
        +    if (falloff > 0) {
        +      perlin_amp_falloff = falloff;
             }
        -  }
        -  return r;
        -};
        +  };
         
        -/**
        - * Adjusts the character of the noise produced by the
        - * <a href="#/p5/noise">noise()</a> function.
        - *
        - * Perlin noise values are created by adding layers of noise together. The
        - * noise layers, called octaves, are similar to harmonics in music. Lower
        - * octaves contribute more to the output signal. They define the overall
        - * intensity of the noise. Higher octaves create finer-grained details.
        - *
        - * By default, noise values are created by combining four octaves. Each higher
        - * octave contributes half as much (50% less) compared to its predecessor.
        - * `noiseDetail()` changes the number of octaves and the falloff amount. For
        - * example, calling `noiseDetail(6, 0.25)` ensures that
        - * <a href="#/p5/noise">noise()</a> will use six octaves. Each higher octave
        - * will contribute 25% as much (75% less) compared to its predecessor. Falloff
        - * values between 0 and 1 are valid. However, falloff values greater than 0.5
        - * might result in noise values greater than 1.
        - *
        - * @method noiseDetail
        - * @param {Number} lod number of octaves to be used by the noise.
        - * @param {Number} falloff falloff factor for each octave.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Set the noise level and scale.
        - *   let noiseLevel = 255;
        - *   let noiseScale = 0.02;
        - *
        - *   // Iterate from top to bottom.
        - *   for (let y = 0; y < height; y += 1) {
        - *     // Iterate from left to right.
        - *     for (let x = 0; x < width / 2; x += 1) {
        - *       // Scale the input coordinates.
        - *       let nx = noiseScale * x;
        - *       let ny = noiseScale * y;
        - *
        - *       // Compute the noise value with six octaves
        - *       // and a low falloff factor.
        - *       noiseDetail(6, 0.25);
        - *       let c = noiseLevel * noise(nx, ny);
        - *
        - *       // Draw the left side.
        - *       stroke(c);
        - *       point(x, y);
        - *
        - *       // Compute the noise value with four octaves
        - *       // and a high falloff factor.
        - *       noiseDetail(4, 0.5);
        - *       c = noiseLevel * noise(nx, ny);
        - *
        - *       // Draw the right side.
        - *       stroke(c);
        - *       point(x + 50, y);
        - *     }
        - *   }
        - *
        - *   describe('Two gray cloudy patterns. The pattern on the right is cloudier than the pattern on the left.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noiseDetail = function(lod, falloff) {
        -  if (lod > 0) {
        -    perlin_octaves = lod;
        -  }
        -  if (falloff > 0) {
        -    perlin_amp_falloff = falloff;
        -  }
        -};
        +  /**
        +   * Sets the seed value for the <a href="#/p5/noise">noise()</a> function.
        +   *
        +   * By default, <a href="#/p5/noise">noise()</a> produces different results
        +   * each time a sketch is run. Calling `noiseSeed()` with a constant argument,
        +   * such as `noiseSeed(99)`, makes <a href="#/p5/noise">noise()</a> produce the
        +   * same results each time a sketch is run.
        +   *
        +   * @method noiseSeed
        +   * @param {Number} seed   seed value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the noise seed for consistent results.
        +   *   noiseSeed(99);
        +   *
        +   *   describe('A black rectangle that grows randomly, first to the right and then to the left.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Set the noise level and scale.
        +   *   let noiseLevel = 100;
        +   *   let noiseScale = 0.005;
        +   *
        +   *   // Scale the input coordinate.
        +   *   let nt = noiseScale * frameCount;
        +   *
        +   *   // Compute the noise value.
        +   *   let x = noiseLevel * noise(nt);
        +   *
        +   *   // Draw the line.
        +   *   line(x, 0, x, height);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noiseSeed = function(seed) {
        +    // Linear Congruential Generator
        +    // Variant of a Lehman Generator
        +    const lcg = (() => {
        +      // Set to values from http://en.wikipedia.org/wiki/Numerical_Recipes
        +      // m is basically chosen to be large (as it is the max period)
        +      // and for its relationships to a and c
        +      const m = 4294967296;
        +      // a - 1 should be divisible by m's prime factors
        +      const a = 1664525;
        +      // c and m should be co-prime
        +      const c = 1013904223;
        +      let seed, z;
        +      return {
        +        setSeed(val) {
        +          // pick a random seed if val is undefined or null
        +          // the >>> 0 casts the seed to an unsigned 32-bit integer
        +          z = seed = (val == null ? Math.random() * m : val) >>> 0;
        +        },
        +        getSeed() {
        +          return seed;
        +        },
        +        rand() {
        +          // define the recurrence relationship
        +          z = (a * z + c) % m;
        +          // return a float in [0, 1)
        +          // if z = m then z / m = 0 therefore (z % m) / m < 1 always
        +          return z / m;
        +        }
        +      };
        +    })();
         
        -/**
        - * Sets the seed value for the <a href="#/p5/noise">noise()</a> function.
        - *
        - * By default, <a href="#/p5/noise">noise()</a> produces different results
        - * each time a sketch is run. Calling `noiseSeed()` with a constant argument,
        - * such as `noiseSeed(99)`, makes <a href="#/p5/noise">noise()</a> produce the
        - * same results each time a sketch is run.
        - *
        - * @method noiseSeed
        - * @param {Number} seed   seed value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Set the noise seed for consistent results.
        - *   noiseSeed(99);
        - *
        -  *   describe('A black rectangle that grows randomly, first to the right and then to the left.');
        - * }
        - *
        - * function draw() {
        - *   // Set the noise level and scale.
        - *   let noiseLevel = 100;
        - *   let noiseScale = 0.005;
        - *
        - *   // Scale the input coordinate.
        - *   let nt = noiseScale * frameCount;
        - *
        - *   // Compute the noise value.
        - *   let x = noiseLevel * noise(nt);
        - *
        - *   // Draw the line.
        - *   line(x, 0, x, height);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noiseSeed = function(seed) {
        -  // Linear Congruential Generator
        -  // Variant of a Lehman Generator
        -  const lcg = (() => {
        -    // Set to values from http://en.wikipedia.org/wiki/Numerical_Recipes
        -    // m is basically chosen to be large (as it is the max period)
        -    // and for its relationships to a and c
        -    const m = 4294967296;
        -    // a - 1 should be divisible by m's prime factors
        -    const a = 1664525;
        -    // c and m should be co-prime
        -    const c = 1013904223;
        -    let seed, z;
        -    return {
        -      setSeed(val) {
        -        // pick a random seed if val is undefined or null
        -        // the >>> 0 casts the seed to an unsigned 32-bit integer
        -        z = seed = (val == null ? Math.random() * m : val) >>> 0;
        -      },
        -      getSeed() {
        -        return seed;
        -      },
        -      rand() {
        -        // define the recurrence relationship
        -        z = (a * z + c) % m;
        -        // return a float in [0, 1)
        -        // if z = m then z / m = 0 therefore (z % m) / m < 1 always
        -        return z / m;
        -      }
        -    };
        -  })();
        +    lcg.setSeed(seed);
        +    perlin = new Array(PERLIN_SIZE + 1);
        +    for (let i = 0; i < PERLIN_SIZE + 1; i++) {
        +      perlin[i] = lcg.rand();
        +    }
        +  };
        +}
         
        -  lcg.setSeed(seed);
        -  perlin = new Array(PERLIN_SIZE + 1);
        -  for (let i = 0; i < PERLIN_SIZE + 1; i++) {
        -    perlin[i] = lcg.rand();
        -  }
        -};
        +export default noise;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  noise(p5, p5.prototype);
        +}
        diff --git a/src/math/p5.Matrix.js b/src/math/p5.Matrix.js
        new file mode 100644
        index 0000000000..704544dc86
        --- /dev/null
        +++ b/src/math/p5.Matrix.js
        @@ -0,0 +1,30 @@
        +/**
        + * @requires constants
        + * @todo see methods below needing further implementation.
        + * future consideration: implement SIMD optimizations
        + * when browser compatibility becomes available
        + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/
        + *   Reference/Global_Objects/SIMD
        + */
        +import { Matrix } from './Matrices/Matrix'
        +// import { MatrixNumjs as Matrix } from './Matrices/MatrixNumjs'
        +
        +
        +
        +function matrix(p5, fn){
        +  /**
        +   * A class to describe a 4×4 matrix
        +   * for model and view matrix manipulation in the p5js webgl renderer.
        +   * @class p5.Matrix
        +   * @private
        +   * @param {Array} [mat4] column-major array literal of our 4×4 matrix
        +   */
        +  p5.Matrix = Matrix
        +}
        +
        +export default matrix;
        +export { Matrix };
        +
        +if(typeof p5 !== 'undefined'){
        +  matrix(p5, p5.prototype);
        +}
        diff --git a/src/math/p5.Vector.js b/src/math/p5.Vector.js
        index f54667f6ee..46723223a1 100644
        --- a/src/math/p5.Vector.js
        +++ b/src/math/p5.Vector.js
        @@ -4,462 +4,525 @@
          * @requires constants
          */
         
        -import p5 from '../core/main';
         import * as constants from '../core/constants';
         
        +/// HELPERS FOR REMAINDER METHOD
        +const calculateRemainder2D = function (xComponent, yComponent) {
        +  if (xComponent !== 0) {
        +    this.x = this.x % xComponent;
        +  }
        +  if (yComponent !== 0) {
        +    this.y = this.y % yComponent;
        +  }
        +  return this;
        +};
         
        -/**
        - * A class to describe a two or three-dimensional vector.
        - *
        - * A vector can be thought of in different ways. In one view, a vector is like
        - * an arrow pointing in space. Vectors have both magnitude (length) and
        - * direction.
        - *
        - * `p5.Vector` objects are often used to program motion because they simplify
        - * the math. For example, a moving ball has a position and a velocity.
        - * Position describes where the ball is in space. The ball's position vector
        - * extends from the origin to the ball's center. Velocity describes the ball's
        - * speed and the direction it's moving. If the ball is moving straight up, its
        - * velocity vector points straight up. Adding the ball's velocity vector to
        - * its position vector moves it, as in `pos.add(vel)`. Vector math relies on
        - * methods inside the `p5.Vector` class.
        - *
        - * Note: <a href="#/p5/createVector">createVector()</a> is the recommended way
        - * to make an instance of this class.
        - *
        - * @class p5.Vector
        - * @constructor
        - * @param {Number} [x] x component of the vector.
        - * @param {Number} [y] y component of the vector.
        - * @param {Number} [z] z component of the vector.
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create p5.Vector objects.
        - *   let p1 = createVector(25, 25);
        - *   let p2 = createVector(75, 75);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Draw the first point using a p5.Vector.
        - *   point(p1);
        - *
        - *   // Draw the second point using a p5.Vector's components.
        - *   point(p2.x, p2.y);
        - *
        - *   describe('Two black dots on a gray square, one at the top left and the other at the bottom right.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let pos;
        - * let vel;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create p5.Vector objects.
        - *   pos = createVector(50, 100);
        - *   vel = createVector(0, -1);
        - *
        - *   describe('A black dot moves from bottom to top on a gray square. The dot reappears at the bottom when it reaches the top.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Add velocity to position.
        - *   pos.add(vel);
        - *
        - *   // If the dot reaches the top of the canvas,
        - *   // restart from the bottom.
        - *   if (pos.y < 0) {
        - *     pos.y = 100;
        - *   }
        - *
        - *   // Draw the dot.
        - *   strokeWeight(5);
        - *   point(pos);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Vector = class {
        +const calculateRemainder3D = function (xComponent, yComponent, zComponent) {
        +  if (xComponent !== 0) {
        +    this.x = this.x % xComponent;
        +  }
        +  if (yComponent !== 0) {
        +    this.y = this.y % yComponent;
        +  }
        +  if (zComponent !== 0) {
        +    this.z = this.z % zComponent;
        +  }
        +  return this;
        +};
        +
        +class Vector {
           // This is how it comes in with createVector()
           // This check if the first argument is a function
           constructor(...args) {
        -    let x, y, z;
        -    if (typeof args[0] === 'function') {
        +    let dimensions = args.length; // TODO: make default 3 if no arguments
        +    let values = args.map((arg) => arg || 0);
        +    if (typeof args[0] === "function") {
               this.isPInst = true;
               this._fromRadians = args[0];
               this._toRadians = args[1];
        -      x = args[2] || 0;
        -      y = args[3] || 0;
        -      z = args[4] || 0;
        -      // This is what we'll get with new p5.Vector()
        +      values = args.slice(2).map((arg) => arg || 0);
        +    }
        +    if (dimensions === 0) {
        +      this.dimensions = 2;
        +      this._values = [0, 0, 0];
             } else {
        -      x = args[0] || 0;
        -      y = args[1] || 0;
        -      z = args[2] || 0;
        +      this.dimensions = dimensions;
        +      this._values = values;
             }
        -    /**
        -     * The x component of the vector
        -     * @type {Number}
        -     * @property x
        -     * @name x
        -     */
        -    this.x = x;
        -    /**
        -     * The y component of the vector
        -     * @type {Number}
        -     * @property y
        -     * @name y
        -     */
        -    this.y = y;
        -    /**
        -     * The z component of the vector
        -     * @type {Number}
        -     * @property z
        -     * @name z
        -     */
        -    this.z = z;
        -  }
        -
        -  /**
        - * Returns a string representation of a vector.
        - *
        - * Calling `toString()` is useful for printing vectors to the console while
        - * debugging.
        - *
        - * @method  toString
        - * @return {String} string representation of the vector.
        - *
        - * @example
        - * <div class = "norender">
        - * <code>
        - * function setup() {
        - *   let v = createVector(20, 30);
        - *
        - *   // Prints 'p5.Vector Object : [20, 30, 0]'.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - */
        -  toString() {
        -    return `p5.Vector Object : [${this.x}, ${this.y}, ${this.z}]`;
        -  }
        -
        -  /**
        - * Sets the vector's `x`, `y`, and `z` components.
        - *
        - * `set()` can use separate numbers, as in `v.set(1, 2, 3)`, a
        - * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.set(v2)`, or an
        - * array of numbers, as in `v.set([1, 2, 3])`.
        - *
        - * If a value isn't provided for a component, it will be set to 0. For
        - * example, `v.set(4, 5)` sets `v.x` to 4, `v.y` to 5, and `v.z` to 0.
        - * Calling `set()` with no arguments, as in `v.set()`, sets all the vector's
        - * components to 0.
        - *
        - * @method set
        - * @param {Number} [x] x component of the vector.
        - * @param {Number} [y] y component of the vector.
        - * @param {Number} [z] z component of the vector.
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Top left.
        - *   let pos = createVector(25, 25);
        - *   point(pos);
        - *
        - *   // Top right.
        - *   // set() with numbers.
        - *   pos.set(75, 25);
        - *   point(pos);
        - *
        - *   // Bottom right.
        - *   // set() with a p5.Vector.
        - *   let p2 = createVector(75, 75);
        - *   pos.set(p2);
        - *   point(pos);
        - *
        - *   // Bottom left.
        - *   // set() with an array.
        - *   let arr = [25, 75];
        - *   pos.set(arr);
        - *   point(pos);
        - *
        - *   describe('Four black dots arranged in a square on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        +  }
        +
           /**
        - * @method set
        - * @param {p5.Vector|Number[]} value vector to set.
        - * @chainable
        - */
        -  set (x, y, z) {
        -    if (x instanceof p5.Vector) {
        -      this.x = x.x || 0;
        -      this.y = x.y || 0;
        -      this.z = x.z || 0;
        -      return this;
        -    }
        -    if (Array.isArray(x)) {
        -      this.x = x[0] || 0;
        -      this.y = x[1] || 0;
        -      this.z = x[2] || 0;
        -      return this;
        +   * Gets the values of the vector.
        +   *
        +   * This method returns an array of numbers that represent the vector.
        +   * Each number in the array corresponds to a different component of the vector,
        +   * like its position in different directions (e.g., x, y, z).
        +   *
        +   * @returns {Array<number>} The array of values representing the vector.
        +   */
        +  get values() {
        +    return this._values;
        +  }
        +
        +  /**
        +   * Sets the values of the vector.
        +   *
        +   * This method allows you to update the entire vector with a new set of values.
        +   * You need to provide an array of numbers, where each number represents a component
        +   * of the vector (e.g., x, y, z). The length of the array should match the number of
        +   * dimensions of the vector. If the array is shorter, the missing components will be
        +   * set to 0. If the array is longer, the extra values will be ignored.
        +   *
        +   * @param {Array<number>} newValues - An array of numbers representing the new values for the vector.
        +   *
        +   */
        +  set values(newValues) {
        +    let dimensions = newValues.length;
        +    if (dimensions === 0) {
        +      this.dimensions = 2;
        +      this._values = [0, 0, 0];
        +    } else {
        +      this.dimensions = dimensions;
        +      this._values = newValues.slice();
             }
        -    this.x = x || 0;
        -    this.y = y || 0;
        -    this.z = z || 0;
        -    return this;
           }
         
           /**
        - * Returns a copy of the <a href="#/p5.Vector">p5.Vector</a> object.
        - *
        - * @method copy
        - * @return {p5.Vector} copy of the <a href="#/p5.Vector">p5.Vector</a> object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100 ,100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Vector object.
        - *   let pos = createVector(50, 50);
        - *
        - *   // Make a copy.
        - *   let pc = pos.copy();
        - *
        - *   // Draw the point.
        - *   strokeWeight(5);
        - *   point(pc);
        - *
        - *   describe('A black point drawn in the middle of a gray square.');
        - * }
        - * </code>
        - * </div>
        - */
        -  copy () {
        -    if (this.isPInst) {
        -      return new p5.Vector(
        -        this._fromRadians,
        -        this._toRadians,
        -        this.x,
        -        this.y,
        -        this.z
        +   * Gets the x component of the vector.
        +   *
        +   * This method returns the value of the x component of the vector.
        +   * Think of the x component as the horizontal position or the first number in the vector.
        +   * If the x component is not defined, it will return 0.
        +   *
        +   * @returns {number} The x component of the vector. Returns 0 if the value is not defined.
        +   */
        +  get x() {
        +    return this._values[0] || 0;
        +  }
        +
        +  /**
        +   * Retrieves the value at the specified index from the vector.
        +   *
        +   * This method allows you to get the value of a specific component of the vector
        +   * by providing its index. Think of the vector as a list of numbers, where each
        +   * number represents a different direction (like x, y, or z). The index is just
        +   * the position of the number in that list.
        +   *
        +   * For example, if you have a vector with values 10, 20, 30 the index 0 would
        +   * give you the first value 10, index 1 would give you the second value 20,
        +   * and so on.
        +   *
        +   * @param {number} index - The position of the value you want to get from the vector.
        +   * @returns {number} The value at the specified position in the vector.
        +   * @throws Will throw an error if the index is out of bounds, meaning if you try to
        +   *          get a value from a position that doesn't exist in the vector.
        +   */
        +  getValue(index) {
        +    if (index < this._values.length) {
        +      return this._values[index];
        +    } else {
        +      p5._friendlyError(
        +        "The index parameter is trying to set a value outside the bounds of the vector",
        +        "p5.Vector.setValue"
               );
        +    }
        +  }
        +
        +  /**
        +   * Sets the value at the specified index of the vector.
        +   *
        +   * This method allows you to change a specific component of the vector by providing its index and the new value you want to set.
        +   * Think of the vector as a list of numbers, where each number represents a different direction (like x, y, or z).
        +   * The index is just the position of the number in that list.
        +   *
        +   * For example, if you have a vector with values [0, 20, 30], and you want to change the second value (20) to 50,
        +   * you would use this method with index 1 (since indexes start at 0) and value 50.
        +   *
        +   * @param {number} index - The position in the vector where you want to set the new value.
        +   * @param {number} value - The new value you want to set at the specified position.
        +   * @throws Will throw an error if the index is outside the bounds of the vector, meaning if you try to set a value at a position that doesn't exist in the vector.
        +   */
        +
        +  setValue(index, value) {
        +    if (index < this._values.length) {
        +      this._values[index] = value;
             } else {
        -      return new p5.Vector(this.x, this.y, this.z);
        +      p5._friendlyError(
        +        "The index parameter is trying to set a value outside the bounds of the vector",
        +        "p5.Vector.setValue"
        +      );
             }
           }
         
           /**
        - * Adds to a vector's `x`, `y`, and `z` components.
        - *
        - * `add()` can use separate numbers, as in `v.add(1, 2, 3)`,
        - * another <a href="#/p5.Vector">p5.Vector</a> object, as in `v.add(v2)`, or
        - * an array of numbers, as in `v.add([1, 2, 3])`.
        - *
        - * If a value isn't provided for a component, it won't change. For
        - * example, `v.add(4, 5)` adds 4 to `v.x`, 5 to `v.y`, and 0 to `v.z`.
        - * Calling `add()` with no arguments, as in `v.add()`, has no effect.
        - *
        - * The static version of `add()`, as in `p5.Vector.add(v2, v1)`, returns a new
        - * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        - * originals.
        - *
        - * @method add
        - * @param  {Number} x   x component of the vector to be added.
        - * @param  {Number} [y] y component of the vector to be added.
        - * @param  {Number} [z] z component of the vector to be added.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Top left.
        - *   let pos = createVector(25, 25);
        - *   point(pos);
        - *
        - *   // Top right.
        - *   // Add numbers.
        - *   pos.add(50, 0);
        - *   point(pos);
        - *
        - *   // Bottom right.
        - *   // Add a p5.Vector.
        - *   let p2 = createVector(0, 50);
        - *   pos.add(p2);
        - *   point(pos);
        - *
        - *   // Bottom left.
        - *   // Add an array.
        - *   let arr = [-50, 0];
        - *   pos.add(arr);
        - *   point(pos);
        - *
        - *   describe('Four black dots arranged in a square on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top left.
        - *   let p1 = createVector(25, 25);
        - *
        - *   // Center.
        - *   let p2 = createVector(50, 50);
        - *
        - *   // Bottom right.
        - *   // Add p1 and p2.
        - *   let p3 = p5.Vector.add(p1, p2);
        - *
        - *   // Draw the points.
        - *   strokeWeight(5);
        - *   point(p1);
        - *   point(p2);
        - *   point(p3);
        - *
        - *   describe('Three black dots in a diagonal line from top left to bottom right.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Three arrows drawn on a gray square. A red arrow extends from the top left corner to the center. A blue arrow extends from the tip of the red arrow. A purple arrow extends from the origin to the tip of the blue arrow.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   let origin = createVector(0, 0);
        - *
        - *   // Draw the red arrow.
        - *   let v1 = createVector(50, 50);
        - *   drawArrow(origin, v1, 'red');
        - *
        - *   // Draw the blue arrow.
        - *   let v2 = createVector(-30, 20);
        - *   drawArrow(v1, v2, 'blue');
        - *
        - *   // Purple arrow.
        - *   let v3 = p5.Vector.add(v1, v2);
        - *   drawArrow(origin, v3, 'purple');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Gets the y component of the vector.
        +   *
        +   * This method returns the value of the y component of the vector.
        +   * Think of the y component as the vertical position or the second number in the vector.
        +   * If the y component is not defined, it will return 0.
        +   *
        +   * @returns {number} The y component of the vector. Returns 0 if the value is not defined.
        +   */
        +  get y() {
        +    return this._values[1] || 0;
        +  }
        +
           /**
        - * @method add
        - * @param  {p5.Vector|Number[]} value The vector to add
        - * @chainable
        - */
        -  add (x, y, z) {
        -    if (x instanceof p5.Vector) {
        -      this.x += x.x || 0;
        -      this.y += x.y || 0;
        -      this.z += x.z || 0;
        -      return this;
        +   * Gets the z component of the vector.
        +   *
        +   * This method returns the value of the z component of the vector.
        +   * Think of the z component as the depth or the third number in the vector.
        +   * If the z component is not defined, it will return 0.
        +   *
        +   * @returns {number} The z component of the vector. Returns 0 if the value is not defined.
        +   */
        +  get z() {
        +    return this._values[2] || 0;
        +  }
        +
        +  /**
        +   * Gets the w component of the vector.
        +   *
        +   * This method returns the value of the w component of the vector.
        +   * Think of the w component as the fourth number in the vector.
        +   * If the w component is not defined, it will return 0.
        +   *
        +   * @returns {number} The w component of the vector. Returns 0 if the value is not defined.
        +   */
        +  get w() {
        +    return this._values[3] || 0;
        +  }
        +
        +  /**
        +   * Sets the x component of the vector.
        +   *
        +   * This method allows you to change the x value of the vector.
        +   * The x value is the first number in the vector, representing the horizontal position.
        +   * By calling this method, you can update the x value to a new number.
        +   *
        +   * @param {number} xVal - The new value for the x component.
        +   */
        +  set x(xVal) {
        +    if (this._values.length > 1) {
        +      this._values[0] = xVal;
             }
        -    if (Array.isArray(x)) {
        -      this.x += x[0] || 0;
        -      this.y += x[1] || 0;
        -      this.z += x[2] || 0;
        -      return this;
        +  }
        +
        +  /**
        +   * Sets the y component of the vector.
        +   *
        +   * This method allows you to change the y value of the vector.
        +   * The y value is the second number in the vector, representing the vertical position.
        +   * By calling this method, you can update the y value to a new number.
        +   *
        +   * @param {number} yVal - The new value for the y component.
        +   */
        +  set y(yVal) {
        +    if (this._values.length > 1) {
        +      this._values[1] = yVal;
             }
        -    this.x += x || 0;
        -    this.y += y || 0;
        -    this.z += z || 0;
        -    return this;
           }
         
           /**
        -   * @private
        -   * @chainable
        +   * Sets the z component of the vector.
        +   *
        +   * This method allows you to change the z value of the vector.
        +   * The z value is the third number in the vector, representing the depth or the third dimension.
        +   * By calling this method, you can update the z value to a new number.
        +   *
        +   * @param {number} zVal - The new value for the z component.
            */
        -  calculateRemainder2D (xComponent, yComponent) {
        -    if (xComponent !== 0) {
        -      this.x = this.x % xComponent;
        +  set z(zVal) {
        +    if (this._values.length > 2) {
        +      this._values[2] = zVal;
             }
        -    if (yComponent !== 0) {
        -      this.y = this.y % yComponent;
        +  }
        +
        +  /**
        +   * Sets the w component of the vector.
        +   *
        +   * This method allows you to change the w value of the vector.
        +   * The w value is the fourth number in the vector, representing the fourth dimension.
        +   * By calling this method, you can update the w value to a new number.
        +   *
        +   * @param {number} wVal - The new value for the w component.
        +   */
        +  set w(wVal) {
        +    if (this._values.length > 3) {
        +      this._values[3] = wVal;
             }
        -    return this;
           }
         
           /**
        -   * @private
        +   * Returns a string representation of a vector.
        +   *
        +   * Calling `toString()` is useful for printing vectors to the console while
        +   * debugging.
        +   *
        +   * @return {String} string representation of the vector.
        +   *
        +   * @example
        +   * <div class = "norender">
        +   * <code>
        +   * function setup() {
        +   *   let v = createVector(20, 30);
        +   *
        +   *   // Prints 'p5.Vector Object : [20, 30, 0]'.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  toString() {
        +    return `[${this.values.join(", ")}]`;
        +  }
        +
        +  /**
        +   * Sets the vector's `x`, `y`, and `z` components.
        +   *
        +   * `set()` can use separate numbers, as in `v.set(1, 2, 3)`, a
        +   * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.set(v2)`, or an
        +   * array of numbers, as in `v.set([1, 2, 3])`.
        +   *
        +   * If a value isn't provided for a component, it will be set to 0. For
        +   * example, `v.set(4, 5)` sets `v.x` to 4, `v.y` to 5, and `v.z` to 0.
        +   * Calling `set()` with no arguments, as in `v.set()`, sets all the vector's
        +   * components to 0.
        +   *
        +   * @param {Number} [x] x component of the vector.
        +   * @param {Number} [y] y component of the vector.
        +   * @param {Number} [z] z component of the vector.
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Top left.
        +   *   let pos = createVector(25, 25);
        +   *   point(pos);
        +   *
        +   *   // Top right.
        +   *   // set() with numbers.
        +   *   pos.set(75, 25);
        +   *   point(pos);
        +   *
        +   *   // Bottom right.
        +   *   // set() with a p5.Vector.
        +   *   let p2 = createVector(75, 75);
        +   *   pos.set(p2);
        +   *   point(pos);
        +   *
        +   *   // Bottom left.
        +   *   // set() with an array.
        +   *   let arr = [25, 75];
        +   *   pos.set(arr);
        +   *   point(pos);
        +   *
        +   *   describe('Four black dots arranged in a square on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @param {p5.Vector|Number[]} value vector to set.
            * @chainable
            */
        -  calculateRemainder3D (xComponent, yComponent, zComponent) {
        -    if (xComponent !== 0) {
        -      this.x = this.x % xComponent;
        +  set(...args) {
        +    if (args[0] instanceof Vector) {
        +      this.values = args[0].values.slice();
        +    } else if (Array.isArray(args[0])) {
        +      this.values = args[0].map((arg) => arg || 0);
        +    } else {
        +      this.values = args.map((arg) => arg || 0);
             }
        -    if (yComponent !== 0) {
        -      this.y = this.y % yComponent;
        +    this.dimensions = this.values.length;
        +    return this;
        +  }
        +
        +  /**
        +   * Returns a copy of the <a href="#/p5.Vector">p5.Vector</a> object.
        +   *
        +   * @return {p5.Vector} copy of the <a href="#/p5.Vector">p5.Vector</a> object.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100 ,100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Vector object.
        +   *   let pos = createVector(50, 50);
        +   *
        +   *   // Make a copy.
        +   *   let pc = pos.copy();
        +   *
        +   *   // Draw the point.
        +   *   strokeWeight(5);
        +   *   point(pc);
        +   *
        +   *   describe('A black point drawn in the middle of a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  copy() {
        +    if (this.isPInst) {
        +      return new Vector(this._fromRadians, this._toRadians, ...this.values);
        +    } else {
        +      return new Vector(...this.values);
             }
        -    if (zComponent !== 0) {
        -      this.z = this.z % zComponent;
        +  }
        +
        +  /**
        +   * Adds to a vector's `x`, `y`, and `z` components.
        +   *
        +   * `add()` can use separate numbers, as in `v.add(1, 2, 3)`,
        +   * another <a href="#/p5.Vector">p5.Vector</a> object, as in `v.add(v2)`, or
        +   * an array of numbers, as in `v.add([1, 2, 3])`.
        +   *
        +   * If a value isn't provided for a component, it won't change. For
        +   * example, `v.add(4, 5)` adds 4 to `v.x`, 5 to `v.y`, and 0 to `v.z`.
        +   * Calling `add()` with no arguments, as in `v.add()`, has no effect.
        +   *
        +   * The static version of `add()`, as in `p5.Vector.add(v2, v1)`, returns a new
        +   * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        +   * originals.
        +   *
        +   * @param  {Number} x   x component of the vector to be added.
        +   * @param  {Number} [y] y component of the vector to be added.
        +   * @param  {Number} [z] z component of the vector to be added.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Top left.
        +   *   let pos = createVector(25, 25);
        +   *   point(pos);
        +   *
        +   *   // Top right.
        +   *   // Add numbers.
        +   *   pos.add(50, 0);
        +   *   point(pos);
        +   *
        +   *   // Bottom right.
        +   *   // Add a p5.Vector.
        +   *   let p2 = createVector(0, 50);
        +   *   pos.add(p2);
        +   *   point(pos);
        +   *
        +   *   // Bottom left.
        +   *   // Add an array.
        +   *   let arr = [-50, 0];
        +   *   pos.add(arr);
        +   *   point(pos);
        +   *
        +   *   describe('Four black dots arranged in a square on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Top left.
        +   *   let p1 = createVector(25, 25);
        +   *
        +   *   // Center.
        +   *   let p2 = createVector(50, 50);
        +   *
        +   *   // Bottom right.
        +   *   // Add p1 and p2.
        +   *   let p3 = p5.Vector.add(p1, p2);
        +   *
        +   *   // Draw the points.
        +   *   strokeWeight(5);
        +   *   point(p1);
        +   *   point(p2);
        +   *   point(p3);
        +   *
        +   *   describe('Three black dots in a diagonal line from top left to bottom right.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Three arrows drawn on a gray square. A red arrow extends from the top left corner to the center. A blue arrow extends from the tip of the red arrow. A purple arrow extends from the origin to the tip of the blue arrow.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   let origin = createVector(0, 0);
        +   *
        +   *   // Draw the red arrow.
        +   *   let v1 = createVector(50, 50);
        +   *   drawArrow(origin, v1, 'red');
        +   *
        +   *   // Draw the blue arrow.
        +   *   let v2 = createVector(-30, 20);
        +   *   drawArrow(v1, v2, 'blue');
        +   *
        +   *   // Purple arrow.
        +   *   let v3 = p5.Vector.add(v1, v2);
        +   *   drawArrow(origin, v3, 'purple');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @param  {p5.Vector|Number[]} value The vector to add
        +   * @chainable
        +   */
        +  add(...args) {
        +    if (args[0] instanceof Vector) {
        +      args = args[0].values;
        +    } else if (Array.isArray(args[0])) {
        +      args = args[0];
             }
        +    args.forEach((value, index) => {
        +      this.values[index] = (this.values[index] || 0) + (value || 0);
        +    });
             return this;
           }
         
        @@ -480,7 +543,6 @@ p5.Vector = class {
            * new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
            * originals.
            *
        -   * @method rem
            * @param {Number} x x component of divisor vector.
            * @param {Number} y y component of divisor vector.
            * @param {Number} z z component of divisor vector.
        @@ -581,1249 +643,1154 @@ p5.Vector = class {
            * </div>
            */
           /**
        -   * @method rem
            * @param {p5.Vector | Number[]}  value  divisor vector.
            * @chainable
            */
        -  rem (...args) {
        -    let [x, y, z] = args;
        -    if (x instanceof p5.Vector) {
        -      if ([x.x,x.y,x.z].every(Number.isFinite)) {
        +  rem(x, y, z) {
        +    if (x instanceof Vector) {
        +      if ([x.x, x.y, x.z].every(Number.isFinite)) {
                 const xComponent = parseFloat(x.x);
                 const yComponent = parseFloat(x.y);
                 const zComponent = parseFloat(x.z);
        -        return this.calculateRemainder3D(
        +        return calculateRemainder3D.call(
        +          this,
                   xComponent,
                   yComponent,
                   zComponent
                 );
               }
             } else if (Array.isArray(x)) {
        -      if (x.every(Number.isFinite)) {
        +      if (x.every((element) => Number.isFinite(element))) {
                 if (x.length === 2) {
        -          return this.calculateRemainder2D(x[0], x[1]);
        +          return calculateRemainder2D.call(this, x[0], x[1]);
                 }
                 if (x.length === 3) {
        -          return this.calculateRemainder3D(x[0], x[1], x[2]);
        +          return calculateRemainder3D.call(this, x[0], x[1], x[2]);
                 }
               }
        -    } else if (args.length === 1) {
        -      if (Number.isFinite(x) && x !== 0) {
        -        this.x = this.x % x;
        -        this.y = this.y % x;
        -        this.z = this.z % x;
        +    } else if (arguments.length === 1) {
        +      if (Number.isFinite(arguments[0]) && arguments[0] !== 0) {
        +        this.x = this.x % arguments[0];
        +        this.y = this.y % arguments[0];
        +        this.z = this.z % arguments[0];
                 return this;
               }
        -    } else if (args.length === 2) {
        -      if (args.every(Number.isFinite)) {
        -        return this.calculateRemainder2D(
        -          x,
        -          y
        -        );
        +    } else if (arguments.length === 2) {
        +      const vectorComponents = [...arguments];
        +      if (vectorComponents.every((element) => Number.isFinite(element))) {
        +        if (vectorComponents.length === 2) {
        +          return calculateRemainder2D.call(
        +            this,
        +            vectorComponents[0],
        +            vectorComponents[1]
        +          );
        +        }
               }
        -    } else if (args.length === 3) {
        -      if (args.every(Number.isFinite)) {
        -        return this.calculateRemainder3D(
        -          x,
        -          y,
        -          z
        -        );
        +    } else if (arguments.length === 3) {
        +      const vectorComponents = [...arguments];
        +      if (vectorComponents.every((element) => Number.isFinite(element))) {
        +        if (vectorComponents.length === 3) {
        +          return calculateRemainder3D.call(
        +            this,
        +            vectorComponents[0],
        +            vectorComponents[1],
        +            vectorComponents[2]
        +          );
        +        }
               }
             }
           }
         
           /**
        - * Subtracts from a vector's `x`, `y`, and `z` components.
        - *
        - * `sub()` can use separate numbers, as in `v.sub(1, 2, 3)`, another
        - * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.sub(v2)`, or an array
        - * of numbers, as in `v.sub([1, 2, 3])`.
        - *
        - * If a value isn't provided for a component, it won't change. For
        - * example, `v.sub(4, 5)` subtracts 4 from `v.x`, 5 from `v.y`, and 0 from `v.z`.
        - * Calling `sub()` with no arguments, as in `v.sub()`, has no effect.
        - *
        - * The static version of `sub()`, as in `p5.Vector.sub(v2, v1)`, returns a new
        - * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        - * originals.
        - *
        - * @method sub
        - * @param  {Number} x   x component of the vector to subtract.
        - * @param  {Number} [y] y component of the vector to subtract.
        - * @param  {Number} [z] z component of the vector to subtract.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Bottom right.
        - *   let pos = createVector(75, 75);
        - *   point(pos);
        - *
        - *   // Top right.
        - *   // Subtract numbers.
        - *   pos.sub(0, 50);
        - *   point(pos);
        - *
        - *   // Top left.
        - *   // Subtract a p5.Vector.
        - *   let p2 = createVector(50, 0);
        - *   pos.sub(p2);
        - *   point(pos);
        - *
        - *   // Bottom left.
        - *   // Subtract an array.
        - *   let arr = [0, -50];
        - *   pos.sub(arr);
        - *   point(pos);
        - *
        - *   describe('Four black dots arranged in a square on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create p5.Vector objects.
        - *   let p1 = createVector(75, 75);
        - *   let p2 = createVector(50, 50);
        - *
        - *   // Subtract without modifying the original vectors.
        - *   let p3 = p5.Vector.sub(p1, p2);
        - *
        - *   // Draw the points.
        - *   strokeWeight(5);
        - *   point(p1);
        - *   point(p2);
        - *   point(p3);
        - *
        - *   describe('Three black dots in a diagonal line from top left to bottom right.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Three arrows drawn on a gray square. A red and a blue arrow extend from the top left. A purple arrow extends from the tip of the red arrow to the tip of the blue arrow.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   let origin = createVector(0, 0);
        - *
        - *   // Draw the red arrow.
        - *   let v1 = createVector(50, 50);
        - *   drawArrow(origin, v1, 'red');
        - *
        - *   // Draw the blue arrow.
        - *   let v2 = createVector(20, 70);
        - *   drawArrow(origin, v2, 'blue');
        - *
        - *   // Purple arrow.
        - *   let v3 = p5.Vector.sub(v2, v1);
        - *   drawArrow(v1, v3, 'purple');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        -  /**
        - * @method sub
        - * @param  {p5.Vector|Number[]} value the vector to subtract
        - * @chainable
        - */
        -  sub(x, y, z) {
        -    if (x instanceof p5.Vector) {
        -      this.x -= x.x || 0;
        -      this.y -= x.y || 0;
        -      this.z -= x.z || 0;
        -      return this;
        -    }
        -    if (Array.isArray(x)) {
        -      this.x -= x[0] || 0;
        -      this.y -= x[1] || 0;
        -      this.z -= x[2] || 0;
        -      return this;
        -    }
        -    this.x -= x || 0;
        -    this.y -= y || 0;
        -    this.z -= z || 0;
        -    return this;
        -  }
        -
        -  /**
        - * Multiplies a vector's `x`, `y`, and `z` components.
        - *
        - * `mult()` can use separate numbers, as in `v.mult(1, 2, 3)`, another
        - * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.mult(v2)`, or an array
        - * of numbers, as in `v.mult([1, 2, 3])`.
        - *
        - * If only one value is provided, as in `v.mult(2)`, then all the components
        - * will be multiplied by 2. If a value isn't provided for a component, it
        - * won't change. For example, `v.mult(4, 5)` multiplies `v.x` by 4, `v.y` by 5,
        - * and `v.z` by 1. Calling `mult()` with no arguments, as in `v.mult()`, has
        - * no effect.
        - *
        - * The static version of `mult()`, as in `p5.Vector.mult(v, 2)`, returns a new
        - * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        - * originals.
        - *
        - * @method mult
        - * @param  {Number} n The number to multiply with the vector
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Top-left.
        - *   let p = createVector(25, 25);
        - *   point(p);
        - *
        - *   // Center.
        - *   // Multiply all components by 2.
        - *   p.mult(2);
        - *   point(p);
        - *
        - *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   strokeWeight(5);
        - *
        - *   // Top-left.
        - *   let p = createVector(25, 25);
        - *   point(p);
        - *
        - *   // Bottom-right.
        - *   // Multiply p.x * 2 and p.y * 3
        - *   p.mult(2, 3);
        - *   point(p);
        - *
        - *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Top-left.
        - *   let p = createVector(25, 25);
        - *   point(p);
        - *
        - *   // Bottom-right.
        - *   // Multiply p.x * 2 and p.y * 3
        - *   let arr = [2, 3];
        - *   p.mult(arr);
        - *   point(p);
        - *
        - *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Top-left.
        - *   let p = createVector(25, 25);
        - *   point(p);
        - *
        - *   // Bottom-right.
        - *   // Multiply p.x * p2.x and p.y * p2.y
        - *   let p2 = createVector(2, 3);
        - *   p.mult(p2);
        - *   point(p);
        - *
        - *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Top-left.
        - *   let p = createVector(25, 25);
        - *   point(p);
        - *
        - *   // Bottom-right.
        - *   // Create a new p5.Vector with
        - *   // p3.x = p.x * p2.x
        - *   // p3.y = p.y * p2.y
        - *   let p2 = createVector(2, 3);
        - *   let p3 = p5.Vector.mult(p, p2);
        - *   point(p3);
        - *
        - *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Two arrows extending from the top left corner. The blue arrow is twice the length of the red arrow.');
        - * }
        - * function draw() {
        - *   background(200);
        - *
        - *   let origin = createVector(0, 0);
        - *
        - *   // Draw the red arrow.
        - *   let v1 = createVector(25, 25);
        - *   drawArrow(origin, v1, 'red');
        - *
        - *   // Draw the blue arrow.
        - *   let v2 = p5.Vector.mult(v1, 2);
        - *   drawArrow(origin, v2, 'blue');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        -
        -  /**
        - * @method mult
        - * @param  {Number} x number to multiply with the x component of the vector.
        - * @param  {Number} y number to multiply with the y component of the vector.
        - * @param  {Number} [z] number to multiply with the z component of the vector.
        - * @chainable
        - */
        -
        -  /**
        - * @method mult
        - * @param  {Number[]} arr array to multiply with the components of the vector.
        - * @chainable
        - */
        -
        -  /**
        - * @method mult
        - * @param  {p5.Vector} v vector to multiply with the components of the original vector.
        - * @chainable
        - */
        -
        -  mult(...args) {
        -    let [x, y, z] = args;
        -    if (x instanceof p5.Vector) {
        -    // new p5.Vector will check that values are valid upon construction but it's possible
        -    // that someone could change the value of a component after creation, which is why we still
        -    // perform this check
        -      if (
        -        Number.isFinite(x.x) &&
        -      Number.isFinite(x.y) &&
        -      Number.isFinite(x.z) &&
        -      typeof x.x === 'number' &&
        -      typeof x.y === 'number' &&
        -      typeof x.z === 'number'
        -      ) {
        -        this.x *= x.x;
        -        this.y *= x.y;
        -        this.z *= x.z;
        -      } else {
        -        console.warn(
        -          'p5.Vector.prototype.mult:',
        -          'x contains components that are either undefined or not finite numbers'
        -        );
        -      }
        -      return this;
        -    }
        -    if (Array.isArray(x)) {
        -      if (
        -        x.every(element => Number.isFinite(element)) &&
        -      x.every(element => typeof element === 'number')
        -      ) {
        -        if (x.length === 1) {
        -          this.x *= x[0];
        -          this.y *= x[0];
        -          this.z *= x[0];
        -        } else if (x.length === 2) {
        -          this.x *= x[0];
        -          this.y *= x[1];
        -        } else if (x.length === 3) {
        -          this.x *= x[0];
        -          this.y *= x[1];
        -          this.z *= x[2];
        -        }
        -      } else {
        -        console.warn(
        -          'p5.Vector.prototype.mult:',
        -          'x contains elements that are either undefined or not finite numbers'
        -        );
        -      }
        -      return this;
        -    }
        -
        -    const vectorComponents = args;
        -    if (
        -      vectorComponents.every(element => Number.isFinite(element)) &&
        -    vectorComponents.every(element => typeof element === 'number')
        -    ) {
        -      if (args.length === 1) {
        -        this.x *= x;
        -        this.y *= x;
        -        this.z *= x;
        -      }
        -      if (args.length === 2) {
        -        this.x *= x;
        -        this.y *= y;
        -      }
        -      if (args.length === 3) {
        -        this.x *= x;
        -        this.y *= y;
        -        this.z *= z;
        -      }
        +   * Subtracts from a vector's `x`, `y`, and `z` components.
        +   *
        +   * `sub()` can use separate numbers, as in `v.sub(1, 2, 3)`, another
        +   * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.sub(v2)`, or an array
        +   * of numbers, as in `v.sub([1, 2, 3])`.
        +   *
        +   * If a value isn't provided for a component, it won't change. For
        +   * example, `v.sub(4, 5)` subtracts 4 from `v.x`, 5 from `v.y`, and 0 from `v.z`.
        +   * Calling `sub()` with no arguments, as in `v.sub()`, has no effect.
        +   *
        +   * The static version of `sub()`, as in `p5.Vector.sub(v2, v1)`, returns a new
        +   * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        +   * originals.
        +   *
        +   * @method sub
        +   * @param  {Number} x   x component of the vector to subtract.
        +   * @param  {Number} [y] y component of the vector to subtract.
        +   * @param  {Number} [z] z component of the vector to subtract.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Bottom right.
        +   *   let pos = createVector(75, 75);
        +   *   point(pos);
        +   *
        +   *   // Top right.
        +   *   // Subtract numbers.
        +   *   pos.sub(0, 50);
        +   *   point(pos);
        +   *
        +   *   // Top left.
        +   *   // Subtract a p5.Vector.
        +   *   let p2 = createVector(50, 0);
        +   *   pos.sub(p2);
        +   *   point(pos);
        +   *
        +   *   // Bottom left.
        +   *   // Subtract an array.
        +   *   let arr = [0, -50];
        +   *   pos.sub(arr);
        +   *   point(pos);
        +   *
        +   *   describe('Four black dots arranged in a square on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   let p1 = createVector(75, 75);
        +   *   let p2 = createVector(50, 50);
        +   *
        +   *   // Subtract with modifying the original vectors.
        +   *   let p3 = p5.Vector.sub(p1, p2);
        +   *
        +   *   // Draw the points.
        +   *   strokeWeight(5);
        +   *   point(p1);
        +   *   point(p2);
        +   *   point(p3);
        +   *
        +   *   describe('Three black dots in a diagonal line from top left to bottom right.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Three arrows drawn on a gray square. A red and a blue arrow extend from the top left. A purple arrow extends from the tip of the red arrow to the tip of the blue arrow.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   let origin = createVector(0, 0);
        +   *
        +   *   // Draw the red arrow.
        +   *   let v1 = createVector(50, 50);
        +   *   drawArrow(origin, v1, 'red');
        +   *
        +   *   // Draw the blue arrow.
        +   *   let v2 = createVector(20, 70);
        +   *   drawArrow(origin, v2, 'blue');
        +   *
        +   *   // Purple arrow.
        +   *   let v3 = p5.Vector.sub(v2, v1);
        +   *   drawArrow(v1, v3, 'purple');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @param  {p5.Vector|Number[]} value the vector to subtract
        +   * @chainable
        +   */
        +  sub(...args) {
        +    if (args[0] instanceof Vector) {
        +      args[0].values.forEach((value, index) => {
        +        this.values[index] -= value || 0;
        +      });
        +    } else if (Array.isArray(args[0])) {
        +      args[0].forEach((value, index) => {
        +        this.values[index] -= value || 0;
        +      });
             } else {
        -      console.warn(
        -        'p5.Vector.prototype.mult:',
        -        'x, y, or z arguments are either undefined or not a finite number'
        -      );
        +      args.forEach((value, index) => {
        +        this.values[index] -= value || 0;
        +      });
             }
        -
             return this;
           }
         
           /**
        - * Divides a vector's `x`, `y`, and `z` components.
        - *
        - * `div()` can use separate numbers, as in `v.div(1, 2, 3)`, another
        - * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.div(v2)`, or an array
        - * of numbers, as in `v.div([1, 2, 3])`.
        - *
        - * If only one value is provided, as in `v.div(2)`, then all the components
        - * will be divided by 2. If a value isn't provided for a component, it
        - * won't change. For example, `v.div(4, 5)` divides `v.x` by, `v.y` by 5,
        - * and `v.z` by 1. Calling `div()` with no arguments, as in `v.div()`, has
        - * no effect.
        - *
        - * The static version of `div()`, as in `p5.Vector.div(v, 2)`, returns a new
        - * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        - * originals.
        - *
        - * @method div
        - * @param  {number}    n The number to divide the vector by
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Center.
        - *   let p = createVector(50, 50);
        - *   point(p);
        - *
        - *   // Top-left.
        - *   // Divide p.x / 2 and p.y / 2
        - *   p.div(2);
        - *   point(p);
        - *
        - *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Bottom-right.
        - *   let p = createVector(50, 75);
        - *   point(p);
        - *
        - *   // Top-left.
        - *   // Divide p.x / 2 and p.y / 3
        - *   p.div(2, 3);
        - *   point(p);
        - *
        - *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Bottom-right.
        - *   let p = createVector(50, 75);
        - *   point(p);
        - *
        - *   // Top-left.
        - *   // Divide p.x / 2 and p.y / 3
        - *   let arr = [2, 3];
        - *   p.div(arr);
        - *   point(p);
        - *
        - *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Bottom-right.
        - *   let p = createVector(50, 75);
        - *   point(p);
        - *
        - *   // Top-left.
        - *   // Divide p.x / 2 and p.y / 3
        - *   let p2 = createVector(2, 3);
        - *   p.div(p2);
        - *   point(p);
        - *
        - *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the points.
        - *   strokeWeight(5);
        - *
        - *   // Bottom-right.
        - *   let p = createVector(50, 75);
        - *   point(p);
        - *
        - *   // Top-left.
        - *   // Create a new p5.Vector with
        - *   // p3.x = p.x / p2.x
        - *   // p3.y = p.y / p2.y
        - *   let p2 = createVector(2, 3);
        - *   let p3 = p5.Vector.div(p, p2);
        - *   point(p3);
        - *
        - *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function draw() {
        - *   background(200);
        - *
        - *   let origin = createVector(0, 0);
        - *
        - *   // Draw the red arrow.
        - *   let v1 = createVector(50, 50);
        - *   drawArrow(origin, v1, 'red');
        - *
        - *   // Draw the blue arrow.
        - *   let v2 = p5.Vector.div(v1, 2);
        - *   drawArrow(origin, v2, 'blue');
        - *
        - *   describe('Two arrows extending from the top left corner. The blue arrow is half the length of the red arrow.');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        -
        +   * Multiplies a vector's `x`, `y`, and `z` components.
        +   *
        +   * `mult()` can use separate numbers, as in `v.mult(1, 2, 3)`, another
        +   * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.mult(v2)`, or an array
        +   * of numbers, as in `v.mult([1, 2, 3])`.
        +   *
        +   * If only one value is provided, as in `v.mult(2)`, then all the components
        +   * will be multiplied by 2. If a value isn't provided for a component, it
        +   * won't change. For example, `v.mult(4, 5)` multiplies `v.x` by, `v.y` by 5,
        +   * and `v.z` by 1. Calling `mult()` with no arguments, as in `v.mult()`, has
        +   * no effect.
        +   *
        +   * The static version of `mult()`, as in `p5.Vector.mult(v, 2)`, returns a new
        +   * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        +   * originals.
        +   *
        +   * @method mult
        +   * @param  {Number} n The number to multiply with the vector
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Top-left.
        +   *   let p = createVector(25, 25);
        +   *   point(p);
        +   *
        +   *   // Center.
        +   *   // Multiply all components by 2.
        +   *   p.mult(2);
        +   *   point(p);
        +   *
        +   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   strokeWeight(5);
        +   *
        +   *   // Top-left.
        +   *   let p = createVector(25, 25);
        +   *   point(p);
        +   *
        +   *   // Bottom-right.
        +   *   // Multiply p.x * 2 and p.y * 3
        +   *   p.mult(2, 3);
        +   *   point(p);
        +   *
        +   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Top-left.
        +   *   let p = createVector(25, 25);
        +   *   point(p);
        +   *
        +   *   // Bottom-right.
        +   *   // Multiply p.x * 2 and p.y * 3
        +   *   let arr = [2, 3];
        +   *   p.mult(arr);
        +   *   point(p);
        +   *
        +   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Top-left.
        +   *   let p = createVector(25, 25);
        +   *   point(p);
        +   *
        +   *   // Bottom-right.
        +   *   // Multiply p.x * p2.x and p.y * p2.y
        +   *   let p2 = createVector(2, 3);
        +   *   p.mult(p2);
        +   *   point(p);
        +   *
        +   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Top-left.
        +   *   let p = createVector(25, 25);
        +   *   point(p);
        +   *
        +   *   // Bottom-right.
        +   *   // Create a new p5.Vector with
        +   *   // p3.x = p.x * p2.x
        +   *   // p3.y = p.y * p2.y
        +   *   let p2 = createVector(2, 3);
        +   *   let p3 = p5.Vector.mult(p, p2);
        +   *   point(p3);
        +   *
        +   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Two arrows extending from the top left corner. The blue arrow is twice the length of the red arrow.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   let origin = createVector(0, 0);
        +   *
        +   *   // Draw the red arrow.
        +   *   let v1 = createVector(25, 25);
        +   *   drawArrow(origin, v1, 'red');
        +   *
        +   *   // Draw the blue arrow.
        +   *   let v2 = p5.Vector.mult(v1, 2);
        +   *   drawArrow(origin, v2, 'blue');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           /**
        - * @method div
        - * @param  {Number} x number to divide with the x component of the vector.
        - * @param  {Number} y number to divide with the y component of the vector.
        - * @param  {Number} [z] number to divide with the z component of the vector.
        - * @chainable
        - */
        -
        +   * @param  {Number} x number to multiply with the x component of the vector.
        +   * @param  {Number} y number to multiply with the y component of the vector.
        +   * @param  {Number} [z] number to multiply with the z component of the vector.
        +   * @chainable
        +   */
           /**
        - * @method div
        - * @param  {Number[]} arr array to divide the components of the vector by.
        - * @chainable
        - */
        +   * @param  {Number[]} arr array to multiply with the components of the vector.
        +   * @chainable
        +   */
        +  /**
        +   * @param  {p5.Vector} v vector to multiply with the components of the original vector.
        +   * @chainable
        +   */
        +  mult(...args) {
        +    if (args.length === 1 && args[0] instanceof Vector) {
        +      const v = args[0];
        +      const maxLen = Math.min(this.values.length, v.values.length);
        +      for (let i = 0; i < maxLen; i++) {
        +        if (Number.isFinite(v.values[i]) && typeof v.values[i] === "number") {
        +          this._values[i] *= v.values[i];
        +        } else {
        +          console.warn(
        +            "p5.Vector.prototype.mult:",
        +            "v contains components that are either undefined or not finite numbers"
        +          );
        +          return this;
        +        }
        +      }
        +    } else if (args.length === 1 && Array.isArray(args[0])) {
        +      const arr = args[0];
        +      const maxLen = Math.min(this.values.length, arr.length);
        +      for (let i = 0; i < maxLen; i++) {
        +        if (Number.isFinite(arr[i]) && typeof arr[i] === "number") {
        +          this._values[i] *= arr[i];
        +        } else {
        +          console.warn(
        +            "p5.Vector.prototype.mult:",
        +            "arr contains elements that are either undefined or not finite numbers"
        +          );
        +          return this;
        +        }
        +      }
        +    } else if (
        +      args.length === 1 &&
        +      typeof args[0] === "number" &&
        +      Number.isFinite(args[0])
        +    ) {
        +      for (let i = 0; i < this._values.length; i++) {
        +        this._values[i] *= args[0];
        +      }
        +    }
        +    return this;
        +  }
         
           /**
        - * @method div
        - * @param  {p5.Vector} v vector to divide the components of the original vector by.
        - * @chainable
        - */
        +   * Divides a vector's `x`, `y`, and `z` components.
        +   *
        +   * `div()` can use separate numbers, as in `v.div(1, 2, 3)`, another
        +   * <a href="#/p5.Vector">p5.Vector</a> object, as in `v.div(v2)`, or an array
        +   * of numbers, as in `v.div([1, 2, 3])`.
        +   *
        +   * If only one value is provided, as in `v.div(2)`, then all the components
        +   * will be divided by 2. If a value isn't provided for a component, it
        +   * won't change. For example, `v.div(4, 5)` divides `v.x` by, `v.y` by 5,
        +   * and `v.z` by 1. Calling `div()` with no arguments, as in `v.div()`, has
        +   * no effect.
        +   *
        +   * The static version of `div()`, as in `p5.Vector.div(v, 2)`, returns a new
        +   * <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        +   * originals.
        +   *
        +   * @param  {Number}    n The number to divide the vector by
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Center.
        +   *   let p = createVector(50, 50);
        +   *   point(p);
        +   *
        +   *   // Top-left.
        +   *   // Divide p.x / 2 and p.y / 2
        +   *   p.div(2);
        +   *   point(p);
        +   *
        +   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Bottom-right.
        +   *   let p = createVector(50, 75);
        +   *   point(p);
        +   *
        +   *   // Top-left.
        +   *   // Divide p.x / 2 and p.y / 3
        +   *   p.div(2, 3);
        +   *   point(p);
        +   *
        +   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Bottom-right.
        +   *   let p = createVector(50, 75);
        +   *   point(p);
        +   *
        +   *   // Top-left.
        +   *   // Divide p.x / 2 and p.y / 3
        +   *   let arr = [2, 3];
        +   *   p.div(arr);
        +   *   point(p);
        +   *
        +   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Bottom-right.
        +   *   let p = createVector(50, 75);
        +   *   point(p);
        +   *
        +   *   // Top-left.
        +   *   // Divide p.x / 2 and p.y / 3
        +   *   let p2 = createVector(2, 3);
        +   *   p.div(p2);
        +   *   point(p);
        +   *
        +   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Bottom-right.
        +   *   let p = createVector(50, 75);
        +   *   point(p);
        +   *
        +   *   // Top-left.
        +   *   // Create a new p5.Vector with
        +   *   // p3.x = p.x / p2.x
        +   *   // p3.y = p.y / p2.y
        +   *   let p2 = createVector(2, 3);
        +   *   let p3 = p5.Vector.div(p, p2);
        +   *   point(p3);
        +   *
        +   *   describe('Two black dots drawn on a gray square. One dot is in the top left corner and the other is in the bottom center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   let origin = createVector(0, 0);
        +   *
        +   *   // Draw the red arrow.
        +   *   let v1 = createVector(50, 50);
        +   *   drawArrow(origin, v1, 'red');
        +   *
        +   *   // Draw the blue arrow.
        +   *   let v2 = p5.Vector.div(v1, 2);
        +   *   drawArrow(origin, v2, 'blue');
        +   *
        +   *   describe('Two arrows extending from the top left corner. The blue arrow is half the length of the red arrow.');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @param  {Number} x number to divide with the x component of the vector.
        +   * @param  {Number} y number to divide with the y component of the vector.
        +   * @param  {Number} [z] number to divide with the z component of the vector.
        +   * @chainable
        +   */
        +  /**
        +   * @param  {Number[]} arr array to divide the components of the vector by.
        +   * @chainable
        +   */
        +  /**
        +   * @param  {p5.Vector} v vector to divide the components of the original vector by.
        +   * @chainable
        +   */
           div(...args) {
        -    let [x, y, z] = args;
        -    if (x instanceof p5.Vector) {
        -    // new p5.Vector will check that values are valid upon construction but it's possible
        -    // that someone could change the value of a component after creation, which is why we still
        -    // perform this check
        +    if (args.length === 0) return this;
        +    if (args.length === 1 && args[0] instanceof Vector) {
        +      const v = args[0];
               if (
        -        Number.isFinite(x.x) &&
        -      Number.isFinite(x.y) &&
        -      Number.isFinite(x.z) &&
        -      typeof x.x === 'number' &&
        -      typeof x.y === 'number' &&
        -      typeof x.z === 'number'
        +        v._values.every(
        +          (val) => Number.isFinite(val) && typeof val === "number"
        +        )
               ) {
        -        const isLikely2D = x.z === 0 && this.z === 0;
        -        if (x.x === 0 || x.y === 0 || (!isLikely2D && x.z === 0)) {
        -          console.warn('p5.Vector.prototype.div:', 'divide by 0');
        +        if (v._values.some((val) => val === 0)) {
        +          console.warn("p5.Vector.prototype.div:", "divide by 0");
                   return this;
                 }
        -        this.x /= x.x;
        -        this.y /= x.y;
        -        if (!isLikely2D) {
        -          this.z /= x.z;
        -        }
        +        this._values = this._values.map((val, i) => val / v._values[i]);
               } else {
                 console.warn(
        -          'p5.Vector.prototype.div:',
        -          'x contains components that are either undefined or not finite numbers'
        +          "p5.Vector.prototype.div:",
        +          "vector contains components that are either undefined or not finite numbers"
                 );
               }
               return this;
             }
        -    if (Array.isArray(x)) {
        -      if (
        -        x.every(Number.isFinite) &&
        -      x.every(element => typeof element === 'number')
        -      ) {
        -        if (x.some(element => element === 0)) {
        -          console.warn('p5.Vector.prototype.div:', 'divide by 0');
        -          return this;
        -        }
         
        -        if (x.length === 1) {
        -          this.x /= x[0];
        -          this.y /= x[0];
        -          this.z /= x[0];
        -        } else if (x.length === 2) {
        -          this.x /= x[0];
        -          this.y /= x[1];
        -        } else if (x.length === 3) {
        -          this.x /= x[0];
        -          this.y /= x[1];
        -          this.z /= x[2];
        +    if (args.length === 1 && Array.isArray(args[0])) {
        +      const arr = args[0];
        +      if (arr.every((val) => Number.isFinite(val) && typeof val === "number")) {
        +        if (arr.some((val) => val === 0)) {
        +          console.warn("p5.Vector.prototype.div:", "divide by 0");
        +          return this;
                 }
        +        this._values = this._values.map((val, i) => val / arr[i]);
               } else {
                 console.warn(
        -          'p5.Vector.prototype.div:',
        -          'x contains components that are either undefined or not finite numbers'
        +          "p5.Vector.prototype.div:",
        +          "array contains components that are either undefined or not finite numbers"
                 );
               }
        -
               return this;
             }
         
        -    if (
        -      args.every(Number.isFinite) &&
        -    args.every(element => typeof element === 'number')
        -    ) {
        -      if (args.some(element => element === 0)) {
        -        console.warn('p5.Vector.prototype.div:', 'divide by 0');
        +    if (args.every((val) => Number.isFinite(val) && typeof val === "number")) {
        +      if (args.some((val) => val === 0)) {
        +        console.warn("p5.Vector.prototype.div:", "divide by 0");
                 return this;
               }
        -
        -      if (args.length === 1) {
        -        this.x /= x;
        -        this.y /= x;
        -        this.z /= x;
        -      }
        -      if (args.length === 2) {
        -        this.x /= x;
        -        this.y /= y;
        -      }
        -      if (args.length === 3) {
        -        this.x /= x;
        -        this.y /= y;
        -        this.z /= z;
        -      }
        +      this._values = this._values.map((val, i) => val / args[0]);
             } else {
               console.warn(
        -        'p5.Vector.prototype.div:',
        -        'x, y, or z arguments are either undefined or not a finite number'
        +        "p5.Vector.prototype.div:",
        +        "arguments contain components that are either undefined or not finite numbers"
               );
             }
         
             return this;
           }
        -  /**
        - * Calculates the magnitude (length) of the vector.
        - *
        - * Use <a href="#/p5/mag">mag()</a> to calculate the magnitude of a 2D vector
        - * using components as in `mag(x, y)`.
        - *
        - * @method mag
        - * @return {Number} magnitude of the vector.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Vector object.
        - *   let p = createVector(30, 40);
        - *
        - *   // Draw a line from the origin.
        - *   line(0, 0, p.x, p.y);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the vector's magnitude.
        - *   let m = p.mag();
        - *   text(m, p.x, p.y);
        - *
        - *   describe('A diagonal black line extends from the top left corner of a gray square. The number 50 is written at the end of the line.');
        - * }
        - * </code>
        - * </div>
        - */
        -  mag() {
        -    return Math.sqrt(this.magSq());
        -  }
         
           /**
        - * Calculates the magnitude (length) of the vector squared.
        - *
        - * @method magSq
        - * @return {number} squared magnitude of the vector.
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Vector object.
        - *   let p = createVector(30, 40);
        - *
        - *   // Draw a line from the origin.
        - *   line(0, 0, p.x, p.y);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *
        - *   // Display the vector's magnitude squared.
        - *   let m = p.magSq();
        - *   text(m, p.x, p.y);
        - *
        - *   describe('A diagonal black line extends from the top left corner of a gray square. The number 2500 is written at the end of the line.');
        - * }
        - * </code>
        - * </div>
        - */
        -  magSq() {
        -    const x = this.x;
        -    const y = this.y;
        -    const z = this.z;
        -    return x * x + y * y + z * z;
        -  }
        -
        -  /**
        - * Calculates the dot product of two vectors.
        - *
        - * The dot product is a number that describes the overlap between two vectors.
        - * Visually, the dot product can be thought of as the "shadow" one vector
        - * casts on another. The dot product's magnitude is largest when two vectors
        - * point in the same or opposite directions. Its magnitude is 0 when two
        - * vectors form a right angle.
        - *
        - * The version of `dot()` with one parameter interprets it as another
        - * <a href="#/p5.Vector">p5.Vector</a> object.
        - *
        - * The version of `dot()` with multiple parameters interprets them as the
        - * `x`, `y`, and `z` components of another vector.
        - *
        - * The static version of `dot()`, as in `p5.Vector.dot(v1, v2)`, is the same
        - * as calling `v1.dot(v2)`.
        - *
        - * @method dot
        - * @param  {Number} x   x component of the vector.
        - * @param  {Number} [y] y component of the vector.
        - * @param  {Number} [z] z component of the vector.
        - * @return {Number}     dot product.
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create p5.Vector objects.
        - *   let v1 = createVector(3, 4);
        - *   let v2 = createVector(3, 0);
        - *
        - *   // Calculate the dot product.
        - *   let dp = v1.dot(v2);
        - *
        - *   // Prints "9" to the console.
        - *   print(dp);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create p5.Vector objects.
        - *   let v1 = createVector(1, 0);
        - *   let v2 = createVector(0, 1);
        - *
        - *   // Calculate the dot product.
        - *   let dp = p5.Vector.dot(v1, v2);
        - *
        - *   // Prints "0" to the console.
        - *   print(dp);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Two arrows drawn on a gray square. A black arrow points to the right and a red arrow follows the mouse. The text "v1 • v2 = something" changes as the mouse moves.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Center.
        - *   let v0 = createVector(50, 50);
        - *
        - *   // Draw the black arrow.
        - *   let v1 = createVector(30, 0);
        - *   drawArrow(v0, v1, 'black');
        - *
        - *   // Draw the red arrow.
        - *   let v2 = createVector(mouseX - 50, mouseY - 50);
        - *   drawArrow(v0, v2, 'red');
        - *
        - *   // Display the dot product.
        - *   let dp = v2.dot(v1);
        - *   text(`v2 • v1 = ${dp}`, 10, 20);
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Calculates the magnitude (length) of the vector.
        +   *
        +   * Use <a href="#/p5/mag">mag()</a> to calculate the magnitude of a 2D vector
        +   * using components as in `mag(x, y)`.
        +   *
        +   * @return {Number} magnitude of the vector.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Vector object.
        +   *   let p = createVector(30, 40);
        +   *
        +   *   // Draw a line from the origin.
        +   *   line(0, 0, p.x, p.y);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the vector's magnitude.
        +   *   let m = p.mag();
        +   *   text(m, p.x, p.y);
        +   *
        +   *   describe('A diagonal black line extends from the top left corner of a gray square. The number 50 is written at the end of the line.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  mag() {
        +    return Math.sqrt(this.magSq());
        +  }
        +
           /**
        - * @method dot
        - * @param  {p5.Vector} v <a href="#/p5.Vector">p5.Vector</a> to be dotted.
        - * @return {Number}
        - */
        -  dot(x, y, z) {
        -    if (x instanceof p5.Vector) {
        -      return this.dot(x.x, x.y, x.z);
        +   * Calculates the magnitude (length) of the vector squared.
        +   *
        +   * @return {Number} squared magnitude of the vector.
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Vector object.
        +   *   let p = createVector(30, 40);
        +   *
        +   *   // Draw a line from the origin.
        +   *   line(0, 0, p.x, p.y);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the vector's magnitude squared.
        +   *   let m = p.magSq();
        +   *   text(m, p.x, p.y);
        +   *
        +   *   describe('A diagonal black line extends from the top left corner of a gray square. The number 2500 is written at the end of the line.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  magSq() {
        +    return this._values.reduce(
        +      (sum, component) => sum + component * component,
        +      0
        +    );
        +  }
        +
        +  /**
        +   * Calculates the dot product of two vectors.
        +   *
        +   * The dot product is a number that describes the overlap between two vectors.
        +   * Visually, the dot product can be thought of as the "shadow" one vector
        +   * casts on another. The dot product's magnitude is largest when two vectors
        +   * point in the same or opposite directions. Its magnitude is 0 when two
        +   * vectors form a right angle.
        +   *
        +   * The version of `dot()` with one parameter interprets it as another
        +   * <a href="#/p5.Vector">p5.Vector</a> object.
        +   *
        +   * The version of `dot()` with multiple parameters interprets them as the
        +   * `x`, `y`, and `z` components of another vector.
        +   *
        +   * The static version of `dot()`, as in `p5.Vector.dot(v1, v2)`, is the same
        +   * as calling `v1.dot(v2)`.
        +   *
        +   * @param  {Number} x   x component of the vector.
        +   * @param  {Number} [y] y component of the vector.
        +   * @param  {Number} [z] z component of the vector.
        +   * @return {Number}     dot product.
        +   *
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create p5.Vector objects.
        +   *   let v1 = createVector(3, 4);
        +   *   let v2 = createVector(3, 0);
        +   *
        +   *   // Calculate the dot product.
        +   *   let dp = v1.dot(v2);
        +   *
        +   *   // Prints "9" to the console.
        +   *   print(dp);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create p5.Vector objects.
        +   *   let v1 = createVector(1, 0);
        +   *   let v2 = createVector(0, 1);
        +   *
        +   *   // Calculate the dot product.
        +   *   let dp = p5.Vector.dot(v1, v2);
        +   *
        +   *   // Prints "0" to the console.
        +   *   print(dp);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Two arrows drawn on a gray square. A black arrow points to the right and a red arrow follows the mouse. The text "v1 • v2 = something" changes as the mouse moves.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Center.
        +   *   let v0 = createVector(50, 50);
        +   *
        +   *   // Draw the black arrow.
        +   *   let v1 = createVector(30, 0);
        +   *   drawArrow(v0, v1, 'black');
        +   *
        +   *   // Draw the red arrow.
        +   *   let v2 = createVector(mouseX - 50, mouseY - 50);
        +   *   drawArrow(v0, v2, 'red');
        +   *
        +   *   // Display the dot product.
        +   *   let dp = v2.dot(v1);
        +   *   text(`v2 • v1 = ${dp}`, 10, 20);
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @param  {p5.Vector} v <a href="#/p5.Vector">p5.Vector</a> to be dotted.
        +   * @return {Number}
        +   */
        +  dot(...args) {
        +    if (args[0] instanceof Vector) {
        +      return this.dot(...args[0]._values);
             }
        -    return this.x * (x || 0) + this.y * (y || 0) + this.z * (z || 0);
        -  }
        -
        -  /**
        - * Calculates the cross product of two vectors.
        - *
        - * The cross product is a vector that points straight out of the plane created
        - * by two vectors. The cross product's magnitude is the area of the parallelogram
        - * formed by the original two vectors.
        - *
        - * The static version of `cross()`, as in `p5.Vector.cross(v1, v2)`, is the same
        - * as calling `v1.cross(v2)`.
        - *
        - * @method cross
        - * @param  {p5.Vector} v <a href="#/p5.Vector">p5.Vector</a> to be crossed.
        - * @return {p5.Vector}   cross product as a <a href="#/p5.Vector">p5.Vector</a>.
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create p5.Vector objects.
        - *   let v1 = createVector(1, 0);
        - *   let v2 = createVector(3, 4);
        - *
        - *   // Calculate the cross product.
        - *   let cp = v1.cross(v2);
        - *
        - *   // Prints "p5.Vector Object : [0, 0, 4]" to the console.
        - *   print(cp.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create p5.Vector objects.
        - *   let v1 = createVector(1, 0);
        - *   let v2 = createVector(3, 4);
        - *
        - *   // Calculate the cross product.
        - *   let cp = p5.Vector.cross(v1, v2);
        - *
        - *   // Prints "p5.Vector Object : [0, 0, 4]" to the console.
        - *   print(cp.toString());
        - * }
        - * </code>
        - * </div>
        - */
        +    return this._values.reduce((sum, component, index) => {
        +      return sum + component * (args[index] || 0);
        +    }, 0);
        +  }
        +
        +  /**
        +   * Calculates the cross product of two vectors.
        +   *
        +   * The cross product is a vector that points straight out of the plane created
        +   * by two vectors. The cross product's magnitude is the area of the parallelogram
        +   * formed by the original two vectors.
        +   *
        +   * The static version of `cross()`, as in `p5.Vector.cross(v1, v2)`, is the same
        +   * as calling `v1.cross(v2)`.
        +   *
        +   * @param  {p5.Vector} v <a href="#/p5.Vector">p5.Vector</a> to be crossed.
        +   * @return {p5.Vector}   cross product as a <a href="#/p5.Vector">p5.Vector</a>.
        +   *
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create p5.Vector objects.
        +   *   let v1 = createVector(1, 0);
        +   *   let v2 = createVector(3, 4);
        +   *
        +   *   // Calculate the cross product.
        +   *   let cp = v1.cross(v2);
        +   *
        +   *   // Prints "p5.Vector Object : [0, 0, 4]" to the console.
        +   *   print(cp.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create p5.Vector objects.
        +   *   let v1 = createVector(1, 0);
        +   *   let v2 = createVector(3, 4);
        +   *
        +   *   // Calculate the cross product.
        +   *   let cp = p5.Vector.cross(v1, v2);
        +   *
        +   *   // Prints "p5.Vector Object : [0, 0, 4]" to the console.
        +   *   print(cp.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   */
           cross(v) {
             const x = this.y * v.z - this.z * v.y;
             const y = this.z * v.x - this.x * v.z;
             const z = this.x * v.y - this.y * v.x;
             if (this.isPInst) {
        -      return new p5.Vector(this._fromRadians, this._toRadians, x, y, z);
        +      return new Vector(this._fromRadians, this._toRadians, x, y, z);
             } else {
        -      return new p5.Vector(x, y, z);
        +      return new Vector(x, y, z);
             }
           }
         
           /**
        - * Calculates the distance between two points represented by vectors.
        - *
        - * A point's coordinates can be represented by the components of a vector
        - * that extends from the origin to the point.
        - *
        - * The static version of `dist()`, as in `p5.Vector.dist(v1, v2)`, is the same
        - * as calling `v1.dist(v2)`.
        - *
        - * Use <a href="#/p5/dist">dist()</a> to calculate the distance between points
        - * using coordinates as in `dist(x1, y1, x2, y2)`.
        - *
        - * @method dist
        - * @param  {p5.Vector} v x, y, and z coordinates of a <a href="#/p5.Vector">p5.Vector</a>.
        - * @return {Number}      distance.
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create p5.Vector objects.
        - *   let v1 = createVector(1, 0);
        - *   let v2 = createVector(0, 1);
        - *
        - *   // Calculate the distance between them.
        - *   let d = v1.dist(v2);
        - *
        - *   // Prints "1.414..." to the console.
        - *   print(d);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create p5.Vector objects.
        - *   let v1 = createVector(1, 0);
        - *   let v2 = createVector(0, 1);
        - *
        - *   // Calculate the distance between them.
        - *   let d = p5.Vector.dist(v1, v2);
        - *
        - *   // Prints "1.414..." to the console.
        - *   print(d);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Three arrows drawn on a gray square. A red and a blue arrow extend from the top left. A purple arrow extends from the tip of the red arrow to the tip of the blue arrow. The number 36 is written in black near the purple arrow.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   let origin = createVector(0, 0);
        - *
        - *   // Draw the red arrow.
        - *   let v1 = createVector(50, 50);
        - *   drawArrow(origin, v1, 'red');
        - *
        - *   // Draw the blue arrow.
        - *   let v2 = createVector(20, 70);
        - *   drawArrow(origin, v2, 'blue');
        - *
        - *   // Purple arrow.
        - *   let v3 = p5.Vector.sub(v2, v1);
        - *   drawArrow(v1, v3, 'purple');
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *
        - *   // Display the magnitude. The same as floor(v3.mag());
        - *   let m = floor(p5.Vector.dist(v1, v2));
        - *   text(m, 50, 75);
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Calculates the distance between two points represented by vectors.
        +   *
        +   * A point's coordinates can be represented by the components of a vector
        +   * that extends from the origin to the point.
        +   *
        +   * The static version of `dist()`, as in `p5.Vector.dist(v1, v2)`, is the same
        +   * as calling `v1.dist(v2)`.
        +   *
        +   * Use <a href="#/p5/dist">dist()</a> to calculate the distance between points
        +   * using coordinates as in `dist(x1, y1, x2, y2)`.
        +   *
        +   * @method dist
        +   * @param  {p5.Vector} v x, y, and z coordinates of a <a href="#/p5.Vector">p5.Vector</a>.
        +   * @return {Number}      distance.
        +   *
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   let v1 = createVector(1, 0);
        +   *   let v2 = createVector(0, 1);
        +   *
        +   *   // Calculate the distance between them.
        +   *   let d = v1.dist(v2);
        +   *
        +   *   // Prints "1.414..." to the console.
        +   *   print(d);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   let v1 = createVector(1, 0);
        +   *   let v2 = createVector(0, 1);
        +   *
        +   *   // Calculate the distance between them.
        +   *   let d = p5.Vector.dist(v1, v2);
        +   *
        +   *   // Prints "1.414..." to the console.
        +   *   print(d);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Three arrows drawn on a gray square. A red and a blue arrow extend from the top left. A purple arrow extends from the tip of the red arrow to the tip of the blue arrow. The number 36 is written in black near the purple arrow.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   let origin = createVector(0, 0);
        +   *
        +   *   // Draw the red arrow.
        +   *   let v1 = createVector(50, 50);
        +   *   drawArrow(origin, v1, 'red');
        +   *
        +   *   // Draw the blue arrow.
        +   *   let v2 = createVector(20, 70);
        +   *   drawArrow(origin, v2, 'blue');
        +   *
        +   *   // Purple arrow.
        +   *   let v3 = p5.Vector.sub(v2, v1);
        +   *   drawArrow(v1, v3, 'purple');
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *
        +   *   // Display the magnitude. The same as floor(v3.mag());
        +   *   let m = floor(p5.Vector.dist(v1, v2));
        +   *   text(m, 50, 75);
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           dist(v) {
        -    return v
        -      .copy()
        -      .sub(this)
        -      .mag();
        -  }
        -
        -  /**
        - * Scales the components of a <a href="#/p5.Vector">p5.Vector</a> object so
        - * that its magnitude is 1.
        - *
        - * The static version of `normalize()`,  as in `p5.Vector.normalize(v)`,
        - * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
        - * the original.
        - *
        - * @method normalize
        - * @return {p5.Vector} normalized <a href="#/p5.Vector">p5.Vector</a>.
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Vector.
        - *   let v = createVector(10, 20, 2);
        - *
        - *   // Normalize.
        - *   v.normalize();
        - *
        - *   // Prints "p5.Vector Object : [0.445..., 0.890..., 0.089...]" to the console.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Vector.
        - *   let v0 = createVector(10, 20, 2);
        - *
        - *   // Create a normalized copy.
        - *   let v1 = p5.Vector.normalize(v0);
        - *
        - *   // Prints "p5.Vector Object : [10, 20, 2]" to the console.
        - *   print(v0.toString());
        - *   // Prints "p5.Vector Object : [0.445..., 0.890..., 0.089...]" to the console.
        - *   print(v1.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe("A red and blue arrow extend from the center of a circle. Both arrows follow the mouse, but the blue arrow's length is fixed to the circle's radius.");
        - * }
        - *
        - * function draw() {
        - *   background(240);
        - *
        - *   // Vector to the center.
        - *   let v0 = createVector(50, 50);
        - *
        - *   // Vector from the center to the mouse.
        - *   let v1 = createVector(mouseX - 50, mouseY - 50);
        - *
        - *   // Circle's radius.
        - *   let r = 25;
        - *
        - *   // Draw the red arrow.
        - *   drawArrow(v0, v1, 'red');
        - *
        - *   // Draw the blue arrow.
        - *   v1.normalize();
        - *   drawArrow(v0, v1.mult(r), 'blue');
        - *
        - *   // Draw the circle.
        - *   noFill();
        - *   circle(50, 50, r * 2);
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +    return v.copy().sub(this).mag();
        +  }
        +
        +  /**
        +   * Scales the components of a <a href="#/p5.Vector">p5.Vector</a> object so
        +   * that its magnitude is 1.
        +   *
        +   * The static version of `normalize()`,  as in `p5.Vector.normalize(v)`,
        +   * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
        +   * the original.
        +   *
        +   * @return {p5.Vector} normalized <a href="#/p5.Vector">p5.Vector</a>.
        +   *
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Vector.
        +   *   let v = createVector(10, 20, 2);
        +   *
        +   *   // Normalize.
        +   *   v.normalize();
        +   *
        +   *   // Prints "p5.Vector Object : [0.445..., 0.890..., 0.089...]" to the console.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Vector.
        +   *   let v0 = createVector(10, 20, 2);
        +   *
        +   *   // Create a normalized copy.
        +   *   let v1 = p5.Vector.normalize(v0);
        +   *
        +   *   // Prints "p5.Vector Object : [10, 20, 2]" to the console.
        +   *   print(v0.toString());
        +   *   // Prints "p5.Vector Object : [0.445..., 0.890..., 0.089...]" to the console.
        +   *   print(v1.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe("A red and blue arrow extend from the center of a circle. Both arrows follow the mouse, but the blue arrow's length is fixed to the circle's radius.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(240);
        +   *
        +   *   // Vector to the center.
        +   *   let v0 = createVector(50, 50);
        +   *
        +   *   // Vector from the center to the mouse.
        +   *   let v1 = createVector(mouseX - 50, mouseY - 50);
        +   *
        +   *   // Circle's radius.
        +   *   let r = 25;
        +   *
        +   *   // Draw the red arrow.
        +   *   drawArrow(v0, v1, 'red');
        +   *
        +   *   // Draw the blue arrow.
        +   *   v1.normalize();
        +   *   drawArrow(v0, v1.mult(r), 'blue');
        +   *
        +   *   // Draw the circle.
        +   *   noFill();
        +   *   circle(50, 50, r * 2);
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           normalize() {
             const len = this.mag();
             // here we multiply by the reciprocal instead of calling 'div()'
        @@ -1833,94 +1800,93 @@ p5.Vector = class {
           }
         
           /**
        - * Limits a vector's magnitude to a maximum value.
        - *
        - * The static version of `limit()`, as in `p5.Vector.limit(v, 5)`, returns a
        - * new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        - * original.
        - *
        - * @method limit
        - * @param  {Number}    max maximum magnitude for the vector.
        - * @chainable
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = createVector(10, 20, 2);
        - *
        - *   // Limit its magnitude.
        - *   v.limit(5);
        - *
        - *   // Prints "p5.Vector Object : [2.227..., 4.454..., 0.445...]" to the console.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v0 = createVector(10, 20, 2);
        - *
        - *   // Create a copy an limit its magintude.
        - *   let v1 = p5.Vector.limit(v0, 5);
        - *
        - *   // Prints "p5.Vector Object : [2.227..., 4.454..., 0.445...]" to the console.
        - *   print(v1.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe("A red and blue arrow extend from the center of a circle. Both arrows follow the mouse, but the blue arrow never crosses the circle's edge.");
        - * }
        - * function draw() {
        - *   background(240);
        - *
        - *   // Vector to the center.
        - *   let v0 = createVector(50, 50);
        - *
        - *   // Vector from the center to the mouse.
        - *   let v1 = createVector(mouseX - 50, mouseY - 50);
        - *
        - *   // Circle's radius.
        - *   let r = 25;
        - *
        - *   // Draw the red arrow.
        - *   drawArrow(v0, v1, 'red');
        - *
        - *   // Draw the blue arrow.
        - *   drawArrow(v0, v1.limit(r), 'blue');
        - *
        - *   // Draw the circle.
        - *   noFill();
        - *   circle(50, 50, r * 2);
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Limits a vector's magnitude to a maximum value.
        +   *
        +   * The static version of `limit()`, as in `p5.Vector.limit(v, 5)`, returns a
        +   * new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        +   * original.
        +   *
        +   * @param  {Number}    max maximum magnitude for the vector.
        +   * @chainable
        +   *
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(10, 20, 2);
        +   *
        +   *   // Limit its magnitude.
        +   *   v.limit(5);
        +   *
        +   *   // Prints "p5.Vector Object : [2.227..., 4.454..., 0.445...]" to the console.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v0 = createVector(10, 20, 2);
        +   *
        +   *   // Create a copy an limit its magintude.
        +   *   let v1 = p5.Vector.limit(v0, 5);
        +   *
        +   *   // Prints "p5.Vector Object : [2.227..., 4.454..., 0.445...]" to the console.
        +   *   print(v1.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe("A red and blue arrow extend from the center of a circle. Both arrows follow the mouse, but the blue arrow never crosses the circle's edge.");
        +   * }
        +   * function draw() {
        +   *   background(240);
        +   *
        +   *   // Vector to the center.
        +   *   let v0 = createVector(50, 50);
        +   *
        +   *   // Vector from the center to the mouse.
        +   *   let v1 = createVector(mouseX - 50, mouseY - 50);
        +   *
        +   *   // Circle's radius.
        +   *   let r = 25;
        +   *
        +   *   // Draw the red arrow.
        +   *   drawArrow(v0, v1, 'red');
        +   *
        +   *   // Draw the blue arrow.
        +   *   drawArrow(v0, v1.limit(r), 'blue');
        +   *
        +   *   // Draw the circle.
        +   *   noFill();
        +   *   circle(50, 50, r * 2);
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           limit(max) {
             const mSq = this.magSq();
             if (mSq > max * max) {
        @@ -1931,200 +1897,198 @@ p5.Vector = class {
           }
         
           /**
        - * Sets a vector's magnitude to a given value.
        - *
        - * The static version of `setMag()`, as in `p5.Vector.setMag(v, 10)`, returns
        - * a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        - * original.
        - *
        - * @method setMag
        - * @param  {number}    len new length for this vector.
        - * @chainable
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = createVector(3, 4, 0);
        - *
        - *   // Prints "5" to the console.
        - *   print(v.mag());
        - *
        - *   // Set its magnitude to 10.
        - *   v.setMag(10);
        - *
        - *   // Prints "p5.Vector Object : [6, 8, 0]" to the console.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v0 = createVector(3, 4, 0);
        - *
        - *   // Create a copy with a magnitude of 10.
        - *   let v1 = p5.Vector.setMag(v0, 10);
        - *
        - *   // Prints "5" to the console.
        - *   print(v0.mag());
        - *
        - *   // Prints "p5.Vector Object : [6, 8, 0]" to the console.
        - *   print(v1.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Two arrows extend from the top left corner of a square toward its center. The red arrow reaches the center and the blue arrow only extends part of the way.');
        - * }
        - *
        - * function draw() {
        - *   background(240);
        - *
        - *   let origin = createVector(0, 0);
        - *   let v = createVector(50, 50);
        - *
        - *   // Draw the red arrow.
        - *   drawArrow(origin, v, 'red');
        - *
        - *   // Set v's magnitude to 30.
        - *   v.setMag(30);
        - *
        - *   // Draw the blue arrow.
        - *   drawArrow(origin, v, 'blue');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Sets a vector's magnitude to a given value.
        +   *
        +   * The static version of `setMag()`, as in `p5.Vector.setMag(v, 10)`, returns
        +   * a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change the
        +   * original.
        +   *
        +   * @param  {Number}    len new length for this vector.
        +   * @chainable
        +   *
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(3, 4, 0);
        +   *
        +   *   // Prints "5" to the console.
        +   *   print(v.mag());
        +   *
        +   *   // Set its magnitude to 10.
        +   *   v.setMag(10);
        +   *
        +   *   // Prints "p5.Vector Object : [6, 8, 0]" to the console.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v0 = createVector(3, 4, 0);
        +   *
        +   *   // Create a copy with a magnitude of 10.
        +   *   let v1 = p5.Vector.setMag(v0, 10);
        +   *
        +   *   // Prints "5" to the console.
        +   *   print(v0.mag());
        +   *
        +   *   // Prints "p5.Vector Object : [6, 8, 0]" to the console.
        +   *   print(v1.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Two arrows extend from the top left corner of a square toward its center. The red arrow reaches the center and the blue arrow only extends part of the way.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(240);
        +   *
        +   *   let origin = createVector(0, 0);
        +   *   let v = createVector(50, 50);
        +   *
        +   *   // Draw the red arrow.
        +   *   drawArrow(origin, v, 'red');
        +   *
        +   *   // Set v's magnitude to 30.
        +   *   v.setMag(30);
        +   *
        +   *   // Draw the blue arrow.
        +   *   drawArrow(origin, v, 'blue');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           setMag(n) {
             return this.normalize().mult(n);
           }
         
           /**
        - * Calculates the angle a 2D vector makes with the positive x-axis.
        - *
        - * By convention, the positive x-axis has an angle of 0. Angles increase in
        - * the clockwise direction.
        - *
        - * If the vector was created with
        - * <a href="#/p5/createVector">createVector()</a>, `heading()` returns angles
        - * in the units of the current <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * The static version of `heading()`, as in `p5.Vector.heading(v)`, works the
        - * same way.
        - *
        - * @method heading
        - * @return {Number} angle of rotation.
        - *
        - * @example
        - * <div class = "norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = createVector(1, 1);
        - *
        - *   // Prints "0.785..." to the console.
        - *   print(v.heading());
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   // Prints "45" to the console.
        - *   print(v.heading());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class = "norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = createVector(1, 1);
        - *
        - *   // Prints "0.785..." to the console.
        - *   print(p5.Vector.heading(v));
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   // Prints "45" to the console.
        - *   print(p5.Vector.heading(v));
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A black arrow extends from the top left of a square to its center. The text "Radians: 0.79" and "Degrees: 45" is written near the tip of the arrow.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   let origin = createVector(0, 0);
        - *   let v = createVector(50, 50);
        - *
        - *   // Draw the black arrow.
        - *   drawArrow(origin, v, 'black');
        - *
        - *   // Use radians.
        - *   angleMode(RADIANS);
        - *
        - *   // Display the heading in radians.
        - *   let h = round(v.heading(), 2);
        - *   text(`Radians: ${h}`, 20, 70);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   // Display the heading in degrees.
        - *   h = v.heading();
        - *   text(`Degrees: ${h}`, 20, 85);
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Calculates the angle a 2D vector makes with the positive x-axis.
        +   *
        +   * By convention, the positive x-axis has an angle of 0. Angles increase in
        +   * the clockwise direction.
        +   *
        +   * If the vector was created with
        +   * <a href="#/p5/createVector">createVector()</a>, `heading()` returns angles
        +   * in the units of the current <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * The static version of `heading()`, as in `p5.Vector.heading(v)`, works the
        +   * same way.
        +   *
        +   * @return {Number} angle of rotation.
        +   *
        +   * @example
        +   * <div class = "norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(1, 1);
        +   *
        +   *   // Prints "0.785..." to the console.
        +   *   print(v.heading());
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   // Prints "45" to the console.
        +   *   print(v.heading());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class = "norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(1, 1);
        +   *
        +   *   // Prints "0.785..." to the console.
        +   *   print(p5.Vector.heading(v));
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   // Prints "45" to the console.
        +   *   print(p5.Vector.heading(v));
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A black arrow extends from the top left of a square to its center. The text "Radians: 0.79" and "Degrees: 45" is written near the tip of the arrow.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   let origin = createVector(0, 0);
        +   *   let v = createVector(50, 50);
        +   *
        +   *   // Draw the black arrow.
        +   *   drawArrow(origin, v, 'black');
        +   *
        +   *   // Use radians.
        +   *   angleMode(RADIANS);
        +   *
        +   *   // Display the heading in radians.
        +   *   let h = round(v.heading(), 2);
        +   *   text(`Radians: ${h}`, 20, 70);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   // Display the heading in degrees.
        +   *   h = v.heading();
        +   *   text(`Degrees: ${h}`, 20, 85);
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           heading() {
             const h = Math.atan2(this.y, this.x);
             if (this.isPInst) return this._fromRadians(h);
        @@ -2132,101 +2096,99 @@ p5.Vector = class {
           }
         
           /**
        - * Rotates a 2D vector to a specific angle without changing its magnitude.
        - *
        - * By convention, the positive x-axis has an angle of 0. Angles increase in
        - * the clockwise direction.
        - *
        - * If the vector was created with
        - * <a href="#/p5/createVector">createVector()</a>, `setHeading()` uses
        - * the units of the current <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * @method setHeading
        - * @param  {number}    angle angle of rotation.
        - * @chainable
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = createVector(0, 1);
        - *
        - *   // Prints "1.570..." to the console.
        - *   print(v.heading());
        - *
        - *   // Point to the left.
        - *   v.setHeading(PI);
        - *
        - *   // Prints "3.141..." to the console.
        - *   print(v.heading());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   // Create a p5.Vector object.
        - *   let v = createVector(0, 1);
        - *
        - *   // Prints "90" to the console.
        - *   print(v.heading());
        - *
        - *   // Point to the left.
        - *   v.setHeading(180);
        - *
        - *   // Prints "180" to the console.
        - *   print(v.heading());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Two arrows extend from the center of a gray square. The red arrow points to the right and the blue arrow points down.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(50, 50);
        - *   let v1 = createVector(30, 0);
        - *
        - *   // Draw the red arrow.
        - *   drawArrow(v0, v1, 'red');
        - *
        - *   // Point down.
        - *   v1.setHeading(HALF_PI);
        - *
        - *   // Draw the blue arrow.
        - *   drawArrow(v0, v1, 'blue');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        -
        +   * Rotates a 2D vector to a specific angle without changing its magnitude.
        +   *
        +   * By convention, the positive x-axis has an angle of 0. Angles increase in
        +   * the clockwise direction.
        +   *
        +   * If the vector was created with
        +   * <a href="#/p5/createVector">createVector()</a>, `setHeading()` uses
        +   * the units of the current <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * @param  {Number}    angle angle of rotation.
        +   * @chainable
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(0, 1);
        +   *
        +   *   // Prints "1.570..." to the console.
        +   *   print(v.heading());
        +   *
        +   *   // Point to the left.
        +   *   v.setHeading(PI);
        +   *
        +   *   // Prints "3.141..." to the console.
        +   *   print(v.heading());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(0, 1);
        +   *
        +   *   // Prints "90" to the console.
        +   *   print(v.heading());
        +   *
        +   *   // Point to the left.
        +   *   v.setHeading(180);
        +   *
        +   *   // Prints "180" to the console.
        +   *   print(v.heading());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Two arrows extend from the center of a gray square. The red arrow points to the right and the blue arrow points down.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(50, 50);
        +   *   let v1 = createVector(30, 0);
        +   *
        +   *   // Draw the red arrow.
        +   *   drawArrow(v0, v1, 'red');
        +   *
        +   *   // Point down.
        +   *   v1.setHeading(HALF_PI);
        +   *
        +   *   // Draw the blue arrow.
        +   *   drawArrow(v0, v1, 'blue');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           setHeading(a) {
             if (this.isPInst) a = this._toRadians(a);
             let m = this.mag();
        @@ -2236,142 +2198,141 @@ p5.Vector = class {
           }
         
           /**
        - * Rotates a 2D vector by an angle without changing its magnitude.
        - *
        - * By convention, the positive x-axis has an angle of 0. Angles increase in
        - * the clockwise direction.
        - *
        - * If the vector was created with
        - * <a href="#/p5/createVector">createVector()</a>, `rotate()` uses
        - * the units of the current <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * The static version of `rotate()`, as in `p5.Vector.rotate(v, PI)`,
        - * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
        - * the original.
        - *
        - * @method rotate
        - * @param  {number}    angle angle of rotation.
        - * @chainable
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = createVector(1, 0);
        - *
        - *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
        - *   print(v.toString());
        - *
        - *   // Rotate a quarter turn.
        - *   v.rotate(HALF_PI);
        - *
        - *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   // Create a p5.Vector object.
        - *   let v = createVector(1, 0);
        - *
        - *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
        - *   print(v.toString());
        - *
        - *   // Rotate a quarter turn.
        - *   v.rotate(90);
        - *
        - *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v0 = createVector(1, 0);
        - *
        - *   // Create a rotated copy.
        - *   let v1 = p5.Vector.rotate(v0, HALF_PI);
        - *
        - *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
        - *   print(v0.toString());
        - *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
        - *   print(v1.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   // Create a p5.Vector object.
        - *   let v0 = createVector(1, 0);
        - *
        - *   // Create a rotated copy.
        - *   let v1 = p5.Vector.rotate(v0, 90);
        - *
        - *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
        - *   print(v0.toString());
        - *
        - *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
        - *   print(v1.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let v0;
        - * let v1;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Create p5.Vector objects.
        - *   v0 = createVector(50, 50);
        - *   v1 = createVector(30, 0);
        - *
        - *   describe('A black arrow extends from the center of a gray square. The arrow rotates clockwise.');
        - * }
        - *
        - * function draw() {
        - *   background(240);
        - *
        - *   // Rotate v1.
        - *   v1.rotate(0.01);
        - *
        - *   // Draw the black arrow.
        - *   drawArrow(v0, v1, 'black');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Rotates a 2D vector by an angle without changing its magnitude.
        +   *
        +   * By convention, the positive x-axis has an angle of 0. Angles increase in
        +   * the clockwise direction.
        +   *
        +   * If the vector was created with
        +   * <a href="#/p5/createVector">createVector()</a>, `rotate()` uses
        +   * the units of the current <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * The static version of `rotate()`, as in `p5.Vector.rotate(v, PI)`,
        +   * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
        +   * the original.
        +   *
        +   * @param  {Number}    angle angle of rotation.
        +   * @chainable
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(1, 0);
        +   *
        +   *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
        +   *   print(v.toString());
        +   *
        +   *   // Rotate a quarter turn.
        +   *   v.rotate(HALF_PI);
        +   *
        +   *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(1, 0);
        +   *
        +   *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
        +   *   print(v.toString());
        +   *
        +   *   // Rotate a quarter turn.
        +   *   v.rotate(90);
        +   *
        +   *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v0 = createVector(1, 0);
        +   *
        +   *   // Create a rotated copy.
        +   *   let v1 = p5.Vector.rotate(v0, HALF_PI);
        +   *
        +   *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
        +   *   print(v0.toString());
        +   *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
        +   *   print(v1.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   // Create a p5.Vector object.
        +   *   let v0 = createVector(1, 0);
        +   *
        +   *   // Create a rotated copy.
        +   *   let v1 = p5.Vector.rotate(v0, 90);
        +   *
        +   *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
        +   *   print(v0.toString());
        +   *
        +   *   // Prints "p5.Vector Object : [0, 1, 0]" to the console.
        +   *   print(v1.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let v0;
        +   * let v1;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   v0 = createVector(50, 50);
        +   *   v1 = createVector(30, 0);
        +   *
        +   *   describe('A black arrow extends from the center of a gray square. The arrow rotates clockwise.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(240);
        +   *
        +   *   // Rotate v1.
        +   *   v1.rotate(0.01);
        +   *
        +   *   // Draw the black arrow.
        +   *   drawArrow(v0, v1, 'black');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           rotate(a) {
             let newHeading = this.heading() + a;
             if (this.isPInst) newHeading = this._toRadians(newHeading);
        @@ -2382,142 +2343,141 @@ p5.Vector = class {
           }
         
           /**
        - * Calculates the angle between two vectors.
        - *
        - * The angles returned are signed, which means that
        - * `v1.angleBetween(v2) === -v2.angleBetween(v1)`.
        - *
        - * If the vector was created with
        - * <a href="#/p5/createVector">createVector()</a>, `angleBetween()` returns
        - * angles in the units of the current
        - * <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * @method angleBetween
        - * @param  {p5.Vector}    value x, y, and z components of a <a href="#/p5.Vector">p5.Vector</a>.
        - * @return {Number}       angle between the vectors.
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(1, 0);
        - *   let v1 = createVector(0, 1);
        - *
        - *   // Prints "1.570..." to the console.
        - *   print(v0.angleBetween(v1));
        - *
        - *   // Prints "-1.570..." to the console.
        - *   print(v1.angleBetween(v0));
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(1, 0);
        - *   let v1 = createVector(0, 1);
        - *
        - *   // Prints "90" to the console.
        - *   print(v0.angleBetween(v1));
        - *
        - *   // Prints "-90" to the console.
        - *   print(v1.angleBetween(v0));
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(1, 0);
        - *   let v1 = createVector(0, 1);
        - *
        - *   // Prints "1.570..." to the console.
        - *   print(p5.Vector.angleBetween(v0, v1));
        - *
        - *   // Prints "-1.570..." to the console.
        - *   print(p5.Vector.angleBetween(v1, v0));
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(1, 0);
        - *   let v1 = createVector(0, 1);
        - *
        - *   // Prints "90" to the console.
        - *   print(p5.Vector.angleBetween(v0, v1));
        - *
        - *   // Prints "-90" to the console.
        - *   print(p5.Vector.angleBetween(v1, v0));
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Two arrows extend from the center of a gray square. A red arrow points to the right and a blue arrow points down. The text "Radians: 1.57" and "Degrees: 90" is written above the arrows.');
        - * }
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(50, 50);
        - *   let v1 = createVector(30, 0);
        - *   let v2 = createVector(0, 30);
        - *
        - *   // Draw the red arrow.
        - *   drawArrow(v0, v1, 'red');
        - *
        - *   // Draw the blue arrow.
        - *   drawArrow(v0, v2, 'blue');
        - *
        - *   // Use radians.
        - *   angleMode(RADIANS);
        - *
        - *   // Display the angle in radians.
        - *   let angle = round(v1.angleBetween(v2), 2);
        - *   text(`Radians: ${angle}`, 20, 20);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   // Display the angle in degrees.
        - *   angle = round(v1.angleBetween(v2), 2);
        - *   text(`Degrees: ${angle}`, 20, 35);
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Calculates the angle between two vectors.
        +   *
        +   * The angles returned are signed, which means that
        +   * `v1.angleBetween(v2) === -v2.angleBetween(v1)`.
        +   *
        +   * If the vector was created with
        +   * <a href="#/p5/createVector">createVector()</a>, `angleBetween()` returns
        +   * angles in the units of the current
        +   * <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * @param  {p5.Vector}    value x, y, and z components of a <a href="#/p5.Vector">p5.Vector</a>.
        +   * @return {Number}       angle between the vectors.
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(1, 0);
        +   *   let v1 = createVector(0, 1);
        +   *
        +   *   // Prints "1.570..." to the console.
        +   *   print(v0.angleBetween(v1));
        +   *
        +   *   // Prints "-1.570..." to the console.
        +   *   print(v1.angleBetween(v0));
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(1, 0);
        +   *   let v1 = createVector(0, 1);
        +   *
        +   *   // Prints "90" to the console.
        +   *   print(v0.angleBetween(v1));
        +   *
        +   *   // Prints "-90" to the console.
        +   *   print(v1.angleBetween(v0));
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(1, 0);
        +   *   let v1 = createVector(0, 1);
        +   *
        +   *   // Prints "1.570..." to the console.
        +   *   print(p5.Vector.angleBetween(v0, v1));
        +   *
        +   *   // Prints "-1.570..." to the console.
        +   *   print(p5.Vector.angleBetween(v1, v0));
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(1, 0);
        +   *   let v1 = createVector(0, 1);
        +   *
        +   *   // Prints "90" to the console.
        +   *   print(p5.Vector.angleBetween(v0, v1));
        +   *
        +   *   // Prints "-90" to the console.
        +   *   print(p5.Vector.angleBetween(v1, v0));
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Two arrows extend from the center of a gray square. A red arrow points to the right and a blue arrow points down. The text "Radians: 1.57" and "Degrees: 90" is written above the arrows.');
        +   * }
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(50, 50);
        +   *   let v1 = createVector(30, 0);
        +   *   let v2 = createVector(0, 30);
        +   *
        +   *   // Draw the red arrow.
        +   *   drawArrow(v0, v1, 'red');
        +   *
        +   *   // Draw the blue arrow.
        +   *   drawArrow(v0, v2, 'blue');
        +   *
        +   *   // Use radians.
        +   *   angleMode(RADIANS);
        +   *
        +   *   // Display the angle in radians.
        +   *   let angle = round(v1.angleBetween(v2), 2);
        +   *   text(`Radians: ${angle}`, 20, 20);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   // Display the angle in degrees.
        +   *   angle = round(v1.angleBetween(v2), 2);
        +   *   text(`Degrees: ${angle}`, 20, 35);
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           angleBetween(v) {
             const magSqMult = this.magSq() * v.magSq();
             // Returns NaN if either vector is the zero vector.
        @@ -2536,126 +2496,124 @@ p5.Vector = class {
           }
         
           /**
        - * Calculates new `x`, `y`, and `z` components that are proportionally the
        - * same distance between two vectors.
        - *
        - * The `amt` parameter is the amount to interpolate between the old vector and
        - * the new vector. 0.0 keeps all components equal to the old vector's, 0.5 is
        - * halfway between, and 1.0 sets all components equal to the new vector's.
        - *
        - * The static version of `lerp()`, as in `p5.Vector.lerp(v0, v1, 0.5)`,
        - * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
        - * the original.
        - *
        - * @method lerp
        - * @param  {Number}    x   x component.
        - * @param  {Number}    y   y component.
        - * @param  {Number}    z   z component.
        - * @param  {Number}    amt amount of interpolation between 0.0 (old vector)
        - *                         and 1.0 (new vector). 0.5 is halfway between.
        - * @chainable
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v0 = createVector(1, 1, 1);
        - *   let v1 = createVector(3, 3, 3);
        - *
        - *   // Interpolate.
        - *   v0.lerp(v1, 0.5);
        - *
        - *   // Prints "p5.Vector Object : [2, 2, 2]" to the console.
        - *   print(v0.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = createVector(1, 1, 1);
        - *
        - *   // Interpolate.
        - *   v.lerp(3, 3, 3, 0.5);
        - *
        - *   // Prints "p5.Vector Object : [2, 2, 2]" to the console.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(1, 1, 1);
        - *   let v1 = createVector(3, 3, 3);
        - *
        - *   // Interpolate.
        - *   let v2 = p5.Vector.lerp(v0, v1, 0.5);
        - *
        - *   // Prints "p5.Vector Object : [2, 2, 2]" to the console.
        - *   print(v2.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Three arrows extend from the center of a gray square. A red arrow points to the right, a blue arrow points down, and a purple arrow points to the bottom right.');
        - * }
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(50, 50);
        - *   let v1 = createVector(30, 0);
        - *   let v2 = createVector(0, 30);
        - *
        - *   // Interpolate.
        - *   let v3 = p5.Vector.lerp(v1, v2, 0.5);
        - *
        - *   // Draw the red arrow.
        - *   drawArrow(v0, v1, 'red');
        - *
        - *   // Draw the blue arrow.
        - *   drawArrow(v0, v2, 'blue');
        - *
        - *   // Draw the purple arrow.
        - *   drawArrow(v0, v3, 'purple');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Calculates new `x`, `y`, and `z` components that are proportionally the
        +   * same distance between two vectors.
        +   *
        +   * The `amt` parameter is the amount to interpolate between the old vector and
        +   * the new vector. 0.0 keeps all components equal to the old vector's, 0.5 is
        +   * halfway between, and 1.0 sets all components equal to the new vector's.
        +   *
        +   * The static version of `lerp()`, as in `p5.Vector.lerp(v0, v1, 0.5)`,
        +   * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
        +   * the original.
        +   *
        +   * @param  {Number}    x   x component.
        +   * @param  {Number}    y   y component.
        +   * @param  {Number}    z   z component.
        +   * @param  {Number}    amt amount of interpolation between 0.0 (old vector)
        +   *                         and 1.0 (new vector). 0.5 is halfway between.
        +   * @chainable
        +   *
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v0 = createVector(1, 1, 1);
        +   *   let v1 = createVector(3, 3, 3);
        +   *
        +   *   // Interpolate.
        +   *   v0.lerp(v1, 0.5);
        +   *
        +   *   // Prints "p5.Vector Object : [2, 2, 2]" to the console.
        +   *   print(v0.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(1, 1, 1);
        +   *
        +   *   // Interpolate.
        +   *   v.lerp(3, 3, 3, 0.5);
        +   *
        +   *   // Prints "p5.Vector Object : [2, 2, 2]" to the console.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(1, 1, 1);
        +   *   let v1 = createVector(3, 3, 3);
        +   *
        +   *   // Interpolate.
        +   *   let v2 = p5.Vector.lerp(v0, v1, 0.5);
        +   *
        +   *   // Prints "p5.Vector Object : [2, 2, 2]" to the console.
        +   *   print(v2.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Three arrows extend from the center of a gray square. A red arrow points to the right, a blue arrow points down, and a purple arrow points to the bottom right.');
        +   * }
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(50, 50);
        +   *   let v1 = createVector(30, 0);
        +   *   let v2 = createVector(0, 30);
        +   *
        +   *   // Interpolate.
        +   *   let v3 = p5.Vector.lerp(v1, v2, 0.5);
        +   *
        +   *   // Draw the red arrow.
        +   *   drawArrow(v0, v1, 'red');
        +   *
        +   *   // Draw the blue arrow.
        +   *   drawArrow(v0, v2, 'blue');
        +   *
        +   *   // Draw the purple arrow.
        +   *   drawArrow(v0, v3, 'purple');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           /**
        - * @method lerp
        - * @param  {p5.Vector} v  <a href="#/p5.Vector">p5.Vector</a> to lerp toward.
        - * @param  {Number}    amt
        - * @chainable
        - */
        +   * @param  {p5.Vector} v  <a href="#/p5.Vector">p5.Vector</a> to lerp toward.
        +   * @param  {Number}    amt
        +   * @chainable
        +   */
           lerp(x, y, z, amt) {
        -    if (x instanceof p5.Vector) {
        +    if (x instanceof Vector) {
               return this.lerp(x.x, x.y, x.z, y);
             }
             this.x += (x - this.x) * amt || 0;
        @@ -2665,145 +2623,148 @@ p5.Vector = class {
           }
         
           /**
        - * Calculates a new heading and magnitude that are between two vectors.
        - *
        - * The `amt` parameter is the amount to interpolate between the old vector and
        - * the new vector. 0.0 keeps the heading and magnitude equal to the old
        - * vector's, 0.5 sets them halfway between, and 1.0 sets the heading and
        - * magnitude equal to the new vector's.
        - *
        - * `slerp()` differs from <a href="#/p5.Vector/lerp">lerp()</a> because
        - * it interpolates magnitude. Calling `v0.slerp(v1, 0.5)` sets `v0`'s
        - * magnitude to a value halfway between its original magnitude and `v1`'s.
        - * Calling `v0.lerp(v1, 0.5)` makes no such guarantee.
        - *
        - * The static version of `slerp()`, as in `p5.Vector.slerp(v0, v1, 0.5)`,
        - * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
        - * the original.
        - *
        - * @method slerp
        - * @param {p5.Vector} v <a href="#/p5.Vector">p5.Vector</a> to slerp toward.
        - * @param {Number} amt  amount of interpolation between 0.0 (old vector)
        - *                      and 1.0 (new vector). 0.5 is halfway between.
        - * @return {p5.Vector}
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v0 = createVector(3, 0);
        - *
        - *   // Prints "3" to the console.
        - *   print(v0.mag());
        - *
        - *   // Prints "0" to the console.
        - *   print(v0.heading());
        - *
        - *   // Create a p5.Vector object.
        - *   let v1 = createVector(0, 1);
        - *
        - *   // Prints "1" to the console.
        - *   print(v1.mag());
        - *
        - *   // Prints "1.570..." to the console.
        - *   print(v1.heading());
        - *
        - *   // Interpolate halfway between v0 and v1.
        - *   v0.slerp(v1, 0.5);
        - *
        - *   // Prints "2" to the console.
        - *   print(v0.mag());
        - *
        - *   // Prints "0.785..." to the console.
        - *   print(v0.heading());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v0 = createVector(3, 0);
        - *
        - *   // Prints "3" to the console.
        - *   print(v0.mag());
        - *
        - *   // Prints "0" to the console.
        - *   print(v0.heading());
        - *
        - *   // Create a p5.Vector object.
        - *   let v1 = createVector(0, 1);
        - *
        - *   // Prints "1" to the console.
        - *   print(v1.mag());
        - *
        - *   // Prints "1.570..." to the console.
        - *   print(v1.heading());
        - *
        - *   // Create a p5.Vector that's halfway between v0 and v1.
        - *   let v3 = p5.Vector.slerp(v0, v1, 0.5);
        - *
        - *   // Prints "2" to the console.
        - *   print(v3.mag());
        - *
        - *   // Prints "0.785..." to the console.
        - *   print(v3.heading());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Three arrows extend from the center of a gray square. A red arrow points to the right, a blue arrow points to the left, and a purple arrow points down.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(50, 50);
        - *   let v1 = createVector(20, 0);
        - *   let v2 = createVector(-40, 0);
        - *
        - *   // Create a p5.Vector that's halfway between v1 and v2.
        - *   let v3 = p5.Vector.slerp(v1, v2, 0.5);
        - *
        - *   // Draw the red arrow.
        - *   drawArrow(v0, v1, 'red');
        - *
        - *   // Draw the blue arrow.
        - *   drawArrow(v0, v2, 'blue');
        - *
        - *   // Draw the purple arrow.
        - *   drawArrow(v0, v3, 'purple');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Calculates a new heading and magnitude that are between two vectors.
        +   *
        +   * The `amt` parameter is the amount to interpolate between the old vector and
        +   * the new vector. 0.0 keeps the heading and magnitude equal to the old
        +   * vector's, 0.5 sets them halfway between, and 1.0 sets the heading and
        +   * magnitude equal to the new vector's.
        +   *
        +   * `slerp()` differs from <a href="#/p5.Vector/lerp">lerp()</a> because
        +   * it interpolates magnitude. Calling `v0.slerp(v1, 0.5)` sets `v0`'s
        +   * magnitude to a value halfway between its original magnitude and `v1`'s.
        +   * Calling `v0.lerp(v1, 0.5)` makes no such guarantee.
        +   *
        +   * The static version of `slerp()`, as in `p5.Vector.slerp(v0, v1, 0.5)`,
        +   * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
        +   * the original.
        +   *
        +   * @param {p5.Vector} v <a href="#/p5.Vector">p5.Vector</a> to slerp toward.
        +   * @param {Number} amt  amount of interpolation between 0.0 (old vector)
        +   *                      and 1.0 (new vector). 0.5 is halfway between.
        +   * @return {p5.Vector}
        +   *
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v0 = createVector(3, 0);
        +   *
        +   *   // Prints "3" to the console.
        +   *   print(v0.mag());
        +   *
        +   *   // Prints "0" to the console.
        +   *   print(v0.heading());
        +   *
        +   *   // Create a p5.Vector object.
        +   *   let v1 = createVector(0, 1);
        +   *
        +   *   // Prints "1" to the console.
        +   *   print(v1.mag());
        +   *
        +   *   // Prints "1.570..." to the console.
        +   *   print(v1.heading());
        +   *
        +   *   // Interpolate halfway between v0 and v1.
        +   *   v0.slerp(v1, 0.5);
        +   *
        +   *   // Prints "2" to the console.
        +   *   print(v0.mag());
        +   *
        +   *   // Prints "0.785..." to the console.
        +   *   print(v0.heading());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v0 = createVector(3, 0);
        +   *
        +   *   // Prints "3" to the console.
        +   *   print(v0.mag());
        +   *
        +   *   // Prints "0" to the console.
        +   *   print(v0.heading());
        +   *
        +   *   // Create a p5.Vector object.
        +   *   let v1 = createVector(0, 1);
        +   *
        +   *   // Prints "1" to the console.
        +   *   print(v1.mag());
        +   *
        +   *   // Prints "1.570..." to the console.
        +   *   print(v1.heading());
        +   *
        +   *   // Create a p5.Vector that's halfway between v0 and v1.
        +   *   let v3 = p5.Vector.slerp(v0, v1, 0.5);
        +   *
        +   *   // Prints "2" to the console.
        +   *   print(v3.mag());
        +   *
        +   *   // Prints "0.785..." to the console.
        +   *   print(v3.heading());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Three arrows extend from the center of a gray square. A red arrow points to the right, a blue arrow points to the left, and a purple arrow points down.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(50, 50);
        +   *   let v1 = createVector(20, 0);
        +   *   let v2 = createVector(-40, 0);
        +   *
        +   *   // Create a p5.Vector that's halfway between v1 and v2.
        +   *   let v3 = p5.Vector.slerp(v1, v2, 0.5);
        +   *
        +   *   // Draw the red arrow.
        +   *   drawArrow(v0, v1, 'red');
        +   *
        +   *   // Draw the blue arrow.
        +   *   drawArrow(v0, v2, 'blue');
        +   *
        +   *   // Draw the purple arrow.
        +   *   drawArrow(v0, v3, 'purple');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           slerp(v, amt) {
             // edge cases.
        -    if (amt === 0) { return this; }
        -    if (amt === 1) { return this.set(v); }
        +    if (amt === 0) {
        +      return this;
        +    }
        +    if (amt === 1) {
        +      return this.set(v);
        +    }
         
             // calculate magnitudes
             const selfMag = this.mag();
        @@ -2852,7 +2813,7 @@ p5.Vector = class {
             // Since 'axis' is a unit vector, ey is a vector of the same length as 'this'.
             const ey = axis.cross(this);
             // interpolate the length with 'this' and 'v'.
        -    const lerpedMagFactor = (1 - amt) + amt * vMag / selfMag;
        +    const lerpedMagFactor = 1 - amt + (amt * vMag) / selfMag;
             // imagine a situation where 'axis', 'this', and 'ey' are pointing
             // along the z, x, and y axes, respectively.
             // rotates 'this' around 'axis' by amt * theta towards 'ey'.
        @@ -2867,373 +2828,401 @@ p5.Vector = class {
           }
         
           /**
        - * Reflects a vector about a line in 2D or a plane in 3D.
        - *
        - * The orientation of the line or plane is described by a normal vector that
        - * points away from the shape.
        - *
        - * The static version of `reflect()`, as in `p5.Vector.reflect(v, n)`,
        - * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
        - * the original.
        - *
        - * @method reflect
        - * @param  {p5.Vector} surfaceNormal  <a href="#/p5.Vector">p5.Vector</a>
        - *                                    to reflect about.
        - * @chainable
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a normal vector.
        - *   let n = createVector(0, 1);
        - *   // Create a vector to reflect.
        - *   let v = createVector(4, 6);
        - *
        - *   // Reflect v about n.
        - *   v.reflect(n);
        - *
        - *   // Prints "p5.Vector Object : [4, -6, 0]" to the console.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a normal vector.
        - *   let n = createVector(0, 1);
        - *
        - *   // Create a vector to reflect.
        - *   let v0 = createVector(4, 6);
        - *
        - *   // Create a reflected vector.
        - *   let v1 = p5.Vector.reflect(v0, n);
        - *
        - *   // Prints "p5.Vector Object : [4, -6, 0]" to the console.
        - *   print(v1.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('Three arrows extend from the center of a gray square with a vertical line down its middle. A black arrow points to the right, a blue arrow points to the bottom left, and a red arrow points to the bottom right.');
        - * }
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a vertical line.
        - *   line(50, 0, 50, 100);
        - *
        - *   // Create a normal vector.
        - *   let n = createVector(1, 0);
        - *
        - *   // Center.
        - *   let v0 = createVector(50, 50);
        - *
        - *   // Create a vector to reflect.
        - *   let v1 = createVector(30, 40);
        - *
        - *   // Create a reflected vector.
        - *   let v2 = p5.Vector.reflect(v1, n);
        - *
        - *   // Scale the normal vector for drawing.
        - *   n.setMag(30);
        - *
        - *   // Draw the black arrow.
        - *   drawArrow(v0, n, 'black');
        - *
        - *   // Draw the red arrow.
        - *   drawArrow(v0, v1, 'red');
        - *
        - *   // Draw the blue arrow.
        - *   drawArrow(v0, v2, 'blue');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Reflects a vector about a line in 2D or a plane in 3D.
        +   *
        +   * The orientation of the line or plane is described by a normal vector that
        +   * points away from the shape.
        +   *
        +   * The static version of `reflect()`, as in `p5.Vector.reflect(v, n)`,
        +   * returns a new <a href="#/p5.Vector">p5.Vector</a> object and doesn't change
        +   * the original.
        +   *
        +   * @param  {p5.Vector} surfaceNormal  <a href="#/p5.Vector">p5.Vector</a>
        +   *                                    to reflect about.
        +   * @chainable
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a normal vector.
        +   *   let n = createVector(0, 1);
        +   *   // Create a vector to reflect.
        +   *   let v = createVector(4, 6);
        +   *
        +   *   // Reflect v about n.
        +   *   v.reflect(n);
        +   *
        +   *   // Prints "p5.Vector Object : [4, -6, 0]" to the console.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a normal vector.
        +   *   let n = createVector(0, 1);
        +   *
        +   *   // Create a vector to reflect.
        +   *   let v0 = createVector(4, 6);
        +   *
        +   *   // Create a reflected vector.
        +   *   let v1 = p5.Vector.reflect(v0, n);
        +   *
        +   *   // Prints "p5.Vector Object : [4, -6, 0]" to the console.
        +   *   print(v1.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('Three arrows extend from the center of a gray square with a vertical line down its middle. A black arrow points to the right, a blue arrow points to the bottom left, and a red arrow points to the bottom right.');
        +   * }
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a vertical line.
        +   *   line(50, 0, 50, 100);
        +   *
        +   *   // Create a normal vector.
        +   *   let n = createVector(1, 0);
        +   *
        +   *   // Center.
        +   *   let v0 = createVector(50, 50);
        +   *
        +   *   // Create a vector to reflect.
        +   *   let v1 = createVector(30, 40);
        +   *
        +   *   // Create a reflected vector.
        +   *   let v2 = p5.Vector.reflect(v1, n);
        +   *
        +   *   // Scale the normal vector for drawing.
        +   *   n.setMag(30);
        +   *
        +   *   // Draw the black arrow.
        +   *   drawArrow(v0, n, 'black');
        +   *
        +   *   // Draw the red arrow.
        +   *   drawArrow(v0, v1, 'red');
        +   *
        +   *   // Draw the blue arrow.
        +   *   drawArrow(v0, v2, 'blue');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           reflect(surfaceNormal) {
        -    const surfaceNormalCopy = p5.Vector.normalize(surfaceNormal);
        +    const surfaceNormalCopy = Vector.normalize(surfaceNormal);
             return this.sub(surfaceNormalCopy.mult(2 * this.dot(surfaceNormalCopy)));
           }
         
           /**
        - * Returns the vector's components as an array of numbers.
        - *
        - * @method array
        - * @return {Number[]} array with the vector's components.
        - * @example
        - * <div class = "norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = createVector(20, 30);
        - *
        - *   // Prints "[20, 30, 0]" to the console.
        - *   print(v.array());
        - * }
        - * </code>
        - * </div>
        - */
        +   * Returns the vector's components as an array of numbers.
        +   *
        +   * @return {Number[]} array with the vector's components.
        +   * @example
        +   * <div class = "norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = createVector(20, 30);
        +   *
        +   *   // Prints "[20, 30, 0]" to the console.
        +   *   print(v.array());
        +   * }
        +   * </code>
        +   * </div>
        +   */
           array() {
             return [this.x || 0, this.y || 0, this.z || 0];
           }
         
           /**
        - * Checks whether all the vector's components are equal to another vector's.
        - *
        - * `equals()` returns `true` if the vector's components are all the same as another
        - * vector's and `false` if not.
        - *
        - * The version of `equals()` with one parameter interprets it as another
        - * <a href="#/p5.Vector">p5.Vector</a> object.
        - *
        - * The version of `equals()` with multiple parameters interprets them as the
        - * components of another vector. Any missing parameters are assigned the value
        - * 0.
        - *
        - * The static version of `equals()`, as in `p5.Vector.equals(v0, v1)`,
        - * interprets both parameters as <a href="#/p5.Vector">p5.Vector</a> objects.
        - *
        - * @method equals
        - * @param {Number} [x] x component of the vector.
        - * @param {Number} [y] y component of the vector.
        - * @param {Number} [z] z component of the vector.
        - * @return {Boolean} whether the vectors are equal.
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(10, 20, 30);
        - *   let v1 = createVector(10, 20, 30);
        - *   let v2 = createVector(0, 0, 0);
        - *
        - *   // Prints "true" to the console.
        - *   print(v0.equals(v1));
        - *
        - *   // Prints "false" to the console.
        - *   print(v0.equals(v2));
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class = "norender">
        - * <code>
        - * function setup() {
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(5, 10, 20);
        - *   let v1 = createVector(5, 10, 20);
        - *   let v2 = createVector(13, 10, 19);
        - *
        - *   // Prints "true" to the console.
        - *   print(v0.equals(v1.x, v1.y, v1.z));
        - *
        - *   // Prints "false" to the console.
        - *   print(v0.equals(v2.x, v2.y, v2.z));
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create p5.Vector objects.
        - *   let v0 = createVector(10, 20, 30);
        - *   let v1 = createVector(10, 20, 30);
        - *   let v2 = createVector(0, 0, 0);
        - *
        - *   // Prints "true" to the console.
        - *   print(p5.Vector.equals(v0, v1));
        - *
        - *   // Prints "false" to the console.
        - *   print(p5.Vector.equals(v0, v2));
        - * }
        - * </code>
        - * </div>
        - */
        +   * Checks whether all the vector's components are equal to another vector's.
        +   *
        +   * `equals()` returns `true` if the vector's components are all the same as another
        +   * vector's and `false` if not.
        +   *
        +   * The version of `equals()` with one parameter interprets it as another
        +   * <a href="#/p5.Vector">p5.Vector</a> object.
        +   *
        +   * The version of `equals()` with multiple parameters interprets them as the
        +   * components of another vector. Any missing parameters are assigned the value
        +   * 0.
        +   *
        +   * The static version of `equals()`, as in `p5.Vector.equals(v0, v1)`,
        +   * interprets both parameters as <a href="#/p5.Vector">p5.Vector</a> objects.
        +   *
        +   * @param {Number} [x] x component of the vector.
        +   * @param {Number} [y] y component of the vector.
        +   * @param {Number} [z] z component of the vector.
        +   * @return {Boolean} whether the vectors are equal.
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(10, 20, 30);
        +   *   let v1 = createVector(10, 20, 30);
        +   *   let v2 = createVector(0, 0, 0);
        +   *
        +   *   // Prints "true" to the console.
        +   *   print(v0.equals(v1));
        +   *
        +   *   // Prints "false" to the console.
        +   *   print(v0.equals(v2));
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class = "norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(5, 10, 20);
        +   *   let v1 = createVector(5, 10, 20);
        +   *   let v2 = createVector(13, 10, 19);
        +   *
        +   *   // Prints "true" to the console.
        +   *   print(v0.equals(v1.x, v1.y, v1.z));
        +   *
        +   *   // Prints "false" to the console.
        +   *   print(v0.equals(v2.x, v2.y, v2.z));
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create p5.Vector objects.
        +   *   let v0 = createVector(10, 20, 30);
        +   *   let v1 = createVector(10, 20, 30);
        +   *   let v2 = createVector(0, 0, 0);
        +   *
        +   *   // Prints "true" to the console.
        +   *   print(p5.Vector.equals(v0, v1));
        +   *
        +   *   // Prints "false" to the console.
        +   *   print(p5.Vector.equals(v0, v2));
        +   * }
        +   * </code>
        +   * </div>
        +   */
           /**
        - * @method equals
        - * @param {p5.Vector|Array} value vector to compare.
        - * @return {Boolean}
        - */
        -  equals(x, y, z) {
        -    let a, b, c;
        -    if (x instanceof p5.Vector) {
        -      a = x.x || 0;
        -      b = x.y || 0;
        -      c = x.z || 0;
        -    } else if (Array.isArray(x)) {
        -      a = x[0] || 0;
        -      b = x[1] || 0;
        -      c = x[2] || 0;
        +   * @param {p5.Vector|Array} value vector to compare.
        +   * @return {Boolean}
        +   */
        +  equals(...args) {
        +    let values;
        +    if (args[0] instanceof Vector) {
        +      values = args[0]._values;
        +    } else if (Array.isArray(args[0])) {
        +      values = args[0];
             } else {
        -      a = x || 0;
        -      b = y || 0;
        -      c = z || 0;
        +      values = args;
        +    }
        +
        +    for (let i = 0; i < this._values.length; i++) {
        +      if (this._values[i] !== (values[i] || 0)) {
        +        return false;
        +      }
        +    }
        +    return true;
        +  }
        +
        +  /**
        +   * Replaces the components of a <a href="#/p5.Vector">p5.Vector</a> that are very close to zero with zero.
        +   *
        +   * In computers, handling numbers with decimals can give slightly imprecise answers due to the way those numbers are represented.
        +   * This can make it hard to check if a number is zero, as it may be close but not exactly zero.
        +   * This method rounds very close numbers to zero to make those checks easier
        +   *
        +   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON
        +   *
        +   * @method clampToZero
        +   * @return {p5.Vector} with components very close to zero replaced with zero.
        +   * @chainable
        +   */
        +  clampToZero() {
        +    for (let i = 0; i < this._values.length; i++) {
        +      this._values[i] = this._clampToZero(this._values[i]);
        +    }
        +    return this;
        +  }
        +
        +  /**
        +   * Helper function for clampToZero
        +   * @private
        +   */
        +  _clampToZero(val) {
        +    return Math.abs((val || 0) - 0) <= Number.EPSILON ? 0 : val;
        +  }
        +
        +  // Static Methods
        +
        +  /**
        +   * Creates a new 2D vector from an angle.
        +   *
        +   * @static
        +   * @param {Number}     angle desired angle, in radians. Unaffected by <a href="#/p5/angleMode">angleMode()</a>.
        +   * @param {Number}     [length] length of the new vector (defaults to 1).
        +   * @return {p5.Vector}       new <a href="#/p5.Vector">p5.Vector</a> object.
        +   *
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = p5.Vector.fromAngle(0);
        +   *
        +   *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = p5.Vector.fromAngle(0, 30);
        +   *
        +   *   // Prints "p5.Vector Object : [30, 0, 0]" to the console.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A black arrow extends from the center of a gray square. It points to the right.');
        +   * }
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create a p5.Vector to the center.
        +   *   let v0 = createVector(50, 50);
        +   *
        +   *   // Create a p5.Vector with an angle 0 and magnitude 30.
        +   *   let v1 = p5.Vector.fromAngle(0, 30);
        +   *
        +   *   // Draw the black arrow.
        +   *   drawArrow(v0, v1, 'black');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  static fromAngle(angle, length) {
        +    if (typeof length === "undefined") {
        +      length = 1;
             }
        -    return this.x === a && this.y === b && this.z === c;
        +    return new Vector(length * Math.cos(angle), length * Math.sin(angle), 0);
           }
         
        -  // Static Methods
        -
           /**
        - * Creates a new 2D vector from an angle.
        - *
        - * @method fromAngle
        - * @static
        - * @param {Number}     angle desired angle, in radians. Unaffected by <a href="#/p5/angleMode">angleMode()</a>.
        - * @param {Number}     [length] length of the new vector (defaults to 1).
        - * @return {p5.Vector}       new <a href="#/p5.Vector">p5.Vector</a> object.
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = p5.Vector.fromAngle(0);
        - *
        - *   // Prints "p5.Vector Object : [1, 0, 0]" to the console.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = p5.Vector.fromAngle(0, 30);
        - *
        - *   // Prints "p5.Vector Object : [30, 0, 0]" to the console.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A black arrow extends from the center of a gray square. It points to the right.');
        - * }
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create a p5.Vector to the center.
        - *   let v0 = createVector(50, 50);
        - *
        - *   // Create a p5.Vector with an angle 0 and magnitude 30.
        - *   let v1 = p5.Vector.fromAngle(0, 30);
        - *
        - *   // Draw the black arrow.
        - *   drawArrow(v0, v1, 'black');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        -  static fromAngle(angle, length = 1) {
        -    return new p5.Vector(length * Math.cos(angle), length * Math.sin(angle), 0);
        -  }
        -
        -  /**
        - * Creates a new 3D vector from a pair of ISO spherical angles.
        - *
        - * @method fromAngles
        - * @static
        - * @param {Number}     theta    polar angle in radians (zero is up).
        - * @param {Number}     phi      azimuthal angle in radians
        - *                               (zero is out of the screen).
        - * @param {Number}     [length] length of the new vector (defaults to 1).
        - * @return {p5.Vector}          new <a href="#/p5.Vector">p5.Vector</a> object.
        - *
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = p5.Vector.fromAngles(0, 0);
        - *
        - *   // Prints "p5.Vector Object : [0, -1, 0]" to the console.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A light shines on a pink sphere as it orbits.');
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Calculate the ISO angles.
        - *   let theta = frameCount *  0.05;
        - *   let phi = 0;
        - *
        - *   // Create a p5.Vector object.
        - *   let v = p5.Vector.fromAngles(theta, phi, 100);
        - *
        - *   // Create a point light using the p5.Vector.
        - *   let c = color('deeppink');
        - *   pointLight(c, v);
        - *
        - *   // Style the sphere.
        - *   fill(255);
        - *   noStroke();
        - *
        - *   // Draw the sphere.
        - *   sphere(35);
        - * }
        - * </code>
        - * </div>
        - */
        -  static fromAngles(theta, phi, length = 1) {
        +   * Creates a new 3D vector from a pair of ISO spherical angles.
        +   *
        +   * @static
        +   * @param {Number}     theta    polar angle in radians (zero is up).
        +   * @param {Number}     phi      azimuthal angle in radians
        +   *                               (zero is out of the screen).
        +   * @param {Number}     [length] length of the new vector (defaults to 1).
        +   * @return {p5.Vector}          new <a href="#/p5.Vector">p5.Vector</a> object.
        +   *
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = p5.Vector.fromAngles(0, 0);
        +   *
        +   *   // Prints "p5.Vector Object : [0, -1, 0]" to the console.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A light shines on a pink sphere as it orbits.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Calculate the ISO angles.
        +   *   let theta = frameCount *  0.05;
        +   *   let phi = 0;
        +   *
        +   *   // Create a p5.Vector object.
        +   *   let v = p5.Vector.fromAngles(theta, phi, 100);
        +   *
        +   *   // Create a point light using the p5.Vector.
        +   *   let c = color('deeppink');
        +   *   pointLight(c, v);
        +   *
        +   *   // Style the sphere.
        +   *   fill(255);
        +   *   noStroke();
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(35);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  static fromAngles(theta, phi, length) {
        +    if (typeof length === "undefined") {
        +      length = 1;
        +    }
             const cosPhi = Math.cos(phi);
             const sinPhi = Math.sin(phi);
             const cosTheta = Math.cos(theta);
             const sinTheta = Math.sin(theta);
         
        -    return new p5.Vector(
        +    return new Vector(
               length * sinTheta * sinPhi,
               -length * cosTheta,
               length * sinTheta * cosPhi
        @@ -3241,132 +3230,125 @@ p5.Vector = class {
           }
         
           /**
        - * Creates a new 2D unit vector with a random heading.
        - *
        - * @method random2D
        - * @static
        - * @return {p5.Vector} new <a href="#/p5.Vector">p5.Vector</a> object.
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = p5.Vector.random2D();
        - *
        - *   // Prints "p5.Vector Object : [x, y, 0]" to the console
        - *   // where x and y are small random numbers.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(1);
        - *
        - *   describe('A black arrow in extends from the center of a gray square. It changes direction once per second.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create a p5.Vector to the center.
        - *   let v0 = createVector(50, 50);
        - *
        - *   // Create a random p5.Vector.
        - *   let v1 = p5.Vector.random2D();
        - *
        - *   // Scale v1 for drawing.
        - *   v1.mult(30);
        - *
        - *   // Draw the black arrow.
        - *   drawArrow(v0, v1, 'black');
        - * }
        - *
        - * // Draws an arrow between two vectors.
        - * function drawArrow(base, vec, myColor) {
        - *   push();
        - *   stroke(myColor);
        - *   strokeWeight(3);
        - *   fill(myColor);
        - *   translate(base.x, base.y);
        - *   line(0, 0, vec.x, vec.y);
        - *   rotate(vec.heading());
        - *   let arrowSize = 7;
        - *   translate(vec.mag() - arrowSize, 0);
        - *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Creates a new 2D unit vector with a random heading.
        +   *
        +   * @static
        +   * @return {p5.Vector} new <a href="#/p5.Vector">p5.Vector</a> object.
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = p5.Vector.random2D();
        +   *
        +   *   // Prints "p5.Vector Object : [x, y, 0]" to the console
        +   *   // where x and y are small random numbers.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(1);
        +   *
        +   *   describe('A black arrow in extends from the center of a gray square. It changes direction once per second.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create a p5.Vector to the center.
        +   *   let v0 = createVector(50, 50);
        +   *
        +   *   // Create a random p5.Vector.
        +   *   let v1 = p5.Vector.random2D();
        +   *
        +   *   // Scale v1 for drawing.
        +   *   v1.mult(30);
        +   *
        +   *   // Draw the black arrow.
        +   *   drawArrow(v0, v1, 'black');
        +   * }
        +   *
        +   * // Draws an arrow between two vectors.
        +   * function drawArrow(base, vec, myColor) {
        +   *   push();
        +   *   stroke(myColor);
        +   *   strokeWeight(3);
        +   *   fill(myColor);
        +   *   translate(base.x, base.y);
        +   *   line(0, 0, vec.x, vec.y);
        +   *   rotate(vec.heading());
        +   *   let arrowSize = 7;
        +   *   translate(vec.mag() - arrowSize, 0);
        +   *   triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           static random2D() {
             return this.fromAngle(Math.random() * constants.TWO_PI);
           }
         
           /**
        - * Creates a new 3D unit vector with a random heading.
        - *
        - * @method random3D
        - * @static
        - * @return {p5.Vector} new <a href="#/p5.Vector">p5.Vector</a> object.
        - * @example
        - * <div class="norender">
        - * <code>
        - * function setup() {
        - *   // Create a p5.Vector object.
        - *   let v = p5.Vector.random3D();
        - *
        - *   // Prints "p5.Vector Object : [x, y, z]" to the console
        - *   // where x, y, and z are small random numbers.
        - *   print(v.toString());
        - * }
        - * </code>
        - * </div>
        - */
        +   * Creates a new 3D unit vector with a random heading.
        +   *
        +   * @static
        +   * @return {p5.Vector} new <a href="#/p5.Vector">p5.Vector</a> object.
        +   * @example
        +   * <div class="norender">
        +   * <code>
        +   * function setup() {
        +   *   // Create a p5.Vector object.
        +   *   let v = p5.Vector.random3D();
        +   *
        +   *   // Prints "p5.Vector Object : [x, y, z]" to the console
        +   *   // where x, y, and z are small random numbers.
        +   *   print(v.toString());
        +   * }
        +   * </code>
        +   * </div>
        +   */
           static random3D() {
             const angle = Math.random() * constants.TWO_PI;
             const vz = Math.random() * 2 - 1;
             const vzBase = Math.sqrt(1 - vz * vz);
             const vx = vzBase * Math.cos(angle);
             const vy = vzBase * Math.sin(angle);
        -    return new p5.Vector(vx, vy, vz);
        +    return new Vector(vx, vy, vz);
           }
         
           // Returns a copy of a vector.
           /**
        - * @method copy
        - * @static
        - * @param  {p5.Vector} v the <a href="#/p5.Vector">p5.Vector</a> to create a copy of
        - * @return {p5.Vector} the copy of the <a href="#/p5.Vector">p5.Vector</a> object
        - */
        -
        +   * @static
        +   * @param  {p5.Vector} v the <a href="#/p5.Vector">p5.Vector</a> to create a copy of
        +   * @return {p5.Vector} the copy of the <a href="#/p5.Vector">p5.Vector</a> object
        +   */
           static copy(v) {
             return v.copy(v);
           }
         
           // Adds two vectors together and returns a new one.
           /**
        - * @method add
        - * @static
        - * @param  {p5.Vector} v1 A <a href="#/p5.Vector">p5.Vector</a> to add
        - * @param  {p5.Vector} v2 A <a href="#/p5.Vector">p5.Vector</a> to add
        - * @param  {p5.Vector} [target] vector to receive the result.
        - * @return {p5.Vector} resulting <a href="#/p5.Vector">p5.Vector</a>.
        - */
        -
        -  static add(...args) {
        -    let [v1, v2, target] = args;
        +   * @static
        +   * @param  {p5.Vector} v1 A <a href="#/p5.Vector">p5.Vector</a> to add
        +   * @param  {p5.Vector} v2 A <a href="#/p5.Vector">p5.Vector</a> to add
        +   * @param  {p5.Vector} [target] vector to receive the result.
        +   * @return {p5.Vector} resulting <a href="#/p5.Vector">p5.Vector</a>.
        +   */
        +  static add(v1, v2, target) {
             if (!target) {
               target = v1.copy();
        -      if (args.length === 3) {
        +      if (arguments.length === 3) {
                 p5._friendlyError(
        -          'The target parameter is undefined, it should be of type p5.Vector',
        -          'p5.Vector.add'
        +          "The target parameter is undefined, it should be of type p5.Vector",
        +          "p5.Vector.add"
                 );
               }
             } else {
        @@ -3378,20 +3360,18 @@ p5.Vector = class {
         
           // Returns a vector remainder when it is divided by another vector
           /**
        - * @method rem
        - * @static
        - * @param  {p5.Vector} v1 The dividend <a href="#/p5.Vector">p5.Vector</a>
        - * @param  {p5.Vector} v2 The divisor <a href="#/p5.Vector">p5.Vector</a>
        - */
        +   * @static
        +   * @param  {p5.Vector} v1 The dividend <a href="#/p5.Vector">p5.Vector</a>
        +   * @param  {p5.Vector} v2 The divisor <a href="#/p5.Vector">p5.Vector</a>
        +   */
           /**
        - * @method rem
        - * @static
        - * @param  {p5.Vector} v1
        - * @param  {p5.Vector} v2
        - * @return {p5.Vector} The resulting <a href="#/p5.Vector">p5.Vector</a>
        - */
        +   * @static
        +   * @param  {p5.Vector} v1
        +   * @param  {p5.Vector} v2
        +   * @return {p5.Vector} The resulting <a href="#/p5.Vector">p5.Vector</a>
        +   */
           static rem(v1, v2) {
        -    if (v1 instanceof p5.Vector && v2 instanceof p5.Vector) {
        +    if (v1 instanceof Vector && v2 instanceof Vector) {
               let target = v1.copy();
               target.rem(v2);
               return target;
        @@ -3399,26 +3379,23 @@ p5.Vector = class {
           }
         
           /*
        - * Subtracts one <a href="#/p5.Vector">p5.Vector</a> from another and returns a new one.  The second
        - * vector (`v2`) is subtracted from the first (`v1`), resulting in `v1-v2`.
        - */
        +   * Subtracts one <a href="#/p5.Vector">p5.Vector</a> from another and returns a new one.  The second
        +   * vector (`v2`) is subtracted from the first (`v1`), resulting in `v1-v2`.
        +   */
           /**
        - * @method sub
        - * @static
        - * @param  {p5.Vector} v1 A <a href="#/p5.Vector">p5.Vector</a> to subtract from
        - * @param  {p5.Vector} v2 A <a href="#/p5.Vector">p5.Vector</a> to subtract
        - * @param  {p5.Vector} [target] vector to receive the result.
        - * @return {p5.Vector} The resulting <a href="#/p5.Vector">p5.Vector</a>
        - */
        -
        -  static sub(...args) {
        -    let [v1, v2, target] = args;
        +   * @static
        +   * @param  {p5.Vector} v1 A <a href="#/p5.Vector">p5.Vector</a> to subtract from
        +   * @param  {p5.Vector} v2 A <a href="#/p5.Vector">p5.Vector</a> to subtract
        +   * @param  {p5.Vector} [target] vector to receive the result.
        +   * @return {p5.Vector} The resulting <a href="#/p5.Vector">p5.Vector</a>
        +   */
        +  static sub(v1, v2, target) {
             if (!target) {
               target = v1.copy();
        -      if (args.length === 3) {
        +      if (arguments.length === 3) {
                 p5._friendlyError(
        -          'The target parameter is undefined, it should be of type p5.Vector',
        -          'p5.Vector.sub'
        +          "The target parameter is undefined, it should be of type p5.Vector",
        +          "p5.Vector.sub"
                 );
               }
             } else {
        @@ -3429,52 +3406,40 @@ p5.Vector = class {
           }
         
           /**
        - * Multiplies a vector by a scalar and returns a new vector.
        - */
        -
        +   * Multiplies a vector by a scalar and returns a new vector.
        +   */
           /**
        - * @method mult
        - * @static
        - * @param  {Number} x
        - * @param  {Number} y
        - * @param  {Number} [z]
        - * @return {p5.Vector} resulting new <a href="#/p5.Vector">p5.Vector</a>.
        - */
        -
        +   * @static
        +   * @param  {Number} x
        +   * @param  {Number} y
        +   * @param  {Number} [z]
        +   * @return {p5.Vector} resulting new <a href="#/p5.Vector">p5.Vector</a>.
        +   */
           /**
        - * @method mult
        - * @static
        - * @param  {p5.Vector} v
        - * @param  {Number}  n
        - * @param  {p5.Vector} [target] vector to receive the result.
        - * @return {p5.Vector} The resulting new <a href="#/p5.Vector">p5.Vector</a>
        - */
        -
        +   * @static
        +   * @param  {p5.Vector} v
        +   * @param  {Number}  n
        +   * @param  {p5.Vector} [target] vector to receive the result.
        +   */
           /**
        - * @method mult
        - * @static
        - * @param  {p5.Vector} v0
        - * @param  {p5.Vector} v1
        - * @param  {p5.Vector} [target]
        - * @return {p5.Vector} The resulting new <a href="#/p5.Vector">p5.Vector</a>
        - */
        -
        +   * @static
        +   * @param  {p5.Vector} v0
        +   * @param  {p5.Vector} v1
        +   * @param  {p5.Vector} [target]
        +   */
           /**
        - * @method mult
        - * @static
        - * @param  {p5.Vector} v0
        - * @param  {Number[]} arr
        - * @param  {p5.Vector} [target]
        - * @return {p5.Vector} The resulting new <a href="#/p5.Vector">p5.Vector</a>
        - */
        -  static mult(...args) {
        -    let [v, n, target] = args;
        +   * @static
        +   * @param  {p5.Vector} v0
        +   * @param  {Number[]} arr
        +   * @param  {p5.Vector} [target]
        +   */
        +  static mult(v, n, target) {
             if (!target) {
               target = v.copy();
        -      if (args.length === 3) {
        +      if (arguments.length === 3) {
                 p5._friendlyError(
        -          'The target parameter is undefined, it should be of type p5.Vector',
        -          'p5.Vector.mult'
        +          "The target parameter is undefined, it should be of type p5.Vector",
        +          "p5.Vector.mult"
                 );
               }
             } else {
        @@ -3485,26 +3450,22 @@ p5.Vector = class {
           }
         
           /**
        - * Rotates the vector (only 2D vectors) by the given angle; magnitude remains the same. Returns a new vector.
        - */
        -
        +   * Rotates the vector (only 2D vectors) by the given angle; magnitude remains the same. Returns a new vector.
        +   */
           /**
        - * @method rotate
        - * @static
        - * @param  {p5.Vector} v
        - * @param  {Number} angle
        - * @param  {p5.Vector} [target] The vector to receive the result
        - * @return {p5.Vector} The resulting new <a href="#/p5.Vector">p5.Vector</a>
        - */
        -  static rotate(...args) {
        -    let [v, a, target] = args;
        -    if (args.length === 2) {
        +   * @static
        +   * @param  {p5.Vector} v
        +   * @param  {Number} angle
        +   * @param  {p5.Vector} [target] The vector to receive the result
        +   */
        +  static rotate(v, a, target) {
        +    if (arguments.length === 2) {
               target = v.copy();
             } else {
        -      if (!(target instanceof p5.Vector)) {
        +      if (!(target instanceof Vector)) {
                 p5._friendlyError(
        -          'The target parameter should be of type p5.Vector',
        -          'p5.Vector.rotate'
        +          "The target parameter should be of type p5.Vector",
        +          "p5.Vector.rotate"
                 );
               }
               target.set(v);
        @@ -3514,53 +3475,41 @@ p5.Vector = class {
           }
         
           /**
        - * Divides a vector by a scalar and returns a new vector.
        - */
        -
        +   * Divides a vector by a scalar and returns a new vector.
        +   */
           /**
        - * @method div
        - * @static
        - * @param  {Number} x
        - * @param  {Number} y
        - * @param  {Number} [z]
        - * @return {p5.Vector} The resulting new <a href="#/p5.Vector">p5.Vector</a>
        - */
        -
        +   * @static
        +   * @param  {Number} x
        +   * @param  {Number} y
        +   * @param  {Number} [z]
        +   * @return {p5.Vector} The resulting new <a href="#/p5.Vector">p5.Vector</a>
        +   */
           /**
        - * @method div
        - * @static
        - * @param  {p5.Vector} v
        - * @param  {Number}  n
        - * @param  {p5.Vector} [target] The vector to receive the result
        - * @return {p5.Vector} The resulting new <a href="#/p5.Vector">p5.Vector</a>
        - */
        -
        +   * @static
        +   * @param  {p5.Vector} v
        +   * @param  {Number}  n
        +   * @param  {p5.Vector} [target] The vector to receive the result
        +   */
           /**
        - * @method div
        - * @static
        - * @param  {p5.Vector} v0
        - * @param  {p5.Vector} v1
        - * @param  {p5.Vector} [target]
        - * @return {p5.Vector} The resulting new <a href="#/p5.Vector">p5.Vector</a>
        - */
        -
        +   * @static
        +   * @param  {p5.Vector} v0
        +   * @param  {p5.Vector} v1
        +   * @param  {p5.Vector} [target]
        +   */
           /**
        - * @method div
        - * @static
        - * @param  {p5.Vector} v0
        - * @param  {Number[]} arr
        - * @param  {p5.Vector} [target]
        - * @return {p5.Vector} The resulting new <a href="#/p5.Vector">p5.Vector</a>
        - */
        -  static div(...args) {
        -    let [v, n, target] = args;
        +   * @static
        +   * @param  {p5.Vector} v0
        +   * @param  {Number[]} arr
        +   * @param  {p5.Vector} [target]
        +   */
        +  static div(v, n, target) {
             if (!target) {
               target = v.copy();
         
        -      if (args.length === 3) {
        +      if (arguments.length === 3) {
                 p5._friendlyError(
        -          'The target parameter is undefined, it should be of type p5.Vector',
        -          'p5.Vector.div'
        +          "The target parameter is undefined, it should be of type p5.Vector",
        +          "p5.Vector.div"
                 );
               }
             } else {
        @@ -3571,69 +3520,64 @@ p5.Vector = class {
           }
         
           /**
        - * Calculates the dot product of two vectors.
        - */
        +   * Calculates the dot product of two vectors.
        +   */
           /**
        - * @method dot
        - * @static
        - * @param  {p5.Vector} v1 first <a href="#/p5.Vector">p5.Vector</a>.
        - * @param  {p5.Vector} v2 second <a href="#/p5.Vector">p5.Vector</a>.
        - * @return {Number}     dot product.
        - */
        +   * @static
        +   * @param  {p5.Vector} v1 first <a href="#/p5.Vector">p5.Vector</a>.
        +   * @param  {p5.Vector} v2 second <a href="#/p5.Vector">p5.Vector</a>.
        +   * @return {Number}     dot product.
        +   */
           static dot(v1, v2) {
             return v1.dot(v2);
           }
         
           /**
        - * Calculates the cross product of two vectors.
        - */
        +   * Calculates the cross product of two vectors.
        +   */
           /**
        - * @method cross
        - * @static
        - * @param  {p5.Vector} v1 first <a href="#/p5.Vector">p5.Vector</a>.
        - * @param  {p5.Vector} v2 second <a href="#/p5.Vector">p5.Vector</a>.
        - * @return {Number}     cross product.
        - */
        +   * @static
        +   * @param  {p5.Vector} v1 first <a href="#/p5.Vector">p5.Vector</a>.
        +   * @param  {p5.Vector} v2 second <a href="#/p5.Vector">p5.Vector</a>.
        +   * @return {Number}     cross product.
        +   */
           static cross(v1, v2) {
             return v1.cross(v2);
           }
         
           /**
        - * Calculates the Euclidean distance between two points (considering a
        - * point as a vector object).
        - */
        +   * Calculates the Euclidean distance between two points (considering a
        +   * point as a vector object).
        +   */
           /**
        - * @method dist
        - * @static
        - * @param  {p5.Vector} v1 The first <a href="#/p5.Vector">p5.Vector</a>
        - * @param  {p5.Vector} v2 The second <a href="#/p5.Vector">p5.Vector</a>
        - * @return {Number}     The distance
        - */
        +   * @static
        +   * @param  {p5.Vector} v1 The first <a href="#/p5.Vector">p5.Vector</a>
        +   * @param  {p5.Vector} v2 The second <a href="#/p5.Vector">p5.Vector</a>
        +   * @return {Number}     The distance
        +   */
           static dist(v1, v2) {
             return v1.dist(v2);
           }
         
           /**
        - * Linear interpolate a vector to another vector and return the result as a
        - * new vector.
        - */
        +   * Linear interpolate a vector to another vector and return the result as a
        +   * new vector.
        +   */
           /**
        - * @method lerp
        - * @static
        - * @param {p5.Vector} v1
        - * @param {p5.Vector} v2
        - * @param {Number} amt
        - * @param {p5.Vector} [target] The vector to receive the result
        - * @return {p5.Vector}      The lerped value
        - */
        -  static lerp(...args) {
        -    let [v1, v2, amt, target] = args;
        +   * @static
        +   * @param {p5.Vector} v1
        +   * @param {p5.Vector} v2
        +   * @param {Number} amt
        +   * @param {p5.Vector} [target] The vector to receive the result
        +   * @return {p5.Vector}      The lerped value
        +   */
        +  static lerp(v1, v2, amt, target) {
             if (!target) {
               target = v1.copy();
        -      if (args.length === 4) {
        +      if (arguments.length === 4) {
                 p5._friendlyError(
        -          'The target parameter is undefined, it should be of type p5.Vector',
        -          'p5.Vector.lerp'
        +          "The target parameter is undefined, it should be of type p5.Vector",
        +          "p5.Vector.lerp"
                 );
               }
             } else {
        @@ -3644,28 +3588,26 @@ p5.Vector = class {
           }
         
           /**
        - * Performs spherical linear interpolation with the other vector
        - * and returns the resulting vector.
        - * This works in both 3D and 2D. As for 2D, the result of slerping
        - * between 2D vectors is always a 2D vector.
        - */
        +   * Performs spherical linear interpolation with the other vector
        +   * and returns the resulting vector.
        +   * This works in both 3D and 2D. As for 2D, the result of slerping
        +   * between 2D vectors is always a 2D vector.
        +   */
           /**
        - * @method slerp
        - * @static
        - * @param {p5.Vector} v1 old vector.
        - * @param {p5.Vector} v2 new vector.
        - * @param {Number} amt
        - * @param {p5.Vector} [target] vector to receive the result.
        - * @return {p5.Vector} slerped vector between v1 and v2
        - */
        -  static slerp(...args) {
        -    let [v1, v2, amt, target] = args;
        +   * @static
        +   * @param {p5.Vector} v1 old vector.
        +   * @param {p5.Vector} v2 new vector.
        +   * @param {Number} amt
        +   * @param {p5.Vector} [target] vector to receive the result.
        +   * @return {p5.Vector} slerped vector between v1 and v2
        +   */
        +  static slerp(v1, v2, amt, target) {
             if (!target) {
               target = v1.copy();
        -      if (args.length === 4) {
        +      if (arguments.length === 4) {
                 p5._friendlyError(
        -          'The target parameter is undefined, it should be of type p5.Vector',
        -          'p5.Vector.slerp'
        +          "The target parameter is undefined, it should be of type p5.Vector",
        +          "p5.Vector.slerp"
                 );
               }
             } else {
        @@ -3676,54 +3618,50 @@ p5.Vector = class {
           }
         
           /**
        - * Calculates the magnitude (length) of the vector and returns the result as
        - * a float (this is simply the equation `sqrt(x*x + y*y + z*z)`.)
        - */
        +   * Calculates the magnitude (length) of the vector and returns the result as
        +   * a float (this is simply the equation `sqrt(x*x + y*y + z*z)`.)
        +   */
           /**
        - * @method mag
        - * @static
        - * @param {p5.Vector} vecT The vector to return the magnitude of
        - * @return {Number}        The magnitude of vecT
        - */
        +   * @static
        +   * @param {p5.Vector} vecT The vector to return the magnitude of
        +   * @return {Number}        The magnitude of vecT
        +   */
           static mag(vecT) {
             return vecT.mag();
           }
         
           /**
        - * Calculates the squared magnitude of the vector and returns the result
        - * as a float (this is simply the equation <em>(x\*x + y\*y + z\*z)</em>.)
        - * Faster if the real length is not required in the
        - * case of comparing vectors, etc.
        - */
        +   * Calculates the squared magnitude of the vector and returns the result
        +   * as a float (this is simply the equation <em>(x\*x + y\*y + z\*z)</em>.)
        +   * Faster if the real length is not required in the
        +   * case of comparing vectors, etc.
        +   */
           /**
        - * @method magSq
        - * @static
        - * @param {p5.Vector} vecT the vector to return the squared magnitude of
        - * @return {Number}        the squared magnitude of vecT
        - */
        +   * @static
        +   * @param {p5.Vector} vecT the vector to return the squared magnitude of
        +   * @return {Number}        the squared magnitude of vecT
        +   */
           static magSq(vecT) {
             return vecT.magSq();
           }
         
           /**
        - * Normalize the vector to length 1 (make it a unit vector).
        - */
        +   * Normalize the vector to length 1 (make it a unit vector).
        +   */
           /**
        - * @method normalize
        - * @static
        - * @param {p5.Vector} v  The vector to normalize
        - * @param {p5.Vector} [target] The vector to receive the result
        - * @return {p5.Vector}   The vector v, normalized to a length of 1
        - */
        -  static normalize(...args) {
        -    let [v, target] = args;
        -    if (args.length < 2) {
        +   * @static
        +   * @param {p5.Vector} v  The vector to normalize
        +   * @param {p5.Vector} [target] The vector to receive the result
        +   * @return {p5.Vector}   The vector v, normalized to a length of 1
        +   */
        +  static normalize(v, target) {
        +    if (arguments.length < 2) {
               target = v.copy();
             } else {
        -      if (!(target instanceof p5.Vector)) {
        +      if (!(target instanceof Vector)) {
                 p5._friendlyError(
        -          'The target parameter should be of type p5.Vector',
        -          'p5.Vector.normalize'
        +          "The target parameter should be of type p5.Vector",
        +          "p5.Vector.normalize"
                 );
               }
               target.set(v);
        @@ -3732,26 +3670,24 @@ p5.Vector = class {
           }
         
           /**
        - * Limit the magnitude of the vector to the value used for the <b>max</b>
        - * parameter.
        - */
        +   * Limit the magnitude of the vector to the value used for the <b>max</b>
        +   * parameter.
        +   */
           /**
        - * @method limit
        - * @static
        - * @param {p5.Vector} v  the vector to limit
        - * @param {Number}    max
        - * @param {p5.Vector} [target] the vector to receive the result (Optional)
        - * @return {p5.Vector} v with a magnitude limited to max
        - */
        -  static limit(...args) {
        -    let [v, max, target] = args;
        -    if (args.length < 3) {
        +   * @static
        +   * @param {p5.Vector} v  the vector to limit
        +   * @param {Number}    max
        +   * @param {p5.Vector} [target] the vector to receive the result (Optional)
        +   * @return {p5.Vector} v with a magnitude limited to max
        +   */
        +  static limit(v, max, target) {
        +    if (arguments.length < 3) {
               target = v.copy();
             } else {
        -      if (!(target instanceof p5.Vector)) {
        +      if (!(target instanceof Vector)) {
                 p5._friendlyError(
        -          'The target parameter should be of type p5.Vector',
        -          'p5.Vector.limit'
        +          "The target parameter should be of type p5.Vector",
        +          "p5.Vector.limit"
                 );
               }
               target.set(v);
        @@ -3760,26 +3696,24 @@ p5.Vector = class {
           }
         
           /**
        - * Set the magnitude of the vector to the value used for the <b>len</b>
        - * parameter.
        - */
        +   * Set the magnitude of the vector to the value used for the <b>len</b>
        +   * parameter.
        +   */
           /**
        - * @method setMag
        - * @static
        - * @param {p5.Vector} v  the vector to set the magnitude of
        - * @param {number}    len
        - * @param {p5.Vector} [target] the vector to receive the result (Optional)
        - * @return {p5.Vector} v with a magnitude set to len
        - */
        -  static setMag(...args) {
        -    let [v, len, target] = args;
        -    if (args.length < 3) {
        +   * @static
        +   * @param {p5.Vector} v  the vector to set the magnitude of
        +   * @param {Number}    len
        +   * @param {p5.Vector} [target] the vector to receive the result (Optional)
        +   * @return {p5.Vector} v with a magnitude set to len
        +   */
        +  static setMag(v, len, target) {
        +    if (arguments.length < 3) {
               target = v.copy();
             } else {
        -      if (!(target instanceof p5.Vector)) {
        +      if (!(target instanceof Vector)) {
                 p5._friendlyError(
        -          'The target parameter should be of type p5.Vector',
        -          'p5.Vector.setMag'
        +          "The target parameter should be of type p5.Vector",
        +          "p5.Vector.setMag"
                 );
               }
               target.set(v);
        @@ -3788,58 +3722,54 @@ p5.Vector = class {
           }
         
           /**
        - * Calculate the angle of rotation for this vector (only 2D vectors).
        - * p5.Vectors created using <a href="#/p5/createVector">createVector()</a>
        - * will take the current <a href="#/p5/angleMode">angleMode</a> into
        - * consideration, and give the angle in radians or degrees accordingly.
        - */
        +   * Calculate the angle of rotation for this vector (only 2D vectors).
        +   * p5.Vectors created using <a href="#/p5/createVector">createVector()</a>
        +   * will take the current <a href="#/p5/angleMode">angleMode</a> into
        +   * consideration, and give the angle in radians or degrees accordingly.
        +   */
           /**
        - * @method heading
        - * @static
        - * @param {p5.Vector} v the vector to find the angle of
        - * @return {Number} the angle of rotation
        - */
        +   * @static
        +   * @param {p5.Vector} v the vector to find the angle of
        +   * @return {Number} the angle of rotation
        +   */
           static heading(v) {
             return v.heading();
           }
         
           /**
        - * Calculates and returns the angle between two vectors. This function will take
        - * the <a href="#/p5/angleMode">angleMode</a> on v1 into consideration, and
        - * give the angle in radians or degrees accordingly.
        - */
        +   * Calculates and returns the angle between two vectors. This function will take
        +   * the <a href="#/p5/angleMode">angleMode</a> on v1 into consideration, and
        +   * give the angle in radians or degrees accordingly.
        +   */
           /**
        - * @method angleBetween
        - * @static
        - * @param  {p5.Vector}    v1 the first vector.
        - * @param  {p5.Vector}    v2 the second vector.
        - * @return {Number}       angle between the two vectors.
        - */
        +   * @static
        +   * @param  {p5.Vector}    v1 the first vector.
        +   * @param  {p5.Vector}    v2 the second vector.
        +   * @return {Number}       angle between the two vectors.
        +   */
           static angleBetween(v1, v2) {
             return v1.angleBetween(v2);
           }
         
           /**
        - * Reflect a vector about a normal to a line in 2D, or about a normal to a
        - * plane in 3D.
        - */
        +   * Reflect a vector about a normal to a line in 2D, or about a normal to a
        +   * plane in 3D.
        +   */
           /**
        - * @method reflect
        - * @static
        - * @param  {p5.Vector} incidentVector vector to be reflected.
        - * @param  {p5.Vector} surfaceNormal
        - * @param  {p5.Vector} [target] vector to receive the result.
        - * @return {p5.Vector} the reflected vector
        - */
        -  static reflect(...args) {
        -    let [incidentVector, surfaceNormal, target] = args;
        -    if (args.length < 3) {
        +   * @static
        +   * @param  {p5.Vector} incidentVector vector to be reflected.
        +   * @param  {p5.Vector} surfaceNormal
        +   * @param  {p5.Vector} [target] vector to receive the result.
        +   * @return {p5.Vector} the reflected vector
        +   */
        +  static reflect(incidentVector, surfaceNormal, target) {
        +    if (arguments.length < 3) {
               target = incidentVector.copy();
             } else {
        -      if (!(target instanceof p5.Vector)) {
        +      if (!(target instanceof Vector)) {
                 p5._friendlyError(
        -          'The target parameter should be of type p5.Vector',
        -          'p5.Vector.reflect'
        +          "The target parameter should be of type p5.Vector",
        +          "p5.Vector.reflect"
                 );
               }
               target.set(incidentVector);
        @@ -3848,72 +3778,159 @@ p5.Vector = class {
           }
         
           /**
        - * Return a representation of this vector as a float array. This is only
        - * for temporary use. If used in any other fashion, the contents should be
        - * copied by using the <b>p5.Vector.<a href="#/p5.Vector/copy">copy()</a></b>
        - * method to copy into your own vector.
        - */
        +   * Return a representation of this vector as a float array. This is only
        +   * for temporary use. If used in any other fashion, the contents should be
        +   * copied by using the <b>p5.Vector.<a href="#/p5.Vector/copy">copy()</a></b>
        +   * method to copy into your own vector.
        +   */
           /**
        - * @method array
        - * @static
        - * @param  {p5.Vector} v the vector to convert to an array
        - * @return {Number[]} an Array with the 3 values
        - */
        +   * @static
        +   * @param  {p5.Vector} v the vector to convert to an array
        +   * @return {Number[]} an Array with the 3 values
        +   */
           static array(v) {
             return v.array();
           }
         
           /**
        - * Equality check against a <a href="#/p5.Vector">p5.Vector</a>
        - */
        +   * Equality check against a <a href="#/p5.Vector">p5.Vector</a>
        +   */
           /**
        - * @method equals
        - * @static
        - * @param {p5.Vector|Array} v1 the first vector to compare
        - * @param {p5.Vector|Array} v2 the second vector to compare
        - * @return {Boolean}
        - */
        +   * @static
        +   * @param {p5.Vector|Array} v1 the first vector to compare
        +   * @param {p5.Vector|Array} v2 the second vector to compare
        +   * @return {Boolean}
        +   */
           static equals(v1, v2) {
             let v;
        -    if (v1 instanceof p5.Vector) {
        +    if (v1 instanceof Vector) {
               v = v1;
        -    } else if (Array.isArray(v1)) {
        -      v = new p5.Vector().set(v1);
        +    } else if (v1 instanceof Array) {
        +      v = new Vector().set(v1);
             } else {
               p5._friendlyError(
        -        'The v1 parameter should be of type Array or p5.Vector',
        -        'p5.Vector.equals'
        +        "The v1 parameter should be of type Array or p5.Vector",
        +        "p5.Vector.equals"
               );
             }
             return v.equals(v2);
           }
        +};
        +
        +function vector(p5, fn) {
        +  /**
        +   * A class to describe a two or three-dimensional vector.
        +   *
        +   * A vector can be thought of in different ways. In one view, a vector is like
        +   * an arrow pointing in space. Vectors have both magnitude (length) and
        +   * direction.
        +   *
        +   * `p5.Vector` objects are often used to program motion because they simplify
        +   * the math. For example, a moving ball has a position and a velocity.
        +   * Position describes where the ball is in space. The ball's position vector
        +   * extends from the origin to the ball's center. Velocity describes the ball's
        +   * speed and the direction it's moving. If the ball is moving straight up, its
        +   * velocity vector points straight up. Adding the ball's velocity vector to
        +   * its position vector moves it, as in `pos.add(vel)`. Vector math relies on
        +   * methods inside the `p5.Vector` class.
        +   *
        +   * Note: <a href="#/p5/createVector">createVector()</a> is the recommended way
        +   * to make an instance of this class.
        +   *
        +   * @class p5.Vector
        +   * @param {Number} [x] x component of the vector.
        +   * @param {Number} [y] y component of the vector.
        +   * @param {Number} [z] z component of the vector.
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   let p1 = createVector(25, 25);
        +   *   let p2 = createVector(75, 75);
        +   *
        +   *   // Style the points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Draw the first point using a p5.Vector.
        +   *   point(p1);
        +   *
        +   *   // Draw the second point using a p5.Vector's components.
        +   *   point(p2.x, p2.y);
        +   *
        +   *   describe('Two black dots on a gray square, one at the top left and the other at the bottom right.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let pos;
        +   * let vel;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Create p5.Vector objects.
        +   *   pos = createVector(50, 100);
        +   *   vel = createVector(0, -1);
        +   *
        +   *   describe('A black dot moves from bottom to top on a gray square. The dot reappears at the bottom when it reaches the top.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Add velocity to position.
        +   *   pos.add(vel);
        +   *
        +   *   // If the dot reaches the top of the canvas,
        +   *   // restart from the bottom.
        +   *   if (pos.y < 0) {
        +   *     pos.y = 100;
        +   *   }
        +   *
        +   *   // Draw the dot.
        +   *   strokeWeight(5);
        +   *   point(pos);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  p5.Vector = Vector;
         
        +  /**
        +   * The x component of the vector
        +   * @type {Number}
        +   * @for p5.Vector
        +   * @property x
        +   * @name x
        +   */
         
           /**
        - * Replaces the components of a <a href="#/p5.Vector">p5.Vector</a> that are very close to zero with zero.
        - *
        - * In computers, handling numbers with decimals can give slightly imprecise answers due to the way those numbers are represented.
        - * This can make it hard to check if a number is zero, as it may be close but not exactly zero.
        - * This method rounds very close numbers to zero to make those checks easier
        - *
        - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON
        - *
        - * @method clampToZero
        - * @return {p5.Vector} with components very close to zero replaced with zero.
        - * @chainable
        - */
        -  clampToZero() {
        -    this.x = this._clampToZero(this.x);
        -    this.y = this._clampToZero(this.y);
        -    this.z = this._clampToZero(this.z);
        -    return this;
        -  }
        +   * The y component of the vector
        +   * @type {Number}
        +   * @for p5.Vector
        +   * @property y
        +   * @name y
        +   */
         
           /**
        -     * Helper function for clampToZero
        -   * @private
        +   * The z component of the vector
        +   * @type {Number}
        +   * @for p5.Vector
        +   * @property z
        +   * @name z
            */
        -  _clampToZero(val) {
        -    return Math.abs((val||0) - 0) <= Number.EPSILON ? 0 : val;
        -  }
        -};export default p5.Vector;
        +}
        +
        +export default vector;
        +export { Vector };
        +
        +if (typeof p5 !== 'undefined') {
        +  vector(p5, p5.prototype);
        +}
        diff --git a/src/math/random.js b/src/math/random.js
        index f5d5568f9d..7e272c7e02 100644
        --- a/src/math/random.js
        +++ b/src/math/random.js
        @@ -5,371 +5,375 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        +function random(p5, fn){
        +  // variables used for random number generators
        +  const randomStateProp = '_lcg_random_state';
        +  // Set to values from http://en.wikipedia.org/wiki/Numerical_Recipes
        +  // m is basically chosen to be large (as it is the max period)
        +  // and for its relationships to a and c
        +  const m = 4294967296;
        +  // a - 1 should be divisible by m's prime factors
        +  const a = 1664525;
        +  // c and m should be co-prime
        +  const c = 1013904223;
        +  let y2 = 0;
         
        -// variables used for random number generators
        -const randomStateProp = '_lcg_random_state';
        -// Set to values from http://en.wikipedia.org/wiki/Numerical_Recipes
        -// m is basically chosen to be large (as it is the max period)
        -// and for its relationships to a and c
        -const m = 4294967296;
        -// a - 1 should be divisible by m's prime factors
        -const a = 1664525;
        -// c and m should be co-prime
        -const c = 1013904223;
        -let y2 = 0;
        +  // Linear Congruential Generator that stores its state at instance[stateProperty]
        +  fn._lcg = function(stateProperty) {
        +    // define the recurrence relationship
        +    this[stateProperty] = (a * this[stateProperty] + c) % m;
        +    // return a float in [0, 1)
        +    // we've just used % m, so / m is always < 1
        +    return this[stateProperty] / m;
        +  };
         
        -// Linear Congruential Generator that stores its state at instance[stateProperty]
        -p5.prototype._lcg = function(stateProperty) {
        -  // define the recurrence relationship
        -  this[stateProperty] = (a * this[stateProperty] + c) % m;
        -  // return a float in [0, 1)
        -  // we've just used % m, so / m is always < 1
        -  return this[stateProperty] / m;
        -};
        +  fn._lcgSetSeed = function(stateProperty, val) {
        +    // pick a random seed if val is undefined or null
        +    // the >>> 0 casts the seed to an unsigned 32-bit integer
        +    this[stateProperty] = (val == null ? Math.random() * m : val) >>> 0;
        +  };
         
        -p5.prototype._lcgSetSeed = function(stateProperty, val) {
        -  // pick a random seed if val is undefined or null
        -  // the >>> 0 casts the seed to an unsigned 32-bit integer
        -  this[stateProperty] = (val == null ? Math.random() * m : val) >>> 0;
        -};
        -
        -/**
        - * Sets the seed value for the <a href="#/p5/random">random()</a> and
        - * <a href="#/p5/randomGaussian">randomGaussian()</a> functions.
        - *
        - * By default, <a href="#/p5/random">random()</a> and
        - * <a href="#/p5/randomGaussian">randomGaussian()</a> produce different
        - * results each time a sketch is run. Calling `randomSeed()` with a constant
        - * argument, such as `randomSeed(99)`, makes these functions produce the same
        - * results each time a sketch is run.
        - *
        - * @method randomSeed
        - * @param {Number} seed   seed value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get random coordinates.
        - *   let x = random(0, 100);
        - *   let y = random(0, 100);
        - *
        - *   // Draw the white circle.
        - *   circle(x, y, 10);
        - *
        - *   // Set a random seed for consistency.
        - *   randomSeed(99);
        - *
        - *   // Get random coordinates.
        - *   x = random(0, 100);
        - *   y = random(0, 100);
        - *
        - *   // Draw the black circle.
        - *   fill(0);
        - *   circle(x, y, 10);
        - *
        - *   describe('A white circle appears at a random position. A black circle appears at (27.4, 25.8).');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.randomSeed = function(seed) {
        -  this._lcgSetSeed(randomStateProp, seed);
        -  this._gaussian_previous = false;
        -};
        +  /**
        +   * Sets the seed value for the <a href="#/p5/random">random()</a> and
        +   * <a href="#/p5/randomGaussian">randomGaussian()</a> functions.
        +   *
        +   * By default, <a href="#/p5/random">random()</a> and
        +   * <a href="#/p5/randomGaussian">randomGaussian()</a> produce different
        +   * results each time a sketch is run. Calling `randomSeed()` with a constant
        +   * argument, such as `randomSeed(99)`, makes these functions produce the same
        +   * results each time a sketch is run.
        +   *
        +   * @method randomSeed
        +   * @param {Number} seed   seed value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get random coordinates.
        +   *   let x = random(0, 100);
        +   *   let y = random(0, 100);
        +   *
        +   *   // Draw the white circle.
        +   *   circle(x, y, 10);
        +   *
        +   *   // Set a random seed for consistency.
        +   *   randomSeed(99);
        +   *
        +   *   // Get random coordinates.
        +   *   x = random(0, 100);
        +   *   y = random(0, 100);
        +   *
        +   *   // Draw the black circle.
        +   *   fill(0);
        +   *   circle(x, y, 10);
        +   *
        +   *   describe('A white circle appears at a random position. A black circle appears at (27.4, 25.8).');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.randomSeed = function(seed) {
        +    this._lcgSetSeed(randomStateProp, seed);
        +    this._gaussian_previous = false;
        +  };
         
        -/**
        - * Returns a random number or a random element from an array.
        - *
        - * `random()` follows uniform distribution, which means that all outcomes are
        - * equally likely. When `random()` is used to generate numbers, all
        - * numbers in the output range are equally likely to be returned. When
        - * `random()` is used to select elements from an array, all elements are
        - * equally likely to be chosen.
        - *
        - * By default, `random()` produces different results each time a sketch runs.
        - * The <a href="#/p5/randomSeed">randomSeed()</a> function can be used to
        - * generate the same sequence of numbers or choices each time a sketch runs.
        - *
        - * The version of `random()` with no parameters returns a random number from 0
        - * up to but not including 1.
        - *
        - * The version of `random()` with one parameter works one of two ways. If the
        - * argument passed is a number, `random()` returns a random number from 0 up
        - * to but not including the number. For example, calling `random(5)` returns
        - * values between 0 and 5. If the argument passed is an array, `random()`
        - * returns a random element from that array. For example, calling
        - * `random(['🦁', '🐯', '🐻'])` returns either a lion, tiger, or bear emoji.
        - *
        - * The version of `random()` with two parameters returns a random number from
        - * a given range. The arguments passed set the range's lower and upper bounds.
        - * For example, calling `random(-5, 10.2)` returns values from -5 up to but
        - * not including 10.2.
        - *
        - * @method random
        - * @param  {Number} [min]   lower bound (inclusive).
        - * @param  {Number} [max]   upper bound (exclusive).
        - * @return {Number} random number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get random coordinates between 0 and 100.
        - *   let x = random(0, 100);
        - *   let y = random(0, 100);
        - *
        - *   // Draw a point.
        - *   strokeWeight(5);
        - *   point(x, y);
        - *
        - *   describe('A black dot appears in a random position on a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get random coordinates between 0 and 100.
        - *   let x = random(100);
        - *   let y = random(100);
        - *
        - *   // Draw the point.
        - *   strokeWeight(5);
        - *   point(x, y);
        - *
        - *   describe('A black dot appears in a random position on a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of emoji strings.
        - *   let animals = ['🦁', '🐯', '🐻'];
        - *
        - *   // Choose a random element from the array.
        - *   let choice = random(animals);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(20);
        - *
        - *   // Display the emoji.
        - *   text(choice, 50, 50);
        - *
        - *   describe('An animal face is displayed at random. Either a lion, tiger, or bear.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(5);
        - *
        - *   describe('A black dot moves around randomly on a gray square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Get random coordinates between 0 and 100.
        - *   let x = random(100);
        - *   let y = random(100);
        - *
        - *   // Draw the point.
        - *   strokeWeight(5);
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Slow the frame rate.
        - *   frameRate(5);
        - *
        - *   describe('A black dot moves around randomly in the middle of a gray square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Get random coordinates between 45 and 55.
        - *   let x = random(45, 55);
        - *   let y = random(45, 55);
        - *
        - *   // Draw the point.
        - *   strokeWeight(5);
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let x = 50;
        - * let y = 50;
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A black dot moves around randomly leaving a trail.');
        - * }
        - *
        - * function draw() {
        - *   // Update x and y randomly.
        - *   x += random(-1, 1);
        - *   y += random(-1, 1);
        - *
        - *   // Draw the point.
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method random
        - * @param  {Array} choices   array to choose from.
        - * @return {*} random element from the array.
        - */
        -p5.prototype.random = function(min, max) {
        -  p5._validateParameters('random', arguments);
        -  let rand;
        +  /**
        +   * Returns a random number or a random element from an array.
        +   *
        +   * `random()` follows uniform distribution, which means that all outcomes are
        +   * equally likely. When `random()` is used to generate numbers, all
        +   * numbers in the output range are equally likely to be returned. When
        +   * `random()` is used to select elements from an array, all elements are
        +   * equally likely to be chosen.
        +   *
        +   * By default, `random()` produces different results each time a sketch runs.
        +   * The <a href="#/p5/randomSeed">randomSeed()</a> function can be used to
        +   * generate the same sequence of numbers or choices each time a sketch runs.
        +   *
        +   * The version of `random()` with no parameters returns a random number from 0
        +   * up to but not including 1.
        +   *
        +   * The version of `random()` with one parameter works one of two ways. If the
        +   * argument passed is a number, `random()` returns a random number from 0 up
        +   * to but not including the number. For example, calling `random(5)` returns
        +   * values between 0 and 5. If the argument passed is an array, `random()`
        +   * returns a random element from that array. For example, calling
        +   * `random(['🦁', '🐯', '🐻'])` returns either a lion, tiger, or bear emoji.
        +   *
        +   * The version of `random()` with two parameters returns a random number from
        +   * a given range. The arguments passed set the range's lower and upper bounds.
        +   * For example, calling `random(-5, 10.2)` returns values from -5 up to but
        +   * not including 10.2.
        +   *
        +   * @method random
        +   * @param  {Number} [min]   lower bound (inclusive).
        +   * @param  {Number} [max]   upper bound (exclusive).
        +   * @return {Number} random number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get random coordinates between 0 and 100.
        +   *   let x = random(0, 100);
        +   *   let y = random(0, 100);
        +   *
        +   *   // Draw a point.
        +   *   strokeWeight(5);
        +   *   point(x, y);
        +   *
        +   *   describe('A black dot appears in a random position on a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get random coordinates between 0 and 100.
        +   *   let x = random(100);
        +   *   let y = random(100);
        +   *
        +   *   // Draw the point.
        +   *   strokeWeight(5);
        +   *   point(x, y);
        +   *
        +   *   describe('A black dot appears in a random position on a gray square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of emoji strings.
        +   *   let animals = ['🦁', '🐯', '🐻'];
        +   *
        +   *   // Choose a random element from the array.
        +   *   let choice = random(animals);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(20);
        +   *
        +   *   // Display the emoji.
        +   *   text(choice, 50, 50);
        +   *
        +   *   describe('An animal face is displayed at random. Either a lion, tiger, or bear.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(5);
        +   *
        +   *   describe('A black dot moves around randomly on a gray square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Get random coordinates between 0 and 100.
        +   *   let x = random(100);
        +   *   let y = random(100);
        +   *
        +   *   // Draw the point.
        +   *   strokeWeight(5);
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Slow the frame rate.
        +   *   frameRate(5);
        +   *
        +   *   describe('A black dot moves around randomly in the middle of a gray square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Get random coordinates between 45 and 55.
        +   *   let x = random(45, 55);
        +   *   let y = random(45, 55);
        +   *
        +   *   // Draw the point.
        +   *   strokeWeight(5);
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let x = 50;
        +   * let y = 50;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A black dot moves around randomly leaving a trail.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Update x and y randomly.
        +   *   x += random(-1, 1);
        +   *   y += random(-1, 1);
        +   *
        +   *   // Draw the point.
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method random
        +   * @param  {Array} choices   array to choose from.
        +   * @return {*} random element from the array.
        +   */
        +  fn.random = function(min, max) {
        +    // p5._validateParameters('random', arguments);
        +    let rand;
         
        -  if (this[randomStateProp] != null) {
        -    rand = this._lcg(randomStateProp);
        -  } else {
        -    rand = Math.random();
        -  }
        -  if (typeof min === 'undefined') {
        -    return rand;
        -  } else if (typeof max === 'undefined') {
        -    if (Array.isArray(min)) {
        -      return min[Math.floor(rand * min.length)];
        +    if (this[randomStateProp] != null) {
        +      rand = this._lcg(randomStateProp);
             } else {
        -      return rand * min;
        +      rand = Math.random();
             }
        -  } else {
        -    if (min > max) {
        -      const tmp = min;
        -      min = max;
        -      max = tmp;
        +    if (typeof min === 'undefined') {
        +      return rand;
        +    } else if (typeof max === 'undefined') {
        +      if (min instanceof Array) {
        +        return min[Math.floor(rand * min.length)];
        +      } else {
        +        return rand * min;
        +      }
        +    } else {
        +      if (min > max) {
        +        const tmp = min;
        +        min = max;
        +        max = tmp;
        +      }
        +
        +      return rand * (max - min) + min;
             }
        +  };
         
        -    return rand * (max - min) + min;
        -  }
        -};
        +  /**
        +   * Returns a random number fitting a Gaussian, or normal, distribution.
        +   *
        +   * Normal distributions look like bell curves when plotted. Values from a
        +   * normal distribution cluster around a central value called the mean. The
        +   * cluster's standard deviation describes its spread.
        +   *
        +   * By default, `randomGaussian()` produces different results each time a
        +   * sketch runs. The <a href="#/p5/randomSeed">randomSeed()</a> function can be
        +   * used to generate the same sequence of numbers each time a sketch runs.
        +   *
        +   * There's no minimum or maximum value that `randomGaussian()` might return.
        +   * Values far from the mean are very unlikely and values near the mean are
        +   * very likely.
        +   *
        +   * The version of `randomGaussian()` with no parameters returns values with a
        +   * mean of 0 and standard deviation of 1.
        +   *
        +   * The version of `randomGaussian()` with one parameter interprets the
        +   * argument passed as the mean. The standard deviation is 1.
        +   *
        +   * The version of `randomGaussian()` with two parameters interprets the first
        +   * argument passed as the mean and the second as the standard deviation.
        +   *
        +   * @method randomGaussian
        +   * @param  {Number} [mean]  mean.
        +   * @param  {Number} [sd]    standard deviation.
        +   * @return {Number} random number.
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('Three horizontal black lines are filled in randomly. The top line spans entire canvas. The middle line is very short. The bottom line spans two-thirds of the canvas.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Style the circles.
        +   *   noStroke();
        +   *   fill(0, 10);
        +   *
        +   *   // Uniform distribution between 0 and 100.
        +   *   let x = random(100);
        +   *   let y = 25;
        +   *   circle(x, y, 5);
        +   *
        +   *   // Gaussian distribution with a mean of 50 and sd of 1.
        +   *   x = randomGaussian(50);
        +   *   y = 50;
        +   *   circle(x, y, 5);
        +   *
        +   *   // Gaussian distribution with a mean of 50 and sd of 10.
        +   *   x = randomGaussian(50, 10);
        +   *   y = 75;
        +   *   circle(x, y, 5);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.randomGaussian = function(mean, sd = 1) {
        +    let y1, x1, x2, w;
        +    if (this._gaussian_previous) {
        +      y1 = y2;
        +      this._gaussian_previous = false;
        +    } else {
        +      do {
        +        x1 = this.random(2) - 1;
        +        x2 = this.random(2) - 1;
        +        w = x1 * x1 + x2 * x2;
        +      } while (w >= 1);
        +      w = Math.sqrt(-2 * Math.log(w) / w);
        +      y1 = x1 * w;
        +      y2 = x2 * w;
        +      this._gaussian_previous = true;
        +    }
         
        -/**
        - * Returns a random number fitting a Gaussian, or normal, distribution.
        - *
        - * Normal distributions look like bell curves when plotted. Values from a
        - * normal distribution cluster around a central value called the mean. The
        - * cluster's standard deviation describes its spread.
        - *
        - * By default, `randomGaussian()` produces different results each time a
        - * sketch runs. The <a href="#/p5/randomSeed">randomSeed()</a> function can be
        - * used to generate the same sequence of numbers each time a sketch runs.
        - *
        - * There's no minimum or maximum value that `randomGaussian()` might return.
        - * Values far from the mean are very unlikely and values near the mean are
        - * very likely.
        - *
        - * The version of `randomGaussian()` with no parameters returns values with a
        - * mean of 0 and standard deviation of 1.
        - *
        - * The version of `randomGaussian()` with one parameter interprets the
        - * argument passed as the mean. The standard deviation is 1.
        - *
        - * The version of `randomGaussian()` with two parameters interprets the first
        - * argument passed as the mean and the second as the standard deviation.
        - *
        - * @method randomGaussian
        - * @param  {Number} [mean]  mean.
        - * @param  {Number} [sd]    standard deviation.
        - * @return {Number} random number.
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('Three horizontal black lines are filled in randomly. The top line spans entire canvas. The middle line is very short. The bottom line spans two-thirds of the canvas.');
        - * }
        - *
        - * function draw() {
        - *   // Style the circles.
        - *   noStroke();
        - *   fill(0, 10);
        - *
        - *   // Uniform distribution between 0 and 100.
        - *   let x = random(100);
        - *   let y = 25;
        - *   circle(x, y, 5);
        - *
        - *   // Gaussian distribution with a mean of 50 and sd of 1.
        - *   x = randomGaussian(50);
        - *   y = 50;
        - *   circle(x, y, 5);
        - *
        - *   // Gaussian distribution with a mean of 50 and sd of 10.
        - *   x = randomGaussian(50, 10);
        - *   y = 75;
        - *   circle(x, y, 5);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.randomGaussian = function(mean, sd = 1) {
        -  let y1, x1, x2, w;
        -  if (this._gaussian_previous) {
        -    y1 = y2;
        -    this._gaussian_previous = false;
        -  } else {
        -    do {
        -      x1 = this.random(2) - 1;
        -      x2 = this.random(2) - 1;
        -      w = x1 * x1 + x2 * x2;
        -    } while (w >= 1);
        -    w = Math.sqrt(-2 * Math.log(w) / w);
        -    y1 = x1 * w;
        -    y2 = x2 * w;
        -    this._gaussian_previous = true;
        -  }
        +    const m = mean || 0;
        +    return y1 * sd + m;
        +  };
        +}
         
        -  const m = mean || 0;
        -  return y1 * sd + m;
        -};
        +export default random;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  random(p5, p5.prototype);
        +}
        diff --git a/src/math/trigonometry.js b/src/math/trigonometry.js
        index cb2af7c709..39b8e4e196 100644
        --- a/src/math/trigonometry.js
        +++ b/src/math/trigonometry.js
        @@ -6,824 +6,831 @@
          * @requires constants
          */
         
        -import p5 from '../core/main';
         import * as constants from '../core/constants';
         
        -/*
        - * all DEGREES/RADIANS conversion should be done in the p5 instance
        - * if possible, using the p5._toRadians(), p5._fromRadians() methods.
        - */
        -p5.prototype._angleMode = constants.RADIANS;
        +function trigonometry(p5, fn){
        +  const DEGREES = fn.DEGREES = 'degrees';
        +  const RADIANS = fn.RADIANS = 'radians';
         
        -/**
        - * Calculates the arc cosine of a number.
        - *
        - * `acos()` is the inverse of <a href="#/p5/cos">cos()</a>. It expects
        - * arguments in the range -1 to 1. By default, `acos()` returns values in the
        - * range 0 to &pi; (about 3.14). If the
        - * <a href="#/p5/angleMode">angleMode()</a> is `DEGREES`, then values are
        - * returned in the range 0 to 180.
        - *
        - * @method acos
        - * @param  {Number} value value whose arc cosine is to be returned.
        - * @return {Number}       arc cosine of the given value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Calculate cos() and acos() values.
        - *   let a = PI;
        - *   let c = cos(a);
        - *   let ac = acos(c);
        - *
        - *   // Display the values.
        - *   text(`${round(a, 3)}`, 35, 25);
        - *   text(`${round(c, 3)}`, 35, 50);
        - *   text(`${round(ac, 3)}`, 35, 75);
        - *
        - *   describe('The numbers 3.142, -1, and 3.142 written on separate rows.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Calculate cos() and acos() values.
        - *   let a = PI + QUARTER_PI;
        - *   let c = cos(a);
        - *   let ac = acos(c);
        - *
        - *   // Display the values.
        - *   text(`${round(a, 3)}`, 35, 25);
        - *   text(`${round(c, 3)}`, 35, 50);
        - *   text(`${round(ac, 3)}`, 35, 75);
        - *
        - *   describe('The numbers 3.927, -0.707, and 2.356 written on separate rows.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.acos = function(ratio) {
        -  return this._fromRadians(Math.acos(ratio));
        -};
        +  /*
        +   * all DEGREES/RADIANS conversion should be done in the p5 instance
        +   * if possible, using the p5._toRadians(), p5._fromRadians() methods.
        +   */
        +  fn._angleMode = RADIANS;
         
        -/**
        - * Calculates the arc sine of a number.
        - *
        - * `asin()` is the inverse of <a href="#/p5/sin">sin()</a>. It expects input
        - * values in the range of -1 to 1. By default, `asin()` returns values in the
        - * range -&pi; &divide; 2 (about -1.57) to &pi; &divide; 2 (about 1.57). If
        - * the <a href="#/p5/angleMode">angleMode()</a> is `DEGREES` then values are
        - * returned in the range -90 to 90.
        - *
        - * @method asin
        - * @param  {Number} value value whose arc sine is to be returned.
        - * @return {Number}       arc sine of the given value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Calculate sin() and asin() values.
        - *   let a = PI / 3;
        - *   let s = sin(a);
        - *   let as = asin(s);
        - *
        - *   // Display the values.
        - *   text(`${round(a, 3)}`, 35, 25);
        - *   text(`${round(s, 3)}`, 35, 50);
        - *   text(`${round(as, 3)}`, 35, 75);
        - *
        - *   describe('The numbers 1.047, 0.866, and 1.047 written on separate rows.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Calculate sin() and asin() values.
        - *   let a = PI + PI / 3;
        - *   let s = sin(a);
        - *   let as = asin(s);
        - *
        - *   // Display the values.
        - *   text(`${round(a, 3)}`, 35, 25);
        - *   text(`${round(s, 3)}`, 35, 50);
        - *   text(`${round(as, 3)}`, 35, 75);
        - *
        - *   describe('The numbers 4.189, -0.866, and -1.047 written on separate rows.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.asin = function(ratio) {
        -  return this._fromRadians(Math.asin(ratio));
        -};
        +  /**
        +   * Calculates the arc cosine of a number.
        +   *
        +   * `acos()` is the inverse of <a href="#/p5/cos">cos()</a>. It expects
        +   * arguments in the range -1 to 1. By default, `acos()` returns values in the
        +   * range 0 to &pi; (about 3.14). If the
        +   * <a href="#/p5/angleMode">angleMode()</a> is `DEGREES`, then values are
        +   * returned in the range 0 to 180.
        +   *
        +   * @method acos
        +   * @param  {Number} value value whose arc cosine is to be returned.
        +   * @return {Number}       arc cosine of the given value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Calculate cos() and acos() values.
        +   *   let a = PI;
        +   *   let c = cos(a);
        +   *   let ac = acos(c);
        +   *
        +   *   // Display the values.
        +   *   text(`${round(a, 3)}`, 35, 25);
        +   *   text(`${round(c, 3)}`, 35, 50);
        +   *   text(`${round(ac, 3)}`, 35, 75);
        +   *
        +   *   describe('The numbers 3.142, -1, and 3.142 written on separate rows.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Calculate cos() and acos() values.
        +   *   let a = PI + QUARTER_PI;
        +   *   let c = cos(a);
        +   *   let ac = acos(c);
        +   *
        +   *   // Display the values.
        +   *   text(`${round(a, 3)}`, 35, 25);
        +   *   text(`${round(c, 3)}`, 35, 50);
        +   *   text(`${round(ac, 3)}`, 35, 75);
        +   *
        +   *   describe('The numbers 3.927, -0.707, and 2.356 written on separate rows.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.acos = function(ratio) {
        +    return this._fromRadians(Math.acos(ratio));
        +  };
         
        -/**
        - * Calculates the arc tangent of a number.
        - *
        - * `atan()` is the inverse of <a href="#/p5/tan">tan()</a>. It expects input
        - * values in the range of -Infinity to Infinity. By default, `atan()` returns
        - * values in the range -&pi; &divide; 2 (about -1.57) to &pi; &divide; 2
        - * (about 1.57). If the <a href="#/p5/angleMode">angleMode()</a> is `DEGREES`
        - * then values are returned in the range -90 to 90.
        - *
        - * @method atan
        - * @param  {Number} value value whose arc tangent is to be returned.
        - * @return {Number}       arc tangent of the given value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Calculate tan() and atan() values.
        - *   let a = PI / 3;
        - *   let t = tan(a);
        - *   let at = atan(t);
        - *
        - *   // Display the values.
        - *   text(`${round(a, 3)}`, 35, 25);
        - *   text(`${round(t, 3)}`, 35, 50);
        - *   text(`${round(at, 3)}`, 35, 75);
        - *
        - *   describe('The numbers 1.047, 1.732, and 1.047 written on separate rows.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Calculate tan() and atan() values.
        - *   let a = PI + PI / 3;
        - *   let t = tan(a);
        - *   let at = atan(t);
        - *
        - *   // Display the values.
        - *   text(`${round(a, 3)}`, 35, 25);
        - *   text(`${round(t, 3)}`, 35, 50);
        - *   text(`${round(at, 3)}`, 35, 75);
        - *
        - *   describe('The numbers 4.189, 1.732, and 1.047 written on separate rows.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.atan = function(ratio) {
        -  return this._fromRadians(Math.atan(ratio));
        -};
        +  /**
        +   * Calculates the arc sine of a number.
        +   *
        +   * `asin()` is the inverse of <a href="#/p5/sin">sin()</a>. It expects input
        +   * values in the range of -1 to 1. By default, `asin()` returns values in the
        +   * range -&pi; &divide; 2 (about -1.57) to &pi; &divide; 2 (about 1.57). If
        +   * the <a href="#/p5/angleMode">angleMode()</a> is `DEGREES` then values are
        +   * returned in the range -90 to 90.
        +   *
        +   * @method asin
        +   * @param  {Number} value value whose arc sine is to be returned.
        +   * @return {Number}       arc sine of the given value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Calculate sin() and asin() values.
        +   *   let a = PI / 3;
        +   *   let s = sin(a);
        +   *   let as = asin(s);
        +   *
        +   *   // Display the values.
        +   *   text(`${round(a, 3)}`, 35, 25);
        +   *   text(`${round(s, 3)}`, 35, 50);
        +   *   text(`${round(as, 3)}`, 35, 75);
        +   *
        +   *   describe('The numbers 1.047, 0.866, and 1.047 written on separate rows.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Calculate sin() and asin() values.
        +   *   let a = PI + PI / 3;
        +   *   let s = sin(a);
        +   *   let as = asin(s);
        +   *
        +   *   // Display the values.
        +   *   text(`${round(a, 3)}`, 35, 25);
        +   *   text(`${round(s, 3)}`, 35, 50);
        +   *   text(`${round(as, 3)}`, 35, 75);
        +   *
        +   *   describe('The numbers 4.189, -0.866, and -1.047 written on separate rows.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.asin = function(ratio) {
        +    return this._fromRadians(Math.asin(ratio));
        +  };
         
        -/**
        - * Calculates the angle formed by a point, the origin, and the positive
        - * x-axis.
        - *
        - * `atan2()` is most often used for orienting geometry to the mouse's
        - * position, as in `atan2(mouseY, mouseX)`. The first parameter is the point's
        - * y-coordinate and the second parameter is its x-coordinate.
        - *
        - * By default, `atan2()` returns values in the range
        - * -&pi; (about -3.14) to &pi; (3.14). If the
        - * <a href="#/p5/angleMode">angleMode()</a> is `DEGREES`, then values are
        - * returned in the range -180 to 180.
        - *
        - * @method atan2
        - * @param  {Number} y y-coordinate of the point.
        - * @param  {Number} x x-coordinate of the point.
        - * @return {Number}   arc tangent of the given point.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A rectangle at the top-left of the canvas rotates with mouse movements.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the angle between the mouse
        - *   // and the origin.
        - *   let a = atan2(mouseY, mouseX);
        - *
        - *   // Rotate.
        - *   rotate(a);
        - *
        - *   // Draw the shape.
        - *   rect(0, 0, 60, 10);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A rectangle at the center of the canvas rotates with mouse movements.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin to the center.
        - *   translate(50, 50);
        - *
        - *   // Get the mouse's coordinates relative to the origin.
        - *   let x = mouseX - 50;
        - *   let y = mouseY - 50;
        - *
        - *   // Calculate the angle between the mouse and the origin.
        - *   let a = atan2(y, x);
        - *
        - *   // Rotate.
        - *   rotate(a);
        - *
        - *   // Draw the shape.
        - *   rect(-30, -5, 60, 10);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.atan2 = function(y, x) {
        -  return this._fromRadians(Math.atan2(y, x));
        -};
        +  /**
        +   * Calculates the arc tangent of a number.
        +   *
        +   * `atan()` is the inverse of <a href="#/p5/tan">tan()</a>. It expects input
        +   * values in the range of -Infinity to Infinity. By default, `atan()` returns
        +   * values in the range -&pi; &divide; 2 (about -1.57) to &pi; &divide; 2
        +   * (about 1.57). If the <a href="#/p5/angleMode">angleMode()</a> is `DEGREES`
        +   * then values are returned in the range -90 to 90.
        +   *
        +   * @method atan
        +   * @param  {Number} value value whose arc tangent is to be returned.
        +   * @return {Number}       arc tangent of the given value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Calculate tan() and atan() values.
        +   *   let a = PI / 3;
        +   *   let t = tan(a);
        +   *   let at = atan(t);
        +   *
        +   *   // Display the values.
        +   *   text(`${round(a, 3)}`, 35, 25);
        +   *   text(`${round(t, 3)}`, 35, 50);
        +   *   text(`${round(at, 3)}`, 35, 75);
        +   *
        +   *   describe('The numbers 1.047, 1.732, and 1.047 written on separate rows.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Calculate tan() and atan() values.
        +   *   let a = PI + PI / 3;
        +   *   let t = tan(a);
        +   *   let at = atan(t);
        +   *
        +   *   // Display the values.
        +   *   text(`${round(a, 3)}`, 35, 25);
        +   *   text(`${round(t, 3)}`, 35, 50);
        +   *   text(`${round(at, 3)}`, 35, 75);
        +   *
        +   *   describe('The numbers 4.189, 1.732, and 1.047 written on separate rows.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.atan = function(ratio) {
        +    return this._fromRadians(Math.atan(ratio));
        +  };
         
        -/**
        - * Calculates the cosine of an angle.
        - *
        - * `cos()` is useful for many geometric tasks in creative coding. The values
        - * returned oscillate between -1 and 1 as the input angle increases. `cos()`
        - * takes into account the current <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * @method cos
        - * @param  {Number} angle the angle in radians unless specified by <a href="/reference/p5/angleMode/">angleMode()</a>.
        - * @return {Number}       cosine of the angle.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white ball on a string oscillates left and right.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the coordinates.
        - *   let x = 30 * cos(frameCount * 0.05) + 50;
        - *   let y = 50;
        - *
        - *   // Draw the oscillator.
        - *   line(50, y, x, y);
        - *   circle(x, y, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A series of black dots form a wave pattern.');
        - * }
        - *
        - * function draw() {
        - *   // Calculate the coordinates.
        - *   let x = frameCount;
        - *   let y = 30 * cos(x * 0.1) + 50;
        - *
        - *   // Draw the point.
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A series of black dots form an infinity symbol.');
        - * }
        - *
        - * function draw() {
        - *   // Calculate the coordinates.
        - *   let x = 30 * cos(frameCount * 0.1) + 50;
        - *   let y = 10 * sin(frameCount * 0.2) + 50;
        - *
        - *   // Draw the point.
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.cos = function(angle) {
        -  return Math.cos(this._toRadians(angle));
        -};
        +  /**
        +   * Calculates the angle formed by a point, the origin, and the positive
        +   * x-axis.
        +   *
        +   * `atan2()` is most often used for orienting geometry to the mouse's
        +   * position, as in `atan2(mouseY, mouseX)`. The first parameter is the point's
        +   * y-coordinate and the second parameter is its x-coordinate.
        +   *
        +   * By default, `atan2()` returns values in the range
        +   * -&pi; (about -3.14) to &pi; (3.14). If the
        +   * <a href="#/p5/angleMode">angleMode()</a> is `DEGREES`, then values are
        +   * returned in the range -180 to 180.
        +   *
        +   * @method atan2
        +   * @param  {Number} y y-coordinate of the point.
        +   * @param  {Number} x x-coordinate of the point.
        +   * @return {Number}   arc tangent of the given point.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A rectangle at the top-left of the canvas rotates with mouse movements.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the angle between the mouse
        +   *   // and the origin.
        +   *   let a = atan2(mouseY, mouseX);
        +   *
        +   *   // Rotate.
        +   *   rotate(a);
        +   *
        +   *   // Draw the shape.
        +   *   rect(0, 0, 60, 10);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A rectangle at the center of the canvas rotates with mouse movements.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin to the center.
        +   *   translate(50, 50);
        +   *
        +   *   // Get the mouse's coordinates relative to the origin.
        +   *   let x = mouseX - 50;
        +   *   let y = mouseY - 50;
        +   *
        +   *   // Calculate the angle between the mouse and the origin.
        +   *   let a = atan2(y, x);
        +   *
        +   *   // Rotate.
        +   *   rotate(a);
        +   *
        +   *   // Draw the shape.
        +   *   rect(-30, -5, 60, 10);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.atan2 = function(y, x) {
        +    return this._fromRadians(Math.atan2(y, x));
        +  };
         
        -/**
        - * Calculates the sine of an angle.
        - *
        - * `sin()` is useful for many geometric tasks in creative coding. The values
        - * returned oscillate between -1 and 1 as the input angle increases. `sin()`
        - * takes into account the current <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * @method sin
        - * @param  {Number} angle the angle in radians unless specified by <a href="/reference/p5/angleMode/">angleMode()</a>.
        - * @return {Number}       sine of the angle.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white ball on a string oscillates up and down.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the coordinates.
        - *   let x = 50;
        - *   let y = 30 * sin(frameCount * 0.05) + 50;
        - *
        - *   // Draw the oscillator.
        - *   line(50, y, x, y);
        - *   circle(x, y, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A series of black dots form a wave pattern.');
        - * }
        - *
        - * function draw() {
        - *   // Calculate the coordinates.
        - *   let x = frameCount;
        - *   let y = 30 * sin(x * 0.1) + 50;
        - *
        - *   // Draw the point.
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A series of black dots form an infinity symbol.');
        - * }
        - *
        - * function draw() {
        - *   // Calculate the coordinates.
        - *   let x = 30 * cos(frameCount * 0.1) + 50;
        - *   let y = 10 * sin(frameCount * 0.2) + 50;
        - *
        - *   // Draw the point.
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.sin = function(angle) {
        -  return Math.sin(this._toRadians(angle));
        -};
        +  /**
        +   * Calculates the cosine of an angle.
        +   *
        +   * `cos()` is useful for many geometric tasks in creative coding. The values
        +   * returned oscillate between -1 and 1 as the input angle increases. `cos()`
        +   * takes into account the current <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * @method cos
        +   * @param  {Number} angle the angle in radians unless specified by <a href="/reference/p5/angleMode/">angleMode()</a>.
        +   * @return {Number}       cosine of the angle.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white ball on a string oscillates left and right.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the coordinates.
        +   *   let x = 30 * cos(frameCount * 0.05) + 50;
        +   *   let y = 50;
        +   *
        +   *   // Draw the oscillator.
        +   *   line(50, y, x, y);
        +   *   circle(x, y, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A series of black dots form a wave pattern.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Calculate the coordinates.
        +   *   let x = frameCount;
        +   *   let y = 30 * cos(x * 0.1) + 50;
        +   *
        +   *   // Draw the point.
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A series of black dots form an infinity symbol.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Calculate the coordinates.
        +   *   let x = 30 * cos(frameCount * 0.1) + 50;
        +   *   let y = 10 * sin(frameCount * 0.2) + 50;
        +   *
        +   *   // Draw the point.
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.cos = function(angle) {
        +    return Math.cos(this._toRadians(angle));
        +  };
         
        -/**
        - * Calculates the tangent of an angle.
        - *
        - * `tan()` is useful for many geometric tasks in creative coding. The values
        - * returned range from -Infinity to Infinity and repeat periodically as the
        - * input angle increases. `tan()` takes into account the current
        - * <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * @method tan
        - * @param  {Number} angle the angle in radians unless specified by <a href="/reference/p5/angleMode/">angleMode()</a>.
        - * @return {Number}       tangent of the angle.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   describe('A series of identical curves drawn with black dots. Each curve starts from the top of the canvas, continues down at a slight angle, flattens out at the middle of the canvas, then continues to the bottom.');
        - * }
        - *
        - * function draw() {
        - *   // Calculate the coordinates.
        - *   let x = frameCount;
        - *   let y = 5 * tan(x * 0.1) + 50;
        - *
        - *   // Draw the point.
        - *   point(x, y);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.tan = function(angle) {
        -  return Math.tan(this._toRadians(angle));
        -};
        +  /**
        +   * Calculates the sine of an angle.
        +   *
        +   * `sin()` is useful for many geometric tasks in creative coding. The values
        +   * returned oscillate between -1 and 1 as the input angle increases. `sin()`
        +   * takes into account the current <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * @method sin
        +   * @param  {Number} angle the angle in radians unless specified by <a href="/reference/p5/angleMode/">angleMode()</a>.
        +   * @return {Number}       sine of the angle.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white ball on a string oscillates up and down.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the coordinates.
        +   *   let x = 50;
        +   *   let y = 30 * sin(frameCount * 0.05) + 50;
        +   *
        +   *   // Draw the oscillator.
        +   *   line(50, y, x, y);
        +   *   circle(x, y, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A series of black dots form a wave pattern.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Calculate the coordinates.
        +   *   let x = frameCount;
        +   *   let y = 30 * sin(x * 0.1) + 50;
        +   *
        +   *   // Draw the point.
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A series of black dots form an infinity symbol.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Calculate the coordinates.
        +   *   let x = 30 * cos(frameCount * 0.1) + 50;
        +   *   let y = 10 * sin(frameCount * 0.2) + 50;
        +   *
        +   *   // Draw the point.
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.sin = function(angle) {
        +    return Math.sin(this._toRadians(angle));
        +  };
         
        -/**
        - * Converts an angle measured in radians to its value in degrees.
        - *
        - * Degrees and radians are both units for measuring angles. There are 360˚ in
        - * one full rotation. A full rotation is 2 &times; &pi; (about 6.28) radians.
        - *
        - * The same angle can be expressed in with either unit. For example, 90° is a
        - * quarter of a full rotation. The same angle is 2 &times; &pi; &divide; 4
        - * (about 1.57) radians.
        - *
        - * @method degrees
        - * @param  {Number} radians radians value to convert to degrees.
        - * @return {Number}         converted angle.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Calculate the angle conversion.
        - *   let rad = QUARTER_PI;
        - *   let deg = degrees(rad);
        - *
        - *   // Display the conversion.
        - *   text(`${round(rad, 2)} rad = ${deg}˚`, 10, 50);
        - *
        - *   describe('The text "0.79 rad = 45˚".');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.degrees = angle => angle * constants.RAD_TO_DEG;
        +  /**
        +   * Calculates the tangent of an angle.
        +   *
        +   * `tan()` is useful for many geometric tasks in creative coding. The values
        +   * returned range from -Infinity to Infinity and repeat periodically as the
        +   * input angle increases. `tan()` takes into account the current
        +   * <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * @method tan
        +   * @param  {Number} angle the angle in radians unless specified by <a href="/reference/p5/angleMode/">angleMode()</a>.
        +   * @return {Number}       tangent of the angle.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   describe('A series of identical curves drawn with black dots. Each curve starts from the top of the canvas, continues down at a slight angle, flattens out at the middle of the canvas, then continues to the bottom.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Calculate the coordinates.
        +   *   let x = frameCount;
        +   *   let y = 5 * tan(x * 0.1) + 50;
        +   *
        +   *   // Draw the point.
        +   *   point(x, y);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.tan = function(angle) {
        +    return Math.tan(this._toRadians(angle));
        +  };
         
        -/**
        - * Converts an angle measured in degrees to its value in radians.
        - *
        - * Degrees and radians are both units for measuring angles. There are 360˚ in
        - * one full rotation. A full rotation is 2 &times; &pi; (about 6.28) radians.
        - *
        - * The same angle can be expressed in with either unit. For example, 90° is a
        - * quarter of a full rotation. The same angle is 2 &times; &pi; &divide; 4
        - * (about 1.57) radians.
        - *
        - * @method radians
        - * @param  {Number} degrees degree value to convert to radians.
        - * @return {Number}         converted angle.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Caclulate the angle conversion.
        - *   let deg = 45;
        - *   let rad = radians(deg);
        - *
        - *   // Display the angle conversion.
        - *   text(`${deg}˚ = ${round(rad, 3)} rad`, 10, 50);
        - *
        - *   describe('The text "45˚ = 0.785 rad".');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.radians = angle => angle * constants.DEG_TO_RAD;
        +  /**
        +   * Converts an angle measured in radians to its value in degrees.
        +   *
        +   * Degrees and radians are both units for measuring angles. There are 360˚ in
        +   * one full rotation. A full rotation is 2 &times; &pi; (about 6.28) radians.
        +   *
        +   * The same angle can be expressed in with either unit. For example, 90° is a
        +   * quarter of a full rotation. The same angle is 2 &times; &pi; &divide; 4
        +   * (about 1.57) radians.
        +   *
        +   * @method degrees
        +   * @param  {Number} radians radians value to convert to degrees.
        +   * @return {Number}         converted angle.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Calculate the angle conversion.
        +   *   let rad = QUARTER_PI;
        +   *   let deg = degrees(rad);
        +   *
        +   *   // Display the conversion.
        +   *   text(`${round(rad, 2)} rad = ${deg}˚`, 10, 50);
        +   *
        +   *   describe('The text "0.79 rad = 45˚".');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.degrees = angle => angle * constants.RAD_TO_DEG;
         
        -/**
        - * Changes the unit system used to measure angles.
        - *
        - * Degrees and radians are both units for measuring angles. There are 360˚ in
        - * one full rotation. A full rotation is 2 &times; &pi; (about 6.28) radians.
        - *
        - * Functions such as <a href="#/p5/rotate">rotate()</a> and
        - * <a href="#/p5/sin">sin()</a> expect angles measured radians by default.
        - * Calling `angleMode(DEGREES)` switches to degrees. Calling
        - * `angleMode(RADIANS)` switches back to radians.
        - *
        - * Calling `angleMode()` with no arguments returns current angle mode, which
        - * is either `RADIANS` or `DEGREES`.
        - *
        - * @method angleMode
        - * @param {Constant} mode either RADIANS or DEGREES.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Rotate 1/8 turn.
        - *   rotate(QUARTER_PI);
        - *
        - *   // Draw a line.
        - *   line(0, 0, 80, 0);
        - *
        - *   describe('A diagonal line radiating from the top-left corner of a square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   // Rotate 1/8 turn.
        - *   rotate(45);
        - *
        - *   // Draw a line.
        - *   line(0, 0, 80, 0);
        - *
        - *   describe('A diagonal line radiating from the top-left corner of a square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Calculate the angle to rotate.
        - *   let angle = TWO_PI / 7;
        - *
        - *   // Move the origin to the center.
        - *   translate(50, 50);
        - *
        - *   // Style the flower.
        - *   noStroke();
        - *   fill(255, 50);
        - *
        - *   // Draw the flower.
        - *   for (let i = 0; i < 7; i += 1) {
        - *     ellipse(0, 0, 80, 20);
        - *     rotate(angle);
        - *   }
        - *
        - *   describe('A translucent white flower on a dark background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(50);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   // Calculate the angle to rotate.
        - *   let angle = 360 / 7;
        - *
        - *   // Move the origin to the center.
        - *   translate(50, 50);
        - *
        - *   // Style the flower.
        - *   noStroke();
        - *   fill(255, 50);
        - *
        - *   // Draw the flower.
        - *   for (let i = 0; i < 7; i += 1) {
        - *     ellipse(0, 0, 80, 20);
        - *     rotate(angle);
        - *   }
        - *
        - *   describe('A translucent white flower on a dark background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white ball on a string oscillates left and right.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the coordinates.
        - *   let x = 30 * cos(frameCount * 0.05) + 50;
        - *   let y = 50;
        - *
        - *   // Draw the oscillator.
        - *   line(50, y, x, y);
        - *   circle(x, y, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   describe('A white ball on a string oscillates left and right.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the coordinates.
        - *   let x = 30 * cos(frameCount * 2.86) + 50;
        - *   let y = 50;
        - *
        - *   // Draw the oscillator.
        - *   line(50, y, x, y);
        - *   circle(x, y, 20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw the upper line.
        - *   rotate(PI / 6);
        - *   line(0, 0, 80, 0);
        - *
        - *   // Use degrees.
        - *   angleMode(DEGREES);
        - *
        - *   // Draw the lower line.
        - *   rotate(30);
        - *   line(0, 0, 80, 0);
        - *
        - *   describe('Two diagonal lines radiating from the top-left corner of a square. The lines are oriented 30 degrees from the edges of the square and 30 degrees apart from each other.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method angleMode
        - * @return {Constant} mode either RADIANS or DEGREES
        - */
        -p5.prototype.angleMode = function(mode) {
        -  p5._validateParameters('angleMode', arguments);
        -  if (typeof mode === 'undefined') {
        -    return this._angleMode;
        -  } else if (mode === constants.DEGREES || mode === constants.RADIANS) {
        -    const prevMode = this._angleMode;
        +  /**
        +   * Converts an angle measured in degrees to its value in radians.
        +   *
        +   * Degrees and radians are both units for measuring angles. There are 360˚ in
        +   * one full rotation. A full rotation is 2 &times; &pi; (about 6.28) radians.
        +   *
        +   * The same angle can be expressed in with either unit. For example, 90° is a
        +   * quarter of a full rotation. The same angle is 2 &times; &pi; &divide; 4
        +   * (about 1.57) radians.
        +   *
        +   * @method radians
        +   * @param  {Number} degrees degree value to convert to radians.
        +   * @return {Number}         converted angle.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Caclulate the angle conversion.
        +   *   let deg = 45;
        +   *   let rad = radians(deg);
        +   *
        +   *   // Display the angle conversion.
        +   *   text(`${deg}˚ = ${round(rad, 3)} rad`, 10, 50);
        +   *
        +   *   describe('The text "45˚ = 0.785 rad".');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.radians = angle => angle * constants.DEG_TO_RAD;
        +
        +  /**
        +   * Changes the unit system used to measure angles.
        +   *
        +   * Degrees and radians are both units for measuring angles. There are 360˚ in
        +   * one full rotation. A full rotation is 2 &times; &pi; (about 6.28) radians.
        +   *
        +   * Functions such as <a href="#/p5/rotate">rotate()</a> and
        +   * <a href="#/p5/sin">sin()</a> expect angles measured radians by default.
        +   * Calling `angleMode(DEGREES)` switches to degrees. Calling
        +   * `angleMode(RADIANS)` switches back to radians.
        +   *
        +   * Calling `angleMode()` with no arguments returns current angle mode, which
        +   * is either `RADIANS` or `DEGREES`.
        +   *
        +   * @method angleMode
        +   * @param {(RADIANS|DEGREES)} mode either RADIANS or DEGREES.
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Rotate 1/8 turn.
        +   *   rotate(QUARTER_PI);
        +   *
        +   *   // Draw a line.
        +   *   line(0, 0, 80, 0);
        +   *
        +   *   describe('A diagonal line radiating from the top-left corner of a square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   // Rotate 1/8 turn.
        +   *   rotate(45);
        +   *
        +   *   // Draw a line.
        +   *   line(0, 0, 80, 0);
        +   *
        +   *   describe('A diagonal line radiating from the top-left corner of a square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Calculate the angle to rotate.
        +   *   let angle = TWO_PI / 7;
        +   *
        +   *   // Move the origin to the center.
        +   *   translate(50, 50);
        +   *
        +   *   // Style the flower.
        +   *   noStroke();
        +   *   fill(255, 50);
        +   *
        +   *   // Draw the flower.
        +   *   for (let i = 0; i < 7; i += 1) {
        +   *     ellipse(0, 0, 80, 20);
        +   *     rotate(angle);
        +   *   }
        +   *
        +   *   describe('A translucent white flower on a dark background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   // Calculate the angle to rotate.
        +   *   let angle = 360 / 7;
        +   *
        +   *   // Move the origin to the center.
        +   *   translate(50, 50);
        +   *
        +   *   // Style the flower.
        +   *   noStroke();
        +   *   fill(255, 50);
        +   *
        +   *   // Draw the flower.
        +   *   for (let i = 0; i < 7; i += 1) {
        +   *     ellipse(0, 0, 80, 20);
        +   *     rotate(angle);
        +   *   }
        +   *
        +   *   describe('A translucent white flower on a dark background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white ball on a string oscillates left and right.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the coordinates.
        +   *   let x = 30 * cos(frameCount * 0.05) + 50;
        +   *   let y = 50;
        +   *
        +   *   // Draw the oscillator.
        +   *   line(50, y, x, y);
        +   *   circle(x, y, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   describe('A white ball on a string oscillates left and right.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the coordinates.
        +   *   let x = 30 * cos(frameCount * 2.86) + 50;
        +   *   let y = 50;
        +   *
        +   *   // Draw the oscillator.
        +   *   line(50, y, x, y);
        +   *   circle(x, y, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw the upper line.
        +   *   rotate(PI / 6);
        +   *   line(0, 0, 80, 0);
        +   *
        +   *   // Use degrees.
        +   *   angleMode(DEGREES);
        +   *
        +   *   // Draw the lower line.
        +   *   rotate(30);
        +   *   line(0, 0, 80, 0);
        +   *
        +   *   describe('Two diagonal lines radiating from the top-left corner of a square. The lines are oriented 30 degrees from the edges of the square and 30 degrees apart from each other.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method angleMode
        +   * @return {(RADIANS|DEGREES)} mode either RADIANS or DEGREES
        +   */
        +  fn.angleMode = function(mode) {
        +    // p5._validateParameters('angleMode', arguments);
        +    if (typeof mode === 'undefined') {
        +      return this._angleMode;
        +    } else if (mode === DEGREES || mode === RADIANS) {
        +      const prevMode = this._angleMode;
         
        -    // No change
        -    if(mode === prevMode) return;
        +      // No change
        +      if(mode === prevMode) return;
         
        -    // Otherwise adjust pRotation according to new mode
        -    // This is necessary for acceleration events to work properly
        -    if(mode === constants.RADIANS) {
        -      // Change pRotation to radians
        -      this._setProperty('pRotationX', this.pRotationX * constants.DEG_TO_RAD);
        -      this._setProperty('pRotationY', this.pRotationY * constants.DEG_TO_RAD);
        -      this._setProperty('pRotationZ', this.pRotationZ * constants.DEG_TO_RAD);
        -    } else {
        -      // Change pRotation to degrees
        -      this._setProperty('pRotationX', this.pRotationX * constants.RAD_TO_DEG);
        -      this._setProperty('pRotationY', this.pRotationY * constants.RAD_TO_DEG);
        -      this._setProperty('pRotationZ', this.pRotationZ * constants.RAD_TO_DEG);
        +      // Otherwise adjust pRotation according to new mode
        +      // This is necessary for acceleration events to work properly
        +      if(mode === RADIANS) {
        +        // Change pRotation to radians
        +        this.pRotationX = this.pRotationX * constants.DEG_TO_RAD;
        +        this.pRotationY = this.pRotationY * constants.DEG_TO_RAD;
        +        this.pRotationZ = this.pRotationZ * constants.DEG_TO_RAD;
        +      } else {
        +        // Change pRotation to degrees
        +        this.pRotationX = this.pRotationX * constants.RAD_TO_DEG;
        +        this.pRotationY = this.pRotationY * constants.RAD_TO_DEG;
        +        this.pRotationZ = this.pRotationZ * constants.RAD_TO_DEG;
        +      }
        +
        +      this._angleMode = mode;
             }
        +  };
         
        -    this._angleMode = mode;
        -  }
        -};
        +  /**
        +   * converts angles from the current angleMode to RADIANS
        +   *
        +   * @method _toRadians
        +   * @private
        +   * @param {Number} angle
        +   * @returns {Number}
        +   */
        +  fn._toRadians = function(angle) {
        +    if (this._angleMode === DEGREES) {
        +      return angle * constants.DEG_TO_RAD;
        +    }
        +    return angle;
        +  };
         
        -/**
        - * converts angles from the current angleMode to RADIANS
        - *
        - * @method _toRadians
        - * @private
        - * @param {Number} angle
        - * @returns {Number}
        - */
        -p5.prototype._toRadians = function(angle) {
        -  if (this._angleMode === constants.DEGREES) {
        -    return angle * constants.DEG_TO_RAD;
        -  }
        -  return angle;
        -};
        +  /**
        +   * converts angles from the current angleMode to DEGREES
        +   *
        +   * @method _toDegrees
        +   * @private
        +   * @param {Number} angle
        +   * @returns {Number}
        +   */
        +  fn._toDegrees = function(angle) {
        +    if (this._angleMode === RADIANS) {
        +      return angle * constants.RAD_TO_DEG;
        +    }
        +    return angle;
        +  };
         
        -/**
        - * converts angles from the current angleMode to DEGREES
        - *
        - * @method _toDegrees
        - * @private
        - * @param {Number} angle
        - * @returns {Number}
        - */
        -p5.prototype._toDegrees = function(angle) {
        -  if (this._angleMode === constants.RADIANS) {
        -    return angle * constants.RAD_TO_DEG;
        -  }
        -  return angle;
        -};
        +  /**
        +   * converts angles from RADIANS into the current angleMode
        +   *
        +   * @method _fromRadians
        +   * @private
        +   * @param {Number} angle
        +   * @returns {Number}
        +   */
        +  fn._fromRadians = function(angle) {
        +    if (this._angleMode === DEGREES) {
        +      return angle * constants.RAD_TO_DEG;
        +    }
        +    return angle;
        +  };
         
        -/**
        - * converts angles from RADIANS into the current angleMode
        - *
        - * @method _fromRadians
        - * @private
        - * @param {Number} angle
        - * @returns {Number}
        - */
        -p5.prototype._fromRadians = function(angle) {
        -  if (this._angleMode === constants.DEGREES) {
        -    return angle * constants.RAD_TO_DEG;
        -  }
        -  return angle;
        -};
        +  /**
        +   * converts angles from DEGREES into the current angleMode
        +   *
        +   * @method _fromDegrees
        +   * @private
        +   * @param {Number} angle
        +   * @returns {Number}
        +   */
        +  fn._fromDegrees = function(angle) {
        +    if (this._angleMode === RADIANS) {
        +      return angle * constants.DEG_TO_RAD;
        +    }
        +    return angle;
        +  };
        +}
         
        -/**
        - * converts angles from DEGREES into the current angleMode
        - *
        - * @method _fromDegrees
        - * @private
        - * @param {Number} angle
        - * @returns {Number}
        - */
        -p5.prototype._fromDegrees = function(angle) {
        -  if (this._angleMode === constants.RADIANS) {
        -    return angle * constants.DEG_TO_RAD;
        -  }
        -  return angle;
        -};
        +export default trigonometry;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  trigonometry(p5, p5.prototype);
        +}
        diff --git a/src/shape/2d_primitives.js b/src/shape/2d_primitives.js
        new file mode 100644
        index 0000000000..eca8b6e05f
        --- /dev/null
        +++ b/src/shape/2d_primitives.js
        @@ -0,0 +1,1452 @@
        +/**
        + * @module Shape
        + * @submodule 2D Primitives
        + * @for p5
        + * @requires core
        + * @requires constants
        + */
        +
        +import * as constants from '../core/constants';
        +import canvas from '../core/helpers';
        +
        +function primitives(p5, fn){
        +  /**
        +   * This function does 3 things:
        +   *
        +   *   1. Bounds the desired start/stop angles for an arc (in radians) so that:
        +   *
        +   *          0 <= start < TWO_PI ;    start <= stop < start + TWO_PI
        +   *
        +   *      This means that the arc rendering functions don't have to be concerned
        +   *      with what happens if stop is smaller than start, or if the arc 'goes
        +   *      round more than once', etc.: they can just start at start and increase
        +   *      until stop and the correct arc will be drawn.
        +   *
        +   *   2. Optionally adjusts the angles within each quadrant to counter the naive
        +   *      scaling of the underlying ellipse up from the unit circle.  Without
        +   *      this, the angles become arbitrary when width != height: 45 degrees
        +   *      might be drawn at 5 degrees on a 'wide' ellipse, or at 85 degrees on
        +   *      a 'tall' ellipse.
        +   *
        +   *   3. Flags up when start and stop correspond to the same place on the
        +   *      underlying ellipse.  This is useful if you want to do something special
        +   *      there (like rendering a whole ellipse instead).
        +   */
        +  fn._normalizeArcAngles = (
        +    start,
        +    stop,
        +    width,
        +    height,
        +    correctForScaling
        +  ) => {
        +    const epsilon = 0.00001; // Smallest visible angle on displays up to 4K.
        +    let separation;
        +
        +    // The order of the steps is important here: each one builds upon the
        +    // adjustments made in the steps that precede it.
        +
        +    // Constrain both start and stop to [0,TWO_PI).
        +    start = start - constants.TWO_PI * Math.floor(start / constants.TWO_PI);
        +    stop = stop - constants.TWO_PI * Math.floor(stop / constants.TWO_PI);
        +
        +    // Get the angular separation between the requested start and stop points.
        +    //
        +    // Technically this separation only matches what gets drawn if
        +    // correctForScaling is enabled.  We could add a more complicated calculation
        +    // for when the scaling is uncorrected (in which case the drawn points could
        +    // end up pushed together or pulled apart quite dramatically relative to what
        +    // was requested), but it would make things more opaque for little practical
        +    // benefit.
        +    //
        +    // (If you do disable correctForScaling and find that correspondToSamePoint
        +    // is set too aggressively, the easiest thing to do is probably to just make
        +    // epsilon smaller...)
        +    separation = Math.min(
        +      Math.abs(start - stop),
        +      constants.TWO_PI - Math.abs(start - stop)
        +    );
        +
        +    // Optionally adjust the angles to counter linear scaling.
        +    if (correctForScaling) {
        +      if (start <= constants.HALF_PI) {
        +        start = Math.atan(width / height * Math.tan(start));
        +      } else if (start > constants.HALF_PI && start <= 3 * constants.HALF_PI) {
        +        start = Math.atan(width / height * Math.tan(start)) + constants.PI;
        +      } else {
        +        start = Math.atan(width / height * Math.tan(start)) + constants.TWO_PI;
        +      }
        +      if (stop <= constants.HALF_PI) {
        +        stop = Math.atan(width / height * Math.tan(stop));
        +      } else if (stop > constants.HALF_PI && stop <= 3 * constants.HALF_PI) {
        +        stop = Math.atan(width / height * Math.tan(stop)) + constants.PI;
        +      } else {
        +        stop = Math.atan(width / height * Math.tan(stop)) + constants.TWO_PI;
        +      }
        +    }
        +
        +    // Ensure that start <= stop < start + TWO_PI.
        +    if (start > stop) {
        +      stop += constants.TWO_PI;
        +    }
        +
        +    return {
        +      start,
        +      stop,
        +      correspondToSamePoint: separation < epsilon
        +    };
        +  };
        +
        +  /**
        +   * Draws an arc.
        +   *
        +   * An arc is a section of an ellipse defined by the `x`, `y`, `w`, and
        +   * `h` parameters. `x` and `y` set the location of the arc's center. `w` and
        +   * `h` set the arc's width and height. See
        +   * <a href="#/p5/ellipse">ellipse()</a> and
        +   * <a href="#/p5/ellipseMode">ellipseMode()</a> for more details.
        +   *
        +   * The fifth and sixth parameters, `start` and `stop`, set the angles
        +   * between which to draw the arc. Arcs are always drawn clockwise from
        +   * `start` to `stop`. Angles are always given in radians.
        +   *
        +   * The seventh parameter, `mode`, is optional. It determines the arc's fill
        +   * style. The fill modes are a semi-circle (`OPEN`), a closed semi-circle
        +   * (`CHORD`), or a closed pie segment (`PIE`).
        +   *
        +   * The eighth parameter, `detail`, is also optional. It determines how many
        +   * vertices are used to draw the arc in WebGL mode. The default value is 25.
        +   *
        +   * @method arc
        +   * @param  {Number} x      x-coordinate of the arc's ellipse.
        +   * @param  {Number} y      y-coordinate of the arc's ellipse.
        +   * @param  {Number} w      width of the arc's ellipse by default.
        +   * @param  {Number} h      height of the arc's ellipse by default.
        +   * @param  {Number} start  angle to start the arc, specified in radians.
        +   * @param  {Number} stop   angle to stop the arc, specified in radians.
        +   * @param  {(CHORD|PIE|OPEN)} [mode] optional parameter to determine the way of drawing
        +   *                         the arc. either CHORD, PIE, or OPEN.
        +   * @param  {Integer} [detail] optional parameter for WebGL mode only. This is to
        +   *                         specify the number of vertices that makes up the
        +   *                         perimeter of the arc. Default value is 25. Won't
        +   *                         draw a stroke for a detail of more than 50.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   arc(50, 50, 80, 80, 0, PI + HALF_PI);
        +   *
        +   *   describe('A white circle on a gray canvas. The top-right quarter of the circle is missing.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   arc(50, 50, 80, 40, 0, PI + HALF_PI);
        +   *
        +   *   describe('A white ellipse on a gray canvas. The top-right quarter of the ellipse is missing.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Bottom-right.
        +   *   arc(50, 55, 50, 50, 0, HALF_PI);
        +   *
        +   *   noFill();
        +   *
        +   *   // Bottom-left.
        +   *   arc(50, 55, 60, 60, HALF_PI, PI);
        +   *
        +   *   // Top-left.
        +   *   arc(50, 55, 70, 70, PI, PI + QUARTER_PI);
        +   *
        +   *   // Top-right.
        +   *   arc(50, 55, 80, 80, PI + QUARTER_PI, TWO_PI);
        +   *
        +   *   describe(
        +   *     'A shattered outline of an circle with a quarter of a white circle at the bottom-right.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Default fill mode.
        +   *   arc(50, 50, 80, 80, 0, PI + QUARTER_PI);
        +   *
        +   *   describe('A white circle with the top-right third missing. The bottom is outlined in black.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // OPEN fill mode.
        +   *   arc(50, 50, 80, 80, 0, PI + QUARTER_PI, OPEN);
        +   *
        +   *   describe(
        +   *     'A white circle missing a section from the top-right. The bottom is outlined in black.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // CHORD fill mode.
        +   *   arc(50, 50, 80, 80, 0, PI + QUARTER_PI, CHORD);
        +   *
        +   *   describe('A white circle with a black outline missing a section from the top-right.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // PIE fill mode.
        +   *   arc(50, 50, 80, 80, 0, PI + QUARTER_PI, PIE);
        +   *
        +   *   describe('A white circle with a black outline. The top-right third is missing.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // PIE fill mode.
        +   *   arc(0, 0, 80, 80, 0, PI + QUARTER_PI, PIE);
        +   *
        +   *   describe('A white circle with a black outline. The top-right third is missing.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // PIE fill mode with 5 vertices.
        +   *   arc(0, 0, 80, 80, 0, PI + QUARTER_PI, PIE, 5);
        +   *
        +   *   describe('A white circle with a black outline. The top-right third is missing.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A yellow circle on a black background. The circle opens and closes its mouth.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Style the arc.
        +   *   noStroke();
        +   *   fill(255, 255, 0);
        +   *
        +   *   // Update start and stop angles.
        +   *   let biteSize = PI / 16;
        +   *   let startAngle = biteSize * sin(frameCount * 0.1) + biteSize;
        +   *   let endAngle = TWO_PI - startAngle;
        +   *
        +   *   // Draw the arc.
        +   *   arc(50, 50, 80, 80, startAngle, endAngle, PIE);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.arc = function(x, y, w, h, start, stop, mode, detail) {
        +    // this.validate("p5.arc", arguments);
        +    // p5._validateParameters('arc', arguments);
        +
        +    // if the current stroke and fill settings wouldn't result in something
        +    // visible, exit immediately
        +    if (!this._renderer.states.strokeColor && !this._renderer.states.fillColor) {
        +      return this;
        +    }
        +
        +    if (start === stop) {
        +      return this;
        +    }
        +
        +    start = this._toRadians(start);
        +    stop = this._toRadians(stop);
        +
        +    const vals = canvas.modeAdjust(x, y, w, h, this._renderer.states.ellipseMode);
        +    const angles = this._normalizeArcAngles(start, stop, vals.w, vals.h, true);
        +
        +    if (angles.correspondToSamePoint) {
        +      // If the arc starts and ends at (near enough) the same place, we choose to
        +      // draw an ellipse instead.  This is preferable to faking an ellipse (by
        +      // making stop ever-so-slightly less than start + TWO_PI) because the ends
        +      // join up to each other rather than at a vertex at the centre (leaving
        +      // an unwanted spike in the stroke/fill).
        +      this._renderer.ellipse([vals.x, vals.y, vals.w, vals.h, detail]);
        +    } else {
        +      this._renderer.arc(
        +        vals.x,
        +        vals.y,
        +        vals.w,
        +        vals.h,
        +        angles.start, // [0, TWO_PI)
        +        angles.stop, // [start, start + TWO_PI)
        +        mode,
        +        detail
        +      );
        +
        +      //accessible Outputs
        +      if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        +        this._accsOutput('arc', [
        +          vals.x,
        +          vals.y,
        +          vals.w,
        +          vals.h,
        +          angles.start,
        +          angles.stop,
        +          mode
        +        ]);
        +      }
        +    }
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Draws an ellipse (oval).
        +   *
        +   * An ellipse is a round shape defined by the `x`, `y`, `w`, and
        +   * `h` parameters. `x` and `y` set the location of its center. `w` and
        +   * `h` set its width and height. See
        +   * <a href="#/p5/ellipseMode">ellipseMode()</a> for other ways to set
        +   * its position.
        +   *
        +   * If no height is set, the value of width is used for both the width and
        +   * height. If a negative height or width is specified, the absolute value is
        +   * taken.
        +   *
        +   * The fifth parameter, `detail`, is also optional. It determines how many
        +   * vertices are used to draw the ellipse in WebGL mode. The default value is
        +   * 25.
        +   *
        +   * @method ellipse
        +   * @param  {Number} x x-coordinate of the center of the ellipse.
        +   * @param  {Number} y y-coordinate of the center of the ellipse.
        +   * @param  {Number} w width of the ellipse.
        +   * @param  {Number} [h] height of the ellipse.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   ellipse(50, 50, 80, 80);
        +   *
        +   *   describe('A white circle on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   ellipse(50, 50, 80);
        +   *
        +   *   describe('A white circle on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   ellipse(50, 50, 80, 40);
        +   *
        +   *   describe('A white ellipse on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   ellipse(0, 0, 80, 40);
        +   *
        +   *   describe('A white ellipse on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Use 6 vertices.
        +   *   ellipse(0, 0, 80, 40, 6);
        +   *
        +   *   describe('A white hexagon on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method ellipse
        +   * @param  {Number} x
        +   * @param  {Number} y
        +   * @param  {Number} w
        +   * @param  {Number} h
        +   * @param  {Integer} [detail] optional parameter for WebGL mode only. This is to
        +   *                         specify the number of vertices that makes up the
        +   *                         perimeter of the ellipse. Default value is 25. Won't
        +   *                         draw a stroke for a detail of more than 50.
        +   */
        +  fn.ellipse = function(x, y, w, h, detailX) {
        +    // p5._validateParameters('ellipse', arguments);
        +    return this._renderEllipse(...arguments);
        +  };
        +
        +  /**
        +   * Draws a circle.
        +   *
        +   * A circle is a round shape defined by the `x`, `y`, and `d` parameters.
        +   * `x` and `y` set the location of its center. `d` sets its width and height (diameter).
        +   * Every point on the circle's edge is the same distance, `0.5 * d`, from its center.
        +   * `0.5 * d` (half the diameter) is the circle's radius.
        +   * See <a href="#/p5/ellipseMode">ellipseMode()</a> for other ways to set its position.
        +   *
        +   * @method circle
        +   * @param  {Number} x  x-coordinate of the center of the circle.
        +   * @param  {Number} y  y-coordinate of the center of the circle.
        +   * @param  {Number} d  diameter of the circle.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   circle(50, 50, 25);
        +   *
        +   *   describe('A white circle with black outline in the middle of a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   circle(0, 0, 25);
        +   *
        +   *   describe('A white circle with black outline in the middle of a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.circle = function(...args) {
        +    // p5._validateParameters('circle', args);
        +    const argss = args.slice( 0, 2);
        +    argss.push(args[2], args[2]);
        +    return this._renderEllipse(...argss);
        +  };
        +
        +  // internal method for drawing ellipses (without parameter validation)
        +  fn._renderEllipse = function(x, y, w, h, detailX) {
        +    // if the current stroke and fill settings wouldn't result in something
        +    // visible, exit immediately
        +    if (!this._renderer.states.strokeColor && !this._renderer.states.fillColor) {
        +      return this;
        +    }
        +
        +    // Duplicate 3rd argument if only 3 given.
        +    if (typeof h === 'undefined') {
        +      h = w;
        +    }
        +
        +    const vals = canvas.modeAdjust(x, y, w, h, this._renderer.states.ellipseMode);
        +    this._renderer.ellipse([vals.x, vals.y, vals.w, vals.h, detailX]);
        +
        +    //accessible Outputs
        +    if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        +      this._accsOutput('ellipse', [vals.x, vals.y, vals.w, vals.h]);
        +    }
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Draws a straight line between two points.
        +   *
        +   * A line's default width is one pixel. The version of `line()` with four
        +   * parameters draws the line in 2D. To color a line, use the
        +   * <a href="#/p5/stroke">stroke()</a> function. To change its width, use the
        +   * <a href="#/p5/strokeWeight">strokeWeight()</a> function. A line
        +   * can't be filled, so the <a href="#/p5/fill">fill()</a> function won't
        +   * affect the line's color.
        +   *
        +   * The version of `line()` with six parameters allows the line to be drawn in
        +   * 3D space. Doing so requires adding the `WEBGL` argument to
        +   * <a href="#/p5/createCanvas">createCanvas()</a>.
        +   *
        +   * @method line
        +   * @param  {Number} x1 the x-coordinate of the first point.
        +   * @param  {Number} y1 the y-coordinate of the first point.
        +   * @param  {Number} x2 the x-coordinate of the second point.
        +   * @param  {Number} y2 the y-coordinate of the second point.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   line(30, 20, 85, 75);
        +   *
        +   *   describe(
        +   *     'A black line on a gray canvas running from top-center to bottom-right.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the line.
        +   *   stroke('magenta');
        +   *   strokeWeight(5);
        +   *
        +   *   line(30, 20, 85, 75);
        +   *
        +   *   describe(
        +   *     'A thick, magenta line on a gray canvas running from top-center to bottom-right.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Top.
        +   *   line(30, 20, 85, 20);
        +   *
        +   *   // Right.
        +   *   stroke(126);
        +   *   line(85, 20, 85, 75);
        +   *
        +   *   // Bottom.
        +   *   stroke(255);
        +   *   line(85, 75, 30, 75);
        +   *
        +   *   describe(
        +   *     'Three lines drawn in grayscale on a gray canvas. They form the top, right, and bottom sides of a square.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   line(-20, -30, 35, 25);
        +   *
        +   *   describe(
        +   *     'A black line on a gray canvas running from top-center to bottom-right.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A black line connecting two spheres. The scene spins slowly.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Draw a line.
        +   *   line(0, 0, 0, 30, 20, -10);
        +   *
        +   *   // Draw the center sphere.
        +   *   sphere(10);
        +   *
        +   *   // Translate to the second point.
        +   *   translate(30, 20, -10);
        +   *
        +   *   // Draw the bottom-right sphere.
        +   *   sphere(10);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   */
        +
        +  /**
        +   * @method line
        +   * @param  {Number} x1
        +   * @param  {Number} y1
        +   * @param  {Number} z1 the z-coordinate of the first point.
        +   * @param  {Number} x2
        +   * @param  {Number} y2
        +   * @param  {Number} z2 the z-coordinate of the second point.
        +   * @chainable
        +   */
        +  fn.line = function(...args) {
        +    // p5._validateParameters('line', args);
        +
        +    if (this._renderer.states.strokeColor) {
        +      this._renderer.line(...args);
        +    }
        +
        +    //accessible Outputs
        +    if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        +      this._accsOutput('line', args);
        +    }
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Draws a single point in space.
        +   *
        +   * A point's default width is one pixel. To color a point, use the
        +   * <a href="#/p5/stroke">stroke()</a> function. To change its width, use the
        +   * <a href="#/p5/strokeWeight">strokeWeight()</a> function. A point
        +   * can't be filled, so the <a href="#/p5/fill">fill()</a> function won't
        +   * affect the point's color.
        +   *
        +   * The version of `point()` with two parameters allows the point's location to
        +   * be set with its x- and y-coordinates, as in `point(10, 20)`.
        +   *
        +   * The version of `point()` with three parameters allows the point to be drawn
        +   * in 3D space with x-, y-, and z-coordinates, as in `point(10, 20, 30)`.
        +   * Doing so requires adding the `WEBGL` argument to
        +   * <a href="#/p5/createCanvas">createCanvas()</a>.
        +   *
        +   * The version of `point()` with one parameter allows the point's location to
        +   * be set with a <a href="#/p5/p5.Vector">p5.Vector</a> object.
        +   *
        +   * @method point
        +   * @param  {Number} x the x-coordinate.
        +   * @param  {Number} y the y-coordinate.
        +   * @param  {Number} [z] the z-coordinate (for WebGL mode).
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Top-left.
        +   *   point(30, 20);
        +   *
        +   *   // Top-right.
        +   *   point(85, 20);
        +   *
        +   *   // Bottom-right.
        +   *   point(85, 75);
        +   *
        +   *   // Bottom-left.
        +   *   point(30, 75);
        +   *
        +   *   describe(
        +   *     'Four small, black points drawn on a gray canvas. The points form the corners of a square.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Top-left.
        +   *   point(30, 20);
        +   *
        +   *   // Top-right.
        +   *   point(70, 20);
        +   *
        +   *   // Style the next points.
        +   *   stroke('purple');
        +   *   strokeWeight(10);
        +   *
        +   *   // Bottom-right.
        +   *   point(70, 80);
        +   *
        +   *   // Bottom-left.
        +   *   point(30, 80);
        +   *
        +   *   describe(
        +   *     'Four points drawn on a gray canvas. Two are black and two are purple. The points form the corners of a square.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Top-left.
        +   *   let a = createVector(30, 20);
        +   *   point(a);
        +   *
        +   *   // Top-right.
        +   *   let b = createVector(70, 20);
        +   *   point(b);
        +   *
        +   *   // Bottom-right.
        +   *   let c = createVector(70, 80);
        +   *   point(c);
        +   *
        +   *   // Bottom-left.
        +   *   let d = createVector(30, 80);
        +   *   point(d);
        +   *
        +   *   describe(
        +   *     'Four small, black points drawn on a gray canvas. The points form the corners of a square.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('Two purple points drawn on a gray canvas.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the points.
        +   *   stroke('purple');
        +   *   strokeWeight(10);
        +   *
        +   *   // Top-left.
        +   *   point(-20, -30);
        +   *
        +   *   // Bottom-right.
        +   *   point(20, 30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('Two purple points drawn on a gray canvas. The scene spins slowly.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Style the points.
        +   *   stroke('purple');
        +   *   strokeWeight(10);
        +   *
        +   *   // Top-left.
        +   *   point(-20, -30, 0);
        +   *
        +   *   // Bottom-right.
        +   *   point(20, 30, -50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method point
        +   * @param {p5.Vector} coordinateVector the coordinate vector.
        +   * @chainable
        +   */
        +  fn.point = function(...args) {
        +    // p5._validateParameters('point', args);
        +
        +    if (this._renderer.states.strokeColor) {
        +      if (args.length === 1 && args[0] instanceof p5.Vector) {
        +        this._renderer.point.call(
        +          this._renderer,
        +          args[0].x,
        +          args[0].y,
        +          args[0].z
        +        );
        +      } else {
        +        this._renderer.point(...args);
        +        //accessible Outputs
        +        if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        +          this._accsOutput('point', args);
        +        }
        +      }
        +    }
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Draws a quadrilateral (four-sided shape).
        +   *
        +   * Quadrilaterals include rectangles, squares, rhombuses, and trapezoids. The
        +   * first pair of parameters `(x1, y1)` sets the quad's first point. The next
        +   * three pairs of parameters set the coordinates for its next three points
        +   * `(x2, y2)`, `(x3, y3)`, and `(x4, y4)`. Points should be added in either
        +   * clockwise or counter-clockwise order.
        +   *
        +   * The version of `quad()` with twelve parameters allows the quad to be drawn
        +   * in 3D space. Doing so requires adding the `WEBGL` argument to
        +   * <a href="#/p5/createCanvas">createCanvas()</a>.
        +   *
        +   * The thirteenth and fourteenth parameters are optional. In WebGL mode, they
        +   * set the number of segments used to draw the quadrilateral in the x- and
        +   * y-directions. They're both 2 by default.
        +   *
        +   * @method quad
        +   * @param {Number} x1 the x-coordinate of the first point.
        +   * @param {Number} y1 the y-coordinate of the first point.
        +   * @param {Number} x2 the x-coordinate of the second point.
        +   * @param {Number} y2 the y-coordinate of the second point.
        +   * @param {Number} x3 the x-coordinate of the third point.
        +   * @param {Number} y3 the y-coordinate of the third point.
        +   * @param {Number} x4 the x-coordinate of the fourth point.
        +   * @param {Number} y4 the y-coordinate of the fourth point.
        +   * @param {Integer} [detailX] number of segments in the x-direction.
        +   * @param {Integer} [detailY] number of segments in the y-direction.
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   quad(20, 20, 80, 20, 80, 80, 20, 80);
        +   *
        +   *   describe('A white square with a black outline drawn on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   quad(20, 30, 80, 30, 80, 70, 20, 70);
        +   *
        +   *   describe('A white rectangle with a black outline drawn on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   quad(50, 62, 86, 50, 50, 38, 14, 50);
        +   *
        +   *   describe('A white rhombus with a black outline drawn on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   quad(20, 50, 80, 30, 80, 70, 20, 70);
        +   *
        +   *   describe('A white trapezoid with a black outline drawn on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   quad(-30, -30, 30, -30, 30, 30, -30, 30);
        +   *
        +   *   describe('A white square with a black outline drawn on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A wavy white surface spins around on gray canvas.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Draw the quad.
        +   *   quad(-30, -30, 0, 30, -30, 0, 30, 30, 20, -30, 30, -20);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method quad
        +   * @param {Number} x1
        +   * @param {Number} y1
        +   * @param {Number} z1 the z-coordinate of the first point.
        +   * @param {Number} x2
        +   * @param {Number} y2
        +   * @param {Number} z2 the z-coordinate of the second point.
        +   * @param {Number} x3
        +   * @param {Number} y3
        +   * @param {Number} z3 the z-coordinate of the third point.
        +   * @param {Number} x4
        +   * @param {Number} y4
        +   * @param {Number} z4 the z-coordinate of the fourth point.
        +   * @param {Integer} [detailX]
        +   * @param {Integer} [detailY]
        +   * @chainable
        +   */
        +  fn.quad = function(...args) {
        +    // p5._validateParameters('quad', args);
        +
        +    if (this._renderer.states.strokeColor || this._renderer.states.fillColor) {
        +      if (this._renderer.isP3D && args.length < 12) {
        +        // if 3D and we weren't passed 12 args, assume Z is 0
        +        this._renderer.quad.call(
        +          this._renderer,
        +          args[0], args[1], 0,
        +          args[2], args[3], 0,
        +          args[4], args[5], 0,
        +          args[6], args[7], 0,
        +          args[8], args[9]);
        +      } else {
        +        this._renderer.quad(...args);
        +        //accessibile outputs
        +        if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        +          this._accsOutput('quadrilateral', args);
        +        }
        +      }
        +    }
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Draws a rectangle.
        +   *
        +   * A rectangle is a four-sided shape defined by the `x`, `y`, `w`, and `h`
        +   * parameters. `x` and `y` set the location of its top-left corner. `w` sets
        +   * its width and `h` sets its height. Every angle in the rectangle measures
        +   * 90˚. See <a href="#/p5/rectMode">rectMode()</a> for other ways to define
        +   * rectangles.
        +   *
        +   * The version of `rect()` with five parameters creates a rounded rectangle. The
        +   * fifth parameter sets the radius for all four corners.
        +   *
        +   * The version of `rect()` with eight parameters also creates a rounded
        +   * rectangle. Each of the last four parameters set the radius of a corner. The
        +   * radii start with the top-left corner and move clockwise around the
        +   * rectangle. If any of these parameters are omitted, they are set to the
        +   * value of the last radius that was set.
        +   *
        +   * @method rect
        +   * @param  {Number} x  x-coordinate of the rectangle.
        +   * @param  {Number} y  y-coordinate of the rectangle.
        +   * @param  {Number} w  width of the rectangle.
        +   * @param  {Number} [h]  height of the rectangle.
        +   * @param  {Number} [tl] optional radius of top-left corner.
        +   * @param  {Number} [tr] optional radius of top-right corner.
        +   * @param  {Number} [br] optional radius of bottom-right corner.
        +   * @param  {Number} [bl] optional radius of bottom-left corner.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   rect(30, 20, 55, 55);
        +   *
        +   *   describe('A white square with a black outline on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   rect(30, 20, 55, 40);
        +   *
        +   *   describe('A white rectangle with a black outline on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Give all corners a radius of 20.
        +   *   rect(30, 20, 55, 50, 20);
        +   *
        +   *   describe('A white rectangle with a black outline and round edges on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Give each corner a unique radius.
        +   *   rect(30, 20, 55, 50, 20, 15, 10, 5);
        +   *
        +   *   describe('A white rectangle with a black outline and round edges of different radii.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   rect(-20, -30, 55, 55);
        +   *
        +   *   describe('A white square with a black outline on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white square spins around on gray canvas.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Draw the rectangle.
        +   *   rect(-20, -30, 55, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method rect
        +   * @param  {Number} x
        +   * @param  {Number} y
        +   * @param  {Number} w
        +   * @param  {Number} h
        +   * @param  {Integer} [detailX] number of segments in the x-direction (for WebGL mode).
        +   * @param  {Integer} [detailY] number of segments in the y-direction (for WebGL mode).
        +   * @chainable
        +   */
        +  fn.rect = function(...args) {
        +    // p5._validateParameters('rect', args);
        +    return this._renderRect(...args);
        +  };
        +
        +  /**
        +   * Draws a square.
        +   *
        +   * A square is a four-sided shape defined by the `x`, `y`, and `s`
        +   * parameters. `x` and `y` set the location of its top-left corner. `s` sets
        +   * its width and height. Every angle in the square measures 90˚ and all its
        +   * sides are the same length. See <a href="#/p5/rectMode">rectMode()</a> for
        +   * other ways to define squares.
        +   *
        +   * The version of `square()` with four parameters creates a rounded square.
        +   * The fourth parameter sets the radius for all four corners.
        +   *
        +   * The version of `square()` with seven parameters also creates a rounded
        +   * square. Each of the last four parameters set the radius of a corner. The
        +   * radii start with the top-left corner and move clockwise around the
        +   * square. If any of these parameters are omitted, they are set to the
        +   * value of the last radius that was set.
        +   *
        +   * @method square
        +   * @param  {Number} x  x-coordinate of the square.
        +   * @param  {Number} y  y-coordinate of the square.
        +   * @param  {Number} s  side size of the square.
        +   * @param  {Number} [tl] optional radius of top-left corner.
        +   * @param  {Number} [tr] optional radius of top-right corner.
        +   * @param  {Number} [br] optional radius of bottom-right corner.
        +   * @param  {Number} [bl] optional radius of bottom-left corner.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   square(30, 20, 55);
        +   *
        +   *   describe('A white square with a black outline in on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Give all corners a radius of 20.
        +   *   square(30, 20, 55, 20);
        +   *
        +   *   describe(
        +   *     'A white square with a black outline and round edges on a gray canvas.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Give each corner a unique radius.
        +   *   square(30, 20, 55, 20, 15, 10, 5);
        +   *
        +   *   describe('A white square with a black outline and round edges of different radii.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   square(-20, -30, 55);
        +   *
        +   *   describe('A white square with a black outline in on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white square spins around on gray canvas.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Draw the square.
        +   *   square(-20, -30, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.square = function(x, y, s, tl, tr, br, bl) {
        +    // p5._validateParameters('square', arguments);
        +    // duplicate width for height in case of square
        +    return this._renderRect.call(this, x, y, s, s, tl, tr, br, bl);
        +  };
        +
        +  // internal method to have renderer draw a rectangle
        +  fn._renderRect = function() {
        +    if (this._renderer.states.strokeColor || this._renderer.states.fillColor) {
        +      // duplicate width for height in case only 3 arguments is provided
        +      if (arguments.length === 3) {
        +        arguments[3] = arguments[2];
        +      }
        +      const vals = canvas.modeAdjust(
        +        arguments[0],
        +        arguments[1],
        +        arguments[2],
        +        arguments[3],
        +        this._renderer.states.rectMode
        +      );
        +
        +      // For the default rectMode (CORNER), restore a possible negative width/height
        +      // removed by modeAdjust(). This results in flipped/mirrored rendering,
        +      // which is especially noticable when using WEGBL rendering and texture().
        +      // Note that this behavior only applies to rect(), NOT to ellipse() and arc().
        +      if (this._renderer.states.rectMode === constants.CORNER) {
        +        vals.w = arguments[2];
        +        vals.h = arguments[3];
        +      }
        +
        +      const args = [vals.x, vals.y, vals.w, vals.h];
        +      // append the additional arguments (either cornder radii, or
        +      // segment details) to the argument list
        +      for (let i = 4; i < arguments.length; i++) {
        +        args[i] = arguments[i];
        +      }
        +      this._renderer.rect(args);
        +
        +      //accessible outputs
        +      if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        +        this._accsOutput('rectangle', [vals.x, vals.y, vals.w, vals.h]);
        +      }
        +    }
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Draws a triangle.
        +   *
        +   * A triangle is a three-sided shape defined by three points. The
        +   * first two parameters specify the triangle's first point `(x1, y1)`. The
        +   * middle two parameters specify its second point `(x2, y2)`. And the last two
        +   * parameters specify its third point `(x3, y3)`.
        +   *
        +   * @method triangle
        +   * @param  {Number} x1 x-coordinate of the first point.
        +   * @param  {Number} y1 y-coordinate of the first point.
        +   * @param  {Number} x2 x-coordinate of the second point.
        +   * @param  {Number} y2 y-coordinate of the second point.
        +   * @param  {Number} x3 x-coordinate of the third point.
        +   * @param  {Number} y3 y-coordinate of the third point.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   triangle(30, 75, 58, 20, 86, 75);
        +   *
        +   *   describe('A white triangle with a black outline on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   triangle(-20, 25, 8, -30, 36, 25);
        +   *
        +   *   describe('A white triangle with a black outline on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white triangle spins around on a gray canvas.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Draw the triangle.
        +   *   triangle(-20, 25, 8, -30, 36, 25);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.triangle = function(...args) {
        +    // p5._validateParameters('triangle', args);
        +
        +    if (this._renderer.states.strokeColor || this._renderer.states.fillColor) {
        +      this._renderer.triangle(args);
        +    }
        +
        +    //accessible outputs
        +    if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
        +      this._accsOutput('triangle', args);
        +    }
        +
        +    return this;
        +  };
        +}
        +
        +export default primitives;
        +
        +if(typeof p5 !== 'undefined'){
        +  primitives(p5, p5.prototype);
        +}
        diff --git a/src/shape/attributes.js b/src/shape/attributes.js
        new file mode 100644
        index 0000000000..317d510245
        --- /dev/null
        +++ b/src/shape/attributes.js
        @@ -0,0 +1,607 @@
        +/**
        + * @module Shape
        + * @submodule Attributes
        + * @for p5
        + * @requires core
        + * @requires constants
        + */
        +
        +import * as constants from '../core/constants';
        +
        +function attributes(p5, fn){
        +  /**
        +   * Changes where ellipses, circles, and arcs are drawn.
        +   *
        +   * By default, the first two parameters of
        +   * <a href="#/p5/ellipse">ellipse()</a>, <a href="#/p5/circle">circle()</a>,
        +   * and <a href="#/p5/arc">arc()</a>
        +   * are the x- and y-coordinates of the shape's center. The next parameters set
        +   * the shape's width and height. This is the same as calling
        +   * `ellipseMode(CENTER)`.
        +   *
        +   * `ellipseMode(RADIUS)` also uses the first two parameters to set the x- and
        +   * y-coordinates of the shape's center. The next parameters are half of the
        +   * shapes's width and height. Calling `ellipse(0, 0, 10, 15)` draws a shape
        +   * with a width of 20 and height of 30.
        +   *
        +   * `ellipseMode(CORNER)` uses the first two parameters as the upper-left
        +   * corner of the shape. The next parameters are its width and height.
        +   *
        +   * `ellipseMode(CORNERS)` uses the first two parameters as the location of one
        +   * corner of the ellipse's bounding box. The next parameters are the location
        +   * of the opposite corner.
        +   *
        +   * The argument passed to `ellipseMode()` must be written in ALL CAPS because
        +   * the constants `CENTER`, `RADIUS`, `CORNER`, and `CORNERS` are defined this
        +   * way. JavaScript is a case-sensitive language.
        +   *
        +   * @method ellipseMode
        +   * @param  {(CENTER|RADIUS|CORNER|CORNERS)} mode either CENTER, RADIUS, CORNER, or CORNERS
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // White ellipse.
        +   *   ellipseMode(RADIUS);
        +   *   fill(255);
        +   *   ellipse(50, 50, 30, 30);
        +   *
        +   *   // Gray ellipse.
        +   *   ellipseMode(CENTER);
        +   *   fill(100);
        +   *   ellipse(50, 50, 30, 30);
        +   *
        +   *   describe('A white circle with a gray circle at its center. Both circles have black outlines.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // White ellipse.
        +   *   ellipseMode(CORNER);
        +   *   fill(255);
        +   *   ellipse(25, 25, 50, 50);
        +   *
        +   *   // Gray ellipse.
        +   *   ellipseMode(CORNERS);
        +   *   fill(100);
        +   *   ellipse(25, 25, 50, 50);
        +   *
        +   *   describe('A white circle with a gray circle at its top-left corner. Both circles have black outlines.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.ellipseMode = function(m) {
        +    // p5._validateParameters('ellipseMode', arguments);
        +    if (
        +      m === constants.CORNER ||
        +      m === constants.CORNERS ||
        +      m === constants.RADIUS ||
        +      m === constants.CENTER
        +    ) {
        +      this._renderer.states.ellipseMode = m;
        +    }
        +    return this;
        +  };
        +
        +  /**
        +   * Draws certain features with jagged (aliased) edges.
        +   *
        +   * <a href="#/p5/smooth">smooth()</a> is active by default. In 2D mode,
        +   * `noSmooth()` is helpful for scaling up images without blurring. The
        +   * functions don't affect shapes or fonts.
        +   *
        +   * In WebGL mode, `noSmooth()` causes all shapes to be drawn with jagged
        +   * (aliased) edges. The functions don't affect images or fonts.
        +   *
        +   * @method noSmooth
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let heart;
        +   *
        +   * // Load a pixelated heart image from an image data string.
        +   * function preload() {
        +   *   heart = loadImage('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAAXNSR0IArs4c6QAAAEZJREFUGFd9jcsNACAIQ9tB2MeR3YdBMBBq8CIXPi2vBICIiOwkOedatllqWO6Y8yOWoyuNf1GZwgmf+RRG2YXr+xVFmA8HZ9Mx/KGPMtcAAAAASUVORK5CYII=');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Antialiased hearts.
        +   *   image(heart, 10, 10);
        +   *   image(heart, 20, 10, 16, 16);
        +   *   image(heart, 40, 10, 32, 32);
        +   *
        +   *   // Aliased hearts.
        +   *   noSmooth();
        +   *   image(heart, 10, 60);
        +   *   image(heart, 20, 60, 16, 16);
        +   *   image(heart, 40, 60, 32, 32);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   circle(0, 0, 80);
        +   *
        +   *   describe('A white circle on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Disable smoothing.
        +   *   noSmooth();
        +   *
        +   *   background(200);
        +   *
        +   *   circle(0, 0, 80);
        +   *
        +   *   describe('A pixelated white circle on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noSmooth = function() {
        +    if (!this._renderer.isP3D) {
        +      if ('imageSmoothingEnabled' in this.drawingContext) {
        +        this.drawingContext.imageSmoothingEnabled = false;
        +      }
        +    } else {
        +      this.setAttributes('antialias', false);
        +    }
        +    return this;
        +  };
        +
        +  /**
        +   * Changes where rectangles and squares are drawn.
        +   *
        +   * By default, the first two parameters of
        +   * <a href="#/p5/rect">rect()</a> and <a href="#/p5/square">square()</a>,
        +   * are the x- and y-coordinates of the shape's upper left corner. The next parameters set
        +   * the shape's width and height. This is the same as calling
        +   * `rectMode(CORNER)`.
        +   *
        +   * `rectMode(CORNERS)` also uses the first two parameters as the location of
        +   * one of the corners. The next parameters are the location of the opposite
        +   * corner. This mode only works for <a href="#/p5/rect">rect()</a>.
        +   *
        +   * `rectMode(CENTER)` uses the first two parameters as the x- and
        +   * y-coordinates of the shape's center. The next parameters are its width and
        +   * height.
        +   *
        +   * `rectMode(RADIUS)` also uses the first two parameters as the x- and
        +   * y-coordinates of the shape's center. The next parameters are
        +   * half of the shape's width and height.
        +   *
        +   * The argument passed to `rectMode()` must be written in ALL CAPS because the
        +   * constants `CENTER`, `RADIUS`, `CORNER`, and `CORNERS` are defined this way.
        +   * JavaScript is a case-sensitive language.
        +   *
        +   * @method rectMode
        +   * @param  {(CENTER|RADIUS|CORNER|CORNERS)} mode either CORNER, CORNERS, CENTER, or RADIUS
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   rectMode(CORNER);
        +   *   fill(255);
        +   *   rect(25, 25, 50, 50);
        +   *
        +   *   rectMode(CORNERS);
        +   *   fill(100);
        +   *   rect(25, 25, 50, 50);
        +   *
        +   *   describe('A small gray square drawn at the top-left corner of a white square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   rectMode(RADIUS);
        +   *   fill(255);
        +   *   rect(50, 50, 30, 30);
        +   *
        +   *   rectMode(CENTER);
        +   *   fill(100);
        +   *   rect(50, 50, 30, 30);
        +   *
        +   *   describe('A small gray square drawn at the center of a white square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   rectMode(CORNER);
        +   *   fill(255);
        +   *   square(25, 25, 50);
        +   *
        +   *   describe('A white square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   rectMode(RADIUS);
        +   *   fill(255);
        +   *   square(50, 50, 30);
        +   *
        +   *   rectMode(CENTER);
        +   *   fill(100);
        +   *   square(50, 50, 30);
        +   *
        +   *   describe('A small gray square drawn at the center of a white square.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.rectMode = function(m) {
        +    // p5._validateParameters('rectMode', arguments);
        +    if (
        +      m === constants.CORNER ||
        +      m === constants.CORNERS ||
        +      m === constants.RADIUS ||
        +      m === constants.CENTER
        +    ) {
        +      this._renderer.states.rectMode = m;
        +    }
        +    return this; // return current rectMode ?
        +  };
        +
        +  /**
        +   * Draws certain features with smooth (antialiased) edges.
        +   *
        +   * `smooth()` is active by default. In 2D mode,
        +   * <a href="#/p5/noSmooth">noSmooth()</a> is helpful for scaling up images
        +   * without blurring. The functions don't affect shapes or fonts.
        +   *
        +   * In WebGL mode, <a href="#/p5/noSmooth">noSmooth()</a> causes all shapes to
        +   * be drawn with jagged (aliased) edges. The functions don't affect images or
        +   * fonts.
        +   *
        +   * @method smooth
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let heart;
        +   *
        +   * // Load a pixelated heart image from an image data string.
        +   * function preload() {
        +   *   heart = loadImage('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAAXNSR0IArs4c6QAAAEZJREFUGFd9jcsNACAIQ9tB2MeR3YdBMBBq8CIXPi2vBICIiOwkOedatllqWO6Y8yOWoyuNf1GZwgmf+RRG2YXr+xVFmA8HZ9Mx/KGPMtcAAAAASUVORK5CYII=');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(50);
        +   *
        +   *   // Antialiased hearts.
        +   *   image(heart, 10, 10);
        +   *   image(heart, 20, 10, 16, 16);
        +   *   image(heart, 40, 10, 32, 32);
        +   *
        +   *   // Aliased hearts.
        +   *   noSmooth();
        +   *   image(heart, 10, 60);
        +   *   image(heart, 20, 60, 16, 16);
        +   *   image(heart, 40, 60, 32, 32);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   circle(0, 0, 80);
        +   *
        +   *   describe('A white circle on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Disable smoothing.
        +   *   noSmooth();
        +   *
        +   *   background(200);
        +   *
        +   *   circle(0, 0, 80);
        +   *
        +   *   describe('A pixelated white circle on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.smooth = function() {
        +    if (!this._renderer.isP3D) {
        +      if ('imageSmoothingEnabled' in this.drawingContext) {
        +        this.drawingContext.imageSmoothingEnabled = true;
        +      }
        +    } else {
        +      this.setAttributes('antialias', true);
        +    }
        +    return this;
        +  };
        +
        +  /**
        +   * Sets the style for rendering the ends of lines.
        +   *
        +   * The caps for line endings are either rounded (`ROUND`), squared
        +   * (`SQUARE`), or extended (`PROJECT`). The default cap is `ROUND`.
        +   *
        +   * The argument passed to `strokeCap()` must be written in ALL CAPS because
        +   * the constants `ROUND`, `SQUARE`, and `PROJECT` are defined this way.
        +   * JavaScript is a case-sensitive language.
        +   *
        +   * @method strokeCap
        +   * @param  {(ROUND|SQUARE|PROJECT)} cap either ROUND, SQUARE, or PROJECT
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   strokeWeight(12);
        +   *
        +   *   // Top.
        +   *   strokeCap(ROUND);
        +   *   line(20, 30, 80, 30);
        +   *
        +   *   // Middle.
        +   *   strokeCap(SQUARE);
        +   *   line(20, 50, 80, 50);
        +   *
        +   *   // Bottom.
        +   *   strokeCap(PROJECT);
        +   *   line(20, 70, 80, 70);
        +   *
        +   *   describe(
        +   *     'Three horizontal lines. The top line has rounded ends, the middle line has squared ends, and the bottom line has longer, squared ends.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.strokeCap = function(cap) {
        +    // p5._validateParameters('strokeCap', arguments);
        +    if (
        +      cap === constants.ROUND ||
        +      cap === constants.SQUARE ||
        +      cap === constants.PROJECT
        +    ) {
        +      this._renderer.strokeCap(cap);
        +    }
        +    return this;
        +  };
        +
        +  /**
        +   * Sets the style of the joints that connect line segments.
        +   *
        +   * Joints are either mitered (`MITER`), beveled (`BEVEL`), or rounded
        +   * (`ROUND`). The default joint is `MITER` in 2D mode and `ROUND` in WebGL
        +   * mode.
        +   *
        +   * The argument passed to `strokeJoin()` must be written in ALL CAPS because
        +   * the constants `MITER`, `BEVEL`, and `ROUND` are defined this way.
        +   * JavaScript is a case-sensitive language.
        +   *
        +   * @method strokeJoin
        +   * @param  {(MITER|BEVEL|ROUND)} join either MITER, BEVEL, or ROUND
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the line.
        +   *   noFill();
        +   *   strokeWeight(10);
        +   *   strokeJoin(MITER);
        +   *
        +   *   // Draw the line.
        +   *   beginShape();
        +   *   vertex(35, 20);
        +   *   vertex(65, 50);
        +   *   vertex(35, 80);
        +   *   endShape();
        +   *
        +   *   describe('A right-facing arrowhead shape with a pointed tip in center of canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the line.
        +   *   noFill();
        +   *   strokeWeight(10);
        +   *   strokeJoin(BEVEL);
        +   *
        +   *   // Draw the line.
        +   *   beginShape();
        +   *   vertex(35, 20);
        +   *   vertex(65, 50);
        +   *   vertex(35, 80);
        +   *   endShape();
        +   *
        +   *   describe('A right-facing arrowhead shape with a flat tip in center of canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the line.
        +   *   noFill();
        +   *   strokeWeight(10);
        +   *   strokeJoin(ROUND);
        +   *
        +   *   // Draw the line.
        +   *   beginShape();
        +   *   vertex(35, 20);
        +   *   vertex(65, 50);
        +   *   vertex(35, 80);
        +   *   endShape();
        +   *
        +   *   describe('A right-facing arrowhead shape with a rounded tip in center of canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.strokeJoin = function(join) {
        +    // p5._validateParameters('strokeJoin', arguments);
        +    if (
        +      join === constants.ROUND ||
        +      join === constants.BEVEL ||
        +      join === constants.MITER
        +    ) {
        +      this._renderer.strokeJoin(join);
        +    }
        +    return this;
        +  };
        +
        +  /**
        +   * Sets the width of the stroke used for points, lines, and the outlines of
        +   * shapes.
        +   *
        +   * Note: `strokeWeight()` is affected by transformations, especially calls to
        +   * <a href="#/p5/scale">scale()</a>.
        +   *
        +   * @method strokeWeight
        +   * @param  {Number} weight the weight of the stroke (in pixels).
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Top.
        +   *   line(20, 20, 80, 20);
        +   *
        +   *   // Middle.
        +   *   strokeWeight(4);
        +   *   line(20, 40, 80, 40);
        +   *
        +   *   // Bottom.
        +   *   strokeWeight(10);
        +   *   line(20, 70, 80, 70);
        +   *
        +   *   describe('Three horizontal black lines. The top line is thin, the middle is medium, and the bottom is thick.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Top.
        +   *   line(20, 20, 80, 20);
        +   *
        +   *   // Scale by a factor of 5.
        +   *   scale(5);
        +   *
        +   *   // Bottom. Coordinates are adjusted for scaling.
        +   *   line(4, 8, 16, 8);
        +   *
        +   *   describe('Two horizontal black lines. The top line is thin and the bottom is five times thicker than the top.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.strokeWeight = function(w) {
        +    // p5._validateParameters('strokeWeight', arguments);
        +    this._renderer.strokeWeight(w);
        +    return this;
        +  };
        +}
        +
        +export default attributes;
        +
        +if(typeof p5 !== 'undefined'){
        +  attributes(p5, p5.prototype);
        +}
        diff --git a/src/shape/curves.js b/src/shape/curves.js
        new file mode 100644
        index 0000000000..370011152e
        --- /dev/null
        +++ b/src/shape/curves.js
        @@ -0,0 +1,1014 @@
        +/**
        + * @module Shape
        + * @submodule Curves
        + * @for p5
        + * @requires core
        + */
        +
        +function curves(p5, fn){
        +  /**
        +   * Draws a Bézier curve.
        +   *
        +   * Bézier curves can form shapes and curves that slope gently. They're defined
        +   * by two anchor points and two control points. Bézier curves provide more
        +   * control than the spline curves created with the
        +   * <a href="#/p5/curve">curve()</a> function.
        +   *
        +   * The first two parameters, `x1` and `y1`, set the first anchor point. The
        +   * first anchor point is where the curve starts.
        +   *
        +   * The next four parameters, `x2`, `y2`, `x3`, and `y3`, set the two control
        +   * points. The control points "pull" the curve towards them.
        +   *
        +   * The seventh and eighth parameters, `x4` and `y4`, set the last anchor
        +   * point. The last anchor point is where the curve ends.
        +   *
        +   * Bézier curves can also be drawn in 3D using WebGL mode. The 3D version of
        +   * `bezier()` has twelve arguments because each point has x-, y-,
        +   * and z-coordinates.
        +   *
        +   * @method bezier
        +   * @param  {Number} x1 x-coordinate of the first anchor point.
        +   * @param  {Number} y1 y-coordinate of the first anchor point.
        +   * @param  {Number} x2 x-coordinate of the first control point.
        +   * @param  {Number} y2 y-coordinate of the first control point.
        +   * @param  {Number} x3 x-coordinate of the second control point.
        +   * @param  {Number} y3 y-coordinate of the second control point.
        +   * @param  {Number} x4 x-coordinate of the second anchor point.
        +   * @param  {Number} y4 y-coordinate of the second anchor point.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   stroke(0);
        +   *   strokeWeight(5);
        +   *   point(85, 20);
        +   *   point(15, 80);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(10, 10);
        +   *   point(90, 90);
        +   *
        +   *   // Draw a black bezier curve.
        +   *   noFill();
        +   *   stroke(0);
        +   *   strokeWeight(1);
        +   *   bezier(85, 20, 10, 10, 90, 90, 15, 80);
        +   *
        +   *   // Draw red lines from the anchor points to the control points.
        +   *   stroke(255, 0, 0);
        +   *   line(85, 20, 10, 10);
        +   *   line(15, 80, 90, 90);
        +   *
        +   *   describe(
        +   *     'A gray square with three curves. A black s-curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click the mouse near the red dot in the top-left corner
        +   * // and drag to change the curve's shape.
        +   *
        +   * let x2 = 10;
        +   * let y2 = 10;
        +   * let isChanging = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with three curves. A black s-curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   stroke(0);
        +   *   strokeWeight(5);
        +   *   point(85, 20);
        +   *   point(15, 80);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(x2, y2);
        +   *   point(90, 90);
        +   *
        +   *   // Draw a black bezier curve.
        +   *   noFill();
        +   *   stroke(0);
        +   *   strokeWeight(1);
        +   *   bezier(85, 20, x2, y2, 90, 90, 15, 80);
        +   *
        +   *   // Draw red lines from the anchor points to the control points.
        +   *   stroke(255, 0, 0);
        +   *   line(85, 20, x2, y2);
        +   *   line(15, 80, 90, 90);
        +   * }
        +   *
        +   * // Start changing the first control point if the user clicks near it.
        +   * function mousePressed() {
        +   *   if (dist(mouseX, mouseY, x2, y2) < 20) {
        +   *     isChanging = true;
        +   *   }
        +   * }
        +   *
        +   * // Stop changing the first control point when the user releases the mouse.
        +   * function mouseReleased() {
        +   *   isChanging = false;
        +   * }
        +   *
        +   * // Update the first control point while the user drags the mouse.
        +   * function mouseDragged() {
        +   *   if (isChanging === true) {
        +   *     x2 = mouseX;
        +   *     y2 = mouseY;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background('skyblue');
        +   *
        +   *   // Draw the red balloon.
        +   *   fill('red');
        +   *   bezier(50, 60, 5, 15, 95, 15, 50, 60);
        +   *
        +   *   // Draw the balloon string.
        +   *   line(50, 60, 50, 80);
        +   *
        +   *   describe('A red balloon in a blue sky.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A red balloon in a blue sky. The balloon rotates slowly, revealing that it is flat.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background('skyblue');
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Draw the red balloon.
        +   *   fill('red');
        +   *   bezier(0, 0, 0, -45, -45, 0, 45, -45, 0, 0, 0, 0);
        +   *
        +   *   // Draw the balloon string.
        +   *   line(0, 0, 0, 0, 20, 0);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method bezier
        +   * @param  {Number} x1
        +   * @param  {Number} y1
        +   * @param  {Number} z1 z-coordinate of the first anchor point.
        +   * @param  {Number} x2
        +   * @param  {Number} y2
        +   * @param  {Number} z2 z-coordinate of the first control point.
        +   * @param  {Number} x3
        +   * @param  {Number} y3
        +   * @param  {Number} z3 z-coordinate of the second control point.
        +   * @param  {Number} x4
        +   * @param  {Number} y4
        +   * @param  {Number} z4 z-coordinate of the second anchor point.
        +   * @chainable
        +   */
        +  fn.bezier = function(...args) {
        +    // p5._validateParameters('bezier', args);
        +
        +    // if the current stroke and fill settings wouldn't result in something
        +    // visible, exit immediately
        +    if (!this._renderer.states.strokeColor && !this._renderer.states.fillColor) {
        +      return this;
        +    }
        +
        +    this._renderer.bezier(...args);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Sets the number of segments used to draw Bézier curves in WebGL mode.
        +   *
        +   * In WebGL mode, smooth shapes are drawn using many flat segments. Adding
        +   * more flat segments makes shapes appear smoother.
        +   *
        +   * The parameter, `detail`, is the number of segments to use while drawing a
        +   * Bézier curve. For example, calling `bezierDetail(5)` will use 5 segments to
        +   * draw curves with the <a href="#/p5/bezier">bezier()</a> function. By
        +   * default,`detail` is 20.
        +   *
        +   * Note: `bezierDetail()` has no effect in 2D mode.
        +   *
        +   * @method bezierDetail
        +   * @param {Number} detail number of segments to use. Defaults to 20.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Draw the original curve.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   stroke(0);
        +   *   strokeWeight(5);
        +   *   point(85, 20);
        +   *   point(15, 80);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(10, 10);
        +   *   point(90, 90);
        +   *
        +   *   // Draw a black bezier curve.
        +   *   noFill();
        +   *   stroke(0);
        +   *   strokeWeight(1);
        +   *   bezier(85, 20, 10, 10, 90, 90, 15, 80);
        +   *
        +   *   // Draw red lines from the anchor points to the control points.
        +   *   stroke(255, 0, 0);
        +   *   line(85, 20, 10, 10);
        +   *   line(15, 80, 90, 90);
        +   *
        +   *   describe(
        +   *     'A gray square with three curves. A black s-curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Draw the curve with less detail.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the curveDetail() to 5.
        +   *   bezierDetail(5);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   stroke(0);
        +   *   strokeWeight(5);
        +   *   point(35, -30, 0);
        +   *   point(-35, 30, 0);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(-40, -40, 0);
        +   *   point(40, 40, 0);
        +   *
        +   *   // Draw a black bezier curve.
        +   *   noFill();
        +   *   stroke(0);
        +   *   strokeWeight(1);
        +   *   bezier(35, -30, 0, -40, -40, 0, 40, 40, 0, -35, 30, 0);
        +   *
        +   *   // Draw red lines from the anchor points to the control points.
        +   *   stroke(255, 0, 0);
        +   *   line(35, -30, -40, -40);
        +   *   line(-35, 30, 40, 40);
        +   *
        +   *   describe(
        +   *     'A gray square with three curves. A black s-curve is drawn with jagged segments. Two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.bezierDetail = function(d) {
        +    // p5._validateParameters('bezierDetail', arguments);
        +    this._bezierDetail = d;
        +    return this;
        +  };
        +
        +  /**
        +   * Calculates coordinates along a Bézier curve using interpolation.
        +   *
        +   * `bezierPoint()` calculates coordinates along a Bézier curve using the
        +   * anchor and control points. It expects points in the same order as the
        +   * <a href="#/p5/bezier">bezier()</a> function. `bezierPoint()` works one axis
        +   * at a time. Passing the anchor and control points' x-coordinates will
        +   * calculate the x-coordinate of a point on the curve. Passing the anchor and
        +   * control points' y-coordinates will calculate the y-coordinate of a point on
        +   * the curve.
        +   *
        +   * The first parameter, `a`, is the coordinate of the first anchor point.
        +   *
        +   * The second and third parameters, `b` and `c`, are the coordinates of the
        +   * control points.
        +   *
        +   * The fourth parameter, `d`, is the coordinate of the last anchor point.
        +   *
        +   * The fifth parameter, `t`, is the amount to interpolate along the curve. 0
        +   * is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
        +   * between them.
        +   *
        +   * @method bezierPoint
        +   * @param {Number} a coordinate of first control point.
        +   * @param {Number} b coordinate of first anchor point.
        +   * @param {Number} c coordinate of second anchor point.
        +   * @param {Number} d coordinate of second control point.
        +   * @param {Number} t amount to interpolate between 0 and 1.
        +   * @return {Number} coordinate of the point on the curve.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the coordinates for the curve's anchor and control points.
        +   *   let x1 = 85;
        +   *   let x2 = 10;
        +   *   let x3 = 90;
        +   *   let x4 = 15;
        +   *   let y1 = 20;
        +   *   let y2 = 10;
        +   *   let y3 = 90;
        +   *   let y4 = 80;
        +   *
        +   *   // Style the curve.
        +   *   noFill();
        +   *
        +   *   // Draw the curve.
        +   *   bezier(x1, y1, x2, y2, x3, y3, x4, y4);
        +   *
        +   *   // Draw circles along the curve's path.
        +   *   fill(255);
        +   *
        +   *   // Top-right.
        +   *   let x = bezierPoint(x1, x2, x3, x4, 0);
        +   *   let y = bezierPoint(y1, y2, y3, y4, 0);
        +   *   circle(x, y, 5);
        +   *
        +   *   // Center.
        +   *   x = bezierPoint(x1, x2, x3, x4, 0.5);
        +   *   y = bezierPoint(y1, y2, y3, y4, 0.5);
        +   *   circle(x, y, 5);
        +   *
        +   *   // Bottom-left.
        +   *   x = bezierPoint(x1, x2, x3, x4, 1);
        +   *   y = bezierPoint(y1, y2, y3, y4, 1);
        +   *   circle(x, y, 5);
        +   *
        +   *   describe('A black s-curve on a gray square. The endpoints and center of the curve are marked with white circles.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A black s-curve on a gray square. A white circle moves back and forth along the curve.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Set the coordinates for the curve's anchor and control points.
        +   *   let x1 = 85;
        +   *   let x2 = 10;
        +   *   let x3 = 90;
        +   *   let x4 = 15;
        +   *   let y1 = 20;
        +   *   let y2 = 10;
        +   *   let y3 = 90;
        +   *   let y4 = 80;
        +   *
        +   *   // Draw the curve.
        +   *   noFill();
        +   *   bezier(x1, y1, x2, y2, x3, y3, x4, y4);
        +   *
        +   *   // Calculate the circle's coordinates.
        +   *   let t = 0.5 * sin(frameCount * 0.01) + 0.5;
        +   *   let x = bezierPoint(x1, x2, x3, x4, t);
        +   *   let y = bezierPoint(y1, y2, y3, y4, t);
        +   *
        +   *   // Draw the circle.
        +   *   fill(255);
        +   *   circle(x, y, 5);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.bezierPoint = function(a, b, c, d, t) {
        +    // p5._validateParameters('bezierPoint', arguments);
        +
        +    const adjustedT = 1 - t;
        +    return (
        +      Math.pow(adjustedT, 3) * a +
        +      3 * Math.pow(adjustedT, 2) * t * b +
        +      3 * adjustedT * Math.pow(t, 2) * c +
        +      Math.pow(t, 3) * d
        +    );
        +  };
        +
        +  /**
        +   * Calculates coordinates along a line that's tangent to a Bézier curve.
        +   *
        +   * Tangent lines skim the surface of a curve. A tangent line's slope equals
        +   * the curve's slope at the point where it intersects.
        +   *
        +   * `bezierTangent()` calculates coordinates along a tangent line using the
        +   * Bézier curve's anchor and control points. It expects points in the same
        +   * order as the <a href="#/p5/bezier">bezier()</a> function. `bezierTangent()`
        +   * works one axis at a time. Passing the anchor and control points'
        +   * x-coordinates will calculate the x-coordinate of a point on the tangent
        +   * line. Passing the anchor and control points' y-coordinates will calculate
        +   * the y-coordinate of a point on the tangent line.
        +   *
        +   * The first parameter, `a`, is the coordinate of the first anchor point.
        +   *
        +   * The second and third parameters, `b` and `c`, are the coordinates of the
        +   * control points.
        +   *
        +   * The fourth parameter, `d`, is the coordinate of the last anchor point.
        +   *
        +   * The fifth parameter, `t`, is the amount to interpolate along the curve. 0
        +   * is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
        +   * between them.
        +   *
        +   * @method bezierTangent
        +   * @param {Number} a coordinate of first anchor point.
        +   * @param {Number} b coordinate of first control point.
        +   * @param {Number} c coordinate of second control point.
        +   * @param {Number} d coordinate of second anchor point.
        +   * @param {Number} t amount to interpolate between 0 and 1.
        +   * @return {Number} coordinate of a point on the tangent line.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the coordinates for the curve's anchor and control points.
        +   *   let x1 = 85;
        +   *   let x2 = 10;
        +   *   let x3 = 90;
        +   *   let x4 = 15;
        +   *   let y1 = 20;
        +   *   let y2 = 10;
        +   *   let y3 = 90;
        +   *   let y4 = 80;
        +   *
        +   *   // Style the curve.
        +   *   noFill();
        +   *
        +   *   // Draw the curve.
        +   *   bezier(x1, y1, x2, y2, x3, y3, x4, y4);
        +   *
        +   *   // Draw tangents along the curve's path.
        +   *   fill(255);
        +   *
        +   *   // Top-right circle.
        +   *   stroke(0);
        +   *   let x = bezierPoint(x1, x2, x3, x4, 0);
        +   *   let y = bezierPoint(y1, y2, y3, y4, 0);
        +   *   circle(x, y, 5);
        +   *
        +   *   // Top-right tangent line.
        +   *   // Scale the tangent point to draw a shorter line.
        +   *   stroke(255, 0, 0);
        +   *   let tx = 0.1 * bezierTangent(x1, x2, x3, x4, 0);
        +   *   let ty = 0.1 * bezierTangent(y1, y2, y3, y4, 0);
        +   *   line(x + tx, y + ty, x - tx, y - ty);
        +   *
        +   *   // Center circle.
        +   *   stroke(0);
        +   *   x = bezierPoint(x1, x2, x3, x4, 0.5);
        +   *   y = bezierPoint(y1, y2, y3, y4, 0.5);
        +   *   circle(x, y, 5);
        +   *
        +   *   // Center tangent line.
        +   *   // Scale the tangent point to draw a shorter line.
        +   *   stroke(255, 0, 0);
        +   *   tx = 0.1 * bezierTangent(x1, x2, x3, x4, 0.5);
        +   *   ty = 0.1 * bezierTangent(y1, y2, y3, y4, 0.5);
        +   *   line(x + tx, y + ty, x - tx, y - ty);
        +   *
        +   *   // Bottom-left circle.
        +   *   stroke(0);
        +   *   x = bezierPoint(x1, x2, x3, x4, 1);
        +   *   y = bezierPoint(y1, y2, y3, y4, 1);
        +   *   circle(x, y, 5);
        +   *
        +   *   // Bottom-left tangent.
        +   *   // Scale the tangent point to draw a shorter line.
        +   *   stroke(255, 0, 0);
        +   *   tx = 0.1 * bezierTangent(x1, x2, x3, x4, 1);
        +   *   ty = 0.1 * bezierTangent(y1, y2, y3, y4, 1);
        +   *   line(x + tx, y + ty, x - tx, y - ty);
        +   *
        +   *   describe(
        +   *     'A black s-curve on a gray square. The endpoints and center of the curve are marked with white circles. Red tangent lines extend from the white circles.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.bezierTangent = function(a, b, c, d, t) {
        +    // p5._validateParameters('bezierTangent', arguments);
        +
        +    const adjustedT = 1 - t;
        +    return (
        +      3 * d * Math.pow(t, 2) -
        +      3 * c * Math.pow(t, 2) +
        +      6 * c * adjustedT * t -
        +      6 * b * adjustedT * t +
        +      3 * b * Math.pow(adjustedT, 2) -
        +      3 * a * Math.pow(adjustedT, 2)
        +    );
        +  };
        +
        +  /**
        +   * Draws a curve using a Catmull-Rom spline.
        +   *
        +   * Spline curves can form shapes and curves that slope gently. They’re like
        +   * cables that are attached to a set of points. Splines are defined by two
        +   * anchor points and two control points.
        +   *
        +   * The first two parameters, `x1` and `y1`, set the first control point. This
        +   * point isn’t drawn and can be thought of as the curve’s starting point.
        +   *
        +   * The next four parameters, `x2`, `y2`, `x3`, and `y3`, set the two anchor
        +   * points. The anchor points are the start and end points of the curve’s
        +   * visible segment.
        +   *
        +   * The seventh and eighth parameters, `x4` and `y4`, set the last control
        +   * point. This point isn’t drawn and can be thought of as the curve’s ending
        +   * point.
        +   *
        +   * Spline curves can also be drawn in 3D using WebGL mode. The 3D version of
        +   * `curve()` has twelve arguments because each point has x-, y-, and
        +   * z-coordinates.
        +   *
        +   * @method curve
        +   * @param  {Number} x1 x-coordinate of the first control point.
        +   * @param  {Number} y1 y-coordinate of the first control point.
        +   * @param  {Number} x2 x-coordinate of the first anchor point.
        +   * @param  {Number} y2 y-coordinate of the first anchor point.
        +   * @param  {Number} x3 x-coordinate of the second anchor point.
        +   * @param  {Number} y3 y-coordinate of the second anchor point.
        +   * @param  {Number} x4 x-coordinate of the second control point.
        +   * @param  {Number} y4 y-coordinate of the second control point.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw a black spline curve.
        +   *   noFill();
        +   *   strokeWeight(1);
        +   *   stroke(0);
        +   *   curve(5, 26, 73, 24, 73, 61, 15, 65);
        +   *
        +   *   // Draw red spline curves from the anchor points to the control points.
        +   *   stroke(255, 0, 0);
        +   *   curve(5, 26, 5, 26, 73, 24, 73, 61);
        +   *   curve(73, 24, 73, 61, 15, 65, 15, 65);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   strokeWeight(5);
        +   *   stroke(0);
        +   *   point(73, 24);
        +   *   point(73, 61);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(5, 26);
        +   *   point(15, 65);
        +   *
        +   *   describe(
        +   *     'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let x1 = 5;
        +   * let y1 = 26;
        +   * let isChanging = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a black spline curve.
        +   *   noFill();
        +   *   strokeWeight(1);
        +   *   stroke(0);
        +   *   curve(x1, y1, 73, 24, 73, 61, 15, 65);
        +   *
        +   *   // Draw red spline curves from the anchor points to the control points.
        +   *   stroke(255, 0, 0);
        +   *   curve(x1, y1, x1, y1, 73, 24, 73, 61);
        +   *   curve(73, 24, 73, 61, 15, 65, 15, 65);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   strokeWeight(5);
        +   *   stroke(0);
        +   *   point(73, 24);
        +   *   point(73, 61);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(x1, y1);
        +   *   point(15, 65);
        +   * }
        +   *
        +   * // Start changing the first control point if the user clicks near it.
        +   * function mousePressed() {
        +   *   if (dist(mouseX, mouseY, x1, y1) < 20) {
        +   *     isChanging = true;
        +   *   }
        +   * }
        +   *
        +   * // Stop changing the first control point when the user releases the mouse.
        +   * function mouseReleased() {
        +   *   isChanging = false;
        +   * }
        +   *
        +   * // Update the first control point while the user drags the mouse.
        +   * function mouseDragged() {
        +   *   if (isChanging === true) {
        +   *     x1 = mouseX;
        +   *     y1 = mouseY;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background('skyblue');
        +   *
        +   *   // Draw the red balloon.
        +   *   fill('red');
        +   *   curve(-150, 275, 50, 60, 50, 60, 250, 275);
        +   *
        +   *   // Draw the balloon string.
        +   *   line(50, 60, 50, 80);
        +   *
        +   *   describe('A red balloon in a blue sky.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A red balloon in a blue sky.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background('skyblue');
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Draw the red balloon.
        +   *   fill('red');
        +   *   curve(-200, 225, 0, 0, 10, 0, 0, 10, 0, 200, 225, 0);
        +   *
        +   *   // Draw the balloon string.
        +   *   line(0, 10, 0, 0, 30, 0);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method curve
        +   * @param  {Number} x1
        +   * @param  {Number} y1
        +   * @param  {Number} z1 z-coordinate of the first control point.
        +   * @param  {Number} x2
        +   * @param  {Number} y2
        +   * @param  {Number} z2 z-coordinate of the first anchor point.
        +   * @param  {Number} x3
        +   * @param  {Number} y3
        +   * @param  {Number} z3 z-coordinate of the second anchor point.
        +   * @param  {Number} x4
        +   * @param  {Number} y4
        +   * @param  {Number} z4 z-coordinate of the second control point.
        +   * @chainable
        +   */
        +  fn.curve = function(...args) {
        +    // p5._validateParameters('curve', args);
        +
        +    if (this._renderer.states.strokeColor) {
        +      this._renderer.curve(...args);
        +    }
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Calculates coordinates along a spline curve using interpolation.
        +   *
        +   * `curvePoint()` calculates coordinates along a spline curve using the
        +   * anchor and control points. It expects points in the same order as the
        +   * <a href="#/p5/curve">curve()</a> function. `curvePoint()` works one axis
        +   * at a time. Passing the anchor and control points' x-coordinates will
        +   * calculate the x-coordinate of a point on the curve. Passing the anchor and
        +   * control points' y-coordinates will calculate the y-coordinate of a point on
        +   * the curve.
        +   *
        +   * The first parameter, `a`, is the coordinate of the first control point.
        +   *
        +   * The second and third parameters, `b` and `c`, are the coordinates of the
        +   * anchor points.
        +   *
        +   * The fourth parameter, `d`, is the coordinate of the last control point.
        +   *
        +   * The fifth parameter, `t`, is the amount to interpolate along the curve. 0
        +   * is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
        +   * between them.
        +   *
        +   * @method curvePoint
        +   * @param {Number} a coordinate of first anchor point.
        +   * @param {Number} b coordinate of first control point.
        +   * @param {Number} c coordinate of second control point.
        +   * @param {Number} d coordinate of second anchor point.
        +   * @param {Number} t amount to interpolate between 0 and 1.
        +   * @return {Number} coordinate of a point on the curve.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the coordinates for the curve's anchor and control points.
        +   *   let x1 = 5;
        +   *   let y1 = 26;
        +   *   let x2 = 73;
        +   *   let y2 = 24;
        +   *   let x3 = 73;
        +   *   let y3 = 61;
        +   *   let x4 = 15;
        +   *   let y4 = 65;
        +   *
        +   *   // Draw the curve.
        +   *   noFill();
        +   *   curve(x1, y1, x2, y2, x3, y3, x4, y4);
        +   *
        +   *   // Draw circles along the curve's path.
        +   *   fill(255);
        +   *
        +   *   // Top.
        +   *   let x = curvePoint(x1, x2, x3, x4, 0);
        +   *   let y = curvePoint(y1, y2, y3, y4, 0);
        +   *   circle(x, y, 5);
        +   *
        +   *   // Center.
        +   *   x = curvePoint(x1, x2, x3, x4, 0.5);
        +   *   y = curvePoint(y1, y2, y3, y4, 0.5);
        +   *   circle(x, y, 5);
        +   *
        +   *   // Bottom.
        +   *   x = curvePoint(x1, x2, x3, x4, 1);
        +   *   y = curvePoint(y1, y2, y3, y4, 1);
        +   *   circle(x, y, 5);
        +   *
        +   *   describe('A black curve on a gray square. The endpoints and center of the curve are marked with white circles.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A black curve on a gray square. A white circle moves back and forth along the curve.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Set the coordinates for the curve's anchor and control points.
        +   *   let x1 = 5;
        +   *   let y1 = 26;
        +   *   let x2 = 73;
        +   *   let y2 = 24;
        +   *   let x3 = 73;
        +   *   let y3 = 61;
        +   *   let x4 = 15;
        +   *   let y4 = 65;
        +   *
        +   *   // Draw the curve.
        +   *   noFill();
        +   *   curve(x1, y1, x2, y2, x3, y3, x4, y4);
        +   *
        +   *   // Calculate the circle's coordinates.
        +   *   let t = 0.5 * sin(frameCount * 0.01) + 0.5;
        +   *   let x = curvePoint(x1, x2, x3, x4, t);
        +   *   let y = curvePoint(y1, y2, y3, y4, t);
        +   *
        +   *   // Draw the circle.
        +   *   fill(255);
        +   *   circle(x, y, 5);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.curvePoint = function(a, b, c, d, t) {
        +    // p5._validateParameters('curvePoint', arguments);
        +    const s = this._renderer.states.splineProperties.tightness,
        +      t3 = t * t * t,
        +      t2 = t * t,
        +      f1 = (s - 1) / 2 * t3 + (1 - s) * t2 + (s - 1) / 2 * t,
        +      f2 = (s + 3) / 2 * t3 + (-5 - s) / 2 * t2 + 1.0,
        +      f3 = (-3 - s) / 2 * t3 + (s + 2) * t2 + (1 - s) / 2 * t,
        +      f4 = (1 - s) / 2 * t3 + (s - 1) / 2 * t2;
        +    return a * f1 + b * f2 + c * f3 + d * f4;
        +  };
        +
        +  /**
        +   * Calculates coordinates along a line that's tangent to a spline curve.
        +   *
        +   * Tangent lines skim the surface of a curve. A tangent line's slope equals
        +   * the curve's slope at the point where it intersects.
        +   *
        +   * `curveTangent()` calculates coordinates along a tangent line using the
        +   * spline curve's anchor and control points. It expects points in the same
        +   * order as the <a href="#/p5/curve">curve()</a> function. `curveTangent()`
        +   * works one axis at a time. Passing the anchor and control points'
        +   * x-coordinates will calculate the x-coordinate of a point on the tangent
        +   * line. Passing the anchor and control points' y-coordinates will calculate
        +   * the y-coordinate of a point on the tangent line.
        +   *
        +   * The first parameter, `a`, is the coordinate of the first control point.
        +   *
        +   * The second and third parameters, `b` and `c`, are the coordinates of the
        +   * anchor points.
        +   *
        +   * The fourth parameter, `d`, is the coordinate of the last control point.
        +   *
        +   * The fifth parameter, `t`, is the amount to interpolate along the curve. 0
        +   * is the first anchor point, 1 is the second anchor point, and 0.5 is halfway
        +   * between them.
        +   *
        +   * @method curveTangent
        +   * @param {Number} a coordinate of first control point.
        +   * @param {Number} b coordinate of first anchor point.
        +   * @param {Number} c coordinate of second anchor point.
        +   * @param {Number} d coordinate of second control point.
        +   * @param {Number} t amount to interpolate between 0 and 1.
        +   * @return {Number} coordinate of a point on the tangent line.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the coordinates for the curve's anchor and control points.
        +   *   let x1 = 5;
        +   *   let y1 = 26;
        +   *   let x2 = 73;
        +   *   let y2 = 24;
        +   *   let x3 = 73;
        +   *   let y3 = 61;
        +   *   let x4 = 15;
        +   *   let y4 = 65;
        +   *
        +   *   // Draw the curve.
        +   *   noFill();
        +   *   curve(x1, y1, x2, y2, x3, y3, x4, y4);
        +   *
        +   *   // Draw tangents along the curve's path.
        +   *   fill(255);
        +   *
        +   *   // Top circle.
        +   *   stroke(0);
        +   *   let x = curvePoint(x1, x2, x3, x4, 0);
        +   *   let y = curvePoint(y1, y2, y3, y4, 0);
        +   *   circle(x, y, 5);
        +   *
        +   *   // Top tangent line.
        +   *   // Scale the tangent point to draw a shorter line.
        +   *   stroke(255, 0, 0);
        +   *   let tx = 0.2 * curveTangent(x1, x2, x3, x4, 0);
        +   *   let ty = 0.2 * curveTangent(y1, y2, y3, y4, 0);
        +   *   line(x + tx, y + ty, x - tx, y - ty);
        +   *
        +   *   // Center circle.
        +   *   stroke(0);
        +   *   x = curvePoint(x1, x2, x3, x4, 0.5);
        +   *   y = curvePoint(y1, y2, y3, y4, 0.5);
        +   *   circle(x, y, 5);
        +   *
        +   *   // Center tangent line.
        +   *   // Scale the tangent point to draw a shorter line.
        +   *   stroke(255, 0, 0);
        +   *   tx = 0.2 * curveTangent(x1, x2, x3, x4, 0.5);
        +   *   ty = 0.2 * curveTangent(y1, y2, y3, y4, 0.5);
        +   *   line(x + tx, y + ty, x - tx, y - ty);
        +   *
        +   *   // Bottom circle.
        +   *   stroke(0);
        +   *   x = curvePoint(x1, x2, x3, x4, 1);
        +   *   y = curvePoint(y1, y2, y3, y4, 1);
        +   *   circle(x, y, 5);
        +   *
        +   *   // Bottom tangent line.
        +   *   // Scale the tangent point to draw a shorter line.
        +   *   stroke(255, 0, 0);
        +   *   tx = 0.2 * curveTangent(x1, x2, x3, x4, 1);
        +   *   ty = 0.2 * curveTangent(y1, y2, y3, y4, 1);
        +   *   line(x + tx, y + ty, x - tx, y - ty);
        +   *
        +   *   describe(
        +   *     'A black curve on a gray square. A white circle moves back and forth along the curve.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.curveTangent = function(a, b, c, d, t) {
        +    // p5._validateParameters('curveTangent', arguments);
        +
        +    const s = this._renderer.states.splineProperties.tightness,
        +      tt3 = t * t * 3,
        +      t2 = t * 2,
        +      f1 = (s - 1) / 2 * tt3 + (1 - s) * t2 + (s - 1) / 2,
        +      f2 = (s + 3) / 2 * tt3 + (-5 - s) / 2 * t2,
        +      f3 = (-3 - s) / 2 * tt3 + (s + 2) * t2 + (1 - s) / 2,
        +      f4 = (1 - s) / 2 * tt3 + (s - 1) / 2 * t2;
        +    return a * f1 + b * f2 + c * f3 + d * f4;
        +  };
        +}
        +
        +export default curves;
        +
        +if(typeof p5 !== 'undefined'){
        +  curves(p5, p5.prototype);
        +}
        diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js
        new file mode 100644
        index 0000000000..cb53761d19
        --- /dev/null
        +++ b/src/shape/custom_shapes.js
        @@ -0,0 +1,2116 @@
        +/**
        + * @module Shape
        + * @submodule Custom Shapes
        + * @for p5
        + * @requires core
        + * @requires constants
        + */
        +
        +// REMINDER: remove .js extension (currently using it to run file locally)
        +import { Color } from '../color/p5.Color';
        +import { Vector } from '../math/p5.Vector';
        +import * as constants from '../core/constants';
        +
        +// ---- UTILITY FUNCTIONS ----
        +function polylineLength(vertices) {
        +  let length = 0;
        +  for (let i = 1; i < vertices.length; i++) {
        +    length += vertices[i-1].position.dist(vertices[i].position);
        +  }
        +  return length;
        +}
        +
        +// ---- GENERAL BUILDING BLOCKS ----
        +
        +class Vertex {
        +  constructor(properties) {
        +    for (const [key, value] of Object.entries(properties)) {
        +      this[key] = value;
        +    }
        +  }
        +  /*
        +  get array() {
        +    // convert to 1D array
        +    // call `toArray()` if value is an object with a toArray() method
        +    // handle primitive values separately
        +    // maybe handle object literals too, with Object.values()?
        +    // probably don’t need anything else for now?
        +  }
        +  */
        +  // TODO: make sure name of array conversion method is
        +  // consistent with any modifications to the names of corresponding
        +  // properties of p5.Vector and p5.Color
        +}
        +
        +class ShapePrimitive {
        +  vertices;
        +  _shape = null;
        +  _primitivesIndex = null;
        +  _contoursIndex = null;
        +  isClosing = false;
        +
        +  constructor(...vertices) {
        +    if (this.constructor === ShapePrimitive) {
        +      throw new Error('ShapePrimitive is an abstract class: it cannot be instantiated.');
        +    }
        +    if (vertices.length > 0) {
        +      this.vertices = vertices;
        +    }
        +    else {
        +      throw new Error('At least one vertex must be passed to the constructor.');
        +    }
        +  }
        +
        +  get vertexCount() {
        +    return this.vertices.length;
        +  }
        +
        +  get vertexCapacity() {
        +    throw new Error('Getter vertexCapacity must be implemented.');
        +  }
        +
        +  get _firstInterpolatedVertex() {
        +    return this.startVertex();
        +  }
        +
        +  get canOverrideAnchor() {
        +    return false;
        +  }
        +
        +  accept(visitor) {
        +    throw new Error('Method accept() must be implemented.');
        +  }
        +
        +  addToShape(shape) {
        +    /*
        +    TODO:
        +    Refactor?
        +    Test this method once more primitives are implemented.
        +    Test segments separately (Segment adds an extra step to this method).
        +    */
        +    let lastContour = shape.at(-1);
        +
        +    if (lastContour.primitives.length === 0) {
        +      lastContour.primitives.push(this);
        +    } else {
        +      // last primitive in shape
        +      let lastPrimitive = shape.at(-1, -1);
        +      let hasSameType = lastPrimitive instanceof this.constructor;
        +      let spareCapacity = lastPrimitive.vertexCapacity -
        +                          lastPrimitive.vertexCount;
        +
        +      // this primitive
        +      let pushableVertices;
        +      let remainingVertices;
        +
        +      if (hasSameType && spareCapacity > 0) {
        +
        +        pushableVertices = this.vertices.splice(0, spareCapacity);
        +        remainingVertices = this.vertices;
        +        lastPrimitive.vertices.push(...pushableVertices);
        +
        +        if (remainingVertices.length > 0) {
        +          lastContour.primitives.push(this);
        +        }
        +      }
        +      else {
        +        lastContour.primitives.push(this);
        +      }
        +    }
        +
        +    // if primitive itself was added
        +    // (i.e. its individual vertices weren't all added to an existing primitive)
        +    // give it a reference to the shape and store its location within the shape
        +    let addedToShape = this.vertices.length > 0;
        +    if (addedToShape) {
        +      let lastContour = shape.at(-1);
        +      this._primitivesIndex = lastContour.primitives.length - 1;
        +      this._contoursIndex = shape.contours.length - 1;
        +      this._shape = shape;
        +    }
        +
        +    return shape.at(-1, -1);
        +  }
        +
        +  get _nextPrimitive() {
        +    return this._belongsToShape ?
        +      this._shape.at(this._contoursIndex, this._primitivesIndex + 1) :
        +      null;
        +  }
        +
        +  get _belongsToShape() {
        +    return this._shape !== null;
        +  }
        +
        +  handlesClose() {
        +    return false;
        +  }
        +
        +  close(vertex) {
        +    throw new Error('Unimplemented!');
        +  }
        +}
        +
        +class Contour {
        +  #kind;
        +  primitives;
        +
        +  constructor(kind = constants.PATH) {
        +    this.#kind = kind;
        +    this.primitives = [];
        +  }
        +
        +  get kind() {
        +    const isEmpty = this.primitives.length === 0;
        +    const isPath = this.#kind === constants.PATH;
        +    return isEmpty && isPath ? constants.EMPTY_PATH : this.#kind;
        +  }
        +
        +  accept(visitor) {
        +    for (const primitive of this.primitives) {
        +      primitive.accept(visitor);
        +    }
        +  }
        +}
        +
        +// ---- PATH PRIMITIVES ----
        +
        +class Anchor extends ShapePrimitive {
        +  #vertexCapacity = 1;
        +
        +  get vertexCapacity() {
        +    return this.#vertexCapacity;
        +  }
        +
        +  accept(visitor) {
        +    visitor.visitAnchor(this);
        +  }
        +
        +  getEndVertex() {
        +    return this.vertices[0];
        +  }
        +}
        +
        +// abstract class
        +class Segment extends ShapePrimitive {
        +  constructor(...vertices) {
        +    super(...vertices);
        +    if (this.constructor === Segment) {
        +      throw new Error('Segment is an abstract class: it cannot be instantiated.');
        +    }
        +  }
        +
        +  // segments in a shape always have a predecessor
        +  // (either an anchor or another segment)
        +  get _previousPrimitive() {
        +    return this._belongsToShape ?
        +      this._shape.at(this._contoursIndex, this._primitivesIndex - 1) :
        +      null;
        +  }
        +
        +  getStartVertex() {
        +    return this._previousPrimitive.getEndVertex();
        +  }
        +
        +  getEndVertex() {
        +    return this.vertices.at(-1);
        +  }
        +}
        +
        +class LineSegment extends Segment {
        +  #vertexCapacity = 1;
        +
        +  get vertexCapacity() {
        +    return this.#vertexCapacity;
        +  }
        +
        +  accept(visitor) {
        +    visitor.visitLineSegment(this);
        +  }
        +}
        +
        +class BezierSegment extends Segment {
        +  #order;
        +  #vertexCapacity;
        +
        +  constructor(order, ...vertices) {
        +    super(...vertices);
        +
        +    // Order m may sometimes be passed as an array [m], since arrays
        +    // may be used elsewhere to store order of
        +    // Bezier curves and surfaces in a common format
        +
        +    let numericalOrder = Array.isArray(order) ? order[0] : order;
        +    this.#order = numericalOrder;
        +    this.#vertexCapacity = numericalOrder;
        +  }
        +
        +  get order() {
        +    return this.#order;
        +  }
        +
        +  get vertexCapacity() {
        +    return this.#vertexCapacity;
        +  }
        +
        +  #_hullLength;
        +  hullLength() {
        +    if (this.#_hullLength === undefined) {
        +      this.#_hullLength = polylineLength([
        +        this.getStartVertex(),
        +        ...this.vertices
        +      ]);
        +    }
        +    return this.#_hullLength;
        +  }
        +
        +  accept(visitor) {
        +    visitor.visitBezierSegment(this);
        +  }
        +}
        +
        +/*
        +To-do: Consider type and end modes -- see #6766
        +may want to use separate classes, but maybe not
        +
        +For now, the implementation overrides
        +super.getEndVertex() in order to preserve current p5
        +endpoint behavior, but we're considering defaulting
        +to interpolated endpoints (a breaking change)
        +*/
        +class SplineSegment extends Segment {
        +  #vertexCapacity = Infinity;
        +  _splineProperties = {
        +    ends: constants.INCLUDE,
        +    tightness: 0
        +  };
        +
        +  get vertexCapacity() {
        +    return this.#vertexCapacity;
        +  }
        +
        +  accept(visitor) {
        +    visitor.visitSplineSegment(this);
        +  }
        +
        +  get _comesAfterSegment() {
        +    return this._previousPrimitive instanceof Segment;
        +  }
        +
        +  get canOverrideAnchor() {
        +    return this._splineProperties.ends === constants.EXCLUDE;
        +  }
        +
        +  // assuming for now that the first interpolated vertex is always
        +  // the second vertex passed to splineVertex()
        +  // if this spline segment doesn't follow another segment,
        +  // the first vertex is in an anchor
        +  get _firstInterpolatedVertex() {
        +    if (this._splineProperties.ends === constants.EXCLUDE) {
        +      return this._comesAfterSegment ?
        +        this.vertices[1] :
        +        this.vertices[0];
        +    } else {
        +      return this.getStartVertex()
        +    }
        +  }
        +
        +  get _chainedToSegment() {
        +    if (this._belongsToShape && this._comesAfterSegment) {
        +      let interpolatedStartPosition = this._firstInterpolatedVertex.position;
        +      let predecessorEndPosition = this.getStartVertex().position;
        +      return predecessorEndPosition.equals(interpolatedStartPosition);
        +    }
        +    else {
        +      return false;
        +    }
        +  }
        +
        +  // extend addToShape() with a warning in case second vertex
        +  // doesn't line up with end of last segment
        +  addToShape(shape) {
        +    const added = super.addToShape(shape);
        +    this._splineProperties.ends = shape._splineProperties.ends;
        +    this._splineProperties.tightness = shape._splineProperties.tightness;
        +
        +    if (this._splineProperties.ends !== constants.EXCLUDE) return added;
        +
        +    let verticesPushed = !this._belongsToShape;
        +    let lastPrimitive = shape.at(-1, -1);
        +
        +    let message = (array1, array2) =>
        +      `Spline does not start where previous path segment ends:
        +      second spline vertex at (${array1})
        +      expected to be at (${array2}).`;
        +
        +    if (verticesPushed &&
        +      // Only check once the first interpolated vertex has been added
        +      lastPrimitive.vertices.length === 2 &&
        +      lastPrimitive._comesAfterSegment &&
        +      !lastPrimitive._chainedToSegment
        +    ) {
        +      let interpolatedStart = lastPrimitive._firstInterpolatedVertex.position;
        +      let predecessorEnd = lastPrimitive.getStartVertex().position;
        +
        +      console.warn(
        +        message(interpolatedStart.array(), predecessorEnd.array())
        +      );
        +    }
        +
        +    // Note: Could add a warning in an else-if case for when this spline segment
        +    // is added directly to the shape instead of pushing its vertices to
        +    // an existing spline segment. However, if we assume addToShape() is called by
        +    // splineVertex(), it'd add a new spline segment with only one vertex in that case,
        +    // and the check wouldn't be needed yet.
        +
        +    // TODO: Consider case where positions match but other vertex properties don't.
        +    return added;
        +  }
        +
        +  // override method on base class
        +  getEndVertex() {
        +    if (this._splineProperties.ends === constants.INCLUDE) {
        +      return super.getEndVertex();
        +    } else if (this._splineProperties.ends === constants.EXCLUDE) {
        +      return this.vertices.at(-2);
        +    } else {
        +      return this.getStartVertex();
        +    }
        +  }
        +
        +  getControlPoints() {
        +    let points = [];
        +
        +    if (this._comesAfterSegment) {
        +      points.push(this.getStartVertex());
        +    }
        +    points.push(this.getStartVertex());
        +
        +    for (const vertex of this.vertices) {
        +      points.push(vertex);
        +    }
        +
        +    const prevVertex = this.getStartVertex();
        +    if (this._splineProperties.ends === constants.INCLUDE) {
        +      points.unshift(prevVertex);
        +      points.push(this.vertices.at(-1));
        +    } else if (this._splineProperties.ends === constants.JOIN) {
        +      points.unshift(this.vertices.at(-1));
        +      points.push(prevVertex, this.vertices.at(0));
        +    }
        +
        +    return points;
        +  }
        +
        +  handlesClose() {
        +    if (!this._belongsToShape) return false;
        +
        +    // Only handle closing if the spline is the only thing in its contour after
        +    // the anchor
        +    const contour = this._shape.at(this._contoursIndex);
        +    return contour.primitives.length === 2 && this._primitivesIndex === 1;
        +  }
        +
        +  close() {
        +    this._splineProperties.ends = constants.JOIN;
        +  }
        +}
        +
        +// ---- ISOLATED PRIMITIVES ----
        +
        +class Point extends ShapePrimitive {
        +  #vertexCapacity = 1;
        +
        +  get vertexCapacity() {
        +    return this.#vertexCapacity;
        +  }
        +
        +  accept(visitor) {
        +    visitor.visitPoint(this);
        +  }
        +}
        +
        +class Line extends ShapePrimitive {
        +  #vertexCapacity = 2;
        +
        +  get vertexCapacity() {
        +    return this.#vertexCapacity;
        +  }
        +
        +  accept(visitor) {
        +    visitor.visitLine(this);
        +  }
        +}
        +
        +class Triangle extends ShapePrimitive {
        +  #vertexCapacity = 3;
        +
        +  get vertexCapacity() {
        +    return this.#vertexCapacity;
        +  }
        +
        +  accept(visitor) {
        +    visitor.visitTriangle(this);
        +  }
        +}
        +
        +class Quad extends ShapePrimitive {
        +  #vertexCapacity = 4;
        +
        +  get vertexCapacity() {
        +    return this.#vertexCapacity;
        +  }
        +
        +  accept(visitor) {
        +    visitor.visitQuad(this);
        +  }
        +}
        +
        +// ---- TESSELLATION PRIMITIVES ----
        +
        +class TriangleFan extends ShapePrimitive {
        +  #vertexCapacity = Infinity;
        +
        +  get vertexCapacity() {
        +    return this.#vertexCapacity;
        +  }
        +
        +  accept(visitor) {
        +    visitor.visitTriangleFan(this);
        +  }
        +}
        +
        +class TriangleStrip extends ShapePrimitive {
        +  #vertexCapacity = Infinity;
        +
        +  get vertexCapacity() {
        +    return this.#vertexCapacity;
        +  }
        +
        +  accept(visitor) {
        +    visitor.visitTriangleStrip(this);
        +  }
        +}
        +
        +class QuadStrip extends ShapePrimitive {
        +  #vertexCapacity = Infinity;
        +
        +  get vertexCapacity() {
        +    return this.#vertexCapacity;
        +  }
        +
        +  accept(visitor) {
        +    visitor.visitQuadStrip(this);
        +  }
        +}
        +
        +// ---- PRIMITIVE SHAPE CREATORS ----
        +
        +class PrimitiveShapeCreators {
        +  // TODO: make creators private?
        +  // That'd probably be better, but for now, it may be convenient to use
        +  // native Map properties like size, e.g. for testing, and it's simpler to
        +  // not have to wrap all the properties that might be useful
        +  creators;
        +
        +  constructor() {
        +    let creators = new Map();
        +
        +    /* TODO: REFACTOR BASED ON THE CODE BELOW,
        +       ONCE CONSTANTS ARE IMPLEMENTED AS SYMBOLS
        +
        +    // Store Symbols as strings for use in Map keys
        +    const EMPTY_PATH = constants.EMPTY_PATH.description;
        +    const PATH = constants.PATH.description;
        +    //etc.
        +
        +    creators.set(`vertex-${EMPTY_PATH}`, (...vertices) => new Anchor(...vertices));
        +    // etc.
        +
        +    get(vertexKind, shapeKind) {
        +      const key = `${vertexKind}-${shapeKind.description}`;
        +      return this.creators.get(key);
        +    }
        +    // etc.
        +    */
        +
        +    // vertex
        +    creators.set(`vertex-${constants.EMPTY_PATH}`, (...vertices) => new Anchor(...vertices));
        +    creators.set(`vertex-${constants.PATH}`, (...vertices) => new LineSegment(...vertices));
        +    creators.set(`vertex-${constants.POINTS}`, (...vertices) => new Point(...vertices));
        +    creators.set(`vertex-${constants.LINES}`, (...vertices) => new Line(...vertices));
        +    creators.set(`vertex-${constants.TRIANGLES}`, (...vertices) => new Triangle(...vertices));
        +    creators.set(`vertex-${constants.QUADS}`, (...vertices) => new Quad(...vertices));
        +    creators.set(`vertex-${constants.TRIANGLE_FAN}`, (...vertices) => new TriangleFan(...vertices));
        +    creators.set(`vertex-${constants.TRIANGLE_STRIP}`, (...vertices) => new TriangleStrip(...vertices));
        +    creators.set(`vertex-${constants.QUAD_STRIP}`, (...vertices) => new QuadStrip(...vertices));
        +
        +    // bezierVertex (constructors all take order and vertices so they can be called in a uniform way)
        +    creators.set(`bezierVertex-${constants.EMPTY_PATH}`, (order, ...vertices) => new Anchor(...vertices));
        +    creators.set(`bezierVertex-${constants.PATH}`, (order, ...vertices) => new BezierSegment(order, ...vertices));
        +
        +    // splineVertex
        +    creators.set(`splineVertex-${constants.EMPTY_PATH}`, (...vertices) => new Anchor(...vertices));
        +    creators.set(`splineVertex-${constants.PATH}`, (...vertices) => new SplineSegment(...vertices));
        +
        +    this.creators = creators;
        +  }
        +
        +  get(vertexKind, shapeKind) {
        +    const key = `${vertexKind}-${shapeKind}`;
        +    return this.creators.get(key);
        +  }
        +
        +  set(vertexKind, shapeKind, creator) {
        +    const key = `${vertexKind}-${shapeKind}`;
        +    this.creators.set(key, creator);
        +  }
        +
        +  clear() {
        +    this.creators.clear();
        +  }
        +}
        +
        +// ---- SHAPE ----
        +
        +/* Note: It's assumed that Shape instances are always built through
        + * their beginShape()/endShape() methods. For example, this ensures
        + * that a segment is never the first primitive in a contour (paths
        + * always start with an anchor), which simplifies code elsewhere.
        + */
        +class Shape {
        +  #vertexProperties;
        +  #initialVertexProperties;
        +  #primitiveShapeCreators;
        +  #bezierOrder = 3;
        +  kind = null;
        +  contours = [];
        +  _splineProperties = {
        +    tightness: 0,
        +    ends: constants.INCLUDE
        +  };
        +  userVertexProperties = null;
        +
        +  constructor(
        +    vertexProperties,
        +    primitiveShapeCreators = new PrimitiveShapeCreators()
        +  ) {
        +    this.#initialVertexProperties = vertexProperties;
        +    this.#vertexProperties = vertexProperties;
        +    this.#primitiveShapeCreators = primitiveShapeCreators;
        +
        +    for (const key in this.#vertexProperties) {
        +      if (key !== 'position' && key !== 'textureCoordinates') {
        +        this[key] = function(value) {
        +          this.#vertexProperties[key] = value;
        +        };
        +      }
        +    }
        +  }
        +
        +  serializeToArray(val) {
        +    if (val === null) {
        +      return [];
        +    } if (val instanceof Number) {
        +      return [val];
        +    } else if (val instanceof Array) {
        +      return val;
        +    } else if (val.array instanceof Function) {
        +      return val.array();
        +    } else {
        +      throw new Error(`Can't convert ${val} to array!`);
        +    }
        +  }
        +
        +  vertexToArray(vertex) {
        +    const array = [];
        +    for (const key in this.#vertexProperties) {
        +      if (this.userVertexProperties && key in this.userVertexProperties)
        +        continue;
        +      const val = vertex[key];
        +      array.push(...this.serializeToArray(val));
        +    }
        +    for (const key in this.userVertexProperties) {
        +      if (key in vertex) {
        +        array.push(...this.serializeToArray(vertex[key]));
        +      } else {
        +        array.push(...new Array(this.userVertexProperties[key]).fill(0));
        +      }
        +    }
        +    return array;
        +  }
        +
        +  hydrateValue(queue, original) {
        +    if (original === null) {
        +      return null;
        +    } else if (original instanceof Number) {
        +      return queue.shift();
        +    } else if (original instanceof Array) {
        +      const array = [];
        +      for (let i = 0; i < original.length; i++) {
        +        array.push(queue.shift());
        +      }
        +      return array;
        +    } else if (original instanceof Vector) {
        +      return new Vector(queue.shift(), queue.shift(), queue.shift());
        +    } else if (original instanceof Color) {
        +      // NOTE: Not sure what intention here is, `Color` constructor signature
        +      // has changed so needed to be reviewed
        +      const array = [
        +        queue.shift(),
        +        queue.shift(),
        +        queue.shift(),
        +        queue.shift()
        +      ];
        +      return new Color(array);
        +    }
        +  }
        +
        +  arrayToVertex(array) {
        +    const vertex = {};
        +    const queue = [...array];
        +
        +    for (const key in this.#vertexProperties) {
        +      if (this.userVertexProperties && key in this.userVertexProperties)
        +        continue;
        +      const original = this.#vertexProperties[key];
        +      vertex[key] = this.hydrateValue(queue, original);
        +    }
        +    for (const key in this.userVertexProperties) {
        +      const original = this.#vertexProperties[key];
        +      vertex[key] = this.hydrateValue(queue, original);
        +    }
        +    return vertex;
        +  }
        +
        +  arrayScale(array, scale) {
        +    return array.map(v => v * scale);
        +  }
        +
        +  arraySum(first, ...rest) {
        +    return first.map((v, i) => {
        +      let result = v;
        +      for (let j = 0; j < rest.length; j++) {
        +        result += rest[j][i];
        +      }
        +      return result;
        +    });
        +  }
        +
        +  arrayMinus(a, b) {
        +    return a.map((v, i) => v - b[i]);
        +  }
        +
        +  evaluateCubicBezier([a, b, c, d], t) {
        +    return this.arraySum(
        +      this.arrayScale(a, Math.pow(1 - t, 3)),
        +      this.arrayScale(b, 3 * Math.pow(1 - t, 2) * t),
        +      this.arrayScale(c, 3 * (1 - t) * Math.pow(t, 2)),
        +      this.arrayScale(d, Math.pow(t, 3))
        +    );
        +  }
        +
        +  evaluateQuadraticBezier([a, b, c], t) {
        +    return this.arraySum(
        +      this.arrayScale(a, Math.pow(1 - t, 2)),
        +      this.arrayScale(b, 2 * (1 - t) * t),
        +      this.arrayScale(c, t * t)
        +    );
        +  }
        +
        +  /*
        +  catmullRomToBezier(vertices, tightness)
        +
        +  Abbreviated description:
        +  Converts a Catmull-Rom spline to a sequence of Bezier curveTo points.
        +
        +  Parameters:
        +  vertices -> Array [v0, v1, v2, v3, ...] of at least four vertices
        +  tightness -> Number affecting shape of curve
        +
        +  Returns:
        +  array of Bezier curveTo control points, each represented as [c1, c2, c3][]
        +
        +  TODO:
        +  1. It seems p5 contains code for converting from Catmull-Rom to Bezier in at least two places:
        +
        +  catmullRomToBezier() is based on code in the legacy endShape() function:
        +  https://github.com/processing/p5.js/blob/1b66f097761d3c2057c0cec4349247d6125f93ca/src/core/p5.Renderer2D.js#L859C1-L886C1
        +
        +  A different conversion can be found elsewhere in p5:
        +  https://github.com/processing/p5.js/blob/17304ce9e9ef3f967bd828102a51b62a2d39d4f4/src/typography/p5.Font.js#L1179
        +
        +  A more careful review and comparison of both implementations would be helpful. They're different. I put
        +  catmullRomToBezier() together quickly without checking the math/algorithm, when I made the proof of concept
        +  for the refactor.
        +
        +  2. It may be possible to replace the code in p5.Font.js with the code here, to reduce duplication.
        +  */
        +  catmullRomToBezier(vertices, tightness) {
        +    let s = 1 - tightness;
        +    let bezArrays = [];
        +
        +    for (let i = 0; i + 3 < vertices.length; i++) {
        +      const [a, b, c, d] = vertices.slice(i, i + 4);
        +      const bezB = this.arraySum(
        +        b,
        +        this.arrayScale(this.arrayMinus(c, a), s / 6)
        +      );
        +      const bezC = this.arraySum(
        +        c,
        +        this.arrayScale(this.arrayMinus(b, d), s / 6)
        +      );
        +      const bezD = c;
        +
        +      bezArrays.push([bezB, bezC, bezD]);
        +    }
        +    return bezArrays;
        +  }
        +
        +  // TODO for at() method:
        +
        +  // RENAME?
        +  // -at() indicates it works like Array.prototype.at(), e.g. with negative indices
        +  // -get() may work better if we want to add a corresponding set() method
        +  // -a set() method could maybe check for problematic usage (e.g. inserting a Triangle into a PATH)
        +  // -renaming or removing would necessitate changes at call sites (it's already in use)
        +
        +  // REFACTOR?
        +
        +  // TEST
        +  at(contoursIndex, primitivesIndex, verticesIndex) {
        +    let contour;
        +    let primitive;
        +
        +    contour = this.contours.at(contoursIndex);
        +
        +    switch(arguments.length) {
        +      case 1:
        +        return contour;
        +      case 2:
        +        return contour.primitives.at(primitivesIndex);
        +      case 3:
        +        primitive = contour.primitives.at(primitivesIndex);
        +        return primitive.vertices.at(verticesIndex);
        +    }
        +  }
        +
        +  // maybe call this clear() for consistency with PrimitiveShapeCreators.clear()?
        +  // note: p5.Geometry has a reset() method, but also clearColors()
        +  // looks like reset() isn't in the public reference, so maybe we can switch
        +  // everything to clear()? Not sure if reset/clear is used in other classes,
        +  // but it'd be good if geometries and shapes are consistent
        +  reset() {
        +    this.#vertexProperties = { ...this.#initialVertexProperties };
        +    this.kind = null;
        +    this.contours = [];
        +    this.userVertexProperties = null;
        +  }
        +
        +  vertexProperty(name, data) {
        +    this.userVertexProperties = this.userVertexProperties || {};
        +    const key = this.vertexPropertyKey(name);
        +    if (!this.userVertexProperties[key]) {
        +      this.userVertexProperties[key] = data.length ? data.length : 1;
        +    }
        +    this.#vertexProperties[key] = data;
        +  }
        +  vertexPropertyName(key) {
        +    return key.replace(/Src$/, '');
        +  }
        +  vertexPropertyKey(name) {
        +    return name + 'Src';
        +  }
        +
        +  /*
        +  Note: Internally, #bezierOrder is stored as an array, in order to accommodate
        +  primitives including Bezier segments, Bezier triangles, and Bezier quads. For example,
        +  a segment may have #bezierOrder [m], whereas a quad may have #bezierOrder [m, n].
        +   */
        +
        +  bezierOrder(...order) {
        +    this.#bezierOrder = order;
        +  }
        +
        +  splineProperty(key, value) {
        +    this._splineProperties[key] = value;
        +  }
        +
        +  /*
        +  To-do: Maybe refactor #createVertex() since this has side effects that aren't advertised
        +  in the method name?
        +  */
        +  #createVertex(position, textureCoordinates) {
        +    this.#vertexProperties.position = position;
        +
        +    if (textureCoordinates !== undefined) {
        +      this.#vertexProperties.textureCoordinates = textureCoordinates;
        +    }
        +
        +    return new Vertex(this.#vertexProperties);
        +  }
        +
        +  #createPrimitiveShape(vertexKind, shapeKind, ...vertices) {
        +    let primitiveShapeCreator = this.#primitiveShapeCreators.get(
        +      vertexKind, shapeKind
        +    );
        +
        +    return  vertexKind === 'bezierVertex' ?
        +      primitiveShapeCreator(this.#bezierOrder, ...vertices) :
        +      primitiveShapeCreator(...vertices);
        +  }
        +
        +  /*
        +    #generalVertex() is reused by the special vertex functions,
        +    including vertex(), bezierVertex(), splineVertex(), and arcVertex():
        +
        +    It creates a vertex, builds a primitive including that
        +    vertex, and has the primitive add itself to the shape.
        +  */
        +  #generalVertex(kind, position, textureCoordinates) {
        +    let vertexKind = kind;
        +    let lastContourKind = this.at(-1).kind;
        +    let vertex = this.#createVertex(position, textureCoordinates);
        +
        +    let primitiveShape = this.#createPrimitiveShape(
        +      vertexKind,
        +      lastContourKind,
        +      vertex
        +    );
        +
        +    return primitiveShape.addToShape(this);
        +  }
        +
        +  vertex(position, textureCoordinates, { isClosing = false } = {}) {
        +    const added = this.#generalVertex('vertex', position, textureCoordinates);
        +    added.isClosing = isClosing;
        +  }
        +
        +  bezierVertex(position, textureCoordinates) {
        +    this.#generalVertex('bezierVertex', position, textureCoordinates);
        +  }
        +
        +  splineVertex(position, textureCoordinates) {
        +    this.#generalVertex('splineVertex', position, textureCoordinates);
        +  }
        +
        +  arcVertex(position, textureCoordinates) {
        +    this.#generalVertex('arcVertex', position, textureCoordinates);
        +  }
        +
        +  beginContour(shapeKind = constants.PATH) {
        +    if (this.at(-1)?.kind === constants.EMPTY_PATH) {
        +      this.contours.pop();
        +    }
        +    this.contours.push(new Contour(shapeKind));
        +  }
        +
        +  endContour(closeMode = constants.OPEN, _index = this.contours.length - 1) {
        +    const contour = this.at(_index);
        +    if (closeMode === constants.CLOSE) {
        +      // shape characteristics
        +      const isPath = contour.kind === constants.PATH;
        +
        +      // anchor characteristics
        +      const anchorVertex = this.at(_index, 0, 0);
        +      const anchorHasPosition = Object.hasOwn(anchorVertex, 'position');
        +      const lastSegment = this.at(_index, -1);
        +
        +      // close path
        +      if (isPath && anchorHasPosition) {
        +        if (lastSegment.handlesClose()) {
        +          lastSegment.close(anchorVertex);
        +        } else {
        +          // Temporarily remove contours after the current one so that we add to the original
        +          // contour again
        +          const rest = this.contours.splice(
        +            _index + 1,
        +            this.contours.length - _index - 1
        +          );
        +          const prevVertexProperties = this.#vertexProperties;
        +          this.#vertexProperties = { ...prevVertexProperties };
        +          for (const key in anchorVertex) {
        +            if (['position', 'textureCoordinates'].includes(key)) continue;
        +            this.#vertexProperties[key] = anchorVertex[key];
        +          }
        +          this.vertex(
        +            anchorVertex.position,
        +            anchorVertex.textureCoordinates,
        +            { isClosing: true }
        +          );
        +          this.#vertexProperties = prevVertexProperties;
        +          this.contours.push(...rest);
        +        }
        +      }
        +    }
        +  }
        +
        +  beginShape(shapeKind = constants.PATH) {
        +    this.kind = shapeKind;
        +    // Implicitly start a contour
        +    this.beginContour(shapeKind);
        +  }
        +  /* TO-DO:
        +     Refactor?
        +     - Might not need anchorHasPosition.
        +     - Might combine conditions at top, and rely on shortcircuiting.
        +     Does nothing if shape is not a path or has multiple contours. Might discuss this.
        +  */
        +  endShape(closeMode = constants.OPEN) {
        +    if (closeMode === constants.CLOSE) {
        +      // Close the first contour, the one implicitly used for shape data
        +      // added without an explicit contour
        +      this.endContour(closeMode, 0);
        +    }
        +  }
        +
        +  accept(visitor) {
        +    for (const contour of this.contours) {
        +      contour.accept(visitor);
        +    }
        +  }
        +}
        +
        +// ---- PRIMITIVE VISITORS ----
        +
        +// abstract class
        +class PrimitiveVisitor {
        +  constructor() {
        +    if (this.constructor === PrimitiveVisitor) {
        +      throw new Error('PrimitiveVisitor is an abstract class: it cannot be instantiated.');
        +    }
        +  }
        +  // path primitives
        +  visitAnchor(anchor) {
        +    throw new Error('Method visitAnchor() has not been implemented.');
        +  }
        +  visitLineSegment(lineSegment) {
        +    throw new Error('Method visitLineSegment() has not been implemented.');
        +  }
        +  visitBezierSegment(bezierSegment) {
        +    throw new Error('Method visitBezierSegment() has not been implemented.');
        +  }
        +  visitSplineSegment(curveSegment) {
        +    throw new Error('Method visitSplineSegment() has not been implemented.');
        +  }
        +  visitArcSegment(arcSegment) {
        +    throw new Error('Method visitArcSegment() has not been implemented.');
        +  }
        +
        +  // isolated primitives
        +  visitPoint(point) {
        +    throw new Error('Method visitPoint() has not been implemented.');
        +  }
        +  visitLine(line) {
        +    throw new Error('Method visitLine() has not been implemented.');
        +  }
        +  visitTriangle(triangle) {
        +    throw new Error('Method visitTriangle() has not been implemented.');
        +  }
        +  visitQuad(quad) {
        +    throw new Error('Method visitQuad() has not been implemented.');
        +  }
        +
        +  // tessellation primitives
        +  visitTriangleFan(triangleFan) {
        +    throw new Error('Method visitTriangleFan() has not been implemented.');
        +  }
        +  visitTriangleStrip(triangleStrip) {
        +    throw new Error('Method visitTriangleStrip() has not been implemented.');
        +  }
        +  visitQuadStrip(quadStrip) {
        +    throw new Error('Method visitQuadStrip() has not been implemented.');
        +  }
        +}
        +
        +// requires testing
        +class PrimitiveToPath2DConverter extends PrimitiveVisitor {
        +  path = new Path2D();
        +  strokeWeight;
        +
        +  constructor({ strokeWeight }) {
        +    super();
        +    this.strokeWeight = strokeWeight;
        +  }
        +
        +  // path primitives
        +  visitAnchor(anchor) {
        +    let vertex = anchor.getEndVertex();
        +    this.path.moveTo(vertex.position.x, vertex.position.y);
        +  }
        +  visitLineSegment(lineSegment) {
        +    if (lineSegment.isClosing) {
        +      // The same as lineTo, but it adds a stroke join between this
        +      // and the starting vertex rather than having two caps
        +      this.path.closePath();
        +    } else {
        +      let vertex = lineSegment.getEndVertex();
        +      this.path.lineTo(vertex.position.x, vertex.position.y);
        +    }
        +  }
        +  visitBezierSegment(bezierSegment) {
        +    let [v1, v2, v3] = bezierSegment.vertices;
        +
        +    switch (bezierSegment.order) {
        +      case 2:
        +        this.path.quadraticCurveTo(
        +          v1.position.x,
        +          v1.position.y,
        +          v2.position.x,
        +          v2.position.y
        +        );
        +        break;
        +      case 3:
        +        this.path.bezierCurveTo(
        +          v1.position.x,
        +          v1.position.y,
        +          v2.position.x,
        +          v2.position.y,
        +          v3.position.x,
        +          v3.position.y
        +        );
        +        break;
        +    }
        +  }
        +  visitSplineSegment(splineSegment) {
        +    const shape = splineSegment._shape;
        +
        +    if (
        +      splineSegment._splineProperties.ends === constants.EXCLUDE &&
        +      !splineSegment._comesAfterSegment
        +    ) {
        +      let startVertex = splineSegment._firstInterpolatedVertex;
        +      this.path.moveTo(startVertex.position.x, startVertex.position.y);
        +    }
        +
        +    const arrayVertices = splineSegment.getControlPoints().map(
        +      v => shape.vertexToArray(v)
        +    );
        +    let bezierArrays = shape.catmullRomToBezier(
        +      arrayVertices,
        +      splineSegment._splineProperties.tightness
        +    ).map(arr => arr.map(vertArr => shape.arrayToVertex(vertArr)));
        +    for (const array of bezierArrays) {
        +      const points = array.flatMap(vert => [vert.position.x, vert.position.y]);
        +      this.path.bezierCurveTo(...points);
        +    }
        +  }
        +  visitPoint(point) {
        +    const { x, y } = point.vertices[0].position;
        +    this.path.moveTo(x, y);
        +    // Hack: to draw just strokes and not fills, draw a very very tiny line
        +    this.path.lineTo(x + 0.00001, y);
        +  }
        +  visitLine(line) {
        +    const { x: x0, y: y0 } = line.vertices[0].position;
        +    const { x: x1, y: y1 } = line.vertices[1].position;
        +    this.path.moveTo(x0, y0);
        +    this.path.lineTo(x1, y1);
        +  }
        +  visitTriangle(triangle) {
        +    const [v0, v1, v2] = triangle.vertices;
        +    this.path.moveTo(v0.position.x, v0.position.y);
        +    this.path.lineTo(v1.position.x, v1.position.y);
        +    this.path.lineTo(v2.position.x, v2.position.y);
        +    this.path.closePath();
        +  }
        +  visitQuad(quad) {
        +    const [v0, v1, v2, v3] = quad.vertices;
        +    this.path.moveTo(v0.position.x, v0.position.y);
        +    this.path.lineTo(v1.position.x, v1.position.y);
        +    this.path.lineTo(v2.position.x, v2.position.y);
        +    this.path.lineTo(v3.position.x, v3.position.y);
        +    this.path.closePath();
        +  }
        +  visitTriangleFan(triangleFan) {
        +    const [v0, ...rest] = triangleFan.vertices;
        +    for (let i = 0; i < rest.length - 1; i++) {
        +      const v1 = rest[i];
        +      const v2 = rest[i + 1];
        +      this.path.moveTo(v0.position.x, v0.position.y);
        +      this.path.lineTo(v1.position.x, v1.position.y);
        +      this.path.lineTo(v2.position.x, v2.position.y);
        +      this.path.closePath();
        +    }
        +  }
        +  visitTriangleStrip(triangleStrip) {
        +    for (let i = 0; i < triangleStrip.vertices.length - 2; i++) {
        +      const v0 = triangleStrip.vertices[i];
        +      const v1 = triangleStrip.vertices[i + 1];
        +      const v2 = triangleStrip.vertices[i + 2];
        +      this.path.moveTo(v0.position.x, v0.position.y);
        +      this.path.lineTo(v1.position.x, v1.position.y);
        +      this.path.lineTo(v2.position.x, v2.position.y);
        +      this.path.closePath();
        +    }
        +  }
        +  visitQuadStrip(quadStrip) {
        +    for (let i = 0; i < quadStrip.vertices.length - 3; i += 2) {
        +      const v0 = quadStrip.vertices[i];
        +      const v1 = quadStrip.vertices[i + 1];
        +      const v2 = quadStrip.vertices[i + 2];
        +      const v3 = quadStrip.vertices[i + 3];
        +      this.path.moveTo(v0.position.x, v0.position.y);
        +      this.path.lineTo(v1.position.x, v1.position.y);
        +      // These are intentionally out of order to go around the quad
        +      this.path.lineTo(v3.position.x, v3.position.y);
        +      this.path.lineTo(v2.position.x, v2.position.y);
        +      this.path.closePath();
        +    }
        +  }
        +}
        +
        +class PrimitiveToVerticesConverter extends PrimitiveVisitor {
        +  contours = [];
        +  curveDetail;
        +
        +  constructor({ curveDetail = 1 } = {}) {
        +    super();
        +    this.curveDetail = curveDetail;
        +  }
        +
        +  lastContour() {
        +    return this.contours[this.contours.length - 1];
        +  }
        +
        +  visitAnchor(anchor) {
        +    this.contours.push([]);
        +    // Weird edge case: if the next segment is a spline, we might
        +    // need to jump to a different vertex.
        +    const next = anchor._nextPrimitive;
        +    if (next?.canOverrideAnchor) {
        +      this.lastContour().push(next._firstInterpolatedVertex);
        +    } else {
        +      this.lastContour().push(anchor.getEndVertex());
        +    }
        +  }
        +  visitLineSegment(lineSegment) {
        +    this.lastContour().push(lineSegment.getEndVertex());
        +  }
        +  visitBezierSegment(bezierSegment) {
        +    const contour = this.lastContour();
        +    const numPoints = Math.max(
        +      1,
        +      Math.ceil(bezierSegment.hullLength() * this.curveDetail)
        +    );
        +    const vertexArrays = [
        +      bezierSegment.getStartVertex(),
        +      ...bezierSegment.vertices
        +    ].map(v => bezierSegment._shape.vertexToArray(v));
        +    for (let i = 0; i < numPoints; i++) {
        +      const t = (i + 1) / numPoints;
        +      contour.push(
        +        bezierSegment._shape.arrayToVertex(
        +          bezierSegment.order === 3
        +            ? bezierSegment._shape.evaluateCubicBezier(vertexArrays, t)
        +            : bezierSegment._shape.evaluateQuadraticBezier(vertexArrays, t)
        +        )
        +      );
        +    }
        +  }
        +  visitSplineSegment(splineSegment) {
        +    const shape = splineSegment._shape;
        +    const contour = this.lastContour();
        +
        +    const arrayVertices = splineSegment.getControlPoints().map(
        +      v => shape.vertexToArray(v)
        +    );
        +    let bezierArrays = shape.catmullRomToBezier(
        +      arrayVertices,
        +      splineSegment._splineProperties.tightness
        +    );
        +    let startVertex = shape.vertexToArray(splineSegment._firstInterpolatedVertex);
        +    for (const array of bezierArrays) {
        +      const bezierControls = [startVertex, ...array];
        +      const numPoints = Math.max(
        +        1,
        +        Math.ceil(
        +          polylineLength(bezierControls.map(v => shape.arrayToVertex(v))) *
        +          this.curveDetail
        +        )
        +      );
        +      for (let i = 0; i < numPoints; i++) {
        +        const t = (i + 1) / numPoints;
        +        contour.push(
        +          shape.arrayToVertex(shape.evaluateCubicBezier(bezierControls, t))
        +        );
        +      }
        +      startVertex = array[2];
        +    }
        +  }
        +  visitPoint(point) {
        +    this.contours.push(point.vertices.slice());
        +  }
        +  visitLine(line) {
        +    this.contours.push(line.vertices.slice());
        +  }
        +  visitTriangle(triangle) {
        +    this.contours.push(triangle.vertices.slice());
        +  }
        +  visitQuad(quad) {
        +    this.contours.push(quad.vertices.slice());
        +  }
        +  visitTriangleFan(triangleFan) {
        +    // WebGL itself interprets the vertices as a fan, no reformatting needed
        +    this.contours.push(triangleFan.vertices.slice());
        +  }
        +  visitTriangleStrip(triangleStrip) {
        +    // WebGL itself interprets the vertices as a strip, no reformatting needed
        +    this.contours.push(triangleStrip.vertices.slice());
        +  }
        +  visitQuadStrip(quadStrip) {
        +    // WebGL itself interprets the vertices as a strip, no reformatting needed
        +    this.contours.push(quadStrip.vertices.slice());
        +  }
        +}
        +
        +class PointAtLengthGetter extends PrimitiveVisitor {
        +  constructor() {
        +    super();
        +  }
        +}
        +
        +function customShapes(p5, fn) {
        +  // ---- GENERAL CLASSES ----
        +
        +  /**
        +     * @private
        +     * A class to describe a custom shape made with `beginShape()`/`endShape()`.
        +     *
        +     * Every `Shape` has a `kind`. The kind takes any value that
        +     * can be passed to <a href="#/p5/beginShape">beginShape()</a>:
        +     *
        +     * - `PATH`
        +     * - `POINTS`
        +     * - `LINES`
        +     * - `TRIANGLES`
        +     * - `QUADS`
        +     * - `TRIANGLE_FAN`
        +     * - `TRIANGLE_STRIP`
        +     * - `QUAD_STRIP`
        +     *
        +     * A `Shape` of any kind consists of `contours`, which can be thought of as
        +     * subshapes (shapes inside another shape). Each `contour` is built from
        +     * basic shapes called primitives, and each primitive consists of one or more vertices.
        +     *
        +     * For example, a square can be made from a single path contour with four line-segment
        +     * primitives. Each line segment contains a vertex that indicates its endpoint. A square
        +     * with a circular hole in it contains the circle in a separate contour.
        +     *
        +     * By default, each vertex only has a position, but a shape's vertices may have other
        +     * properties such as texture coordinates, a normal vector, a fill color, and a stroke color.
        +     * The properties every vertex should have may be customized by passing `vertexProperties` to
        +     * `createShape()`.
        +     *
        +     * Once a shape is created and given a name like `myShape`, it can be built up with
        +     * methods such as `myShape.beginShape()`, `myShape.vertex()`, and `myShape.endShape()`.
        +     *
        +     * Vertex functions such as `vertex()` or `bezierVertex()` are used to set the `position`
        +     * property of vertices, as well as the `textureCoordinates` property if applicable. Those
        +     * properties only apply to a single vertex.
        +     *
        +     * If `vertexProperties` includes other properties, they are each set by a method of the
        +     * same name. For example, if vertices in `myShape` have a `fill`, then that is set with
        +     * `myShape.fill()`. In the same way that a <a href="#/p5/fill">fill()</a> may be applied
        +     * to one or more shapes, `myShape.fill()` may be applied to one or more vertices.
        +     *
        +     * @class p5.Shape
        +     * @param {Object} [vertexProperties={position: createVector(0, 0)}] vertex properties and their initial values.
        +     */
        +
        +  p5.Shape = Shape;
        +
        +  /**
        +     * @private
        +     * A class to describe a contour made with `beginContour()`/`endContour()`.
        +     *
        +     * Contours may be thought of as shapes inside of other shapes.
        +     * For example, a contour may be used to create a hole in a shape that is created
        +     * with <a href="#/p5/beginShape">beginShape()</a>/<a href="#/p5/endShape">endShape()</a>.
        +     * Multiple contours may be included inside a single shape.
        +     *
        +     * Contours can have any `kind` that a shape can have:
        +     *
        +     * - `PATH`
        +     * - `POINTS`
        +     * - `LINES`
        +     * - `TRIANGLES`
        +     * - `QUADS`
        +     * - `TRIANGLE_FAN`
        +     * - `TRIANGLE_STRIP`
        +     * - `QUAD_STRIP`
        +     *
        +     * By default, a contour has the same kind as the shape that contains it, but this
        +     * may be changed by passing a different `kind` to <a href="#/p5/beginContour">beginContour()</a>.
        +     *
        +     * A `Contour` of any kind consists of `primitives`, which are the most basic
        +     * shapes that can be drawn. For example, if a contour is a hexagon, then
        +     * it's made from six line-segment primitives.
        +     *
        +     * @class p5.Contour
        +     */
        +
        +  p5.Contour = Contour;
        +
        +  /**
        +     * @private
        +     * A base class to describe a shape primitive (a basic shape drawn with
        +     * `beginShape()`/`endShape()`).
        +     *
        +     * Shape primitives are the most basic shapes that can be drawn with
        +     * <a href="#/p5/beginShape">beginShape()</a>/<a href="#/p5/endShape">endShape()</a>:
        +     *
        +     * - segment primitives: line segments, bezier segments, spline segments, and arc segments
        +     * - isolated primitives: points, lines, triangles, and quads
        +     * - tessellation primitives: triangle fans, triangle strips, and quad strips
        +     *
        +     * More complex shapes may be created by combining many primitives, possibly of different kinds,
        +     * into a single shape.
        +     *
        +     * In a similar way, every shape primitive is built from one or more vertices.
        +     * For example, a point consists of a single vertex, while a triangle consists of three vertices.
        +     * Each type of shape primitive has a `vertexCapacity`, which may be `Infinity` (for example, a
        +     * spline may consist of any number of vertices). A primitive's `vertexCount` is the number of
        +     * vertices it currently contains.
        +     *
        +     * Each primitive can add itself to a shape with an `addToShape()` method.
        +     *
        +     * It can also accept visitor objects with an `accept()` method. When a primitive accepts a visitor,
        +     * it gives the visitor access to its vertex data. For example, one visitor to a segment might turn
        +     * the data into 2D drawing instructions. Another might find a point at a given distance
        +     * along the segment.
        +     *
        +     * @class p5.ShapePrimitive
        +     * @abstract
        +     */
        +
        +  p5.ShapePrimitive = ShapePrimitive;
        +
        +  /**
        +     * @private
        +     * A class to describe a vertex (a point on a shape), in 2D or 3D.
        +     *
        +     * Vertices are the basic building blocks of all `p5.Shape` objects, including
        +     * shapes made with <a href="#/p5/vertex">vertex()</a>, <a href="#/p5/arcVertex">arcVertex()</a>,
        +     * <a href="#/p5/bezierVertex">bezierVertex()</a>, and <a href="#/p5/splineVertex">splineVertex()</a>.
        +     *
        +     * Like a point on an object in the real world, a vertex may have different properties.
        +     * These may include coordinate properties such as `position`, `textureCoordinates`, and `normal`,
        +     * color properties such as `fill` and `stroke`, and more.
        +     *
        +     * A vertex called `myVertex` with position coordinates `(2, 3, 5)` and a green stroke may be created
        +     * like this:
        +     *
        +     * ```js
        +     * let myVertex = new p5.Vertex({
        +     *   position: createVector(2, 3, 5),
        +     *   stroke: color('green')
        +     * });
        +     * ```
        +     *
        +     * Any property names may be used. The `p5.Shape` class assumes that if a vertex has a
        +     * position or texture coordinates, they are stored in `position` and `textureCoordinates`
        +     * properties.
        +     *
        +     * Property values may be any
        +     * <a href="https://developer.mozilla.org/en-US/docs/Glossary/Primitive">JavaScript primitive</a>, any
        +     * <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer">object literal</a>,
        +     * or any object with an `array` property.
        +     *
        +     * For example, if a position is stored as a `p5.Vector` object and a stroke is stored as a `p5.Color` object,
        +     * then the `array` properties of those objects will be used by the vertex's own `array` property, which provides
        +     * all the vertex data in a single array.
        +     *
        +     * @class p5.Vertex
        +     * @param {Object} [properties={position: createVector(0, 0)}] vertex properties.
        +     */
        +
        +  p5.Vertex = Vertex;
        +
        +  // ---- PATH PRIMITIVES ----
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     *
        +     * @class p5.Anchor
        +     * @extends p5.ShapePrimitive
        +     * @param {p5.Vertex} vertex the vertex to include in the anchor.
        +     */
        +
        +  p5.Anchor = Anchor;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     *
        +     * Note: When a segment is added to a shape, it's attached to an anchor or another segment.
        +     * Adding it to another shape may result in unexpected behavior.
        +     *
        +     * @class p5.Segment
        +     * @extends p5.ShapePrimitive
        +     * @param {...p5.Vertex} vertices the vertices to include in the segment.
        +     */
        +
        +  p5.Segment = Segment;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     *
        +     * @class p5.LineSegment
        +     * @param {p5.Vertex} vertex the vertex to include in the anchor.
        +     */
        +
        +  p5.LineSegment = LineSegment;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     */
        +
        +  p5.BezierSegment = BezierSegment;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     */
        +
        +  p5.SplineSegment = SplineSegment;
        +
        +  // ---- ISOLATED PRIMITIVES ----
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     */
        +
        +  p5.Point = Point;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     *
        +     * @class p5.Line
        +     * @param {...p5.Vertex} vertices the vertices to include in the line.
        +     */
        +
        +  p5.Line = Line;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     */
        +
        +  p5.Triangle = Triangle;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     */
        +
        +  p5.Quad = Quad;
        +
        +  // ---- TESSELLATION PRIMITIVES ----
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     */
        +
        +  p5.TriangleFan = TriangleFan;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     */
        +
        +  p5.TriangleStrip = TriangleStrip;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     */
        +
        +  p5.QuadStrip = QuadStrip;
        +
        +  // ---- PRIMITIVE VISITORS ----
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     */
        +
        +  p5.PrimitiveVisitor = PrimitiveVisitor;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     *
        +     * Notes:
        +     * 1. Assumes vertex positions are stored as p5.Vector instances.
        +     * 2. Currently only supports position properties of vectors.
        +     */
        +
        +  p5.PrimitiveToPath2DConverter = PrimitiveToPath2DConverter;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     */
        +
        +  p5.PrimitiveToVerticesConverter = PrimitiveToVerticesConverter;
        +
        +  /**
        +     * @private
        +     * A class responsible for...
        +     */
        +
        +  p5.PointAtLengthGetter = PointAtLengthGetter;
        +
        +  // ---- FUNCTIONS ----
        +
        +  /**
        +   * TODO: documentation
        +   */
        +  fn.bezierOrder = function(order) {
        +    return this._renderer.bezierOrder(order);
        +  };
        +
        +  /**
        +   * TODO: documentation
        +   */
        +  fn.splineVertex = function(...args) {
        +    let x = 0, y = 0, z = 0, u = 0, v = 0;
        +    if (args.length === 2) {
        +      [x, y] = args;
        +    } else if (args.length === 4) {
        +      [x, y, u, v] = args;
        +    } else if (args.length === 3) {
        +      [x, y, z] = args;
        +    } else if (args.length === 5) {
        +      [x, y, z, u, v] = args;
        +    }
        +    this._renderer.splineVertex(x, y, z, u, v);
        +  };
        +
        +  /**
        +   * TODO: documentation
        +   * @param {String} key
        +   * @param value
        +   */
        +  fn.splineProperty = function(key, value) {
        +    return this._renderer.splineProperty(key, value);
        +  };
        +
        +  /**
        +   * Adds a vertex to a custom shape.
        +   *
        +   * `vertex()` sets the coordinates of vertices drawn between the
        +   * <a href="#/p5/beginShape">beginShape()</a> and
        +   * <a href="#/p5/endShape">endShape()</a> functions.
        +   *
        +   * The first two parameters, `x` and `y`, set the x- and y-coordinates of the
        +   * vertex.
        +   *
        +   * The third parameter, `z`, is optional. It sets the z-coordinate of the
        +   * vertex in WebGL mode. By default, `z` is 0.
        +   *
        +   * The fourth and fifth parameters, `u` and `v`, are also optional. They set
        +   * the u- and v-coordinates for the vertex’s texture when used with
        +   * <a href="#/p5/endShape">endShape()</a>. By default, `u` and `v` are both 0.
        +   *
        +   * @method vertex
        +   * @param  {Number} x x-coordinate of the vertex.
        +   * @param  {Number} y y-coordinate of the vertex.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the shape.
        +   *   strokeWeight(3);
        +   *
        +   *   // Start drawing the shape.
        +   *   // Only draw the vertices.
        +   *   beginShape(POINTS);
        +   *
        +   *   // Add the vertices.
        +   *   vertex(30, 20);
        +   *   vertex(85, 20);
        +   *   vertex(85, 75);
        +   *   vertex(30, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('Four black dots that form a square are drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add vertices.
        +   *   vertex(30, 20);
        +   *   vertex(85, 20);
        +   *   vertex(85, 75);
        +   *   vertex(30, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape(CLOSE);
        +   *
        +   *   describe('A white square on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add vertices.
        +   *   vertex(-20, -30, 0);
        +   *   vertex(35, -30, 0);
        +   *   vertex(35, 25, 0);
        +   *   vertex(-20, 25, 0);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape(CLOSE);
        +   *
        +   *   describe('A white square on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white square spins around slowly on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add vertices.
        +   *   vertex(-20, -30, 0);
        +   *   vertex(35, -30, 0);
        +   *   vertex(35, 25, 0);
        +   *   vertex(-20, 25, 0);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape(CLOSE);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load an image to apply as a texture.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A photograph of a ceiling rotates slowly against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Style the shape.
        +   *   noStroke();
        +   *
        +   *   // Apply the texture.
        +   *   texture(img);
        +   *   textureMode(NORMAL);
        +   *
        +   *   // Start drawing the shape
        +   *   beginShape();
        +   *
        +   *   // Add vertices.
        +   *   vertex(-20, -30, 0, 0, 0);
        +   *   vertex(35, -30, 0, 1, 0);
        +   *   vertex(35, 25, 0, 1, 1);
        +   *   vertex(-20, 25, 0, 0, 1);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method vertex
        +   * @param  {Number} x
        +   * @param  {Number} y
        +   * @param  {Number} [z]   z-coordinate of the vertex. Defaults to 0.
        +   */
        +  /**
        +   * @method vertex
        +   * @param  {Number} x
        +   * @param  {Number} y
        +   * @param  {Number} [z]
        +   * @param  {Number} [u]   u-coordinate of the vertex's texture. Defaults to 0.
        +   * @param  {Number} [v]   v-coordinate of the vertex's texture. Defaults to 0.
        +   */
        +  fn.vertex = function(x, y) {
        +    let z, u, v;
        +
        +    // default to (x, y) mode: all other arguments assumed to be 0.
        +    z = u = v = 0;
        +
        +    if (arguments.length === 3) {
        +      // (x, y, z) mode: (u, v) assumed to be 0.
        +      z = arguments[2];
        +    } else if (arguments.length === 4) {
        +      // (x, y, u, v) mode: z assumed to be 0.
        +      u = arguments[2];
        +      v = arguments[3];
        +    } else if (arguments.length === 5) {
        +      // (x, y, z, u, v) mode
        +      z = arguments[2];
        +      u = arguments[3];
        +      v = arguments[4];
        +    }
        +    this._renderer.vertex(x, y, z, u, v);
        +    return;
        +  };
        +
        +  // Note: Code is commented out for now, to avoid conflicts with the existing implementation.
        +
        +  /**
        +     * Top-line description
        +     *
        +     * More details...
        +     */
        +
        +  // fn.beginShape = function() {
        +
        +  // };
        +
        +  /**
        +     * Top-line description
        +     *
        +     * More details...
        +     */
        +
        +  // fn.bezierVertex = function() {
        +
        +  // };
        +
        +  /**
        +     * Top-line description
        +     *
        +     * More details...
        +     */
        +
        +  // fn.curveVertex = function() {
        +
        +  // };
        +
        +  /**
        +   * Begins creating a hole within a flat shape.
        +   *
        +   * The `beginContour()` and <a href="#/p5/endContour">endContour()</a>
        +   * functions allow for creating negative space within custom shapes that are
        +   * flat. `beginContour()` begins adding vertices to a negative space and
        +   * <a href="#/p5/endContour">endContour()</a> stops adding them.
        +   * `beginContour()` and <a href="#/p5/endContour">endContour()</a> must be
        +   * called between <a href="#/p5/beginShape">beginShape()</a> and
        +   * <a href="#/p5/endShape">endShape()</a>.
        +   *
        +   * Transformations such as <a href="#/p5/translate">translate()</a>,
        +   * <a href="#/p5/rotate">rotate()</a>, and <a href="#/p5/scale">scale()</a>
        +   * don't work between `beginContour()` and
        +   * <a href="#/p5/endContour">endContour()</a>. It's also not possible to use
        +   * other shapes, such as <a href="#/p5/ellipse">ellipse()</a> or
        +   * <a href="#/p5/rect">rect()</a>, between `beginContour()` and
        +   * <a href="#/p5/endContour">endContour()</a>.
        +   *
        +   * Note: The vertices that define a negative space must "wind" in the opposite
        +   * direction from the outer shape. First, draw vertices for the outer shape
        +   * clockwise order. Then, draw vertices for the negative space in
        +   * counter-clockwise order.
        +   *
        +   * @method beginContour
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Exterior vertices, clockwise winding.
        +   *   vertex(10, 10);
        +   *   vertex(90, 10);
        +   *   vertex(90, 90);
        +   *   vertex(10, 90);
        +   *
        +   *   // Interior vertices, counter-clockwise winding.
        +   *   beginContour();
        +   *   vertex(30, 30);
        +   *   vertex(30, 70);
        +   *   vertex(70, 70);
        +   *   vertex(70, 30);
        +   *   endContour();
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape(CLOSE);
        +   *
        +   *   describe('A white square with a square hole in its center drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white square with a square hole in its center drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Exterior vertices, clockwise winding.
        +   *   vertex(-40, -40);
        +   *   vertex(40, -40);
        +   *   vertex(40, 40);
        +   *   vertex(-40, 40);
        +   *
        +   *   // Interior vertices, counter-clockwise winding.
        +   *   beginContour();
        +   *   vertex(-20, -20);
        +   *   vertex(-20, 20);
        +   *   vertex(20, 20);
        +   *   vertex(20, -20);
        +   *   endContour();
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape(CLOSE);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.beginContour = function(kind) {
        +    this._renderer.beginContour(kind);
        +  };
        +
        +  /**
        +   * Stops creating a hole within a flat shape.
        +   *
        +   * The <a href="#/p5/beginContour">beginContour()</a> and `endContour()`
        +   * functions allow for creating negative space within custom shapes that are
        +   * flat. <a href="#/p5/beginContour">beginContour()</a> begins adding vertices
        +   * to a negative space and `endContour()` stops adding them.
        +   * <a href="#/p5/beginContour">beginContour()</a> and `endContour()` must be
        +   * called between <a href="#/p5/beginShape">beginShape()</a> and
        +   * <a href="#/p5/endShape">endShape()</a>.
        +   *
        +   * Transformations such as <a href="#/p5/translate">translate()</a>,
        +   * <a href="#/p5/rotate">rotate()</a>, and <a href="#/p5/scale">scale()</a>
        +   * don't work between <a href="#/p5/beginContour">beginContour()</a> and
        +   * `endContour()`. It's also not possible to use other shapes, such as
        +   * <a href="#/p5/ellipse">ellipse()</a> or <a href="#/p5/rect">rect()</a>,
        +   * between <a href="#/p5/beginContour">beginContour()</a> and `endContour()`.
        +   *
        +   * Note: The vertices that define a negative space must "wind" in the opposite
        +   * direction from the outer shape. First, draw vertices for the outer shape
        +   * clockwise order. Then, draw vertices for the negative space in
        +   * counter-clockwise order.
        +   *
        +   * @method endContour
        +   * @param {OPEN|CLOSE} [mode=OPEN]
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Exterior vertices, clockwise winding.
        +   *   vertex(10, 10);
        +   *   vertex(90, 10);
        +   *   vertex(90, 90);
        +   *   vertex(10, 90);
        +   *
        +   *   // Interior vertices, counter-clockwise winding.
        +   *   beginContour();
        +   *   vertex(30, 30);
        +   *   vertex(30, 70);
        +   *   vertex(70, 70);
        +   *   vertex(70, 30);
        +   *   endContour();
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape(CLOSE);
        +   *
        +   *   describe('A white square with a square hole in its center drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white square with a square hole in its center drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Exterior vertices, clockwise winding.
        +   *   vertex(-40, -40);
        +   *   vertex(40, -40);
        +   *   vertex(40, 40);
        +   *   vertex(-40, 40);
        +   *
        +   *   // Interior vertices, counter-clockwise winding.
        +   *   beginContour();
        +   *   vertex(-20, -20);
        +   *   vertex(-20, 20);
        +   *   vertex(20, 20);
        +   *   vertex(20, -20);
        +   *   endContour();
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape(CLOSE);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.endContour = function(mode = constants.OPEN) {
        +    this._renderer.endContour(mode);
        +  };
        +
        +  /**
        +     * Top-line description
        +     *
        +     * More details...
        +     */
        +
        +  // fn.endShape = function() {
        +
        +  // };
        +
        +  /**
        +     * Top-line description
        +     *
        +     * More details...
        +     */
        +
        +  // fn.vertex = function() {
        +
        +  // };
        +
        +  /**
        +     * Top-line description
        +     *
        +     * More details...
        +     */
        +
        +  // fn.normal = function() {
        +
        +  // };
        +
        +  /**
        +     * Top-line description
        +     *
        +     * More details...
        +     */
        +
        +  // fn.vertexProperty = function() {
        +
        +  // };
        +}
        +
        +export default customShapes;
        +export {
        +  Shape,
        +  Contour,
        +  ShapePrimitive,
        +  Vertex,
        +  Anchor,
        +  Segment,
        +  LineSegment,
        +  BezierSegment,
        +  SplineSegment,
        +  Point,
        +  Line,
        +  Triangle,
        +  Quad,
        +  TriangleFan,
        +  TriangleStrip,
        +  QuadStrip,
        +  PrimitiveVisitor,
        +  PrimitiveToPath2DConverter,
        +  PrimitiveToVerticesConverter,
        +  PointAtLengthGetter
        +};
        +
        +if (typeof p5 !== 'undefined') {
        +  customShapes(p5, p5.prototype);
        +}
        diff --git a/src/shape/index.js b/src/shape/index.js
        new file mode 100644
        index 0000000000..dbc520ff83
        --- /dev/null
        +++ b/src/shape/index.js
        @@ -0,0 +1,13 @@
        +import primitives from './2d_primitives.js';
        +import attributes from './attributes.js';
        +import curves from './curves.js';
        +import vertex from './vertex.js';
        +import customShapes from './custom_shapes.js';
        +
        +export default function(p5){
        +  p5.registerAddon(primitives);
        +  p5.registerAddon(attributes);
        +  p5.registerAddon(curves);
        +  p5.registerAddon(vertex);
        +  p5.registerAddon(customShapes);
        +}
        \ No newline at end of file
        diff --git a/src/shape/vertex.js b/src/shape/vertex.js
        new file mode 100644
        index 0000000000..5b4b0cb2d7
        --- /dev/null
        +++ b/src/shape/vertex.js
        @@ -0,0 +1,1866 @@
        +/**
        + * @module Shape
        + * @submodule Vertex
        + * @for p5
        + * @requires core
        + * @requires constants
        + */
        +
        +import * as constants from '../core/constants';
        +
        +function vertex(p5, fn){
        +  /**
        +   * Begins adding vertices to a custom shape.
        +   *
        +   * The `beginShape()` and <a href="#/p5/endShape">endShape()</a> functions
        +   * allow for creating custom shapes in 2D or 3D. `beginShape()` begins adding
        +   * vertices to a custom shape and <a href="#/p5/endShape">endShape()</a> stops
        +   * adding them.
        +   *
        +   * The parameter, `kind`, sets the kind of shape to make. The available kinds are:
        +   *
        +   * - `PATH` (the default) to draw shapes by tracing out the path along their edges.
        +   * - `POINTS` to draw a series of points.
        +   * - `LINES` to draw a series of unconnected line segments.
        +   * - `TRIANGLES` to draw a series of separate triangles.
        +   * - `TRIANGLE_FAN` to draw a series of connected triangles sharing the first vertex in a fan-like fashion.
        +   * - `TRIANGLE_STRIP` to draw a series of connected triangles in strip fashion.
        +   * - `QUADS` to draw a series of separate quadrilaterals (quads).
        +   * - `QUAD_STRIP` to draw quad strip using adjacent edges to form the next quad.
        +   *
        +   * After calling `beginShape()`, shapes can be built by calling
        +   * <a href="#/p5/vertex">vertex()</a>,
        +   * <a href="#/p5/bezierVertex">bezierVertex()</a>,
        +   * <a href="#/p5/bezierVertex">bezierVertex()</a>, and/or
        +   * <a href="#/p5/splineVertex">splineVertex()</a>. Calling
        +   * <a href="#/p5/endShape">endShape()</a> will stop adding vertices to the
        +   * shape. Each shape will be outlined with the current stroke color and filled
        +   * with the current fill color.
        +   *
        +   * Transformations such as <a href="#/p5/translate">translate()</a>,
        +   * <a href="#/p5/rotate">rotate()</a>, and
        +   * <a href="#/p5/scale">scale()</a> don't work between `beginShape()` and
        +   * <a href="#/p5/endShape">endShape()</a>. It's also not possible to use
        +   * other shapes, such as <a href="#/p5/ellipse">ellipse()</a> or
        +   * <a href="#/p5/rect">rect()</a>, between `beginShape()` and
        +   * <a href="#/p5/endShape">endShape()</a>.
        +   *
        +   * @method beginShape
        +   * @param  {(POINTS|LINES|TRIANGLES|TRIANGLE_FAN|TRIANGLE_STRIP|QUADS|QUAD_STRIP|PATH)} [kind=PATH] either POINTS, LINES, TRIANGLES, TRIANGLE_FAN
        +   *                                TRIANGLE_STRIP, QUADS, QUAD_STRIP or PATH. Defaults to PATH.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add vertices.
        +   *   vertex(30, 20);
        +   *   vertex(85, 20);
        +   *   vertex(85, 75);
        +   *   vertex(30, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape(CLOSE);
        +   *
        +   *   describe('A white square on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   // Only draw the vertices (points).
        +   *   beginShape(POINTS);
        +   *
        +   *   // Add vertices.
        +   *   vertex(30, 20);
        +   *   vertex(85, 20);
        +   *   vertex(85, 75);
        +   *   vertex(30, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('Four black dots that form a square are drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   // Only draw lines between alternating pairs of vertices.
        +   *   beginShape(LINES);
        +   *
        +   *   // Add vertices.
        +   *   vertex(30, 20);
        +   *   vertex(85, 20);
        +   *   vertex(85, 75);
        +   *   vertex(30, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('Two horizontal black lines on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the shape.
        +   *   noFill();
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add vertices.
        +   *   vertex(30, 20);
        +   *   vertex(85, 20);
        +   *   vertex(85, 75);
        +   *   vertex(30, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('Three black lines form a sideways U shape on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the shape.
        +   *   noFill();
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add vertices.
        +   *   vertex(30, 20);
        +   *   vertex(85, 20);
        +   *   vertex(85, 75);
        +   *   vertex(30, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   // Connect the first and last vertices.
        +   *   endShape(CLOSE);
        +   *
        +   *   describe('A black outline of a square drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   // Draw a series of triangles.
        +   *   beginShape(TRIANGLES);
        +   *
        +   *   // Left triangle.
        +   *   vertex(30, 75);
        +   *   vertex(40, 20);
        +   *   vertex(50, 75);
        +   *
        +   *   // Right triangle.
        +   *   vertex(60, 20);
        +   *   vertex(70, 75);
        +   *   vertex(80, 20);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('Two white triangles drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   // Draw a series of triangles.
        +   *   beginShape(TRIANGLE_STRIP);
        +   *
        +   *   // Add vertices.
        +   *   vertex(30, 75);
        +   *   vertex(40, 20);
        +   *   vertex(50, 75);
        +   *   vertex(60, 20);
        +   *   vertex(70, 75);
        +   *   vertex(80, 20);
        +   *   vertex(90, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('Five white triangles that are interleaved drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   // Draw a series of triangles that share their first vertex.
        +   *   beginShape(TRIANGLE_FAN);
        +   *
        +   *   // Add vertices.
        +   *   vertex(57.5, 50);
        +   *   vertex(57.5, 15);
        +   *   vertex(92, 50);
        +   *   vertex(57.5, 85);
        +   *   vertex(22, 50);
        +   *   vertex(57.5, 15);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('Four white triangles form a square are drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   // Draw a series of quadrilaterals.
        +   *   beginShape(QUADS);
        +   *
        +   *   // Left rectangle.
        +   *   vertex(30, 20);
        +   *   vertex(30, 75);
        +   *   vertex(50, 75);
        +   *   vertex(50, 20);
        +   *
        +   *   // Right rectangle.
        +   *   vertex(65, 20);
        +   *   vertex(65, 75);
        +   *   vertex(85, 75);
        +   *   vertex(85, 20);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('Two white rectangles drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   // Draw a series of quadrilaterals.
        +   *   beginShape(QUAD_STRIP);
        +   *
        +   *   // Add vertices.
        +   *   vertex(30, 20);
        +   *   vertex(30, 75);
        +   *   vertex(50, 20);
        +   *   vertex(50, 75);
        +   *   vertex(65, 20);
        +   *   vertex(65, 75);
        +   *   vertex(85, 20);
        +   *   vertex(85, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('Three white rectangles that share edges are drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   // Draw a series of quadrilaterals.
        +   *   beginShape(PATH);
        +   *
        +   *   // Add the vertices.
        +   *   vertex(-30, -30, 0);
        +   *   vertex(30, -30, 0);
        +   *   vertex(30, -10, 0);
        +   *   vertex(-10, -10, 0);
        +   *   vertex(-10, 10, 0);
        +   *   vertex(30, 10, 0);
        +   *   vertex(30, 30, 0);
        +   *   vertex(-30, 30, 0);
        +   *
        +   *   // Stop drawing the shape.
        +   *   // Connect the first and last vertices.
        +   *   endShape(CLOSE);
        +   *
        +   *   describe('A blocky C shape drawn in white on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag with the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A blocky C shape drawn in red, blue, and green on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Start drawing the shape.
        +   *   // Draw a series of quadrilaterals.
        +   *   beginShape(PATH);
        +   *
        +   *   // Add the vertices.
        +   *   fill('red');
        +   *   stroke('red');
        +   *   vertex(-30, -30, 0);
        +   *   vertex(30, -30, 0);
        +   *   vertex(30, -10, 0);
        +   *   fill('green');
        +   *   stroke('green');
        +   *   vertex(-10, -10, 0);
        +   *   vertex(-10, 10, 0);
        +   *   vertex(30, 10, 0);
        +   *   fill('blue');
        +   *   stroke('blue');
        +   *   vertex(30, 30, 0);
        +   *   vertex(-30, 30, 0);
        +   *
        +   *   // Stop drawing the shape.
        +   *   // Connect the first and last vertices.
        +   *   endShape(CLOSE);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.beginShape = function(kind) {
        +    // p5._validateParameters('beginShape', arguments);
        +    this._renderer.beginShape(...arguments);
        +  };
        +
        +  /**
        +   * Adds a Bézier curve segment to a custom shape.
        +   *
        +   * `bezierVertex()` adds a curved segment to custom shapes. The Bézier curves
        +   * it creates are defined like those made by the
        +   * <a href="#/p5/bezier">bezier()</a> function. `bezierVertex()` must be
        +   * called between the
        +   * <a href="#/p5/beginShape">beginShape()</a> and
        +   * <a href="#/p5/endShape">endShape()</a> functions. The curved segment uses
        +   * the previous vertex as the first anchor point, so there must be at least
        +   * one call to <a href="#/p5/vertex">vertex()</a> before `bezierVertex()` can
        +   * be used.
        +   *
        +   * The first four parameters, `x2`, `y2`, `x3`, and `y3`, set the curve’s two
        +   * control points. The control points "pull" the curve towards them.
        +   *
        +   * The fifth and sixth parameters, `x4`, and `y4`, set the last anchor point.
        +   * The last anchor point is where the curve ends.
        +   *
        +   * Bézier curves can also be drawn in 3D using WebGL mode. The 3D version of
        +   * `bezierVertex()` has eight arguments because each point has x-, y-, and
        +   * z-coordinates.
        +   *
        +   * Note: `bezierVertex()` won’t work when an argument is passed to
        +   * <a href="#/p5/beginShape">beginShape()</a>.
        +   *
        +   * @method bezierVertex
        +   * @param  {Number} x2 x-coordinate of the first control point.
        +   * @param  {Number} y2 y-coordinate of the first control point.
        +   * @param  {Number} x3 x-coordinate of the second control point.
        +   * @param  {Number} y3 y-coordinate of the second control point.
        +   * @param  {Number} x4 x-coordinate of the anchor point.
        +   * @param  {Number} y4 y-coordinate of the anchor point.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the shape.
        +   *   noFill();
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add the first anchor point.
        +   *   vertex(30, 20);
        +   *
        +   *   // Add the Bézier vertex.
        +   *   bezierVertex(80, 0, 80, 75, 30, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('A black C curve on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   stroke(0);
        +   *   strokeWeight(5);
        +   *   point(30, 20);
        +   *   point(30, 75);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(80, 0);
        +   *   point(80, 75);
        +   *
        +   *   // Style the shape.
        +   *   noFill();
        +   *   stroke(0);
        +   *   strokeWeight(1);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add the first anchor point.
        +   *   vertex(30, 20);
        +   *
        +   *   // Add the Bézier vertex.
        +   *   bezierVertex(80, 0, 80, 75, 30, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   // Draw red lines from the anchor points to the control points.
        +   *   stroke(255, 0, 0);
        +   *   line(30, 20, 80, 0);
        +   *   line(30, 75, 80, 75);
        +   *
        +   *   describe(
        +   *     'A gray square with three curves. A black curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click the mouse near the red dot in the top-right corner
        +   * // and drag to change the curve's shape.
        +   *
        +   * let x2 = 80;
        +   * let y2 = 0;
        +   * let isChanging = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A gray square with three curves. A black curve has two straight, red lines that extend from its ends. The endpoints of all the curves are marked with dots.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   stroke(0);
        +   *   strokeWeight(5);
        +   *   point(30, 20);
        +   *   point(30, 75);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(x2, y2);
        +   *   point(80, 75);
        +   *
        +   *   // Style the shape.
        +   *   noFill();
        +   *   stroke(0);
        +   *   strokeWeight(1);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add the first anchor point.
        +   *   vertex(30, 20);
        +   *
        +   *   // Add the Bézier vertex.
        +   *   bezierVertex(x2, y2, 80, 75, 30, 75);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   // Draw red lines from the anchor points to the control points.
        +   *   stroke(255, 0, 0);
        +   *   line(30, 20, x2, y2);
        +   *   line(30, 75, 80, 75);
        +   * }
        +   *
        +   * // Start changing the first control point if the user clicks near it.
        +   * function mousePressed() {
        +   *   if (dist(mouseX, mouseY, x2, y2) < 20) {
        +   *     isChanging = true;
        +   *   }
        +   * }
        +   *
        +   * // Stop changing the first control point when the user releases the mouse.
        +   * function mouseReleased() {
        +   *   isChanging = false;
        +   * }
        +   *
        +   * // Update the first control point while the user drags the mouse.
        +   * function mouseDragged() {
        +   *   if (isChanging === true) {
        +   *     x2 = mouseX;
        +   *     y2 = mouseY;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add the first anchor point.
        +   *   vertex(30, 20);
        +   *
        +   *   // Add the Bézier vertices.
        +   *   bezierVertex(80, 0, 80, 75, 30, 75);
        +   *   bezierVertex(50, 80, 60, 25, 30, 20);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('A crescent moon shape drawn in white on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A crescent moon shape drawn in white on a blue background. When the user drags the mouse, the scene rotates and a second moon is revealed.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background('midnightblue');
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the moons.
        +   *   noStroke();
        +   *   fill('lemonchiffon');
        +   *
        +   *   // Draw the first moon.
        +   *   beginShape();
        +   *   vertex(-20, -30, 0);
        +   *   bezierVertex(30, -50, 0, 30, 25, 0, -20, 25, 0);
        +   *   bezierVertex(0, 30, 0, 10, -25, 0, -20, -30, 0);
        +   *   endShape();
        +   *
        +   *   // Draw the second moon.
        +   *   beginShape();
        +   *   vertex(-20, -30, -20);
        +   *   bezierVertex(30, -50, -20, 30, 25, -20, -20, 25, -20);
        +   *   bezierVertex(0, 30, -20, 10, -25, -20, -20, -30, -20);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method bezierVertex
        +   * @param  {Number} x2
        +   * @param  {Number} y2
        +   * @param  {Number} z2 z-coordinate of the first control point.
        +   * @param  {Number} x3
        +   * @param  {Number} y3
        +   * @param  {Number} z3 z-coordinate of the second control point.
        +   * @param  {Number} x4
        +   * @param  {Number} y4
        +   * @param  {Number} z4 z-coordinate of the anchor point.
        +   */
        +  fn.bezierVertex = function(...args) {
        +    if (args.length === 2 * 3 || args.length === 3 * 3) {
        +      // Handle the legacy case where all bezier control points are provided
        +      // at once. We'll translate them into 3 individual calls.
        +      const stride = args.length / 3;
        +
        +      const prevOrder = this._renderer.bezierOrder();
        +      this._renderer.bezierOrder(3);
        +      for (let i = 0; i < args.length; i += stride) {
        +        this._renderer.bezierVertex(...args.slice(i, i + stride));
        +      }
        +      this._renderer.bezierOrder(prevOrder);
        +    } else {
        +      this._renderer.bezierVertex(...args);
        +    }
        +  };
        +
        +  /**
        +   * Adds a spline curve segment to a custom shape.
        +   *
        +   * `splineVertex()` adds a curved segment to custom shapes. The spline curves
        +   * it creates are defined like those made by the
        +   * <a href="#/p5/curve">curve()</a> function. `splineVertex()` must be called
        +   * between the <a href="#/p5/beginShape">beginShape()</a> and
        +   * <a href="#/p5/endShape">endShape()</a> functions.
        +   *
        +   * Spline curves can form shapes and curves that slope gently. They’re like
        +   * cables that are attached to a set of points. Splines are defined by two
        +   * anchor points and two control points. `splineVertex()` must be called at
        +   * least four times between
        +   * <a href="#/p5/beginShape">beginShape()</a> and
        +   * <a href="#/p5/endShape">endShape()</a> in order to draw a curve:
        +   *
        +   * ```js
        +   * beginShape();
        +   *
        +   * // Add the first control point.
        +   * splineVertex(84, 91);
        +   *
        +   * // Add the anchor points to draw between.
        +   * splineVertex(68, 19);
        +   * splineVertex(21, 17);
        +   *
        +   * // Add the second control point.
        +   * splineVertex(32, 91);
        +   *
        +   * endShape();
        +   * ```
        +   *
        +   * The code snippet above would only draw the curve between the anchor points,
        +   * similar to the <a href="#/p5/curve">curve()</a> function. The segments
        +   * between the control and anchor points can be drawn by calling
        +   * `splineVertex()` with the coordinates of the control points:
        +   *
        +   * ```js
        +   * beginShape();
        +   *
        +   * // Add the first control point and draw a segment to it.
        +   * splineVertex(84, 91);
        +   * splineVertex(84, 91);
        +   *
        +   * // Add the anchor points to draw between.
        +   * splineVertex(68, 19);
        +   * splineVertex(21, 17);
        +   *
        +   * // Add the second control point.
        +   * splineVertex(32, 91);
        +   *
        +   * // Uncomment the next line to draw the segment to the second control point.
        +   * // splineVertex(32, 91);
        +   *
        +   * endShape();
        +   * ```
        +   *
        +   * The first two parameters, `x` and `y`, set the vertex’s location. For
        +   * example, calling `splineVertex(10, 10)` adds a point to the curve at
        +   * `(10, 10)`.
        +   *
        +   * Spline curves can also be drawn in 3D using WebGL mode. The 3D version of
        +   * `splineVertex()` has three arguments because each point has x-, y-, and
        +   * z-coordinates. By default, the vertex’s z-coordinate is set to 0.
        +   *
        +   * Note: `splineVertex()` won’t work when an argument is passed to
        +   * <a href="#/p5/beginShape">beginShape()</a>.
        +   *
        +   * @method curveVertex
        +   * @param {Number} x x-coordinate of the vertex
        +   * @param {Number} y y-coordinate of the vertex
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the shape.
        +   *   noFill();
        +   *   strokeWeight(1);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add the first control point.
        +   *   splineVertex(32, 91);
        +   *
        +   *   // Add the anchor points.
        +   *   splineVertex(21, 17);
        +   *   splineVertex(68, 19);
        +   *
        +   *   // Add the second control point.
        +   *   splineVertex(84, 91);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   // Style the anchor and control points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   stroke(0);
        +   *   point(21, 17);
        +   *   point(68, 19);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(32, 91);
        +   *   point(84, 91);
        +   *
        +   *   describe(
        +   *     'A black curve drawn on a gray background. The curve has black dots at its ends. Two red dots appear near the bottom of the canvas.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the shape.
        +   *   noFill();
        +   *   strokeWeight(1);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add the first control point and draw a segment to it.
        +   *   splineVertex(32, 91);
        +   *   splineVertex(32, 91);
        +   *
        +   *   // Add the anchor points.
        +   *   splineVertex(21, 17);
        +   *   splineVertex(68, 19);
        +   *
        +   *   // Add the second control point.
        +   *   splineVertex(84, 91);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   // Style the anchor and control points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   stroke(0);
        +   *   point(21, 17);
        +   *   point(68, 19);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(32, 91);
        +   *   point(84, 91);
        +   *
        +   *   describe(
        +   *     'A black curve drawn on a gray background. The curve passes through one red dot and two black dots. Another red dot appears near the bottom of the canvas.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the shape.
        +   *   noFill();
        +   *   strokeWeight(1);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add the first control point and draw a segment to it.
        +   *   splineVertex(32, 91);
        +   *   splineVertex(32, 91);
        +   *
        +   *   // Add the anchor points.
        +   *   splineVertex(21, 17);
        +   *   splineVertex(68, 19);
        +   *
        +   *   // Add the second control point and draw a segment to it.
        +   *   splineVertex(84, 91);
        +   *   splineVertex(84, 91);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   // Style the anchor and control points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   stroke(0);
        +   *   point(21, 17);
        +   *   point(68, 19);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(32, 91);
        +   *   point(84, 91);
        +   *
        +   *   describe(
        +   *     'A black U curve drawn upside down on a gray background. The curve passes from one red dot through two black dots and ends at another red dot.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click the mouse near the red dot in the bottom-left corner
        +   * // and drag to change the curve's shape.
        +   *
        +   * let x1 = 32;
        +   * let y1 = 91;
        +   * let isChanging = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe(
        +   *     'A black U curve drawn upside down on a gray background. The curve passes from one red dot through two black dots and ends at another red dot.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the shape.
        +   *   noFill();
        +   *   stroke(0);
        +   *   strokeWeight(1);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add the first control point and draw a segment to it.
        +   *   splineVertex(x1, y1);
        +   *   splineVertex(x1, y1);
        +   *
        +   *   // Add the anchor points.
        +   *   splineVertex(21, 17);
        +   *   splineVertex(68, 19);
        +   *
        +   *   // Add the second control point and draw a segment to it.
        +   *   splineVertex(84, 91);
        +   *   splineVertex(84, 91);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   // Style the anchor and control points.
        +   *   strokeWeight(5);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   stroke(0);
        +   *   point(21, 17);
        +   *   point(68, 19);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(x1, y1);
        +   *   point(84, 91);
        +   * }
        +   *
        +   * // Start changing the first control point if the user clicks near it.
        +   * function mousePressed() {
        +   *   if (dist(mouseX, mouseY, x1, y1) < 20) {
        +   *     isChanging = true;
        +   *   }
        +   * }
        +   *
        +   * // Stop changing the first control point when the user releases the mouse.
        +   * function mouseReleased() {
        +   *   isChanging = false;
        +   * }
        +   *
        +   * // Update the first control point while the user drags the mouse.
        +   * function mouseDragged() {
        +   *   if (isChanging === true) {
        +   *     x1 = mouseX;
        +   *     y1 = mouseY;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add the first control point and draw a segment to it.
        +   *   splineVertex(32, 91);
        +   *   splineVertex(32, 91);
        +   *
        +   *   // Add the anchor points.
        +   *   splineVertex(21, 17);
        +   *   splineVertex(68, 19);
        +   *
        +   *   // Add the second control point.
        +   *   splineVertex(84, 91);
        +   *   splineVertex(84, 91);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('A ghost shape drawn in white on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method curveVertex
        +   * @param {Number} x
        +   * @param {Number} y
        +   * @param {Number} [z] z-coordinate of the vertex.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A ghost shape drawn in white on a blue background. When the user drags the mouse, the scene rotates to reveal the outline of a second ghost.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background('midnightblue');
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the first ghost.
        +   *   noStroke();
        +   *   fill('ghostwhite');
        +   *
        +   *   beginShape();
        +   *   splineVertex(-28, 41, 0);
        +   *   splineVertex(-28, 41, 0);
        +   *   splineVertex(-29, -33, 0);
        +   *   splineVertex(18, -31, 0);
        +   *   splineVertex(34, 41, 0);
        +   *   splineVertex(34, 41, 0);
        +   *   endShape();
        +   *
        +   *   // Draw the second ghost.
        +   *   noFill();
        +   *   stroke('ghostwhite');
        +   *
        +   *   beginShape();
        +   *   splineVertex(-28, 41, -20);
        +   *   splineVertex(-28, 41, -20);
        +   *   splineVertex(-29, -33, -20);
        +   *   splineVertex(18, -31, -20);
        +   *   splineVertex(34, 41, -20);
        +   *   splineVertex(34, 41, -20);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.curveVertex = function(...args) {
        +    // p5._validateParameters('curveVertex', args);
        +    this._renderer.splineVertex(...args);
        +    return this;
        +  };
        +
        +  /**
        +   * Begins adding vertices to a custom shape.
        +   *
        +   * The <a href="#/p5/beginShape">beginShape()</a> and `endShape()` functions
        +   * allow for creating custom shapes in 2D or 3D.
        +   * <a href="#/p5/beginShape">beginShape()</a> begins adding vertices to a
        +   * custom shape and `endShape()` stops adding them.
        +   *
        +   * The first parameter, `mode`, is optional. By default, the first and last
        +   * vertices of a shape aren't connected. If the constant `CLOSE` is passed, as
        +   * in `endShape(CLOSE)`, then the first and last vertices will be connected.
        +   *
        +   * The second parameter, `count`, is also optional. In WebGL mode, it’s more
        +   * efficient to draw many copies of the same shape using a technique called
        +   * <a href="https://webglfundamentals.org/webgl/lessons/webgl-instanced-drawing.html" target="_blank">instancing</a>.
        +   * The `count` parameter tells WebGL mode how many copies to draw. For
        +   * example, calling `endShape(CLOSE, 400)` after drawing a custom shape will
        +   * make it efficient to draw 400 copies. This feature requires
        +   * <a href="https://p5js.org/tutorials/intro-to-shaders/" target="_blank">writing a custom shader</a>.
        +   *
        +   * After calling <a href="#/p5/beginShape">beginShape()</a>, shapes can be
        +   * built by calling <a href="#/p5/vertex">vertex()</a>,
        +   * <a href="#/p5/bezierVertex">bezierVertex()</a>,
        +   * <a href="#/p5/quadraticVertex">quadraticVertex()</a>, and/or
        +   * <a href="#/p5/curveVertex">splineVertex()</a>. Calling
        +   * `endShape()` will stop adding vertices to the
        +   * shape. Each shape will be outlined with the current stroke color and filled
        +   * with the current fill color.
        +   *
        +   * Transformations such as <a href="#/p5/translate">translate()</a>,
        +   * <a href="#/p5/rotate">rotate()</a>, and
        +   * <a href="#/p5/scale">scale()</a> don't work between
        +   * <a href="#/p5/beginShape">beginShape()</a> and `endShape()`. It's also not
        +   * possible to use other shapes, such as <a href="#/p5/ellipse">ellipse()</a> or
        +   * <a href="#/p5/rect">rect()</a>, between
        +   * <a href="#/p5/beginShape">beginShape()</a> and `endShape()`.
        +   *
        +   * @method endShape
        +   * @param  {CLOSE} [mode] use CLOSE to close the shape
        +   * @param  {Integer} [count] number of times you want to draw/instance the shape (for WebGL mode).
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the shapes.
        +   *   noFill();
        +   *
        +   *   // Left triangle.
        +   *   beginShape();
        +   *   vertex(20, 20);
        +   *   vertex(45, 20);
        +   *   vertex(45, 80);
        +   *   endShape(CLOSE);
        +   *
        +   *   // Right triangle.
        +   *   beginShape();
        +   *   vertex(50, 20);
        +   *   vertex(75, 20);
        +   *   vertex(75, 80);
        +   *   endShape();
        +   *
        +   *   describe(
        +   *     'Two sets of black lines drawn on a gray background. The three lines on the left form a right triangle. The two lines on the right form a right angle.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Note: A "uniform" is a global variable within a shader program.
        +   *
        +   * // Create a string with the vertex shader program.
        +   * // The vertex shader is called for each vertex.
        +   * let vertSrc = `#version 300 es
        +   *
        +   * precision mediump float;
        +   *
        +   * in vec3 aPosition;
        +   * flat out int instanceID;
        +   *
        +   * uniform mat4 uModelViewMatrix;
        +   * uniform mat4 uProjectionMatrix;
        +   *
        +   * void main() {
        +   *
        +   *   // Copy the instance ID to the fragment shader.
        +   *   instanceID = gl_InstanceID;
        +   *   vec4 positionVec4 = vec4(aPosition, 1.0);
        +   *
        +   *   // gl_InstanceID represents a numeric value for each instance.
        +   *   // Using gl_InstanceID allows us to move each instance separately.
        +   *   // Here we move each instance horizontally by ID * 23.
        +   *   float xOffset = float(gl_InstanceID) * 23.0;
        +   *
        +   *   // Apply the offset to the final position.
        +   *   gl_Position = uProjectionMatrix * uModelViewMatrix * (positionVec4 -
        +   *     vec4(xOffset, 0.0, 0.0, 0.0));
        +   * }
        +   * `;
        +   *
        +   * // Create a string with the fragment shader program.
        +   * // The fragment shader is called for each pixel.
        +   * let fragSrc = `#version 300 es
        +   *
        +   * precision mediump float;
        +   *
        +   * out vec4 outColor;
        +   * flat in int instanceID;
        +   * uniform float numInstances;
        +   *
        +   * void main() {
        +   *   vec4 red = vec4(1.0, 0.0, 0.0, 1.0);
        +   *   vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);
        +   *
        +   *   // Normalize the instance ID.
        +   *   float normId = float(instanceID) / numInstances;
        +   *
        +   *   // Mix between two colors using the normalized instance ID.
        +   *   outColor = mix(red, blue, normId);
        +   * }
        +   * `;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Shader object.
        +   *   let myShader = createShader(vertSrc, fragSrc);
        +   *
        +   *   background(220);
        +   *
        +   *   // Compile and apply the p5.Shader.
        +   *   shader(myShader);
        +   *
        +   *   // Set the numInstances uniform.
        +   *   myShader.setUniform('numInstances', 4);
        +   *
        +   *   // Translate the origin to help align the drawing.
        +   *   translate(25, -10);
        +   *
        +   *   // Style the shapes.
        +   *   noStroke();
        +   *
        +   *   // Draw the shapes.
        +   *   beginShape();
        +   *   vertex(0, 0);
        +   *   vertex(0, 20);
        +   *   vertex(20, 20);
        +   *   vertex(20, 0);
        +   *   vertex(0, 0);
        +   *   endShape(CLOSE, 4);
        +   *
        +   *   describe('A row of four squares. Their colors transition from purple on the left to red on the right');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.endShape = function(mode, count = 1) {
        +    // p5._validateParameters('endShape', arguments);
        +    if (count < 1) {
        +      console.log('🌸 p5.js says: You can not have less than one instance');
        +      count = 1;
        +    }
        +
        +    this._renderer.endShape(mode, count);
        +  };
        +
        +  /**
        +   * Adds a quadratic Bézier curve segment to a custom shape.
        +   *
        +   * `quadraticVertex()` adds a curved segment to custom shapes. The Bézier
        +   * curve segments it creates are similar to those made by the
        +   * <a href="#/p5/bezierVertex">bezierVertex()</a> function.
        +   * `quadraticVertex()` must be called between the
        +   * <a href="#/p5/beginShape">beginShape()</a> and
        +   * <a href="#/p5/endShape">endShape()</a> functions. The curved segment uses
        +   * the previous vertex as the first anchor point, so there must be at least
        +   * one call to <a href="#/p5/vertex">vertex()</a> before `quadraticVertex()` can
        +   * be used.
        +   *
        +   * The first two parameters, `cx` and `cy`, set the curve’s control point.
        +   * The control point "pulls" the curve towards its.
        +   *
        +   * The last two parameters, `x3`, and `y3`, set the last anchor point. The
        +   * last anchor point is where the curve ends.
        +   *
        +   * Bézier curves can also be drawn in 3D using WebGL mode. The 3D version of
        +   * `bezierVertex()` has eight arguments because each point has x-, y-, and
        +   * z-coordinates.
        +   *
        +   * Note: `quadraticVertex()` won’t work when an argument is passed to
        +   * <a href="#/p5/beginShape">beginShape()</a>.
        +   *
        +   * @method quadraticVertex
        +   * @param  {Number} cx x-coordinate of the control point.
        +   * @param  {Number} cy y-coordinate of the control point.
        +   * @param  {Number} x3 x-coordinate of the anchor point.
        +   * @param  {Number} y3 y-coordinate of the anchor point.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the curve.
        +   *   noFill();
        +   *
        +   *   // Draw the curve.
        +   *   beginShape();
        +   *   vertex(20, 20);
        +   *   quadraticVertex(80, 20, 50, 50);
        +   *   endShape();
        +   *
        +   *   describe('A black curve drawn on a gray square. The curve starts at the top-left corner and ends at the center.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw the curve.
        +   *   noFill();
        +   *   beginShape();
        +   *   vertex(20, 20);
        +   *   quadraticVertex(80, 20, 50, 50);
        +   *   endShape();
        +   *
        +   *   // Draw red lines from the anchor points to the control point.
        +   *   stroke(255, 0, 0);
        +   *   line(20, 20, 80, 20);
        +   *   line(50, 50, 80, 20);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   strokeWeight(5);
        +   *   stroke(0);
        +   *   point(20, 20);
        +   *   point(50, 50);
        +   *
        +   *   // Draw the control point in red.
        +   *   stroke(255, 0, 0);
        +   *   point(80, 20);
        +   *
        +   *   describe('A black curve that starts at the top-left corner and ends at the center. Its anchor and control points are marked with dots. Red lines connect both anchor points to the control point.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click the mouse near the red dot in the top-right corner
        +   * // and drag to change the curve's shape.
        +   *
        +   * let x2 = 80;
        +   * let y2 = 20;
        +   * let isChanging = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A black curve that starts at the top-left corner and ends at the center. Its anchor and control points are marked with dots. Red lines connect both anchor points to the control point.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the curve.
        +   *   noFill();
        +   *   strokeWeight(1);
        +   *   stroke(0);
        +   *
        +   *   // Draw the curve.
        +   *   beginShape();
        +   *   vertex(20, 20);
        +   *   quadraticVertex(x2, y2, 50, 50);
        +   *   endShape();
        +   *
        +   *   // Draw red lines from the anchor points to the control point.
        +   *   stroke(255, 0, 0);
        +   *   line(20, 20, x2, y2);
        +   *   line(50, 50, x2, y2);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   strokeWeight(5);
        +   *   stroke(0);
        +   *   point(20, 20);
        +   *   point(50, 50);
        +   *
        +   *   // Draw the control point in red.
        +   *   stroke(255, 0, 0);
        +   *   point(x2, y2);
        +   * }
        +   *
        +   * // Start changing the first control point if the user clicks near it.
        +   * function mousePressed() {
        +   *   if (dist(mouseX, mouseY, x2, y2) < 20) {
        +   *     isChanging = true;
        +   *   }
        +   * }
        +   *
        +   * // Stop changing the first control point when the user releases the mouse.
        +   * function mouseReleased() {
        +   *   isChanging = false;
        +   * }
        +   *
        +   * // Update the first control point while the user drags the mouse.
        +   * function mouseDragged() {
        +   *   if (isChanging === true) {
        +   *     x2 = mouseX;
        +   *     y2 = mouseY;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Start drawing the shape.
        +   *   beginShape();
        +   *
        +   *   // Add the curved segments.
        +   *   vertex(20, 20);
        +   *   quadraticVertex(80, 20, 50, 50);
        +   *   quadraticVertex(20, 80, 80, 80);
        +   *
        +   *   // Add the straight segments.
        +   *   vertex(80, 10);
        +   *   vertex(20, 10);
        +   *   vertex(20, 20);
        +   *
        +   *   // Stop drawing the shape.
        +   *   endShape();
        +   *
        +   *   describe('A white puzzle piece drawn on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click the and drag the mouse to view the scene from a different angle.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white puzzle piece on a dark gray background. When the user clicks and drags the scene, the outline of a second puzzle piece is revealed.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the first puzzle piece.
        +   *   noStroke();
        +   *   fill(255);
        +   *
        +   *   // Draw the first puzzle piece.
        +   *   beginShape();
        +   *   vertex(-30, -30, 0);
        +   *   quadraticVertex(30, -30, 0, 0, 0, 0);
        +   *   quadraticVertex(-30, 30, 0, 30, 30, 0);
        +   *   vertex(30, -40, 0);
        +   *   vertex(-30, -40, 0);
        +   *   vertex(-30, -30, 0);
        +   *   endShape();
        +   *
        +   *   // Style the second puzzle piece.
        +   *   stroke(255);
        +   *   noFill();
        +   *
        +   *   // Draw the second puzzle piece.
        +   *   beginShape();
        +   *   vertex(-30, -30, -20);
        +   *   quadraticVertex(30, -30, -20, 0, 0, -20);
        +   *   quadraticVertex(-30, 30, -20, 30, 30, -20);
        +   *   vertex(30, -40, -20);
        +   *   vertex(-30, -40, -20);
        +   *   vertex(-30, -30, -20);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method quadraticVertex
        +   * @param  {Number} cx
        +   * @param  {Number} cy
        +   * @param  {Number} cz z-coordinate of the control point.
        +   * @param  {Number} x3
        +   * @param  {Number} y3
        +   * @param  {Number} z3 z-coordinate of the anchor point.
        +   */
        +  fn.quadraticVertex = function(...args) {
        +    let x1, y1, z1, x2, y2, z2 = 0;
        +    if (args.length === 4) {
        +      [x1, y1, x2, y2] = args;
        +    } else {
        +      [x1, y1, z1, x2, y2, z2] = args;
        +    }
        +    // p5._validateParameters('quadraticVertex', args);
        +    const prevOrder = this.bezierOrder();
        +    this.bezierOrder(2);
        +    this.bezierVertex(x1, y1, z1);
        +    this.bezierVertex(x2, y2, z2);
        +    this.bezierOrder(prevOrder);
        +    return this;
        +  };
        +
        +  /**
        +   * Sets the normal vector for vertices in a custom 3D shape.
        +   *
        +   * 3D shapes created with <a href="#/p5/beginShape">beginShape()</a> and
        +   * <a href="#/p5/endShape">endShape()</a> are made by connecting sets of
        +   * points called vertices. Each vertex added with
        +   * <a href="#/p5/vertex">vertex()</a> has a normal vector that points away
        +   * from it. The normal vector controls how light reflects off the shape.
        +   *
        +   * `normal()` can be called two ways with different parameters to define the
        +   * normal vector's components.
        +   *
        +   * The first way to call `normal()` has three parameters, `x`, `y`, and `z`.
        +   * If `Number`s are passed, as in `normal(1, 2, 3)`, they set the x-, y-, and
        +   * z-components of the normal vector.
        +   *
        +   * The second way to call `normal()` has one parameter, `vector`. If a
        +   * <a href="#/p5.Vector">p5.Vector</a> object is passed, as in
        +   * `normal(myVector)`, its components will be used to set the normal vector.
        +   *
        +   * `normal()` changes the normal vector of vertices added to a custom shape
        +   * with <a href="#/p5/vertex">vertex()</a>. `normal()` must be called between
        +   * the <a href="#/p5/beginShape">beginShape()</a> and
        +   * <a href="#/p5/endShape">endShape()</a> functions, just like
        +   * <a href="#/p5/vertex">vertex()</a>. The normal vector set by calling
        +   * `normal()` will affect all following vertices until `normal()` is called
        +   * again:
        +   *
        +   * ```js
        +   * beginShape();
        +   *
        +   * // Set the vertex normal.
        +   * normal(-0.4, -0.4, 0.8);
        +   *
        +   * // Add a vertex.
        +   * vertex(-30, -30, 0);
        +   *
        +   * // Set the vertex normal.
        +   * normal(0, 0, 1);
        +   *
        +   * // Add vertices.
        +   * vertex(30, -30, 0);
        +   * vertex(30, 30, 0);
        +   *
        +   * // Set the vertex normal.
        +   * normal(0.4, -0.4, 0.8);
        +   *
        +   * // Add a vertex.
        +   * vertex(-30, 30, 0);
        +   *
        +   * endShape();
        +   * ```
        +   *
        +   * @method normal
        +   * @param  {p5.Vector} vector vertex normal as a <a href="#/p5.Vector">p5.Vector</a> object.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click the and drag the mouse to view the scene from a different angle.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'A colorful square on a black background. The square changes color and rotates when the user drags the mouse. Parts of its surface reflect light in different directions.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the shape.
        +   *   normalMaterial();
        +   *   noStroke();
        +   *
        +   *   // Draw the shape.
        +   *   beginShape();
        +   *   vertex(-30, -30, 0);
        +   *   vertex(30, -30, 0);
        +   *   vertex(30, 30, 0);
        +   *   vertex(-30, 30, 0);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click the and drag the mouse to view the scene from a different angle.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'A colorful square on a black background. The square changes color and rotates when the user drags the mouse. Parts of its surface reflect light in different directions.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the shape.
        +   *   normalMaterial();
        +   *   noStroke();
        +   *
        +   *   // Draw the shape.
        +   *   // Use normal() to set vertex normals.
        +   *   beginShape();
        +   *   normal(-0.4, -0.4, 0.8);
        +   *   vertex(-30, -30, 0);
        +   *
        +   *   normal(0, 0, 1);
        +   *   vertex(30, -30, 0);
        +   *   vertex(30, 30, 0);
        +   *
        +   *   normal(0.4, -0.4, 0.8);
        +   *   vertex(-30, 30, 0);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='notest'>
        +   * <code>
        +   * // Click the and drag the mouse to view the scene from a different angle.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'A colorful square on a black background. The square changes color and rotates when the user drags the mouse. Parts of its surface reflect light in different directions.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the shape.
        +   *   normalMaterial();
        +   *   noStroke();
        +   *
        +   *   // Create p5.Vector objects.
        +   *   let n1 = createVector(-0.4, -0.4, 0.8);
        +   *   let n2 = createVector(0, 0, 1);
        +   *   let n3 = createVector(0.4, -0.4, 0.8);
        +   *
        +   *   // Draw the shape.
        +   *   // Use normal() to set vertex normals.
        +   *   beginShape();
        +   *   normal(n1);
        +   *   vertex(-30, -30, 0);
        +   *
        +   *   normal(n2);
        +   *   vertex(30, -30, 0);
        +   *   vertex(30, 30, 0);
        +   *
        +   *   normal(n3);
        +   *   vertex(-30, 30, 0);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method normal
        +   * @param  {Number} x x-component of the vertex normal.
        +   * @param  {Number} y y-component of the vertex normal.
        +   * @param  {Number} z z-component of the vertex normal.
        +   * @chainable
        +   */
        +  fn.normal = function(x, y, z) {
        +    this._assert3d('normal');
        +    // p5._validateParameters('normal', arguments);
        +    this._renderer.normal(...arguments);
        +
        +    return this;
        +  };
        +
        +  /** Sets the shader's vertex property or attribute variables.
        +   *
        +   * An vertex property or vertex attribute is a variable belonging to a vertex in a shader. p5.js provides some
        +   * default properties, such as `aPosition`, `aNormal`, `aVertexColor`, etc. These are
        +   * set using <a href="#/p5/vertex">vertex()</a>, <a href="#/p5/normal">normal()</a>
        +   * and <a href="#/p5/fill">fill()</a> respectively. Custom properties can also
        +   * be defined within <a href="#/p5/beginShape">beginShape()</a> and
        +   * <a href="#/p5/endShape">endShape()</a>.
        +   *
        +   * The first parameter, `propertyName`, is a string with the property's name.
        +   * This is the same variable name which should be declared in the shader, such as
        +   * `in vec3 aProperty`, similar to .`setUniform()`.
        +   *
        +   * The second parameter, `data`, is the value assigned to the shader variable. This
        +   * value will be applied to subsequent vertices created with
        +   * <a href="#/p5/vertex">vertex()</a>. It can be a Number or an array of numbers,
        +   * and in the shader program the type can be declared according to the WebGL
        +   * specification. Common types include `float`, `vec2`, `vec3`, `vec4` or matrices.
        +   *
        +   * See also the <a href="#/p5/vertexProperty">vertexProperty()</a> method on
        +   * <a href="#/p5/Geometry">Geometry</a> objects.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * const vertSrc = `#version 300 es
        +   *  precision mediump float;
        +   *  uniform mat4 uModelViewMatrix;
        +   *  uniform mat4 uProjectionMatrix;
        +   *
        +   *  in vec3 aPosition;
        +   *  in vec2 aOffset;
        +   *
        +   *  void main(){
        +   *    vec4 positionVec4 = vec4(aPosition.xyz, 1.0);
        +   *    positionVec4.xy += aOffset;
        +   *    gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        +   *  }
        +   * `;
        +   *
        +   * const fragSrc = `#version 300 es
        +   *  precision mediump float;
        +   *  out vec4 outColor;
        +   *  void main(){
        +   *    outColor = vec4(0.0, 1.0, 1.0, 1.0);
        +   *  }
        +   * `;
        +   *
        +   * function setup(){
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create and use the custom shader.
        +   *   const myShader = createShader(vertSrc, fragSrc);
        +   *   shader(myShader);
        +   *
        +   *   describe('A wobbly, cyan circle on a gray background.');
        +   * }
        +   *
        +   * function draw(){
        +   *   // Set the styles
        +   *   background(125);
        +   *   noStroke();
        +   *
        +   *   // Draw the circle.
        +   *   beginShape();
        +   *   for (let i = 0; i < 30; i++){
        +   *     const x = 40 * cos(i/30 * TWO_PI);
        +   *     const y = 40 * sin(i/30 * TWO_PI);
        +   *
        +   *     // Apply some noise to the coordinates.
        +   *     const xOff = 10 * noise(x + millis()/1000) - 5;
        +   *     const yOff = 10 * noise(y + millis()/1000) - 5;
        +   *
        +   *     // Apply these noise values to the following vertex.
        +   *     vertexProperty('aOffset', [xOff, yOff]);
        +   *     vertex(x, y);
        +   *   }
        +   *   endShape(CLOSE);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let myShader;
        +   * const cols = 10;
        +   * const rows = 10;
        +   * const cellSize = 6;
        +   *
        +   * const vertSrc = `#version 300 es
        +   *   precision mediump float;
        +   *   uniform mat4 uProjectionMatrix;
        +   *   uniform mat4 uModelViewMatrix;
        +   *
        +   *   in vec3 aPosition;
        +   *   in vec3 aNormal;
        +   *   in vec3 aVertexColor;
        +   *   in float aDistance;
        +   *
        +   *   out vec3 vVertexColor;
        +   *
        +   *   void main(){
        +   *     vec4 positionVec4 = vec4(aPosition, 1.0);
        +   *     positionVec4.xyz += aDistance * aNormal * 2.0;;
        +   *     vVertexColor = aVertexColor;
        +   *     gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        +   *   }
        +   * `;
        +   *
        +   * const fragSrc = `#version 300 es
        +   *   precision mediump float;
        +   *
        +   *   in vec3 vVertexColor;
        +   *   out vec4 outColor;
        +   *
        +   *   void main(){
        +   *     outColor = vec4(vVertexColor, 1.0);
        +   *   }
        +   * `;
        +   *
        +   * function setup(){
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create and apply the custom shader.
        +   *   myShader = createShader(vertSrc, fragSrc);
        +   *   shader(myShader);
        +   *   noStroke();
        +   *   describe('A blue grid, which moves away from the mouse position, on a gray background.');
        +   * }
        +   *
        +   * function draw(){
        +   *   background(200);
        +   *
        +   *   // Draw the grid in the middle of the screen.
        +   *   translate(-cols*cellSize/2, -rows*cellSize/2);
        +   *   beginShape(QUADS);
        +   *   for (let i = 0; i < cols; i++) {
        +   *     for (let j = 0; j < rows; j++) {
        +   *
        +   *       // Calculate the cell position.
        +   *       let x = i * cellSize;
        +   *       let y = j * cellSize;
        +   *
        +   *       fill(j/rows*255, j/cols*255, 255);
        +   *
        +   *       // Calculate the distance from the corner of each cell to the mouse.
        +   *       let distance = dist(x, y, mouseX, mouseY);
        +   *
        +   *       // Send the distance to the shader.
        +   *       vertexProperty('aDistance', min(distance, 100));
        +   *
        +   *       vertex(x, y);
        +   *       vertex(x + cellSize, y);
        +   *       vertex(x + cellSize, y + cellSize);
        +   *       vertex(x, y + cellSize);
        +   *     }
        +   *   }
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @method vertexProperty
        +   * @param {String} attributeName the name of the vertex attribute.
        +   * @param {Number|Number[]} data the data tied to the vertex attribute.
        +   */
        +  fn.vertexProperty = function(attributeName, data){
        +    // this._assert3d('vertexProperty');
        +    // p5._validateParameters('vertexProperty', arguments);
        +    this._renderer.vertexProperty(attributeName, data);
        +  };
        +}
        +
        +export default vertex;
        +
        +if(typeof p5 !== 'undefined'){
        +  vertex(p5, p5.prototype);
        +}
        diff --git a/src/type/index.js b/src/type/index.js
        new file mode 100644
        index 0000000000..576ae5e792
        --- /dev/null
        +++ b/src/type/index.js
        @@ -0,0 +1,9 @@
        +
        +import text2d from './text2d.js';
        +import p5font from './p5.Font.js';
        +
        +export default function(p5){
        +  p5.registerAddon(text2d);
        +  p5.registerAddon(p5font);
        +}
        +
        diff --git a/src/type/lib/Typr.js b/src/type/lib/Typr.js
        new file mode 100644
        index 0000000000..621152d689
        --- /dev/null
        +++ b/src/type/lib/Typr.js
        @@ -0,0 +1,2962 @@
        +
        +
        +var Typr = {};
        +
        +Typr["parse"] = function (buff) {
        +  var bin = Typr["B"];
        +
        +  var readFont = function (data, idx, offset, tmap) {
        +    var T = Typr["T"];
        +    var prsr = {
        +      "cmap": T.cmap,
        +      "head": T.head,
        +      "hhea": T.hhea,
        +      "maxp": T.maxp,
        +      "hmtx": T.hmtx,
        +      "name": T.name,
        +      "OS/2": T.OS2,
        +      "post": T.post,
        +
        +      "loca": T.loca,
        +      "kern": T.kern,
        +      "glyf": T.glyf,
        +
        +      "CFF ": T.CFF,
        +      /*
        +      "GPOS",
        +      "GSUB",
        +      "GDEF",*/
        +      "GSUB": T.GSUB,
        +      "CBLC": T.CBLC,
        +      "CBDT": T.CBDT,
        +
        +      "SVG ": T.SVG,
        +      "COLR": T.colr,
        +      "CPAL": T.cpal,
        +      "sbix": T.sbix,
        +
        +      "fvar": T.fvar,
        +      "gvar": T.gvar,
        +      "avar": T.avar,
        +      "HVAR": T.HVAR
        +      //"VORG",
        +    };
        +    var obj = { "_data": data, "_index": idx, "_offset": offset };
        +
        +    for (var t in prsr) {
        +      var tab = Typr["findTable"](data, t, offset);
        +      if (tab) {
        +        var off = tab[0], tobj = tmap[off];
        +        if (tobj == null) tobj = prsr[t].parseTab(data, off, tab[1], obj);
        +        obj[t] = tmap[off] = tobj;
        +      }
        +    }
        +    return obj;
        +  }
        +
        +  function woffToOtf(data) {
        +    var numTables = bin.readUshort(data, 12);
        +    var totalSize = bin.readUint(data, 16);
        +
        +    var otf = new Uint8Array(totalSize), toff = 12 + numTables * 16;
        +
        +    bin.writeASCII(otf, 0, "OTTO");
        +    bin.writeUshort(otf, 4, numTables);
        +
        +    var off = 44;
        +    for (var i = 0; i < numTables; i++) {
        +      var tag = bin.readASCII(data, off, 4);
        +      var tof = bin.readUint(data, off + 4);
        +      var cLe = bin.readUint(data, off + 8);
        +      var oLe = bin.readUint(data, off + 12);
        +      off += 20;
        +      //console.log(i, ":::", tag,tof,oLe);
        +
        +      var tab = data.slice(tof, tof + cLe);
        +      if (cLe != oLe) tab = pako["inflate"](tab);
        +
        +      var to = 12 + i * 16;
        +      bin.writeASCII(otf, to, tag);
        +      bin.writeUint(otf, to + 8, toff);
        +      bin.writeUint(otf, to + 12, oLe);
        +
        +      otf.set(tab, toff); toff += oLe;
        +    }
        +    //console.log(otf);  
        +    return otf;
        +  }
        +
        +
        +  var data = new Uint8Array(buff);
        +  if (data[0] == 0x77) data = woffToOtf(data);
        +
        +  var tmap = {};
        +  var tag = bin.readASCII(data, 0, 4);
        +  if (tag == "ttcf") {
        +    var offset = 4;
        +    var majV = bin.readUshort(data, offset); offset += 2;
        +    var minV = bin.readUshort(data, offset); offset += 2;
        +    var numF = bin.readUint(data, offset); offset += 4;
        +    var fnts = [];
        +    for (var i = 0; i < numF; i++) {
        +      var foff = bin.readUint(data, offset); offset += 4;
        +      fnts.push(readFont(data, i, foff, tmap));
        +    }
        +    return fnts;
        +  }
        +  var fnt = readFont(data, 0, 0, tmap);  //console.log(fnt);  throw "e";
        +  var fvar = fnt["fvar"];
        +  if (fvar) {
        +    var out = [fnt];
        +    for (var i = 0; i < fvar[1].length; i++) {
        +      var fv = fvar[1][i];
        +      var obj = {}; out.push(obj); for (var p in fnt) obj[p] = fnt[p];
        +      obj["_index"] = i;
        +      var name = obj["name"] = JSON.parse(JSON.stringify(obj["name"]));
        +      name["fontSubfamily"] = fv[0];
        +      if (fv[3] == null) fv[3] = (name["fontFamily"] + "-" + name["fontSubfamily"])["replaceAll"](" ", "");
        +      name["postScriptName"] = fv[3];
        +    }
        +    return out;
        +  }
        +
        +  return [fnt];
        +}
        +
        +
        +Typr["findTable"] = function (data, tab, foff) {
        +  var bin = Typr["B"];
        +  var numTables = bin.readUshort(data, foff + 4);
        +  var offset = foff + 12;
        +  for (var i = 0; i < numTables; i++) {
        +    var tag = bin.readASCII(data, offset, 4);   //console.log(tag);
        +    var checkSum = bin.readUint(data, offset + 4);
        +    var toffset = bin.readUint(data, offset + 8);
        +    var length = bin.readUint(data, offset + 12);
        +    if (tag == tab) return [toffset, length];
        +    offset += 16;
        +  }
        +  return null;
        +}
        +/*
        +Typr["splitBy"] = function(data,tag) {
        +  data = new Uint8Array(data);  console.log(data.slice(0,64));
        +  var bin = Typr["B"];
        +  var ttcf = bin.readASCII(data, 0, 4);  if(ttcf!="ttcf") return {};
        +	
        +  var offset = 8;
        +  var numF = bin.readUint  (data, offset);  offset+=4;
        +  var colls = [], used={};
        +  for(var i=0; i<numF; i++) {
        +    var foff = bin.readUint  (data, offset);  offset+=4;
        +    var toff = Typr["findTable"](data,tag,foff)[0];
        +    if(used[toff]==null) used[toff] = [];
        +    used[toff].push([foff,bin.readUshort(data,foff+4)]);  // font offset, numTables
        +  }
        +  for(var toff in used) {
        +    var offs = used[toff];
        +    var hlen = 12+4*offs.length;
        +    var out = new Uint8Array(hlen);		
        +    for(var i=0; i<8; i++) out[i]=data[i];
        +    bin.writeUint(out,8,offs.length);
        +  	
        +    for(var i=0; i<offs.length; i++) hlen += 12+offs[i][1]*16;
        +  	
        +    var hdrs = [out], tabs = [], hoff=out.length, toff=hlen, noffs={};
        +    for(var i=0; i<offs.length; i++) {
        +      bin.writeUint(out, 12+i*4, hoff);  hoff+=12+offs[i][1]*16;
        +      toff = Typr["_cutFont"](data, offs[i][0], hdrs, tabs, toff, noffs);
        +    }
        +    colls.push(Typr["_joinArrs"](hdrs.concat(tabs)));
        +  }
        +  return colls;
        +}
        +
        +Typr["splitFonts"] = function(data) {
        +  data = new Uint8Array(data);
        +  var bin = Typr["B"];
        +  var ttcf = bin.readASCII(data, 0, 4);  if(ttcf!="ttcf") return {};
        +	
        +  var offset = 8;
        +  var numF = bin.readUint  (data, offset);  offset+=4;
        +  var fnts = [];
        +  for(var i=0; i<numF; i++) {
        +    var foff = bin.readUint  (data, offset);  offset+=4;
        +    fnts.push(Typr._cutFont(data, foff));
        +    break;
        +  }
        +  return fnts;
        +}
        +
        +Typr["_cutFont"] = function(data,foff,hdrs,tabs,toff, noffs) {
        +  var bin = Typr["B"];
        +  var numTables = bin.readUshort(data, foff+4);
        +	
        +  var out = new Uint8Array(12+numTables*16);  hdrs.push(out);
        +  for(var i=0; i<12; i++) out[i]=data[foff+i];  //console.log(out);
        +	
        +  var off = 12;
        +  for(var i=0; i<numTables; i++)
        +  {
        +    var tag      = bin.readASCII(data, foff+off, 4); 
        +    var checkSum = bin.readUint (data, foff+off+ 4);
        +    var toffset  = bin.readUint (data, foff+off+ 8); 
        +    var length   = bin.readUint (data, foff+off+12);
        +  	
        +    while((length&3)!=0) length++;
        +  	
        +    for(var j=0; j<16; j++) out[off+j]=data[foff+off+j];
        +  	
        +    if(noffs[toffset]!=null) bin.writeUint(out,off+8,noffs[toffset]);
        +    else {
        +      noffs[toffset] = toff;
        +      bin.writeUint(out, off+8, toff);  
        +      tabs.push(new Uint8Array(data.buffer, toffset, length));  toff+=length;
        +    }
        +    off+=16;
        +  }
        +  return toff;
        +}
        +Typr["_joinArrs"] = function(tabs) {
        +  var len = 0;
        +  for(var i=0; i<tabs.length; i++) len+=tabs[i].length;
        +  var out = new Uint8Array(len), ooff=0;
        +  for(var i=0; i<tabs.length; i++) {
        +    var tab = tabs[i];
        +    for(var j=0; j<tab.length; j++) out[ooff+j]=tab[j];
        +    ooff+=tab.length;
        +  }
        +  return out;
        +}
        +*/
        +
        +Typr["T"] = {};
        +
        +
        +
        +
        +
        +Typr["B"] = {
        +  readFixed: function (data, o) {
        +    return ((data[o] << 8) | data[o + 1]) + (((data[o + 2] << 8) | data[o + 3]) / (256 * 256 + 4));
        +  },
        +  readF2dot14: function (data, o) {
        +    var num = Typr["B"].readShort(data, o);
        +    return num / 16384;
        +  },
        +  readInt: function (buff, p) {
        +    //if(p>=buff.length) throw "error";
        +    var a = Typr["B"].t.uint8;
        +    a[0] = buff[p + 3];
        +    a[1] = buff[p + 2];
        +    a[2] = buff[p + 1];
        +    a[3] = buff[p];
        +    return Typr["B"].t.int32[0];
        +  },
        +
        +  readInt8: function (buff, p) {
        +    //if(p>=buff.length) throw "error";
        +    var a = Typr["B"].t.uint8;
        +    a[0] = buff[p];
        +    return Typr["B"].t.int8[0];
        +  },
        +  readShort: function (buff, p) {
        +    //if(p>=buff.length) throw "error";
        +    var a = Typr["B"].t.uint16;
        +    a[0] = (buff[p] << 8) | buff[p + 1];
        +    return Typr["B"].t.int16[0];
        +  },
        +  readUshort: function (buff, p) {
        +    //if(p>=buff.length) throw "error";
        +    return (buff[p] << 8) | buff[p + 1];
        +  },
        +  writeUshort: function (buff, p, n) {
        +    buff[p] = (n >> 8) & 255; buff[p + 1] = n & 255;
        +  },
        +  readUshorts: function (buff, p, len) {
        +    var arr = [];
        +    for (var i = 0; i < len; i++) {
        +      var v = Typr["B"].readUshort(buff, p + i * 2);  //if(v==932) console.log(p+i*2);
        +      arr.push(v);
        +    }
        +    return arr;
        +  },
        +  readUint: function (buff, p) {
        +    //if(p>=buff.length) throw "error";
        +    var a = Typr["B"].t.uint8;
        +    a[3] = buff[p]; a[2] = buff[p + 1]; a[1] = buff[p + 2]; a[0] = buff[p + 3];
        +    return Typr["B"].t.uint32[0];
        +  },
        +  writeUint: function (buff, p, n) {
        +    buff[p] = (n >> 24) & 255; buff[p + 1] = (n >> 16) & 255; buff[p + 2] = (n >> 8) & 255; buff[p + 3] = (n >> 0) & 255;
        +  },
        +  readUint64: function (buff, p) {
        +    //if(p>=buff.length) throw "error";
        +    return (Typr["B"].readUint(buff, p) * (0xffffffff + 1)) + Typr["B"].readUint(buff, p + 4);
        +  },
        +  readASCII: function (buff, p, l)	// l : length in Characters (not Bytes)
        +  {
        +    //if(p>=buff.length) throw "error";
        +    var s = "";
        +    for (var i = 0; i < l; i++) s += String.fromCharCode(buff[p + i]);
        +    return s;
        +  },
        +  writeASCII: function (buff, p, s)	// l : length in Characters (not Bytes)
        +  {
        +    for (var i = 0; i < s.length; i++)
        +      buff[p + i] = s.charCodeAt(i);
        +  },
        +  readUnicode: function (buff, p, l) {
        +    //if(p>=buff.length) throw "error";
        +    var s = "";
        +    for (var i = 0; i < l; i++) {
        +      var c = (buff[p++] << 8) | buff[p++];
        +      s += String.fromCharCode(c);
        +    }
        +    return s;
        +  },
        +  _tdec: window["TextDecoder"] ? new window["TextDecoder"]() : null,
        +  readUTF8: function (buff, p, l) {
        +    var tdec = Typr["B"]._tdec;
        +    if (tdec && p == 0 && l == buff.length) return tdec["decode"](buff);
        +    return Typr["B"].readASCII(buff, p, l);
        +  },
        +  readBytes: function (buff, p, l) {
        +    //if(p>=buff.length) throw "error";
        +    var arr = [];
        +    for (var i = 0; i < l; i++) arr.push(buff[p + i]);
        +    return arr;
        +  },
        +  readASCIIArray: function (buff, p, l)	// l : length in Characters (not Bytes)
        +  {
        +    //if(p>=buff.length) throw "error";
        +    var s = [];
        +    for (var i = 0; i < l; i++)
        +      s.push(String.fromCharCode(buff[p + i]));
        +    return s;
        +  },
        +  t: function () {
        +    var ab = new ArrayBuffer(8);
        +    return {
        +      buff: ab,
        +      int8: new Int8Array(ab),
        +      uint8: new Uint8Array(ab),
        +      int16: new Int16Array(ab),
        +      uint16: new Uint16Array(ab),
        +      int32: new Int32Array(ab),
        +      uint32: new Uint32Array(ab)
        +    }
        +  }()
        +};
        +
        +
        +
        +
        +
        +
        +Typr["T"].CFF = {
        +  parseTab: function (data, offset, length) {
        +    var bin = Typr["B"];
        +    var CFF = Typr["T"].CFF;
        +
        +    data = new Uint8Array(data.buffer, offset, length);
        +    offset = 0;
        +
        +    // Header
        +    var major = data[offset]; offset++;
        +    var minor = data[offset]; offset++;
        +    var hdrSize = data[offset]; offset++;
        +    var offsize = data[offset]; offset++;
        +    //console.log(major, minor, hdrSize, offsize);
        +
        +    // Name INDEX
        +    var ninds = [];
        +    offset = CFF.readIndex(data, offset, ninds);
        +    var names = [];
        +
        +    for (var i = 0; i < ninds.length - 1; i++) names.push(bin.readASCII(data, offset + ninds[i], ninds[i + 1] - ninds[i]));
        +    offset += ninds[ninds.length - 1];
        +
        +
        +    // Top DICT INDEX
        +    var tdinds = [];
        +    offset = CFF.readIndex(data, offset, tdinds);  //console.log(tdinds);
        +    // Top DICT Data
        +    var topDicts = [];
        +    for (var i = 0; i < tdinds.length - 1; i++) topDicts.push(CFF.readDict(data, offset + tdinds[i], offset + tdinds[i + 1]));
        +    offset += tdinds[tdinds.length - 1];
        +    var topdict = topDicts[0];
        +    //console.log(topdict);
        +
        +    // String INDEX
        +    var sinds = [];
        +    offset = CFF.readIndex(data, offset, sinds);
        +    // String Data
        +    var strings = [];
        +    for (var i = 0; i < sinds.length - 1; i++) strings.push(bin.readASCII(data, offset + sinds[i], sinds[i + 1] - sinds[i]));
        +    offset += sinds[sinds.length - 1];
        +
        +    // Global Subr INDEX  (subroutines)		
        +    CFF.readSubrs(data, offset, topdict);
        +
        +    // charstrings
        +
        +    if (topdict["CharStrings"]) topdict["CharStrings"] = CFF.readBytes(data, topdict["CharStrings"]);
        +
        +    // CID font
        +    if (topdict["ROS"]) {
        +      offset = topdict["FDArray"];
        +      var fdind = [];
        +      offset = CFF.readIndex(data, offset, fdind);
        +
        +      topdict["FDArray"] = [];
        +      for (var i = 0; i < fdind.length - 1; i++) {
        +        var dict = CFF.readDict(data, offset + fdind[i], offset + fdind[i + 1]);
        +        CFF._readFDict(data, dict, strings);
        +        topdict["FDArray"].push(dict);
        +      }
        +      offset += fdind[fdind.length - 1];
        +
        +      offset = topdict["FDSelect"];
        +      topdict["FDSelect"] = [];
        +      var fmt = data[offset]; offset++;
        +      if (fmt == 3) {
        +        var rns = bin.readUshort(data, offset); offset += 2;
        +        for (var i = 0; i < rns + 1; i++) {
        +          topdict["FDSelect"].push(bin.readUshort(data, offset), data[offset + 2]); offset += 3;
        +        }
        +      }
        +      else throw fmt;
        +    }
        +
        +    // Encoding
        +    //if(topdict["Encoding"]) topdict["Encoding"] = CFF.readEncoding(data, topdict["Encoding"], topdict["CharStrings"].length);
        +
        +    // charset
        +    if (topdict["charset"]) topdict["charset"] = CFF.readCharset(data, topdict["charset"], topdict["CharStrings"].length);
        +
        +    CFF._readFDict(data, topdict, strings);
        +    return topdict;
        +  },
        +
        +  _readFDict: function (data, dict, ss) {
        +    var CFF = Typr["T"].CFF;
        +    var offset;
        +    if (dict["Private"]) {
        +      offset = dict["Private"][1];
        +      dict["Private"] = CFF.readDict(data, offset, offset + dict["Private"][0]);
        +      if (dict["Private"]["Subrs"]) CFF.readSubrs(data, offset + dict["Private"]["Subrs"], dict["Private"]);
        +    }
        +    for (var p in dict) if (["FamilyName", "FontName", "FullName", "Notice", "version", "Copyright"].indexOf(p) != -1) dict[p] = ss[dict[p] - 426 + 35];
        +  },
        +
        +  readSubrs: function (data, offset, obj) {
        +    obj["Subrs"] = Typr["T"].CFF.readBytes(data, offset);
        +
        +    var bias, nSubrs = obj["Subrs"].length + 1;
        +    if (false) bias = 0;
        +    else if (nSubrs < 1240) bias = 107;
        +    else if (nSubrs < 33900) bias = 1131;
        +    else bias = 32768;
        +    obj["Bias"] = bias;
        +  },
        +  readBytes: function (data, offset) {
        +    var bin = Typr["B"];
        +    var arr = [];
        +    offset = Typr["T"].CFF.readIndex(data, offset, arr);
        +
        +    var subrs = [], arl = arr.length - 1, no = data.byteOffset + offset;
        +    for (var i = 0; i < arl; i++) {
        +      var ari = arr[i];
        +      subrs.push(new Uint8Array(data.buffer, no + ari, arr[i + 1] - ari));
        +    }
        +    return subrs;
        +  },
        +
        +  tableSE: [
        +    0, 0, 0, 0, 0, 0, 0, 0,
        +    0, 0, 0, 0, 0, 0, 0, 0,
        +    0, 0, 0, 0, 0, 0, 0, 0,
        +    0, 0, 0, 0, 0, 0, 0, 0,
        +    1, 2, 3, 4, 5, 6, 7, 8,
        +    9, 10, 11, 12, 13, 14, 15, 16,
        +    17, 18, 19, 20, 21, 22, 23, 24,
        +    25, 26, 27, 28, 29, 30, 31, 32,
        +    33, 34, 35, 36, 37, 38, 39, 40,
        +    41, 42, 43, 44, 45, 46, 47, 48,
        +    49, 50, 51, 52, 53, 54, 55, 56,
        +    57, 58, 59, 60, 61, 62, 63, 64,
        +    65, 66, 67, 68, 69, 70, 71, 72,
        +    73, 74, 75, 76, 77, 78, 79, 80,
        +    81, 82, 83, 84, 85, 86, 87, 88,
        +    89, 90, 91, 92, 93, 94, 95, 0,
        +    0, 0, 0, 0, 0, 0, 0, 0,
        +    0, 0, 0, 0, 0, 0, 0, 0,
        +    0, 0, 0, 0, 0, 0, 0, 0,
        +    0, 0, 0, 0, 0, 0, 0, 0,
        +    0, 96, 97, 98, 99, 100, 101, 102,
        +    103, 104, 105, 106, 107, 108, 109, 110,
        +    0, 111, 112, 113, 114, 0, 115, 116,
        +    117, 118, 119, 120, 121, 122, 0, 123,
        +    0, 124, 125, 126, 127, 128, 129, 130,
        +    131, 0, 132, 133, 0, 134, 135, 136,
        +    137, 0, 0, 0, 0, 0, 0, 0,
        +    0, 0, 0, 0, 0, 0, 0, 0,
        +    0, 138, 0, 139, 0, 0, 0, 0,
        +    140, 141, 142, 143, 0, 0, 0, 0,
        +    0, 144, 0, 0, 0, 145, 0, 0,
        +    146, 147, 148, 149, 0, 0, 0, 0
        +  ],
        +
        +  glyphByUnicode: function (cff, code) {
        +    for (var i = 0; i < cff["charset"].length; i++) if (cff["charset"][i] == code) return i;
        +    return -1;
        +  },
        +
        +  glyphBySE: function (cff, charcode)	// glyph by standard encoding
        +  {
        +    if (charcode < 0 || charcode > 255) return -1;
        +    return Typr["T"].CFF.glyphByUnicode(cff, Typr["T"].CFF.tableSE[charcode]);
        +  },
        +
        +  /*readEncoding : function(data, offset, num)
        +  {
        +    var bin = Typr["B"];
        +  	
        +    var array = ['.notdef'];
        +    var format = data[offset];  offset++;
        +    //console.log("Encoding");
        +    //console.log(format);
        +  	
        +    if(format==0)
        +    {
        +      var nCodes = data[offset];  offset++;
        +      for(var i=0; i<nCodes; i++)  array.push(data[offset+i]);
        +    }
        +    /*
        +    else if(format==1 || format==2)
        +    {
        +      while(charset.length<num)
        +      {
        +        var first = bin.readUshort(data, offset);  offset+=2;
        +        var nLeft=0;
        +        if(format==1) {  nLeft = data[offset];  offset++;  }
        +        else          {  nLeft = bin.readUshort(data, offset);  offset+=2;  }
        +        for(var i=0; i<=nLeft; i++)  {  charset.push(first);  first++;  }
        +      }
        +    }
        +  	
        +    else throw "error: unknown encoding format: " + format;
        +  	
        +    return array;
        +  },*/
        +
        +  readCharset: function (data, offset, num) {
        +    var bin = Typr["B"];
        +
        +    var charset = ['.notdef'];
        +    var format = data[offset]; offset++;
        +
        +    if (format == 0) {
        +      for (var i = 0; i < num; i++) {
        +        var first = bin.readUshort(data, offset); offset += 2;
        +        charset.push(first);
        +      }
        +    }
        +    else if (format == 1 || format == 2) {
        +      while (charset.length < num) {
        +        var first = bin.readUshort(data, offset); offset += 2;
        +        var nLeft = 0;
        +        if (format == 1) { nLeft = data[offset]; offset++; }
        +        else { nLeft = bin.readUshort(data, offset); offset += 2; }
        +        for (var i = 0; i <= nLeft; i++) { charset.push(first); first++; }
        +      }
        +    }
        +    else throw "error: format: " + format;
        +
        +    return charset;
        +  },
        +
        +  readIndex: function (data, offset, inds) {
        +    var bin = Typr["B"];
        +
        +    var count = bin.readUshort(data, offset) + 1; offset += 2;
        +    var offsize = data[offset]; offset++;
        +
        +    if (offsize == 1) for (var i = 0; i < count; i++) inds.push(data[offset + i]);
        +    else if (offsize == 2) for (var i = 0; i < count; i++) inds.push(bin.readUshort(data, offset + i * 2));
        +    else if (offsize == 3) for (var i = 0; i < count; i++) inds.push(bin.readUint(data, offset + i * 3 - 1) & 0x00ffffff);
        +    else if (offsize == 4) for (var i = 0; i < count; i++) inds.push(bin.readUint(data, offset + i * 4));
        +    else if (count != 1) throw "unsupported offset size: " + offsize + ", count: " + count;
        +
        +    offset += count * offsize;
        +    return offset - 1;
        +  },
        +
        +  getCharString: function (data, offset, o) {
        +    var bin = Typr["B"];
        +
        +    var b0 = data[offset], b1 = data[offset + 1], b2 = data[offset + 2], b3 = data[offset + 3], b4 = data[offset + 4];
        +    var vs = 1;
        +    var op = null, val = null;
        +    // operand
        +    if (b0 <= 20) { op = b0; vs = 1; }
        +    if (b0 == 12) { op = b0 * 100 + b1; vs = 2; }
        +    //if(b0==19 || b0==20) { op = b0/*+" "+b1*/;  vs=2; }
        +    if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; }
        +    if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; }
        +    if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; }
        +    if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; }
        +    if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; }
        +    if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; }
        +    if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; }
        +
        +    o.val = val != null ? val : "o" + op;
        +    o.size = vs;
        +  },
        +
        +  readCharString: function (data, offset, length) {
        +    var end = offset + length;
        +    var bin = Typr["B"];
        +    var arr = [];
        +
        +    while (offset < end) {
        +      var b0 = data[offset], b1 = data[offset + 1], b2 = data[offset + 2], b3 = data[offset + 3], b4 = data[offset + 4];
        +      var vs = 1;
        +      var op = null, val = null;
        +      // operand
        +      if (b0 <= 20) { op = b0; vs = 1; }
        +      if (b0 == 12) { op = b0 * 100 + b1; vs = 2; }
        +      if (b0 == 19 || b0 == 20) { op = b0/*+" "+b1*/; vs = 2; }
        +      if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; }
        +      if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; }
        +      if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; }
        +      if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; }
        +      if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; }
        +      if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; }
        +      if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; }
        +
        +      arr.push(val != null ? val : "o" + op);
        +      offset += vs;
        +
        +      //var cv = arr[arr.length-1];
        +      //if(cv==undefined) throw "error";
        +      //console.log()
        +    }
        +    return arr;
        +  },
        +
        +  readDict: function (data, offset, end) {
        +    var bin = Typr["B"];
        +    //var dict = [];
        +    var dict = {};
        +    var carr = [];
        +
        +    while (offset < end) {
        +      var b0 = data[offset], b1 = data[offset + 1], b2 = data[offset + 2], b3 = data[offset + 3], b4 = data[offset + 4];
        +      var vs = 1;
        +      var key = null, val = null;
        +      // operand
        +      if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; }
        +      if (b0 == 29) { val = bin.readInt(data, offset + 1); vs = 5; }
        +      if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; }
        +      if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; }
        +      if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; }
        +      if (b0 == 255) { val = bin.readInt(data, offset + 1) / 0xffff; vs = 5; throw "unknown number"; }
        +
        +      if (b0 == 30) {
        +        var nibs = [];
        +        vs = 1;
        +        while (true) {
        +          var b = data[offset + vs]; vs++;
        +          var nib0 = b >> 4, nib1 = b & 0xf;
        +          if (nib0 != 0xf) nibs.push(nib0); if (nib1 != 0xf) nibs.push(nib1);
        +          if (nib1 == 0xf) break;
        +        }
        +        var s = "";
        +        var chars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ".", "e", "e-", "reserved", "-", "endOfNumber"];
        +        for (var i = 0; i < nibs.length; i++) s += chars[nibs[i]];
        +        //console.log(nibs);
        +        val = parseFloat(s);
        +      }
        +
        +      if (b0 <= 21)	// operator
        +      {
        +        var keys = ["version", "Notice", "FullName", "FamilyName", "Weight", "FontBBox", "BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues",
        +          "StdHW", "StdVW", "escape", "UniqueID", "XUID", "charset", "Encoding", "CharStrings", "Private", "Subrs",
        +          "defaultWidthX", "nominalWidthX"];
        +
        +        key = keys[b0]; vs = 1;
        +        if (b0 == 12) {
        +          var keys = ["Copyright", "isFixedPitch", "ItalicAngle", "UnderlinePosition", "UnderlineThickness", "PaintType", "CharstringType", "FontMatrix", "StrokeWidth", "BlueScale",
        +            "BlueShift", "BlueFuzz", "StemSnapH", "StemSnapV", "ForceBold", "", "", "LanguageGroup", "ExpansionFactor", "initialRandomSeed",
        +            "SyntheticBase", "PostScript", "BaseFontName", "BaseFontBlend", "", "", "", "", "", "",
        +            "ROS", "CIDFontVersion", "CIDFontRevision", "CIDFontType", "CIDCount", "UIDBase", "FDArray", "FDSelect", "FontName"];
        +          key = keys[b1]; vs = 2;
        +        }
        +      }
        +
        +      if (key != null) { dict[key] = carr.length == 1 ? carr[0] : carr; carr = []; }
        +      else carr.push(val);
        +
        +      offset += vs;
        +    }
        +    return dict;
        +  }
        +};
        +
        +
        +Typr["T"].cmap = {
        +  parseTab: function (data, offset, length) {
        +    var obj = { tables: [], ids: {}, off: offset };
        +    data = new Uint8Array(data.buffer, offset, length);
        +    offset = 0;
        +
        +    var offset0 = offset;
        +    var bin = Typr["B"], rU = bin.readUshort, cmap = Typr["T"].cmap;
        +    var version = rU(data, offset); offset += 2;
        +    var numTables = rU(data, offset); offset += 2;
        +
        +    //console.log(version, numTables);
        +
        +    var offs = [];
        +
        +
        +    for (var i = 0; i < numTables; i++) {
        +      var platformID = rU(data, offset); offset += 2;
        +      var encodingID = rU(data, offset); offset += 2;
        +      var noffset = bin.readUint(data, offset); offset += 4;
        +
        +      var id = "p" + platformID + "e" + encodingID;
        +
        +      //console.log("cmap subtable", platformID, encodingID, noffset);
        +
        +
        +      var tind = offs.indexOf(noffset);
        +
        +      if (tind == -1) {
        +        tind = obj.tables.length;
        +        var subt = {};
        +        offs.push(noffset);
        +        //var time = Date.now();
        +        var format = subt.format = rU(data, noffset);
        +        if (format == 0) subt = cmap.parse0(data, noffset, subt);
        +        //else if(format== 2) subt.off = noffset;
        +        else if (format == 4) subt = cmap.parse4(data, noffset, subt);
        +        else if (format == 6) subt = cmap.parse6(data, noffset, subt);
        +        else if (format == 12) subt = cmap.parse12(data, noffset, subt);
        +        //console.log(format, Date.now()-time);
        +        //else console.log("unknown format: "+format, platformID, encodingID, noffset);
        +        obj.tables.push(subt);
        +      }
        +
        +      if (obj.ids[id] != null) console.log("multiple tables for one platform+encoding: " + id);
        +      obj.ids[id] = tind;
        +    }
        +    return obj;
        +  },
        +
        +  parse0: function (data, offset, obj) {
        +    var bin = Typr["B"];
        +    offset += 2;
        +    var len = bin.readUshort(data, offset); offset += 2;
        +    var lang = bin.readUshort(data, offset); offset += 2;
        +    obj.map = [];
        +    for (var i = 0; i < len - 6; i++) obj.map.push(data[offset + i]);
        +    return obj;
        +  },
        +
        +  parse4: function (data, offset, obj) {
        +    var bin = Typr["B"], rU = bin.readUshort, rUs = bin.readUshorts;
        +    var offset0 = offset;
        +    offset += 2;
        +    var length = rU(data, offset); offset += 2;
        +    var language = rU(data, offset); offset += 2;
        +    var segCountX2 = rU(data, offset); offset += 2;
        +    var segCount = segCountX2 >>> 1;
        +    obj.searchRange = rU(data, offset); offset += 2;
        +    obj.entrySelector = rU(data, offset); offset += 2;
        +    obj.rangeShift = rU(data, offset); offset += 2;
        +    obj.endCount = rUs(data, offset, segCount); offset += segCount * 2;
        +    offset += 2;
        +    obj.startCount = rUs(data, offset, segCount); offset += segCount * 2;
        +    obj.idDelta = [];
        +    for (var i = 0; i < segCount; i++) { obj.idDelta.push(bin.readShort(data, offset)); offset += 2; }
        +    obj.idRangeOffset = rUs(data, offset, segCount); offset += segCount * 2;
        +    obj.glyphIdArray = rUs(data, offset, ((offset0 + length) - offset) >> 1);  //offset += segCount*2;
        +    return obj;
        +  },
        +
        +  parse6: function (data, offset, obj) {
        +    var bin = Typr["B"];
        +    var offset0 = offset;
        +    offset += 2;
        +    var length = bin.readUshort(data, offset); offset += 2;
        +    var language = bin.readUshort(data, offset); offset += 2;
        +    obj.firstCode = bin.readUshort(data, offset); offset += 2;
        +    var entryCount = bin.readUshort(data, offset); offset += 2;
        +    obj.glyphIdArray = [];
        +    for (var i = 0; i < entryCount; i++) { obj.glyphIdArray.push(bin.readUshort(data, offset)); offset += 2; }
        +
        +    return obj;
        +  },
        +
        +  parse12: function (data, offset, obj) {
        +    var bin = Typr["B"], rU = bin.readUint;
        +    var offset0 = offset;
        +    offset += 4;
        +    var length = rU(data, offset); offset += 4;
        +    var lang = rU(data, offset); offset += 4;
        +    var nGroups = rU(data, offset) * 3; offset += 4;
        +
        +    var gps = obj.groups = new Uint32Array(nGroups);//new Uint32Array(data.slice(offset, offset+nGroups*12).buffer);  
        +
        +    for (var i = 0; i < nGroups; i += 3) {
        +      gps[i] = rU(data, offset + (i << 2));
        +      gps[i + 1] = rU(data, offset + (i << 2) + 4);
        +      gps[i + 2] = rU(data, offset + (i << 2) + 8);
        +    }
        +    return obj;
        +  }
        +};
        +
        +Typr["T"].CBLC = {
        +  parseTab: function (data, offset, length) {
        +    var bin = Typr["B"], ooff = offset;
        +
        +    var maj = bin.readUshort(data, offset); offset += 2;
        +    var min = bin.readUshort(data, offset); offset += 2;
        +
        +    var numSizes = bin.readUint(data, offset); offset += 4;
        +
        +    var out = [];
        +    for (var i = 0; i < numSizes; i++) {
        +      var off = bin.readUint(data, offset); offset += 4;  // indexSubTableArrayOffset
        +      var siz = bin.readUint(data, offset); offset += 4;  // indexTablesSize
        +      var num = bin.readUint(data, offset); offset += 4;  // numberOfIndexSubTables
        +      offset += 4;
        +
        +      offset += 2 * 12;
        +
        +      var sGlyph = bin.readUshort(data, offset); offset += 2;
        +      var eGlyph = bin.readUshort(data, offset); offset += 2;
        +
        +      //console.log(off,siz,num, sGlyph, eGlyph);
        +
        +      offset += 4;
        +
        +      var coff = ooff + off;
        +      for (var j = 0; j < 3; j++) {
        +        var fgI = bin.readUshort(data, coff); coff += 2;
        +        var lgI = bin.readUshort(data, coff); coff += 2;
        +        var nxt = bin.readUint(data, coff); coff += 4;
        +        var gcnt = lgI - fgI + 1;
        +        //console.log(fgI, lgI, nxt);   //if(nxt==0) break;
        +
        +        var ioff = ooff + off + nxt;
        +
        +        var inF = bin.readUshort(data, ioff); ioff += 2; if (inF != 1) throw inF;
        +        var imF = bin.readUshort(data, ioff); ioff += 2;
        +        var imgo = bin.readUint(data, ioff); ioff += 4;
        +
        +        var oarr = [];
        +        for (var gi = 0; gi < gcnt; gi++) {
        +          var sbitO = bin.readUint(data, ioff + gi * 4); oarr.push(imgo + sbitO);
        +          //console.log("--",sbitO);
        +        }
        +        out.push([fgI, lgI, imF, oarr]);
        +      }
        +    }
        +    return out;
        +  }
        +};
        +
        +Typr["T"].CBDT = {
        +  parseTab: function (data, offset, length) {
        +    var bin = Typr["B"];
        +    var ooff = offset;
        +
        +    //var maj = bin.readUshort(data,offset);  offset+=2;
        +    //var min = bin.readUshort(data,offset);  offset+=2;
        +
        +    return new Uint8Array(data.buffer, data.byteOffset + offset, length);
        +  }
        +};
        +
        +Typr["T"].glyf = {
        +  parseTab: function (data, offset, length, font) {
        +    var obj = [], ng = font["maxp"]["numGlyphs"];
        +    for (var g = 0; g < ng; g++) obj.push(null);
        +    return obj;
        +  },
        +
        +  _parseGlyf: function (font, g) {
        +    var bin = Typr["B"];
        +    var data = font["_data"], loca = font["loca"];
        +
        +    if (loca[g] == loca[g + 1]) return null;
        +
        +    var offset = Typr["findTable"](data, "glyf", font["_offset"])[0] + loca[g];
        +
        +    var gl = {};
        +
        +    gl.noc = bin.readShort(data, offset); offset += 2;		// number of contours
        +    gl.xMin = bin.readShort(data, offset); offset += 2;
        +    gl.yMin = bin.readShort(data, offset); offset += 2;
        +    gl.xMax = bin.readShort(data, offset); offset += 2;
        +    gl.yMax = bin.readShort(data, offset); offset += 2;
        +
        +    if (gl.xMin >= gl.xMax || gl.yMin >= gl.yMax) return null;
        +
        +    if (gl.noc > 0) {
        +      gl.endPts = [];
        +      for (var i = 0; i < gl.noc; i++) { gl.endPts.push(bin.readUshort(data, offset)); offset += 2; }
        +
        +      var instructionLength = bin.readUshort(data, offset); offset += 2;
        +      if ((data.length - offset) < instructionLength) return null;
        +      gl.instructions = bin.readBytes(data, offset, instructionLength); offset += instructionLength;
        +
        +      var crdnum = gl.endPts[gl.noc - 1] + 1;
        +      gl.flags = [];
        +      for (var i = 0; i < crdnum; i++) {
        +        var flag = data[offset]; offset++;
        +        gl.flags.push(flag);
        +        if ((flag & 8) != 0) {
        +          var rep = data[offset]; offset++;
        +          for (var j = 0; j < rep; j++) { gl.flags.push(flag); i++; }
        +        }
        +      }
        +      gl.xs = [];
        +      for (var i = 0; i < crdnum; i++) {
        +        var i8 = ((gl.flags[i] & 2) != 0), same = ((gl.flags[i] & 16) != 0);
        +        if (i8) { gl.xs.push(same ? data[offset] : -data[offset]); offset++; }
        +        else {
        +          if (same) gl.xs.push(0);
        +          else { gl.xs.push(bin.readShort(data, offset)); offset += 2; }
        +        }
        +      }
        +      gl.ys = [];
        +      for (var i = 0; i < crdnum; i++) {
        +        var i8 = ((gl.flags[i] & 4) != 0), same = ((gl.flags[i] & 32) != 0);
        +        if (i8) { gl.ys.push(same ? data[offset] : -data[offset]); offset++; }
        +        else {
        +          if (same) gl.ys.push(0);
        +          else { gl.ys.push(bin.readShort(data, offset)); offset += 2; }
        +        }
        +      }
        +      var x = 0, y = 0;
        +      for (var i = 0; i < crdnum; i++) { x += gl.xs[i]; y += gl.ys[i]; gl.xs[i] = x; gl.ys[i] = y; }
        +      //console.log(endPtsOfContours, instructionLength, instructions, flags, xCoordinates, yCoordinates);
        +    }
        +    else {
        +      var ARG_1_AND_2_ARE_WORDS = 1 << 0;
        +      var ARGS_ARE_XY_VALUES = 1 << 1;
        +      var ROUND_XY_TO_GRID = 1 << 2;
        +      var WE_HAVE_A_SCALE = 1 << 3;
        +      var RESERVED = 1 << 4;
        +      var MORE_COMPONENTS = 1 << 5;
        +      var WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
        +      var WE_HAVE_A_TWO_BY_TWO = 1 << 7;
        +      var WE_HAVE_INSTRUCTIONS = 1 << 8;
        +      var USE_MY_METRICS = 1 << 9;
        +      var OVERLAP_COMPOUND = 1 << 10;
        +      var SCALED_COMPONENT_OFFSET = 1 << 11;
        +      var UNSCALED_COMPONENT_OFFSET = 1 << 12;
        +
        +      gl.parts = [];
        +      var flags;
        +      do {
        +        flags = bin.readUshort(data, offset); offset += 2;
        +        var part = { m: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 }, p1: -1, p2: -1 }; gl.parts.push(part);
        +        part.glyphIndex = bin.readUshort(data, offset); offset += 2;
        +        if (flags & ARG_1_AND_2_ARE_WORDS) {
        +          var arg1 = bin.readShort(data, offset); offset += 2;
        +          var arg2 = bin.readShort(data, offset); offset += 2;
        +        } else {
        +          var arg1 = bin.readInt8(data, offset); offset++;
        +          var arg2 = bin.readInt8(data, offset); offset++;
        +        }
        +
        +        if (flags & ARGS_ARE_XY_VALUES) { part.m.tx = arg1; part.m.ty = arg2; }
        +        else { part.p1 = arg1; part.p2 = arg2; }
        +        //part.m.tx = arg1;  part.m.ty = arg2;
        +        //else { throw "params are not XY values"; }
        +
        +        if (flags & WE_HAVE_A_SCALE) {
        +          part.m.a = part.m.d = bin.readF2dot14(data, offset); offset += 2;
        +        } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
        +          part.m.a = bin.readF2dot14(data, offset); offset += 2;
        +          part.m.d = bin.readF2dot14(data, offset); offset += 2;
        +        } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
        +          part.m.a = bin.readF2dot14(data, offset); offset += 2;
        +          part.m.b = bin.readF2dot14(data, offset); offset += 2;
        +          part.m.c = bin.readF2dot14(data, offset); offset += 2;
        +          part.m.d = bin.readF2dot14(data, offset); offset += 2;
        +        }
        +      } while (flags & MORE_COMPONENTS)
        +      if (flags & WE_HAVE_INSTRUCTIONS) {
        +        var numInstr = bin.readUshort(data, offset); offset += 2;
        +        gl.instr = [];
        +        for (var i = 0; i < numInstr; i++) { gl.instr.push(data[offset]); offset++; }
        +      }
        +    }
        +    return gl;
        +  }
        +};
        +
        +Typr["T"].head = {
        +  parseTab: function (data, offset, length) {
        +    var bin = Typr["B"];
        +    var obj = {};
        +    var tableVersion = bin.readFixed(data, offset); offset += 4;
        +
        +    obj["fontRevision"] = bin.readFixed(data, offset); offset += 4;
        +    var checkSumAdjustment = bin.readUint(data, offset); offset += 4;
        +    var magicNumber = bin.readUint(data, offset); offset += 4;
        +    obj["flags"] = bin.readUshort(data, offset); offset += 2;
        +    obj["unitsPerEm"] = bin.readUshort(data, offset); offset += 2;
        +    obj["created"] = bin.readUint64(data, offset); offset += 8;
        +    obj["modified"] = bin.readUint64(data, offset); offset += 8;
        +    obj["xMin"] = bin.readShort(data, offset); offset += 2;
        +    obj["yMin"] = bin.readShort(data, offset); offset += 2;
        +    obj["xMax"] = bin.readShort(data, offset); offset += 2;
        +    obj["yMax"] = bin.readShort(data, offset); offset += 2;
        +    obj["macStyle"] = bin.readUshort(data, offset); offset += 2;
        +    obj["lowestRecPPEM"] = bin.readUshort(data, offset); offset += 2;
        +    obj["fontDirectionHint"] = bin.readShort(data, offset); offset += 2;
        +    obj["indexToLocFormat"] = bin.readShort(data, offset); offset += 2;
        +    obj["glyphDataFormat"] = bin.readShort(data, offset); offset += 2;
        +    return obj;
        +  }
        +};
        +
        +Typr["T"].hhea = {
        +  parseTab: function (data, offset, length) {
        +    var bin = Typr["B"];
        +    var obj = {};
        +    var tableVersion = bin.readFixed(data, offset); offset += 4;
        +
        +    var keys = ["ascender", "descender", "lineGap",
        +      "advanceWidthMax", "minLeftSideBearing", "minRightSideBearing", "xMaxExtent",
        +      "caretSlopeRise", "caretSlopeRun", "caretOffset",
        +      "res0", "res1", "res2", "res3",
        +      "metricDataFormat", "numberOfHMetrics"];
        +
        +    for (var i = 0; i < keys.length; i++) {
        +      var key = keys[i];
        +      var func = (key == "advanceWidthMax" || key == "numberOfHMetrics") ? bin.readUshort : bin.readShort;
        +      obj[key] = func(data, offset + i * 2);
        +    }
        +    return obj;
        +  }
        +};
        +
        +
        +Typr["T"].hmtx = {
        +  parseTab: function (data, offset, length, font) {
        +    var bin = Typr["B"];
        +    var aWidth = [];
        +    var lsBearing = [];
        +
        +    var nG = font["maxp"]["numGlyphs"], nH = font["hhea"]["numberOfHMetrics"];
        +    var aw = 0, lsb = 0, i = 0;
        +    while (i < nH) { aw = bin.readUshort(data, offset + (i << 2)); lsb = bin.readShort(data, offset + (i << 2) + 2); aWidth.push(aw); lsBearing.push(lsb); i++; }
        +    while (i < nG) { aWidth.push(aw); lsBearing.push(lsb); i++; }
        +
        +    return { aWidth: aWidth, lsBearing: lsBearing };
        +  }
        +};
        +
        +
        +Typr["T"].kern = {
        +  parseTab: function (data, offset, length, font) {
        +    var bin = Typr["B"], kern = Typr["T"].kern;
        +
        +    var version = bin.readUshort(data, offset);
        +    if (version == 1) return kern.parseV1(data, offset, length, font);
        +    var nTables = bin.readUshort(data, offset + 2); offset += 4;
        +
        +    var map = { glyph1: [], rval: [] };
        +    for (var i = 0; i < nTables; i++) {
        +      offset += 2;	// skip version
        +      var length = bin.readUshort(data, offset); offset += 2;
        +      var coverage = bin.readUshort(data, offset); offset += 2;
        +      var format = coverage >>> 8;
        +			/* I have seen format 128 once, that's why I do */ format &= 0xf;
        +      if (format == 0) offset = kern.readFormat0(data, offset, map);
        +      //else throw "unknown kern table format: "+format;
        +    }
        +    return map;
        +  },
        +
        +  parseV1: function (data, offset, length, font) {
        +    var bin = Typr["B"], kern = Typr["T"].kern;
        +
        +    var version = bin.readFixed(data, offset);   // 0x00010000 
        +    var nTables = bin.readUint(data, offset + 4); offset += 8;
        +
        +    var map = { glyph1: [], rval: [] };
        +    for (var i = 0; i < nTables; i++) {
        +      var length = bin.readUint(data, offset); offset += 4;
        +      var coverage = bin.readUshort(data, offset); offset += 2;
        +      var tupleIndex = bin.readUshort(data, offset); offset += 2;
        +      var format = coverage & 0xff;
        +      if (format == 0) offset = kern.readFormat0(data, offset, map);
        +      //else throw "unknown kern table format: "+format;
        +    }
        +    return map;
        +  },
        +
        +  readFormat0: function (data, offset, map) {
        +    var bin = Typr["B"], rUs = bin.readUshort;
        +    var pleft = -1;
        +    var nPairs = rUs(data, offset);
        +    var searchRange = rUs(data, offset + 2);
        +    var entrySelector = rUs(data, offset + 4);
        +    var rangeShift = rUs(data, offset + 6); offset += 8;
        +    for (var j = 0; j < nPairs; j++) {
        +      var left = rUs(data, offset); offset += 2;
        +      var right = rUs(data, offset); offset += 2;
        +      var value = bin.readShort(data, offset); offset += 2;
        +      if (left != pleft) { map.glyph1.push(left); map.rval.push({ glyph2: [], vals: [] }) }
        +      var rval = map.rval[map.rval.length - 1];
        +      rval.glyph2.push(right); rval.vals.push(value);
        +      pleft = left;
        +    }
        +    return offset;
        +  }
        +};
        +
        +
        +Typr["T"].loca = {
        +  parseTab: function (data, offset, length, font) {
        +    var bin = Typr["B"];
        +    var obj = [];
        +
        +    var ver = font["head"]["indexToLocFormat"];
        +    var len = font["maxp"]["numGlyphs"] + 1;
        +
        +    if (ver == 0) for (var i = 0; i < len; i++) obj.push(bin.readUshort(data, offset + (i << 1)) << 1);
        +    if (ver == 1) for (var i = 0; i < len; i++) obj.push(bin.readUint(data, offset + (i << 2)));
        +
        +    return obj;
        +  }
        +};
        +
        +
        +Typr["T"].maxp = {
        +  parseTab: function (data, offset, length) {
        +    //console.log(data.length, offset, length);
        +
        +    var bin = Typr["B"], rU = bin.readUshort;
        +    var obj = {};
        +
        +    // both versions 0.5 and 1.0
        +    var ver = bin.readUint(data, offset); offset += 4;
        +
        +    obj["numGlyphs"] = rU(data, offset); offset += 2;
        +
        +    // only 1.0
        +    /*
        +    if(ver == 0x00010000) {
        +      obj.maxPoints             = rU(data, offset);  offset += 2;
        +      obj.maxContours           = rU(data, offset);  offset += 2;
        +      obj.maxCompositePoints    = rU(data, offset);  offset += 2;
        +      obj.maxCompositeContours  = rU(data, offset);  offset += 2;
        +      obj.maxZones              = rU(data, offset);  offset += 2;
        +      obj.maxTwilightPoints     = rU(data, offset);  offset += 2;
        +      obj.maxStorage            = rU(data, offset);  offset += 2;
        +      obj.maxFunctionDefs       = rU(data, offset);  offset += 2;
        +      obj.maxInstructionDefs    = rU(data, offset);  offset += 2;
        +      obj.maxStackElements      = rU(data, offset);  offset += 2;
        +      obj.maxSizeOfInstructions = rU(data, offset);  offset += 2;
        +      obj.maxComponentElements  = rU(data, offset);  offset += 2;
        +      obj.maxComponentDepth     = rU(data, offset);  offset += 2;
        +    }
        +    */
        +
        +    return obj;
        +  }
        +};
        +Typr["T"].name = {
        +  parseTab: function (data, offset, length) {
        +    var bin = Typr["B"];
        +    var obj = {};
        +    var format = bin.readUshort(data, offset); offset += 2;
        +    var count = bin.readUshort(data, offset); offset += 2;
        +    var stringOffset = bin.readUshort(data, offset); offset += 2;
        +
        +    var ooo = offset - 6 + stringOffset;
        +    //console.log(format,count);
        +
        +    var names = [
        +      "copyright",
        +      "fontFamily",
        +      "fontSubfamily",
        +      "ID",
        +      "fullName",
        +      "version",
        +      "postScriptName",
        +      "trademark",
        +      "manufacturer",
        +      "designer",
        +      "description",
        +      "urlVendor",
        +      "urlDesigner",
        +      "licence",
        +      "licenceURL",
        +      "---",
        +      "typoFamilyName",
        +      "typoSubfamilyName",
        +      "compatibleFull",
        +      "sampleText",
        +      "postScriptCID",
        +      "wwsFamilyName",
        +      "wwsSubfamilyName",
        +      "lightPalette",
        +      "darkPalette"
        +    ];
        +
        +    var rU = bin.readUshort;
        +
        +    for (var i = 0; i < count; i++) {
        +      var platformID = rU(data, offset); offset += 2;
        +      var encodingID = rU(data, offset); offset += 2;
        +      var languageID = rU(data, offset); offset += 2;
        +      var nameID = rU(data, offset); offset += 2;
        +      var slen = rU(data, offset); offset += 2;
        +      var noffset = rU(data, offset); offset += 2;
        +      //console.log(platformID, encodingID, languageID.toString(16), nameID, length, noffset);
        +
        +
        +      var soff = ooo + noffset;
        +      var str;
        +      if (false) { }
        +      else if (platformID == 0) str = bin.readUnicode(data, soff, slen / 2);
        +      else if (platformID == 3 && encodingID == 0) str = bin.readUnicode(data, soff, slen / 2);
        +      else if (platformID == 1 && encodingID == 25) str = bin.readUnicode(data, soff, slen / 2);
        +      else if (encodingID == 0) str = bin.readASCII(data, soff, slen);
        +      else if (encodingID == 1) str = bin.readUnicode(data, soff, slen / 2);
        +      else if (encodingID == 3) str = bin.readUnicode(data, soff, slen / 2);
        +      else if (encodingID == 4) str = bin.readUnicode(data, soff, slen / 2);
        +      else if (encodingID == 5) str = bin.readUnicode(data, soff, slen / 2);
        +      else if (encodingID == 10) str = bin.readUnicode(data, soff, slen / 2);
        +
        +      else if (platformID == 1) { str = bin.readASCII(data, soff, slen); console.log("reading unknown MAC encoding " + encodingID + " as ASCII") }
        +      else {
        +        console.log("unknown encoding " + encodingID + ", platformID: " + platformID);
        +        str = bin.readASCII(data, soff, slen);
        +      }
        +
        +      var tid = "p" + platformID + "," + (languageID).toString(16);//Typr._platforms[platformID];
        +      if (obj[tid] == null) obj[tid] = {};
        +      var name = names[nameID]; if (name == null) name = "_" + nameID;
        +      obj[tid][name] = str;
        +      obj[tid]["_lang"] = languageID;
        +      //console.log(tid, obj[tid]);
        +    }
        +    /*
        +    if(format == 1)
        +    {
        +      var langTagCount = bin.readUshort(data, offset);  offset += 2;
        +      for(var i=0; i<langTagCount; i++)
        +      {
        +        var length  = bin.readUshort(data, offset);  offset += 2;
        +        var noffset = bin.readUshort(data, offset);  offset += 2;
        +      }
        +    }
        +    */
        +    var out = Typr["T"].name.selectOne(obj), ff = "fontFamily";
        +    if (out[ff] == null) for (var p in obj) if (obj[p][ff] != null) out[ff] = obj[p][ff];
        +    return out;
        +  },
        +  selectOne: function (obj) {
        +    //console.log(obj);
        +    var psn = "postScriptName";
        +
        +    for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0409) return obj[p];		// United States
        +    for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0000) return obj[p];		// Universal
        +    for (var p in obj) if (obj[p][psn] != null && obj[p]["_lang"] == 0x0c0c) return obj[p];		// Canada
        +    for (var p in obj) if (obj[p][psn] != null) return obj[p];
        +
        +    var out;
        +    for (var p in obj) { out = obj[p]; break; }
        +    console.log("returning name table with languageID " + out._lang);
        +    if (out[psn] == null && out["ID"] != null) out[psn] = out["ID"];
        +    return out;
        +  }
        +}
        +
        +Typr["T"].OS2 = {
        +  parseTab: function (data, offset, length) {
        +    var bin = Typr["B"];
        +    var ver = bin.readUshort(data, offset); offset += 2;
        +
        +    var OS2 = Typr["T"].OS2;
        +
        +    var obj = {};
        +    if (ver == 0) OS2.version0(data, offset, obj);
        +    else if (ver == 1) OS2.version1(data, offset, obj);
        +    else if (ver == 2 || ver == 3 || ver == 4) OS2.version2(data, offset, obj);
        +    else if (ver == 5) OS2.version5(data, offset, obj);
        +    else throw "unknown OS/2 table version: " + ver;
        +
        +    return obj;
        +  },
        +
        +  version0: function (data, offset, obj) {
        +    var bin = Typr["B"];
        +    obj["xAvgCharWidth"] = bin.readShort(data, offset); offset += 2;
        +    obj["usWeightClass"] = bin.readUshort(data, offset); offset += 2;
        +    obj["usWidthClass"] = bin.readUshort(data, offset); offset += 2;
        +    obj["fsType"] = bin.readUshort(data, offset); offset += 2;
        +    obj["ySubscriptXSize"] = bin.readShort(data, offset); offset += 2;
        +    obj["ySubscriptYSize"] = bin.readShort(data, offset); offset += 2;
        +    obj["ySubscriptXOffset"] = bin.readShort(data, offset); offset += 2;
        +    obj["ySubscriptYOffset"] = bin.readShort(data, offset); offset += 2;
        +    obj["ySuperscriptXSize"] = bin.readShort(data, offset); offset += 2;
        +    obj["ySuperscriptYSize"] = bin.readShort(data, offset); offset += 2;
        +    obj["ySuperscriptXOffset"] = bin.readShort(data, offset); offset += 2;
        +    obj["ySuperscriptYOffset"] = bin.readShort(data, offset); offset += 2;
        +    obj["yStrikeoutSize"] = bin.readShort(data, offset); offset += 2;
        +    obj["yStrikeoutPosition"] = bin.readShort(data, offset); offset += 2;
        +    obj["sFamilyClass"] = bin.readShort(data, offset); offset += 2;
        +    obj["panose"] = bin.readBytes(data, offset, 10); offset += 10;
        +    obj["ulUnicodeRange1"] = bin.readUint(data, offset); offset += 4;
        +    obj["ulUnicodeRange2"] = bin.readUint(data, offset); offset += 4;
        +    obj["ulUnicodeRange3"] = bin.readUint(data, offset); offset += 4;
        +    obj["ulUnicodeRange4"] = bin.readUint(data, offset); offset += 4;
        +    obj["achVendID"] = bin.readASCII(data, offset, 4); offset += 4;
        +    obj["fsSelection"] = bin.readUshort(data, offset); offset += 2;
        +    obj["usFirstCharIndex"] = bin.readUshort(data, offset); offset += 2;
        +    obj["usLastCharIndex"] = bin.readUshort(data, offset); offset += 2;
        +    obj["sTypoAscender"] = bin.readShort(data, offset); offset += 2;
        +    obj["sTypoDescender"] = bin.readShort(data, offset); offset += 2;
        +    obj["sTypoLineGap"] = bin.readShort(data, offset); offset += 2;
        +    obj["usWinAscent"] = bin.readUshort(data, offset); offset += 2;
        +    obj["usWinDescent"] = bin.readUshort(data, offset); offset += 2;
        +    return offset;
        +  },
        +
        +  version1: function (data, offset, obj) {
        +    var bin = Typr["B"];
        +    offset = Typr["T"].OS2.version0(data, offset, obj);
        +
        +    obj["ulCodePageRange1"] = bin.readUint(data, offset); offset += 4;
        +    obj["ulCodePageRange2"] = bin.readUint(data, offset); offset += 4;
        +    return offset;
        +  },
        +
        +  version2: function (data, offset, obj) {
        +    var bin = Typr["B"], rU = bin.readUshort;
        +    offset = Typr["T"].OS2.version1(data, offset, obj);
        +
        +    obj["sxHeight"] = bin.readShort(data, offset); offset += 2;
        +    obj["sCapHeight"] = bin.readShort(data, offset); offset += 2;
        +    obj["usDefault"] = rU(data, offset); offset += 2;
        +    obj["usBreak"] = rU(data, offset); offset += 2;
        +    obj["usMaxContext"] = rU(data, offset); offset += 2;
        +    return offset;
        +  },
        +
        +  version5: function (data, offset, obj) {
        +    var rU = Typr["B"].readUshort;
        +    offset = Typr["T"].OS2.version2(data, offset, obj);
        +
        +    obj["usLowerOpticalPointSize"] = rU(data, offset); offset += 2;
        +    obj["usUpperOpticalPointSize"] = rU(data, offset); offset += 2;
        +    return offset;
        +  }
        +}
        +
        +Typr["T"].post = {
        +  parseTab: function (data, offset, length) {
        +    var bin = Typr["B"];
        +    var obj = {};
        +
        +    obj["version"] = bin.readFixed(data, offset); offset += 4;
        +    obj["italicAngle"] = bin.readFixed(data, offset); offset += 4;
        +    obj["underlinePosition"] = bin.readShort(data, offset); offset += 2;
        +    obj["underlineThickness"] = bin.readShort(data, offset); offset += 2;
        +
        +    return obj;
        +  }
        +};
        +Typr["T"].SVG = {
        +  parseTab: function (data, offset, length) {
        +    var bin = Typr["B"];
        +    var obj = { entries: [], svgs: [] };
        +
        +    var offset0 = offset;
        +
        +    var tableVersion = bin.readUshort(data, offset); offset += 2;
        +    var svgDocIndexOffset = bin.readUint(data, offset); offset += 4;
        +    var reserved = bin.readUint(data, offset); offset += 4;
        +
        +    offset = svgDocIndexOffset + offset0;
        +
        +    var numEntries = bin.readUshort(data, offset); offset += 2;
        +
        +    for (var i = 0; i < numEntries; i++) {
        +      var startGlyphID = bin.readUshort(data, offset); offset += 2;
        +      var endGlyphID = bin.readUshort(data, offset); offset += 2;
        +      var svgDocOffset = bin.readUint(data, offset); offset += 4;
        +      var svgDocLength = bin.readUint(data, offset); offset += 4;
        +
        +      var sbuf = new Uint8Array(data.buffer, offset0 + svgDocOffset + svgDocIndexOffset, svgDocLength);
        +      if (sbuf[0] == 0x1f && sbuf[1] == 0x8b && sbuf[2] == 0x08) sbuf = pako["inflate"](sbuf);
        +      var svg = bin.readUTF8(sbuf, 0, sbuf.length);
        +
        +      for (var f = startGlyphID; f <= endGlyphID; f++) {
        +        obj.entries[f] = obj.svgs.length;
        +      }
        +      obj.svgs.push(svg);
        +    }
        +    return obj;
        +  }
        +};
        +
        +
        +Typr["T"].sbix = {
        +  parseTab: function (data, offset, length, obj) {
        +    var numGlyphs = obj["maxp"]["numGlyphs"];
        +    var ooff = offset;
        +    var bin = Typr["B"];
        +
        +    //var ver = bin.readUshort(data,offset);  offset+=2;
        +    //var flg = bin.readUshort(data,offset);  offset+=2;
        +
        +    var numStrikes = bin.readUint(data, offset + 4);
        +
        +    var out = [];
        +    for (var si = numStrikes - 1; si < numStrikes; si++) {
        +      var off = ooff + bin.readUint(data, offset + 8 + si * 4);
        +
        +      //var ppem = bin.readUshort(data,off);  off+=2;
        +      //var ppi  = bin.readUshort(data,off);  off+=2;
        +
        +      for (var gi = 0; gi < numGlyphs; gi++) {
        +        var aoff = bin.readUint(data, off + 4 + gi * 4);
        +        var noff = bin.readUint(data, off + 4 + gi * 4 + 4); if (aoff == noff) { out[gi] = null; continue; }
        +        var go = off + aoff;
        +        //var ooX = bin.readUshort(data,go);
        +        //var ooY = bin.readUshort(data,go+2);
        +        var tag = bin.readASCII(data, go + 4, 4); if (tag != "png ") throw tag;
        +
        +        out[gi] = new Uint8Array(data.buffer, data.byteOffset + go + 8, noff - aoff - 8);
        +      }
        +    }
        +    return out;
        +  }
        +};
        +
        +Typr["T"].colr = {
        +  parseTab: function (data, offset, length) {
        +    var bin = Typr["B"];
        +    var ooff = offset;
        +    offset += 2;
        +    var num = bin.readUshort(data, offset); offset += 2;
        +
        +    var boff = bin.readUint(data, offset); offset += 4;
        +    var loff = bin.readUint(data, offset); offset += 4;
        +
        +    var lnum = bin.readUshort(data, offset); offset += 2;
        +    //console.log(num,boff,loff,lnum);
        +
        +    var base = {};
        +    var coff = ooff + boff;
        +    for (var i = 0; i < num; i++) {
        +      base["g" + bin.readUshort(data, coff)] = [bin.readUshort(data, coff + 2), bin.readUshort(data, coff + 4)];
        +      coff += 6;
        +    }
        +
        +    var lays = [];
        +    coff = ooff + loff;
        +    for (var i = 0; i < lnum; i++) {
        +      lays.push(bin.readUshort(data, coff), bin.readUshort(data, coff + 2)); coff += 4;
        +    }
        +    return [base, lays];
        +  }
        +};
        +
        +Typr["T"].cpal = {
        +  parseTab: function (data, offset, length) {
        +    var bin = Typr["B"];
        +    var ooff = offset;
        +    var vsn = bin.readUshort(data, offset); offset += 2;
        +
        +    if (vsn == 0) {
        +      var ets = bin.readUshort(data, offset); offset += 2;
        +      var pts = bin.readUshort(data, offset); offset += 2;
        +      var tot = bin.readUshort(data, offset); offset += 2;
        +
        +      var fst = bin.readUint(data, offset); offset += 4;
        +
        +      return new Uint8Array(data.buffer, ooff + fst, tot * 4);
        +      /*
        +      var coff=ooff+fst;
        +    	
        +      for(var i=0; i<tot; i++) {
        +        console.log(data[coff],data[coff+1],data[coff+2],data[coff+3]);
        +        coff+=4;
        +      }
        +    	
        +      console.log(ets,pts,tot); */
        +    }
        +    else throw vsn;//console.log("unknown color palette",vsn);
        +  }
        +};
        +
        +Typr["T"].GSUB = {
        +  parseTab: function (data, offset, length, obj) {
        +    //console.log(obj.name.ID);
        +
        +    var bin = Typr["B"], rU = bin.readUshort, rI = bin.readUint;
        +
        +
        +    var off = offset;
        +    var maj = rU(data, off); off += 2;
        +    var min = rU(data, off); off += 2;
        +    var slO = rU(data, off); off += 2;
        +    var flO = rU(data, off); off += 2;
        +    var llO = rU(data, off); off += 2;
        +
        +    //console.log(maj,min,slO,flO,llO);
        +
        +    off = offset + flO;
        +
        +    var fmap = {};
        +    var cnt = rU(data, off); off += 2;
        +    for (var i = 0; i < cnt; i++) {
        +      var tag = bin.readASCII(data, off, 4); off += 4;
        +      var fof = rU(data, off); off += 2;
        +      fmap[tag] = true;
        +    }
        +    //console.log(fmap);
        +    return fmap;
        +  }
        +};
        +
        +Typr["T"].fvar = {
        +  parseTab: function (data, offset, length, obj) {
        +    var name = obj["name"];
        +    var off = offset;
        +    var bin = Typr["B"];
        +    var axes = [], inst = [];
        +
        +    off += 8;
        +    var acnt = bin.readUshort(data, off); off += 2;
        +    off += 2;
        +    var icnt = bin.readUshort(data, off); off += 2;
        +    var isiz = bin.readUshort(data, off); off += 2;
        +
        +    for (var i = 0; i < acnt; i++) {
        +      var tag = bin.readASCII(data, off, 4);
        +      var min = bin.readFixed(data, off + 4);
        +      var def = bin.readFixed(data, off + 8);
        +      var max = bin.readFixed(data, off + 12);
        +      var flg = bin.readUshort(data, off + 16);
        +      var nid = bin.readUshort(data, off + 18);
        +      axes.push([tag, min, def, max, flg, name["_" + nid]]);
        +      //console.log(tag,min,def,max,flg,nid);
        +      off += 20;
        +    }
        +    for (var i = 0; i < icnt; i++) {
        +      var snid = bin.readUshort(data, off), pnid = null;
        +      var flg = bin.readUshort(data, off + 2);
        +      var crd = []; for (var j = 0; j < acnt; j++) crd.push(bin.readFixed(data, off + 4 + j * 4));
        +      off += 4 + acnt * 4;
        +      if ((isiz & 3) == 2) { pnid = bin.readUshort(data, off); off += 2; }
        +      inst.push([name["_" + snid], flg, crd, pnid]);
        +      //console.log(snid,flg, crd);
        +    }
        +
        +    return [axes, inst];
        +  }
        +};
        +
        +Typr["T"].gvar = (function () {
        +
        +  var EMBEDDED_PEAK_TUPLE = 0x8000;
        +  var INTERMEDIATE_REGION = 0x4000;
        +  var PRIVATE_POINT_NUMBERS = 0x2000;
        +
        +  var DELTAS_ARE_ZERO = 0x80;
        +  var DELTAS_ARE_WORDS = 0x40;
        +
        +  var POINTS_ARE_WORDS = 0x80;
        +
        +  var SHARED_POINT_NUMBERS = 0x8000;
        +
        +  var bin = Typr["B"];
        +
        +  function readTuple(data, o, acnt) {
        +    var tup = []; for (var j = 0; j < acnt; j++) tup.push(bin.readF2dot14(data, o + j * 2));
        +    return tup;
        +  }
        +
        +  function readTupleVarHeader(data, off, vcnt, acnt, eoff) {
        +    var out = [];
        +    for (var j = 0; j < vcnt; j++) {
        +      var dsiz = bin.readUshort(data, off); off += 2;
        +      var tind = bin.readUshort(data, off), flag = tind & 0xf000; tind = tind & 0xfff; off += 2;
        +      //console.log(j, dsiz,tind, flag.toString(16));
        +
        +      var peak = null, start = null, end = null;
        +      if (flag & EMBEDDED_PEAK_TUPLE) { peak = readTuple(data, off, acnt); off += acnt * 2; }
        +      if (flag & INTERMEDIATE_REGION) { start = readTuple(data, off, acnt); off += acnt * 2; }
        +      if (flag & INTERMEDIATE_REGION) { end = readTuple(data, off, acnt); off += acnt * 2; }
        +      out.push([dsiz, tind, flag, start, peak, end]);
        +    }
        +    return out;
        +  }
        +
        +  // Packed "point" numbers
        +  function readPointNumbers(data, off, gid) {
        +    var cnt = data[off]; off++; if (cnt == 0) return [[], off];
        +    if (127 < cnt) { cnt = ((cnt & 127) << 8) | data[off++]; }
        +
        +    //if(gid==116) console.log("---",cnt);
        +    var pts = [], last = 0;  // point number data runs
        +    while (pts.length < cnt) {
        +      var v = data[off]; off++;
        +      var wds = (v & POINTS_ARE_WORDS) != 0; v = (v & 127) + 1;
        +      //if(gid==116) console.log("-",v);
        +      for (var i = 0; i < v; i++) {
        +        var dif = 0;
        +        if (wds) { dif = bin.readUshort(data, off); off += 2; }
        +        else { dif = data[off]; off++; }
        +        //if(gid==116) console.log(dif);
        +        last += dif;
        +        pts.push(last);
        +      }
        +    }
        +    //console.log(pts);
        +    return [pts, off];
        +
        +
        +    //throw "e";
        +  }
        +
        +
        +  function parseTab(data, offset, length, obj) {
        +    var off = offset + 4;
        +    var acnt = bin.readUshort(data, off); off += 2;
        +    var tcnt = bin.readUshort(data, off); off += 2;
        +    var toff = bin.readUint(data, off); off += 4;
        +    var gcnt = bin.readUshort(data, off); off += 2;
        +    var flgs = bin.readUshort(data, off); off += 2;
        +
        +    var goff = bin.readUint(data, off); off += 4;
        +
        +    // glyphVariationDataOffsets
        +    var offs = []; for (var i = 0; i < gcnt + 1; i++) offs.push(bin.readUint(data, off + i * 4));
        +
        +
        +    // sharedTuples
        +    var tups = [], mins = [], maxs = []; off = offset + toff;
        +    for (var i = 0; i < tcnt; i++) {
        +      var peak = readTuple(data, off + i * acnt * 2, acnt), imin = [], imax = []; tups.push(peak); mins.push(imin); maxs.push(imax);
        +      for (var k = 0; k < acnt; k++) {
        +        imin[k] = Math.min(peak[k], 0);
        +        imax[k] = Math.max(peak[k], 0);
        +      }
        +    }
        +    //console.log(tups);
        +
        +    //console.log(acnt,stcnt,stoff,gcnt,flgs,goff);
        +
        +    var i8 = new Int8Array(data.buffer);
        +
        +    // GlyphVariationData table array
        +    var tabs = [];
        +    for (var i = 0; i < gcnt; i++) {
        +      //console.log("-------",i);
        +      off = offset + goff + offs[i];
        +      // tupleVariationCount
        +      var vcnt = bin.readUshort(data, off); off += 2;  //if((vcnt>>>12)!=0) throw "e";
        +
        +      var snum = vcnt & SHARED_POINT_NUMBERS; vcnt &= 0xfff;
        +      //  offset to the serialized data
        +      var soff = bin.readUshort(data, off); off += 2;
        +
        +      var hdr = readTupleVarHeader(data, off, vcnt, acnt, offset + goff + offs[i + 1]);
        +
        +      var tab = []; tabs.push(tab);
        +      // Serialized Data
        +      off = offset + goff + offs[i] + soff;
        +
        +      var sind = null;
        +      if (snum) {
        +        var oo = readPointNumbers(data, off, i);
        +        sind = oo[0]; off = oo[1];
        +      }
        +
        +      for (var j = 0; j < vcnt; j++) {
        +        var vr = hdr[j], end = off + vr[0];  //console.log(vr);  console.log(data.slice(off,off+vr[0]));
        +
        +        var ind = sind;
        +        if (vr[2] & PRIVATE_POINT_NUMBERS) {
        +          var oo = readPointNumbers(data, off, i);
        +          ind = oo[0]; off = oo[1];
        +        }
        +        // read packed deltas (delta runs)
        +        var ds = [];
        +        while (off < end) {
        +          var cb = data[off++];  // control byte;
        +          var cnt = (cb & 0x3f) + 1;
        +          if (cb & DELTAS_ARE_ZERO) { for (var k = 0; k < cnt; k++) ds.push(0); }
        +          else if (cb & DELTAS_ARE_WORDS) { for (var k = 0; k < cnt; k++) ds.push(bin.readShort(data, off + k * 2)); off += cnt * 2; }
        +          else { for (var k = 0; k < cnt; k++) ds.push(i8[off + k]); off += cnt; }
        +        }
        +        //if(ind) console.log(ind, ds);
        +        var ti = vr[1];
        +
        +        tab.push([[
        +          vr[3] ? vr[3] : mins[ti],
        +          vr[4] ? vr[4] : tups[ti],
        +          vr[5] ? vr[5] : maxs[ti]
        +        ], ds, ind.length == 0 ? null : ind]);
        +
        +        if (ind.length != 0 && ind.length * 2 != ds.length) throw "e";
        +        //if(i==116) console.log(ind, ds);
        +      }
        +    }
        +    return tabs;
        +  }
        +
        +  return { parseTab: parseTab };
        +})();
        +
        +Typr["T"].avar = {
        +  parseTab: function (data, offset, length, obj) {
        +    var off = offset;
        +    var bin = Typr["B"], out = [];
        +
        +    off += 6;
        +    var acnt = bin.readUshort(data, off); off += 2;
        +
        +    for (var ai = 0; ai < acnt; ai++) {
        +      var cnt = bin.readUshort(data, off); off += 2;
        +      var poly = []; out.push(poly);
        +      for (var i = 0; i < cnt; i++) {
        +        var x = bin.readF2dot14(data, off);
        +        var y = bin.readF2dot14(data, off + 2); off += 4;
        +        poly.push(x, y);
        +      }
        +    }
        +
        +    return out;
        +  }
        +};
        +
        +Typr["T"].HVAR = {
        +  parseTab: function (data, offset, length, obj) {
        +    var off = offset, oo = offset;
        +    var bin = Typr["B"], out = [];
        +
        +    //console.log(data.slice(off));
        +    off += 4;
        +
        +    var varO = bin.readUint(data, off); off += 4;
        +    var advO = bin.readUint(data, off); off += 4;
        +    var lsbO = bin.readUint(data, off); off += 4;
        +    var rsbO = bin.readUint(data, off); off += 4;
        +    if (lsbO != 0 || rsbO != 0) throw lsbO;
        +
        +    //console.log(varO,advO,lsbO,rsbO);
        +
        +    off = oo + varO;  // item variation store
        +
        +    // ItemVariationStore 
        +    var ioff = off;
        +
        +    var fmt = bin.readUshort(data, off); off += 2; if (fmt != 1) throw "e";
        +    var vregO = bin.readUint(data, off); off += 4;
        +    // itemVariationDataCount
        +    var vcnt = bin.readUshort(data, off); off += 2;
        +
        +    var offs = []; for (var i = 0; i < vcnt; i++) offs.push(bin.readUint(data, off + i * 4)); off += vcnt * 4;  //if(offs.length!=1) throw "e";
        +    //console.log(vregO,vcnt,offs);		
        +
        +    off = ioff + vregO;
        +    var acnt = bin.readUshort(data, off); off += 2;
        +    var rcnt = bin.readUshort(data, off); off += 2;
        +
        +    var regs = [];
        +    for (var i = 0; i < rcnt; i++) {
        +      var crd = [[], [], []]; regs.push(crd);
        +      for (var j = 0; j < acnt; j++) {
        +        crd[0].push(bin.readF2dot14(data, off + 0));
        +        crd[1].push(bin.readF2dot14(data, off + 2));
        +        crd[2].push(bin.readF2dot14(data, off + 4));
        +        off += 6;
        +      }
        +    }
        +    //console.log(acnt, rcnt, regs);
        +
        +
        +    var i8 = new Int8Array(data.buffer);
        +    var varStore = [];
        +    for (var i = 0; i < offs.length; i++) {
        +      // ItemVariationData 
        +      off = oo + varO + offs[i]; var vdata = []; varStore.push(vdata);
        +      var icnt = bin.readUshort(data, off); off += 2;  // itemCount
        +      var dcnt = bin.readUshort(data, off); off += 2; if (dcnt & 0x8000) throw "e";
        +      var rcnt = bin.readUshort(data, off); off += 2;
        +      var ixs = []; for (var j = 0; j < rcnt; j++) ixs.push(bin.readUshort(data, off + j * 2)); off += rcnt * 2;
        +      //console.log(icnt,dcnt,rcnt,ixs);
        +      //console.log(data.slice(off));
        +
        +      for (var k = 0; k < icnt; k++) {  // deltaSets
        +        var deltaData = [];  //vdata.push(deltaData);
        +        for (var ri = 0; ri < rcnt; ri++) {
        +          deltaData.push(ri < dcnt ? bin.readShort(data, off) : i8[off]);
        +          off += ri < dcnt ? 2 : 1;
        +
        +        }
        +        var dd = new Array(regs.length); dd.fill(0); vdata.push(dd);
        +        for (var j = 0; j < ixs.length; j++) dd[ixs[j]] = deltaData[j];
        +      }
        +    }
        +
        +    //console.log(varStore);
        +
        +    // VariationRegionList
        +
        +
        +
        +    off = oo + advO;  // advance widths
        +
        +    // DeltaSetIndexMap 
        +
        +    var fmt = data[off++]; if (fmt != 0) throw "e";
        +    var entryFormat = data[off++];
        +
        +    var mapCount = bin.readUshort(data, off); off += 2;
        +
        +    var INNER_INDEX_BIT_COUNT_MASK = 0x0f;
        +    var MAP_ENTRY_SIZE_MASK = 0x30;
        +    var entrySize = ((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1;  //if(entrySize!=1) throw entrySize;
        +
        +    //console.log(fmt, entryFormat, mapCount, entrySize);
        +
        +    var dfs = [];
        +    for (var i = 0; i < mapCount; i++) {
        +      var entry = 0;
        +      if (entrySize == 1) entry = data[off++];
        +      else { entry = bin.readUshort(data, off); off += 2; }
        +      var outerIndex = entry >> ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1);
        +      var innerIndex = entry & ((1 << ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1)) - 1);
        +      //map.push(outerIndex,innerIndex);
        +      dfs.push(varStore[outerIndex][innerIndex]);
        +      //console.log(outerIndex,innerIndex);
        +      //console.log(i,varStore[outerIndex][innerIndex]);
        +    }
        +
        +    return [regs, dfs];
        +  }
        +};
        +
        +Typr["U"] = function () {
        +  var P = {
        +    MoveTo: function (p, x, y) { p.cmds.push("M"); p.crds.push(x, y); },
        +    LineTo: function (p, x, y) { p.cmds.push("L"); p.crds.push(x, y); },
        +    CurveTo: function (p, a, b, c, d, e, f) { p.cmds.push("C"); p.crds.push(a, b, c, d, e, f); },
        +    qCurveTo: function (p, a, b, c, d) { p.cmds.push("Q"); p.crds.push(a, b, c, d); },
        +    ClosePath: function (p) { p.cmds.push("Z"); }
        +  }
        +
        +  function getGlyphPosition(font, gls, i1, ltr) {
        +    var g1 = gls[i1], g2 = gls[i1 + 1], kern = font["kern"];
        +    if (kern) {
        +      var ind1 = kern.glyph1.indexOf(g1);
        +      if (ind1 != -1) {
        +        var ind2 = kern.rval[ind1].glyph2.indexOf(g2);
        +        if (ind2 != -1) return [0, 0, kern.rval[ind1].vals[ind2], 0];
        +      }
        +    }
        +    //console.log("no kern");
        +    return [0, 0, 0, 0];
        +  }
        +  function shape(font, str, prm) {
        +    if (prm == null) prm = {};
        +    var ltr = prm["ltr"], fts = prm["fts"], axs = prm["axs"];
        +    if (font["fvar"] && axs == null) axs = font["fvar"][1][font["_index"]][2];
        +
        +    var HVAR = font["HVAR"];  //console.log(HVAR);
        +    if (axs && HVAR) { axs = _normalizeAxis(font, axs); }  //console.log(S,axs);
        +    var gls = [];
        +    for (var i = 0; i < str.length; i++) {
        +      var cc = str.codePointAt(i); if (cc > 0xffff) i++;
        +      gls.push(codeToGlyph(font, cc));
        +    }
        +    var shape = [];
        +    var x = 0, y = 0;
        +
        +    for (var i = 0; i < gls.length; i++) {
        +      var padj = getGlyphPosition(font, gls, i, ltr);
        +      var gid = gls[i];  //console.log(gid);
        +      var ax = font["hmtx"].aWidth[gid] + padj[2];
        +      if (HVAR && HVAR[1][gid]) { //ax+=S*HVAR[1][gid][0];
        +        var difs = HVAR[1][gid];  //console.log(difs);
        +        for (var j = 0; j < HVAR[0].length; j++) {
        +          ax += _interpolate(HVAR[0][j], axs) * difs[j];
        +        }
        +      }
        +      shape.push({ "g": gid, "cl": i, "dx": 0, "dy": 0, "ax": ax, "ay": 0 });
        +      x += ax;
        +    }
        +    return shape;
        +  }
        +
        +  function shapeToPath(font, shape, prm) {
        +    var tpath = { cmds: [], crds: [] };
        +    var x = 0, y = 0, clr, axs;
        +    if (prm) { clr = prm["clr"]; axs = prm["axs"]; }
        +
        +    for (var i = 0; i < shape.length; i++) {
        +      var it = shape[i]
        +      var path = glyphToPath(font, it["g"], false, axs), crds = path["crds"];
        +      for (var j = 0; j < crds.length; j += 2) {
        +        tpath.crds.push(crds[j] + x + it["dx"]);
        +        tpath.crds.push(crds[j + 1] + y + it["dy"]);
        +      }
        +      if (clr) tpath.cmds.push(clr);
        +      for (var j = 0; j < path["cmds"].length; j++) tpath.cmds.push(path["cmds"][j]);
        +      var clen = tpath.cmds.length;
        +      if (clr) if (clen != 0 && tpath.cmds[clen - 1] != "X") tpath.cmds.push("X");  // SVG fonts might contain "X". Then, nothing would stroke non-SVG glyphs.
        +
        +      x += it["ax"]; y += it["ay"];
        +    }
        +    return { "cmds": tpath.cmds, "crds": tpath.crds };
        +  }
        +
        +
        +  // find the greatest index with a value <=v
        +  function arrSearch(arr, k, v) {
        +    var l = 0, r = ~~(arr.length / k);
        +    while (l + 1 != r) { var mid = l + ((r - l) >>> 1); if (arr[mid * k] <= v) l = mid; else r = mid; }
        +
        +    //var mi = 0;  for(var i=0; i<arr.length; i+=k) if(arr[i]<=v) mi=i;  if(mi!=l*k) throw "e";
        +
        +    return l * k;
        +  }
        +
        +  var wha = [0x9, 0xa, 0xb, 0xc, 0xd, 0x20, 0x85, 0xa0, 0x1680, 0x180e, 0x2028, 0x2029, 0x202f, 0x2060, 0x3000, 0xfeff], whm = {};
        +  for (var i = 0; i < wha.length; i++) whm[wha[i]] = 1;
        +  for (var i = 0x2000; i <= 0x200d; i++) whm[i] = 1;
        +
        +  function codeToGlyph(font, code) {
        +    //console.log(cmap);
        +    // "p3e10" for NotoEmoji-Regular.ttf
        +    //console.log(cmap);
        +
        +    if (font["_ctab"] == null) {
        +      var cmap = font["cmap"];
        +      var tind = -1, pps = ["p3e10", "p0e4", "p3e1", "p1e0", "p0e3", "p0e1"/*,"p3e3"*/, "p3e0" /*Hebrew*/, "p3e5" /*Korean*/];
        +      for (var i = 0; i < pps.length; i++) if (cmap.ids[pps[i]] != null) { tind = cmap.ids[pps[i]]; break; }
        +      if (tind == -1) throw "no familiar platform and encoding!";
        +      font["_ctab"] = cmap.tables[tind];
        +    }
        +
        +    var tab = font["_ctab"], fmt = tab.format, gid = -1;  //console.log(fmt); throw "e";
        +
        +    if (fmt == 0) {
        +      if (code >= tab.map.length) gid = 0;
        +      else gid = tab.map[code];
        +    }
        +    /*else if(fmt==2) {
        +      var data=font["_data"], off = cmap.off+tab.off+6, bin=Typr["B"];
        +      var shKey = bin.readUshort(data,off + 2*(code>>>8));
        +      var shInd = off + 256*2 + shKey*8;
        +    	
        +      var firstCode = bin.readUshort(data,shInd);
        +      var entryCount= bin.readUshort(data,shInd+2);
        +      var idDelta   = bin.readShort (data,shInd+4);
        +      var idRangeOffset = bin.readUshort(data,shInd+6);
        +    	
        +      if(firstCode<=code && code<=firstCode+entryCount) {
        +        // not completely correct
        +        gid = bin.readUshort(data, shInd+6+idRangeOffset + (code&255)*2);
        +      }
        +      else gid=0;
        +      //if(code>256) console.log(code,(code>>>8),shKey,firstCode,entryCount,idDelta,idRangeOffset);
        +    	
        +      //throw "e";
        +      //console.log(tab,  bin.readUshort(data,off));
        +      //throw "e";
        +    }*/
        +    else if (fmt == 4) {
        +      var ec = tab.endCount; gid = 0;
        +      if (code <= ec[ec.length - 1]) {
        +        // smallest index with code <= value
        +        var sind = arrSearch(ec, 1, code);
        +        if (ec[sind] < code) sind++;
        +
        +        if (code >= tab.startCount[sind]) {
        +          var gli = 0;
        +          if (tab.idRangeOffset[sind] != 0) gli = tab.glyphIdArray[(code - tab.startCount[sind]) + (tab.idRangeOffset[sind] >> 1) - (tab.idRangeOffset.length - sind)];
        +          else gli = code + tab.idDelta[sind];
        +          gid = (gli & 0xFFFF);
        +        }
        +      }
        +    }
        +    else if (fmt == 6) {
        +      var off = code - tab.firstCode, arr = tab.glyphIdArray;
        +      if (off < 0 || off >= arr.length) gid = 0;
        +      else gid = arr[off];
        +    }
        +    else if (fmt == 12) {
        +      var grp = tab.groups; gid = 0;  //console.log(grp);  throw "e";
        +
        +      if (code <= grp[grp.length - 2]) {
        +        var i = arrSearch(grp, 3, code);
        +        if (grp[i] <= code && code <= grp[i + 1]) { gid = grp[i + 2] + (code - grp[i]); }
        +      }
        +    }
        +    else throw "unknown cmap table format " + tab.format;
        +
        +    //*
        +    var SVG = font["SVG "], loca = font["loca"];
        +    // if the font claims to have a Glyph for a character, but the glyph is empty, and the character is not "white", it is a lie!
        +    if (gid != 0 && font["CFF "] == null && (SVG == null || SVG.entries[gid] == null) && loca && loca[gid] == loca[gid + 1]  // loca not present in CFF or SVG fonts
        +      && whm[code] == null) gid = 0;
        +    //*/
        +
        +    return gid;
        +  }
        +  function glyphToPath(font, gid, noColor, axs) {
        +    var path = { cmds: [], crds: [] };
        +
        +    if (font["fvar"]) {
        +      if (axs == null) axs = font["fvar"][1][font["_index"]][2];
        +      axs = _normalizeAxis(font, axs);
        +    }
        +
        +    var SVG = font["SVG "], CFF = font["CFF "], COLR = font["COLR"], CBLC = font["CBLC"], CBDT = font["CBDT"], sbix = font["sbix"], upng = window["UPNG"];
        +
        +    var strike = null;
        +    if (CBLC && upng) for (var i = 0; i < CBLC.length; i++) if (CBLC[i][0] <= gid && gid <= CBLC[i][1]) strike = CBLC[i];
        +
        +    if (strike || (sbix && sbix[gid])) {
        +      if (strike && strike[2] != 17) throw "not a PNG";
        +
        +      if (font["__tmp"] == null) font["__tmp"] = {};
        +      var cmd = font["__tmp"]["g" + gid];
        +      if (cmd == null) {
        +        var bmp, len;
        +        if (sbix) { bmp = sbix[gid]; len = bmp.length; }
        +        else {
        +          var boff = strike[3][gid - strike[0]] + 5;  // smallGlyphMetrics
        +          len = (CBDT[boff + 1] << 16) | (CBDT[boff + 2] << 8) | CBDT[boff + 3]; boff += 4;
        +          bmp = new Uint8Array(CBDT.buffer, CBDT.byteOffset + boff, len);
        +        }
        +        var str = ""; for (var i = 0; i < len; i++) str += String.fromCharCode(bmp[i]);
        +        cmd = font["__tmp"]["g" + gid] = "data:image/png;base64," + btoa(str);
        +      }
        +
        +      path.cmds.push(cmd);
        +      var upe = font["head"]["unitsPerEm"] * 1.15;
        +      var gw = Math.round(upe), gh = Math.round(upe), dy = Math.round(-gh * 0.15);
        +      path.crds.push(0, gh + dy, gw, gh + dy, gw, dy, 0, dy); //*/
        +    }
        +    else if (SVG && SVG.entries[gid]) {
        +      var p = SVG.entries[gid];
        +      if (p != null) {
        +        if (typeof p == "number") {
        +          var svg = SVG.svgs[p];
        +          if (typeof svg == "string") {
        +            var prsr = new DOMParser();
        +            var doc = prsr["parseFromString"](svg, "image/svg+xml");
        +            svg = SVG.svgs[p] = doc.getElementsByTagName("svg")[0];
        +          }
        +          p = Typr["U"]["SVG"].toPath(svg, gid); SVG.entries[gid] = p;
        +        }
        +        path = p;
        +      }
        +    }
        +    else if (noColor != true && COLR && COLR[0]["g" + gid] && COLR[0]["g" + gid][1] > 1) {
        +
        +      function toHex(n) { var o = n.toString(16); return (o.length == 1 ? "0" : "") + o; }
        +
        +      var CPAL = font["CPAL"], gl = COLR[0]["g" + gid];
        +      for (var i = 0; i < gl[1]; i++) {
        +        var lid = gl[0] + i;
        +        var cgl = COLR[1][2 * lid], pid = COLR[1][2 * lid + 1] * 4;
        +        var pth = glyphToPath(font, cgl, cgl == gid);
        +
        +        var col = "#" + toHex(CPAL[pid + 2]) + toHex(CPAL[pid + 1]) + toHex(CPAL[pid + 0]);
        +        path.cmds.push(col);
        +
        +        path.cmds = path.cmds.concat(pth["cmds"]);
        +        path.crds = path.crds.concat(pth["crds"]);
        +        //console.log(gid, cgl,pid,col);
        +
        +        path.cmds.push("X");
        +      }
        +    }
        +    else if (CFF) {
        +      var pdct = CFF["Private"];
        +      var state = { x: 0, y: 0, stack: [], nStems: 0, haveWidth: false, width: pdct ? pdct["defaultWidthX"] : 0, open: false };
        +      if (CFF["ROS"]) {
        +        var gi = 0;
        +        while (CFF["FDSelect"][gi + 2] <= gid) gi += 2;
        +        pdct = CFF["FDArray"][CFF["FDSelect"][gi + 1]]["Private"];
        +      }
        +      _drawCFF(CFF["CharStrings"][gid], state, CFF, pdct, path);
        +    }
        +    else if (font["glyf"]) { _drawGlyf(gid, font, path, axs); }
        +    return { "cmds": path.cmds, "crds": path.crds };
        +  }
        +
        +  function _drawGlyf(gid, font, path, axs) {
        +    var gl = font["glyf"][gid];
        +
        +    if (gl == null) gl = font["glyf"][gid] = Typr["T"].glyf._parseGlyf(font, gid);
        +    if (gl != null) {
        +      if (gl.noc > -1) _simpleGlyph(gl, font, gid, path, axs);
        +      else _compoGlyph(gl, font, gid, path, axs);
        +    }
        +  }
        +  function _interpolate(axs, v) {
        +    var acnt = v.length, S = 1;
        +    var s = axs[0];  // start
        +    var p = axs[1];  // peak
        +    var e = axs[2];  // end
        +
        +    for (var i = 0; i < v.length; i++) {
        +      var AS = 1;
        +      if (s[i] > p[i] || p[i] > e[i]) AS = 1;
        +      else if (s[i] < 0 && e[i] > 0 && p[i] != 0) AS = 1;
        +      else if (p[i] == 0) AS = 1;
        +      else if (v[i] < s[i] || v[i] > e[i]) AS = 0;
        +      else {
        +        if (v[i] == p[i]) AS = 1;
        +        else if (v[i] < p[i]) AS = (v[i] - s[i]) / (p[i] - s[i]);
        +        else AS = (e[i] - v[i]) / (e[i] - p[i]);
        +      }
        +      S = S * AS;
        +    }
        +    return S;
        +  }
        +  function _normalizeAxis(font, vv) {
        +    var fvar = font["fvar"], avar = font["avar"];
        +    var fv = fvar ? fvar[0] : null;
        +
        +    var nv = [];
        +    for (var i = 0; i < fv.length; i++) {
        +      var min = fv[i][1], def = fv[i][2], max = fv[i][3], v = Math.max(min, Math.min(max, vv[i]));
        +      if (v < def) nv[i] = (def - v) / (min - def);
        +      else if (v > def) nv[i] = (v - def) / (max - def);
        +      else nv[i] = 0;
        +
        +      if (avar && nv[i] != -1) {
        +        var av = avar[i], j = 0;
        +        for (; j < av.length; j += 2) if (av[j] >= nv[i]) break;
        +        var f = (nv[i] - av[j - 2]) / (av[j] - av[j - 2]);
        +        nv[i] = f * av[j + 1] + (1 - f) * av[j - 1];
        +      }
        +
        +    }
        +    return nv;
        +  }
        +  function interpolateDeltas(dfs, ind, xs, ys, endPts) {
        +    var N = xs.length, ndfs = new Array(N * 2 + 8); ndfs.fill(0);
        +    for (var i = 0; i < N; i++) {
        +      var dx = 0, dy = 0, ii = ind.indexOf(i);
        +      if (ii != -1) { dx = dfs[ii]; dy = dfs[ind.length + ii]; }
        +      else {
        +        var cmp = 0; while (endPts[cmp] < i) cmp++;
        +        var cmp0 = cmp == 0 ? 0 : endPts[cmp - 1] + 1;
        +        var cmp1 = endPts[cmp];
        +
        +        var i0 = -1, i1 = -1;
        +
        +        for (var j = 0; j < ind.length; j++) { var v = ind[j]; if (v < cmp0 || v > cmp1 || v >= N) continue; i0 = j; if (i1 == -1) i1 = j; }
        +        for (var j = 0; j < ind.length; j++) { var v = ind[j]; if (v < cmp0 || v > cmp1 || v >= N) continue; if (v < i) i0 = j; if (i < v) { i1 = j; break; } }
        +
        +        //var i0 = ind.length-1, i1=0;  if(ind[i0]>=N) i0--;
        +        //for(var j=0; j<ind.length; j++) {  var v=ind[j];  if(v<N) { if(v<i) i0=j;  if(i<v) {  i1=j;  break;  }  }  }
        +        for (var ax = 0; ax < 2; ax++) {
        +          var crd = ax == 0 ? xs : ys, ofs = ax * ind.length, dlt = 0;
        +          var c0 = crd[ind[i0]], c1 = crd[ind[i1]], cC = crd[i];
        +          var d0 = dfs[ofs + i0], d1 = dfs[ofs + i1];
        +
        +          if (c0 == c1) {
        +            if (d0 == d1) dlt = d0;
        +            else dlt = 0;
        +          }
        +          else {
        +            if (cC <= Math.min(c0, c1)) {
        +              if (c0 < c1) dlt = d0;
        +              else dlt = d1;
        +            }
        +            else if (Math.max(c0, c1) <= cC) {
        +              if (c0 < c1) dlt = d1;
        +              else dlt = d0;
        +            }
        +            else {
        +              var prop = (cC - c0) / (c1 - c0);  //if(prop<0) throw "e";
        +              dlt = prop * d1 + (1 - prop) * d0;
        +            }
        +          }
        +          if (ax == 0) dx = dlt; else dy = dlt;
        +        }
        +      }
        +      ndfs[i] = dx; ndfs[N + 4 + i] = dy;
        +    }
        +    return ndfs;
        +  }
        +  function _simpleGlyph(gl, font, gid, p, axs) {
        +    var xs = gl.xs, ys = gl.ys;
        +    //*
        +    if (font["fvar"] && axs) {
        +      xs = xs.slice(0); ys = ys.slice(0);
        +      var gvar = font["gvar"];
        +      var gv = gvar ? gvar[gid] : null;
        +
        +      for (var vi = 0; vi < gv.length; vi++) {
        +        var axv = gv[vi][0];  //console.log(axs);
        +        var S = _interpolate(axv, axs); if (S < 1e-9) continue;
        +        var dfs = gv[vi][1], ind = gv[vi][2];  //if(dfs.length!=2*xs.length+8) throw "e";
        +        //console.log(vi,S,axv,ind,dfs);
        +        if (ind) { dfs = gv[vi][1] = interpolateDeltas(dfs, ind, xs, ys, gl.endPts); gv[vi][2] = null; }
        +        //if(ind==null)
        +        if (dfs.length == xs.length * 2 + 8)
        +          for (var i = 0; i < xs.length; i++) {
        +            xs[i] += S * dfs[i];
        +            ys[i] += S * dfs[i + xs.length + 4];
        +          }
        +      }
        +    } //*/
        +
        +    for (var c = 0; c < gl.noc; c++) {
        +      var i0 = (c == 0) ? 0 : (gl.endPts[c - 1] + 1);
        +      var il = gl.endPts[c];
        +
        +      for (var i = i0; i <= il; i++) {
        +        var pr = (i == i0) ? il : (i - 1);
        +        var nx = (i == il) ? i0 : (i + 1);
        +        var onCurve = gl.flags[i] & 1;
        +        var prOnCurve = gl.flags[pr] & 1;
        +        var nxOnCurve = gl.flags[nx] & 1;
        +
        +        var x = xs[i], y = ys[i];
        +
        +        if (i == i0) {
        +          if (onCurve) {
        +            if (prOnCurve) P.MoveTo(p, xs[pr], ys[pr]);
        +            else { P.MoveTo(p, x, y); continue;  /*  will do CurveTo at il  */ }
        +          }
        +          else {
        +            if (prOnCurve) P.MoveTo(p, xs[pr], ys[pr]);
        +            else P.MoveTo(p, Math.floor((xs[pr] + x) * 0.5), Math.floor((ys[pr] + y) * 0.5));
        +          }
        +        }
        +        if (onCurve) {
        +          if (prOnCurve) P.LineTo(p, x, y);
        +        }
        +        else {
        +          if (nxOnCurve) P.qCurveTo(p, x, y, xs[nx], ys[nx]);
        +          else P.qCurveTo(p, x, y, Math.floor((x + xs[nx]) * 0.5), Math.floor((y + ys[nx]) * 0.5));
        +        }
        +      }
        +      P.ClosePath(p);
        +    }
        +  }
        +  function _compoGlyph(gl, font, gid, p, axs) {
        +
        +    var dx = [0, 0, 0, 0, 0, 0], dy = [0, 0, 0, 0, 0, 0], ccnt = gl.parts.length;
        +
        +    if (font["fvar"] && axs) {
        +      var gvar = font["gvar"];
        +      var gv = gvar ? gvar[gid] : null;
        +      for (var vi = 0; vi < gv.length; vi++) {
        +        var axv = gv[vi][0];  //console.log(axs);
        +        var S = _interpolate(axv, axs); if (S < 1e-6) continue;
        +        var dfs = gv[vi][1], ind = gv[vi][2];  //if(dfs.length!=2*ccnt+8) throw "e";
        +        if (ind == null)
        +          for (var i = 0; i < ccnt; i++) {
        +            dx[i] += S * dfs[i];
        +            dy[i] += S * dfs[i + ccnt + 4];
        +          }
        +        else
        +          for (var j = 0; j < ind.length; j++) {
        +            var i = ind[j];
        +            dx[i] += S * dfs[0];
        +            dy[i] += S * dfs[0 + ccnt];
        +          }
        +      }
        +    }
        +
        +    for (var j = 0; j < ccnt; j++) {
        +      var path = { cmds: [], crds: [] };
        +      var prt = gl.parts[j];
        +      _drawGlyf(prt.glyphIndex, font, path, axs);
        +
        +      var m = prt.m, tx = m.tx + dx[j], ty = m.ty + dy[j];
        +      for (var i = 0; i < path.crds.length; i += 2) {
        +        var x = path.crds[i], y = path.crds[i + 1];
        +        p.crds.push(x * m.a + y * m.c + tx);   // not sure, probably right
        +        p.crds.push(x * m.b + y * m.d + ty);
        +      }
        +      for (var i = 0; i < path.cmds.length; i++) p.cmds.push(path.cmds[i]);
        +    }
        +  }
        +
        +  function pathToSVG(path, prec) {
        +    var cmds = path["cmds"], crds = path["crds"];
        +    if (prec == null) prec = 5;
        +    function num(v) { return parseFloat(v.toFixed(prec)); }
        +    function merge(o) {
        +      var no = [], lstF = false, lstC = "";
        +      for (var i = 0; i < o.length; i++) {
        +        var it = o[i], isF = (typeof it) == "number";
        +        if (!isF) { if (it == lstC && it.length == 1 && it != "m") continue; lstC = it; }  // move should not be merged (it actually means lineTo)
        +        if (lstF && isF && it >= 0) no.push(" ");
        +        no.push(it); lstF = isF;
        +      }
        +      return no.join("");
        +    }
        +
        +
        +    var out = [], co = 0, lmap = { "M": 2, "L": 2, "Q": 4, "C": 6 };
        +    var x = 0, y = 0, // perfect coords
        +      //dx=0, dy=0, // relative perfect coords
        +      //rx=0, ry=0, // relative rounded coords
        +      ex = 0, ey = 0, // error between perfect and output coords
        +      mx = 0, my = 0; // perfect coords of the last "Move"
        +
        +    for (var i = 0; i < cmds.length; i++) {
        +      var cmd = cmds[i], cc = (lmap[cmd] ? lmap[cmd] : 0);
        +
        +      var o0 = [], dx, dy, rx, ry;  // o1=[], cx, cy, ax,ay;
        +      if (cmd == "L") {
        +        dx = crds[co] - x; dy = crds[co + 1] - y;
        +        rx = num(dx + ex); ry = num(dy + ey);
        +        // if this "lineTo" leads to the starting point, and "Z" follows, do not output anything.
        +        if (cmds[i + 1] == "Z" && crds[co] == mx && crds[co + 1] == my) { rx = dx; ry = dy; }
        +        else if (rx == 0 && ry == 0) { }
        +        else if (rx == 0) o0.push("v", ry);
        +        else if (ry == 0) o0.push("h", rx);
        +        else { o0.push("l", rx, ry); }
        +      }
        +      else {
        +        o0.push(cmd.toLowerCase());
        +        for (var j = 0; j < cc; j += 2) {
        +          dx = crds[co + j] - x; dy = crds[co + j + 1] - y;
        +          rx = num(dx + ex); ry = num(dy + ey);
        +          o0.push(rx, ry);
        +        }
        +      }
        +      if (cc != 0) { ex += dx - rx; ey += dy - ry; }
        +
        +      var ou = o0;
        +      for (var j = 0; j < ou.length; j++) out.push(ou[j]);
        +
        +      if (cc != 0) { co += cc; x = crds[co - 2]; y = crds[co - 1]; }
        +      if (cmd == "M") { mx = x; my = y; }
        +      if (cmd == "Z") { x = mx; y = my; }
        +    }
        +
        +    return merge(out);
        +  }
        +  function SVGToPath(d) {
        +    var pth = { cmds: [], crds: [] };
        +    Typr["U"]["SVG"].svgToPath(d, pth);
        +    return { "cmds": pth.cmds, "crds": pth.crds };
        +  }
        +
        +  function mipmapB(buff, w, h, hlp) {
        +    var nw = w >> 1, nh = h >> 1;
        +    var nbuf = (hlp && hlp.length == nw * nh * 4) ? hlp : new Uint8Array(nw * nh * 4);
        +    var sb32 = new Uint32Array(buff.buffer), nb32 = new Uint32Array(nbuf.buffer);
        +    for (var y = 0; y < nh; y++)
        +      for (var x = 0; x < nw; x++) {
        +        var ti = (y * nw + x), si = ((y << 1) * w + (x << 1));
        +        //nbuf[ti  ] = buff[si  ];  nbuf[ti+1] = buff[si+1];  nbuf[ti+2] = buff[si+2];  nbuf[ti+3] = buff[si+3];
        +        //*
        +        var c0 = sb32[si], c1 = sb32[si + 1], c2 = sb32[si + w], c3 = sb32[si + w + 1];
        +
        +        var a0 = (c0 >>> 24), a1 = (c1 >>> 24), a2 = (c2 >>> 24), a3 = (c3 >>> 24), a = (a0 + a1 + a2 + a3);
        +
        +        if (a == 1020) {
        +          var r = (((c0 >>> 0) & 255) + ((c1 >>> 0) & 255) + ((c2 >>> 0) & 255) + ((c3 >>> 0) & 255) + 2) >>> 2;
        +          var g = (((c0 >>> 8) & 255) + ((c1 >>> 8) & 255) + ((c2 >>> 8) & 255) + ((c3 >>> 8) & 255) + 2) >>> 2;
        +          var b = (((c0 >>> 16) & 255) + ((c1 >>> 16) & 255) + ((c2 >>> 16) & 255) + ((c3 >>> 16) & 255) + 2) >>> 2;
        +          nb32[ti] = (255 << 24) | (b << 16) | (g << 8) | r;
        +        }
        +        else if (a == 0) nb32[ti] = 0;
        +        else {
        +          var r = ((c0 >>> 0) & 255) * a0 + ((c1 >>> 0) & 255) * a1 + ((c2 >>> 0) & 255) * a2 + ((c3 >>> 0) & 255) * a3;
        +          var g = ((c0 >>> 8) & 255) * a0 + ((c1 >>> 8) & 255) * a1 + ((c2 >>> 8) & 255) * a2 + ((c3 >>> 8) & 255) * a3;
        +          var b = ((c0 >>> 16) & 255) * a0 + ((c1 >>> 16) & 255) * a1 + ((c2 >>> 16) & 255) * a2 + ((c3 >>> 16) & 255) * a3;
        +
        +          var ia = 1 / a; r = ~~(r * ia + 0.5); g = ~~(g * ia + 0.5); b = ~~(b * ia + 0.5);
        +          nb32[ti] = (((a + 2) >>> 2) << 24) | (b << 16) | (g << 8) | r;
        +        }
        +      }
        +    return { buff: nbuf, w: nw, h: nh };
        +  }
        +
        +  var __cnv, __ct;
        +  function pathToContext(path, ctx) {
        +    var c = 0, cmds = path["cmds"], crds = path["crds"];
        +
        +    //ctx.translate(3500,500);  ctx.rotate(0.25);  ctx.scale(1,-1);
        +
        +    for (var j = 0; j < cmds.length; j++) {
        +      var cmd = cmds[j];
        +      if (cmd == "M") {
        +        ctx.moveTo(crds[c], crds[c + 1]);
        +        c += 2;
        +      }
        +      else if (cmd == "L") {
        +        ctx.lineTo(crds[c], crds[c + 1]);
        +        c += 2;
        +      }
        +      else if (cmd == "C") {
        +        ctx.bezierCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3], crds[c + 4], crds[c + 5]);
        +        c += 6;
        +      }
        +      else if (cmd == "Q") {
        +        ctx.quadraticCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3]);
        +        c += 4;
        +      }
        +      else if (cmd[0] == "d") {
        +        var upng = window["UPNG"];
        +        var x0 = crds[c], y0 = crds[c + 1], x1 = crds[c + 2], y1 = crds[c + 3], x2 = crds[c + 4], y2 = crds[c + 5], x3 = crds[c + 6], y3 = crds[c + 7]; c += 8;
        +        //y0+=400;  y1+=400;  y1+=600;
        +        if (upng == null) {
        +          ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.lineTo(x2, y2); ctx.lineTo(x3, y3); ctx.closePath();
        +          continue;
        +        }
        +        var dx0 = (x1 - x0), dy0 = (y1 - y0), dx1 = (x3 - x0), dy1 = (y3 - y0);
        +        var sbmp = atob(cmd.slice(22));
        +        var bmp = new Uint8Array(sbmp.length);
        +        for (var i = 0; i < sbmp.length; i++) bmp[i] = sbmp.charCodeAt(i);
        +
        +        var img = upng["decode"](bmp.buffer), w = img["width"], h = img["height"];  //console.log(img);
        +
        +        var nbmp = new Uint8Array(upng["toRGBA8"](img)[0]);
        +        var tr = ctx["getTransform"]();
        +        var scl = Math.sqrt(Math.abs(tr["a"] * tr["d"] - tr["b"] * tr["c"])) * Math.sqrt(dx1 * dx1 + dy1 * dy1) / h;
        +        while (scl < 0.5) {
        +          var nd = mipmapB(nbmp, w, h);
        +          nbmp = nd.buff; w = nd.w; h = nd.h; scl *= 2;
        +        }
        +
        +        if (__cnv == null) { __cnv = document.createElement("canvas"); __ct = __cnv.getContext("2d"); }
        +        if (__cnv.width != w || __cnv.height != h) { __cnv.width = w; __cnv.height = h; }
        +
        +        __ct.putImageData(new ImageData(new Uint8ClampedArray(nbmp.buffer), w, h), 0, 0);
        +        ctx.save();
        +        ctx.transform(dx0, dy0, dx1, dy1, x0, y0);
        +        ctx.scale(1 / w, 1 / h);
        +        ctx.drawImage(__cnv, 0, 0); //*/
        +        ctx.restore();
        +      }
        +      else if (cmd.charAt(0) == "#" || cmd.charAt(0) == "r") {
        +        ctx.beginPath();
        +        ctx.fillStyle = cmd;
        +      }
        +      else if (cmd.charAt(0) == "O" && cmd != "OX") {
        +        ctx.beginPath();
        +        var pts = cmd.split("-");
        +        ctx.lineWidth = parseFloat(pts[2]);
        +        ctx.lineCap = ["butt", "round", "square"][parseFloat(pts[3])];
        +        ctx.lineJoin = ["miter", "round", "bevel"][parseFloat(pts[4])];
        +        ctx.miterLimit = parseFloat(pts[5]);
        +        ctx.lineDashOffset = parseFloat(pts[6]);
        +        ctx.setLineDash(pts[7].split(",").map(parseFloat));
        +        ctx.strokeStyle = pts[1];
        +      }
        +      else if (cmd == "Z") {
        +        ctx.closePath();
        +      }
        +      else if (cmd == "X") {
        +        ctx.fill();
        +      }
        +      else if (cmd == "OX") {
        +        ctx.stroke();
        +      }
        +    }
        +  }
        +
        +
        +  function _drawCFF(cmds, state, font, pdct, p) {
        +    var stack = state.stack;
        +    var nStems = state.nStems, haveWidth = state.haveWidth, width = state.width, open = state.open;
        +    var i = 0;
        +    var x = state.x, y = state.y, c1x = 0, c1y = 0, c2x = 0, c2y = 0, c3x = 0, c3y = 0, c4x = 0, c4y = 0, jpx = 0, jpy = 0;
        +    var CFF = Typr["T"].CFF;
        +
        +    var nominalWidthX = pdct["nominalWidthX"];
        +    var o = { val: 0, size: 0 };
        +    //console.log(cmds);
        +    while (i < cmds.length) {
        +      CFF.getCharString(cmds, i, o);
        +      var v = o.val;
        +      i += o.size;
        +
        +      if (false) { }
        +      else if (v == "o1" || v == "o18")  //  hstem || hstemhm
        +      {
        +        var hasWidthArg;
        +
        +        // The number of stem operators on the stack is always even.
        +        // If the value is uneven, that means a width is specified.
        +        hasWidthArg = stack.length % 2 !== 0;
        +        if (hasWidthArg && !haveWidth) {
        +          width = stack.shift() + nominalWidthX;
        +        }
        +
        +        nStems += stack.length >> 1;
        +        stack.length = 0;
        +        haveWidth = true;
        +      }
        +      else if (v == "o3" || v == "o23")  // vstem || vstemhm
        +      {
        +        var hasWidthArg;
        +
        +        // The number of stem operators on the stack is always even.
        +        // If the value is uneven, that means a width is specified.
        +        hasWidthArg = stack.length % 2 !== 0;
        +        if (hasWidthArg && !haveWidth) {
        +          width = stack.shift() + nominalWidthX;
        +        }
        +
        +        nStems += stack.length >> 1;
        +        stack.length = 0;
        +        haveWidth = true;
        +      }
        +      else if (v == "o4") {
        +        if (stack.length > 1 && !haveWidth) {
        +          width = stack.shift() + nominalWidthX;
        +          haveWidth = true;
        +        }
        +        if (open) P.ClosePath(p);
        +
        +        y += stack.pop();
        +        P.MoveTo(p, x, y); open = true;
        +      }
        +      else if (v == "o5") {
        +        while (stack.length > 0) {
        +          x += stack.shift();
        +          y += stack.shift();
        +          P.LineTo(p, x, y);
        +        }
        +      }
        +      else if (v == "o6" || v == "o7")  // hlineto || vlineto
        +      {
        +        var count = stack.length;
        +        var isX = (v == "o6");
        +
        +        for (var j = 0; j < count; j++) {
        +          var sval = stack.shift();
        +
        +          if (isX) x += sval; else y += sval;
        +          isX = !isX;
        +          P.LineTo(p, x, y);
        +        }
        +      }
        +      else if (v == "o8" || v == "o24")	// rrcurveto || rcurveline
        +      {
        +        var count = stack.length;
        +        var index = 0;
        +        while (index + 6 <= count) {
        +          c1x = x + stack.shift();
        +          c1y = y + stack.shift();
        +          c2x = c1x + stack.shift();
        +          c2y = c1y + stack.shift();
        +          x = c2x + stack.shift();
        +          y = c2y + stack.shift();
        +          P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
        +          index += 6;
        +        }
        +        if (v == "o24") {
        +          x += stack.shift();
        +          y += stack.shift();
        +          P.LineTo(p, x, y);
        +        }
        +      }
        +      else if (v == "o11") break;
        +      else if (v == "o1234" || v == "o1235" || v == "o1236" || v == "o1237")//if((v+"").slice(0,3)=="o12")
        +      {
        +        if (v == "o1234") {
        +          c1x = x + stack.shift();    // dx1
        +          c1y = y;                      // dy1
        +          c2x = c1x + stack.shift();    // dx2
        +          c2y = c1y + stack.shift();    // dy2
        +          jpx = c2x + stack.shift();    // dx3
        +          jpy = c2y;                    // dy3
        +          c3x = jpx + stack.shift();    // dx4
        +          c3y = c2y;                    // dy4
        +          c4x = c3x + stack.shift();    // dx5
        +          c4y = y;                      // dy5
        +          x = c4x + stack.shift();      // dx6
        +          P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
        +          P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
        +
        +        }
        +        if (v == "o1235") {
        +          c1x = x + stack.shift();    // dx1
        +          c1y = y + stack.shift();    // dy1
        +          c2x = c1x + stack.shift();    // dx2
        +          c2y = c1y + stack.shift();    // dy2
        +          jpx = c2x + stack.shift();    // dx3
        +          jpy = c2y + stack.shift();    // dy3
        +          c3x = jpx + stack.shift();    // dx4
        +          c3y = jpy + stack.shift();    // dy4
        +          c4x = c3x + stack.shift();    // dx5
        +          c4y = c3y + stack.shift();    // dy5
        +          x = c4x + stack.shift();      // dx6
        +          y = c4y + stack.shift();      // dy6
        +          stack.shift();                // flex depth
        +          P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
        +          P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
        +        }
        +        if (v == "o1236") {
        +          c1x = x + stack.shift();    // dx1
        +          c1y = y + stack.shift();    // dy1
        +          c2x = c1x + stack.shift();    // dx2
        +          c2y = c1y + stack.shift();    // dy2
        +          jpx = c2x + stack.shift();    // dx3
        +          jpy = c2y;                    // dy3
        +          c3x = jpx + stack.shift();    // dx4
        +          c3y = c2y;                    // dy4
        +          c4x = c3x + stack.shift();    // dx5
        +          c4y = c3y + stack.shift();    // dy5
        +          x = c4x + stack.shift();      // dx6
        +          P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
        +          P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
        +        }
        +        if (v == "o1237") {
        +          c1x = x + stack.shift();    // dx1
        +          c1y = y + stack.shift();    // dy1
        +          c2x = c1x + stack.shift();    // dx2
        +          c2y = c1y + stack.shift();    // dy2
        +          jpx = c2x + stack.shift();    // dx3
        +          jpy = c2y + stack.shift();    // dy3
        +          c3x = jpx + stack.shift();    // dx4
        +          c3y = jpy + stack.shift();    // dy4
        +          c4x = c3x + stack.shift();    // dx5
        +          c4y = c3y + stack.shift();    // dy5
        +          if (Math.abs(c4x - x) > Math.abs(c4y - y)) {
        +            x = c4x + stack.shift();
        +          } else {
        +            y = c4y + stack.shift();
        +          }
        +          P.CurveTo(p, c1x, c1y, c2x, c2y, jpx, jpy);
        +          P.CurveTo(p, c3x, c3y, c4x, c4y, x, y);
        +        }
        +      }
        +      else if (v == "o14") {
        +        if (stack.length > 0 && stack.length != 4 && !haveWidth) {
        +          width = stack.shift() + font["nominalWidthX"];
        +          haveWidth = true;
        +        }
        +        if (stack.length == 4) // seac = standard encoding accented character
        +        {
        +
        +          var asb = 0;
        +          var adx = stack.shift();
        +          var ady = stack.shift();
        +          var bchar = stack.shift();
        +          var achar = stack.shift();
        +
        +
        +          var bind = CFF.glyphBySE(font, bchar);
        +          var aind = CFF.glyphBySE(font, achar);
        +
        +          //console.log(bchar, bind);
        +          //console.log(achar, aind);
        +          //state.x=x; state.y=y; state.nStems=nStems; state.haveWidth=haveWidth; state.width=width;  state.open=open;
        +
        +          _drawCFF(font["CharStrings"][bind], state, font, pdct, p);
        +          state.x = adx; state.y = ady;
        +          _drawCFF(font["CharStrings"][aind], state, font, pdct, p);
        +
        +          //x=state.x; y=state.y; nStems=state.nStems; haveWidth=state.haveWidth; width=state.width;  open=state.open;
        +        }
        +        if (open) { P.ClosePath(p); open = false; }
        +      }
        +      else if (v == "o19" || v == "o20") {
        +        var hasWidthArg;
        +
        +        // The number of stem operators on the stack is always even.
        +        // If the value is uneven, that means a width is specified.
        +        hasWidthArg = stack.length % 2 !== 0;
        +        if (hasWidthArg && !haveWidth) {
        +          width = stack.shift() + nominalWidthX;
        +        }
        +
        +        nStems += stack.length >> 1;
        +        stack.length = 0;
        +        haveWidth = true;
        +
        +        i += (nStems + 7) >> 3;
        +      }
        +
        +      else if (v == "o21") {
        +        if (stack.length > 2 && !haveWidth) {
        +          width = stack.shift() + nominalWidthX;
        +          haveWidth = true;
        +        }
        +
        +        y += stack.pop();
        +        x += stack.pop();
        +
        +        if (open) P.ClosePath(p);
        +        P.MoveTo(p, x, y); open = true;
        +      }
        +      else if (v == "o22") {
        +        if (stack.length > 1 && !haveWidth) {
        +          width = stack.shift() + nominalWidthX;
        +          haveWidth = true;
        +        }
        +
        +        x += stack.pop();
        +
        +        if (open) P.ClosePath(p);
        +        P.MoveTo(p, x, y); open = true;
        +      }
        +      else if (v == "o25") {
        +        while (stack.length > 6) {
        +          x += stack.shift();
        +          y += stack.shift();
        +          P.LineTo(p, x, y);
        +        }
        +
        +        c1x = x + stack.shift();
        +        c1y = y + stack.shift();
        +        c2x = c1x + stack.shift();
        +        c2y = c1y + stack.shift();
        +        x = c2x + stack.shift();
        +        y = c2y + stack.shift();
        +        P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
        +      }
        +      else if (v == "o26") {
        +        if (stack.length % 2) {
        +          x += stack.shift();
        +        }
        +
        +        while (stack.length > 0) {
        +          c1x = x;
        +          c1y = y + stack.shift();
        +          c2x = c1x + stack.shift();
        +          c2y = c1y + stack.shift();
        +          x = c2x;
        +          y = c2y + stack.shift();
        +          P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
        +        }
        +
        +      }
        +      else if (v == "o27") {
        +        if (stack.length % 2) {
        +          y += stack.shift();
        +        }
        +
        +        while (stack.length > 0) {
        +          c1x = x + stack.shift();
        +          c1y = y;
        +          c2x = c1x + stack.shift();
        +          c2y = c1y + stack.shift();
        +          x = c2x + stack.shift();
        +          y = c2y;
        +          P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
        +        }
        +      }
        +      else if (v == "o10" || v == "o29")	// callsubr || callgsubr
        +      {
        +        var obj = (v == "o10" ? pdct : font);
        +        if (stack.length == 0) { console.log("error: empty stack"); }
        +        else {
        +          var ind = stack.pop();
        +          var subr = obj["Subrs"][ind + obj["Bias"]];
        +          state.x = x; state.y = y; state.nStems = nStems; state.haveWidth = haveWidth; state.width = width; state.open = open;
        +          _drawCFF(subr, state, font, pdct, p);
        +          x = state.x; y = state.y; nStems = state.nStems; haveWidth = state.haveWidth; width = state.width; open = state.open;
        +        }
        +      }
        +      else if (v == "o30" || v == "o31")   // vhcurveto || hvcurveto
        +      {
        +        var count, count1 = stack.length;
        +        var index = 0;
        +        var alternate = v == "o31";
        +
        +        count = count1 & ~2;
        +        index += count1 - count;
        +
        +        while (index < count) {
        +          if (alternate) {
        +            c1x = x + stack.shift();
        +            c1y = y;
        +            c2x = c1x + stack.shift();
        +            c2y = c1y + stack.shift();
        +            y = c2y + stack.shift();
        +            if (count - index == 5) { x = c2x + stack.shift(); index++; }
        +            else x = c2x;
        +            alternate = false;
        +          }
        +          else {
        +            c1x = x;
        +            c1y = y + stack.shift();
        +            c2x = c1x + stack.shift();
        +            c2y = c1y + stack.shift();
        +            x = c2x + stack.shift();
        +            if (count - index == 5) { y = c2y + stack.shift(); index++; }
        +            else y = c2y;
        +            alternate = true;
        +          }
        +          P.CurveTo(p, c1x, c1y, c2x, c2y, x, y);
        +          index += 4;
        +        }
        +      }
        +
        +      else if ((v + "").charAt(0) == "o") { console.log("Unknown operation: " + v, cmds); throw v; }
        +      else stack.push(v);
        +    }
        +    //console.log(cmds);
        +    state.x = x; state.y = y; state.nStems = nStems; state.haveWidth = haveWidth; state.width = width; state.open = open;
        +  }
        +
        +  function initHB(hurl, resp) {
        +    var codeLength = function (code) {
        +      var len = 0;
        +      if ((code & (0xffffffff - (1 << 7) + 1)) == 0) { len = 1; }
        +      else if ((code & (0xffffffff - (1 << 11) + 1)) == 0) { len = 2; }
        +      else if ((code & (0xffffffff - (1 << 16) + 1)) == 0) { len = 3; }
        +      else if ((code & (0xffffffff - (1 << 21) + 1)) == 0) { len = 4; }
        +      return len;
        +    }
        +
        +    fetch(hurl)
        +      .then(function (x) { return x["arrayBuffer"](); })
        +      .then(function (ab) { return WebAssembly["instantiate"](ab); })
        +      .then(function (res) {
        +        console.log("HB ready");
        +        var exp = res["instance"]["exports"], mem = exp["memory"];
        +        //mem["grow"](30); // each page is 64kb in size
        +        var heapu8, u32, i32, f32;
        +        var __lastFnt, blob, blobPtr, face, font;
        +
        +        Typr["U"]["shapeHB"] = (function () {
        +
        +          var toJson = function (ptr) {
        +            var length = exp["hb_buffer_get_length"](ptr);
        +            var result = [];
        +            var iPtr32 = exp["hb_buffer_get_glyph_infos"](ptr, 0) >>> 2;
        +            var pPtr32 = exp["hb_buffer_get_glyph_positions"](ptr, 0) >>> 2;
        +            for (var i = 0; i < length; ++i) {
        +              var a = iPtr32 + i * 5, b = pPtr32 + i * 5;
        +              result.push({
        +                "g": u32[a + 0],
        +                "cl": u32[a + 2],
        +                "ax": i32[b + 0],
        +                "ay": i32[b + 1],
        +                "dx": i32[b + 2],
        +                "dy": i32[b + 3]
        +              });
        +            }
        +            //console.log(result);
        +            return result;
        +          }
        +          var te;
        +
        +          return function (fnt, str, prm) {
        +            var fdata = fnt["_data"], fn = fnt["name"]["postScriptName"];
        +            var ltr = prm["ltr"], fts = prm["fts"], axs = prm["axs"];
        +            if (fnt["fvar"] && axs == null) axs = fnt["fvar"][1][fnt["_index"]][2];
        +
        +            //var olen = mem.buffer.byteLength, nlen = 2*fdata.length+str.length*16 + 4e6;
        +            //if(olen<nlen) mem["grow"](((nlen-olen)>>>16)+4);  //console.log("growing",nlen);
        +
        +            heapu8 = new Uint8Array(mem.buffer);
        +            u32 = new Uint32Array(mem.buffer);
        +            i32 = new Int32Array(mem.buffer);
        +            f32 = new Float32Array(mem.buffer);
        +
        +            if (__lastFnt != fn) {
        +              if (blob != null) {
        +                exp["hb_blob_destroy"](blob);
        +                exp["free"](blobPtr);
        +                exp["hb_face_destroy"](face);
        +                exp["hb_font_destroy"](font);
        +              }
        +              blobPtr = exp["malloc"](fdata.byteLength); heapu8.set(fdata, blobPtr);
        +              blob = exp["hb_blob_create"](blobPtr, fdata.byteLength, 2, 0, 0);
        +              face = exp["hb_face_create"](blob, fnt["_index"]);
        +              font = exp["hb_font_create"](face)
        +              __lastFnt = fn;
        +            }
        +            if (window["TextEncoder"] == null) { alert("Your browser is too old. Please, update it."); return; }
        +            if (te == null) te = new window["TextEncoder"]("utf8");
        +
        +            var buffer = exp["hb_buffer_create"]();
        +            var bytes = te["encode"](str);
        +            var len = bytes.length, strp = exp["malloc"](len); heapu8.set(bytes, strp);
        +            exp["hb_buffer_add_utf8"](buffer, strp, len, 0, len);
        +            exp["free"](strp);
        +
        +            var bin = Typr["B"];
        +
        +            var feat = 0;
        +            if (fts) {
        +              feat = exp["malloc"](16 * fts.length);
        +              for (var i = 0; i < fts.length; i++) {
        +                var fe = fts[i];
        +                var off = feat + i * 16, qo = off >>> 2;
        +                bin.writeASCII(heapu8, off, fe[0].split("").reverse().join(""));
        +                u32[qo + 1] = fe[1];
        +                u32[qo + 2] = fe[2];
        +                u32[qo + 3] = fe[3];
        +              }
        +              //console.log(fts);
        +            }
        +            var vdat = 0;
        +            if (axs && fnt["fvar"]) {
        +              var axes = fnt["fvar"][0];  //console.log(axes, axs);
        +              vdat = exp["malloc"](8 * axs.length);
        +              for (var i = 0; i < axs.length; i++) {
        +                var off = vdat + i * 8, qo = off >>> 2;
        +                bin.writeASCII(heapu8, off, axes[i][0].split("").reverse().join(""));
        +                f32[qo + 1] = axs[i];
        +              }
        +            }
        +            //*/
        +
        +            if (axs) exp["hb_font_set_variations"](font, vdat, axs.length);
        +            exp["hb_buffer_set_direction"](buffer, ltr ? 4 : 5);
        +            exp["hb_buffer_guess_segment_properties"](buffer);
        +            exp["hb_shape"](font, buffer, feat, fts ? fts.length : 0);
        +            var json = toJson(buffer)//buffer["json"]();
        +            exp["hb_buffer_destroy"](buffer);
        +            if (fts) exp["free"](feat);
        +            if (axs) exp["free"](vdat);
        +
        +            var arr = json.slice(0); if (!ltr) arr.reverse();
        +            var ci = 0, bi = 0;  // character index, binary index
        +            for (var i = 1; i < arr.length; i++) {
        +              var gl = arr[i], cl = gl["cl"];
        +              while (true) {
        +                var cpt = str.codePointAt(ci), cln = codeLength(cpt);
        +                if (bi + cln <= cl) { bi += cln; ci += cpt <= 0xffff ? 1 : 2; }
        +                else break;
        +              }
        +              //while(bi+codeLength(str.charCodeAt(ci)) <=cl) {  bi+=codeLength(str.charCodeAt(ci));  ci++;  }
        +              gl["cl"] = ci;
        +            }
        +            return json;
        +          }
        +        }());
        +        resp();
        +      });
        +  }
        +
        +  return { "shape": shape, "shapeToPath": shapeToPath, "codeToGlyph": codeToGlyph, "glyphToPath": glyphToPath, "pathToSVG": pathToSVG, "SVGToPath": SVGToPath, "pathToContext": pathToContext, "initHB": initHB };
        +}();
        +
        +
        +export default Typr;
        \ No newline at end of file
        diff --git a/src/type/p5.Font.js b/src/type/p5.Font.js
        new file mode 100644
        index 0000000000..ffd984b829
        --- /dev/null
        +++ b/src/type/p5.Font.js
        @@ -0,0 +1,795 @@
        +/**
        + * API:
        + *    loadFont("https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&display=swap")
        + *    loadFont("@font-face { font-family: "Bricolage Grotesque", serif; font-optical-sizing: auto; font-weight: <weight> font-style: normal; font-variation-settings: "wdth" 100; });
        + *    loadFont({
        + *        fontFamily: '"Bricolage Grotesque", serif';
        + *        fontOpticalSizing: 'auto';
        + *        fontWeight: '<weight>';
        + *        fontStyle: 'normal';
        + *        fontVariationSettings: '"wdth" 100';
        + *    });
        + *    loadFont("https://fonts.gstatic.com/s/bricolagegrotesque/v1/pxiAZBhjZQIdd8jGnEotWQ.woff2");
        + *    loadFont("./path/to/localFont.ttf");
        + *    loadFont("system-font-name");
        + *
        + *
        + *   NEXT:
        + *     extract axes from font file
        + *
        + *   TEST:
        + *    const font = new FontFace("Inter", "url(./fonts/inter-latin-variable-full-font.woff2)", {
        +        style: "oblique 0deg 10deg",
        +        weight: "100 900",
        +        display: 'fallback'
        +      });
        +*/
        +
        +/**
        + * This module defines the <a href="#/p5.Font">p5.Font</a> class and p5 methods for
        + * loading fonts from files and urls, and extracting points from their paths.
        + */
        +
        +import Typr from './lib/Typr.js';
        +
        +import { createFromCommands } from '@davepagurek/bezier-path';
        +
        +function font(p5, fn) {
        +
        +  const pathArgCounts = { M: 2, L: 2, C: 6, Q: 4 };
        +  const validFontTypes = ['ttf', 'otf', 'woff'];//, 'woff2'];
        +  const validFontTypesRe = new RegExp(`\\.(${validFontTypes.join('|')})`, 'i');
        +  const extractFontNameRe = new RegExp(`([^/]+)(\\.(?:${validFontTypes.join('|')}))`, 'i');
        +  const invalidFontError = 'Sorry, only TTF, OTF and WOFF files are supported.'; // and WOFF2
        +  const fontFaceVariations = ['weight', 'stretch', 'style'];
        +
        +  p5.Font = class Font {
        +
        +    constructor(p, fontFace, name, path, data) {
        +      if (!(fontFace instanceof FontFace)) {
        +        throw Error('FontFace is required');
        +      }
        +      this._pInst = p;
        +      this.name = name;
        +      this.path = path;
        +      this.data = data;
        +      this.face = fontFace;
        +    }
        +
        +    fontBounds(str, x, y, width, height, options) {
        +      ({ width, height, options } = this._parseArgs(width, height, options));
        +      let renderer = options?.graphics?._renderer || this._pInst._renderer;
        +      if (!renderer) throw Error('p5 or graphics required for fontBounds()');
        +      return renderer.fontBounds(str, x, y, width, height);
        +    }
        +
        +    textBounds(str, x, y, width, height, options) {
        +      ({ width, height, options } = this._parseArgs(width, height, options));
        +      let renderer = options?.graphics?._renderer || this._pInst._renderer;
        +      if (!renderer) throw Error('p5 or graphics required for fontBounds()');
        +      return renderer.textBounds(str, x, y, width, height);
        +    }
        +
        +    textToPaths(str, x, y, width, height, options) {
        +
        +      ({ width, height, options } = this._parseArgs(width, height, options));
        +
        +      if (!this.data) {
        +        throw Error('No font data available for "' + this.name
        +          + '"\nTry downloading a local copy of the font file');
        +      }
        +
        +      // lineate and get glyphs/paths for each line
        +      let lines = this._lineateAndPathify(str, x, y, width, height, options);
        +
        +      // flatten into a single array containing all the glyphs
        +      let glyphs = lines.map(o => o.glyphs).flat();
        +
        +      // flatten into a single array with all the path commands
        +      return glyphs.map(g => g.path.commands).flat();
        +    }
        +
        +    textToPoints(str, x, y, width, height, options) {
        +      // By segmenting per contour, pointAtLength becomes much faster
        +      const contourPoints = this.textToContours(str, x, y, width, height, options);
        +      return contourPoints.reduce((acc, next) => {
        +        acc.push(...next);
        +        return acc;
        +      }, []);
        +    }
        +
        +    textToContours(str, x = 0, y = 0, width, height, options) {
        +      ({ width, height, options } = this._parseArgs(width, height, options));
        +
        +      const cmds = this.textToPaths(str, x, y, width, height, options);
        +      const cmdContours = [];
        +      for (const cmd of cmds) {
        +        if (cmd[0] === 'M') {
        +          cmdContours.push([]);
        +        }
        +        cmdContours[cmdContours.length - 1].push(cmd);
        +      }
        +
        +      return cmdContours.map((commands) => pathToPoints(commands, options, this));
        +    }
        +
        +    textToModel(str, x, y, width, height, options) {
        +      ({ width, height, options } = this._parseArgs(width, height, options));
        +      const extrude = options?.extrude || 0;
        +      const contours = this.textToContours(str, x, y, width, height, options);
        +      const geom = this._pInst.buildGeometry(() => {
        +        if (extrude === 0) {
        +          this._pInst.beginShape();
        +          this._pInst.normal(0, 0, 1);
        +          for (const contour of contours) {
        +            this._pInst.beginContour();
        +            for (const { x, y } of contour) {
        +              this._pInst.vertex(x, y);
        +            }
        +            this._pInst.endContour(this._pInst.CLOSE);
        +          }
        +          this._pInst.endShape();
        +        } else {
        +          // Draw front faces
        +          for (const side of [1, -1]) {
        +            this._pInst.beginShape();
        +            for (const contour of contours) {
        +              this._pInst.beginContour();
        +              for (const { x, y } of contour) {
        +                this._pInst.vertex(x, y, side * extrude * 0.5);
        +              }
        +              this._pInst.endContour(this._pInst.CLOSE);
        +            }
        +            this._pInst.endShape();
        +            this._pInst.beginShape();
        +          }
        +          // Draw sides
        +          for (const contour of contours) {
        +            this._pInst.beginShape(this._pInst.QUAD_STRIP);
        +            for (const v of contour) {
        +              for (const side of [-1, 1]) {
        +                this._pInst.vertex(v.x, v.y, side * extrude * 0.5);
        +              }
        +            }
        +            this._pInst.endShape();
        +          }
        +        }
        +      });
        +      if (extrude !== 0) {
        +        geom.computeNormals();
        +        for (const face of geom.faces) {
        +          if (face.every((idx) => geom.vertices[idx].z <= -extrude * 0.5 + 0.1)) {
        +            for (const idx of face) geom.vertexNormals[idx].set(0, 0, -1);
        +            face.reverse();
        +          }
        +        }
        +      }
        +      return geom;
        +    }
        +
        +    variations() {
        +      let vars = {};
        +      if (this.data) {
        +        let axes = this.face?.axes;
        +        if (axes) {
        +          axes.forEach(ax => {
        +            vars[ax.tag] = ax.value;
        +          });
        +        }
        +      }
        +      fontFaceVariations.forEach(v => {
        +        let val = this.face[v];
        +        if (val !== 'normal') {
        +          vars[v] = vars[v] || val;
        +        }
        +      });
        +      return vars;
        +    }
        +
        +    metadata() {
        +      let meta = this.data?.name || {};
        +      for (let p in this.face) {
        +        if (!/^load/.test(p)) {
        +          meta[p] = meta[p] || this.face[p];
        +        }
        +      }
        +      return meta;
        +    }
        +
        +    static async list(log = false) { // tmp
        +      if (log) {
        +        console.log('There are', document.fonts.size, 'font-faces\n');
        +        let loaded = 0;
        +        for (let fontFace of document.fonts.values()) {
        +          console.log('FontFace: {');
        +          for (let property in fontFace) {
        +            console.log('  ' + property + ': ' + fontFace[property]);
        +          }
        +          console.log('}\n');
        +          if (fontFace.status === 'loaded') {
        +            loaded++;
        +          }
        +        }
        +        console.log(loaded + ' loaded');
        +      }
        +      return await Array.from(document.fonts);
        +    }
        +
        +    /////////////////////////////// HELPERS ////////////////////////////////
        +
        +    _verticalAlign(size) {
        +      const { sCapHeight } = this.data?.['OS/2'] || {};
        +      const { unitsPerEm = 1000 } = this.data?.head || {};
        +      const { ascender = 0, descender = 0 } = this.data?.hhea || {};
        +      const current = ascender / 2;
        +      const target = (sCapHeight || (ascender + descender)) / 2;
        +      const offset = target - current;
        +      return offset * size / unitsPerEm;
        +    }
        +
        +    /*
        +      Returns an array of line objects, each containing { text, x, y, glyphs: [ {g, path} ] }
        +    */
        +    _lineateAndPathify(str, x, y, width, height, options = {}) {
        +
        +      let renderer = options?.graphics?._renderer || this._pInst._renderer;
        +
        +      // save the baseline
        +      let setBaseline = renderer.drawingContext.textBaseline;
        +
        +      // lineate and compute bounds for the text
        +      let { lines, bounds } = renderer._computeBounds
        +        (fn._FONT_BOUNDS, str, x, y, width, height,
        +          { ignoreRectMode: true, ...options });
        +
        +      // compute positions for each of the lines
        +      lines = this._position(renderer, lines, bounds, width, height);
        +
        +      // convert lines to paths
        +      let uPE = this.data?.head?.unitsPerEm || 1000;
        +      let scale = renderer.states.textSize / uPE;
        +      let pathsForLine = lines.map(l => this._lineToGlyphs(l, scale));
        +
        +      // restore the baseline
        +      renderer.drawingContext.textBaseline = setBaseline;
        +
        +      return pathsForLine;
        +    }
        +
        +    _textToPathPoints(str, x, y, width, height, options) {
        +
        +      ({ width, height, options } = this._parseArgs(width, height, options));
        +
        +      // lineate and get the points for each line
        +      let cmds = this.textToPaths(str, x, y, width, height, options);
        +
        +      // divide line-segments with intermediate points
        +      const subdivide = (pts, pt1, pt2, md) => {
        +        if (fn.dist(pt1.x, pt1.y, pt2.x, pt2.y) > md) {
        +          let middle = { x: (pt1.x + pt2.x) / 2, y: (pt1.y + pt2.y) / 2 };
        +          pts.push(middle);
        +          subdivide(pts, pt1, middle, md);
        +          subdivide(pts, middle, pt2, md);
        +        }
        +      }
        +
        +      // a point for each path-command plus line subdivisions
        +      let pts = [];
        +      let { textSize } = this._pInst._renderer.states;
        +      let maxDist = (textSize / this.data.head.unitsPerEm) * 500;
        +
        +      for (let i = 0; i < cmds.length; i++) {
        +        let { type, data: d } = cmds[i];
        +        if (type !== 'Z') {
        +          let pt = { x: d[d.length - 2], y: d[d.length - 1] }
        +          if (type === 'L' && pts.length && !options?.nodivide > 0) {
        +            subdivide(pts, pts[pts.length - 1], pt, maxDist);
        +          }
        +          pts.push(pt);
        +        }
        +      }
        +
        +      return pts;
        +    }
        +
        +    _parseArgs(width, height, options = {}) {
        +
        +      if (typeof width === 'object') {
        +        options = width;
        +        width = height = undefined;
        +      }
        +      else if (typeof height === 'object') {
        +        options = height;
        +        height = undefined;
        +      }
        +      return { width, height, options };
        +    }
        +
        +    _position(renderer, lines, bounds, width, height) {
        +
        +      let { textAlign, textLeading } = renderer.states;
        +      let metrics = this._measureTextDefault(renderer, 'X');
        +      let ascent = metrics.fontBoundingBoxAscent;
        +
        +      let coordify = (text, i) => {
        +        let x = bounds.x;
        +        let y = bounds.y + (i * textLeading) + ascent;
        +        let lineWidth = renderer._fontWidthSingle(text);
        +        if (textAlign === fn.CENTER) {
        +          x += (bounds.w - lineWidth) / 2;
        +        }
        +        else if (textAlign === fn.RIGHT) {
        +          x += (bounds.w - lineWidth);
        +        }
        +        if (typeof width !== 'undefined') {
        +          switch (renderer.states.rectMode) {
        +            case fn.CENTER:
        +              x -= width / 2;
        +              y -= height / 2;
        +              break;
        +            case fn.RADIUS:
        +              x -= width;
        +              y -= height;
        +              break;
        +          }
        +        }
        +        return { text, x, y };
        +      }
        +
        +      return lines.map(coordify);
        +    }
        +
        +    _lineToGlyphs(line, scale = 1) {
        +
        +      if (!this.data) {
        +        throw Error('No font data available for "' + this.name
        +          + '"\nTry downloading a local copy of the font file');
        +      }
        +      let glyphShapes = Typr.U.shape(this.data, line.text);
        +      line.glyphShapes = glyphShapes;
        +      line.glyphs = this._shapeToPaths(glyphShapes, line, scale);
        +
        +      return line;
        +    }
        +
        +    _positionGlyphs(text) {
        +      const glyphShapes = Typr.U.shape(this.data, text);
        +      const positionedGlyphs = [];
        +      let x = 0;
        +      for (const glyph of glyphShapes) {
        +        positionedGlyphs.push({ x, index: glyph.g, shape: glyph });
        +        x += glyph.ax;
        +      }
        +      return positionedGlyphs;
        +    }
        +
        +    _singleShapeToPath(shape, { scale = 1, x = 0, y = 0, lineX = 0, lineY = 0 } = {}) {
        +      let font = this.data;
        +      let crdIdx = 0;
        +      let { g, ax, ay, dx, dy } = shape;
        +      let { crds, cmds } = Typr.U.glyphToPath(font, g);
        +
        +      // can get simple points for each glyph here, but we don't need them ?
        +      let glyph = { /*g: line.text[i], points: [],*/ path: { commands: [] } };
        +
        +      for (let j = 0; j < cmds.length; j++) {
        +        let type = cmds[j], command = [type];
        +        if (type in pathArgCounts) {
        +          let argCount = pathArgCounts[type];
        +          for (let k = 0; k < argCount; k += 2) {
        +            let gx = crds[k + crdIdx] + x + dx;
        +            let gy = crds[k + crdIdx + 1] + y + dy;
        +            let fx = lineX + gx * scale;
        +            let fy = lineY + gy * -scale;
        +            command.push(fx);
        +            command.push(fy);
        +            /*if (k === argCount - 2) {
        +              glyph.points.push({ x: fx, y: fy });
        +            }*/
        +          }
        +          crdIdx += argCount;
        +        }
        +        glyph.path.commands.push(command);
        +      }
        +
        +      return { glyph, ax, ay };
        +    }
        +
        +    _shapeToPaths(glyphs, line, scale = 1) {
        +      let x = 0, y = 0, paths = [];
        +
        +      if (glyphs.length !== line.text.length) {
        +        throw Error('Invalid shape data');
        +      }
        +
        +      // iterate over the glyphs, converting each to a glyph object
        +      // with a path property containing an array of commands
        +      for (let i = 0; i < glyphs.length; i++) {
        +        const { glyph, ax, ay } = this._singleShapeToPath(glyphs[i], {
        +          scale,
        +          x,
        +          y,
        +          lineX: line.x,
        +          lineY: line.y,
        +        });
        +
        +        paths.push(glyph);
        +        x += ax; y += ay;
        +      }
        +
        +      return paths;
        +    }
        +
        +    _measureTextDefault(renderer, str) {
        +      let { textAlign, textBaseline } = renderer.states;
        +      let ctx = renderer.textDrawingContext();
        +      ctx.textAlign = 'left';
        +      ctx.textBaseline = 'alphabetic';
        +      let metrics = ctx.measureText(str);
        +      ctx.textAlign = textAlign;
        +      ctx.textBaseline = textBaseline;
        +      return metrics;
        +    }
        +
        +    drawPaths(ctx, commands, opts) { // for debugging
        +      ctx.strokeStyle = opts?.stroke || ctx.strokeStyle;
        +      ctx.fillStyle = opts?.fill || ctx.fillStyle;
        +      ctx.beginPath();
        +      commands.forEach(([type, ...data]) => {
        +        if (type === 'M') {
        +          ctx.moveTo(...data);
        +        } else if (type === 'L') {
        +          ctx.lineTo(...data);
        +        } else if (type === 'C') {
        +          ctx.bezierCurveTo(...data);
        +        } else if (type === 'Q') {
        +          ctx.quadraticCurveTo(...data);
        +        } else if (type === 'Z') {
        +          ctx.closePath();
        +        }
        +      });
        +      if (opts?.fill) ctx.fill();
        +      if (opts?.stroke) ctx.stroke();
        +    }
        +
        +    _pathsToCommands(paths, scale) {
        +      let commands = [];
        +      for (let i = 0; i < paths.length; i++) {
        +        let pathData = paths[i];
        +        let { x, y, path } = pathData;
        +        let { crds, cmds } = path;
        +
        +        // iterate over the path, storing each non-control point
        +        for (let c = 0, j = 0; j < cmds.length; j++) {
        +          let cmd = cmds[j], obj = { type: cmd, data: [] };
        +          if (cmd == "M" || cmd == "L") {
        +            obj.data.push(x + crds[c] * scale, y + crds[c + 1] * -scale);
        +            c += 2;
        +          }
        +          else if (cmd == "C") {
        +            for (let i = 0; i < 6; i += 2) {
        +              obj.data.push(x + crds[c + i] * scale, y + crds[c + i + 1] * -scale);
        +            }
        +            c += 6;
        +          }
        +          else if (cmd == "Q") {
        +            for (let i = 0; i < 4; i += 2) {
        +              obj.data.push(x + crds[c + i] * scale, y + crds[c + i + 1] * -scale);
        +            }
        +            c += 4;
        +          }
        +          commands.push(obj);
        +        }
        +      }
        +
        +      return commands;
        +    }
        +  }// end p5.Font
        +
        +  function parseCreateArgs(...args/*path, name, onSuccess, onError*/) {
        +
        +    // parse the path
        +    let path = args.shift();
        +    if (typeof path !== 'string' || path.length === 0) {
        +      p5._friendlyError(invalidFontError, 'p5.loadFont'); // ?
        +    }
        +
        +    // parse the name
        +    let name;
        +    if (typeof args[0] === 'string') {
        +      name = args.shift();
        +    }
        +
        +    // get the callbacks/descriptors if any
        +    let success, error, descriptors;
        +    for (let i = 0; i < args.length; i++) {
        +      const arg = args[i];
        +      if (typeof arg === 'function') {
        +        if (!success) {
        +          success = arg;
        +        } else {
        +          error = arg;
        +        }
        +      }
        +      else if (typeof arg === 'object') {
        +        descriptors = arg;
        +      }
        +    }
        +
        +    return { path, name, success, error, descriptors };
        +  }
        +
        +  /**
        +   * Load a font and returns a p5.Font instance. The font can be specified by its path or a url.
        +   * Optional arguments include the font name, descriptors for the FontFace object,
        +   * and callbacks for success and error.
        +   * @param  {...any} args - path, name, onSuccess, onError, descriptors
        +   * @returns a Promise that resolves with a p5.Font instance
        +   */
        +
        +  p5.prototype.loadFont = async function (...args/*path, name, onSuccess, onError, descriptors*/) {
        +
        +    let { path, name, success, error, descriptors } = parseCreateArgs(...args);
        +
        +    let isCSS = path.includes('@font-face');
        +
        +    if (!isCSS) {
        +      const info = await fetch(path, { method: 'HEAD' });
        +      const isCSSFile = info.headers.get('content-type')?.startsWith('text/css');
        +      if (isCSSFile) {
        +        isCSS = true;
        +        path = await fetch(path).then((res) => res.text());
        +      }
        +    }
        +
        +    if (isCSS) {
        +      const stylesheet = new CSSStyleSheet();
        +      await stylesheet.replace(path);
        +      const fontPromises = [];
        +      for (const rule of stylesheet.cssRules) {
        +        if (rule instanceof CSSFontFaceRule) {
        +          const style = rule.style;
        +          let name = unquote(style.getPropertyValue('font-family'));
        +          const src = style.getPropertyValue('src');
        +          const fontDescriptors = { ...(descriptors || {}) };
        +          for (const key of style) {
        +            if (key === 'font-family' || key === 'src') continue;
        +            const camelCaseKey = key
        +              .replace(/^font-/, '')
        +              .split('-')
        +              .map((v, i) => i === 0 ? v : `${v[0].toUpperCase()}${v.slice(1)}`)
        +              .join('');
        +            fontDescriptors[camelCaseKey] = style.getPropertyValue(key);
        +          }
        +          fontPromises.push(create(this, name, src, fontDescriptors));
        +        }
        +      }
        +      const fonts = await Promise.all(fontPromises);
        +      return fonts[0]; // TODO: handle multiple faces?
        +    }
        +
        +    let pfont;
        +    try {
        +      // load the raw font bytes
        +      let result = await fn.loadBytes(path);
        +      //console.log('result:', result);
        +
        +      if (!result) {
        +        throw Error('Failed to load font data');
        +      }
        +
        +      // parse the font data
        +      let fonts = Typr.parse(result);
        +
        +      // TODO: generate descriptors from font in the future
        +
        +      if (fonts.length !== 1 || fonts[0].cmap === undefined) {
        +        throw Error('parsing font data');
        +      }
        +
        +      // make sure we have a valid name
        +      if (!name) {
        +        name = extractFontName(fonts[0], path);
        +        if (name.includes(' ')) name = name.replace(/ /g, '_');
        +      }
        +
        +      // create a FontFace object and pass it to the p5.Font constructor
        +      pfont = await create(this, name, path, descriptors, fonts[0]);
        +
        +    } catch (err) {
        +      // failed to parse the font, load it as a simple FontFace
        +      let ident = name || path
        +        .substring(path.lastIndexOf('/') + 1)
        +        .replace(/\.[^/.]+$/, "");
        +
        +      console.warn(`WARN: No glyph data for '${ident}', retrying as FontFace`);
        +
        +      try {
        +        // create a FontFace object and pass it to p5.Font
        +        pfont = await create(this, ident, path, descriptors);
        +      }
        +      catch (err) {
        +        if (error) error(err);
        +        throw err;
        +      }
        +    }
        +    if (success) success(pfont);
        +
        +    return pfont;
        +  }
        +
        +  async function create(pInst, name, path, descriptors, rawFont) {
        +
        +    let face = createFontFace(name, path, descriptors, rawFont);
        +
        +    // load if we need to
        +    if (face.status !== 'loaded') await face.load();
        +
        +    // add it to the document
        +    document.fonts.add(face);
        +
        +    // return a p5.Font instance
        +    return new p5.Font(pInst, face, name, path, rawFont);
        +  }
        +
        +  function unquote(name) {
        +    // Unquote name from CSS
        +    if ((name.startsWith('"') || name.startsWith("'")) && name.at(0) === name.at(-1)) {
        +      return name.slice(1, -1).replace(/\/(['"])/g, '$1');
        +    }
        +    return name;
        +  }
        +
        +  function createFontFace(name, path, descriptors, rawFont) {
        +    let fontArg = rawFont?._data;
        +    if (!fontArg) {
        +      if (!validFontTypesRe.test(path)) {
        +        throw Error(invalidFontError);
        +      }
        +      if (!path.startsWith('url(')) {
        +        path = 'url(' + path + ')';
        +      }
        +      fontArg = path;
        +    }
        +
        +    // create/return the FontFace object
        +    let face = new FontFace(name, fontArg, descriptors);
        +    if (face.status === 'error') {
        +      throw Error('Failed to create FontFace for "' + name + '"');
        +    }
        +    return face;
        +  }
        +
        +  function extractFontName(font, path) {
        +    let meta = font?.name;
        +
        +    // use the metadata if we have it
        +    if (meta) {
        +      if (meta.fullName) {
        +        return meta.fullName;
        +      }
        +      if (meta.familyName) {
        +        return meta.familyName;
        +      }
        +    }
        +
        +    // if not, extract the name from the path
        +    let matches = extractFontNameRe.exec(path);
        +    if (matches && matches.length >= 3) {
        +      return matches[1];
        +    }
        +
        +    // give up and return the full path
        +    return path;
        +  };
        +
        +  function pathToPoints(cmds, options, font) {
        +
        +    const parseOpts = (options, defaults) => {
        +      if (typeof options !== 'object') {
        +        options = defaults;
        +      } else {
        +        for (const key in defaults) {
        +          if (typeof options[key] === 'undefined') {
        +            options[key] = defaults[key];
        +          }
        +        }
        +      }
        +      return options;
        +    }
        +
        +    const at = (v, i) => {
        +      const s = v.length;
        +      return v[i < 0 ? i % s + s : i % s];
        +    }
        +
        +    const simplify = (pts, angle) => {
        +      angle = angle || 0;
        +      let num = 0;
        +      for (let i = pts.length - 1; pts.length > 3 && i >= 0; --i) {
        +        if (collinear(at(pts, i - 1), at(pts, i), at(pts, i + 1), angle)) {
        +          pts.splice(i % pts.length, 1); // Remove middle point
        +          num++;
        +        }
        +      }
        +      return num;
        +    }
        +
        +    const path = createFromCommands(arrayCommandsToObjects(cmds));
        +    let opts = parseOpts(options, {
        +      sampleFactor: 0.1,
        +      simplifyThreshold: 0
        +    });
        +
        +    const totalPoints = Math.ceil(path.getTotalLength() * opts.sampleFactor);
        +    let points = [];
        +
        +    const mode = font._pInst.angleMode();
        +    const DEGREES = font._pInst.DEGREES;
        +    for (let i = 0; i < totalPoints; i++) {
        +      const length = path.getTotalLength() * (i / (totalPoints - 1));
        +      points.push({
        +        ...path.getPointAtLength(length),
        +        get angle() {
        +          const angle = path.getAngleAtLength(length);
        +          if (mode === DEGREES) {
        +            return angle * 180 / Math.PI;
        +          } else {
        +            return angle;
        +          }
        +        },
        +        // For backwards compatibility
        +        get alpha() {
        +          return this.angle;
        +        }
        +      });
        +    }
        +
        +    if (opts.simplifyThreshold) {
        +      simplify(points, opts.simplifyThreshold);
        +    }
        +
        +    return points;
        +  }
        +
        +  function unquote(name) {
        +    // Unquote name from CSS
        +    if ((name.startsWith('"') || name.startsWith("'")) && name.at(0) === name.at(-1)) {
        +      return name.slice(1, -1).replace(/\/(['"])/g, '$1');
        +    }
        +    return name;
        +  }
        +
        +};
        +
        +// Convert arrays to named objects
        +export const arrayCommandsToObjects = (commands) => commands.map((command) => {
        +  const type = command[0];
        +  switch (type) {
        +    case 'Z': {
        +      return { type };
        +    }
        +    case 'M':
        +    case 'L': {
        +      const [, x, y] = command;
        +      return { type, x, y };
        +    }
        +    case 'Q': {
        +      const [, x1, y1, x, y] = command;
        +      return { type, x1, y1, x, y };
        +    }
        +    case 'C': {
        +      const [, x1, y1, x2, y2, x, y] = command;
        +      return { type, x1, y1, x2, y2, x, y };
        +    }
        +    default: {
        +      throw new Error(`Unexpected path command: ${type}`);
        +    }
        +  }
        +});
        +
        +export default font;
        +
        +if (typeof p5 !== 'undefined') {
        +  font(p5, p5.prototype);
        +}
        diff --git a/src/type/text2d.js b/src/type/text2d.js
        new file mode 100644
        index 0000000000..4c97b4af6d
        --- /dev/null
        +++ b/src/type/text2d.js
        @@ -0,0 +1,1282 @@
        +import { Renderer } from '../core/p5.Renderer';
        +
        +/*
        + *  TODO:
        + *   - more with variable fonts, do slider example
        + *   - better font-loading? (google fonts, font-face declarations, multiple fonts with Promise.all())
        + *   - test textToPoints with google/variable fonts?
        + *   - add test for line-height property in textFont() and textProperty()
        + *      - how does this integrate with textLeading?
        + *   - spurious warning in oneoff.html (local)
        +
        + *  ON HOLD:
        + *   - get axes and values for parsed fonts
        + *   - change renderer.state to use getters for textAlign, textBaseline, etc. ??
        + *  DONE:
        + *   - textToPoints/Paths should accept offscreen `graphics` passed in as `options.graphics` [x]
        + *   - textToPaths: test rendering in p5 [x]
        + *   - support direct setting of context2d.font with string [x]
        + *   - textToPoints/Path: add re-sampling support with current options [x]
        + *   - add fontAscent/Descent and textWeight functions [x]
        + *   - textToPaths should split into glyphs and paths [x]
        + *   - add textFont(string) that forces context2d.font to be set (if including size part) [x]
        + *   - textToPoints: test rectMode for all alignments [x]
        + *   - test textToPoints with single line, and overlapping text [x]
        + *  ENHANCEMENTS:
        + *   - cache parsed fonts
        + *   - support idographic and hanging baselines
        + *   - support start and end text-alignments
        + *   - add 'justify' alignment
        + */
        +
        +/**
        + * @module Type
        + * @submodule text2d
        + * @for p5
        + * @requires core
        + */
        +function text2d(p5, fn) {
        +
        +  // additional constants
        +  fn.IDEOGRAPHIC = 'ideographic';
        +  fn.RIGHT_TO_LEFT = 'rtl';
        +  fn.LEFT_TO_RIGHT = 'ltr';
        +  fn._CTX_MIDDLE = 'middle';
        +  fn._TEXT_BOUNDS = '_textBoundsSingle';
        +  fn._FONT_BOUNDS = '_fontBoundsSingle';
        +  fn.HANGING = 'hanging';
        +  fn.START = 'start';
        +  fn.END = 'end';
        +
        +  const LeadingScale = 1.275;
        +  const DefaultFill = '#000000';
        +  const LinebreakRe = /\r?\n/g;
        +  const CommaDelimRe = /,\s+/;
        +  const QuotedRe = /^".*"$/;
        +  const TabsRe = /\t/g;
        +
        +  const FontVariationSettings = 'fontVariationSettings';
        +  const VariableAxes = ['wght', 'wdth', 'ital', 'slnt', 'opsz'];
        +  const VariableAxesRe = new RegExp(`(?:${VariableAxes.join('|')})`);
        +
        +  const textFunctions = [
        +    'text',
        +    'textAlign',
        +    'textAscent',
        +    'textDescent',
        +    'textLeading',
        +    'textMode',
        +    'textFont',
        +    'textSize',
        +    'textStyle',
        +    'textWidth',
        +    'textWrap',
        +    'textBounds',
        +    'textToPoints',
        +    'textDirection',
        +    'textProperty',
        +    'textProperties',
        +    'fontBounds',
        +    'fontWidth',
        +    'fontAscent',
        +    'fontDescent',
        +    'textWeight'
        +  ];
        +
        +  // attach each text func to p5, delegating to the renderer
        +  textFunctions.forEach(func => {
        +    fn[func] = function (...args) {
        +      if (!(func in Renderer.prototype)) {
        +        throw Error(`Renderer2D.prototype.${func} is not defined.`);
        +      }
        +      return this._renderer[func](...args);
        +    };
        +    p5.Graphics.prototype[func] = function (...args) {
        +      return this._renderer[func](...args);
        +    };
        +  });
        +
        +  const RendererTextProps = {
        +    textAlign: { default: fn.LEFT, type: 'Context2d' },
        +    textBaseline: { default: fn.BASELINE, type: 'Context2d' },
        +    textFont: { default: { family: 'sans-serif' } },
        +    textLeading: { default: 15 },
        +    textSize: { default: 12 },
        +    textWrap: { default: fn.WORD },
        +    fontStretch: { default: fn.NORMAL, isShorthand: true },  // font-stretch: { default:  normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded }
        +    fontWeight: { default: fn.NORMAL, isShorthand: true },   // font-stretch: { default:  normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded }
        +    lineHeight: { default: fn.NORMAL, isShorthand: true },   // line-height: { default:  normal | number | length | percentage }
        +    fontVariant: { default: fn.NORMAL, isShorthand: true },  // font-variant: { default:  normal | small-caps }
        +    fontStyle: { default: fn.NORMAL, isShorthand: true },    // font-style: { default:  normal | italic | oblique } [was 'textStyle' in v1]
        +    direction: { default: 'inherit' }, // direction: { default: inherit | ltr | rtl }
        +  };
        +
        +  // note: font must be first here otherwise it may reset other properties
        +  const ContextTextProps = ['font', 'direction', 'fontKerning', 'fontStretch', 'fontVariantCaps', 'letterSpacing', 'textAlign', 'textBaseline', 'textRendering', 'wordSpacing'];
        +
        +  // shorthand font properties that can be set with context2d.font
        +  const ShorthandFontProps = Object.keys(RendererTextProps).filter(p => RendererTextProps[p].isShorthand);
        +
        +  // allowable values for font-stretch property for context2d.font
        +  const FontStretchKeys = ["ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "normal", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded"];
        +
        +  let contextQueue, cachedDiv; // lazy
        +
        +  ////////////////////////////// start API ///////////////////////////////
        +
        +  Renderer.prototype.text = function (str, x, y, width, height) {
        +
        +    let setBaseline = this.textDrawingContext().textBaseline; // store baseline
        +
        +    // adjust {x,y,w,h} properties based on rectMode
        +    ({ x, y, width, height } = this._handleRectMode(x, y, width, height));
        +
        +    // parse the lines according to width, height & linebreaks
        +    let lines = this._processLines(str, width, height);
        +
        +    // add the adjusted positions [x,y] to each line
        +    lines = this._positionLines(x, y, width, height, lines);
        +
        +    // render each line at the adjusted position
        +    lines.forEach(line => this._renderText(line.text, line.x, line.y));
        +
        +    this.textDrawingContext().textBaseline = setBaseline; // restore baseline
        +  };
        +
        +  /**
        +   * Computes the precise (tight) bounding box for a block of text
        +   * @param {string} str - the text to measure
        +   * @param {number} x - the x-coordinate of the text
        +   * @param {number} y - the y-coordinate of the text
        +   * @param {number} width - the max width of the text block
        +   * @param {number} height - the max height of the text block
        +   * @returns - a bounding box object for the text block: {x,y,w,h}
        +   */
        +  Renderer.prototype.textBounds = function (str, x, y, width, height) {
        +    // delegate to _textBoundsSingle for measuring
        +    return this._computeBounds(fn._TEXT_BOUNDS, str, x, y, width, height).bounds;
        +  };
        +
        +  /**
        +   * Computes a generic (non-tight) bounding box for a block of text
        +   * @param {string} str - the text to measure
        +   * @param {number} x - the x-coordinate of the text
        +   * @param {number} y - the y-coordinate of the text
        +   * @param {number} width - the max width of the text block
        +   * @param {number} height - the max height of the text block
        +   * @returns - a bounding box object for the text block: {x,y,w,h}
        +   */
        +  Renderer.prototype.fontBounds = function (str, x, y, width, height) {
        +    // delegate to _fontBoundsSingle for measuring
        +    return this._computeBounds(fn._FONT_BOUNDS, str, x, y, width, height).bounds;
        +  };
        +
        +  /**
        +   * Get the width of a text string in pixels (tight bounds)
        +   * @param {string} theText
        +   * @returns - the width of the text in pixels
        +   */
        +  Renderer.prototype.textWidth = function (theText) {
        +    let lines = this._processLines(theText);
        +    // return the max width of the lines (using tight bounds)
        +    return Math.max(...lines.map(l => this._textWidthSingle(l)));
        +  };
        +
        +  /**
        +   * Get the width of a text string in pixels (loose bounds)
        +   * @param {string} theText
        +   * @returns - the width of the text in pixels
        +   */
        +  Renderer.prototype.fontWidth = function (theText) {
        +    // return the max width of the lines (using loose bounds)
        +    let lines = this._processLines(theText);
        +    return Math.max(...lines.map(l => this._fontWidthSingle(l)));
        +  };
        +
        +  /**
        +   * @param {*} txt - optional text to measure, if provided will be
        +   * used to compute the ascent, otherwise the font's ascent will be used
        +   * @returns - the ascent of the text
        +   */
        +  Renderer.prototype.textAscent = function (txt = '') {
        +    if (!txt.length) return this.fontAscent();
        +    return this.textDrawingContext().measureText(txt)[prop];
        +  };
        +
        +  /**
        +   * @returns - returns the ascent for the current font
        +   */
        +  Renderer.prototype.fontAscent = function () {
        +    return this.textDrawingContext().measureText('_').fontBoundingBoxAscent;
        +  };
        +
        +  /**
        +   * @param {*} txt - optional text to measure, if provided will
        +   * be used to compute the descent, otherwise the font's descent will be used
        +   * @returns - the descent of the text
        +   */
        +  Renderer.prototype.textDescent = function (txt = '') {
        +    if (!txt.length) return this.fontDescent();
        +    return this.textDrawingContext().measureText(txt)[prop];
        +  };
        +
        +  /**
        +   * @returns - returns the descent for the current font
        +   */
        +  Renderer.prototype.fontDescent = function () {
        +    return this.textDrawingContext().measureText('_').fontBoundingBoxDescent;
        +  };
        +
        +  // setters/getters for text properties //////////////////////////
        +
        +  Renderer.prototype.textAlign = function (h, v) {
        +
        +    // the setter
        +    if (typeof h !== 'undefined') {
        +      this.states.textAlign = h;
        +      if (typeof v !== 'undefined') {
        +        if (v === fn.CENTER) {
        +          v = fn._CTX_MIDDLE;
        +        }
        +        this.states.textBaseline = v;
        +      }
        +      return this._applyTextProperties();
        +    }
        +    // the getter
        +    return {
        +      horizontal: this.states.textAlign,
        +      vertical: this.states.textBaseline
        +    };
        +  };
        +
        +  Renderer.prototype._currentTextFont = function () {
        +    return this.states.textFont.font || this.states.textFont.family;
        +  }
        +
        +  /**
        +   * Set the font and [size] and [options] for rendering text
        +   * @param {p5.Font | string} font - the font to use for rendering text
        +   * @param {number} size - the size of the text, can be a number or a css-style string
        +   * @param {object} options - additional options for rendering text, see FontProps
        +   */
        +  Renderer.prototype.textFont = function (font, size, options) {
        +
        +    if (arguments.length === 0) {
        +      return this._currentTextFont();
        +    }
        +
        +    let family = font;
        +
        +    // do we have a custon loaded font ?
        +    if (font instanceof p5.Font) {
        +      family = font.face.family;
        +    }
        +    else if (font.data instanceof Uint8Array) {
        +      family = font.name.fontFamily;
        +      if (font.name?.fontSubfamily) {
        +        family += '-' + font.name.fontSubfamily;
        +      }
        +    }
        +    else if (typeof font === 'string') {
        +      // direct set the font-string if it contains size
        +      if (typeof size === 'undefined' && /[.0-9]+(%|em|p[xt])/.test(family)) {
        +        //console.log('direct set font-string: ', family);
        +        ({ family, size } = this._directSetFontString(family));
        +      }
        +    }
        +
        +    if (typeof family !== 'string') throw Error('null font in textFont()');
        +
        +    // handle two-arg case: textFont(font, options)
        +    if (arguments.length === 2 && typeof size === 'object') {
        +      options = size;
        +      size = undefined;
        +    }
        +
        +    // update font properties in this.states
        +    this.states.textFont = { font, family, size };
        +
        +    // convert/update the size in this.states
        +    if (typeof size !== 'undefined') {
        +      this._setTextSize(size);
        +    }
        +
        +    // apply any options to this.states
        +    if (typeof options === 'object') {
        +      this.textProperties(options);
        +    }
        +
        +    return this._applyTextProperties();
        +  }
        +
        +  Renderer.prototype._directSetFontString = function (font, debug = 0) {
        +    if (debug) console.log('_directSetFontString"' + font + '"');
        +
        +    let defaults = ShorthandFontProps.reduce((props, p) => {
        +      props[p] = RendererTextProps[p].default;
        +      return props;
        +    }, {});
        +
        +    let el = this._cachedDiv(defaults);
        +    el.style.font = font;
        +    let style = getComputedStyle(el);
        +    ShorthandFontProps.forEach(prop => {
        +      this.states[prop] = style[prop];
        +      if (debug) console.log('  this.states.' + prop + '="' + style[prop] + '"');
        +    });
        +
        +    if (debug) console.log('  this.states.textFont="' + style.fontFamily + '"');
        +    if (debug) console.log('  this.states.textSize="' + style.fontSize + '"');
        +
        +    return { family: style.fontFamily, size: style.fontSize };
        +  }
        +
        +  Renderer.prototype.textLeading = function (leading) {
        +    // the setter
        +    if (typeof leading === 'number') {
        +      this.states.leadingSet = true;
        +      this.states.textLeading = leading;
        +      return this._applyTextProperties();
        +    }
        +    // the getter
        +    return this.states.textLeading;
        +  }
        +
        +  Renderer.prototype.textWeight = function (weight) {
        +    // the setter
        +    if (typeof weight === 'number') {
        +      this.states.fontWeight = weight;
        +      this._applyTextProperties();
        +      this._setCanvasStyleProperty('font-variation-settings', `"wght" ${weight}`);
        +      return;
        +    }
        +    // the getter
        +    return this.states.fontWeight;
        +  }
        +
        +  /**
        +   * @param {*} size - the size of the text, can be a number or a css-style string
        +   */
        +  Renderer.prototype.textSize = function (size) {
        +
        +    // the setter
        +    if (typeof size !== 'undefined') {
        +      this._setTextSize(size);
        +      return this._applyTextProperties();
        +    }
        +    // the getter
        +    return this.states.textSize;
        +  }
        +
        +  Renderer.prototype.textStyle = function (style) {
        +
        +    // the setter
        +    if (typeof style !== 'undefined') {
        +      this.states.fontStyle = style;
        +      return this._applyTextProperties();
        +    }
        +    // the getter
        +    return this.states.fontStyle;
        +  }
        +
        +  Renderer.prototype.textWrap = function (wrapStyle) {
        +
        +    if (wrapStyle === fn.WORD || wrapStyle === fn.CHAR) {
        +      this.states.textWrap = wrapStyle;
        +      // no need to apply text properties here as not a context property
        +      return this._pInst;
        +    }
        +    return this.states.textWrap;
        +  };
        +
        +  Renderer.prototype.textDirection = function (direction) {
        +
        +    if (typeof direction !== 'undefined') {
        +      this.states.direction = direction;
        +      return this._applyTextProperties();
        +    }
        +    return this.states.direction;
        +  };
        +
        +  /**
        +   * Sets/gets a single text property for the renderer (eg. fontStyle, fontStretch, etc.)
        +   * The property to be set can be a mapped or unmapped property on `this.states` or a property
        +   * on `this.textDrawingContext()` or on `this.canvas.style`
        +   * The property to get can exist in `this.states` or `this.textDrawingContext()` or `this.canvas.style`
        +   */
        +  Renderer.prototype.textProperty = function (prop, value, opts) {
        +
        +    let modified = false, debug = opts?.debug || false;
        +
        +    // getter: return option from this.states or this.textDrawingContext()
        +    if (typeof value === 'undefined') {
        +      let props = this.textProperties();
        +      if (prop in props) return props[prop];
        +      throw Error('Unknown text option "' + prop + '"'); // FES?
        +    }
        +
        +    // set the option in this.states if it exists
        +    if (prop in this.states && this.states[prop] !== value) {
        +      this.states[prop] = value;
        +      modified = true;
        +      if (debug) {
        +        console.log('this.states.' + prop + '="' + options[prop] + '"');
        +      }
        +    }
        +    // does it exist in CanvasRenderingContext2D ?
        +    else if (prop in this.textDrawingContext()) {
        +      this._setContextProperty(prop, value, debug);
        +      modified = true;
        +    }
        +    // does it exist in the canvas.style ?
        +    else if (prop in this.canvas.style) {
        +      this._setCanvasStyleProperty(prop, value, debug);
        +      modified = true;
        +    }
        +    else {
        +      console.warn('Ignoring unknown text option: "' + prop + '"\n'); // FES?
        +    }
        +
        +    return modified ? this._applyTextProperties() : this._pInst;
        +  };
        +
        +  /**
        +   * Batch set/get text properties for the renderer.
        +   * The properties can be either on `states` or `drawingContext`
        +   */
        +  /**
        +   * Batch set/get text properties for the renderer.
        +   * The properties can be either on `states` or `drawingContext`
        +   */
        +  Renderer.prototype.textProperties = function (properties) {
        +
        +    // setter
        +    if (typeof properties !== 'undefined') {
        +      Object.keys(properties).forEach(opt => {
        +        this.textProperty(opt, properties[opt]);
        +      });
        +      return this._pInst;
        +    }
        +
        +    // getter: get props from drawingContext
        +    let context = this.textDrawingContext();
        +    properties = ContextTextProps.reduce((props, p) => {
        +      props[p] = context[p];
        +      return props;
        +    }, {});
        +
        +    // add renderer props
        +    Object.keys(RendererTextProps).forEach(p => {
        +      if (RendererTextProps[p]?.type === 'Context2d') {
        +        properties[p] = context[p];
        +      }
        +      else { // a renderer.states property
        +        if (p === 'textFont') {
        +          // avoid circular ref. inside textFont
        +          let current = this._currentTextFont();
        +          if (typeof current === 'object' && '_pInst' in current) {
        +            current = Object.assign({}, current);
        +            delete current._pInst;
        +          }
        +          properties[p] = current;
        +        }
        +        else {
        +          properties[p] = this.states[p];
        +        }
        +      }
        +    });
        +
        +    return properties;
        +  };
        +
        +  Renderer.prototype.textMode = function () { /* no-op for processing api */ };
        +
        +  /////////////////////////////// end API ////////////////////////////////
        +
        +  Renderer.prototype._currentTextFont = function () {
        +    return this.states.textFont.font || this.states.textFont.family;
        +  }
        +
        +  /*
        +    Compute the bounds for a block of text based on the specified
        +    measure function, either _textBoundsSingle or _fontBoundsSingle
        +  */
        +  Renderer.prototype._computeBounds = function (type, str, x, y, width, height, opts) {
        +
        +    let setBaseline = this.textDrawingContext().textBaseline;
        +    let { textLeading, textAlign } = this.states;
        +
        +    // adjust width, height based on current rectMode
        +    ({ width, height } = this._rectModeAdjust(x, y, width, height));
        +
        +    // parse the lines according to the width & linebreaks
        +    let lines = this._processLines(str, width, height);
        +
        +    // get the adjusted positions [x,y] for each line
        +    let boxes = lines.map((line, i) => this[type].bind(this)
        +      (line, x, y + i * textLeading));
        +
        +    // adjust the bounding boxes based on horiz. text alignment
        +    if (lines.length > 1) {
        +      // Call the 2D mode version: the WebGL mode version does additional
        +      // alignment adjustments to account for how WebGL renders text.
        +      boxes.forEach(bb => bb.x += p5.Renderer2D.prototype._xAlignOffset.call(this, textAlign, width));
        +    }
        +
        +    // adjust the bounding boxes based on vert. text alignment
        +    if (typeof height !== 'undefined') {
        +      // Call the 2D mode version: the WebGL mode version does additional
        +      // alignment adjustments to account for how WebGL renders text.
        +      p5.Renderer2D.prototype._yAlignOffset.call(this, boxes, height);
        +    }
        +
        +    // get the bounds for the text block
        +    let bounds = boxes[0];
        +    if (lines.length > 1) {
        +
        +      // get the bounds for the multi-line text block
        +      bounds = this._aggregateBounds(boxes);
        +
        +      // align the multi-line bounds
        +      if (!opts?.ignoreRectMode) {
        +        this._rectModeAlign(bounds, width || 0, height || 0);
        +      }
        +    }
        +
        +    if (0 && opts?.ignoreRectMode) boxes.forEach((b, i) => { // draw bounds for debugging
        +      let ss = this.textDrawingContext().strokeStyle;
        +      this.textDrawingContext().strokeStyle = 'green';
        +      this.textDrawingContext().strokeRect(bounds.x, bounds.y, bounds.w, bounds.h);
        +      this.textDrawingContext().strokeStyle = ss;
        +    });
        +
        +    this.textDrawingContext().textBaseline = setBaseline; // restore baseline
        +
        +    return { bounds, lines };
        +  };
        +
        +  /*
        +    Adjust width, height of bounds based on current rectMode
        +  */
        +  Renderer.prototype._rectModeAdjust = function (x, y, width, height) {
        +
        +    if (typeof width !== 'undefined') {
        +      switch (this.states.rectMode) {
        +        case fn.CENTER:
        +          break;
        +        case fn.CORNERS:
        +          width -= x;
        +          height -= y;
        +          break;
        +        case fn.RADIUS:
        +          width *= 2;
        +          height *= 2;
        +          break;
        +      }
        +    }
        +    return { x, y, width, height };
        +  }
        +
        +  /*
        +    Attempts to set a property directly on the canvas.style object
        +  */
        +  Renderer.prototype._setCanvasStyleProperty = function (opt, val, debug) {
        +
        +    let value = val.toString(); // ensure its a string
        +
        +    if (debug) console.log('canvas.style.' + opt + '="' + value + '"');
        +
        +    // handle variable fonts options
        +    if (opt === FontVariationSettings) {
        +      this._handleFontVariationSettings(value);
        +    }
        +
        +    // lets try to set it on the canvas style
        +    this.canvas.style[opt] = value;
        +
        +    // check if the value was set successfully
        +    if (this.canvas.style[opt] !== value) {
        +
        +      // fails on precision for floating points, also quotes and spaces
        +
        +      if (0) console.warn(`Unable to set '${opt}' property` // FES?
        +        + ' on canvas.style. It may not be supported. Expected "'
        +        + value + '" but got: "' + this.canvas.style[opt] + "'");
        +    }
        +  };
        +
        +  /*
        +    Parses the fontVariationSettings string and sets the font properties, only font-weight
        +    working consistently across browsers at present
        +  */
        +  Renderer.prototype._handleFontVariationSettings = function (value, debug = false) {
        +    // check if the value is a string or an object
        +    if (typeof value === 'object') {
        +      value = Object.keys(value).map(k => k + ' ' + value[k]).join(', ');
        +    }
        +    let values = value.split(CommaDelimRe);
        +    values.forEach(v => {
        +      v = v.replace(/["']/g, ''); // remove quotes
        +      let matches = VariableAxesRe.exec(v);
        +      //console.log('matches: ', matches);
        +      if (matches && matches.length) {
        +        let axis = matches[0];
        +        // get the value to 3 digits of precision with no trailing zeros
        +        let val = parseFloat(parseFloat(v.replace(axis, '').trim()).toFixed(3));
        +        switch (axis) {
        +          case 'wght':
        +            if (debug) console.log('setting font-weight=' + val);
        +            // manually set the font-weight via the font string
        +            if (this.states.fontWeight !== val) this.textWeight(val);
        +            return val;
        +          case 'wdth':
        +            if (0) { // attempt to map font-stretch to allowed keywords
        +              const FontStretchMap = {
        +                "ultra-condensed": 50,
        +                "extra-condensed": 62.5,
        +                "condensed": 75,
        +                "semi-condensed": 87.5,
        +                "normal": 100,
        +                "semi-expanded": 112.5,
        +                "expanded": 125,
        +                "extra-expanded": 150,
        +                "ultra-expanded": 200,
        +              };
        +              let values = Object.values(FontStretchMap);
        +              const indexArr = values.map(function (k) { return Math.abs(k - val) })
        +              const min = Math.min.apply(Math, indexArr)
        +              let idx = indexArr.indexOf(min);
        +              let stretch = Object.keys(FontStretchMap)[idx];
        +              this.states.fontStretch = stretch;
        +            }
        +            break;
        +          case 'ital':
        +            if (debug) console.log('setting font-style=' + (val ? 'italic' : 'normal'));
        +            break;
        +          case 'slnt':
        +            if (debug) console.log('setting font-style=' + (val ? 'oblique' : 'normal'));
        +            break;
        +          case 'opsz':
        +            if (debug) console.log('setting font-optical-size=' + val);
        +            break;
        +        }
        +      }
        +    });
        +  };
        +
        +
        +
        +
        +  /*
        +    For properties not directly managed by the renderer in this.states
        +      we check if it has a mapping to a property in this.states
        +    Otherwise, add the property to the context-queue for later application
        +  */
        +  Renderer.prototype._setContextProperty = function (prop, val, debug = false) {
        +
        +    // check if the value is actually different, else short-circuit
        +    if (this.textDrawingContext()[prop] === val) {
        +      return this._pInst;
        +    }
        +
        +    // otherwise, we will set the property directly on the `this.textDrawingContext()`
        +    // by adding [property, value] to context-queue for later application
        +    (contextQueue ??= []).push([prop, val]);
        +
        +    if (debug) console.log('queued context2d.' + prop + '="' + val + '"');
        +  };
        +
        +  /*
        +     Adjust parameters (x,y,w,h) based on current rectMode
        +  */
        +  Renderer.prototype._handleRectMode = function (x, y, width, height) {
        +
        +    let rectMode = this.states.rectMode;
        +
        +    if (typeof width !== 'undefined') {
        +      switch (rectMode) {
        +        case fn.RADIUS:
        +          width *= 2;
        +          x -= width / 2;
        +          if (typeof height !== 'undefined') {
        +            height *= 2;
        +            y -= height / 2;
        +          }
        +          break;
        +        case fn.CENTER:
        +          x -= width / 2;
        +          if (typeof height !== 'undefined') {
        +            y -= height / 2;
        +          }
        +          break;
        +        case fn.CORNERS:
        +          width -= x;
        +          if (typeof height !== 'undefined') {
        +            height -= y;
        +          }
        +          break;
        +      }
        +    }
        +    return { x, y, width, height };
        +  };
        +
        +  /*
        +    Get the computed font-size in pixels for a given size string
        +    @param {string} size - the font-size string to compute
        +    @returns {number} - the computed font-size in pixels
        +   */
        +  Renderer.prototype._fontSizePx = function (theSize, { family } = this.states.textFont) {
        +
        +    const isNumString = (num) => !isNaN(num) && num.trim() !== '';
        +
        +    // check for a number in a string, eg '12'
        +    if (isNumString(theSize)) {
        +      return parseFloat(theSize);
        +    }
        +    let ele = this._cachedDiv({ fontSize: theSize });
        +    ele.style.fontSize = theSize;
        +    ele.style.fontFamily = family;
        +    let fontSizeStr = getComputedStyle(ele).fontSize;
        +    let fontSize = parseFloat(fontSizeStr);
        +    if (typeof fontSize !== 'number') {
        +      throw Error('textSize: invalid font-size');
        +    }
        +    return fontSize;
        +  };
        +
        +  Renderer.prototype._cachedDiv = function (props) {
        +    if (typeof cachedDiv === 'undefined') {
        +      let ele = document.createElement('div');
        +      ele.ariaHidden = 'true';
        +      ele.style.display = 'none';
        +      Object.entries(props).forEach(([prop, val]) => {
        +        ele.style[prop] = val;
        +      });
        +      this.canvas.appendChild(ele);
        +      cachedDiv = ele;
        +    }
        +    return cachedDiv;
        +  }
        +
        +
        +  /*
        +    Aggregate the bounding boxes of multiple lines of text
        +    @param {array} bboxes - the bounding boxes to aggregate
        +    @returns {object} - the aggregated bounding box
        +  */
        +  Renderer.prototype._aggregateBounds = function (bboxes) {
        +    // loop over the bounding boxes to get the min/max x/y values
        +    let minX = Math.min(...bboxes.map(b => b.x));
        +    let minY = Math.min(...bboxes.map(b => b.y));
        +    let maxY = Math.max(...bboxes.map(b => b.y + b.h));
        +    let maxX = Math.max(...bboxes.map(b => b.x + b.w));
        +    return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
        +  };
        +
        +  // Renderer.prototype._aggregateBounds = function (tx, ty, bboxes) {
        +  //   let x = Math.min(...bboxes.map(b => b.x));
        +  //   let y = Math.min(...bboxes.map(b => b.y));
        +  //   // the width is the max of the x-offset + the box width
        +  //   let w = Math.max(...bboxes.map(b => (b.x - tx) + b.w));
        +  //   let h = bboxes[bboxes.length - 1].y - bboxes[0].y + bboxes[bboxes.length - 1].h;
        +
        +
        +  //   return { x, y, w, h };
        +  // };
        +
        +  /*
        +    Process the text string to handle line-breaks and text wrapping
        +    @param {string} str - the text to process
        +    @param {number} width - the width to wrap the text to
        +    @returns {array} - the processed lines of text
        +  */
        +  Renderer.prototype._processLines = function (str, width, height) {
        +
        +    if (typeof width !== 'undefined') { // only for text with bounds
        +      if (this.textDrawingContext().textBaseline === fn.BASELINE) {
        +        this.textDrawingContext().textBaseline = fn.TOP;
        +      }
        +    }
        +
        +    let lines = this._splitOnBreaks(str.toString());
        +    let hasLineBreaks = lines.length > 1;
        +    let hasWidth = typeof width !== 'undefined';
        +    let exceedsWidth = hasWidth && lines.some(l => this._textWidthSingle(l) > width);
        +    let { textLeading: leading, textWrap } = this.states;
        +
        +    //if (!hasLineBreaks && !exceedsWidth) return lines; // a single-line
        +    if (hasLineBreaks || exceedsWidth) {
        +      if (hasWidth) lines = this._lineate(textWrap, lines, width);
        +    }
        +
        +    // handle height truncation
        +    if (hasWidth && typeof height !== 'undefined') {
        +
        +      if (typeof leading === 'undefined') {
        +        throw Error('leading is required if height is specified');
        +      }
        +
        +      // truncate lines that exceed the height
        +      for (let i = 0; i < lines.length; i++) {
        +        let lh = leading * (i + 1);
        +        if (lh > height) {
        +          //console.log('TRUNCATING: ', i, '-', lines.length, '"' + lines.slice(i) + '"');
        +          lines = lines.slice(0, i);
        +          break;
        +        }
        +      }
        +    }
        +
        +    return lines;
        +  };
        +
        +  /*
        +    Get the x-offset for text given the width and textAlign property
        +  */
        +  Renderer.prototype._xAlignOffset = function (textAlign, width) {
        +    switch (textAlign) {
        +      case fn.LEFT:
        +        return 0;
        +      case fn.CENTER:
        +        return width / 2;
        +      case fn.RIGHT:
        +        return width;
        +      case fn.START:
        +        return 0;
        +      case fn.END:
        +        throw new Error('textBounds: END not yet supported for textAlign');
        +      default:
        +        return 0;
        +    }
        +  }
        +
        +  /*
        +    Align the bounding box based on the current rectMode setting
        +  */
        +  Renderer.prototype._rectModeAlign = function (bb, width, height) {
        +    if (typeof width !== 'undefined') {
        +
        +      switch (this.states.rectMode) {
        +        case fn.CENTER:
        +          bb.x -= (width - bb.w) / 2;
        +          bb.y -= (height - bb.h) / 2;
        +          break;
        +        case fn.CORNERS:
        +          bb.w += bb.x;
        +          bb.h += bb.y;
        +          break;
        +        case fn.RADIUS:
        +          bb.x -= (width - bb.w) / 2;
        +          bb.y -= (height - bb.h) / 2;
        +          bb.w /= 2;
        +          bb.h /= 2;
        +          break;
        +      }
        +      return bb;
        +    }
        +  }
        +
        +  Renderer.prototype._rectModeAlignRevert = function (bb, width, height) {
        +    if (typeof width !== 'undefined') {
        +
        +      switch (this.states.rectMode) {
        +        case fn.CENTER:
        +          bb.x += (width - bb.w) / 2;
        +          bb.y += (height - bb.h) / 2;
        +          break;
        +        case fn.CORNERS:
        +          bb.w -= bb.x;
        +          bb.h -= bb.y;
        +          break;
        +        case fn.RADIUS:
        +          bb.x += (width - bb.w) / 2;
        +          bb.y += (height - bb.h) / 2;
        +          bb.w *= 2;
        +          bb.h *= 2;
        +          break;
        +      }
        +      return bb;
        +    }
        +  }
        +
        +  /*
        +    Get the (tight) width of a single line of text
        +  */
        +  Renderer.prototype._textWidthSingle = function (s) {
        +    let metrics = this.textDrawingContext().measureText(s);
        +    let abl = metrics.actualBoundingBoxLeft;
        +    let abr = metrics.actualBoundingBoxRight;
        +    return abr + abl;
        +  };
        +
        +  /*
        +    Get the (loose) width of a single line of text as specified by the font
        +  */
        +  Renderer.prototype._fontWidthSingle = function (s) {
        +    return this.textDrawingContext().measureText(s).width;
        +  };
        +
        +  /*
        +    Get the (tight) bounds of a single line of text based on its actual bounding box
        +  */
        +  Renderer.prototype._textBoundsSingle = function (s, x = 0, y = 0) {
        +
        +    let metrics = this.textDrawingContext().measureText(s);
        +    let asc = metrics.actualBoundingBoxAscent;
        +    let desc = metrics.actualBoundingBoxDescent;
        +    let abl = metrics.actualBoundingBoxLeft;
        +    let abr = metrics.actualBoundingBoxRight;
        +    return { x: x - abl, y: y - asc, w: abr + abl, h: asc + desc };
        +  };
        +
        +  /*
        +    Get the (loose) bounds of a single line of text based on its font's bounding box
        +  */
        +  Renderer.prototype._fontBoundsSingle = function (s, x = 0, y = 0) {
        +
        +    let metrics = this.textDrawingContext().measureText(s);
        +    let asc = metrics.fontBoundingBoxAscent;
        +    let desc = metrics.fontBoundingBoxDescent;
        +    x -= this._xAlignOffset(this.states.textAlign, metrics.width);
        +    return { x, y: y - asc, w: metrics.width, h: asc + desc };;
        +  };
        +
        +  /*
        +    Set the textSize property in `this.states` if it has changed
        +    @param {number | string} theSize - the font-size to set
        +    @returns {boolean} - true if the size was changed, false otherwise
        +   */
        +  Renderer.prototype._setTextSize = function (theSize) {
        +
        +    if (typeof theSize === 'string') {
        +      // parse the size string via computed style, eg '2em'
        +      theSize = this._fontSizePx(theSize);
        +    }
        +
        +    // should be a number now
        +    if (typeof theSize === 'number') {
        +
        +      // set it in `this.states` if its been changed
        +      if (this.states.textSize !== theSize) {
        +        this.states.textSize = theSize;
        +
        +        // handle leading here, if not set otherwise
        +        if (!this.states.leadingSet) {
        +          this.states.textLeading = this.states.textSize * LeadingScale;
        +        }
        +        return true; // size was changed
        +      }
        +    }
        +    else {
        +      console.warn('textSize: invalid size: ' + theSize);
        +    }
        +
        +    return false;
        +  };
        +
        +  /*
        +    Split the lines of text based on the width and the textWrap property
        +    @param {array} lines - the lines of text to split
        +    @param {number} maxWidth - the maximum width of the lines
        +    @param {object} opts - additional options for splitting the lines
        +    @returns {array} - the split lines of text
        +  */
        +  Renderer.prototype._lineate = function (textWrap, lines, maxWidth = Infinity, opts = {}) {
        +
        +    let splitter = opts.splitChar ?? (textWrap === fn.WORD ? ' ' : '');
        +    let line, testLine, testWidth, words, newLines = [];
        +
        +    for (let lidx = 0; lidx < lines.length; lidx++) {
        +      line = '';
        +      words = lines[lidx].split(splitter);
        +      for (let widx = 0; widx < words.length; widx++) {
        +        testLine = `${line + words[widx]}` + splitter;
        +        testWidth = this._textWidthSingle(testLine);
        +        if (line.length > 0 && testWidth > maxWidth) {
        +          newLines.push(line.trim());
        +          line = `${words[widx]}` + splitter;
        +        } else {
        +          line = testLine;
        +        }
        +      }
        +      newLines.push(line.trim());
        +    }
        +    return newLines;
        +  };
        +
        +  /*
        +    Split the text into lines based on line-breaks and tabs
        +  */
        +  Renderer.prototype._splitOnBreaks = function (s) {
        +    if (!s || s.length === 0) return [''];
        +    return s.replace(TabsRe, '  ').split(LinebreakRe);
        +  };
        +
        +  /*
        +    Parse the font-family string to handle complex names, fallbacks, etc.
        +  */
        +  Renderer.prototype._parseFontFamily = function (familyStr) {
        +
        +    let parts = familyStr.split(CommaDelimRe);
        +    let family = parts.map(part => {
        +      part = part.trim();
        +      if (part.indexOf(' ') > -1 && !QuotedRe.test(part)) {
        +        part = `"${part}"`; // quote font names with spaces
        +      }
        +      return part;
        +    }).join(', ');
        +
        +    return family;
        +  };
        +
        +  Renderer.prototype._applyFontString = function () {
        +    /*
        +      Create the font-string according to the CSS font-string specification:
        +      If font is specified as a shorthand for several font-related properties, then:
        +      - it must include values for: <font-size> and <font-family>
        +      - it may optionally include values for:
        +          [<font-style>, <font-variant>, <font-weight>, <font-stretch>, <line-height>]
        +      Format:
        +      - font-style, font-variant and font-weight must precede font-size
        +      - font-variant may only specify the values defined in CSS 2.1, that is 'normal' and 'small-caps'.
        +      - font-stretch may only be a single keyword value.
        +      - line-height must immediately follow font-size, preceded by "/", eg 16px/3.
        +      - font-family must be the last value specified.
        +    */
        +    let { textFont, textSize, lineHeight, fontStyle, fontWeight, fontVariant } = this.states;
        +    let family = this._parseFontFamily(textFont.family);
        +    let style = fontStyle !== fn.NORMAL ? `${fontStyle} ` : '';
        +    let weight = fontWeight !== fn.NORMAL ? `${fontWeight} ` : '';
        +    let variant = fontVariant !== fn.NORMAL ? `${fontVariant} ` : '';
        +    let fsize = `${textSize}px` + (lineHeight !== fn.NORMAL ? `/${lineHeight} ` : ' ');
        +    let fontString = `${style}${variant}${weight}${fsize}${family}`.trim();
        +    //console.log('fontString="' + fontString + '"');
        +
        +    // set the font string on the context
        +    this.textDrawingContext().font = fontString;
        +
        +    // verify that it was set successfully
        +    if (this.textDrawingContext().font !== fontString) {
        +      let expected = fontString;
        +      let actual = this.textDrawingContext().font;
        +      if (expected !== actual) {
        +        //console.warn(`Unable to set font property on context2d. It may not be supported.`);
        +        //console.log('Expected "' + expected + '" but got: "' + actual + '"'); // TMP
        +        return false;
        +      }
        +    }
        +    return true;
        +  }
        +
        +  /*
        +    Apply the text properties in `this.states` to the `this.textDrawingContext()`
        +    Then apply any properties in the context-queue
        +   */
        +  Renderer.prototype._applyTextProperties = function (debug = false) {
        +
        +    this._applyFontString();
        +
        +    // set these after the font so they're not overridden
        +    let context = this.textDrawingContext();
        +    context.direction = this.states.direction;
        +    context.textAlign = this.states.textAlign;
        +    context.textBaseline = this.states.textBaseline;
        +
        +    // set manually as (still) not fully supported as part of font-string
        +    let stretch = this.states.fontStretch;
        +    if (FontStretchKeys.includes(stretch) && context.fontStretch !== stretch) {
        +      context.fontStretch = stretch;
        +    }
        +
        +    // apply each property in queue after the font so they're not overridden
        +    while (contextQueue?.length) {
        +
        +      let [prop, val] = contextQueue.shift();
        +      if (debug) console.log('apply context property "' + prop + '" = "' + val + '"');
        +      context[prop] = val;
        +
        +      // check if the value was set successfully
        +      if (context[prop] !== val) {
        +        console.warn(`Unable to set '${prop}' property on context2d. It may not be supported.`); // FES?
        +        console.log('Expected "' + val + '" but got: "' + context[prop] + '"');
        +      }
        +    }
        +
        +    return this._pInst;
        +  };
        +
        +  if (p5.Renderer2D) {
        +    p5.Renderer2D.prototype.textDrawingContext = function () {
        +      return this.drawingContext;
        +    };
        +    p5.Renderer2D.prototype._renderText = function (text, x, y, maxY, minY) {
        +      let states = this.states;
        +
        +      if (y < minY || y >= maxY) {
        +        return; // don't render lines beyond minY/maxY
        +      }
        +
        +      this.push();
        +
        +      // no stroke unless specified by user
        +      if (states.strokeColor && states.strokeSet) {
        +        this.textDrawingContext().strokeText(text, x, y);
        +      }
        +
        +      if (!this._clipping && states.fillColor) {
        +
        +        // if fill hasn't been set by user, use default text fill
        +        if (!states.fillSet) {
        +          this._setFill(DefaultFill);
        +        }
        +        this.textDrawingContext().fillText(text, x, y);
        +      }
        +
        +      this.pop();
        +    };
        +
        +    /*
        +      Position the lines of text based on their textAlign/textBaseline properties
        +    */
        +    p5.Renderer2D.prototype._positionLines = function (x, y, width, height, lines) {
        +
        +      let { textLeading, textAlign } = this.states;
        +      let adjustedX, lineData = new Array(lines.length);
        +      let adjustedW = typeof width === 'undefined' ? 0 : width;
        +      let adjustedH = typeof height === 'undefined' ? 0 : height;
        +
        +      for (let i = 0; i < lines.length; i++) {
        +        switch (textAlign) {
        +          case fn.START:
        +            throw new Error('textBounds: START not yet supported for textAlign'); // default to LEFT
        +          case fn.LEFT:
        +            adjustedX = x;
        +            break;
        +          case fn.CENTER:
        +            adjustedX = x + adjustedW / 2;
        +            break;
        +          case fn.RIGHT:
        +            adjustedX = x + adjustedW;
        +            break;
        +          case fn.END:
        +            throw new Error('textBounds: END not yet supported for textAlign');
        +        }
        +        lineData[i] = { text: lines[i], x: adjustedX, y: y + i * textLeading };
        +      }
        +
        +      return this._yAlignOffset(lineData, adjustedH);
        +    };
        +
        +    /*
        +      Get the y-offset for text given the height, leading, line-count and textBaseline property
        +    */
        +    p5.Renderer2D.prototype._yAlignOffset = function (dataArr, height) {
        +
        +      if (typeof height === 'undefined') {
        +        throw Error('_yAlignOffset: height is required');
        +      }
        +
        +      let { textLeading, textBaseline } = this.states;
        +      let yOff = 0, numLines = dataArr.length;
        +      let ydiff = height - (textLeading * (numLines - 1));
        +      switch (textBaseline) { // drawingContext ?
        +        case fn.TOP:
        +          break; // ??
        +        case fn.BASELINE:
        +          break;
        +        case fn._CTX_MIDDLE:
        +          yOff = ydiff / 2;
        +          break;
        +        case fn.BOTTOM:
        +          yOff = ydiff;
        +          break;
        +        case fn.IDEOGRAPHIC:
        +          console.warn('textBounds: IDEOGRAPHIC not yet supported for textBaseline'); // FES?
        +          break;
        +        case fn.HANGING:
        +          console.warn('textBounds: HANGING not yet supported for textBaseline'); // FES?
        +          break;
        +      }
        +      dataArr.forEach(ele => ele.y += yOff);
        +      return dataArr;
        +    }
        +  }
        +
        +  if (p5.RendererGL) {
        +    p5.RendererGL.prototype.textDrawingContext = function () {
        +      if (!this._textDrawingContext) {
        +        this._textCanvas = document.createElement('canvas');
        +        this._textCanvas.width = 1;
        +        this._textCanvas.height = 1;
        +        this._textDrawingContext = this._textCanvas.getContext('2d');
        +      }
        +      return this._textDrawingContext;
        +    };
        +
        +    p5.RendererGL.prototype._positionLines = function (x, y, width, height, lines) {
        +
        +      let { textLeading, textAlign } = this.states;
        +      const widths = lines.map((line) => this._fontWidthSingle(line));
        +      let adjustedX, lineData = new Array(lines.length);
        +      let adjustedW = typeof width === 'undefined' ? Math.max(0, ...widths) : width;
        +      let adjustedH = typeof height === 'undefined' ? 0 : height;
        +
        +      for (let i = 0; i < lines.length; i++) {
        +        switch (textAlign) {
        +          case fn.START:
        +            throw new Error('textBounds: START not yet supported for textAlign'); // default to LEFT
        +          case fn.LEFT:
        +            adjustedX = x;
        +            break;
        +          case fn.CENTER:
        +            adjustedX = x + (adjustedW - widths[i]) / 2 - adjustedW / 2 + (width || 0) / 2;
        +            break;
        +          case fn.RIGHT:
        +            adjustedX = x + adjustedW - widths[i] - adjustedW + (width || 0);
        +            break;
        +          case fn.END:
        +            throw new Error('textBounds: END not yet supported for textAlign');
        +        }
        +        lineData[i] = { text: lines[i], x: adjustedX, y: y + i * textLeading };
        +      }
        +
        +      return this._yAlignOffset(lineData, adjustedH);
        +    };
        +
        +    p5.RendererGL.prototype._yAlignOffset = function (dataArr, height) {
        +
        +      if (typeof height === 'undefined') {
        +        throw Error('_yAlignOffset: height is required');
        +      }
        +
        +      let { textLeading, textBaseline, textSize, textFont } = this.states;
        +      let yOff = 0, numLines = dataArr.length;
        +      let totalHeight = textSize * numLines + ((textLeading - textSize) * (numLines - 1));
        +      switch (textBaseline) { // drawingContext ?
        +        case fn.TOP:
        +          yOff = textSize;
        +          break;
        +        case fn.BASELINE:
        +          break;
        +        case fn._CTX_MIDDLE:
        +          yOff = -totalHeight / 2 + textSize + (height || 0) / 2;
        +          break;
        +        case fn.BOTTOM:
        +          yOff = -(totalHeight - textSize) + (height || 0);
        +          break;
        +        default:
        +          console.warn(`${textBaseline} is not supported in WebGL mode.`); // FES?
        +          break;
        +      }
        +      yOff += this.states.textFont.font?._verticalAlign(textSize) || 0;
        +      dataArr.forEach(ele => ele.y += yOff);
        +      return dataArr;
        +    }
        +  }
        +}
        +
        +export default text2d;
        +
        +if (typeof p5 !== 'undefined') {
        +  text2d(p5, p5.prototype);
        +}
        diff --git a/src/typography/attributes.js b/src/typography/attributes.js
        deleted file mode 100644
        index 3960ad53bc..0000000000
        --- a/src/typography/attributes.js
        +++ /dev/null
        @@ -1,547 +0,0 @@
        -/**
        - * @module Typography
        - * @submodule Attributes
        - * @for p5
        - * @requires core
        - * @requires constants
        - */
        -
        -import p5 from '../core/main';
        -
        -/**
        - * Sets the way text is aligned when <a href="#/p5/text">text()</a> is called.
        - *
        - * By default, calling `text('hi', 10, 20)` places the bottom-left corner of
        - * the text's bounding box at (10, 20).
        - *
        - * The first parameter, `horizAlign`, changes the way
        - * <a href="#/p5/text">text()</a> interprets x-coordinates. By default, the
        - * x-coordinate sets the left edge of the bounding box. `textAlign()` accepts
        - * the following values for `horizAlign`: `LEFT`, `CENTER`, or `RIGHT`.
        - *
        - * The second parameter, `vertAlign`, is optional. It changes the way
        - * <a href="#/p5/text">text()</a> interprets y-coordinates. By default, the
        - * y-coordinate sets the bottom edge of the bounding box. `textAlign()`
        - * accepts the following values for `vertAlign`: `TOP`, `BOTTOM`, `CENTER`,
        - * or `BASELINE`.
        - *
        - * @method textAlign
        - * @param {Constant} horizAlign horizontal alignment, either LEFT,
        - *                            CENTER, or RIGHT.
        - * @param {Constant} [vertAlign] vertical alignment, either TOP,
        - *                            BOTTOM, CENTER, or BASELINE.
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Draw a vertical line.
        - *   strokeWeight(0.5);
        - *   line(50, 0, 50, 100);
        - *
        - *   // Top line.
        - *   textSize(16);
        - *   textAlign(RIGHT);
        - *   text('ABCD', 50, 30);
        - *
        - *   // Middle line.
        - *   textAlign(CENTER);
        - *   text('EFGH', 50, 50);
        - *
        - *   // Bottom line.
        - *   textAlign(LEFT);
        - *   text('IJKL', 50, 70);
        - *
        - *   describe('The letters ABCD displayed at top-left, EFGH at center, and IJKL at bottom-right. A vertical line divides the canvas in half.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   strokeWeight(0.5);
        - *
        - *   // First line.
        - *   line(0, 12, width, 12);
        - *   textAlign(CENTER, TOP);
        - *   text('TOP', 50, 12);
        - *
        - *   // Second line.
        - *   line(0, 37, width, 37);
        - *   textAlign(CENTER, CENTER);
        - *   text('CENTER', 50, 37);
        - *
        - *   // Third line.
        - *   line(0, 62, width, 62);
        - *   textAlign(CENTER, BASELINE);
        - *   text('BASELINE', 50, 62);
        - *
        - *   // Fourth line.
        - *   line(0, 97, width, 97);
        - *   textAlign(CENTER, BOTTOM);
        - *   text('BOTTOM', 50, 97);
        - *
        - *   describe('The words "TOP", "CENTER", "BASELINE", and "BOTTOM" each drawn relative to a horizontal line. Their positions demonstrate different vertical alignments.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method textAlign
        - * @return {Object}
        - */
        -p5.prototype.textAlign = function(horizAlign, vertAlign) {
        -  p5._validateParameters('textAlign', arguments);
        -  return this._renderer.textAlign(...arguments);
        -};
        -
        -/**
        - * Sets the spacing between lines of text when
        - * <a href="#/p5/text">text()</a> is called.
        - *
        - * Note: Spacing is measured in pixels.
        - *
        - * Calling `textLeading()` without an argument returns the current spacing.
        - *
        - * @method textLeading
        - * @param {Number} leading spacing between lines of text in units of pixels.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // "\n" starts a new line of text.
        - *   let lines = 'one\ntwo';
        - *
        - *   // Left.
        - *   text(lines, 10, 25);
        - *
        - *   // Right.
        - *   textLeading(30);
        - *   text(lines, 70, 25);
        - *
        - *   describe('The words "one" and "two" written on separate lines twice. The words on the left have less vertical spacing than the words on the right.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method textLeading
        - * @return {Number}
        - */
        -p5.prototype.textLeading = function(theLeading) {
        -  p5._validateParameters('textLeading', arguments);
        -  return this._renderer.textLeading(...arguments);
        -};
        -
        -/**
        - * Sets the font size when
        - * <a href="#/p5/text">text()</a> is called.
        - *
        - * Note: Font size is measured in pixels.
        - *
        - * Calling `textSize()` without an arugment returns the current size.
        - *
        - * @method textSize
        - * @param {Number} size size of the letters in units of pixels.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Top.
        - *   textSize(12);
        - *   text('Font Size 12', 10, 30);
        - *
        - *   // Middle.
        - *   textSize(14);
        - *   text('Font Size 14', 10, 60);
        - *
        - *   // Bottom.
        - *   textSize(16);
        - *   text('Font Size 16', 10, 90);
        - *
        - *   describe('The text "Font Size 12" drawn small, "Font Size 14" drawn medium, and "Font Size 16" drawn large.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method textSize
        - * @return {Number}
        - */
        -p5.prototype.textSize = function(theSize) {
        -  p5._validateParameters('textSize', arguments);
        -  return this._renderer.textSize(...arguments);
        -};
        -
        -/**
        - * Sets the style for system fonts when
        - * <a href="#/p5/text">text()</a> is called.
        - *
        - * The parameter, `style`, can be either `NORMAL`, `ITALIC`, `BOLD`, or
        - * `BOLDITALIC`.
        - *
        - * `textStyle()` may be overridden by CSS styling. This function doesn't
        - * affect fonts loaded with <a href="#/p5/loadFont">loadFont()</a>.
        - *
        - * @method textStyle
        - * @param {Constant} style styling for text, either NORMAL,
        - *                            ITALIC, BOLD or BOLDITALIC.
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textSize(12);
        - *   textAlign(CENTER);
        - *
        - *   // First row.
        - *   textStyle(NORMAL);
        - *   text('Normal', 50, 15);
        - *
        - *   // Second row.
        - *   textStyle(ITALIC);
        - *   text('Italic', 50, 40);
        - *
        - *   // Third row.
        - *   textStyle(BOLD);
        - *   text('Bold', 50, 65);
        - *
        - *   // Fourth row.
        - *   textStyle(BOLDITALIC);
        - *   text('Bold Italic', 50, 90);
        - *
        - *   describe('The words "Normal" displayed normally, "Italic" in italic, "Bold" in bold, and "Bold Italic" in bold italics.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method textStyle
        - * @return {String}
        - */
        -p5.prototype.textStyle = function(theStyle) {
        -  p5._validateParameters('textStyle', arguments);
        -  return this._renderer.textStyle(...arguments);
        -};
        -
        -/**
        - * Calculates the maximum width of a string of text drawn when
        - * <a href="#/p5/text">text()</a> is called.
        - *
        - * @method textWidth
        - * @param {String} str string of text to measure.
        - * @return {Number} width measured in units of pixels.
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textSize(28);
        - *   strokeWeight(0.5);
        - *
        - *   // Calculate the text width.
        - *   let s = 'yoyo';
        - *   let w = textWidth(s);
        - *
        - *   // Display the text.
        - *   text(s, 22, 55);
        - *
        - *   // Underline the text.
        - *   line(22, 55, 22 + w, 55);
        - *
        - *   describe('The word "yoyo" underlined.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textSize(28);
        - *   strokeWeight(0.5);
        - *
        - *   // Calculate the text width.
        - *   // "\n" starts a new line.
        - *   let s = 'yo\nyo';
        - *   let w = textWidth(s);
        - *
        - *   // Display the text.
        - *   text(s, 22, 55);
        - *
        - *   // Underline the text.
        - *   line(22, 55, 22 + w, 55);
        - *
        - *   describe('The word "yo" written twice, one copy beneath the other. The words are divided by a horizontal line.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.textWidth = function (...args) {
        -  args[0] += '';
        -  p5._validateParameters('textWidth', args);
        -  if (args[0].length === 0) {
        -    return 0;
        -  }
        -
        -  // Only use the line with the longest width, and replace tabs with double-space
        -  const textLines = args[0].replace(/\t/g, '  ').split(/\r?\n|\r|\n/g);
        -
        -  const newArr = [];
        -
        -  // Return the textWidth for every line
        -  for(let i=0; i<textLines.length; i++){
        -    newArr.push(this._renderer.textWidth(textLines[i]));
        -  }
        -
        -  // Return the largest textWidth
        -  const largestWidth = Math.max(...newArr);
        -
        -  return largestWidth;
        -};
        -
        -/**
        - * Calculates the ascent of the current font at its current size.
        - *
        - * The ascent represents the distance, in pixels, of the tallest character
        - * above the baseline.
        - *
        - * @method textAscent
        - * @return {Number} ascent measured in units of pixels.
        - * @example
        - * <div>
        - * <code>
        - * let font;
        - *
        - * function preload()  {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup()  {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textFont(font);
        - *
        - *   // Different for each font.
        - *   let fontScale = 0.8;
        - *
        - *   let baseY = 75;
        - *   strokeWeight(0.5);
        - *
        - *   // Draw small text.
        - *   textSize(24);
        - *   text('dp', 0, baseY);
        - *
        - *   // Draw baseline and ascent.
        - *   let a = textAscent() * fontScale;
        - *   line(0, baseY, 23, baseY);
        - *   line(23, baseY - a, 23, baseY);
        - *
        - *   // Draw large text.
        - *   textSize(48);
        - *   text('dp', 45, baseY);
        - *
        - *   // Draw baseline and ascent.
        - *   a = textAscent() * fontScale;
        - *   line(45, baseY, 91, baseY);
        - *   line(91, baseY - a, 91, baseY);
        - *
        - *   describe('The letters "dp" written twice in different sizes. Each version has a horizontal baseline. A vertical line extends upward from each baseline to the top of the "d".');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.textAscent = function(...args) {
        -  p5._validateParameters('textAscent', args);
        -  return this._renderer.textAscent();
        -};
        -
        -/**
        - * Calculates the descent of the current font at its current size.
        - *
        - * The descent represents the distance, in pixels, of the character with the
        - * longest descender below the baseline.
        - *
        - * @method textDescent
        - * @return {Number} descent measured in units of pixels.
        - * @example
        - * <div>
        - * <code>
        - * let font;
        - *
        - * function preload()  {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup()  {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the font.
        - *   textFont(font);
        - *
        - *   // Different for each font.
        - *   let fontScale = 0.9;
        - *
        - *   let baseY = 75;
        - *   strokeWeight(0.5);
        - *
        - *   // Draw small text.
        - *   textSize(24);
        - *   text('dp', 0, baseY);
        - *
        - *   // Draw baseline and descent.
        - *   let d = textDescent() * fontScale;
        - *   line(0, baseY, 23, baseY);
        - *   line(23, baseY, 23, baseY + d);
        - *
        - *   // Draw large text.
        - *   textSize(48);
        - *   text('dp', 45, baseY);
        - *
        - *   // Draw baseline and descent.
        - *   d = textDescent() * fontScale;
        - *   line(45, baseY, 91, baseY);
        - *   line(91, baseY, 91, baseY + d);
        - *
        - *   describe('The letters "dp" written twice in different sizes. Each version has a horizontal baseline. A vertical line extends downward from each baseline to the bottom of the "p".');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.textDescent = function(...args) {
        -  p5._validateParameters('textDescent', args);
        -  return this._renderer.textDescent();
        -};
        -
        -/**
        - * Helper function to measure ascent and descent.
        - */
        -p5.prototype._updateTextMetrics = function() {
        -  return this._renderer._updateTextMetrics();
        -};
        -
        -/**
        - * Sets the style for wrapping text when
        - * <a href="#/p5/text">text()</a> is called.
        - *
        - * The parameter, `style`, can be one of the following values:
        - *
        - * `WORD` starts new lines of text at spaces. If a string of text doesn't
        - * have spaces, it may overflow the text box and the canvas. This is the
        - * default style.
        - *
        - * `CHAR` starts new lines as needed to stay within the text box.
        - *
        - * `textWrap()` only works when the maximum width is set for a text box. For
        - * example, calling `text('Have a wonderful day', 0, 10, 100)` sets the
        - * maximum width to 100 pixels.
        - *
        - * Calling `textWrap()` without an argument returns the current style.
        - *
        - * @method textWrap
        - * @param {Constant} style text wrapping style, either WORD or CHAR.
        - * @return {String} style
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textSize(20);
        - *   textWrap(WORD);
        - *
        - *   // Display the text.
        - *   text('Have a wonderful day', 0, 10, 100);
        - *
        - *   describe('The text "Have a wonderful day" written across three lines.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textSize(20);
        - *   textWrap(CHAR);
        - *
        - *   // Display the text.
        - *   text('Have a wonderful day', 0, 10, 100);
        - *
        - *   describe('The text "Have a wonderful day" written across two lines.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textSize(20);
        - *   textWrap(CHAR);
        - *
        - *   // Display the text.
        - *   text('祝你有美好的一天', 0, 10, 100);
        - *
        - *   describe('The text "祝你有美好的一天" written across two lines.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.textWrap = function(wrapStyle) {
        -  p5._validateParameters('textWrap', [wrapStyle]);
        -
        -  return this._renderer.textWrap(wrapStyle);
        -};
        -
        -export default p5;
        diff --git a/src/typography/loading_displaying.js b/src/typography/loading_displaying.js
        deleted file mode 100644
        index f625db3d99..0000000000
        --- a/src/typography/loading_displaying.js
        +++ /dev/null
        @@ -1,450 +0,0 @@
        -/**
        - * @module Typography
        - * @submodule Loading & Displaying
        - * @for p5
        - * @requires core
        - */
        -
        -import p5 from '../core/main';
        -import * as constants from '../core/constants';
        -import * as opentype from 'opentype.js';
        -
        -import '../core/friendly_errors/validate_params';
        -import '../core/friendly_errors/file_errors';
        -import '../core/friendly_errors/fes_core';
        -
        -/**
        - * Loads a font and creates a <a href="#/p5.Font">p5.Font</a> object.
        - * `loadFont()` can load fonts in either .otf or .ttf format. Loaded fonts can
        - * be used to style text on the canvas and in HTML elements.
        - *
        - * The first parameter, `path`, is the path to a font file.
        - * Paths to local files should be relative. For example,
        - * `'assets/inconsolata.otf'`. The Inconsolata font used in the following
        - * examples can be downloaded for free
        - * <a href="https://www.fontsquirrel.com/fonts/inconsolata" target="_blank">here</a>.
        - * Paths to remote files should be URLs. For example,
        - * `'https://example.com/inconsolata.otf'`. URLs may be blocked due to browser
        - * security.
        - *
        - * The second parameter, `successCallback`, is optional. If a function is
        - * passed, it will be called once the font has loaded. The callback function
        - * may use the new <a href="#/p5.Font">p5.Font</a> object if needed.
        - *
        - * The third parameter, `failureCallback`, is also optional. If a function is
        - * passed, it will be called if the font fails to load. The callback function
        - * may use the error
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event" target="_blank">Event</a>
        - * object if needed.
        - *
        - * Fonts can take time to load. Calling `loadFont()` in
        - * <a href="#/p5/preload">preload()</a> ensures fonts load before they're
        - * used in <a href="#/p5/setup">setup()</a> or
        - * <a href="#/p5/draw">draw()</a>.
        - *
        - * @method loadFont
        - * @param  {String}        path              path of the font to be loaded.
        - * @param  {Function}      [successCallback] function called with the
        - *                                           <a href="#/p5.Font">p5.Font</a> object after it
        - *                                           loads.
        - * @param  {Function}      [failureCallback] function called with the error
        - *                                           <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event" target="_blank">Event</a>
        - *                                           object if the font fails to load.
        - * @return {p5.Font}                         <a href="#/p5.Font">p5.Font</a> object.
        - * @example
        - *
        - * <div>
        - * <code>
        - * let font;
        - *
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   fill('deeppink');
        - *   textFont(font);
        - *   textSize(36);
        - *   text('p5*js', 10, 50);
        - *
        - *   describe('The text "p5*js" written in pink on a white background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   loadFont('assets/inconsolata.otf', font => {
        - *     fill('deeppink');
        - *     textFont(font);
        - *     textSize(36);
        - *     text('p5*js', 10, 50);
        - *
        - *     describe('The text "p5*js" written in pink on a white background.');
        - *   });
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   loadFont('assets/inconsolata.otf', success, failure);
        - * }
        - *
        - * function success(font) {
        - *   fill('deeppink');
        - *   textFont(font);
        - *   textSize(36);
        - *   text('p5*js', 10, 50);
        - *
        - *   describe('The text "p5*js" written in pink on a white background.');
        - * }
        - *
        - * function failure(event) {
        - *   console.error('Oops!', event);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function preload() {
        - *   loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   let p = createP('p5*js');
        - *   p.style('color', 'deeppink');
        - *   p.style('font-family', 'Inconsolata');
        - *   p.style('font-size', '36px');
        - *   p.position(10, 50);
        - *
        - *   describe('The text "p5*js" written in pink on a white background.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.loadFont = function(path, onSuccess, onError) {
        -  p5._validateParameters('loadFont', arguments);
        -  const p5Font = new p5.Font(this);
        -
        -  const self = this;
        -  opentype.load(path, (err, font) => {
        -    if (err) {
        -      p5._friendlyFileLoadError(4, path);
        -      if (typeof onError !== 'undefined') {
        -        return onError(err);
        -      }
        -      console.error(err, path);
        -      return;
        -    }
        -
        -    p5Font.font = font;
        -
        -    if (typeof onSuccess !== 'undefined') {
        -      onSuccess(p5Font);
        -    }
        -
        -    self._decrementPreload();
        -
        -    // check that we have an acceptable font type
        -    const validFontTypes = ['ttf', 'otf', 'woff', 'woff2'];
        -
        -    const fileNoPath = path
        -      .split('\\')
        -      .pop()
        -      .split('/')
        -      .pop();
        -
        -    const lastDotIdx = fileNoPath.lastIndexOf('.');
        -    let fontFamily;
        -    let newStyle;
        -    const fileExt = lastDotIdx < 1 ? null : fileNoPath.slice(lastDotIdx + 1);
        -
        -    // if so, add it to the DOM (name-only) for use with DOM module
        -    if (validFontTypes.includes(fileExt)) {
        -      fontFamily = fileNoPath.slice(0, lastDotIdx !== -1 ? lastDotIdx : 0);
        -      newStyle = document.createElement('style');
        -      newStyle.appendChild(
        -        document.createTextNode(
        -          `\n@font-face {\nfont-family: ${fontFamily};\nsrc: url(${path});\n}\n`
        -        )
        -      );
        -      document.head.appendChild(newStyle);
        -    }
        -  });
        -
        -  return p5Font;
        -};
        -
        -/**
        - * Draws text to the canvas.
        - *
        - * The first parameter, `str`, is the text to be drawn. The second and third
        - * parameters, `x` and `y`, set the coordinates of the text's bottom-left
        - * corner. See <a href="#/p5/textAlign">textAlign()</a> for other ways to
        - * align text.
        - *
        - * The fourth and fifth parameters, `maxWidth` and `maxHeight`, are optional.
        - * They set the dimensions of the invisible rectangle containing the text. By
        - * default, they set its  maximum width and height. See
        - * <a href="#/p5/rectMode">rectMode()</a> for other ways to define the
        - * rectangular text box. Text will wrap to fit within the text box. Text
        - * outside of the box won't be drawn.
        - *
        - * Text can be styled a few ways. Call the <a href="#/p5/fill">fill()</a>
        - * function to set the text's fill color. Call
        - * <a href="#/p5/stroke">stroke()</a> and
        - * <a href="#/p5/strokeWeight">strokeWeight()</a> to set the text's outline.
        - * Call <a href="#/p5/textSize">textSize()</a> and
        - * <a href="#/p5/textFont">textFont()</a> to set the text's size and font,
        - * respectively.
        - *
        - * Note: `WEBGL` mode only supports fonts loaded with
        - * <a href="#/p5/loadFont">loadFont()</a>. Calling
        - * <a href="#/p5/stroke">stroke()</a> has no effect in `WEBGL` mode.
        - *
        - * @method text
        - * @param {String|Object|Array|Number|Boolean} str text to be displayed.
        - * @param {Number} x          x-coordinate of the text box.
        - * @param {Number} y          y-coordinate of the text box.
        - * @param {Number} [maxWidth] maximum width of the text box. See
        - *                            <a href="#/p5/rectMode">rectMode()</a> for
        - *                            other options.
        - * @param {Number} [maxHeight] maximum height of the text box. See
        - *                            <a href="#/p5/rectMode">rectMode()</a> for
        - *                            other options.
        - *
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *   text('hi', 50, 50);
        - *
        - *   describe('The text "hi" written in black in the middle of a gray square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   background('skyblue');
        - *   textSize(100);
        - *   text('🌈', 0, 100);
        - *
        - *   describe('A rainbow in a blue sky.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   textSize(32);
        - *   fill(255);
        - *   stroke(0);
        - *   strokeWeight(4);
        - *   text('hi', 50, 50);
        - *
        - *   describe('The text "hi" written in white with a black outline.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   background('black');
        - *   textSize(22);
        - *   fill('yellow');
        - *   text('rainbows', 6, 20);
        - *   fill('cornflowerblue');
        - *   text('rainbows', 6, 45);
        - *   fill('tomato');
        - *   text('rainbows', 6, 70);
        - *   fill('limegreen');
        - *   text('rainbows', 6, 95);
        - *
        - *   describe('The text "rainbows" written on several lines, each in a different color.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *   let s = 'The quick brown fox jumps over the lazy dog.';
        - *   text(s, 10, 10, 70, 80);
        - *
        - *   describe('The sample text "The quick brown fox..." written in black across several lines.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *   rectMode(CENTER);
        - *   let s = 'The quick brown fox jumps over the lazy dog.';
        - *   text(s, 50, 50, 70, 80);
        - *
        - *   describe('The sample text "The quick brown fox..." written in black across several lines.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div modernizr='webgl'>
        - * <code>
        - * let font;
        - *
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *   textFont(font);
        - *   textSize(32);
        - *   textAlign(CENTER, CENTER);
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *   rotateY(frameCount / 30);
        - *   text('p5*js', 0, 0);
        - *
        - *   describe('The text "p5*js" written in white and spinning in 3D.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.text = function(str, x, y, maxWidth, maxHeight) {
        -  p5._validateParameters('text', arguments);
        -  return !(this._renderer._doFill || this._renderer._doStroke)
        -    ? this
        -    : this._renderer.text(...arguments);
        -};
        -
        -/**
        - * Sets the font used by the <a href="#/p5/text">text()</a> function.
        - *
        - * The first parameter, `font`, sets the font. `textFont()` recognizes either
        - * a <a href="#/p5.Font">p5.Font</a> object or a string with the name of a
        - * system font. For example, `'Courier New'`.
        - *
        - * The second parameter, `size`, is optional. It sets the font size in pixels.
        - * This has the same effect as calling <a href="#/p5/textSize">textSize()</a>.
        - *
        - * Note: `WEBGL` mode only supports fonts loaded with
        - * <a href="#/p5/loadFont">loadFont()</a>.
        - *
        - * @method textFont
        - * @return {Object} current font or p5 Object.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *   textFont('Courier New');
        - *   textSize(24);
        - *   text('hi', 35, 55);
        - *
        - *   describe('The text "hi" written in a black, monospace font on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   background('black');
        - *   fill('palegreen');
        - *   textFont('Courier New', 10);
        - *   text('You turn to the left and see a door. Do you enter?', 5, 5, 90, 90);
        - *   text('>', 5, 70);
        - *
        - *   describe('A text prompt from a game is written in a green, monospace font on a black background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   background(200);
        - *   textFont('Verdana');
        - *   let currentFont = textFont();
        - *   text(currentFont, 25, 50);
        - *
        - *   describe('The text "Verdana" written in a black, sans-serif font on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let fontRegular;
        - * let fontItalic;
        - * let fontBold;
        - *
        - * function preload() {
        - *   fontRegular = loadFont('assets/Regular.otf');
        - *   fontItalic = loadFont('assets/Italic.ttf');
        - *   fontBold = loadFont('assets/Bold.ttf');
        - * }
        - *
        - * function setup() {
        - *   background(200);
        - *   textFont(fontRegular);
        - *   text('I am Normal', 10, 30);
        - *   textFont(fontItalic);
        - *   text('I am Italic', 10, 50);
        - *   textFont(fontBold);
        - *   text('I am Bold', 10, 70);
        - *
        - *   describe('The statements "I am Normal", "I am Italic", and "I am Bold" written in black on separate lines. The statements have normal, italic, and bold fonts, respectively.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method textFont
        - * @param {Object|String} font font as a <a href="#/p5.Font">p5.Font</a> object or a string.
        - * @param {Number} [size] font size in pixels.
        - * @chainable
        - */
        -p5.prototype.textFont = function(theFont, theSize) {
        -  p5._validateParameters('textFont', arguments);
        -  if (arguments.length) {
        -    if (!theFont) {
        -      throw new Error('null font passed to textFont');
        -    }
        -
        -    this._renderer._setProperty('_textFont', theFont);
        -
        -    if (theSize) {
        -      this._renderer._setProperty('_textSize', theSize);
        -      if (!this._renderer._leadingSet) {
        -        // only use a default value if not previously set (#5181)
        -        this._renderer._setProperty(
        -          '_textLeading',
        -          theSize * constants._DEFAULT_LEADMULT
        -        );
        -      }
        -    }
        -
        -    return this._renderer._applyTextProperties();
        -  }
        -
        -  return this._renderer._textFont;
        -};
        -
        -export default p5;
        diff --git a/src/typography/p5.Font.js b/src/typography/p5.Font.js
        deleted file mode 100644
        index 5a555120a0..0000000000
        --- a/src/typography/p5.Font.js
        +++ /dev/null
        @@ -1,1392 +0,0 @@
        -/**
        - * This module defines the <a href="#/p5.Font">p5.Font</a> class and functions for
        - * drawing text to the display canvas.
        - * @module Typography
        - * @submodule Loading & Displaying
        - * @requires core
        - * @requires constants
        - */
        -
        -import p5 from '../core/main';
        -import * as constants from '../core/constants';
        -
        -/**
        - * A class to describe fonts.
        - *
        - * @class p5.Font
        - * @constructor
        - * @param {p5} [pInst] pointer to p5 instance.
        - * @example
        - * <div>
        - * <code>
        - * let font;
        - *
        - * function preload() {
        - *   // Creates a p5.Font object.
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   // Style the text.
        - *   fill('deeppink');
        - *   textFont(font);
        - *   textSize(36);
        - *
        - *   // Display the text.
        - *   text('p5*js', 10, 50);
        - *
        - *   describe('The text "p5*js" written in pink on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Font = class {
        -  constructor(p){
        -    this.parent = p;
        -
        -    this.cache = {};
        -
        -    /**
        -   * The font's underlying
        -   * <a href="https://opentype.js.org/" target="_blank">opentype.js</a>
        -   * font object.
        -   *
        -   * @property font
        -   * @name font
        -   */
        -    this.font = undefined;
        -  }
        -
        -  /**
        - * Returns the bounding box for a string of text written using the font.
        - *
        - * The bounding box is the smallest rectangle that can contain a string of
        - * text. `font.textBounds()` returns an object with the bounding box's
        - * location and size. For example, calling `font.textBounds('p5*js', 5, 20)`
        - * returns an object in the format
        - * `{ x: 5.7, y: 12.1 , w: 9.9, h: 28.6 }`. The `x` and `y` properties are
        - * always the coordinates of the bounding box's top-left corner.
        - *
        - * The first parameter, `str`, is a string of text. The second and third
        - * parameters, `x` and `y`, are the text's position. By default, they set the
        - * coordinates of the bounding box's bottom-left corner. See
        - * <a href="#/p5/textAlign">textAlign()</a> for more ways to align text.
        - *
        - * The fourth parameter, `fontSize`, is optional. It sets the font size used to
        - * determine the bounding box. By default, `font.textBounds()` will use the
        - * current <a href="#/p5/textSize">textSize()</a>.
        - *
        - * @method textBounds
        - * @param  {String} str        string of text.
        - * @param  {Number} x          x-coordinate of the text.
        - * @param  {Number} y          y-coordinate of the text.
        - * @param  {Number} [fontSize] font size. Defaults to the current
        - *                             <a href="#/p5/textSize">textSize()</a>.
        - * @return {Object}            object describing the bounding box with
        - *                             properties x, y, w, and h.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let font;
        - *
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Display the bounding box.
        - *   let bbox = font.textBounds('p5*js', 35, 53);
        - *   rect(bbox.x, bbox.y, bbox.w, bbox.h);
        - *
        - *   // Style the text.
        - *   textFont(font);
        - *
        - *   // Display the text.
        - *   text('p5*js', 35, 53);
        - *
        - *   describe('The text "p5*js" written in black inside a white rectangle.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let font;
        - *
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textFont(font);
        - *   textSize(15);
        - *   textAlign(CENTER, CENTER);
        - *
        - *   // Display the bounding box.
        - *   let bbox = font.textBounds('p5*js', 50, 50);
        - *   rect(bbox.x, bbox.y, bbox.w, bbox.h);
        - *
        - *   // Display the text.
        - *   text('p5*js', 50, 50);
        - *
        - *   describe('The text "p5*js" written in black inside a white rectangle.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let font;
        - *
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Display the bounding box.
        - *   let bbox = font.textBounds('p5*js', 31, 53, 15);
        - *   rect(bbox.x, bbox.y, bbox.w, bbox.h);
        - *
        - *   // Style the text.
        - *   textFont(font);
        - *   textSize(15);
        - *
        - *   // Display the text.
        - *   text('p5*js', 31, 53);
        - *
        - *   describe('The text "p5*js" written in black inside a white rectangle.');
        - * }
        - * </code>
        - * </div>
        - */
        -  textBounds(str, x = 0, y = 0, fontSize, opts) {
        -  // Check cache for existing bounds. Take into consideration the text alignment
        -  // settings. Default alignment should match opentype's origin: left-aligned &
        -  // alphabetic baseline.
        -    const p = (opts && opts.renderer && opts.renderer._pInst) || this.parent;
        -
        -    const ctx = p._renderer.drawingContext;
        -    const alignment = ctx.textAlign || constants.LEFT;
        -    const baseline = ctx.textBaseline || constants.BASELINE;
        -    const cacheResults = false;
        -    let result;
        -    let key;
        -
        -    fontSize = fontSize || p._renderer._textSize;
        -
        -    // NOTE: cache disabled for now pending further discussion of #3436
        -    if (cacheResults) {
        -      key = cacheKey('textBounds', str, x, y, fontSize, alignment, baseline);
        -      result = this.cache[key];
        -    }
        -
        -    if (!result) {
        -      let minX = [];
        -      let minY;
        -      let maxX = [];
        -      let maxY;
        -      let pos;
        -      const xCoords = [];
        -      xCoords[0] = [];
        -      const yCoords = [];
        -      const scale = this._scale(fontSize);
        -      const lineHeight = p._renderer.textLeading();
        -      let lineCount = 0;
        -
        -      this.font.forEachGlyph(
        -        str,
        -        x,
        -        y,
        -        fontSize,
        -        opts,
        -        (glyph, gX, gY, gFontSize) => {
        -          const gm = glyph.getMetrics();
        -          if (glyph.index === 0) {
        -            lineCount += 1;
        -            xCoords[lineCount] = [];
        -          } else {
        -            xCoords[lineCount].push(gX + gm.xMin * scale);
        -            xCoords[lineCount].push(gX + gm.xMax * scale);
        -            yCoords.push(gY + lineCount * lineHeight + -gm.yMin * scale);
        -            yCoords.push(gY + lineCount * lineHeight + -gm.yMax * scale);
        -          }
        -        }
        -      );
        -
        -      if (xCoords[lineCount].length > 0) {
        -        minX[lineCount] = Math.min.apply(null, xCoords[lineCount]);
        -        maxX[lineCount] = Math.max.apply(null, xCoords[lineCount]);
        -      }
        -
        -      let finalMaxX = 0;
        -      for (let i = 0; i <= lineCount; i++) {
        -        minX[i] = Math.min.apply(null, xCoords[i]);
        -        maxX[i] = Math.max.apply(null, xCoords[i]);
        -        const lineLength = maxX[i] - minX[i];
        -        if (lineLength > finalMaxX) {
        -          finalMaxX = lineLength;
        -        }
        -      }
        -
        -      const finalMinX = Math.min.apply(null, minX);
        -      minY = Math.min.apply(null, yCoords);
        -      maxY = Math.max.apply(null, yCoords);
        -
        -      result = {
        -        x: finalMinX,
        -        y: minY,
        -        h: maxY - minY,
        -        w: finalMaxX,
        -        advance: finalMinX - x
        -      };
        -
        -      // Bounds are now calculated, so shift the x & y to match alignment settings
        -      pos = this._handleAlignment(
        -        p._renderer,
        -        str,
        -        result.x,
        -        result.y,
        -        result.w + result.advance
        -      );
        -
        -      result.x = pos.x;
        -      result.y = pos.y;
        -
        -      if (cacheResults) {
        -        this.cache[key] = result;
        -      }
        -    }
        -
        -    return result;
        -  }
        -
        -  /**
        - * Returns an array of points outlining a string of text written using the
        - * font.
        - *
        - * Each point object in the array has three properties that describe the
        - * point's location and orientation, called its path angle. For example,
        - * `{ x: 10, y: 20, alpha: 450 }`.
        - *
        - * The first parameter, `str`, is a string of text. The second and third
        - * parameters, `x` and `y`, are the text's position. By default, they set the
        - * coordinates of the bounding box's bottom-left corner. See
        - * <a href="#/p5/textAlign">textAlign()</a> for more ways to align text.
        - *
        - * The fourth parameter, `fontSize`, is optional. It sets the text's font
        - * size. By default, `font.textToPoints()` will use the current
        - * <a href="#/p5/textSize">textSize()</a>.
        - *
        - * The fifth parameter, `options`, is also optional. `font.textToPoints()`
        - * expects an object with the following properties:
        - *
        - * `sampleFactor` is the ratio of the text's path length to the number of
        - * samples. It defaults to 0.1. Higher values produce more points along the
        - * path and are more precise.
        - *
        - * `simplifyThreshold` removes collinear points if it's set to a number other
        - * than 0. The value represents the threshold angle to use when determining
        - * whether two edges are collinear.
        - *
        - * @method textToPoints
        - * @param  {String} str        string of text.
        - * @param  {Number} x          x-coordinate of the text.
        - * @param  {Number} y          y-coordinate of the text.
        - * @param  {Number} [fontSize] font size. Defaults to the current
        - *                             <a href="#/p5/textSize">textSize()</a>.
        - * @param  {Object} [options]  object with sampleFactor and simplifyThreshold
        - *                             properties.
        - * @return {Array} array of point objects, each with x, y, and alpha (path angle) properties.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let font;
        - *
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the point array.
        - *   let points = font.textToPoints('p5*js', 6, 60, 35, { sampleFactor:  0.5 });
        - *
        - *   // Draw a dot at each point.
        - *   for (let p of points) {
        - *     point(p.x, p.y);
        - *   }
        - *
        - *   describe('A set of black dots outlining the text "p5*js" on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -  textToPoints(txt, x, y, fontSize, options) {
        -    const xOriginal = x;
        -    const result = [];
        -    const p = this.parent;
        -    let pos;
        -    let lines = txt.split(/\r?\n|\r|\n/g);
        -    fontSize = fontSize || this.parent._renderer._textSize;
        -
        -    function isSpace(i, text, glyphsLine) {
        -      return (
        -        (glyphsLine[i].name && glyphsLine[i].name === 'space') ||
        -        (text.length === glyphsLine.length && text[i] === ' ') //||
        -        //(glyphs[i].index && glyphs[i].index === 3)
        -      );
        -    }
        -
        -    for (let i = 0; i < lines.length; i++) {
        -      let xoff = 0;
        -      x = xOriginal;
        -      let line = lines[i];
        -
        -      line = line.replace('\t', '  ');
        -      const glyphs = this._getGlyphs(line);
        -
        -      for (let j = 0; j < glyphs.length; j++) {
        -        if (!isSpace(j, line, glyphs)) {
        -          // fix to #1817, #2069
        -
        -          const gpath = glyphs[j].getPath(x, y, fontSize),
        -            paths = splitPaths(gpath.commands);
        -
        -          for (let k = 0; k < paths.length; k++) {
        -            const pts = pathToPoints(paths[k], options);
        -
        -            for (let l = 0; l < pts.length; l++) {
        -              pts[l].x += xoff;
        -              pos = this._handleAlignment(
        -                p._renderer,
        -                line,
        -                pts[l].x,
        -                pts[l].y
        -              );
        -              pts[l].x = pos.x;
        -              pts[l].y = pos.y;
        -              result.push(pts[l]);
        -            }
        -          }
        -        }
        -
        -        xoff += glyphs[j].advanceWidth * this._scale(fontSize);
        -      }
        -
        -      y = y + this.parent._renderer._textLeading;
        -    }
        -    return result;
        -  }
        -
        -  // ----------------------------- End API ------------------------------
        -
        -  /**
        - * Returns the set of opentype glyphs for the supplied string.
        - *
        - * Note that there is not a strict one-to-one mapping between characters
        - * and glyphs, so the list of returned glyphs can be larger or smaller
        - *  than the length of the given string.
        - *
        - * @private
        - * @param  {String} str the string to be converted
        - * @return {Array}     the opentype glyphs
        - */
        -  _getGlyphs(str) {
        -    return this.font.stringToGlyphs(str);
        -  }
        -
        -  /**
        - * Returns an opentype path for the supplied string and position.
        - *
        - * @private
        - * @param  {String} line     a line of text
        - * @param  {Number} x        x-position
        - * @param  {Number} y        y-position
        - * @param  {Object} options opentype options (optional)
        - * @return {Object}     the opentype path
        - */
        -  _getPath(line, x, y, options) {
        -    const p =
        -      (options && options.renderer && options.renderer._pInst) || this.parent,
        -      renderer = p._renderer,
        -      pos = this._handleAlignment(renderer, line, x, y);
        -
        -    return this.font.getPath(line, pos.x, pos.y, renderer._textSize, options);
        -  }
        -
        -  /*
        - * Creates an SVG-formatted path-data string
        - * (See http://www.w3.org/TR/SVG/paths.html#PathData)
        - * from the given opentype path or string/position
        - *
        - * @param  {Object} path    an opentype path, OR the following:
        - *
        - * @param  {String} line     a line of text
        - * @param  {Number} x        x-position
        - * @param  {Number} y        y-position
        - * @param  {Object} options opentype options (optional), set options.decimals
        - * to set the decimal precision of the path-data
        - *
        - * @return {Object}     this p5.Font object
        - */
        -  _getPathData(line, x, y, options) {
        -    let decimals = 3;
        -
        -    // create path from string/position
        -    if (typeof line === 'string' && arguments.length > 2) {
        -      line = this._getPath(line, x, y, options);
        -    } else if (typeof x === 'object') {
        -    // handle options specified in 2nd arg
        -      options = x;
        -    }
        -
        -    // handle svg arguments
        -    if (options && typeof options.decimals === 'number') {
        -      decimals = options.decimals;
        -    }
        -
        -    return line.toPathData(decimals);
        -  }
        -
        -  /*
        - * Creates an SVG <path> element, as a string,
        - * from the given opentype path or string/position
        - *
        - * @param  {Object} path    an opentype path, OR the following:
        - *
        - * @param  {String} line     a line of text
        - * @param  {Number} x        x-position
        - * @param  {Number} y        y-position
        - * @param  {Object} options opentype options (optional), set options.decimals
        - * to set the decimal precision of the path-data in the <path> element,
        - *  options.fill to set the fill color for the <path> element,
        - *  options.stroke to set the stroke color for the <path> element,
        - *  options.strokeWidth to set the strokeWidth for the <path> element.
        - *
        - * @return {Object}     this p5.Font object
        - */
        -  _getSVG(line, x, y, options) {
        -    let decimals = 3;
        -
        -    // create path from string/position
        -    if (typeof line === 'string' && arguments.length > 2) {
        -      line = this._getPath(line, x, y, options);
        -    } else if (typeof x === 'object') {
        -    // handle options specified in 2nd arg
        -      options = x;
        -    }
        -
        -    // handle svg arguments
        -    if (options) {
        -      if (typeof options.decimals === 'number') {
        -        decimals = options.decimals;
        -      }
        -      if (typeof options.strokeWidth === 'number') {
        -        line.strokeWidth = options.strokeWidth;
        -      }
        -      if (typeof options.fill !== 'undefined') {
        -        line.fill = options.fill;
        -      }
        -      if (typeof options.stroke !== 'undefined') {
        -        line.stroke = options.stroke;
        -      }
        -    }
        -
        -    return line.toSVG(decimals);
        -  }
        -
        -  /*
        - * Renders an opentype path or string/position
        - * to the current graphics context
        - *
        - * @param  {Object} path    an opentype path, OR the following:
        - *
        - * @param  {String} line     a line of text
        - * @param  {Number} x        x-position
        - * @param  {Number} y        y-position
        - * @param  {Object} options opentype options (optional)
        - *
        - * @return {p5.Font}     this p5.Font object
        - */
        -  _renderPath(line, x, y, options) {
        -    let pdata;
        -    const pg = (options && options.renderer) || this.parent._renderer;
        -    const ctx = pg.drawingContext;
        -
        -    if (typeof line === 'object' && line.commands) {
        -      pdata = line.commands;
        -    } else {
        -    //pos = handleAlignment(p, ctx, line, x, y);
        -      pdata = this._getPath(line, x, y, options).commands;
        -    }
        -
        -    if (!pg._clipping) ctx.beginPath();
        -
        -    for (const cmd of pdata) {
        -      if (cmd.type === 'M') {
        -        ctx.moveTo(cmd.x, cmd.y);
        -      } else if (cmd.type === 'L') {
        -        ctx.lineTo(cmd.x, cmd.y);
        -      } else if (cmd.type === 'C') {
        -        ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
        -      } else if (cmd.type === 'Q') {
        -        ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y);
        -      } else if (cmd.type === 'Z') {
        -        ctx.closePath();
        -      }
        -    }
        -
        -    // only draw stroke if manually set by user
        -    if (pg._doStroke && pg._strokeSet && !pg._clipping) {
        -      ctx.stroke();
        -    }
        -
        -    if (pg._doFill && !pg._clipping) {
        -    // if fill hasn't been set by user, use default-text-fill
        -      if (!pg._fillSet) {
        -        pg._setFill(constants._DEFAULT_TEXT_FILL);
        -      }
        -      ctx.fill();
        -    }
        -
        -    return this;
        -  }
        -
        -  _textWidth(str, fontSize) {
        -    return this.font.getAdvanceWidth(str, fontSize);
        -  }
        -
        -  _textAscent(fontSize) {
        -    return this.font.ascender * this._scale(fontSize);
        -  }
        -
        -  _textDescent(fontSize) {
        -    return -this.font.descender * this._scale(fontSize);
        -  }
        -
        -  _scale(fontSize) {
        -    return (
        -      1 / this.font.unitsPerEm * (fontSize || this.parent._renderer._textSize)
        -    );
        -  }
        -
        -  _handleAlignment(renderer, line, x, y, textWidth) {
        -    const fontSize = renderer._textSize;
        -
        -    if (typeof textWidth === 'undefined') {
        -      textWidth = this._textWidth(line, fontSize);
        -    }
        -
        -    switch (renderer._textAlign) {
        -      case constants.CENTER:
        -        x -= textWidth / 2;
        -        break;
        -      case constants.RIGHT:
        -        x -= textWidth;
        -        break;
        -    }
        -
        -    switch (renderer._textBaseline) {
        -      case constants.TOP:
        -        y += this._textAscent(fontSize);
        -        break;
        -      case constants.CENTER:
        -        y += this._textAscent(fontSize) / 2;
        -        break;
        -      case constants.BOTTOM:
        -        y -= this._textDescent(fontSize);
        -        break;
        -    }
        -
        -    return { x, y };
        -  }
        -};
        -// path-utils
        -
        -function pathToPoints(cmds, options) {
        -  const opts = parseOpts(options, {
        -    sampleFactor: 0.1,
        -    simplifyThreshold: 0
        -  });
        -
        -  const // total-length
        -    len = pointAtLength(cmds, 0, 1),
        -    t = len / (len * opts.sampleFactor),
        -    pts = [];
        -
        -  for (let i = 0; i < len; i += t) {
        -    pts.push(pointAtLength(cmds, i));
        -  }
        -
        -  if (opts.simplifyThreshold) {
        -    simplify(pts, opts.simplifyThreshold);
        -  }
        -
        -  return pts;
        -}
        -
        -function simplify(pts, angle = 0) {
        -  let num = 0;
        -  for (let i = pts.length - 1; pts.length > 3 && i >= 0; --i) {
        -    if (collinear(at(pts, i - 1), at(pts, i), at(pts, i + 1), angle)) {
        -      // Remove the middle point
        -      pts.splice(i % pts.length, 1);
        -      num++;
        -    }
        -  }
        -  return num;
        -}
        -
        -function splitPaths(cmds) {
        -  const paths = [];
        -  let current;
        -  for (let i = 0; i < cmds.length; i++) {
        -    if (cmds[i].type === 'M') {
        -      if (current) {
        -        paths.push(current);
        -      }
        -      current = [];
        -    }
        -    current.push(cmdToArr(cmds[i]));
        -  }
        -  paths.push(current);
        -
        -  return paths;
        -}
        -
        -function cmdToArr(cmd) {
        -  const arr = [cmd.type];
        -  if (cmd.type === 'M' || cmd.type === 'L') {
        -    // moveto or lineto
        -    arr.push(cmd.x, cmd.y);
        -  } else if (cmd.type === 'C') {
        -    arr.push(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y);
        -  } else if (cmd.type === 'Q') {
        -    arr.push(cmd.x1, cmd.y1, cmd.x, cmd.y);
        -  }
        -  // else if (cmd.type === 'Z') { /* no-op */ }
        -  return arr;
        -}
        -
        -function parseOpts(options, defaults) {
        -  if (typeof options !== 'object') {
        -    options = defaults;
        -  } else {
        -    for (const key in defaults) {
        -      if (typeof options[key] === 'undefined') {
        -        options[key] = defaults[key];
        -      }
        -    }
        -  }
        -  return options;
        -}
        -
        -//////////////////////// Helpers ////////////////////////////
        -
        -function at(v, i) {
        -  const s = v.length;
        -  return v[i < 0 ? i % s + s : i % s];
        -}
        -
        -function collinear(a, b, c, thresholdAngle) {
        -  if (!thresholdAngle) {
        -    return areaTriangle(a, b, c) === 0;
        -  }
        -
        -  if (typeof collinear.tmpPoint1 === 'undefined') {
        -    collinear.tmpPoint1 = [];
        -    collinear.tmpPoint2 = [];
        -  }
        -
        -  const ab = collinear.tmpPoint1,
        -    bc = collinear.tmpPoint2;
        -  ab.x = b.x - a.x;
        -  ab.y = b.y - a.y;
        -  bc.x = c.x - b.x;
        -  bc.y = c.y - b.y;
        -
        -  const dot = ab.x * bc.x + ab.y * bc.y,
        -    magA = Math.sqrt(ab.x * ab.x + ab.y * ab.y),
        -    magB = Math.sqrt(bc.x * bc.x + bc.y * bc.y),
        -    angle = Math.acos(dot / (magA * magB));
        -
        -  return angle < thresholdAngle;
        -}
        -
        -function areaTriangle(a, b, c) {
        -  return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
        -}
        -
        -// Portions of below code copyright 2008 Dmitry Baranovskiy (via MIT license)
        -
        -function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
        -  const t1 = 1 - t;
        -  const t13 = Math.pow(t1, 3);
        -  const t12 = Math.pow(t1, 2);
        -  const t2 = t * t;
        -  const t3 = t2 * t;
        -  const x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x;
        -  const y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y;
        -  const mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x);
        -  const my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y);
        -  const nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x);
        -  const ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y);
        -  const ax = t1 * p1x + t * c1x;
        -  const ay = t1 * p1y + t * c1y;
        -  const cx = t1 * c2x + t * p2x;
        -  const cy = t1 * c2y + t * p2y;
        -  let alpha = 90 - Math.atan2(mx - nx, my - ny) * 180 / Math.PI;
        -
        -  if (mx > nx || my < ny) {
        -    alpha += 180;
        -  }
        -
        -  return {
        -    x,
        -    y,
        -    m: { x: mx, y: my },
        -    n: { x: nx, y: ny },
        -    start: { x: ax, y: ay },
        -    end: { x: cx, y: cy },
        -    alpha
        -  };
        -}
        -
        -function getPointAtSegmentLength(
        -  p1x,
        -  p1y,
        -  c1x,
        -  c1y,
        -  c2x,
        -  c2y,
        -  p2x,
        -  p2y,
        -  length
        -) {
        -  return length == null
        -    ? bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y)
        -    : findDotsAtSegment(
        -      p1x,
        -      p1y,
        -      c1x,
        -      c1y,
        -      c2x,
        -      c2y,
        -      p2x,
        -      p2y,
        -      getTatLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length)
        -    );
        -}
        -
        -function pointAtLength(path, length, istotal) {
        -  path = path2curve(path);
        -  let x;
        -  let y;
        -  let p;
        -  let l;
        -  let sp = '';
        -  const subpaths = {};
        -  let point;
        -  let len = 0;
        -  for (let i = 0, ii = path.length; i < ii; i++) {
        -    p = path[i];
        -    if (p[0] === 'M') {
        -      x = +p[1];
        -      y = +p[2];
        -    } else {
        -      l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
        -      if (len + l > length) {
        -        if (!istotal) {
        -          point = getPointAtSegmentLength(
        -            x,
        -            y,
        -            p[1],
        -            p[2],
        -            p[3],
        -            p[4],
        -            p[5],
        -            p[6],
        -            length - len
        -          );
        -          return { x: point.x, y: point.y, alpha: point.alpha };
        -        }
        -      }
        -      len += l;
        -      x = +p[5];
        -      y = +p[6];
        -    }
        -    sp += p.shift() + p;
        -  }
        -  subpaths.end = sp;
        -
        -  point = istotal
        -    ? len
        -    : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
        -
        -  if (point.alpha) {
        -    point = { x: point.x, y: point.y, alpha: point.alpha };
        -  }
        -
        -  return point;
        -}
        -
        -function pathToAbsolute(pathArray) {
        -  let res = [],
        -    x = 0,
        -    y = 0,
        -    mx = 0,
        -    my = 0,
        -    start = 0;
        -  if (!pathArray) {
        -    // console.warn("Unexpected state: undefined pathArray"); // shouldn't happen
        -    return res;
        -  }
        -  if (pathArray[0][0] === 'M') {
        -    x = +pathArray[0][1];
        -    y = +pathArray[0][2];
        -    mx = x;
        -    my = y;
        -    start++;
        -    res[0] = ['M', x, y];
        -  }
        -
        -  let dots;
        -
        -  const crz =
        -    pathArray.length === 3 &&
        -    pathArray[0][0] === 'M' &&
        -    pathArray[1][0].toUpperCase() === 'R' &&
        -    pathArray[2][0].toUpperCase() === 'Z';
        -
        -  for (let r, pa, i = start, ii = pathArray.length; i < ii; i++) {
        -    res.push((r = []));
        -    pa = pathArray[i];
        -    if (pa[0] !== pa[0].toUpperCase()) {
        -      r[0] = pa[0].toUpperCase();
        -      switch (r[0]) {
        -        case 'A':
        -          r[1] = pa[1];
        -          r[2] = pa[2];
        -          r[3] = pa[3];
        -          r[4] = pa[4];
        -          r[5] = pa[5];
        -          r[6] = +(pa[6] + x);
        -          r[7] = +(pa[7] + y);
        -          break;
        -        case 'V':
        -          r[1] = +pa[1] + y;
        -          break;
        -        case 'H':
        -          r[1] = +pa[1] + x;
        -          break;
        -        case 'R':
        -          dots = [x, y].concat(pa.slice(1));
        -          for (let j = 2, jj = dots.length; j < jj; j++) {
        -            dots[j] = +dots[j] + x;
        -            dots[++j] = +dots[j] + y;
        -          }
        -          res.pop();
        -          res = res.concat(catmullRom2bezier(dots, crz));
        -          break;
        -        case 'M':
        -          mx = +pa[1] + x;
        -          my = +pa[2] + y;
        -          break;
        -        default:
        -          for (let j = 1, jj = pa.length; j < jj; j++) {
        -            r[j] = +pa[j] + (j % 2 ? x : y);
        -          }
        -      }
        -    } else if (pa[0] === 'R') {
        -      dots = [x, y].concat(pa.slice(1));
        -      res.pop();
        -      res = res.concat(catmullRom2bezier(dots, crz));
        -      r = ['R'].concat(pa.slice(-2));
        -    } else {
        -      for (let k = 0, kk = pa.length; k < kk; k++) {
        -        r[k] = pa[k];
        -      }
        -    }
        -    switch (r[0]) {
        -      case 'Z':
        -        x = mx;
        -        y = my;
        -        break;
        -      case 'H':
        -        x = r[1];
        -        break;
        -      case 'V':
        -        y = r[1];
        -        break;
        -      case 'M':
        -        mx = r[r.length - 2];
        -        my = r[r.length - 1];
        -        break;
        -      default:
        -        x = r[r.length - 2];
        -        y = r[r.length - 1];
        -    }
        -  }
        -  return res;
        -}
        -
        -function path2curve(path, path2) {
        -  const p = pathToAbsolute(path),
        -    p2 = path2 && pathToAbsolute(path2);
        -  const attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null };
        -  const attrs2 = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null };
        -  const pcoms1 = []; // path commands of original path p
        -  const pcoms2 = []; // path commands of original path p2
        -  let ii;
        -
        -  const processPath = (path, d, pcom) => {
        -      let nx;
        -      let ny;
        -      const tq = { T: 1, Q: 1 };
        -      if (!path) {
        -        return ['C', d.x, d.y, d.x, d.y, d.x, d.y];
        -      }
        -      if (!(path[0] in tq)) {
        -        d.qx = d.qy = null;
        -      }
        -      switch (path[0]) {
        -        case 'M':
        -          d.X = path[1];
        -          d.Y = path[2];
        -          break;
        -        case 'A':
        -          path = ['C'].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1))));
        -          break;
        -        case 'S':
        -          if (pcom === 'C' || pcom === 'S') {
        -            nx = d.x * 2 - d.bx;
        -            ny = d.y * 2 - d.by;
        -          } else {
        -            nx = d.x;
        -            ny = d.y;
        -          }
        -          path = ['C', nx, ny].concat(path.slice(1));
        -          break;
        -        case 'T':
        -          if (pcom === 'Q' || pcom === 'T') {
        -            d.qx = d.x * 2 - d.qx;
        -            d.qy = d.y * 2 - d.qy;
        -          } else {
        -            d.qx = d.x;
        -            d.qy = d.y;
        -          }
        -          path = ['C'].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
        -          break;
        -        case 'Q':
        -          d.qx = path[1];
        -          d.qy = path[2];
        -          path = ['C'].concat(
        -            q2c(d.x, d.y, path[1], path[2], path[3], path[4])
        -          );
        -          break;
        -        case 'L':
        -          path = ['C'].concat(l2c(d.x, d.y, path[1], path[2]));
        -          break;
        -        case 'H':
        -          path = ['C'].concat(l2c(d.x, d.y, path[1], d.y));
        -          break;
        -        case 'V':
        -          path = ['C'].concat(l2c(d.x, d.y, d.x, path[1]));
        -          break;
        -        case 'Z':
        -          path = ['C'].concat(l2c(d.x, d.y, d.X, d.Y));
        -          break;
        -      }
        -      return path;
        -    },
        -    fixArc = (pp, i) => {
        -      if (pp[i].length > 7) {
        -        pp[i].shift();
        -        const pi = pp[i];
        -        while (pi.length) {
        -          pcoms1[i] = 'A';
        -          if (p2) {
        -            pcoms2[i] = 'A';
        -          }
        -          pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6)));
        -        }
        -        pp.splice(i, 1);
        -        ii = Math.max(p.length, (p2 && p2.length) || 0);
        -      }
        -    },
        -    fixM = (path1, path2, a1, a2, i) => {
        -      if (path1 && path2 && path1[i][0] === 'M' && path2[i][0] !== 'M') {
        -        path2.splice(i, 0, ['M', a2.x, a2.y]);
        -        a1.bx = 0;
        -        a1.by = 0;
        -        a1.x = path1[i][1];
        -        a1.y = path1[i][2];
        -        ii = Math.max(p.length, (p2 && p2.length) || 0);
        -      }
        -    };
        -
        -  let pfirst = ''; // temporary holder for original path command
        -  let pcom = ''; // holder for previous path command of original path
        -
        -  ii = Math.max(p.length, (p2 && p2.length) || 0);
        -  for (let i = 0; i < ii; i++) {
        -    if (p[i]) {
        -      pfirst = p[i][0];
        -    } // save current path command
        -
        -    if (pfirst !== 'C') {
        -      pcoms1[i] = pfirst; // Save current path command
        -      if (i) {
        -        pcom = pcoms1[i - 1];
        -      } // Get previous path command pcom
        -    }
        -    p[i] = processPath(p[i], attrs, pcom);
        -
        -    if (pcoms1[i] !== 'A' && pfirst === 'C') {
        -      pcoms1[i] = 'C';
        -    }
        -
        -    fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1
        -
        -    if (p2) {
        -      // the same procedures is done to p2
        -      if (p2[i]) {
        -        pfirst = p2[i][0];
        -      }
        -      if (pfirst !== 'C') {
        -        pcoms2[i] = pfirst;
        -        if (i) {
        -          pcom = pcoms2[i - 1];
        -        }
        -      }
        -      p2[i] = processPath(p2[i], attrs2, pcom);
        -
        -      if (pcoms2[i] !== 'A' && pfirst === 'C') {
        -        pcoms2[i] = 'C';
        -      }
        -
        -      fixArc(p2, i);
        -    }
        -    fixM(p, p2, attrs, attrs2, i);
        -    fixM(p2, p, attrs2, attrs, i);
        -    const seg = p[i],
        -      seg2 = p2 && p2[i],
        -      seglen = seg.length,
        -      seg2len = p2 && seg2.length;
        -    attrs.x = seg[seglen - 2];
        -    attrs.y = seg[seglen - 1];
        -    attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
        -    attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
        -    attrs2.bx = p2 && (parseFloat(seg2[seg2len - 4]) || attrs2.x);
        -    attrs2.by = p2 && (parseFloat(seg2[seg2len - 3]) || attrs2.y);
        -    attrs2.x = p2 && seg2[seg2len - 2];
        -    attrs2.y = p2 && seg2[seg2len - 1];
        -  }
        -
        -  return p2 ? [p, p2] : p;
        -}
        -
        -function a2c(x1, y1, rx, ry, angle, lac, sweep_flag, x2, y2, recursive) {
        -  // for more information of where this Math came from visit:
        -  // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
        -  const PI = Math.PI;
        -
        -  const _120 = PI * 120 / 180;
        -  let f1;
        -  let f2;
        -  let cx;
        -  let cy;
        -  const rad = PI / 180 * (+angle || 0);
        -  let res = [];
        -  let xy;
        -
        -  const rotate = (x, y, rad) => {
        -    const X = x * Math.cos(rad) - y * Math.sin(rad),
        -      Y = x * Math.sin(rad) + y * Math.cos(rad);
        -    return { x: X, y: Y };
        -  };
        -
        -  if (!recursive) {
        -    xy = rotate(x1, y1, -rad);
        -    x1 = xy.x;
        -    y1 = xy.y;
        -    xy = rotate(x2, y2, -rad);
        -    x2 = xy.x;
        -    y2 = xy.y;
        -    const x = (x1 - x2) / 2;
        -    const y = (y1 - y2) / 2;
        -    let h = x * x / (rx * rx) + y * y / (ry * ry);
        -    if (h > 1) {
        -      h = Math.sqrt(h);
        -      rx = h * rx;
        -      ry = h * ry;
        -    }
        -    const rx2 = rx * rx,
        -      ry2 = ry * ry;
        -    const k =
        -      (lac === sweep_flag ? -1 : 1) *
        -      Math.sqrt(
        -        Math.abs(
        -          (rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)
        -        )
        -      );
        -
        -    cx = k * rx * y / ry + (x1 + x2) / 2;
        -    cy = k * -ry * x / rx + (y1 + y2) / 2;
        -    f1 = Math.asin(((y1 - cy) / ry).toFixed(9));
        -    f2 = Math.asin(((y2 - cy) / ry).toFixed(9));
        -
        -    f1 = x1 < cx ? PI - f1 : f1;
        -    f2 = x2 < cx ? PI - f2 : f2;
        -
        -    if (f1 < 0) {
        -      f1 = PI * 2 + f1;
        -    }
        -    if (f2 < 0) {
        -      f2 = PI * 2 + f2;
        -    }
        -
        -    if (sweep_flag && f1 > f2) {
        -      f1 = f1 - PI * 2;
        -    }
        -    if (!sweep_flag && f2 > f1) {
        -      f2 = f2 - PI * 2;
        -    }
        -  } else {
        -    f1 = recursive[0];
        -    f2 = recursive[1];
        -    cx = recursive[2];
        -    cy = recursive[3];
        -  }
        -  let df = f2 - f1;
        -  if (Math.abs(df) > _120) {
        -    const f2old = f2,
        -      x2old = x2,
        -      y2old = y2;
        -    f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
        -    x2 = cx + rx * Math.cos(f2);
        -    y2 = cy + ry * Math.sin(f2);
        -    res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [
        -      f2,
        -      f2old,
        -      cx,
        -      cy
        -    ]);
        -  }
        -  df = f2 - f1;
        -  const c1 = Math.cos(f1),
        -    s1 = Math.sin(f1),
        -    c2 = Math.cos(f2),
        -    s2 = Math.sin(f2),
        -    t = Math.tan(df / 4),
        -    hx = 4 / 3 * rx * t,
        -    hy = 4 / 3 * ry * t,
        -    m1 = [x1, y1],
        -    m2 = [x1 + hx * s1, y1 - hy * c1],
        -    m3 = [x2 + hx * s2, y2 - hy * c2],
        -    m4 = [x2, y2];
        -  m2[0] = 2 * m1[0] - m2[0];
        -  m2[1] = 2 * m1[1] - m2[1];
        -  if (recursive) {
        -    return [m2, m3, m4].concat(res);
        -  } else {
        -    res = [m2, m3, m4]
        -      .concat(res)
        -      .join()
        -      .split(',');
        -    const newres = [];
        -    for (let i = 0, ii = res.length; i < ii; i++) {
        -      newres[i] =
        -        i % 2
        -          ? rotate(res[i - 1], res[i], rad).y
        -          : rotate(res[i], res[i + 1], rad).x;
        -    }
        -    return newres;
        -  }
        -}
        -
        -// http://schepers.cc/getting-to-the-point
        -function catmullRom2bezier(crp, z) {
        -  const d = [];
        -  for (let i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
        -    const p = [
        -      {
        -        x: +crp[i - 2],
        -        y: +crp[i - 1]
        -      },
        -      {
        -        x: +crp[i],
        -        y: +crp[i + 1]
        -      },
        -      {
        -        x: +crp[i + 2],
        -        y: +crp[i + 3]
        -      },
        -      {
        -        x: +crp[i + 4],
        -        y: +crp[i + 5]
        -      }
        -    ];
        -    if (z) {
        -      if (!i) {
        -        p[0] = {
        -          x: +crp[iLen - 2],
        -          y: +crp[iLen - 1]
        -        };
        -      } else if (iLen - 4 === i) {
        -        p[3] = {
        -          x: +crp[0],
        -          y: +crp[1]
        -        };
        -      } else if (iLen - 2 === i) {
        -        p[2] = {
        -          x: +crp[0],
        -          y: +crp[1]
        -        };
        -        p[3] = {
        -          x: +crp[2],
        -          y: +crp[3]
        -        };
        -      }
        -    } else {
        -      if (iLen - 4 === i) {
        -        p[3] = p[2];
        -      } else if (!i) {
        -        p[0] = {
        -          x: +crp[i],
        -          y: +crp[i + 1]
        -        };
        -      }
        -    }
        -    d.push([
        -      'C',
        -      (-p[0].x + 6 * p[1].x + p[2].x) / 6,
        -      (-p[0].y + 6 * p[1].y + p[2].y) / 6,
        -      (p[1].x + 6 * p[2].x - p[3].x) / 6,
        -      (p[1].y + 6 * p[2].y - p[3].y) / 6,
        -      p[2].x,
        -      p[2].y
        -    ]);
        -  }
        -
        -  return d;
        -}
        -
        -function l2c(x1, y1, x2, y2) {
        -  return [x1, y1, x2, y2, x2, y2];
        -}
        -
        -function q2c(x1, y1, ax, ay, x2, y2) {
        -  const _13 = 1 / 3,
        -    _23 = 2 / 3;
        -  return [
        -    _13 * x1 + _23 * ax,
        -    _13 * y1 + _23 * ay,
        -    _13 * x2 + _23 * ax,
        -    _13 * y2 + _23 * ay,
        -    x2,
        -    y2
        -  ];
        -}
        -
        -function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
        -  if (z == null) {
        -    z = 1;
        -  }
        -  z = z > 1 ? 1 : z < 0 ? 0 : z;
        -  const z2 = z / 2;
        -  const n = 12;
        -  const Tvalues = [
        -    -0.1252,
        -    0.1252,
        -    -0.3678,
        -    0.3678,
        -    -0.5873,
        -    0.5873,
        -    -0.7699,
        -    0.7699,
        -    -0.9041,
        -    0.9041,
        -    -0.9816,
        -    0.9816
        -  ];
        -
        -  let sum = 0;
        -  const Cvalues = [
        -    0.2491,
        -    0.2491,
        -    0.2335,
        -    0.2335,
        -    0.2032,
        -    0.2032,
        -    0.1601,
        -    0.1601,
        -    0.1069,
        -    0.1069,
        -    0.0472,
        -    0.0472
        -  ];
        -
        -  for (let i = 0; i < n; i++) {
        -    const ct = z2 * Tvalues[i] + z2,
        -      xbase = base3(ct, x1, x2, x3, x4),
        -      ybase = base3(ct, y1, y2, y3, y4),
        -      comb = xbase * xbase + ybase * ybase;
        -    sum += Cvalues[i] * Math.sqrt(comb);
        -  }
        -  return z2 * sum;
        -}
        -
        -function getTatLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
        -  if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
        -    return;
        -  }
        -  const t = 1;
        -  let step = t / 2;
        -  let t2 = t - step;
        -  let l;
        -  const e = 0.01;
        -  l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
        -  while (Math.abs(l - ll) > e) {
        -    step /= 2;
        -    t2 += (l < ll ? 1 : -1) * step;
        -    l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
        -  }
        -  return t2;
        -}
        -
        -function base3(t, p1, p2, p3, p4) {
        -  const t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
        -    t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
        -  return t * t2 - 3 * p1 + 3 * p2;
        -}
        -
        -function cacheKey(...args) {
        -  let hash = '';
        -  for (let i = args.length - 1; i >= 0; --i) {
        -    hash += `?${args[i]}`;
        -  }
        -  return hash;
        -}
        -
        -export default p5;
        diff --git a/src/utilities/array_functions.js b/src/utilities/array_functions.js
        index 7b8dfeb1e2..378810c715 100644
        --- a/src/utilities/array_functions.js
        +++ b/src/utilities/array_functions.js
        @@ -5,413 +5,417 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        +function arrayFunctions(p5, fn){
        +  /**
        +   * Adds a value to the end of an array. Extends the length of
        +   * the array by one. Maps to Array.push().
        +   *
        +   * @method append
        +   * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push">array.push(value)</a> instead.
        +   * @param {Array} array Array to append
        +   * @param {Any} value to be added to the Array
        +   * @return {Array} the array that was appended to
        +   * @example
        +   * <div class='norender'><code>
        +   * function setup() {
        +   *   let myArray = ['Mango', 'Apple', 'Papaya'];
        +   *   print(myArray); // ['Mango', 'Apple', 'Papaya']
        +   *
        +   *   append(myArray, 'Peach');
        +   *   print(myArray); // ['Mango', 'Apple', 'Papaya', 'Peach']
        +   * }
        +   * </code></div>
        +   */
        +  fn.append = function (array, value) {
        +    array.push(value);
        +    return array;
        +  };
         
        -/**
        - * Adds a value to the end of an array. Extends the length of
        - * the array by one. Maps to Array.push().
        - *
        - * @method append
        - * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push">array.push(value)</a> instead.
        - * @param {Array} array Array to append
        - * @param {any} value to be added to the Array
        - * @return {Array} the array that was appended to
        - * @example
        - * <div class='norender'><code>
        - * function setup() {
        - *   let myArray = ['Mango', 'Apple', 'Papaya'];
        - *   print(myArray); // ['Mango', 'Apple', 'Papaya']
        - *
        - *   append(myArray, 'Peach');
        - *   print(myArray); // ['Mango', 'Apple', 'Papaya', 'Peach']
        - * }
        - * </code></div>
        - */
        -p5.prototype.append = function(array, value) {
        -  array.push(value);
        -  return array;
        -};
        +  /**
        +   * Copies an array (or part of an array) to another array. The src array is
        +   * copied to the dst array, beginning at the position specified by
        +   * srcPosition and into the position specified by dstPosition. The number of
        +   * elements to copy is determined by length. Note that copying values
        +   * overwrites existing values in the destination array. To append values
        +   * instead of overwriting them, use <a href="#/p5/concat">concat()</a>.
        +   *
        +   * The simplified version with only two arguments, arrayCopy(src, dst),
        +   * copies an entire array to another of the same size. It is equivalent to
        +   * arrayCopy(src, 0, dst, 0, src.length).
        +   *
        +   * Using this function is far more efficient for copying array data than
        +   * iterating through a for() loop and copying each element individually.
        +   *
        +   * @method arrayCopy
        +   * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin">arr1.copyWithin(arr2)</a> instead.
        +   * @param {Array}  src           the source Array
        +   * @param {Integer} srcPosition  starting position in the source Array
        +   * @param {Array}  dst           the destination Array
        +   * @param {Integer} dstPosition   starting position in the destination Array
        +   * @param {Integer} length        number of Array elements to be copied
        +   *
        +   * @example
        +   * <div class='norender'><code>
        +   * let src = ['A', 'B', 'C'];
        +   * let dst = [1, 2, 3];
        +   * let srcPosition = 1;
        +   * let dstPosition = 0;
        +   * let length = 2;
        +   *
        +   * print(src); // ['A', 'B', 'C']
        +   * print(dst); // [ 1 ,  2 ,  3 ]
        +   *
        +   * arrayCopy(src, srcPosition, dst, dstPosition, length);
        +   * print(dst); // ['B', 'C', 3]
        +   * </code></div>
        +   */
        +  /**
        +   * @method arrayCopy
        +   * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin">arr1.copyWithin(arr2)</a> instead.
        +   * @param {Array}  src
        +   * @param {Array}  dst
        +   * @param {Integer} [length]
        +   */
        +  fn.arrayCopy = function (src, srcPosition, dst, dstPosition, length) {
        +    // the index to begin splicing from dst array
        +    let start;
        +    let end;
         
        -/**
        - * Copies an array (or part of an array) to another array. The src array is
        - * copied to the dst array, beginning at the position specified by
        - * srcPosition and into the position specified by dstPosition. The number of
        - * elements to copy is determined by length. Note that copying values
        - * overwrites existing values in the destination array. To append values
        - * instead of overwriting them, use <a href="#/p5/concat">concat()</a>.
        - *
        - * The simplified version with only two arguments, arrayCopy(src, dst),
        - * copies an entire array to another of the same size. It is equivalent to
        - * arrayCopy(src, 0, dst, 0, src.length).
        - *
        - * Using this function is far more efficient for copying array data than
        - * iterating through a for() loop and copying each element individually.
        - *
        - * @method arrayCopy
        - * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin">arr1.copyWithin(arr2)</a> instead.
        - * @param {Array}  src           the source Array
        - * @param {Integer} srcPosition  starting position in the source Array
        - * @param {Array}  dst           the destination Array
        - * @param {Integer} dstPosition   starting position in the destination Array
        - * @param {Integer} length        number of Array elements to be copied
        - *
        - * @example
        - * <div class='norender'><code>
        - * let src = ['A', 'B', 'C'];
        - * let dst = [1, 2, 3];
        - * let srcPosition = 1;
        - * let dstPosition = 0;
        - * let length = 2;
        - *
        - * print(src); // ['A', 'B', 'C']
        - * print(dst); // [ 1 ,  2 ,  3 ]
        - *
        - * arrayCopy(src, srcPosition, dst, dstPosition, length);
        - * print(dst); // ['B', 'C', 3]
        - * </code></div>
        - */
        -/**
        - * @method arrayCopy
        - * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin">arr1.copyWithin(arr2)</a> instead.
        - * @param {Array}  src
        - * @param {Array}  dst
        - * @param {Integer} [length]
        - */
        -p5.prototype.arrayCopy = function(src, srcPosition, dst, dstPosition, length) {
        -  // the index to begin splicing from dst array
        -  let start;
        -  let end;
        +    if (typeof length !== 'undefined') {
        +      end = Math.min(length, src.length);
        +      start = dstPosition;
        +      src = src.slice(srcPosition, end + srcPosition);
        +    } else {
        +      if (typeof dst !== 'undefined') {
        +        // src, dst, length
        +        // rename  so we don't get confused
        +        end = dst;
        +        end = Math.min(end, src.length);
        +      } else {
        +        // src, dst
        +        end = src.length;
        +      }
         
        -  if (typeof length !== 'undefined') {
        -    end = Math.min(length, src.length);
        -    start = dstPosition;
        -    src = src.slice(srcPosition, end + srcPosition);
        -  } else {
        -    if (typeof dst !== 'undefined') {
        -      // src, dst, length
        +      start = 0;
               // rename  so we don't get confused
        -      end = dst;
        -      end = Math.min(end, src.length);
        -    } else {
        -      // src, dst
        -      end = src.length;
        +      dst = srcPosition;
        +      src = src.slice(0, end);
             }
         
        -    start = 0;
        -    // rename  so we don't get confused
        -    dst = srcPosition;
        -    src = src.slice(0, end);
        -  }
        +    // Since we are not returning the array and JavaScript is pass by reference
        +    // we must modify the actual values of the array
        +    // instead of reassigning arrays
        +    Array.prototype.splice.apply(dst, [start, end].concat(src));
        +  };
         
        -  // Since we are not returning the array and JavaScript is pass by reference
        -  // we must modify the actual values of the array
        -  // instead of reassigning arrays
        -  Array.prototype.splice.apply(dst, [start, end].concat(src));
        -};
        +  /**
        +   * Concatenates two arrays, maps to Array.concat(). Does not modify the
        +   * input arrays.
        +   *
        +   * @method concat
        +   * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat">arr1.concat(arr2)</a> instead.
        +   * @param {Array} a first Array to concatenate
        +   * @param {Array} b second Array to concatenate
        +   * @return {Array} concatenated array
        +   *
        +   * @example
        +   * <div class = 'norender'><code>
        +   * function setup() {
        +   *   let arr1 = ['A', 'B', 'C'];
        +   *   let arr2 = [1, 2, 3];
        +   *
        +   *   print(arr1); // ['A','B','C']
        +   *   print(arr2); // [1,2,3]
        +   *
        +   *   let arr3 = concat(arr1, arr2);
        +   *
        +   *   print(arr1); // ['A','B','C']
        +   *   print(arr2); // [1, 2, 3]
        +   *   print(arr3); // ['A','B','C', 1, 2, 3]
        +   * }
        +   * </code></div>
        +   */
        +  fn.concat = (list0, list1) => list0.concat(list1);
         
        -/**
        - * Concatenates two arrays, maps to Array.concat(). Does not modify the
        - * input arrays.
        - *
        - * @method concat
        - * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat">arr1.concat(arr2)</a> instead.
        - * @param {Array} a first Array to concatenate
        - * @param {Array} b second Array to concatenate
        - * @return {Array} concatenated array
        - *
        - * @example
        - * <div class = 'norender'><code>
        - * function setup() {
        - *   let arr1 = ['A', 'B', 'C'];
        - *   let arr2 = [1, 2, 3];
        - *
        - *   print(arr1); // ['A','B','C']
        - *   print(arr2); // [1,2,3]
        - *
        - *   let arr3 = concat(arr1, arr2);
        - *
        - *   print(arr1); // ['A','B','C']
        - *   print(arr2); // [1, 2, 3]
        - *   print(arr3); // ['A','B','C', 1, 2, 3]
        - * }
        - * </code></div>
        - */
        -p5.prototype.concat = (list0, list1) => list0.concat(list1);
        +  /**
        +   * Reverses the order of an array, maps to Array.reverse()
        +   *
        +   * @method reverse
        +   * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse">array.reverse()</a> instead.
        +   * @param {Array} list Array to reverse
        +   * @return {Array} the reversed list
        +   * @example
        +   * <div class='norender'><code>
        +   * function setup() {
        +   *   let myArray = ['A', 'B', 'C'];
        +   *   print(myArray); // ['A','B','C']
        +   *
        +   *   reverse(myArray);
        +   *   print(myArray); // ['C','B','A']
        +   * }
        +   * </code></div>
        +   */
        +  fn.reverse = list => list.reverse();
         
        -/**
        - * Reverses the order of an array, maps to Array.reverse()
        - *
        - * @method reverse
        - * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse">array.reverse()</a> instead.
        - * @param {Array} list Array to reverse
        - * @return {Array} the reversed list
        - * @example
        - * <div class='norender'><code>
        - * function setup() {
        - *   let myArray = ['A', 'B', 'C'];
        - *   print(myArray); // ['A','B','C']
        - *
        - *   reverse(myArray);
        - *   print(myArray); // ['C','B','A']
        - * }
        - * </code></div>
        - */
        -p5.prototype.reverse = list => list.reverse();
        +  /**
        +   * Decreases an array by one element and returns the shortened array,
        +   * maps to Array.pop().
        +   *
        +   * @method shorten
        +   * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop">array.pop()</a> instead.
        +   * @param  {Array} list Array to shorten
        +   * @return {Array} shortened Array
        +   * @example
        +   * <div class = 'norender'><code>
        +   * function setup() {
        +   *   let myArray = ['A', 'B', 'C'];
        +   *   print(myArray); // ['A', 'B', 'C']
        +   *   let newArray = shorten(myArray);
        +   *   print(myArray); // ['A','B','C']
        +   *   print(newArray); // ['A','B']
        +   * }
        +   * </code></div>
        +   */
        +  fn.shorten = function (list) {
        +    list.pop();
        +    return list;
        +  };
         
        -/**
        - * Decreases an array by one element and returns the shortened array,
        - * maps to Array.pop().
        - *
        - * @method shorten
        - * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop">array.pop()</a> instead.
        - * @param  {Array} list Array to shorten
        - * @return {Array} shortened Array
        - * @example
        - * <div class = 'norender'><code>
        - * function setup() {
        - *   let myArray = ['A', 'B', 'C'];
        - *   print(myArray); // ['A', 'B', 'C']
        - *   let newArray = shorten(myArray);
        - *   print(myArray); // ['A','B','C']
        - *   print(newArray); // ['A','B']
        - * }
        - * </code></div>
        - */
        -p5.prototype.shorten = function(list) {
        -  list.pop();
        -  return list;
        -};
        +  /**
        +   * Shuffles the elements of an array.
        +   *
        +   * The first parameter, `array`, is the array to be shuffled. For example,
        +   * calling `shuffle(myArray)` will shuffle the elements of `myArray`. By
        +   * default, the original array won’t be modified. Instead, a copy will be
        +   * created, shuffled, and returned.
        +   *
        +   * The second parameter, `modify`, is optional. If `true` is passed, as in
        +   * `shuffle(myArray, true)`, then the array will be shuffled in place without
        +   * making a copy.
        +   *
        +   * @method shuffle
        +   * @param  {Array} array array to shuffle.
        +   * @param  {Boolean} [bool] if `true`, shuffle the original array in place. Defaults to `false`.
        +   * @return {Array} shuffled array.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of colors.
        +   *   let colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];
        +   *
        +   *   // Create a shuffled copy of the array.
        +   *   let shuffledColors = shuffle(colors);
        +   *
        +   *   // Draw  a row of circles using the original array.
        +   *   for (let i = 0; i < colors.length; i += 1) {
        +   *     // Calculate the x-coordinate.
        +   *     let x = (i + 1) * 12.5;
        +   *
        +   *     // Style the circle.
        +   *     let c = colors[i];
        +   *     fill(c);
        +   *
        +   *     // Draw the circle.
        +   *     circle(x, 33, 10);
        +   *   }
        +   *
        +   *   // Draw  a row of circles using the original array.
        +   *   for (let i = 0; i < shuffledColors.length; i += 1) {
        +   *     // Calculate the x-coordinate.
        +   *     let x = (i + 1) * 12.5;
        +   *
        +   *     // Style the circle.
        +   *     let c = shuffledColors[i];
        +   *     fill(c);
        +   *
        +   *     // Draw the circle.
        +   *     circle(x, 67, 10);
        +   *   }
        +   *
        +   *   describe(
        +   *     'Two rows of circles on a gray background. The top row follows the color sequence ROYGBIV. The bottom row has all the same colors but they are shuffled.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of colors.
        +   *   let colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];
        +   *
        +   *   // Shuffle the array.
        +   *   shuffle(colors, true);
        +   *
        +   *   // Draw  a row of circles using the original array.
        +   *   for (let i = 0; i < colors.length; i += 1) {
        +   *     // Calculate the x-coordinate.
        +   *     let x = (i + 1) * 12.5;
        +   *
        +   *     // Style the circle.
        +   *     let c = colors[i];
        +   *     fill(c);
        +   *
        +   *     // Draw the circle.
        +   *     circle(x, 50, 10);
        +   *   }
        +   *
        +   *   describe(
        +   *     'A row of colorful circles on a gray background. Their sequence changes each time the sketch runs.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.shuffle = function (arr, bool) {
        +    const isView = ArrayBuffer && ArrayBuffer.isView && ArrayBuffer.isView(arr);
        +    arr = bool || isView ? arr : arr.slice();
         
        -/**
        - * Shuffles the elements of an array.
        - *
        - * The first parameter, `array`, is the array to be shuffled. For example,
        - * calling `shuffle(myArray)` will shuffle the elements of `myArray`. By
        - * default, the original array won’t be modified. Instead, a copy will be
        - * created, shuffled, and returned.
        - *
        - * The second parameter, `modify`, is optional. If `true` is passed, as in
        - * `shuffle(myArray, true)`, then the array will be shuffled in place without
        - * making a copy.
        - *
        - * @method shuffle
        - * @param  {Array} array array to shuffle.
        - * @param  {Boolean} [bool] if `true`, shuffle the original array in place. Defaults to `false`.
        - * @return {Array} shuffled array.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of colors.
        - *   let colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];
        - *
        - *   // Create a shuffled copy of the array.
        - *   let shuffledColors = shuffle(colors);
        - *
        - *   // Draw  a row of circles using the original array.
        - *   for (let i = 0; i < colors.length; i += 1) {
        - *     // Calculate the x-coordinate.
        - *     let x = (i + 1) * 12.5;
        - *
        - *     // Style the circle.
        - *     let c = colors[i];
        - *     fill(c);
        - *
        - *     // Draw the circle.
        - *     circle(x, 33, 10);
        - *   }
        - *
        - *   // Draw  a row of circles using the original array.
        - *   for (let i = 0; i < shuffledColors.length; i += 1) {
        - *     // Calculate the x-coordinate.
        - *     let x = (i + 1) * 12.5;
        - *
        - *     // Style the circle.
        - *     let c = shuffledColors[i];
        - *     fill(c);
        - *
        - *     // Draw the circle.
        - *     circle(x, 67, 10);
        - *   }
        - *
        - *   describe(
        - *     'Two rows of circles on a gray background. The top row follows the color sequence ROYGBIV. The bottom row has all the same colors but they are shuffled.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of colors.
        - *   let colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];
        - *
        - *   // Shuffle the array.
        - *   shuffle(colors, true);
        - *
        - *   // Draw  a row of circles using the original array.
        - *   for (let i = 0; i < colors.length; i += 1) {
        - *     // Calculate the x-coordinate.
        - *     let x = (i + 1) * 12.5;
        - *
        - *     // Style the circle.
        - *     let c = colors[i];
        - *     fill(c);
        - *
        - *     // Draw the circle.
        - *     circle(x, 50, 10);
        - *   }
        - *
        - *   describe(
        - *     'A row of colorful circles on a gray background. Their sequence changes each time the sketch runs.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.shuffle = function(arr, bool) {
        -  const isView = ArrayBuffer && ArrayBuffer.isView && ArrayBuffer.isView(arr);
        -  arr = bool || isView ? arr : arr.slice();
        +    let rnd,
        +      tmp,
        +      idx = arr.length;
        +    while (idx > 1) {
        +      rnd = (this.random(0, 1) * idx) | 0;
         
        -  let rnd,
        -    tmp,
        -    idx = arr.length;
        -  while (idx > 1) {
        -    rnd = (this.random(0, 1) * idx) | 0;
        +      tmp = arr[--idx];
        +      arr[idx] = arr[rnd];
        +      arr[rnd] = tmp;
        +    }
         
        -    tmp = arr[--idx];
        -    arr[idx] = arr[rnd];
        -    arr[rnd] = tmp;
        -  }
        +    return arr;
        +  };
         
        -  return arr;
        -};
        +  /**
        +   * Sorts an array of numbers from smallest to largest, or puts an array of
        +   * words in alphabetical order. The original array is not modified; a
        +   * re-ordered array is returned. The count parameter states the number of
        +   * elements to sort. For example, if there are 12 elements in an array and
        +   * count is set to 5, only the first 5 elements in the array will be sorted.
        +   *
        +   * @method sort
        +   * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort">array.sort()</a> instead.
        +   * @param {Array} list Array to sort
        +   * @param {Integer} [count] number of elements to sort, starting from 0
        +   * @return {Array} the sorted list
        +   *
        +   * @example
        +   * <div class = 'norender'><code>
        +   * function setup() {
        +   *   let words = ['banana', 'apple', 'pear', 'lime'];
        +   *   print(words); // ['banana', 'apple', 'pear', 'lime']
        +   *   let count = 4; // length of array
        +   *
        +   *   words = sort(words, count);
        +   *   print(words); // ['apple', 'banana', 'lime', 'pear']
        +   * }
        +   * </code></div>
        +   * <div class = 'norender'><code>
        +   * function setup() {
        +   *   let numbers = [2, 6, 1, 5, 14, 9, 8, 12];
        +   *   print(numbers); // [2, 6, 1, 5, 14, 9, 8, 12]
        +   *   let count = 5; // Less than the length of the array
        +   *
        +   *   numbers = sort(numbers, count);
        +   *   print(numbers); // [1,2,5,6,14,9,8,12]
        +   * }
        +   * </code></div>
        +   */
        +  fn.sort = function (list, count) {
        +    let arr = count ? list.slice(0, Math.min(count, list.length)) : list;
        +    const rest = count ? list.slice(Math.min(count, list.length)) : [];
        +    if (typeof arr[0] === 'string') {
        +      arr = arr.sort();
        +    } else {
        +      arr = arr.sort((a, b) => a - b);
        +    }
        +    return arr.concat(rest);
        +  };
         
        -/**
        - * Sorts an array of numbers from smallest to largest, or puts an array of
        - * words in alphabetical order. The original array is not modified; a
        - * re-ordered array is returned. The count parameter states the number of
        - * elements to sort. For example, if there are 12 elements in an array and
        - * count is set to 5, only the first 5 elements in the array will be sorted.
        - *
        - * @method sort
        - * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort">array.sort()</a> instead.
        - * @param {Array} list Array to sort
        - * @param {Integer} [count] number of elements to sort, starting from 0
        - * @return {Array} the sorted list
        - *
        - * @example
        - * <div class = 'norender'><code>
        - * function setup() {
        - *   let words = ['banana', 'apple', 'pear', 'lime'];
        - *   print(words); // ['banana', 'apple', 'pear', 'lime']
        - *   let count = 4; // length of array
        - *
        - *   words = sort(words, count);
        - *   print(words); // ['apple', 'banana', 'lime', 'pear']
        - * }
        - * </code></div>
        - * <div class = 'norender'><code>
        - * function setup() {
        - *   let numbers = [2, 6, 1, 5, 14, 9, 8, 12];
        - *   print(numbers); // [2, 6, 1, 5, 14, 9, 8, 12]
        - *   let count = 5; // Less than the length of the array
        - *
        - *   numbers = sort(numbers, count);
        - *   print(numbers); // [1,2,5,6,14,9,8,12]
        - * }
        - * </code></div>
        - */
        -p5.prototype.sort = function(list, count) {
        -  let arr = count ? list.slice(0, Math.min(count, list.length)) : list;
        -  const rest = count ? list.slice(Math.min(count, list.length)) : [];
        -  if (typeof arr[0] === 'string') {
        -    arr = arr.sort();
        -  } else {
        -    arr = arr.sort((a, b) => a - b);
        -  }
        -  return arr.concat(rest);
        -};
        +  /**
        +   * Inserts a value or an array of values into an existing array. The first
        +   * parameter specifies the initial array to be modified, and the second
        +   * parameter defines the data to be inserted. The third parameter is an index
        +   * value which specifies the array position from which to insert data.
        +   * (Remember that array index numbering starts at zero, so the first position
        +   * is 0, the second position is 1, and so on.)
        +   *
        +   * @method splice
        +   * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice">array.splice()</a> instead.
        +   * @param {Array}  list Array to splice into
        +   * @param {Any}    value value to be spliced in
        +   * @param {Integer} position in the array from which to insert data
        +   * @return {Array} the list
        +   *
        +   * @example
        +   * <div class = 'norender'><code>
        +   * function setup() {
        +   *   let myArray = [0, 1, 2, 3, 4];
        +   *   let insArray = ['A', 'B', 'C'];
        +   *   print(myArray); // [0, 1, 2, 3, 4]
        +   *   print(insArray); // ['A','B','C']
        +   *
        +   *   splice(myArray, insArray, 3);
        +   *   print(myArray); // [0,1,2,'A','B','C',3,4]
        +   * }
        +   * </code></div>
        +   */
        +  fn.splice = function (list, value, index) {
        +    // note that splice returns spliced elements and not an array
        +    Array.prototype.splice.apply(list, [index, 0].concat(value));
         
        -/**
        - * Inserts a value or an array of values into an existing array. The first
        - * parameter specifies the initial array to be modified, and the second
        - * parameter defines the data to be inserted. The third parameter is an index
        - * value which specifies the array position from which to insert data.
        - * (Remember that array index numbering starts at zero, so the first position
        - * is 0, the second position is 1, and so on.)
        - *
        - * @method splice
        - * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice">array.splice()</a> instead.
        - * @param {Array}  list Array to splice into
        - * @param {any}    value value to be spliced in
        - * @param {Integer} position in the array from which to insert data
        - * @return {Array} the list
        - *
        - * @example
        - * <div class = 'norender'><code>
        - * function setup() {
        - *   let myArray = [0, 1, 2, 3, 4];
        - *   let insArray = ['A', 'B', 'C'];
        - *   print(myArray); // [0, 1, 2, 3, 4]
        - *   print(insArray); // ['A','B','C']
        - *
        - *   splice(myArray, insArray, 3);
        - *   print(myArray); // [0,1,2,'A','B','C',3,4]
        - * }
        - * </code></div>
        - */
        -p5.prototype.splice = function(list, value, index) {
        -  // note that splice returns spliced elements and not an array
        -  Array.prototype.splice.apply(list, [index, 0].concat(value));
        +    return list;
        +  };
         
        -  return list;
        -};
        +  /**
        +   * Extracts an array of elements from an existing array. The list parameter
        +   * defines the array from which the elements will be copied, and the start
        +   * and count parameters specify which elements to extract. If no count is
        +   * given, elements will be extracted from the start to the end of the array.
        +   * When specifying the start, remember that the first array element is 0.
        +   * This function does not change the source array.
        +   *
        +   * @method subset
        +   * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice">array.slice()</a> instead.
        +   * @param  {Array}  list    Array to extract from
        +   * @param  {Integer} start   position to begin
        +   * @param  {Integer} [count] number of values to extract
        +   * @return {Array}          Array of extracted elements
        +   *
        +   * @example
        +   * <div class = 'norender'><code>
        +   * function setup() {
        +   *   let myArray = [1, 2, 3, 4, 5];
        +   *   print(myArray); // [1, 2, 3, 4, 5]
        +   *
        +   *   let sub1 = subset(myArray, 0, 3);
        +   *   let sub2 = subset(myArray, 2, 2);
        +   *   print(sub1); // [1,2,3]
        +   *   print(sub2); // [3,4]
        +   * }
        +   * </code></div>
        +   */
        +  fn.subset = function (list, start, count) {
        +    if (typeof count !== 'undefined') {
        +      return list.slice(start, start + count);
        +    } else {
        +      return list.slice(start, list.length);
        +    }
        +  };
        +}
         
        -/**
        - * Extracts an array of elements from an existing array. The list parameter
        - * defines the array from which the elements will be copied, and the start
        - * and count parameters specify which elements to extract. If no count is
        - * given, elements will be extracted from the start to the end of the array.
        - * When specifying the start, remember that the first array element is 0.
        - * This function does not change the source array.
        - *
        - * @method subset
        - * @deprecated Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice">array.slice()</a> instead.
        - * @param  {Array}  list    Array to extract from
        - * @param  {Integer} start   position to begin
        - * @param  {Integer} [count] number of values to extract
        - * @return {Array}          Array of extracted elements
        - *
        - * @example
        - * <div class = 'norender'><code>
        - * function setup() {
        - *   let myArray = [1, 2, 3, 4, 5];
        - *   print(myArray); // [1, 2, 3, 4, 5]
        - *
        - *   let sub1 = subset(myArray, 0, 3);
        - *   let sub2 = subset(myArray, 2, 2);
        - *   print(sub1); // [1,2,3]
        - *   print(sub2); // [3,4]
        - * }
        - * </code></div>
        - */
        -p5.prototype.subset = function(list, start, count) {
        -  if (typeof count !== 'undefined') {
        -    return list.slice(start, start + count);
        -  } else {
        -    return list.slice(start, list.length);
        -  }
        -};
        +export default arrayFunctions;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  arrayFunctions(p5, p5.prototype);
        +}
        diff --git a/src/utilities/conversion.js b/src/utilities/conversion.js
        index f0524ec6f5..84254e83b5 100644
        --- a/src/utilities/conversion.js
        +++ b/src/utilities/conversion.js
        @@ -5,1042 +5,1046 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        -
        -/**
        - * Converts a `String` to a floating point (decimal) `Number`.
        - *
        - * `float()` converts strings that resemble numbers, such as `'12.34'`, into
        - * numbers.
        - *
        - * The parameter, `str`, is the string value to convert. For example, calling
        - * `float('12.34')` returns the number `12.34`.  If an array of strings is
        - * passed, as in `float(['12.34', '56.78'])`, then an array of numbers will be
        - * returned.
        - *
        - * Note: If a string can't be converted to a number, as in `float('giraffe')`,
        - * then the value `NaN` (not a number) will be returned.
        - *
        - * @method float
        - * @param {String}  str string to convert.
        - * @return {Number} converted number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let original = '12.3';
        - *
        - *   // Convert the string to a number.
        - *   let converted = float(original);
        - *
        - *   // Double the converted value.
        - *   let twice = converted * 2;
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(12);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} × 2 = ${twice}`, 50, 50);
        - *
        - *   describe('The text "12.3 × 2 = 24.6" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of strings.
        - *   let original = ['60', '30', '15'];
        - *
        - *   // Convert the strings to numbers.
        - *   let diameters = float(original);
        - *
        - *   for (let d of diameters) {
        - *     // Draw a circle.
        - *     circle(50, 50, d);
        - *   }
        - *
        - *   describe('Three white, concentric circles on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method float
        - * @param {String[]} ns array of strings to convert.
        - * @return {Number[]} converted numbers.
        - */
        -p5.prototype.float = function(str) {
        -  if (str instanceof Array) {
        -    return str.map(parseFloat);
        -  }
        -  return parseFloat(str);
        -};
        -
        -/**
        - * Converts a `Boolean`, `String`, or decimal `Number` to an integer.
        - *
        - * `int()` converts values to integers. Integers are positive or negative
        - * numbers without decimals. If the original value has decimals, as in -34.56,
        - * they're removed to produce an integer such as -34.
        - *
        - * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in
        - * `int(false)` or `int(true)`, then the number 0 (`false`) or 1 (`true`) will
        - * be returned. If `n` is a string or number, as in `int('45')` or
        - * `int(67.89)`, then an integer will be returned. If an array is passed, as
        - * in `int([12.34, 56.78])`, then an array of integers will be returned.
        - *
        - * Note: If a value can't be converted to a number, as in `int('giraffe')`,
        - * then the value `NaN` (not a number) will be returned.
        - *
        - * @method int
        - * @param {String|Boolean|Number} n value to convert.
        - * @return {Number} converted number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a Boolean variable.
        - *   let original = false;
        - *
        - *   // Convert the Boolean to an integer.
        - *   let converted = int(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} : ${converted}`, 50, 50);
        - *
        - *   describe('The text "false : 0" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let original = '12.34';
        - *
        - *   // Convert the string to an integer.
        - *   let converted = int(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(14);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} ≈ ${converted}`, 50, 50);
        - *
        - *   describe('The text "12.34 ≈ 12" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a decimal number variable.
        - *   let original = 12.34;
        - *
        - *   // Convert the decimal number to an integer.
        - *   let converted = int(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(14);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} ≈ ${converted}`, 50, 50);
        - *
        - *   describe('The text "12.34 ≈ 12" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of strings.
        - *   let original = ['60', '30', '15'];
        - *
        - *   // Convert the strings to integers.
        - *   let diameters = int(original);
        - *
        - *   for (let d of diameters) {
        - *     // Draw a circle.
        - *     circle(50, 50, d);
        - *   }
        - *
        - *   describe('Three white, concentric circles on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method int
        - * @param {Array} ns values to convert.
        - * @return {Number[]} converted numbers.
        - */
        -p5.prototype.int = function(n, radix = 10) {
        -  if (n === Infinity || n === 'Infinity') {
        -    return Infinity;
        -  } else if (n === -Infinity || n === '-Infinity') {
        -    return -Infinity;
        -  } else if (typeof n === 'string') {
        -    return parseInt(n, radix);
        -  } else if (typeof n === 'number') {
        -    return n | 0;
        -  } else if (typeof n === 'boolean') {
        -    return n ? 1 : 0;
        -  } else if (n instanceof Array) {
        -    return n.map(n => p5.prototype.int(n, radix));
        -  }
        -};
        +function conversion(p5, fn){
        +  /**
        +   * Converts a `String` to a floating point (decimal) `Number`.
        +   *
        +   * `float()` converts strings that resemble numbers, such as `'12.34'`, into
        +   * numbers.
        +   *
        +   * The parameter, `str`, is the string value to convert. For example, calling
        +   * `float('12.34')` returns the number `12.34`.  If an array of strings is
        +   * passed, as in `float(['12.34', '56.78'])`, then an array of numbers will be
        +   * returned.
        +   *
        +   * Note: If a string can't be converted to a number, as in `float('giraffe')`,
        +   * then the value `NaN` (not a number) will be returned.
        +   *
        +   * @method float
        +   * @param {String}  str string to convert.
        +   * @return {Number} converted number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let original = '12.3';
        +   *
        +   *   // Convert the string to a number.
        +   *   let converted = float(original);
        +   *
        +   *   // Double the converted value.
        +   *   let twice = converted * 2;
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(12);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} × 2 = ${twice}`, 50, 50);
        +   *
        +   *   describe('The text "12.3 × 2 = 24.6" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of strings.
        +   *   let original = ['60', '30', '15'];
        +   *
        +   *   // Convert the strings to numbers.
        +   *   let diameters = float(original);
        +   *
        +   *   for (let d of diameters) {
        +   *     // Draw a circle.
        +   *     circle(50, 50, d);
        +   *   }
        +   *
        +   *   describe('Three white, concentric circles on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method float
        +   * @param {String[]} ns array of strings to convert.
        +   * @return {Number[]} converted numbers.
        +   */
        +  fn.float = function(str) {
        +    if (str instanceof Array) {
        +      return str.map(parseFloat);
        +    }
        +    return parseFloat(str);
        +  };
         
        -/**
        - * Converts a `Boolean` or `Number` to `String`.
        - *
        - * `str()` converts values to strings. See the
        - * <a href="#/p5/String">String</a> reference page for guidance on using
        - * template literals instead.
        - *
        - * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in
        - * `str(false)` or `str(true)`, then the value will be returned as a string,
        - * as in `'false'` or `'true'`. If `n` is a number, as in `str(123)`, then its
        - * value will be returned as a string, as in `'123'`. If an array is passed,
        - * as in `str([12.34, 56.78])`, then an array of strings will be returned.
        - *
        - * @method str
        - * @param {String|Boolean|Number} n value to convert.
        - * @return {String} converted string.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a Boolean variable.
        - *   let original = false;
        - *
        - *   // Convert the Boolean to a string.
        - *   let converted = str(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} : ${converted}`, 50, 50);
        - *
        - *   describe('The text "false : false" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a number variable.
        - *   let original = 123;
        - *
        - *   // Convert the number to a string.
        - *   let converted = str(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} = ${converted}`, 50, 50);
        - *
        - *   describe('The text "123 = 123" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of numbers.
        - *   let original = [12, 34, 56];
        - *
        - *   // Convert the numbers to strings.
        - *   let strings = str(original);
        - *
        - *   // Create an empty string variable.
        - *   let final = '';
        - *
        - *   // Concatenate all the strings.
        - *   for (let s of strings) {
        - *     final += s;
        - *   }
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the concatenated string.
        - *   text(final, 50, 50);
        - *
        - *   describe('The text "123456" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.str = function(n) {
        -  if (n instanceof Array) {
        -    return n.map(p5.prototype.str);
        -  } else {
        -    return String(n);
        -  }
        -};
        +  /**
        +   * Converts a `Boolean`, `String`, or decimal `Number` to an integer.
        +   *
        +   * `int()` converts values to integers. Integers are positive or negative
        +   * numbers without decimals. If the original value has decimals, as in -34.56,
        +   * they're removed to produce an integer such as -34.
        +   *
        +   * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in
        +   * `int(false)` or `int(true)`, then the number 0 (`false`) or 1 (`true`) will
        +   * be returned. If `n` is a string or number, as in `int('45')` or
        +   * `int(67.89)`, then an integer will be returned. If an array is passed, as
        +   * in `int([12.34, 56.78])`, then an array of integers will be returned.
        +   *
        +   * Note: If a value can't be converted to a number, as in `int('giraffe')`,
        +   * then the value `NaN` (not a number) will be returned.
        +   *
        +   * @method int
        +   * @param {String|Boolean|Number} n value to convert.
        +   * @return {Number} converted number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a Boolean variable.
        +   *   let original = false;
        +   *
        +   *   // Convert the Boolean to an integer.
        +   *   let converted = int(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} : ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "false : 0" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let original = '12.34';
        +   *
        +   *   // Convert the string to an integer.
        +   *   let converted = int(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(14);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} ≈ ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "12.34 ≈ 12" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a decimal number variable.
        +   *   let original = 12.34;
        +   *
        +   *   // Convert the decimal number to an integer.
        +   *   let converted = int(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(14);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} ≈ ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "12.34 ≈ 12" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of strings.
        +   *   let original = ['60', '30', '15'];
        +   *
        +   *   // Convert the strings to integers.
        +   *   let diameters = int(original);
        +   *
        +   *   for (let d of diameters) {
        +   *     // Draw a circle.
        +   *     circle(50, 50, d);
        +   *   }
        +   *
        +   *   describe('Three white, concentric circles on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method int
        +   * @param {Array} ns values to convert.
        +   * @return {Number[]} converted numbers.
        +   */
        +  fn.int = function(n, radix = 10) {
        +    if (n === Infinity || n === 'Infinity') {
        +      return Infinity;
        +    } else if (n === -Infinity || n === '-Infinity') {
        +      return -Infinity;
        +    } else if (typeof n === 'string') {
        +      return parseInt(n, radix);
        +    } else if (typeof n === 'number') {
        +      return n | 0;
        +    } else if (typeof n === 'boolean') {
        +      return n ? 1 : 0;
        +    } else if (n instanceof Array) {
        +      return n.map(n => fn.int(n, radix));
        +    }
        +  };
         
        -/**
        - * Converts a `String` or `Number` to a `Boolean`.
        - *
        - * `boolean()` converts values to `true` or `false`.
        - *
        - * The parameter, `n`, is the value to convert. If `n` is a string, then
        - * `boolean('true')` will return `true` and every other string value will
        - * return `false`. If `n` is a number, then `boolean(0)` will return `false`
        - * and every other numeric value will return `true`. If an array is passed, as
        - * `in boolean([0, 1, 'true', 'blue'])`, then an array of Boolean values will
        - * be returned.
        - *
        - * @method boolean
        - * @param {String|Boolean|Number} n value to convert.
        - * @return {Boolean} converted Boolean value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a number variable.
        - *   let original = 0;
        - *
        - *   // Convert the number to a Boolean value.
        - *   let converted = boolean(original);
        - *
        - *   // Style the circle based on the converted value.
        - *   if (converted === true) {
        - *     fill('blue');
        - *   } else {
        - *     fill('red');
        - *   }
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, 40);
        - *
        - *   describe('A red circle on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let original = 'true';
        - *
        - *   // Convert the string to a Boolean value.
        - *   let converted = boolean(original);
        - *
        - *   // Style the circle based on the converted value.
        - *   if (converted === true) {
        - *     fill('blue');
        - *   } else {
        - *     fill('red');
        - *   }
        - *
        - *   // Draw the circle.
        - *   circle(50, 50, 40);
        - *
        - *   describe('A blue circle on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of values.
        - *   let original = [0, 'hi', 123, 'true'];
        - *
        - *   // Convert the array to a Boolean values.
        - *   let converted = boolean(original);
        - *
        - *   // Iterate over the array of converted Boolean values.
        - *   for (let i = 0; i < converted.length; i += 1) {
        - *
        - *     // Style the circle based on the converted value.
        - *     if (converted[i] === true) {
        - *       fill('blue');
        - *     } else {
        - *       fill('red');
        - *     }
        - *
        - *     // Calculate the x-coordinate.
        - *     let x = (i + 1) * 20;
        - *
        - *     // Draw the circle.
        - *     circle(x, 50, 15);
        - *   }
        - *
        - *   describe(
        - *     'A row of circles on a gray background. The two circles on the left are red and the two on the right are blue.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method boolean
        - * @param {Array} ns values to convert.
        - * @return {Boolean[]} converted Boolean values.
        - */
        -p5.prototype.boolean = function(n) {
        -  if (typeof n === 'number') {
        -    return n !== 0;
        -  } else if (typeof n === 'string') {
        -    return n.toLowerCase() === 'true';
        -  } else if (typeof n === 'boolean') {
        -    return n;
        -  } else if (n instanceof Array) {
        -    return n.map(p5.prototype.boolean);
        -  }
        -};
        +  /**
        +   * Converts a `Boolean` or `Number` to `String`.
        +   *
        +   * `str()` converts values to strings. See the
        +   * <a href="#/p5/String">String</a> reference page for guidance on using
        +   * template literals instead.
        +   *
        +   * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in
        +   * `str(false)` or `str(true)`, then the value will be returned as a string,
        +   * as in `'false'` or `'true'`. If `n` is a number, as in `str(123)`, then its
        +   * value will be returned as a string, as in `'123'`. If an array is passed,
        +   * as in `str([12.34, 56.78])`, then an array of strings will be returned.
        +   *
        +   * @method str
        +   * @param {String|Boolean|Number} n value to convert.
        +   * @return {String} converted string.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a Boolean variable.
        +   *   let original = false;
        +   *
        +   *   // Convert the Boolean to a string.
        +   *   let converted = str(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} : ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "false : false" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a number variable.
        +   *   let original = 123;
        +   *
        +   *   // Convert the number to a string.
        +   *   let converted = str(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} = ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "123 = 123" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of numbers.
        +   *   let original = [12, 34, 56];
        +   *
        +   *   // Convert the numbers to strings.
        +   *   let strings = str(original);
        +   *
        +   *   // Create an empty string variable.
        +   *   let final = '';
        +   *
        +   *   // Concatenate all the strings.
        +   *   for (let s of strings) {
        +   *     final += s;
        +   *   }
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the concatenated string.
        +   *   text(final, 50, 50);
        +   *
        +   *   describe('The text "123456" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.str = function(n) {
        +    if (n instanceof Array) {
        +      return n.map(fn.str);
        +    } else {
        +      return String(n);
        +    }
        +  };
         
        -/**
        - * Converts a `Boolean`, `String`, or `Number` to its byte value.
        - *
        - * `byte()` converts a value to an integer (whole number) between -128 and
        - * 127. Values greater than 127 wrap around while negative values are
        - * unchanged. For example, 128 becomes -128 and -129 remains the same.
        - *
        - * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in
        - * `byte(false)` or `byte(true)`, the number 0 (`false`) or 1 (`true`) will be
        - * returned. If `n` is a string or number, as in `byte('256')` or `byte(256)`,
        - * then the byte value will be returned. Decimal values are ignored. If an
        - * array is passed, as in `byte([true, 123, '456'])`, then an array of byte
        - * values will be returned.
        - *
        - * Note: If a value can't be converted to a number, as in `byte('giraffe')`,
        - * then the value `NaN` (not a number) will be returned.
        - *
        - * @method byte
        - * @param {String|Boolean|Number} n value to convert.
        - * @return {Number} converted byte value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a Boolean variable.
        - *   let original = true;
        - *
        - *   // Convert the Boolean to its byte value.
        - *   let converted = byte(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} : ${converted}`, 50, 50);
        - *
        - *   describe('The text "true : 1" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let original = '256';
        - *
        - *   // Convert the string to its byte value.
        - *   let converted = byte(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} : ${converted}`, 50, 50);
        - *
        - *   describe('The text "256 : 0" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a number variable.
        - *   let original = 256;
        - *
        - *   // Convert the number to its byte value.
        - *   let converted = byte(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} : ${converted}`, 50, 50);
        - *
        - *   describe('The text "256 : 0" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of values.
        - *   let original = [false, '64', 383];
        - *
        - *   // Convert the array elements to their byte values.
        - *   let converted = byte(original);
        - *
        - *   // Iterate over the converted array elements.
        - *   for (let i = 0; i < converted.length; i += 1) {
        - *
        - *     // Style the circle.
        - *     fill(converted[i]);
        - *
        - *     // Calculate the x-coordinate.
        - *     let x = (i + 1) * 25;
        - *
        - *     // Draw the circle.
        - *     circle(x, 50, 20);
        - *   }
        - *
        - *   describe(
        - *     'Three gray circles on a gray background. The circles get lighter from left to right.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method byte
        - * @param {Array} ns values to convert.
        - * @return {Number[]} converted byte values.
        - */
        -p5.prototype.byte = function(n) {
        -  const nn = p5.prototype.int(n, 10);
        -  if (typeof nn === 'number') {
        -    return (nn + 128) % 256 - 128;
        -  } else if (nn instanceof Array) {
        -    return nn.map(p5.prototype.byte);
        -  }
        -};
        +  /**
        +   * Converts a `String` or `Number` to a `Boolean`.
        +   *
        +   * `boolean()` converts values to `true` or `false`.
        +   *
        +   * The parameter, `n`, is the value to convert. If `n` is a string, then
        +   * `boolean('true')` will return `true` and every other string value will
        +   * return `false`. If `n` is a number, then `boolean(0)` will return `false`
        +   * and every other numeric value will return `true`. If an array is passed, as
        +   * `in boolean([0, 1, 'true', 'blue'])`, then an array of Boolean values will
        +   * be returned.
        +   *
        +   * @method boolean
        +   * @param {String|Boolean|Number} n value to convert.
        +   * @return {Boolean} converted Boolean value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a number variable.
        +   *   let original = 0;
        +   *
        +   *   // Convert the number to a Boolean value.
        +   *   let converted = boolean(original);
        +   *
        +   *   // Style the circle based on the converted value.
        +   *   if (converted === true) {
        +   *     fill('blue');
        +   *   } else {
        +   *     fill('red');
        +   *   }
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, 40);
        +   *
        +   *   describe('A red circle on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let original = 'true';
        +   *
        +   *   // Convert the string to a Boolean value.
        +   *   let converted = boolean(original);
        +   *
        +   *   // Style the circle based on the converted value.
        +   *   if (converted === true) {
        +   *     fill('blue');
        +   *   } else {
        +   *     fill('red');
        +   *   }
        +   *
        +   *   // Draw the circle.
        +   *   circle(50, 50, 40);
        +   *
        +   *   describe('A blue circle on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of values.
        +   *   let original = [0, 'hi', 123, 'true'];
        +   *
        +   *   // Convert the array to a Boolean values.
        +   *   let converted = boolean(original);
        +   *
        +   *   // Iterate over the array of converted Boolean values.
        +   *   for (let i = 0; i < converted.length; i += 1) {
        +   *
        +   *     // Style the circle based on the converted value.
        +   *     if (converted[i] === true) {
        +   *       fill('blue');
        +   *     } else {
        +   *       fill('red');
        +   *     }
        +   *
        +   *     // Calculate the x-coordinate.
        +   *     let x = (i + 1) * 20;
        +   *
        +   *     // Draw the circle.
        +   *     circle(x, 50, 15);
        +   *   }
        +   *
        +   *   describe(
        +   *     'A row of circles on a gray background. The two circles on the left are red and the two on the right are blue.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method boolean
        +   * @param {Array} ns values to convert.
        +   * @return {Boolean[]} converted Boolean values.
        +   */
        +  fn.boolean = function(n) {
        +    if (typeof n === 'number') {
        +      return n !== 0;
        +    } else if (typeof n === 'string') {
        +      return n.toLowerCase() === 'true';
        +    } else if (typeof n === 'boolean') {
        +      return n;
        +    } else if (n instanceof Array) {
        +      return n.map(fn.boolean);
        +    }
        +  };
         
        -/**
        - * Converts a `Number` or `String` to a single-character `String`.
        - *
        - * `char()` converts numbers to their single-character string representations.
        - *
        - * The parameter, `n`, is the value to convert. If a number is passed, as in
        - * `char(65)`, the corresponding single-character string is returned. If a
        - * string is passed, as in `char('65')`, the string is converted to an integer
        - * (whole number) and the corresponding single-character string is returned.
        - * If an array is passed, as in `char([65, 66, 67])`, an array of
        - * single-character strings is returned.
        - *
        - * See <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode" target="_blank">MDN</a>
        - * for more information about conversions.
        - *
        - * @method char
        - * @param {String|Number} n value to convert.
        - * @return {String} converted single-character string.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a number variable.
        - *   let original = 65;
        - *
        - *   // Convert the number to a char.
        - *   let converted = char(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} : ${converted}`, 50, 50);
        - *
        - *   describe('The text "65 : A" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let original = '65';
        - *
        - *   // Convert the string to a char.
        - *   let converted = char(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} : ${converted}`, 50, 50);
        - *
        - *   describe('The text "65 : A" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of numbers.
        - *   let original = ['65', 66, '67'];
        - *
        - *   // Convert the string to a char.
        - *   let converted = char(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Iterate over elements of the converted array.
        - *   for (let i = 0; i < converted.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 25;
        - *
        - *     // Display the original and converted values.
        - *     text(`${original[i]} : ${converted[i]}`, 50, y);
        - *   }
        - *
        - *   describe(
        - *     'The text "65 : A", "66 : B", and "67 : C" written on three separate lines. The text is in black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method char
        - * @param {Array} ns values to convert.
        - * @return {String[]} converted single-character strings.
        - */
        -p5.prototype.char = function(n) {
        -  if (typeof n === 'number' && !isNaN(n)) {
        -    return String.fromCharCode(n);
        -  } else if (n instanceof Array) {
        -    return n.map(p5.prototype.char);
        -  } else if (typeof n === 'string') {
        -    return p5.prototype.char(parseInt(n, 10));
        -  }
        -};
        +  /**
        +   * Converts a `Boolean`, `String`, or `Number` to its byte value.
        +   *
        +   * `byte()` converts a value to an integer (whole number) between -128 and
        +   * 127. Values greater than 127 wrap around while negative values are
        +   * unchanged. For example, 128 becomes -128 and -129 remains the same.
        +   *
        +   * The parameter, `n`, is the value to convert. If `n` is a Boolean, as in
        +   * `byte(false)` or `byte(true)`, the number 0 (`false`) or 1 (`true`) will be
        +   * returned. If `n` is a string or number, as in `byte('256')` or `byte(256)`,
        +   * then the byte value will be returned. Decimal values are ignored. If an
        +   * array is passed, as in `byte([true, 123, '456'])`, then an array of byte
        +   * values will be returned.
        +   *
        +   * Note: If a value can't be converted to a number, as in `byte('giraffe')`,
        +   * then the value `NaN` (not a number) will be returned.
        +   *
        +   * @method byte
        +   * @param {String|Boolean|Number} n value to convert.
        +   * @return {Number} converted byte value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a Boolean variable.
        +   *   let original = true;
        +   *
        +   *   // Convert the Boolean to its byte value.
        +   *   let converted = byte(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} : ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "true : 1" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let original = '256';
        +   *
        +   *   // Convert the string to its byte value.
        +   *   let converted = byte(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} : ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "256 : 0" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a number variable.
        +   *   let original = 256;
        +   *
        +   *   // Convert the number to its byte value.
        +   *   let converted = byte(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} : ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "256 : 0" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of values.
        +   *   let original = [false, '64', 383];
        +   *
        +   *   // Convert the array elements to their byte values.
        +   *   let converted = byte(original);
        +   *
        +   *   // Iterate over the converted array elements.
        +   *   for (let i = 0; i < converted.length; i += 1) {
        +   *
        +   *     // Style the circle.
        +   *     fill(converted[i]);
        +   *
        +   *     // Calculate the x-coordinate.
        +   *     let x = (i + 1) * 25;
        +   *
        +   *     // Draw the circle.
        +   *     circle(x, 50, 20);
        +   *   }
        +   *
        +   *   describe(
        +   *     'Three gray circles on a gray background. The circles get lighter from left to right.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method byte
        +   * @param {Array} ns values to convert.
        +   * @return {Number[]} converted byte values.
        +   */
        +  fn.byte = function(n) {
        +    const nn = fn.int(n, 10);
        +    if (typeof nn === 'number') {
        +      return (nn + 128) % 256 - 128;
        +    } else if (nn instanceof Array) {
        +      return nn.map(fn.byte);
        +    }
        +  };
         
        -/**
        - * Converts a single-character `String` to a `Number`.
        - *
        - * `unchar()` converts single-character strings to their corresponding
        - * integer (whole number).
        - *
        - * The parameter, `n`, is the character to convert. For example,
        - * `unchar('A')`, returns the number 65. If an array is passed, as in
        - * `unchar(['A', 'B', 'C'])`, an array of integers is returned.
        - *
        - * @method unchar
        - * @param {String} n value to convert.
        - * @return {Number} converted number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let original = 'A';
        - *
        - *   // Convert the string to a number.
        - *   let converted = unchar(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} : ${converted}`, 50, 50);
        - *
        - *   describe('The text "A : 65" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of characters.
        - *   let original = ['A', 'B', 'C'];
        - *
        - *   // Convert the string to a number.
        - *   let converted = unchar(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Iterate over elements of the converted array.
        - *   for (let i = 0; i < converted.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 25;
        - *
        - *     // Display the original and converted values.
        - *     text(`${original[i]} : ${converted[i]}`, 50, y);
        - *   }
        - *
        - *   describe(
        - *     'The text "A : 65", "B : 66", and "C :67" written on three separate lines. The text is in black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method unchar
        - * @param {String[]} ns values to convert.
        - * @return {Number[]} converted numbers.
        - */
        -p5.prototype.unchar = function(n) {
        -  if (typeof n === 'string' && n.length === 1) {
        -    return n.charCodeAt(0);
        -  } else if (n instanceof Array) {
        -    return n.map(p5.prototype.unchar);
        -  }
        -};
        +  /**
        +   * Converts a `Number` or `String` to a single-character `String`.
        +   *
        +   * `char()` converts numbers to their single-character string representations.
        +   *
        +   * The parameter, `n`, is the value to convert. If a number is passed, as in
        +   * `char(65)`, the corresponding single-character string is returned. If a
        +   * string is passed, as in `char('65')`, the string is converted to an integer
        +   * (whole number) and the corresponding single-character string is returned.
        +   * If an array is passed, as in `char([65, 66, 67])`, an array of
        +   * single-character strings is returned.
        +   *
        +   * See <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode" target="_blank">MDN</a>
        +   * for more information about conversions.
        +   *
        +   * @method char
        +   * @param {String|Number} n value to convert.
        +   * @return {String} converted single-character string.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a number variable.
        +   *   let original = 65;
        +   *
        +   *   // Convert the number to a char.
        +   *   let converted = char(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} : ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "65 : A" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let original = '65';
        +   *
        +   *   // Convert the string to a char.
        +   *   let converted = char(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} : ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "65 : A" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of numbers.
        +   *   let original = ['65', 66, '67'];
        +   *
        +   *   // Convert the string to a char.
        +   *   let converted = char(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Iterate over elements of the converted array.
        +   *   for (let i = 0; i < converted.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 25;
        +   *
        +   *     // Display the original and converted values.
        +   *     text(`${original[i]} : ${converted[i]}`, 50, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The text "65 : A", "66 : B", and "67 : C" written on three separate lines. The text is in black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method char
        +   * @param {Array} ns values to convert.
        +   * @return {String[]} converted single-character strings.
        +   */
        +  fn.char = function(n) {
        +    if (typeof n === 'number' && !isNaN(n)) {
        +      return String.fromCharCode(n);
        +    } else if (n instanceof Array) {
        +      return n.map(fn.char);
        +    } else if (typeof n === 'string') {
        +      return fn.char(parseInt(n, 10));
        +    }
        +  };
         
        -/**
        - * Converts a `Number` to a `String` with its hexadecimal value.
        - *
        - * `hex()` converts a number to a string with its hexadecimal number value.
        - * Hexadecimal (hex) numbers are base-16, which means there are 16 unique
        - * digits. Hex extends the numbers 0–9 with the letters A–F. For example, the
        - * number `11` (eleven) in base-10 is written as the letter `B` in hex.
        - *
        - * The first parameter, `n`, is the number to convert. For example, `hex(20)`,
        - * returns the string `'00000014'`. If an array is passed, as in
        - * `hex([1, 10, 100])`, an array of hexadecimal strings is returned.
        - *
        - * The second parameter, `digits`, is optional. If a number is passed, as in
        - * `hex(20, 2)`, it sets the number of hexadecimal digits to display. For
        - * example, calling `hex(20, 2)` returns the string `'14'`.
        - *
        - * @method hex
        - * @param {Number} n value to convert.
        - * @param {Number} [digits] number of digits to include.
        - * @return {String} converted hexadecimal value.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a number variable.
        - *   let original = 20;
        - *
        - *   // Convert the number to a hex string.
        - *   let converted = hex(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(14);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} = ${converted}`, 50, 50);
        - *
        - *   describe('The text "20 = 00000014" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a number variable.
        - *   let original = 20;
        - *
        - *   // Convert the number to a hex string.
        - *   // Only display two hex digits.
        - *   let converted = hex(original, 2);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} = ${converted}`, 50, 50);
        - *
        - *   describe('The text "20 = 14" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of numbers.
        - *   let original = [1, 10, 100];
        - *
        - *   // Convert the numbers to hex strings.
        - *   // Only use two hex digits.
        - *   let converted = hex(original, 2);
        - *
        - *   // Style the text.
        - *   textAlign(RIGHT, CENTER);
        - *   textSize(16);
        - *
        - *   // Iterate over the converted values.
        - *   for (let i = 0; i < converted.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 25;
        - *
        - *     // Display the original and converted values.
        - *     text(`${ original[i]} = ${converted[i]}`, 75, y);
        - *   }
        - *
        - *   describe(
        - *     'The text "1 = 01", "10 = 0A", and "100 = 64" written on three separate lines. The text is in black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method hex
        - * @param {Number[]} ns values to convert.
        - * @param {Number} [digits]
        - * @return {String[]} converted hexadecimal values.
        - */
        -p5.prototype.hex = function(n, digits) {
        -  digits = digits === undefined || digits === null ? (digits = 8) : digits;
        -  if (n instanceof Array) {
        -    return n.map(n => p5.prototype.hex(n, digits));
        -  } else if (n === Infinity || n === -Infinity) {
        -    const c = n === Infinity ? 'F' : '0';
        -    return c.repeat(digits);
        -  } else if (typeof n === 'number') {
        -    if (n < 0) {
        -      n = 0xffffffff + n + 1;
        +  /**
        +   * Converts a single-character `String` to a `Number`.
        +   *
        +   * `unchar()` converts single-character strings to their corresponding
        +   * integer (whole number).
        +   *
        +   * The parameter, `n`, is the character to convert. For example,
        +   * `unchar('A')`, returns the number 65. If an array is passed, as in
        +   * `unchar(['A', 'B', 'C'])`, an array of integers is returned.
        +   *
        +   * @method unchar
        +   * @param {String} n value to convert.
        +   * @return {Number} converted number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let original = 'A';
        +   *
        +   *   // Convert the string to a number.
        +   *   let converted = unchar(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} : ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "A : 65" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of characters.
        +   *   let original = ['A', 'B', 'C'];
        +   *
        +   *   // Convert the string to a number.
        +   *   let converted = unchar(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Iterate over elements of the converted array.
        +   *   for (let i = 0; i < converted.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 25;
        +   *
        +   *     // Display the original and converted values.
        +   *     text(`${original[i]} : ${converted[i]}`, 50, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The text "A : 65", "B : 66", and "C :67" written on three separate lines. The text is in black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method unchar
        +   * @param {String[]} ns values to convert.
        +   * @return {Number[]} converted numbers.
        +   */
        +  fn.unchar = function(n) {
        +    if (typeof n === 'string' && n.length === 1) {
        +      return n.charCodeAt(0);
        +    } else if (n instanceof Array) {
        +      return n.map(fn.unchar);
             }
        -    let hex = Number(n)
        -      .toString(16)
        -      .toUpperCase();
        -    while (hex.length < digits) {
        -      hex = `0${hex}`;
        +  };
        +
        +  /**
        +   * Converts a `Number` to a `String` with its hexadecimal value.
        +   *
        +   * `hex()` converts a number to a string with its hexadecimal number value.
        +   * Hexadecimal (hex) numbers are base-16, which means there are 16 unique
        +   * digits. Hex extends the numbers 0–9 with the letters A–F. For example, the
        +   * number `11` (eleven) in base-10 is written as the letter `B` in hex.
        +   *
        +   * The first parameter, `n`, is the number to convert. For example, `hex(20)`,
        +   * returns the string `'00000014'`. If an array is passed, as in
        +   * `hex([1, 10, 100])`, an array of hexadecimal strings is returned.
        +   *
        +   * The second parameter, `digits`, is optional. If a number is passed, as in
        +   * `hex(20, 2)`, it sets the number of hexadecimal digits to display. For
        +   * example, calling `hex(20, 2)` returns the string `'14'`.
        +   *
        +   * @method hex
        +   * @param {Number} n value to convert.
        +   * @param {Number} [digits] number of digits to include.
        +   * @return {String} converted hexadecimal value.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a number variable.
        +   *   let original = 20;
        +   *
        +   *   // Convert the number to a hex string.
        +   *   let converted = hex(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(14);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} = ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "20 = 00000014" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a number variable.
        +   *   let original = 20;
        +   *
        +   *   // Convert the number to a hex string.
        +   *   // Only display two hex digits.
        +   *   let converted = hex(original, 2);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} = ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "20 = 14" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of numbers.
        +   *   let original = [1, 10, 100];
        +   *
        +   *   // Convert the numbers to hex strings.
        +   *   // Only use two hex digits.
        +   *   let converted = hex(original, 2);
        +   *
        +   *   // Style the text.
        +   *   textAlign(RIGHT, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Iterate over the converted values.
        +   *   for (let i = 0; i < converted.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 25;
        +   *
        +   *     // Display the original and converted values.
        +   *     text(`${ original[i]} = ${converted[i]}`, 75, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The text "1 = 01", "10 = 0A", and "100 = 64" written on three separate lines. The text is in black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method hex
        +   * @param {Number[]} ns values to convert.
        +   * @param {Number} [digits]
        +   * @return {String[]} converted hexadecimal values.
        +   */
        +  fn.hex = function(n, digits) {
        +    digits = digits === undefined || digits === null ? (digits = 8) : digits;
        +    if (n instanceof Array) {
        +      return n.map(n => fn.hex(n, digits));
        +    } else if (n === Infinity || n === -Infinity) {
        +      const c = n === Infinity ? 'F' : '0';
        +      return c.repeat(digits);
        +    } else if (typeof n === 'number') {
        +      if (n < 0) {
        +        n = 0xffffffff + n + 1;
        +      }
        +      let hex = Number(n)
        +        .toString(16)
        +        .toUpperCase();
        +      while (hex.length < digits) {
        +        hex = `0${hex}`;
        +      }
        +      if (hex.length >= digits) {
        +        hex = hex.substring(hex.length - digits, hex.length);
        +      }
        +      return hex;
             }
        -    if (hex.length >= digits) {
        -      hex = hex.substring(hex.length - digits, hex.length);
        +  };
        +
        +  /**
        +   * Converts a `String` with a hexadecimal value to a  `Number`.
        +   *
        +   * `unhex()` converts a string with its hexadecimal number value to a number.
        +   * Hexadecimal (hex) numbers are base-16, which means there are 16 unique
        +   * digits. Hex extends the numbers 0–9 with the letters A–F. For example, the
        +   * number `11` (eleven) in base-10 is written as the letter `B` in hex.
        +   *
        +   * The first parameter, `n`, is the hex string to convert. For example,
        +   * `unhex('FF')`, returns the number 255. If an array is passed, as in
        +   * `unhex(['00', '80', 'FF'])`, an array of numbers is returned.
        +   *
        +   * @method unhex
        +   * @param {String} n value to convert.
        +   * @return {Number} converted number.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a a hex string variable
        +   *   let original = 'FF';
        +   *
        +   *   // Convert the hex string to a number.
        +   *   let converted = unhex(original);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the original and converted values.
        +   *   text(`${original} = ${converted}`, 50, 50);
        +   *
        +   *   describe('The text "FF = 255" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of numbers.
        +   *   let original = ['00', '80', 'FF'];
        +   *
        +   *   // Convert the numbers to hex strings.
        +   *   // Only use two hex digits.
        +   *   let converted = unhex(original, 2);
        +   *
        +   *   // Style the text.
        +   *   textAlign(RIGHT, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Iterate over the converted values.
        +   *   for (let i = 0; i < converted.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 25;
        +   *
        +   *     // Display the original and converted values.
        +   *     text(`${ original[i]} = ${converted[i]}`, 80, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The text "00 = 0", "80 = 128", and "FF = 255" written on three separate lines. The text is in black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method unhex
        +   * @param {String[]} ns values to convert.
        +   * @return {Number[]} converted numbers.
        +   */
        +  fn.unhex = function(n) {
        +    if (n instanceof Array) {
        +      return n.map(fn.unhex);
        +    } else {
        +      return parseInt(`0x${n}`, 16);
             }
        -    return hex;
        -  }
        -};
        +  };
        +}
         
        -/**
        - * Converts a `String` with a hexadecimal value to a  `Number`.
        - *
        - * `unhex()` converts a string with its hexadecimal number value to a number.
        - * Hexadecimal (hex) numbers are base-16, which means there are 16 unique
        - * digits. Hex extends the numbers 0–9 with the letters A–F. For example, the
        - * number `11` (eleven) in base-10 is written as the letter `B` in hex.
        - *
        - * The first parameter, `n`, is the hex string to convert. For example,
        - * `unhex('FF')`, returns the number 255. If an array is passed, as in
        - * `unhex(['00', '80', 'FF'])`, an array of numbers is returned.
        - *
        - * @method unhex
        - * @param {String} n value to convert.
        - * @return {Number} converted number.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a a hex string variable
        - *   let original = 'FF';
        - *
        - *   // Convert the hex string to a number.
        - *   let converted = unhex(original);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the original and converted values.
        - *   text(`${original} = ${converted}`, 50, 50);
        - *
        - *   describe('The text "FF = 255" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of numbers.
        - *   let original = ['00', '80', 'FF'];
        - *
        - *   // Convert the numbers to hex strings.
        - *   // Only use two hex digits.
        - *   let converted = unhex(original, 2);
        - *
        - *   // Style the text.
        - *   textAlign(RIGHT, CENTER);
        - *   textSize(16);
        - *
        - *   // Iterate over the converted values.
        - *   for (let i = 0; i < converted.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 25;
        - *
        - *     // Display the original and converted values.
        - *     text(`${ original[i]} = ${converted[i]}`, 80, y);
        - *   }
        - *
        - *   describe(
        - *     'The text "00 = 0", "80 = 128", and "FF = 255" written on three separate lines. The text is in black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method unhex
        - * @param {String[]} ns values to convert.
        - * @return {Number[]} converted numbers.
        - */
        -p5.prototype.unhex = function(n) {
        -  if (n instanceof Array) {
        -    return n.map(p5.prototype.unhex);
        -  } else {
        -    return parseInt(`0x${n}`, 16);
        -  }
        -};
        +export default conversion;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  conversion(p5, p5.prototype);
        +}
        diff --git a/src/utilities/index.js b/src/utilities/index.js
        new file mode 100644
        index 0000000000..b05893fb44
        --- /dev/null
        +++ b/src/utilities/index.js
        @@ -0,0 +1,11 @@
        +import arrayFunctions from './array_functions.js';
        +import conversion from './conversion.js';
        +import stringFunctions from './string_functions.js';
        +import timeDate from './time_date.js';
        +
        +export default function(p5){
        +  p5.registerAddon(arrayFunctions);
        +  p5.registerAddon(conversion);
        +  p5.registerAddon(stringFunctions);
        +  p5.registerAddon(timeDate);
        +}
        diff --git a/src/utilities/string_functions.js b/src/utilities/string_functions.js
        index b7be85a6b6..a837a85591 100644
        --- a/src/utilities/string_functions.js
        +++ b/src/utilities/string_functions.js
        @@ -5,994 +5,992 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        -import '../core/friendly_errors/validate_params';
        -import '../core/friendly_errors/file_errors';
        -import '../core/friendly_errors/fes_core';
        +function stringFunctions(p5, fn){
        +  /**
        +   * Combines an array of strings into one string.
        +   *
        +   * The first parameter, `list`, is the array of strings to join.
        +   *
        +   * The second parameter, `separator`, is the character(s) that should be used
        +   * to separate the combined strings. For example, calling
        +   * `join(myWords, ' : ')` would return a string of words each separated by a
        +   * colon and spaces.
        +   *
        +   * @method join
        +   * @param  {Array}  list array of strings to combine.
        +   * @param  {String} separator character(s) to place between strings when they're combined.
        +   * @return {String} combined string.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of strings.
        +   *   let myWords = ['one', 'two', 'three'];
        +   *
        +   *   // Create a combined string
        +   *   let combined = join(myWords, ' : ');
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *
        +   *   // Display the combined string.
        +   *   text(combined, 50, 50);
        +   *
        +   *   describe('The text "one : two : three" written in black on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.join = function(list, separator) {
        +    // p5._validateParameters('join', arguments);
        +    return list.join(separator);
        +  };
         
        -//return p5; //LM is this a mistake?
        +  /**
        +   * Applies a regular expression to a string and returns an array with the
        +   * first match.
        +   *
        +   * `match()` uses regular expressions (regex) to match patterns in text. For
        +   * example, the regex `abc` can be used to search a string for the exact
        +   * sequence of characters `abc`. See
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#tools" target="_blank">MDN</a>.
        +   * for more information about regexes.
        +   *
        +   * The first parameter, `str`, is the string to search.
        +   *
        +   * The second parameter, `regex`, is a string with the regular expression to
        +   * apply. For example, calling `match('Hello, p5*js!', '[a-z][0-9]')` would
        +   * return the array `['p5']`.
        +   *
        +   * Note: If no matches are found, `null` is returned.
        +   *
        +   * @method match
        +   * @param  {String} str string to search.
        +   * @param  {String} regexp regular expression to match.
        +   * @return {String[]} match if found.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let string = 'Hello, p5*js!';
        +   *
        +   *   // Match the characters that are lowercase
        +   *   // letters followed by digits.
        +   *   let matches = match(string, '[a-z][0-9]');
        +   *
        +   *   // Print the matches array to the console:
        +   *   // ['p5']
        +   *   print(matches);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the matches.
        +   *   text(matches, 50, 50);
        +   *
        +   *   describe('The text "p5" written in black on a gray canvas.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.match = function(str, reg) {
        +    // p5._validateParameters('match', arguments);
        +    return str.match(reg);
        +  };
         
        -/**
        - * Combines an array of strings into one string.
        - *
        - * The first parameter, `list`, is the array of strings to join.
        - *
        - * The second parameter, `separator`, is the character(s) that should be used
        - * to separate the combined strings. For example, calling
        - * `join(myWords, ' : ')` would return a string of words each separated by a
        - * colon and spaces.
        - *
        - * @method join
        - * @param  {Array}  list array of strings to combine.
        - * @param  {String} separator character(s) to place between strings when they're combined.
        - * @return {String} combined string.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of strings.
        - *   let myWords = ['one', 'two', 'three'];
        - *
        - *   // Create a combined string
        - *   let combined = join(myWords, ' : ');
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *
        - *   // Display the combined string.
        - *   text(combined, 50, 50);
        - *
        - *   describe('The text "one : two : three" written in black on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.join = function(list, separator) {
        -  p5._validateParameters('join', arguments);
        -  return list.join(separator);
        -};
        -
        -/**
        - * Applies a regular expression to a string and returns an array with the
        - * first match.
        - *
        - * `match()` uses regular expressions (regex) to match patterns in text. For
        - * example, the regex `abc` can be used to search a string for the exact
        - * sequence of characters `abc`. See
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#tools" target="_blank">MDN</a>.
        - * for more information about regexes.
        - *
        - * The first parameter, `str`, is the string to search.
        - *
        - * The second parameter, `regex`, is a string with the regular expression to
        - * apply. For example, calling `match('Hello, p5*js!', '[a-z][0-9]')` would
        - * return the array `['p5']`.
        - *
        - * Note: If no matches are found, `null` is returned.
        - *
        - * @method match
        - * @param  {String} str string to search.
        - * @param  {String} regexp regular expression to match.
        - * @return {String[]} match if found.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let string = 'Hello, p5*js!';
        - *
        - *   // Match the characters that are lowercase
        - *   // letters followed by digits.
        - *   let matches = match(string, '[a-z][0-9]');
        - *
        - *   // Print the matches array to the console:
        - *   // ['p5']
        - *   print(matches);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the matches.
        - *   text(matches, 50, 50);
        - *
        - *   describe('The text "p5" written in black on a gray canvas.');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.match = function(str, reg) {
        -  p5._validateParameters('match', arguments);
        -  return str.match(reg);
        -};
        -
        -/**
        - * Applies a regular expression to a string and returns an array of matches.
        - *
        - * `match()` uses regular expressions (regex) to match patterns in text. For
        - * example, the regex `abc` can be used to search a string for the exact
        - * sequence of characters `abc`. See
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#tools" target="_blank">MDN</a>.
        - * for more information about regexes. `matchAll()` is different from
        - * <a href="#/p5/match">match()</a> because it returns every match, not just
        - * the first.
        - *
        - * The first parameter, `str`, is the string to search.
        - *
        - * The second parameter, `regex`, is a string with the regular expression to
        - * apply. For example, calling
        - * `matchAll('p5*js is easier than abc123', '[a-z][0-9]')` would return the
        - * 2D array `[['p5'], ['c1']]`.
        - *
        - * Note: If no matches are found, an empty array `[]` is returned.
        - *
        - * @method matchAll
        - * @param  {String} str string to search.
        - * @param  {String} regexp regular expression to match.
        - * @return {String[]} matches found.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let string = 'p5*js is easier than abc123';
        - *
        - *   // Match the character sequences that are
        - *   // lowercase letters followed by digits.
        - *   let matches = matchAll(string, '[a-z][0-9]');
        - *
        - *   // Print the matches array to the console:
        - *   // [['p5'], ['c1']]
        - *   print(matches);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Iterate over the matches array.
        - *   for (let i = 0; i < matches.length; i += 1) {
        - *
        - *     // Calculate the y-coordainate.
        - *     let y = (i + 1) * 33;
        - *
        - *     // Display the match.
        - *     text(matches[i], 50, y);
        - *   }
        - *
        - *   describe(
        - *     'The text "p5" and "c1" written on separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.matchAll = function(str, reg) {
        -  p5._validateParameters('matchAll', arguments);
        -  const re = new RegExp(reg, 'g');
        -  let match = re.exec(str);
        -  const matches = [];
        -  while (match !== null) {
        -    matches.push(match);
        -    // matched text: match[0]
        -    // match start: match.index
        -    // capturing group n: match[n]
        -    match = re.exec(str);
        -  }
        -  return matches;
        -};
        +  /**
        +   * Applies a regular expression to a string and returns an array of matches.
        +   *
        +   * `match()` uses regular expressions (regex) to match patterns in text. For
        +   * example, the regex `abc` can be used to search a string for the exact
        +   * sequence of characters `abc`. See
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#tools" target="_blank">MDN</a>.
        +   * for more information about regexes. `matchAll()` is different from
        +   * <a href="#/p5/match">match()</a> because it returns every match, not just
        +   * the first.
        +   *
        +   * The first parameter, `str`, is the string to search.
        +   *
        +   * The second parameter, `regex`, is a string with the regular expression to
        +   * apply. For example, calling
        +   * `matchAll('p5*js is easier than abc123', '[a-z][0-9]')` would return the
        +   * 2D array `[['p5'], ['c1']]`.
        +   *
        +   * Note: If no matches are found, an empty array `[]` is returned.
        +   *
        +   * @method matchAll
        +   * @param  {String} str string to search.
        +   * @param  {String} regexp regular expression to match.
        +   * @return {String[]} matches found.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let string = 'p5*js is easier than abc123';
        +   *
        +   *   // Match the character sequences that are
        +   *   // lowercase letters followed by digits.
        +   *   let matches = matchAll(string, '[a-z][0-9]');
        +   *
        +   *   // Print the matches array to the console:
        +   *   // [['p5'], ['c1']]
        +   *   print(matches);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Iterate over the matches array.
        +   *   for (let i = 0; i < matches.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordainate.
        +   *     let y = (i + 1) * 33;
        +   *
        +   *     // Display the match.
        +   *     text(matches[i], 50, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The text "p5" and "c1" written on separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.matchAll = function(str, reg) {
        +    // p5._validateParameters('matchAll', arguments);
        +    const re = new RegExp(reg, 'g');
        +    let match = re.exec(str);
        +    const matches = [];
        +    while (match !== null) {
        +      matches.push(match);
        +      // matched text: match[0]
        +      // match start: match.index
        +      // capturing group n: match[n]
        +      match = re.exec(str);
        +    }
        +    return matches;
        +  };
         
        -/**
        - * Converts a `Number` into a `String` with a given number of digits.
        - *
        - * `nf()` converts numbers such as `123.45` into strings formatted with a set
        - * number of digits, as in `'123.4500'`.
        - *
        - * The first parameter, `num`, is the number to convert to a string. For
        - * example, calling `nf(123.45)` returns the string `'123.45'`. If an array of
        - * numbers is passed, as in `nf([123.45, 67.89])`, an array of formatted
        - * strings will be returned.
        - *
        - * The second parameter, `left`, is optional. If a number is passed, as in
        - * `nf(123.45, 4)`, it sets the minimum number of digits to include to the
        - * left of the decimal place. If `left` is larger than the number of digits in
        - * `num`, then unused digits will be set to 0. For example, calling
        - * `nf(123.45, 4)` returns the string `'0123.45'`.
        - *
        - * The third parameter, `right`, is also optional. If a number is passed, as
        - * in `nf(123.45, 4, 1)`, it sets the minimum number of digits to include to
        - * the right of the decimal place. If `right` is smaller than the number of
        - * decimal places in `num`, then `num` will be rounded to the given number of
        - * decimal places. For example, calling `nf(123.45, 4, 1)` returns the string
        - * `'0123.5'`. If right is larger than the number of decimal places in `num`,
        - * then unused decimal places will be set to 0. For example, calling
        - * `nf(123.45, 4, 3)` returns the string `'0123.450'`.
        - *
        - * When the number is negative, for example, calling `nf(-123.45, 5, 2)`
        - * returns the string `'-00123.45'`.
        - *
        - * @method nf
        - * @param {Number|String} num number to format.
        - * @param {Integer|String} [left] number of digits to include to the left of
        - *                                the decimal point.
        - * @param {Integer|String} [right] number of digits to include to the right
        - *                                 of the decimal point.
        - * @return {String} formatted string.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textSize(16);
        - *
        - *   // Create a number variable.
        - *   let number = 123.45;
        - *
        - *   // Display the number as a string.
        - *   let formatted = nf(number);
        - *   text(formatted, 20, 20);
        - *
        - *   let negative = nf(-number, 4, 2);
        - *   text(negative, 20, 40);
        - *
        - *   // Display the number with four digits
        - *   // to the left of the decimal.
        - *   let left = nf(number, 4);
        - *   text(left, 20, 60);
        - *
        - *   // Display the number with four digits
        - *   // to the left of the decimal and one
        - *   // to the right.
        - *   let right = nf(number, 4, 1);
        - *   text(right, 20, 80);
        - *
        - *   describe(
        - *     'The numbers "123.45", "-0123.45", "0123.45", and "0123.5" written on four separate lines. The text is in black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method nf
        - * @param {Number[]} nums numbers to format.
        - * @param {Integer|String} [left]
        - * @param {Integer|String} [right]
        - * @return {String[]} formatted strings.
        - */
        -p5.prototype.nf = function(nums, left, right) {
        -  p5._validateParameters('nf', arguments);
        -  if (nums instanceof Array) {
        -    return nums.map(x => doNf(x, left, right));
        -  } else {
        -    const typeOfFirst = Object.prototype.toString.call(nums);
        -    if (typeOfFirst === '[object Arguments]') {
        -      if (nums.length === 3) {
        -        return this.nf(nums[0], nums[1], nums[2]);
        -      } else if (nums.length === 2) {
        -        return this.nf(nums[0], nums[1]);
        +  /**
        +   * Converts a `Number` into a `String` with a given number of digits.
        +   *
        +   * `nf()` converts numbers such as `123.45` into strings formatted with a set
        +   * number of digits, as in `'123.4500'`.
        +   *
        +   * The first parameter, `num`, is the number to convert to a string. For
        +   * example, calling `nf(123.45)` returns the string `'123.45'`. If an array of
        +   * numbers is passed, as in `nf([123.45, 67.89])`, an array of formatted
        +   * strings will be returned.
        +   *
        +   * The second parameter, `left`, is optional. If a number is passed, as in
        +   * `nf(123.45, 4)`, it sets the minimum number of digits to include to the
        +   * left of the decimal place. If `left` is larger than the number of digits in
        +   * `num`, then unused digits will be set to 0. For example, calling
        +   * `nf(123.45, 4)` returns the string `'0123.45'`.
        +   *
        +   * The third parameter, `right`, is also optional. If a number is passed, as
        +   * in `nf(123.45, 4, 1)`, it sets the minimum number of digits to include to
        +   * the right of the decimal place. If `right` is smaller than the number of
        +   * decimal places in `num`, then `num` will be rounded to the given number of
        +   * decimal places. For example, calling `nf(123.45, 4, 1)` returns the string
        +   * `'0123.5'`. If right is larger than the number of decimal places in `num`,
        +   * then unused decimal places will be set to 0. For example, calling
        +   * `nf(123.45, 4, 3)` returns the string `'0123.450'`.
        +   *
        +   * When the number is negative, for example, calling `nf(-123.45, 5, 2)`
        +   * returns the string `'-00123.45'`.
        +   *
        +   * @method nf
        +   * @param {Number|String} num number to format.
        +   * @param {Integer|String} [left] number of digits to include to the left of
        +   *                                the decimal point.
        +   * @param {Integer|String} [right] number of digits to include to the right
        +   *                                 of the decimal point.
        +   * @return {String} formatted string.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Create a number variable.
        +   *   let number = 123.45;
        +   *
        +   *   // Display the number as a string.
        +   *   let formatted = nf(number);
        +   *   text(formatted, 20, 20);
        +   *
        +   *   let negative = nf(-number, 4, 2);
        +   *   text(negative, 20, 40);
        +   *
        +   *   // Display the number with four digits
        +   *   // to the left of the decimal.
        +   *   let left = nf(number, 4);
        +   *   text(left, 20, 60);
        +   *
        +   *   // Display the number with four digits
        +   *   // to the left of the decimal and one
        +   *   // to the right.
        +   *   let right = nf(number, 4, 1);
        +   *   text(right, 20, 80);
        +   *
        +   *   describe(
        +   *     'The numbers "123.45", "-0123.45", "0123.45", and "0123.5" written on four separate lines. The text is in black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method nf
        +   * @param {Number[]} nums numbers to format.
        +   * @param {Integer|String} [left]
        +   * @param {Integer|String} [right]
        +   * @return {String[]} formatted strings.
        +   */
        +  fn.nf = function(nums, left, right) {
        +    // p5._validateParameters('nf', arguments);
        +    if (nums instanceof Array) {
        +      return nums.map(x => doNf(x, left, right));
        +    } else {
        +      const typeOfFirst = Object.prototype.toString.call(nums);
        +      if (typeOfFirst === '[object Arguments]') {
        +        if (nums.length === 3) {
        +          return this.nf(nums[0], nums[1], nums[2]);
        +        } else if (nums.length === 2) {
        +          return this.nf(nums[0], nums[1]);
        +        } else {
        +          return this.nf(nums[0]);
        +        }
               } else {
        -        return this.nf(nums[0]);
        +        return doNf(nums, left, right);
               }
        -    } else {
        -      return doNf(nums, left, right);
             }
        -  }
        -};
        -
        -function doNf(num, left, right) {
        -  let isNegative = num < 0;
        -  num = Math.abs(num);
        -  let [leftPart, rightPart] = num.toString().split('.');
        +  };
         
        +  function doNf(num, left, right) {
        +    let isNegative = num < 0;
        +    num = Math.abs(num);
        +    let [leftPart, rightPart] = num.toString().split('.');
         
        -  if (typeof right === 'undefined') {
        -    leftPart = leftPart.padStart(left, '0');
        -    let result = rightPart ? leftPart + '.' + rightPart : leftPart;
        -    return isNegative ? '-' + result : result;
        -  } else {
        -    let roundedOff = num.toFixed(right);
        -    [leftPart, rightPart] = roundedOff.toString().split('.');
        -    leftPart = leftPart.padStart(left, '0');
        -    let result = typeof rightPart === 'undefined' ? leftPart : leftPart + '.' + rightPart;
        -    return isNegative ? '-' + result : result;
        +    if (typeof right === 'undefined') {
        +      leftPart = leftPart.padStart(left, '0');
        +      let result = rightPart ? leftPart + '.' + rightPart : leftPart;
        +      return isNegative ? '-' + result : result;
        +    } else {
        +      let roundedOff = num.toFixed(right);
        +      [leftPart, rightPart] = roundedOff.toString().split('.');
        +      leftPart = leftPart.padStart(left, '0');
        +      let result = typeof rightPart === 'undefined' ? leftPart : leftPart + '.' + rightPart;
        +      return isNegative ? '-' + result : result;
        +    }
           }
        -}
         
        -/**
        - * Converts a `Number` into a `String` with commas to mark units of 1,000.
        - *
        - * `nfc()` converts numbers such as 12345 into strings formatted with commas
        - * to mark the thousands place, as in `'12,345'`.
        - *
        - * The first parameter, `num`, is the number to convert to a string. For
        - * example, calling `nfc(12345)` returns the string `'12,345'`.
        - *
        - * The second parameter, `right`, is optional. If a number is passed, as in
        - * `nfc(12345, 1)`, it sets the minimum number of digits to include to the
        - * right of the decimal place. If `right` is smaller than the number of
        - * decimal places in `num`, then `num` will be rounded to the given number of
        - * decimal places. For example, calling `nfc(12345.67, 1)` returns the string
        - * `'12,345.7'`. If `right` is larger than the number of decimal places in
        - * `num`, then unused decimal places will be set to 0. For example, calling
        - * `nfc(12345.67, 3)` returns the string `'12,345.670'`.
        - *
        - * @method nfc
        - * @param  {Number|String} num number to format.
        - * @param  {Integer|String} [right] number of digits to include to the right
        - *                                  of the decimal point.
        - * @return {String} formatted string.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textSize(16);
        - *
        - *   // Create a number variable.
        - *   let number = 12345;
        - *
        - *   // Display the number as a string.
        - *   let commas = nfc(number);
        - *   text(commas, 15, 33);
        - *
        - *   // Display the number with four digits
        - *   // to the left of the decimal.
        - *   let decimals = nfc(number, 2);
        - *   text(decimals, 15, 67);
        - *
        - *   describe(
        - *     'The numbers "12,345" and "12,345.00" written on separate lines. The text is in black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of numbers.
        - *   let numbers = [12345, 6789];
        - *
        - *   // Convert the numbers to formatted strings.
        - *   let formatted = nfc(numbers);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(14);
        - *
        - *   // Iterate over the array.
        - *   for (let i = 0; i < formatted.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 33;
        - *
        - *     // Display the original and formatted numbers.
        - *     text(`${numbers[i]} : ${formatted[i]}`, 50, y);
        - *   }
        - *
        - *   describe(
        - *     'The text "12345 : 12,345" and "6789 : 6,789" written on two separate lines. The text is in black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method nfc
        - * @param  {Number[]} nums numbers to format.
        - * @param  {Integer|String} [right]
        - * @return {String[]} formatted strings.
        - */
        -p5.prototype.nfc = function(num, right) {
        -  p5._validateParameters('nfc', arguments);
        -  if (num instanceof Array) {
        -    return num.map(x => doNfc(x, right));
        -  } else {
        -    return doNfc(num, right);
        -  }
        -};
        -function doNfc(num, right) {
        -  num = num.toString();
        -  const dec = num.indexOf('.');
        -  let rem = dec !== -1 ? num.substring(dec) : '';
        -  let n = dec !== -1 ? num.substring(0, dec) : num;
        -  n = n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
        -  if (right === 0) {
        -    rem = '';
        -  } else if (typeof right !== 'undefined') {
        -    if (right > rem.length) {
        -      rem += dec === -1 ? '.' : '';
        -      const len = right - rem.length + 1;
        -      for (let i = 0; i < len; i++) {
        -        rem += '0';
        -      }
        +  /**
        +   * Converts a `Number` into a `String` with commas to mark units of 1,000.
        +   *
        +   * `nfc()` converts numbers such as 12345 into strings formatted with commas
        +   * to mark the thousands place, as in `'12,345'`.
        +   *
        +   * The first parameter, `num`, is the number to convert to a string. For
        +   * example, calling `nfc(12345)` returns the string `'12,345'`.
        +   *
        +   * The second parameter, `right`, is optional. If a number is passed, as in
        +   * `nfc(12345, 1)`, it sets the minimum number of digits to include to the
        +   * right of the decimal place. If `right` is smaller than the number of
        +   * decimal places in `num`, then `num` will be rounded to the given number of
        +   * decimal places. For example, calling `nfc(12345.67, 1)` returns the string
        +   * `'12,345.7'`. If `right` is larger than the number of decimal places in
        +   * `num`, then unused decimal places will be set to 0. For example, calling
        +   * `nfc(12345.67, 3)` returns the string `'12,345.670'`.
        +   *
        +   * @method nfc
        +   * @param  {Number|String} num number to format.
        +   * @param  {Integer|String} [right] number of digits to include to the right
        +   *                                  of the decimal point.
        +   * @return {String} formatted string.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Create a number variable.
        +   *   let number = 12345;
        +   *
        +   *   // Display the number as a string.
        +   *   let commas = nfc(number);
        +   *   text(commas, 15, 33);
        +   *
        +   *   // Display the number with four digits
        +   *   // to the left of the decimal.
        +   *   let decimals = nfc(number, 2);
        +   *   text(decimals, 15, 67);
        +   *
        +   *   describe(
        +   *     'The numbers "12,345" and "12,345.00" written on separate lines. The text is in black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of numbers.
        +   *   let numbers = [12345, 6789];
        +   *
        +   *   // Convert the numbers to formatted strings.
        +   *   let formatted = nfc(numbers);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(14);
        +   *
        +   *   // Iterate over the array.
        +   *   for (let i = 0; i < formatted.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 33;
        +   *
        +   *     // Display the original and formatted numbers.
        +   *     text(`${numbers[i]} : ${formatted[i]}`, 50, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The text "12345 : 12,345" and "6789 : 6,789" written on two separate lines. The text is in black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method nfc
        +   * @param  {Number[]} nums numbers to format.
        +   * @param  {Integer|String} [right]
        +   * @return {String[]} formatted strings.
        +   */
        +  fn.nfc = function(num, right) {
        +    // p5._validateParameters('nfc', arguments);
        +    if (num instanceof Array) {
        +      return num.map(x => doNfc(x, right));
             } else {
        -      rem = rem.substring(0, right + 1);
        +      return doNfc(num, right);
             }
        +  };
        +  function doNfc(num, right) {
        +    num = num.toString();
        +    const dec = num.indexOf('.');
        +    let rem = dec !== -1 ? num.substring(dec) : '';
        +    let n = dec !== -1 ? num.substring(0, dec) : num;
        +    n = n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
        +    if (right === 0) {
        +      rem = '';
        +    } else if (typeof right !== 'undefined') {
        +      if (right > rem.length) {
        +        rem += dec === -1 ? '.' : '';
        +        const len = right - rem.length + 1;
        +        for (let i = 0; i < len; i++) {
        +          rem += '0';
        +        }
        +      } else {
        +        rem = rem.substring(0, right + 1);
        +      }
        +    }
        +    return n + rem;
           }
        -  return n + rem;
        -}
         
        -/**
        - * Converts a `Number` into a `String` with a plus or minus sign.
        - *
        - * `nfp()` converts numbers such as 123 into strings formatted with a `+` or
        - * `-` symbol to mark whether they're positive or negative, as in `'+123'`.
        - *
        - * The first parameter, `num`, is the number to convert to a string. For
        - * example, calling `nfp(123.45)` returns the string `'+123.45'`. If an array
        - * of numbers is passed, as in `nfp([123.45, -6.78])`, an array of formatted
        - * strings will be returned.
        - *
        - * The second parameter, `left`, is optional. If a number is passed, as in
        - * `nfp(123.45, 4)`, it sets the minimum number of digits to include to the
        - * left of the decimal place. If `left` is larger than the number of digits in
        - * `num`, then unused digits will be set to 0. For example, calling
        - * `nfp(123.45, 4)` returns the string `'+0123.45'`.
        - *
        - * The third parameter, `right`, is also optional. If a number is passed, as
        - * in `nfp(123.45, 4, 1)`, it sets the minimum number of digits to include to
        - * the right of the decimal place. If `right` is smaller than the number of
        - * decimal places in `num`, then `num` will be rounded to the given number of
        - * decimal places.  For example, calling `nfp(123.45, 4, 1)` returns the
        - * string `'+0123.5'`. If `right` is larger than the number of decimal places
        - * in `num`, then unused decimal places will be set to 0.  For example,
        - * calling `nfp(123.45, 4, 3)` returns the string `'+0123.450'`.
        - *
        - * @method nfp
        - * @param {Number} num number to format.
        - * @param {Integer} [left] number of digits to include to the left of the
        - *                         decimal point.
        - * @param {Integer} [right] number of digits to include to the right of the
        - *                          decimal point.
        - * @return {String} formatted string.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create number variables.
        - *   let positive = 123;
        - *   let negative = -123;
        - *
        - *   // Convert the positive number to a formatted string.
        - *   let p = nfp(positive);
        - *
        - *   // Convert the negative number to a formatted string
        - *   // with four digits to the left of the decimal
        - *   // and two digits to the right of the decimal.
        - *   let n = nfp(negative, 4, 2);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(14);
        - *
        - *   // Display the original and formatted numbers.
        - *   text(`${positive} : ${p}`, 50, 33);
        - *   text(`${negative} : ${n}`, 50, 67);
        - *
        - *   describe(
        - *     'The text "123 : +123" and "-123 : -123.00" written on separate lines. The text is in black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create number variables.
        - *   let numbers = [123, -4.56];
        - *
        - *   // Convert the numbers to formatted strings
        - *   // with four digits to the left of the decimal
        - *   // and one digit to the right of the decimal.
        - *   let formatted = nfp(numbers, 4, 1);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(14);
        - *
        - *   // Iterate over the array.
        - *   for (let i = 0; i < formatted.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 33;
        - *
        - *     // Display the original and formatted numbers.
        - *     text(`${numbers[i]} : ${formatted[i]}`, 50, y);
        - *   }
        - *
        - *   describe(
        - *     'The text "123 : +0123.0" and "-4.56 : 00-4.6" written on separate lines. The text is in black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method nfp
        - * @param {Number[]} nums numbers to format.
        - * @param {Integer} [left]
        - * @param {Integer} [right]
        - * @return {String[]} formatted strings.
        - */
        -p5.prototype.nfp = function(...args) {
        -  p5._validateParameters('nfp', args);
        -  const nfRes = p5.prototype.nf.apply(this, args);
        -  if (nfRes instanceof Array) {
        -    return nfRes.map(addNfp);
        -  } else {
        -    return addNfp(nfRes);
        +  /**
        +   * Converts a `Number` into a `String` with a plus or minus sign.
        +   *
        +   * `nfp()` converts numbers such as 123 into strings formatted with a `+` or
        +   * `-` symbol to mark whether they're positive or negative, as in `'+123'`.
        +   *
        +   * The first parameter, `num`, is the number to convert to a string. For
        +   * example, calling `nfp(123.45)` returns the string `'+123.45'`. If an array
        +   * of numbers is passed, as in `nfp([123.45, -6.78])`, an array of formatted
        +   * strings will be returned.
        +   *
        +   * The second parameter, `left`, is optional. If a number is passed, as in
        +   * `nfp(123.45, 4)`, it sets the minimum number of digits to include to the
        +   * left of the decimal place. If `left` is larger than the number of digits in
        +   * `num`, then unused digits will be set to 0. For example, calling
        +   * `nfp(123.45, 4)` returns the string `'+0123.45'`.
        +   *
        +   * The third parameter, `right`, is also optional. If a number is passed, as
        +   * in `nfp(123.45, 4, 1)`, it sets the minimum number of digits to include to
        +   * the right of the decimal place. If `right` is smaller than the number of
        +   * decimal places in `num`, then `num` will be rounded to the given number of
        +   * decimal places.  For example, calling `nfp(123.45, 4, 1)` returns the
        +   * string `'+0123.5'`. If `right` is larger than the number of decimal places
        +   * in `num`, then unused decimal places will be set to 0.  For example,
        +   * calling `nfp(123.45, 4, 3)` returns the string `'+0123.450'`.
        +   *
        +   * @method nfp
        +   * @param {Number} num number to format.
        +   * @param {Integer} [left] number of digits to include to the left of the
        +   *                         decimal point.
        +   * @param {Integer} [right] number of digits to include to the right of the
        +   *                          decimal point.
        +   * @return {String} formatted string.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create number variables.
        +   *   let positive = 123;
        +   *   let negative = -123;
        +   *
        +   *   // Convert the positive number to a formatted string.
        +   *   let p = nfp(positive);
        +   *
        +   *   // Convert the negative number to a formatted string
        +   *   // with four digits to the left of the decimal
        +   *   // and two digits to the right of the decimal.
        +   *   let n = nfp(negative, 4, 2);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(14);
        +   *
        +   *   // Display the original and formatted numbers.
        +   *   text(`${positive} : ${p}`, 50, 33);
        +   *   text(`${negative} : ${n}`, 50, 67);
        +   *
        +   *   describe(
        +   *     'The text "123 : +123" and "-123 : -123.00" written on separate lines. The text is in black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create number variables.
        +   *   let numbers = [123, -4.56];
        +   *
        +   *   // Convert the numbers to formatted strings
        +   *   // with four digits to the left of the decimal
        +   *   // and one digit to the right of the decimal.
        +   *   let formatted = nfp(numbers, 4, 1);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(14);
        +   *
        +   *   // Iterate over the array.
        +   *   for (let i = 0; i < formatted.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 33;
        +   *
        +   *     // Display the original and formatted numbers.
        +   *     text(`${numbers[i]} : ${formatted[i]}`, 50, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The text "123 : +0123.0" and "-4.56 : 00-4.6" written on separate lines. The text is in black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method nfp
        +   * @param {Number[]} nums numbers to format.
        +   * @param {Integer} [left]
        +   * @param {Integer} [right]
        +   * @return {String[]} formatted strings.
        +   */
        +  fn.nfp = function(...args) {
        +    // p5._validateParameters('nfp', args);
        +    const nfRes = fn.nf.apply(this, args);
        +    if (nfRes instanceof Array) {
        +      return nfRes.map(addNfp);
        +    } else {
        +      return addNfp(nfRes);
        +    }
        +  };
        +
        +  function addNfp(num) {
        +    return parseFloat(num) > 0 ? `+${num.toString()}` : num.toString();
           }
        -};
         
        -function addNfp(num) {
        -  return parseFloat(num) > 0 ? `+${num.toString()}` : num.toString();
        -}
        +  /**
        +   * Converts a positive `Number` into a `String` with an extra space in front.
        +   *
        +   * `nfs()` converts positive numbers such as 123.45 into strings formatted
        +   * with an extra space in front, as in ' 123.45'. Doing so can be helpful for
        +   * aligning positive and negative numbers.
        +   *
        +   * The first parameter, `num`, is the number to convert to a string. For
        +   * example, calling `nfs(123.45)` returns the string `' 123.45'`.
        +   *
        +   * The second parameter, `left`, is optional. If a number is passed, as in
        +   * `nfs(123.45, 4)`, it sets the minimum number of digits to include to the
        +   * left of the decimal place. If `left` is larger than the number of digits in
        +   * `num`, then unused digits will be set to 0. For example, calling
        +   * `nfs(123.45, 4)` returns the string `' 0123.45'`.
        +   *
        +   * The third parameter, `right`, is also optional. If a number is passed, as
        +   * in `nfs(123.45, 4, 1)`, it sets the minimum number of digits to include to
        +   * the right of the decimal place. If `right` is smaller than the number of
        +   * decimal places in `num`, then `num` will be rounded to the given number of
        +   * decimal places.  For example, calling `nfs(123.45, 4, 1)` returns the
        +   * string `' 0123.5'`. If `right` is larger than the number of decimal places
        +   * in `num`, then unused decimal places will be set to 0.  For example,
        +   * calling `nfs(123.45, 4, 3)` returns the string `' 0123.450'`.
        +   *
        +   * @method nfs
        +   * @param {Number} num number to format.
        +   * @param {Integer} [left] number of digits to include to the left of the
        +   *                         decimal point.
        +   * @param {Integer} [right] number of digits to include to the right of the
        +   *                          decimal point.
        +   * @return {String} formatted string.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create number variables.
        +   *   let positive = 123;
        +   *   let negative = -123;
        +   *
        +   *   // Convert the positive number to a formatted string.
        +   *   let formatted = nfs(positive);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(16);
        +   *
        +   *   // Display the negative number and the formatted positive number.
        +   *   text(negative, 50, 33);
        +   *   text(formatted, 50, 67);
        +   *
        +   *   describe(
        +   *     'The numbers -123 and 123 written on separate lines. The numbers align vertically. The text is in black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a number variable.
        +   *   let number = 123.45;
        +   *
        +   *   // Convert the positive number to a formatted string.
        +   *   // Use four digits to the left of the decimal and
        +   *   // one digit to the right.
        +   *   let formatted = nfs(number, 4, 1);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(16);
        +   *
        +   *   // Display a negative version of the number and
        +   *   // the formatted positive version.
        +   *   text('-0123.5', 50, 33);
        +   *   text(formatted, 50, 67);
        +   *
        +   *   describe(
        +   *     'The numbers "-0123.5" and "0123.5" written on separate lines. The numbers align vertically. The text is in black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method nfs
        +   * @param {Array} nums numbers to format.
        +   * @param {Integer} [left]
        +   * @param {Integer} [right]
        +   * @return {String[]} formatted strings.
        +   */
        +  fn.nfs = function(...args) {
        +    // p5._validateParameters('nfs', args);
        +    const nfRes = fn.nf.apply(this, args);
        +    if (nfRes instanceof Array) {
        +      return nfRes.map(addNfs);
        +    } else {
        +      return addNfs(nfRes);
        +    }
        +  };
         
        -/**
        - * Converts a positive `Number` into a `String` with an extra space in front.
        - *
        - * `nfs()` converts positive numbers such as 123.45 into strings formatted
        - * with an extra space in front, as in ' 123.45'. Doing so can be helpful for
        - * aligning positive and negative numbers.
        - *
        - * The first parameter, `num`, is the number to convert to a string. For
        - * example, calling `nfs(123.45)` returns the string `' 123.45'`.
        - *
        - * The second parameter, `left`, is optional. If a number is passed, as in
        - * `nfs(123.45, 4)`, it sets the minimum number of digits to include to the
        - * left of the decimal place. If `left` is larger than the number of digits in
        - * `num`, then unused digits will be set to 0. For example, calling
        - * `nfs(123.45, 4)` returns the string `' 0123.45'`.
        - *
        - * The third parameter, `right`, is also optional. If a number is passed, as
        - * in `nfs(123.45, 4, 1)`, it sets the minimum number of digits to include to
        - * the right of the decimal place. If `right` is smaller than the number of
        - * decimal places in `num`, then `num` will be rounded to the given number of
        - * decimal places.  For example, calling `nfs(123.45, 4, 1)` returns the
        - * string `' 0123.5'`. If `right` is larger than the number of decimal places
        - * in `num`, then unused decimal places will be set to 0.  For example,
        - * calling `nfs(123.45, 4, 3)` returns the string `' 0123.450'`.
        - *
        - * @method nfs
        - * @param {Number} num number to format.
        - * @param {Integer} [left] number of digits to include to the left of the
        - *                         decimal point.
        - * @param {Integer} [right] number of digits to include to the right of the
        - *                          decimal point.
        - * @return {String} formatted string.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create number variables.
        - *   let positive = 123;
        - *   let negative = -123;
        - *
        - *   // Convert the positive number to a formatted string.
        - *   let formatted = nfs(positive);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(16);
        - *
        - *   // Display the negative number and the formatted positive number.
        - *   text(negative, 50, 33);
        - *   text(formatted, 50, 67);
        - *
        - *   describe(
        - *     'The numbers -123 and 123 written on separate lines. The numbers align vertically. The text is in black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a number variable.
        - *   let number = 123.45;
        - *
        - *   // Convert the positive number to a formatted string.
        - *   // Use four digits to the left of the decimal and
        - *   // one digit to the right.
        - *   let formatted = nfs(number, 4, 1);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(16);
        - *
        - *   // Display a negative version of the number and
        - *   // the formatted positive version.
        - *   text('-0123.5', 50, 33);
        - *   text(formatted, 50, 67);
        - *
        - *   describe(
        - *     'The numbers "-0123.5" and "0123.5" written on separate lines. The numbers align vertically. The text is in black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method nfs
        - * @param {Array} nums numbers to format.
        - * @param {Integer} [left]
        - * @param {Integer} [right]
        - * @return {String[]} formatted strings.
        - */
        -p5.prototype.nfs = function(...args) {
        -  p5._validateParameters('nfs', args);
        -  const nfRes = p5.prototype.nf.apply(this, args);
        -  if (nfRes instanceof Array) {
        -    return nfRes.map(addNfs);
        -  } else {
        -    return addNfs(nfRes);
        +  function addNfs(num) {
        +    return parseFloat(num) >= 0 ? ` ${num.toString()}` : num.toString();
           }
        -};
         
        -function addNfs(num) {
        -  return parseFloat(num) >= 0 ? ` ${num.toString()}` : num.toString();
        -}
        +  /**
        +   * Splits a `String` into pieces and returns an array containing the pieces.
        +   *
        +   * The first parameter, `value`, is the string to split.
        +   *
        +   * The second parameter, `delim`, is the character(s) that should be used to
        +   * split the string. For example, calling
        +   * `split('rock...paper...scissors', '...')` would return the array
        +   * `['rock', 'paper', 'scissors']` because there are three periods `...`
        +   * between each word.
        +   *
        +   * @method split
        +   * @param  {String} value the String to be split
        +   * @param  {String} delim the String used to separate the data
        +   * @return {String[]}  Array of Strings
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let string = 'rock...paper...scissors';
        +   *
        +   *   // Split the string at each ...
        +   *   let words = split(string, '...');
        +   *
        +   *   // Print the array to the console:
        +   *   // ["rock", "paper", "scissors"]
        +   *   print(words);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(16);
        +   *
        +   *   // Iterate over the words array.
        +   *   for (let i = 0; i < words.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 25;
        +   *
        +   *     // Display the word.
        +   *     text(words[i], 50, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The words "rock", "paper", and "scissors" written on separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.split = function(str, delim) {
        +    // p5._validateParameters('split', arguments);
        +    return str.split(delim);
        +  };
         
        -/**
        - * Splits a `String` into pieces and returns an array containing the pieces.
        - *
        - * The first parameter, `value`, is the string to split.
        - *
        - * The second parameter, `delim`, is the character(s) that should be used to
        - * split the string. For example, calling
        - * `split('rock...paper...scissors', '...')` would return the array
        - * `['rock', 'paper', 'scissors']` because there are three periods `...`
        - * between each word.
        - *
        - * @method split
        - * @param  {String} value the String to be split
        - * @param  {String} delim the String used to separate the data
        - * @return {String[]}  Array of Strings
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let string = 'rock...paper...scissors';
        - *
        - *   // Split the string at each ...
        - *   let words = split(string, '...');
        - *
        - *   // Print the array to the console:
        - *   // ["rock", "paper", "scissors"]
        - *   print(words);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(16);
        - *
        - *   // Iterate over the words array.
        - *   for (let i = 0; i < words.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 25;
        - *
        - *     // Display the word.
        - *     text(words[i], 50, y);
        - *   }
        - *
        - *   describe(
        - *     'The words "rock", "paper", and "scissors" written on separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.split = function(str, delim) {
        -  p5._validateParameters('split', arguments);
        -  return str.split(delim);
        -};
        +  /**
        +   * Splits a `String` into pieces and returns an array containing the pieces.
        +   *
        +   * `splitTokens()` is an enhanced version of
        +   * <a href="#/p5/split">split()</a>. It can split a string when any characters
        +   * from a list are detected.
        +   *
        +   * The first parameter, `value`, is the string to split.
        +   *
        +   * The second parameter, `delim`, is optional. It sets the character(s) that
        +   * should be used to split the string. `delim` can be a single string, as in
        +   * `splitTokens('rock...paper...scissors...shoot', '...')`, or an array of
        +   * strings, as in
        +   * `splitTokens('rock;paper,scissors...shoot, [';', ',', '...'])`. By default,
        +   * if no `delim` characters are specified, then any whitespace character is
        +   * used to split. Whitespace characters include tab (`\t`), line feed (`\n`),
        +   * carriage return (`\r`), form feed (`\f`), and space.
        +   *
        +   * @method splitTokens
        +   * @param  {String} value string to split.
        +   * @param  {String} [delim] character(s) to use for splitting the string.
        +   * @return {String[]} separated strings.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let string = 'rock paper scissors shoot';
        +   *
        +   *   // Split the string at each space.
        +   *   let words = splitTokens(string);
        +   *
        +   *   // Print the array to the console.
        +   *   print(words);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
        +   *
        +   *   // Iterate over the words array.
        +   *   for (let i = 0; i < words.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 20;
        +   *
        +   *     // Display the word.
        +   *     text(words[i], 50, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let string = 'rock...paper...scissors...shoot';
        +   *
        +   *   // Split the string at each ...
        +   *   let words = splitTokens(string, '...');
        +   *
        +   *   // Print the array to the console.
        +   *   print(words);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
        +   *
        +   *   // Iterate over the words array.
        +   *   for (let i = 0; i < words.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 20;
        +   *
        +   *     // Display the word.
        +   *     text(words[i], 50, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='notest'>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let string = 'rock;paper,scissors...shoot';
        +   *
        +   *   // Split the string at each semicolon, comma, or ...
        +   *   let words = splitTokens(string, [';', ',', '...']);
        +   *
        +   *   // Print the array to the console.
        +   *   print(words);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(12);
        +   *
        +   *   // Iterate over the words array.
        +   *   for (let i = 0; i < words.length; i += 1) {
        +   *
        +   *     // Calculate the y-coordinate.
        +   *     let y = (i + 1) * 20;
        +   *
        +   *     // Display the word.
        +   *     text(words[i], 50, y);
        +   *   }
        +   *
        +   *   describe(
        +   *     'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.splitTokens = function(value, delims) {
        +    // p5._validateParameters('splitTokens', arguments);
        +    let d;
        +    if (typeof delims !== 'undefined') {
        +      let str = delims;
        +      const sqc = /\]/g.exec(str);
        +      let sqo = /\[/g.exec(str);
        +      if (sqo && sqc) {
        +        str = str.slice(0, sqc.index) + str.slice(sqc.index + 1);
        +        sqo = /\[/g.exec(str);
        +        str = str.slice(0, sqo.index) + str.slice(sqo.index + 1);
        +        d = new RegExp(`[\\[${str}\\]]`, 'g');
        +      } else if (sqc) {
        +        str = str.slice(0, sqc.index) + str.slice(sqc.index + 1);
        +        d = new RegExp(`[${str}\\]]`, 'g');
        +      } else if (sqo) {
        +        str = str.slice(0, sqo.index) + str.slice(sqo.index + 1);
        +        d = new RegExp(`[${str}\\[]`, 'g');
        +      } else {
        +        d = new RegExp(`[${str}]`, 'g');
        +      }
        +    } else {
        +      d = /\s/g;
        +    }
        +    return value.split(d).filter(n => n);
        +  };
         
        -/**
        - * Splits a `String` into pieces and returns an array containing the pieces.
        - *
        - * `splitTokens()` is an enhanced version of
        - * <a href="#/p5/split">split()</a>. It can split a string when any characters
        - * from a list are detected.
        - *
        - * The first parameter, `value`, is the string to split.
        - *
        - * The second parameter, `delim`, is optional. It sets the character(s) that
        - * should be used to split the string. `delim` can be a single string, as in
        - * `splitTokens('rock...paper...scissors...shoot', '...')`, or an array of
        - * strings, as in
        - * `splitTokens('rock;paper,scissors...shoot, [';', ',', '...'])`. By default,
        - * if no `delim` characters are specified, then any whitespace character is
        - * used to split. Whitespace characters include tab (`\t`), line feed (`\n`),
        - * carriage return (`\r`), form feed (`\f`), and space.
        - *
        - * @method splitTokens
        - * @param  {String} value string to split.
        - * @param  {String} [delim] character(s) to use for splitting the string.
        - * @return {String[]} separated strings.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let string = 'rock paper scissors shoot';
        - *
        - *   // Split the string at each space.
        - *   let words = splitTokens(string);
        - *
        - *   // Print the array to the console.
        - *   print(words);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Iterate over the words array.
        - *   for (let i = 0; i < words.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 20;
        - *
        - *     // Display the word.
        - *     text(words[i], 50, y);
        - *   }
        - *
        - *   describe(
        - *     'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let string = 'rock...paper...scissors...shoot';
        - *
        - *   // Split the string at each ...
        - *   let words = splitTokens(string, '...');
        - *
        - *   // Print the array to the console.
        - *   print(words);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Iterate over the words array.
        - *   for (let i = 0; i < words.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 20;
        - *
        - *     // Display the word.
        - *     text(words[i], 50, y);
        - *   }
        - *
        - *   describe(
        - *     'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='notest'>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let string = 'rock;paper,scissors...shoot';
        - *
        - *   // Split the string at each semicolon, comma, or ...
        - *   let words = splitTokens(string, [';', ',', '...']);
        - *
        - *   // Print the array to the console.
        - *   print(words);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(12);
        - *
        - *   // Iterate over the words array.
        - *   for (let i = 0; i < words.length; i += 1) {
        - *
        - *     // Calculate the y-coordinate.
        - *     let y = (i + 1) * 20;
        - *
        - *     // Display the word.
        - *     text(words[i], 50, y);
        - *   }
        - *
        - *   describe(
        - *     'The words "rock", "paper", "scissors", and "shoot" written on separate lines. The text is black on a gray background.'
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.splitTokens = function(value, delims) {
        -  p5._validateParameters('splitTokens', arguments);
        -  let d;
        -  if (typeof delims !== 'undefined') {
        -    let str = delims;
        -    const sqc = /\]/g.exec(str);
        -    let sqo = /\[/g.exec(str);
        -    if (sqo && sqc) {
        -      str = str.slice(0, sqc.index) + str.slice(sqc.index + 1);
        -      sqo = /\[/g.exec(str);
        -      str = str.slice(0, sqo.index) + str.slice(sqo.index + 1);
        -      d = new RegExp(`[\\[${str}\\]]`, 'g');
        -    } else if (sqc) {
        -      str = str.slice(0, sqc.index) + str.slice(sqc.index + 1);
        -      d = new RegExp(`[${str}\\]]`, 'g');
        -    } else if (sqo) {
        -      str = str.slice(0, sqo.index) + str.slice(sqo.index + 1);
        -      d = new RegExp(`[${str}\\[]`, 'g');
        +  /**
        +   * Removes whitespace from the start and end of a `String` without changing the middle.
        +   *
        +   * `trim()` trims
        +   * <a href="https://developer.mozilla.org/en-US/docs/Glossary/whitespace" target="_blank">whitespace characters</a>
        +   * such as spaces, carriage returns, tabs, Unicode "nbsp" character.
        +   *
        +   * The parameter, `str`, is the string to trim. If a single string is passed,
        +   * as in `trim('   pad   ')`, a single string is returned. If an array of
        +   * strings is passed, as in `trim(['    pad   ', '\n space \n'])`, an array of
        +   * strings is returned.
        +   *
        +   * @method trim
        +   * @param  {String} str string to trim.
        +   * @return {String} trimmed string.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a string variable.
        +   *   let string = '   p5*js   ';
        +   *
        +   *   // Trim the whitespace.
        +   *   let trimmed = trim(string);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textSize(16);
        +   *
        +   *   // Display the text.
        +   *   text(`Hello, ${trimmed}!`, 50, 50);
        +   *
        +   *   describe('The text "Hello, p5*js!" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create an array of strings.
        +   *   let strings = ['   wide  ', '\n  open  ', '\n spaces  '];
        +   *
        +   *   // Trim the whitespace.
        +   *   let trimmed = trim(strings);
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(10);
        +   *
        +   *   // Display the text.
        +   *   text(`${trimmed[0]} ${trimmed[1]} ${trimmed[2]}`, 50, 50);
        +   *
        +   *   describe('The text "wide open spaces" written in black on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method trim
        +   * @param  {String[]} strs strings to trim.
        +   * @return {String[]} trimmed strings.
        +   */
        +  fn.trim = function(str) {
        +    // p5._validateParameters('trim', arguments);
        +    if (str instanceof Array) {
        +      return str.map(this.trim);
             } else {
        -      d = new RegExp(`[${str}]`, 'g');
        +      return str.trim();
             }
        -  } else {
        -    d = /\s/g;
        -  }
        -  return value.split(d).filter(n => n);
        -};
        +  };
        +}
         
        -/**
        - * Removes whitespace from the start and end of a `String` without changing the middle.
        - *
        - * `trim()` trims
        - * <a href="https://developer.mozilla.org/en-US/docs/Glossary/whitespace" target="_blank">whitespace characters</a>
        - * such as spaces, carriage returns, tabs, Unicode "nbsp" character.
        - *
        - * The parameter, `str`, is the string to trim. If a single string is passed,
        - * as in `trim('   pad   ')`, a single string is returned. If an array of
        - * strings is passed, as in `trim(['    pad   ', '\n space \n'])`, an array of
        - * strings is returned.
        - *
        - * @method trim
        - * @param  {String} str string to trim.
        - * @return {String} trimmed string.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create a string variable.
        - *   let string = '   p5*js   ';
        - *
        - *   // Trim the whitespace.
        - *   let trimmed = trim(string);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textSize(16);
        - *
        - *   // Display the text.
        - *   text(`Hello, ${trimmed}!`, 50, 50);
        - *
        - *   describe('The text "Hello, p5*js!" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Create an array of strings.
        - *   let strings = ['   wide  ', '\n  open  ', '\n spaces  '];
        - *
        - *   // Trim the whitespace.
        - *   let trimmed = trim(strings);
        - *
        - *   // Style the text.
        - *   textAlign(CENTER, CENTER);
        - *   textFont('Courier New');
        - *   textSize(10);
        - *
        - *   // Display the text.
        - *   text(`${trimmed[0]} ${trimmed[1]} ${trimmed[2]}`, 50, 50);
        - *
        - *   describe('The text "wide open spaces" written in black on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method trim
        - * @param  {String[]} strs strings to trim.
        - * @return {String[]} trimmed strings.
        - */
        -p5.prototype.trim = function(str) {
        -  p5._validateParameters('trim', arguments);
        -  if (str instanceof Array) {
        -    return str.map(this.trim);
        -  } else {
        -    return str.trim();
        -  }
        -};
        +export default stringFunctions;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  stringFunctions(p5, p5.prototype);
        +}
        diff --git a/src/utilities/time_date.js b/src/utilities/time_date.js
        index 88d91b3371..6177b7093f 100644
        --- a/src/utilities/time_date.js
        +++ b/src/utilities/time_date.js
        @@ -5,341 +5,345 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        +function timeDate(p5, fn){
        +  /**
        +   * Returns the current day as a number from 1–31.
        +   *
        +   * @method day
        +   * @return {Integer} current day between 1 and 31.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the current day.
        +   *   let d = day();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textSize(12);
        +   *   textFont('Courier New');
        +   *
        +   *   // Display the day.
        +   *   text(`Current day: ${d}`, 20, 50, 60);
        +   *
        +   *   describe(`The text 'Current day: ${d}' written in black on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.day = function() {
        +    return new Date().getDate();
        +  };
         
        -/**
        - * Returns the current day as a number from 1–31.
        - *
        - * @method day
        - * @return {Integer} current day between 1 and 31.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the current day.
        - *   let d = day();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textSize(12);
        - *   textFont('Courier New');
        - *
        - *   // Display the day.
        - *   text(`Current day: ${d}`, 20, 50, 60);
        - *
        - *   describe(`The text 'Current day: ${d}' written in black on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.day = function() {
        -  return new Date().getDate();
        -};
        +  /**
        +   * Returns the current hour as a number from 0–23.
        +   *
        +   * @method hour
        +   * @return {Integer} current hour between 0 and 23.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the current hour.
        +   *   let h = hour();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textSize(12);
        +   *   textFont('Courier New');
        +   *
        +   *   // Display the hour.
        +   *   text(`Current hour: ${h}`, 20, 50, 60);
        +   *
        +   *   describe(`The text 'Current hour: ${h}' written in black on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.hour = function() {
        +    return new Date().getHours();
        +  };
         
        -/**
        - * Returns the current hour as a number from 0–23.
        - *
        - * @method hour
        - * @return {Integer} current hour between 0 and 23.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the current hour.
        - *   let h = hour();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textSize(12);
        - *   textFont('Courier New');
        - *
        - *   // Display the hour.
        - *   text(`Current hour: ${h}`, 20, 50, 60);
        - *
        - *   describe(`The text 'Current hour: ${h}' written in black on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.hour = function() {
        -  return new Date().getHours();
        -};
        +  /**
        +   * Returns the current minute as a number from 0–59.
        +   *
        +   * @method minute
        +   * @return {Integer} current minute between 0 and 59.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the current minute.
        +   *   let m = minute();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textSize(12);
        +   *   textFont('Courier New');
        +   *
        +   *   // Display the minute.
        +   *   text(`Current minute: ${m}`, 10, 50, 80);
        +   *
        +   *   describe(`The text 'Current minute: ${m}' written in black on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.minute = function() {
        +    return new Date().getMinutes();
        +  };
         
        -/**
        - * Returns the current minute as a number from 0–59.
        - *
        - * @method minute
        - * @return {Integer} current minute between 0 and 59.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the current minute.
        - *   let m = minute();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textSize(12);
        - *   textFont('Courier New');
        - *
        - *   // Display the minute.
        - *   text(`Current minute: ${m}`, 10, 50, 80);
        - *
        - *   describe(`The text 'Current minute: ${m}' written in black on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.minute = function() {
        -  return new Date().getMinutes();
        -};
        +  /**
        +   * Returns the number of milliseconds since a sketch started running.
        +   *
        +   * `millis()` keeps track of how long a sketch has been running in
        +   * milliseconds (thousandths of a second). This information is often
        +   * helpful for timing events and animations.
        +   *
        +   * If a sketch has a
        +   * <a href="#/p5/setup">setup()</a> function, then `millis()` begins tracking
        +   * time before the code in <a href="#/p5/setup">setup()</a> runs. If a
        +   * sketch includes a <a href="#/p5/preload">preload()</a> function, then
        +   * `millis()` begins tracking time as soon as the code in
        +   * <a href="#/p5/preload">preload()</a> starts running.
        +   *
        +   * @method millis
        +   * @return {Number} number of milliseconds since starting the sketch.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the number of milliseconds the sketch has run.
        +   *   let ms = millis();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textSize(10);
        +   *   textFont('Courier New');
        +   *
        +   *   // Display how long it took setup() to be called.
        +   *   text(`Startup time: ${round(ms, 2)} ms`, 5, 50, 90);
        +   *
        +   *   describe(
        +   *     `The text 'Startup time: ${round(ms, 2)} ms' written in black on a gray background.`
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('The text "Running time: S sec" written in black on a gray background. The number S increases as the sketch runs.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Get the number of seconds the sketch has run.
        +   *   let s = millis() / 1000;
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textSize(10);
        +   *   textFont('Courier New');
        +   *
        +   *   // Display how long the sketch has run.
        +   *   text(`Running time: ${nf(s, 1, 1)} sec`, 5, 50, 90);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   describe('A white circle oscillates left and right on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Get the number of seconds the sketch has run.
        +   *   let s = millis() / 1000;
        +   *
        +   *   // Calculate an x-coordinate.
        +   *   let x = 30 * sin(s) + 50;
        +   *
        +   *   // Draw the circle.
        +   *   circle(x, 50, 30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Load the GeoJSON.
        +   * function preload() {
        +   *   loadJSON('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the number of milliseconds the sketch has run.
        +   *   let ms = millis();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textFont('Courier New');
        +   *   textSize(11);
        +   *
        +   *   // Display how long it took to load the data.
        +   *   text(`It took ${round(ms, 2)} ms to load the data`, 5, 50, 100);
        +   *
        +   *   describe(
        +   *     `The text "It took ${round(ms, 2)} ms to load the data" written in black on a gray background.`
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.millis = function() {
        +    if (this._millisStart === -1) {
        +      // Sketch has not started
        +      return 0;
        +    } else {
        +      return window.performance.now() - this._millisStart;
        +    }
        +  };
         
        -/**
        - * Returns the number of milliseconds since a sketch started running.
        - *
        - * `millis()` keeps track of how long a sketch has been running in
        - * milliseconds (thousandths of a second). This information is often
        - * helpful for timing events and animations.
        - *
        - * If a sketch has a
        - * <a href="#/p5/setup">setup()</a> function, then `millis()` begins tracking
        - * time before the code in <a href="#/p5/setup">setup()</a> runs. If a
        - * sketch includes a <a href="#/p5/preload">preload()</a> function, then
        - * `millis()` begins tracking time as soon as the code in
        - * <a href="#/p5/preload">preload()</a> starts running.
        - *
        - * @method millis
        - * @return {Number} number of milliseconds since starting the sketch.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the number of milliseconds the sketch has run.
        - *   let ms = millis();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textSize(10);
        - *   textFont('Courier New');
        - *
        - *   // Display how long it took setup() to be called.
        - *   text(`Startup time: ${round(ms, 2)} ms`, 5, 50, 90);
        - *
        - *   describe(
        - *     `The text 'Startup time: ${round(ms, 2)} ms' written in black on a gray background.`
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('The text "Running time: S sec" written in black on a gray background. The number S increases as the sketch runs.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Get the number of seconds the sketch has run.
        - *   let s = millis() / 1000;
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textSize(10);
        - *   textFont('Courier New');
        - *
        - *   // Display how long the sketch has run.
        - *   text(`Running time: ${nf(s, 1, 1)} sec`, 5, 50, 90);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   describe('A white circle oscillates left and right on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Get the number of seconds the sketch has run.
        - *   let s = millis() / 1000;
        - *
        - *   // Calculate an x-coordinate.
        - *   let x = 30 * sin(s) + 50;
        - *
        - *   // Draw the circle.
        - *   circle(x, 50, 30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Load the GeoJSON.
        - * function preload() {
        - *   loadJSON('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the number of milliseconds the sketch has run.
        - *   let ms = millis();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textFont('Courier New');
        - *   textSize(11);
        - *
        - *   // Display how long it took to load the data.
        - *   text(`It took ${round(ms, 2)} ms to load the data`, 5, 50, 100);
        - *
        - *   describe(
        - *     `The text "It took ${round(ms, 2)} ms to load the data" written in black on a gray background.`
        - *   );
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.millis = function() {
        -  if (this._millisStart === -1) {
        -    // Sketch has not started
        -    return 0;
        -  } else {
        -    return window.performance.now() - this._millisStart;
        -  }
        -};
        +  /**
        +   * Returns the current month as a number from 1–12.
        +   *
        +   * @method month
        +   * @return {Integer} current month between 1 and 12.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the current month.
        +   *   let m = month();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textSize(12);
        +   *   textFont('Courier New');
        +   *
        +   *   // Display the month.
        +   *   text(`Current month: ${m}`, 10, 50, 80);
        +   *
        +   *   describe(`The text 'Current month: ${m}' written in black on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.month = function() {
        +    //January is 0!
        +    return new Date().getMonth() + 1;
        +  };
         
        -/**
        - * Returns the current month as a number from 1–12.
        - *
        - * @method month
        - * @return {Integer} current month between 1 and 12.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the current month.
        - *   let m = month();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textSize(12);
        - *   textFont('Courier New');
        - *
        - *   // Display the month.
        - *   text(`Current month: ${m}`, 10, 50, 80);
        - *
        - *   describe(`The text 'Current month: ${m}' written in black on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.month = function() {
        -  //January is 0!
        -  return new Date().getMonth() + 1;
        -};
        +  /**
        +   * Returns the current second as a number from 0–59.
        +   *
        +   * @method second
        +   * @return {Integer} current second between 0 and 59.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the current second.
        +   *   let s = second();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textSize(12);
        +   *   textFont('Courier New');
        +   *
        +   *   // Display the second.
        +   *   text(`Current second: ${s}`, 10, 50, 80);
        +   *
        +   *   describe(`The text 'Current second: ${s}' written in black on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.second = function() {
        +    return new Date().getSeconds();
        +  };
         
        -/**
        - * Returns the current second as a number from 0–59.
        - *
        - * @method second
        - * @return {Integer} current second between 0 and 59.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the current second.
        - *   let s = second();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textSize(12);
        - *   textFont('Courier New');
        - *
        - *   // Display the second.
        - *   text(`Current second: ${s}`, 10, 50, 80);
        - *
        - *   describe(`The text 'Current second: ${s}' written in black on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.second = function() {
        -  return new Date().getSeconds();
        -};
        +  /**
        +   * Returns the current year as a number such as 1999.
        +   *
        +   * @method year
        +   * @return {Integer} current year.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Get the current year.
        +   *   let y = year();
        +   *
        +   *   // Style the text.
        +   *   textAlign(LEFT, CENTER);
        +   *   textSize(12);
        +   *   textFont('Courier New');
        +   *
        +   *   // Display the year.
        +   *   text(`Current year: ${y}`, 10, 50, 80);
        +   *
        +   *   describe(`The text 'Current year: ${y}' written in black on a gray background.`);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.year = function() {
        +    return new Date().getFullYear();
        +  };
        +}
         
        -/**
        - * Returns the current year as a number such as 1999.
        - *
        - * @method year
        - * @return {Integer} current year.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100);
        - *
        - *   background(200);
        - *
        - *   // Get the current year.
        - *   let y = year();
        - *
        - *   // Style the text.
        - *   textAlign(LEFT, CENTER);
        - *   textSize(12);
        - *   textFont('Courier New');
        - *
        - *   // Display the year.
        - *   text(`Current year: ${y}`, 10, 50, 80);
        - *
        - *   describe(`The text 'Current year: ${y}' written in black on a gray background.`);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.year = function() {
        -  return new Date().getFullYear();
        -};
        +export default timeDate;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  timeDate(p5, p5.prototype);
        +}
        diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js
        index 920184d065..1c880d87d0 100644
        --- a/src/webgl/3d_primitives.js
        +++ b/src/webgl/3d_primitives.js
        @@ -6,3446 +6,2761 @@
          * @requires p5.Geometry
          */
         
        -import p5 from '../core/main';
        -import './p5.Geometry';
         import * as constants from '../core/constants';
        +import { RendererGL } from './p5.RendererGL';
        +import { Vector } from '../math/p5.Vector';
        +import { Geometry } from './p5.Geometry';
        +import { Matrix } from '../math/p5.Matrix';
         
        +function primitives3D(p5, fn){
         /**
        - * Begins adding shapes to a new
        - * <a href="#/p5.Geometry">p5.Geometry</a> object.
        + * Sets the stroke rendering mode to balance performance and visual features when drawing lines.
          *
        - * The `beginGeometry()` and <a href="#/p5/endGeometry">endGeometry()</a>
        - * functions help with creating complex 3D shapes from simpler ones such as
        - * <a href="#/p5/sphere">sphere()</a>. `beginGeometry()` begins adding shapes
        - * to a custom <a href="#/p5.Geometry">p5.Geometry</a> object and
        - * <a href="#/p5/endGeometry">endGeometry()</a> stops adding them.
        + * `strokeMode()` offers two modes:
          *
        - * `beginGeometry()` and <a href="#/p5/endGeometry">endGeometry()</a> can help
        - * to make sketches more performant. For example, if a complex 3D shape
        - * doesn’t change while a sketch runs, then it can be created with
        - * `beginGeometry()` and <a href="#/p5/endGeometry">endGeometry()</a>.
        - * Creating a <a href="#/p5.Geometry">p5.Geometry</a> object once and then
        - * drawing it will run faster than repeatedly drawing the individual pieces.
        + * - `SIMPLE`: Optimizes for speed by disabling caps, joins, and stroke color features.
        + *   Use this mode for faster line rendering when these visual details are unnecessary.
        + * - `FULL`: Enables caps, joins, and stroke color for lines.
        + *   This mode provides enhanced visuals but may reduce performance due to additional processing.
          *
        - * See <a href="#/p5/buildGeometry">buildGeometry()</a> for another way to
        - * build 3D shapes.
        + * Choose the mode that best suits your application's needs to either improve rendering speed or enhance visual quality.
          *
        - * Note: `beginGeometry()` can only be used in WebGL mode.
        - *
        - * @method beginGeometry
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Start building the p5.Geometry object.
        - *   beginGeometry();
        - *
        - *   // Add a cone.
        - *   cone();
        - *
        - *   // Stop building the p5.Geometry object.
        - *   shape = endGeometry();
        - *
        - *   describe('A white cone drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the p5.Geometry object.
        - *   noStroke();
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(shape);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the p5.Geometry object.
        - *   createArrow();
        - *
        - *   describe('A white arrow drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the p5.Geometry object.
        - *   noStroke();
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(shape);
        - * }
        - *
        - * function createArrow() {
        - *   // Start building the p5.Geometry object.
        - *   beginGeometry();
        - *
        - *   // Add shapes.
        - *   push();
        - *   rotateX(PI);
        - *   cone(10);
        - *   translate(0, -10, 0);
        - *   cylinder(3, 20);
        - *   pop();
        - *
        - *   // Stop building the p5.Geometry object.
        - *   shape = endGeometry();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let blueArrow;
        - * let redArrow;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the arrows.
        - *   redArrow = createArrow('red');
        - *   blueArrow = createArrow('blue');
        - *
        - *   describe('A red arrow and a blue arrow drawn on a gray background. The blue arrow rotates slowly.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the arrows.
        - *   noStroke();
        - *
        - *   // Draw the red arrow.
        - *   model(redArrow);
        - *
        - *   // Translate and rotate the coordinate system.
        - *   translate(30, 0, 0);
        - *   rotateZ(frameCount * 0.01);
        - *
        - *   // Draw the blue arrow.
        - *   model(blueArrow);
        - * }
        - *
        - * function createArrow(fillColor) {
        - *   // Start building the p5.Geometry object.
        - *   beginGeometry();
        - *
        - *   fill(fillColor);
        - *
        - *   // Add shapes to the p5.Geometry object.
        - *   push();
        - *   rotateX(PI);
        - *   cone(10);
        - *   translate(0, -10, 0);
        - *   cylinder(3, 20);
        - *   pop();
        - *
        - *   // Stop building the p5.Geometry object.
        - *   let shape = endGeometry();
        - *
        - *   return shape;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let button;
        - * let particles;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a button to reset the particle system.
        - *   button = createButton('Reset');
        - *
        - *   // Call resetModel() when the user presses the button.
        - *   button.mousePressed(resetModel);
        - *
        - *   // Add the original set of particles.
        - *   resetModel();
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the particles.
        - *   noStroke();
        - *
        - *   // Draw the particles.
        - *   model(particles);
        - * }
        - *
        - * function resetModel() {
        - *   // If the p5.Geometry object has already been created,
        - *   // free those resources.
        - *   if (particles) {
        - *     freeGeometry(particles);
        - *   }
        - *
        - *   // Create a new p5.Geometry object with random spheres.
        - *   particles = createParticles();
        - * }
        - *
        - * function createParticles() {
        - *   // Start building the p5.Geometry object.
        - *   beginGeometry();
        - *
        - *   // Add shapes.
        - *   for (let i = 0; i < 60; i += 1) {
        - *     // Calculate random coordinates.
        - *     let x = randomGaussian(0, 20);
        - *     let y = randomGaussian(0, 20);
        - *     let z = randomGaussian(0, 20);
        - *
        - *     push();
        - *     // Translate to the particle's coordinates.
        - *     translate(x, y, z);
        - *     // Draw the particle.
        - *     sphere(5);
        - *     pop();
        - *   }
        - *
        - *   // Stop building the p5.Geometry object.
        - *   let shape = endGeometry();
        - *
        - *   return shape;
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.beginGeometry = function() {
        -  return this._renderer.beginGeometry();
        -};
        -
        -/**
        - * Stops adding shapes to a new
        - * <a href="#/p5.Geometry">p5.Geometry</a> object and returns the object.
        - *
        - * The `beginGeometry()` and <a href="#/p5/endGeometry">endGeometry()</a>
        - * functions help with creating complex 3D shapes from simpler ones such as
        - * <a href="#/p5/sphere">sphere()</a>. `beginGeometry()` begins adding shapes
        - * to a custom <a href="#/p5.Geometry">p5.Geometry</a> object and
        - * <a href="#/p5/endGeometry">endGeometry()</a> stops adding them.
        - *
        - * `beginGeometry()` and <a href="#/p5/endGeometry">endGeometry()</a> can help
        - * to make sketches more performant. For example, if a complex 3D shape
        - * doesn’t change while a sketch runs, then it can be created with
        - * `beginGeometry()` and <a href="#/p5/endGeometry">endGeometry()</a>.
        - * Creating a <a href="#/p5.Geometry">p5.Geometry</a> object once and then
        - * drawing it will run faster than repeatedly drawing the individual pieces.
        - *
        - * See <a href="#/p5/buildGeometry">buildGeometry()</a> for another way to
        - * build 3D shapes.
        - *
        - * Note: `endGeometry()` can only be used in WebGL mode.
        - *
        - * @method endGeometry
        - * @returns {p5.Geometry} new 3D shape.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Start building the p5.Geometry object.
        - *   beginGeometry();
        - *
        - *   // Add a cone.
        - *   cone();
        - *
        - *   // Stop building the p5.Geometry object.
        - *   shape = endGeometry();
        - *
        - *   describe('A white cone drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the p5.Geometry object.
        - *   noStroke();
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(shape);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the p5.Geometry object.
        - *   createArrow();
        - *
        - *   describe('A white arrow drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the p5.Geometry object.
        - *   noStroke();
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(shape);
        - * }
        - *
        - * function createArrow() {
        - *   // Start building the p5.Geometry object.
        - *   beginGeometry();
        - *
        - *   // Add shapes.
        - *   push();
        - *   rotateX(PI);
        - *   cone(10);
        - *   translate(0, -10, 0);
        - *   cylinder(3, 20);
        - *   pop();
        - *
        - *   // Stop building the p5.Geometry object.
        - *   shape = endGeometry();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let blueArrow;
        - * let redArrow;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the arrows.
        - *   redArrow = createArrow('red');
        - *   blueArrow = createArrow('blue');
        - *
        - *   describe('A red arrow and a blue arrow drawn on a gray background. The blue arrow rotates slowly.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the arrows.
        - *   noStroke();
        - *
        - *   // Draw the red arrow.
        - *   model(redArrow);
        - *
        - *   // Translate and rotate the coordinate system.
        - *   translate(30, 0, 0);
        - *   rotateZ(frameCount * 0.01);
        - *
        - *   // Draw the blue arrow.
        - *   model(blueArrow);
        - * }
        - *
        - * function createArrow(fillColor) {
        - *   // Start building the p5.Geometry object.
        - *   beginGeometry();
        - *
        - *   fill(fillColor);
        - *
        - *   // Add shapes to the p5.Geometry object.
        - *   push();
        - *   rotateX(PI);
        - *   cone(10);
        - *   translate(0, -10, 0);
        - *   cylinder(3, 20);
        - *   pop();
        - *
        - *   // Stop building the p5.Geometry object.
        - *   let shape = endGeometry();
        - *
        - *   return shape;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let button;
        - * let particles;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a button to reset the particle system.
        - *   button = createButton('Reset');
        - *
        - *   // Call resetModel() when the user presses the button.
        - *   button.mousePressed(resetModel);
        - *
        - *   // Add the original set of particles.
        - *   resetModel();
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the particles.
        - *   noStroke();
        - *
        - *   // Draw the particles.
        - *   model(particles);
        - * }
        - *
        - * function resetModel() {
        - *   // If the p5.Geometry object has already been created,
        - *   // free those resources.
        - *   if (particles) {
        - *     freeGeometry(particles);
        - *   }
        - *
        - *   // Create a new p5.Geometry object with random spheres.
        - *   particles = createParticles();
        - * }
        - *
        - * function createParticles() {
        - *   // Start building the p5.Geometry object.
        - *   beginGeometry();
        - *
        - *   // Add shapes.
        - *   for (let i = 0; i < 60; i += 1) {
        - *     // Calculate random coordinates.
        - *     let x = randomGaussian(0, 20);
        - *     let y = randomGaussian(0, 20);
        - *     let z = randomGaussian(0, 20);
        - *
        - *     push();
        - *     // Translate to the particle's coordinates.
        - *     translate(x, y, z);
        - *     // Draw the particle.
        - *     sphere(5);
        - *     pop();
        - *   }
        - *
        - *   // Stop building the p5.Geometry object.
        - *   let shape = endGeometry();
        - *
        - *   return shape;
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.endGeometry = function() {
        -  return this._renderer.endGeometry();
        -};
        -
        -/**
        - * Creates a custom <a href="#/p5.Geometry">p5.Geometry</a> object from
        - * simpler 3D shapes.
        - *
        - * `buildGeometry()` helps with creating complex 3D shapes from simpler ones
        - * such as <a href="#/p5/sphere">sphere()</a>. It can help to make sketches
        - * more performant. For example, if a complex 3D shape doesn’t change while a
        - * sketch runs, then it can be created with `buildGeometry()`. Creating a
        - * <a href="#/p5.Geometry">p5.Geometry</a> object once and then drawing it
        - * will run faster than repeatedly drawing the individual pieces.
        - *
        - * The parameter, `callback`, is a function with the drawing instructions for
        - * the new <a href="#/p5.Geometry">p5.Geometry</a> object. It will be called
        - * once to create the new 3D shape.
        - *
        - * See <a href="#/p5/beginGeometry">beginGeometry()</a> and
        - * <a href="#/p5/endGeometry">endGeometry()</a> for another way to build 3D
        - * shapes.
        - *
        - * Note: `buildGeometry()` can only be used in WebGL mode.
        - *
        - * @method buildGeometry
        - * @param {Function} callback function that draws the shape.
        - * @returns {p5.Geometry} new 3D shape.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the p5.Geometry object.
        - *   shape = buildGeometry(createShape);
        - *
        - *   describe('A white cone drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the p5.Geometry object.
        - *   noStroke();
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(shape);
        - * }
        - *
        - * // Create p5.Geometry object from a single cone.
        - * function createShape() {
        - *   cone();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the arrow.
        - *   shape = buildGeometry(createArrow);
        - *
        - *   describe('A white arrow drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the arrow.
        - *   noStroke();
        - *
        - *   // Draw the arrow.
        - *   model(shape);
        - * }
        - *
        - * function createArrow() {
        - *   // Add shapes to the p5.Geometry object.
        - *   push();
        - *   rotateX(PI);
        - *   cone(10);
        - *   translate(0, -10, 0);
        - *   cylinder(3, 20);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the p5.Geometry object.
        - *   shape = buildGeometry(createArrow);
        - *
        - *   describe('Two white arrows drawn on a gray background. The arrow on the right rotates slowly.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the arrows.
        - *   noStroke();
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(shape);
        - *
        - *   // Translate and rotate the coordinate system.
        - *   translate(30, 0, 0);
        - *   rotateZ(frameCount * 0.01);
        - *
        - *   // Draw the p5.Geometry object again.
        - *   model(shape);
        - * }
        - *
        - * function createArrow() {
        - *   // Add shapes to the p5.Geometry object.
        - *   push();
        - *   rotateX(PI);
        - *   cone(10);
        - *   translate(0, -10, 0);
        - *   cylinder(3, 20);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let button;
        - * let particles;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a button to reset the particle system.
        - *   button = createButton('Reset');
        - *
        - *   // Call resetModel() when the user presses the button.
        - *   button.mousePressed(resetModel);
        - *
        - *   // Add the original set of particles.
        - *   resetModel();
        - *
        - *   describe('A set of white spheres on a gray background. The spheres are positioned randomly. Their positions reset when the user presses the Reset button.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the particles.
        - *   noStroke();
        - *
        - *   // Draw the particles.
        - *   model(particles);
        - * }
        - *
        - * function resetModel() {
        - *   // If the p5.Geometry object has already been created,
        - *   // free those resources.
        - *   if (particles) {
        - *     freeGeometry(particles);
        - *   }
        - *
        - *   // Create a new p5.Geometry object with random spheres.
        - *   particles = buildGeometry(createParticles);
        - * }
        - *
        - * function createParticles() {
        - *   for (let i = 0; i < 60; i += 1) {
        - *     // Calculate random coordinates.
        - *     let x = randomGaussian(0, 20);
        - *     let y = randomGaussian(0, 20);
        - *     let z = randomGaussian(0, 20);
        - *
        - *     push();
        - *     // Translate to the particle's coordinates.
        - *     translate(x, y, z);
        - *     // Draw the particle.
        - *     sphere(5);
        - *     pop();
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.buildGeometry = function(callback) {
        -  return this._renderer.buildGeometry(callback);
        -};
        -
        -/**
        - * Clears a <a href="#/p5.Geometry">p5.Geometry</a> object from the graphics
        - * processing unit (GPU) memory.
        - *
        - * <a href="#/p5.Geometry">p5.Geometry</a> objects can contain lots of data
        - * about their vertices, surface normals, colors, and so on. Complex 3D shapes
        - * can use lots of memory which is a limited resource in many GPUs. Calling
        - * `freeGeometry()` can improve performance by freeing a
        - * <a href="#/p5.Geometry">p5.Geometry</a> object’s resources from GPU memory.
        - * `freeGeometry()` works with <a href="#/p5.Geometry">p5.Geometry</a> objects
        - * created with <a href="#/p5/beginGeometry">beginGeometry()</a> and
        - * <a href="#/p5/endGeometry">endGeometry()</a>,
        - * <a href="#/p5/buildGeometry">buildGeometry()</a>, and
        - * <a href="#/p5/loadModel">loadModel()</a>.
        - *
        - * The parameter, `geometry`, is the <a href="#/p5.Geometry">p5.Geometry</a>
        - * object to be freed.
        - *
        - * Note: A <a href="#/p5.Geometry">p5.Geometry</a> object can still be drawn
        - * after its resources are cleared from GPU memory. It may take longer to draw
        - * the first time it’s redrawn.
        - *
        - * Note: `freeGeometry()` can only be used in WebGL mode.
        - *
        - * @method freeGeometry
        - * @param {p5.Geometry} geometry 3D shape whose resources should be freed.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Geometry object.
        - *   beginGeometry();
        - *   cone();
        - *   let shape = endGeometry();
        - *
        - *   // Draw the shape.
        - *   model(shape);
        - *
        - *   // Free the shape's resources.
        - *   freeGeometry(shape);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let button;
        - * let particles;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a button to reset the particle system.
        - *   button = createButton('Reset');
        - *
        - *   // Call resetModel() when the user presses the button.
        - *   button.mousePressed(resetModel);
        - *
        - *   // Add the original set of particles.
        - *   resetModel();
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the particles.
        - *   noStroke();
        - *
        - *   // Draw the particles.
        - *   model(particles);
        - * }
        - *
        - * function resetModel() {
        - *   // If the p5.Geometry object has already been created,
        - *   // free those resources.
        - *   if (particles) {
        - *     freeGeometry(particles);
        - *   }
        - *
        - *   // Create a new p5.Geometry object with random spheres.
        - *   particles = buildGeometry(createParticles);
        - * }
        - *
        - * function createParticles() {
        - *   for (let i = 0; i < 60; i += 1) {
        - *     // Calculate random coordinates.
        - *     let x = randomGaussian(0, 20);
        - *     let y = randomGaussian(0, 20);
        - *     let z = randomGaussian(0, 20);
        - *
        - *     push();
        - *     // Translate to the particle's coordinates.
        - *     translate(x, y, z);
        - *     // Draw the particle.
        - *     sphere(5);
        - *     pop();
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.freeGeometry = function(geometry) {
        -  this._renderer._freeBuffers(geometry.gid);
        -};
        -
        -/**
        - * Draws a plane.
        - *
        - * A plane is a four-sided, flat shape with every angle measuring 90˚. It’s
        - * similar to a rectangle and offers advanced drawing features in WebGL mode.
        - *
        - * The first parameter, `width`, is optional. If a `Number` is passed, as in
        - * `plane(20)`, it sets the plane’s width and height. By default, `width` is
        - * 50.
        - *
        - * The second parameter, `height`, is also optional. If a `Number` is passed,
        - * as in `plane(20, 30)`, it sets the plane’s height. By default, `height` is
        - * set to the plane’s `width`.
        - *
        - * The third parameter, `detailX`, is also optional. If a `Number` is passed,
        - * as in `plane(20, 30, 5)` it sets the number of triangle subdivisions to use
        - * along the x-axis. All 3D shapes are made by connecting triangles to form
        - * their surfaces. By default, `detailX` is 1.
        - *
        - * The fourth parameter, `detailY`, is also optional. If a `Number` is passed,
        - * as in `plane(20, 30, 5, 7)` it sets the number of triangle subdivisions to
        - * use along the y-axis. All 3D shapes are made by connecting triangles to
        - * form their surfaces. By default, `detailY` is 1.
        - *
        - * Note: `plane()` can only be used in WebGL mode.
        - *
        - * @method plane
        - * @param  {Number} [width]    width of the plane.
        - * @param  {Number} [height]   height of the plane.
        - * @param  {Integer} [detailX] number of triangle subdivisions along the x-axis.
        - * @param {Integer} [detailY]  number of triangle subdivisions along the y-axis.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white plane on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the plane.
        - *   plane();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white plane on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the plane.
        - *   // Set its width and height to 30.
        - *   plane(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white plane on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the plane.
        - *   // Set its width to 30 and height to 50.
        - *   plane(30, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.plane = function(
        -  width = 50,
        -  height = width,
        -  detailX = 1,
        -  detailY = 1
        -) {
        -  this._assert3d('plane');
        -  p5._validateParameters('plane', arguments);
        -
        -  const gId = `plane|${detailX}|${detailY}`;
        -
        -  if (!this._renderer.geometryInHash(gId)) {
        -    const _plane = function() {
        -      let u, v, p;
        -      for (let i = 0; i <= this.detailY; i++) {
        -        v = i / this.detailY;
        -        for (let j = 0; j <= this.detailX; j++) {
        -          u = j / this.detailX;
        -          p = new p5.Vector(u - 0.5, v - 0.5, 0);
        -          this.vertices.push(p);
        -          this.uvs.push(u, v);
        -        }
        -      }
        -    };
        -    const planeGeom = new p5.Geometry(detailX, detailY, _plane);
        -    planeGeom.computeFaces().computeNormals();
        -    if (detailX <= 1 && detailY <= 1) {
        -      planeGeom._makeTriangleEdges()._edgesToVertices();
        -    } else if (this._renderer._doStroke) {
        -      console.log(
        -        'Cannot draw stroke on plane objects with more' +
        -        ' than 1 detailX or 1 detailY'
        -      );
        -    }
        -    this._renderer.createBuffers(gId, planeGeom);
        -  }
        -
        -  this._renderer.drawBuffersScaled(gId, width, height, 1);
        -  return this;
        -};
        -
        -/**
        - * Draws a box (rectangular prism).
        - *
        - * A box is a 3D shape with six faces. Each face makes a 90˚ with four
        - * neighboring faces.
        - *
        - * The first parameter, `width`, is optional. If a `Number` is passed, as in
        - * `box(20)`, it sets the box’s width and height. By default, `width` is 50.
        - *
        - * The second parameter, `height`, is also optional. If a `Number` is passed,
        - * as in `box(20, 30)`, it sets the box’s height. By default, `height` is set
        - * to the box’s `width`.
        - *
        - * The third parameter, `depth`, is also optional. If a `Number` is passed, as
        - * in `box(20, 30, 40)`, it sets the box’s depth. By default, `depth` is set
        - * to the box’s `height`.
        - *
        - * The fourth parameter, `detailX`, is also optional. If a `Number` is passed,
        - * as in `box(20, 30, 40, 5)`, it sets the number of triangle subdivisions to
        - * use along the x-axis. All 3D shapes are made by connecting triangles to
        - * form their surfaces. By default, `detailX` is 1.
        - *
        - * The fifth parameter, `detailY`, is also optional. If a number is passed, as
        - * in `box(20, 30, 40, 5, 7)`, it sets the number of triangle subdivisions to
        - * use along the y-axis. All 3D shapes are made by connecting triangles to
        - * form their surfaces. By default, `detailY` is 1.
        - *
        - * Note: `box()` can only be used in WebGL mode.
        - *
        - * @method  box
        - * @param  {Number} [width]     width of the box.
        - * @param  {Number} [height]    height of the box.
        - * @param  {Number} [depth]     depth of the box.
        - * @param {Integer} [detailX]   number of triangle subdivisions along the x-axis.
        - * @param {Integer} [detailY]   number of triangle subdivisions along the y-axis.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white box on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white box on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the box.
        - *   // Set its width and height to 30.
        - *   box(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white box on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the box.
        - *   // Set its width to 30 and height to 50.
        - *   box(30, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white box on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the box.
        - *   // Set its width to 30, height to 50, and depth to 10.
        - *   box(30, 50, 10);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.box = function(width, height, depth, detailX, detailY) {
        -  this._assert3d('box');
        -  p5._validateParameters('box', arguments);
        -  if (typeof width === 'undefined') {
        -    width = 50;
        -  }
        -  if (typeof height === 'undefined') {
        -    height = width;
        -  }
        -  if (typeof depth === 'undefined') {
        -    depth = height;
        -  }
        -
        -  const perPixelLighting =
        -    this._renderer.attributes && this._renderer.attributes.perPixelLighting;
        -  if (typeof detailX === 'undefined') {
        -    detailX = perPixelLighting ? 1 : 4;
        -  }
        -  if (typeof detailY === 'undefined') {
        -    detailY = perPixelLighting ? 1 : 4;
        -  }
        -
        -  const gId = `box|${detailX}|${detailY}`;
        -  if (!this._renderer.geometryInHash(gId)) {
        -    const _box = function() {
        -      const cubeIndices = [
        -        [0, 4, 2, 6], // -1, 0, 0],// -x
        -        [1, 3, 5, 7], // +1, 0, 0],// +x
        -        [0, 1, 4, 5], // 0, -1, 0],// -y
        -        [2, 6, 3, 7], // 0, +1, 0],// +y
        -        [0, 2, 1, 3], // 0, 0, -1],// -z
        -        [4, 5, 6, 7] // 0, 0, +1] // +z
        -      ];
        -      //using custom edges
        -      //to avoid diagonal stroke lines across face of box
        -      this.edges = [
        -        [0, 1],
        -        [1, 3],
        -        [3, 2],
        -        [6, 7],
        -        [8, 9],
        -        [9, 11],
        -        [14, 15],
        -        [16, 17],
        -        [17, 19],
        -        [18, 19],
        -        [20, 21],
        -        [22, 23]
        -      ];
        -
        -      cubeIndices.forEach((cubeIndex, i) => {
        -        const v = i * 4;
        -        for (let j = 0; j < 4; j++) {
        -          const d = cubeIndex[j];
        -          //inspired by lightgl:
        -          //https://github.com/evanw/lightgl.js
        -          //octants:https://en.wikipedia.org/wiki/Octant_(solid_geometry)
        -          const octant = new p5.Vector(
        -            ((d & 1) * 2 - 1) / 2,
        -            ((d & 2) - 1) / 2,
        -            ((d & 4) / 2 - 1) / 2
        -          );
        -          this.vertices.push(octant);
        -          this.uvs.push(j & 1, (j & 2) / 2);
        -        }
        -        this.faces.push([v, v + 1, v + 2]);
        -        this.faces.push([v + 2, v + 1, v + 3]);
        -      });
        -    };
        -    const boxGeom = new p5.Geometry(detailX, detailY, _box);
        -    boxGeom.computeNormals();
        -    if (detailX <= 4 && detailY <= 4) {
        -      boxGeom._edgesToVertices();
        -    } else if (this._renderer._doStroke) {
        -      console.log(
        -        'Cannot draw stroke on box objects with more' +
        -        ' than 4 detailX or 4 detailY'
        -      );
        -    }
        -    //initialize our geometry buffer with
        -    //the key val pair:
        -    //geometry Id, Geom object
        -    this._renderer.createBuffers(gId, boxGeom);
        -  }
        -  this._renderer.drawBuffersScaled(gId, width, height, depth);
        -
        -  return this;
        -};
        -
        -/**
        - * Draws a sphere.
        - *
        - * A sphere is a 3D shape with triangular faces that connect to form a round
        - * surface. Spheres with few faces look like crystals. Spheres with many faces
        - * have smooth surfaces and look like balls.
        - *
        - * The first parameter, `radius`, is optional. If a `Number` is passed, as in
        - * `sphere(20)`, it sets the radius of the sphere. By default, `radius` is 50.
        - *
        - * The second parameter, `detailX`, is also optional. If a `Number` is passed,
        - * as in `sphere(20, 5)`, it sets the number of triangle subdivisions to use
        - * along the x-axis. All 3D shapes are made by connecting triangles to form
        - * their surfaces. By default, `detailX` is 24.
        - *
        - * The third parameter, `detailY`, is also optional. If a `Number` is passed,
        - * as in `sphere(20, 5, 2)`, it sets the number of triangle subdivisions to
        - * use along the y-axis. All 3D shapes are made by connecting triangles to
        - * form their surfaces. By default, `detailY` is 16.
        - *
        - * Note: `sphere()` can only be used in WebGL mode.
        - *
        - * @method sphere
        - * @param  {Number} [radius]   radius of the sphere. Defaults to 50.
        - * @param  {Integer} [detailX] number of triangle subdivisions along the x-axis. Defaults to 24.
        - * @param  {Integer} [detailY] number of triangle subdivisions along the y-axis. Defaults to 16.
        - *
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white sphere on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the sphere.
        - *   sphere();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white sphere on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the sphere.
        - *   // Set its radius to 30.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white sphere on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the sphere.
        - *   // Set its radius to 30.
        - *   // Set its detailX to 6.
        - *   sphere(30, 6);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white sphere on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the sphere.
        - *   // Set its radius to 30.
        - *   // Set its detailX to 24.
        - *   // Set its detailY to 4.
        - *   sphere(30, 24, 4);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.sphere = function(radius = 50, detailX = 24, detailY = 16) {
        -  this._assert3d('sphere');
        -  p5._validateParameters('sphere', arguments);
        -
        -  this.ellipsoid(radius, radius, radius, detailX, detailY);
        -
        -  return this;
        -};
        -
        -/**
        - * @private
        - * Helper function for creating both cones and cylinders
        - * Will only generate well-defined geometry when bottomRadius, height > 0
        - * and topRadius >= 0
        - * If topRadius == 0, topCap should be false
        - */
        -const _truncatedCone = function(
        -  bottomRadius,
        -  topRadius,
        -  height,
        -  detailX,
        -  detailY,
        -  bottomCap,
        -  topCap
        -) {
        -  bottomRadius = bottomRadius <= 0 ? 1 : bottomRadius;
        -  topRadius = topRadius < 0 ? 0 : topRadius;
        -  height = height <= 0 ? bottomRadius : height;
        -  detailX = detailX < 3 ? 3 : detailX;
        -  detailY = detailY < 1 ? 1 : detailY;
        -  bottomCap = bottomCap === undefined ? true : bottomCap;
        -  topCap = topCap === undefined ? topRadius !== 0 : topCap;
        -  const start = bottomCap ? -2 : 0;
        -  const end = detailY + (topCap ? 2 : 0);
        -  //ensure constant slant for interior vertex normals
        -  const slant = Math.atan2(bottomRadius - topRadius, height);
        -  const sinSlant = Math.sin(slant);
        -  const cosSlant = Math.cos(slant);
        -  let yy, ii, jj;
        -  for (yy = start; yy <= end; ++yy) {
        -    let v = yy / detailY;
        -    let y = height * v;
        -    let ringRadius;
        -    if (yy < 0) {
        -      //for the bottomCap edge
        -      y = 0;
        -      v = 0;
        -      ringRadius = bottomRadius;
        -    } else if (yy > detailY) {
        -      //for the topCap edge
        -      y = height;
        -      v = 1;
        -      ringRadius = topRadius;
        -    } else {
        -      //for the middle
        -      ringRadius = bottomRadius + (topRadius - bottomRadius) * v;
        -    }
        -    if (yy === -2 || yy === detailY + 2) {
        -      //center of bottom or top caps
        -      ringRadius = 0;
        -    }
        -
        -    y -= height / 2; //shift coordiate origin to the center of object
        -    for (ii = 0; ii < detailX; ++ii) {
        -      const u = ii / (detailX - 1);
        -      const ur = 2 * Math.PI * u;
        -      const sur = Math.sin(ur);
        -      const cur = Math.cos(ur);
        -
        -      //VERTICES
        -      this.vertices.push(new p5.Vector(sur * ringRadius, y, cur * ringRadius));
        -
        -      //VERTEX NORMALS
        -      let vertexNormal;
        -      if (yy < 0) {
        -        vertexNormal = new p5.Vector(0, -1, 0);
        -      } else if (yy > detailY && topRadius) {
        -        vertexNormal = new p5.Vector(0, 1, 0);
        -      } else {
        -        vertexNormal = new p5.Vector(sur * cosSlant, sinSlant, cur * cosSlant);
        -      }
        -      this.vertexNormals.push(vertexNormal);
        -      //UVs
        -      this.uvs.push(u, v);
        -    }
        -  }
        -
        -  let startIndex = 0;
        -  if (bottomCap) {
        -    for (jj = 0; jj < detailX; ++jj) {
        -      const nextjj = (jj + 1) % detailX;
        -      this.faces.push([
        -        startIndex + jj,
        -        startIndex + detailX + nextjj,
        -        startIndex + detailX + jj
        -      ]);
        -    }
        -    startIndex += detailX * 2;
        -  }
        -  for (yy = 0; yy < detailY; ++yy) {
        -    for (ii = 0; ii < detailX; ++ii) {
        -      const nextii = (ii + 1) % detailX;
        -      this.faces.push([
        -        startIndex + ii,
        -        startIndex + nextii,
        -        startIndex + detailX + nextii
        -      ]);
        -      this.faces.push([
        -        startIndex + ii,
        -        startIndex + detailX + nextii,
        -        startIndex + detailX + ii
        -      ]);
        -    }
        -    startIndex += detailX;
        -  }
        -  if (topCap) {
        -    startIndex += detailX;
        -    for (ii = 0; ii < detailX; ++ii) {
        -      this.faces.push([
        -        startIndex + ii,
        -        startIndex + (ii + 1) % detailX,
        -        startIndex + detailX
        -      ]);
        -    }
        -  }
        -};
        -
        -/**
        - * Draws a cylinder.
        - *
        - * A cylinder is a 3D shape with triangular faces that connect a flat bottom
        - * to a flat top. Cylinders with few faces look like boxes. Cylinders with
        - * many faces have smooth surfaces.
        - *
        - * The first parameter, `radius`, is optional. If a `Number` is passed, as in
        - * `cylinder(20)`, it sets the radius of the cylinder’s base. By default,
        - * `radius` is 50.
        - *
        - * The second parameter, `height`, is also optional. If a `Number` is passed,
        - * as in `cylinder(20, 30)`, it sets the cylinder’s height. By default,
        - * `height` is set to the cylinder’s `radius`.
        - *
        - * The third parameter, `detailX`, is also optional. If a `Number` is passed,
        - * as in `cylinder(20, 30, 5)`, it sets the number of edges used to form the
        - * cylinder's top and bottom. Using more edges makes the top and bottom look
        - * more like circles. By default, `detailX` is 24.
        - *
        - * The fourth parameter, `detailY`, is also optional. If a `Number` is passed,
        - * as in `cylinder(20, 30, 5, 2)`, it sets the number of triangle subdivisions
        - * to use along the y-axis, between cylinder's the top and bottom. All 3D
        - * shapes are made by connecting triangles to form their surfaces. By default,
        - * `detailY` is 1.
        - *
        - * The fifth parameter, `bottomCap`, is also optional. If a `false` is passed,
        - * as in `cylinder(20, 30, 5, 2, false)` the cylinder’s bottom won’t be drawn.
        - * By default, `bottomCap` is `true`.
        - *
        - * The sixth parameter, `topCap`, is also optional. If a `false` is passed, as
        - * in `cylinder(20, 30, 5, 2, false, false)` the cylinder’s top won’t be
        - * drawn. By default, `topCap` is `true`.
        - *
        - * Note: `cylinder()` can only be used in WebGL mode.
        - *
        - * @method cylinder
        - * @param  {Number}  [radius]    radius of the cylinder. Defaults to 50.
        - * @param  {Number}  [height]    height of the cylinder. Defaults to the value of `radius`.
        - * @param  {Integer} [detailX]   number of edges along the top and bottom. Defaults to 24.
        - * @param  {Integer} [detailY]   number of triangle subdivisions along the y-axis. Defaults to 1.
        - * @param  {Boolean} [bottomCap] whether to draw the cylinder's bottom. Defaults to `true`.
        - * @param  {Boolean} [topCap]    whether to draw the cylinder's top. Defaults to `true`.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cylinder on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cylinder.
        - *   cylinder();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cylinder on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cylinder.
        - *   // Set its radius and height to 30.
        - *   cylinder(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cylinder on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cylinder.
        - *   // Set its radius to 30 and height to 50.
        - *   cylinder(30, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white box on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cylinder.
        - *   // Set its radius to 30 and height to 50.
        - *   // Set its detailX to 5.
        - *   cylinder(30, 50, 5);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cylinder on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cylinder.
        - *   // Set its radius to 30 and height to 50.
        - *   // Set its detailX to 24 and detailY to 2.
        - *   cylinder(30, 50, 24, 2);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cylinder on a gray background. Its top is missing.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cylinder.
        - *   // Set its radius to 30 and height to 50.
        - *   // Set its detailX to 24 and detailY to 1.
        - *   // Don't draw its bottom.
        - *   cylinder(30, 50, 24, 1, false);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cylinder on a gray background. Its top and bottom are missing.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cylinder.
        - *   // Set its radius to 30 and height to 50.
        - *   // Set its detailX to 24 and detailY to 1.
        - *   // Don't draw its bottom or top.
        - *   cylinder(30, 50, 24, 1, false, false);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.cylinder = function(
        -  radius = 50,
        -  height = radius,
        -  detailX = 24,
        -  detailY = 1,
        -  bottomCap = true,
        -  topCap = true
        -) {
        -  this._assert3d('cylinder');
        -  p5._validateParameters('cylinder', arguments);
        -
        -  const gId = `cylinder|${detailX}|${detailY}|${bottomCap}|${topCap}`;
        -  if (!this._renderer.geometryInHash(gId)) {
        -    const cylinderGeom = new p5.Geometry(detailX, detailY);
        -    _truncatedCone.call(
        -      cylinderGeom,
        -      1,
        -      1,
        -      1,
        -      detailX,
        -      detailY,
        -      bottomCap,
        -      topCap
        -    );
        -    // normals are computed in call to _truncatedCone
        -    if (detailX <= 24 && detailY <= 16) {
        -      cylinderGeom._makeTriangleEdges()._edgesToVertices();
        -    } else if (this._renderer._doStroke) {
        -      console.log(
        -        'Cannot draw stroke on cylinder objects with more' +
        -        ' than 24 detailX or 16 detailY'
        -      );
        -    }
        -    this._renderer.createBuffers(gId, cylinderGeom);
        -  }
        -
        -  this._renderer.drawBuffersScaled(gId, radius, height, radius);
        -
        -  return this;
        -};
        -
        -/**
        - * Draws a cone.
        - *
        - * A cone is a 3D shape with triangular faces that connect a flat bottom to a
        - * single point. Cones with few faces look like pyramids. Cones with many
        - * faces have smooth surfaces.
        - *
        - * The first parameter, `radius`, is optional. If a `Number` is passed, as in
        - * `cone(20)`, it sets the radius of the cone’s base. By default, `radius` is
        - * 50.
        - *
        - * The second parameter, `height`, is also optional. If a `Number` is passed,
        - * as in `cone(20, 30)`, it sets the cone’s height. By default, `height` is
        - * set to the cone’s `radius`.
        - *
        - * The third parameter, `detailX`, is also optional. If a `Number` is passed,
        - * as in `cone(20, 30, 5)`, it sets the number of edges used to form the
        - * cone's base. Using more edges makes the base look more like a circle. By
        - * default, `detailX` is 24.
        - *
        - * The fourth parameter, `detailY`, is also optional. If a `Number` is passed,
        - * as in `cone(20, 30, 5, 7)`, it sets the number of triangle subdivisions to
        - * use along the y-axis connecting the base to the tip. All 3D shapes are made
        - * by connecting triangles to form their surfaces. By default, `detailY` is 1.
        - *
        - * The fifth parameter, `cap`, is also optional. If a `false` is passed, as
        - * in `cone(20, 30, 5, 7, false)` the cone’s base won’t be drawn. By default,
        - * `cap` is `true`.
        - *
        - * Note: `cone()` can only be used in WebGL mode.
        - *
        - * @method cone
        - * @param  {Number}  [radius]  radius of the cone's base. Defaults to 50.
        - * @param  {Number}  [height]  height of the cone. Defaults to the value of `radius`.
        - * @param  {Integer} [detailX] number of edges used to draw the base. Defaults to 24.
        - * @param  {Integer} [detailY] number of triangle subdivisions along the y-axis. Defaults to 1.
        - * @param  {Boolean} [cap]     whether to draw the cone's base.  Defaults to `true`.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cone on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cone.
        - *   cone();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cone on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cone.
        - *   // Set its radius and height to 30.
        - *   cone(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cone on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cone.
        - *   // Set its radius to 30 and height to 50.
        - *   cone(30, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cone on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cone.
        - *   // Set its radius to 30 and height to 50.
        - *   // Set its detailX to 5.
        - *   cone(30, 50, 5);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white pyramid on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cone.
        - *   // Set its radius to 30 and height to 50.
        - *   // Set its detailX to 5.
        - *   cone(30, 50, 5);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cone on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cone.
        - *   // Set its radius to 30 and height to 50.
        - *   // Set its detailX to 24 and detailY to 2.
        - *   cone(30, 50, 24, 2);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cone on a gray background. Its base is missing.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the cone.
        - *   // Set its radius to 30 and height to 50.
        - *   // Set its detailX to 24 and detailY to 1.
        - *   // Don't draw its base.
        - *   cone(30, 50, 24, 1, false);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.cone = function(
        -  radius = 50,
        -  height = radius,
        -  detailX = 24,
        -  detailY = 1,
        -  cap = true
        -) {
        -  this._assert3d('cone');
        -  p5._validateParameters('cone', arguments);
        -
        -  const gId = `cone|${detailX}|${detailY}|${cap}`;
        -  if (!this._renderer.geometryInHash(gId)) {
        -    const coneGeom = new p5.Geometry(detailX, detailY);
        -    _truncatedCone.call(coneGeom, 1, 0, 1, detailX, detailY, cap, false);
        -    if (detailX <= 24 && detailY <= 16) {
        -      coneGeom._makeTriangleEdges()._edgesToVertices();
        -    } else if (this._renderer._doStroke) {
        -      console.log(
        -        'Cannot draw stroke on cone objects with more' +
        -        ' than 24 detailX or 16 detailY'
        -      );
        -    }
        -    this._renderer.createBuffers(gId, coneGeom);
        -  }
        -
        -  this._renderer.drawBuffersScaled(gId, radius, height, radius);
        -
        -  return this;
        -};
        -
        -/**
        - * Draws an ellipsoid.
        - *
        - * An ellipsoid is a 3D shape with triangular faces that connect to form a
        - * round surface. Ellipsoids with few faces look like crystals. Ellipsoids
        - * with many faces have smooth surfaces and look like eggs. `ellipsoid()`
        - * defines a shape by its radii. This is different from
        - * <a href="#/p5/ellipse">ellipse()</a> which uses diameters
        - * (width and height).
        - *
        - * The first parameter, `radiusX`, is optional. If a `Number` is passed, as in
        - * `ellipsoid(20)`, it sets the radius of the ellipsoid along the x-axis. By
        - * default, `radiusX` is 50.
        - *
        - * The second parameter, `radiusY`, is also optional. If a `Number` is passed,
        - * as in `ellipsoid(20, 30)`, it sets the ellipsoid’s radius along the y-axis.
        - * By default, `radiusY` is set to the ellipsoid’s `radiusX`.
        - *
        - * The third parameter, `radiusZ`, is also optional. If a `Number` is passed,
        - * as in `ellipsoid(20, 30, 40)`, it sets the ellipsoid’s radius along the
        - * z-axis. By default, `radiusZ` is set to the ellipsoid’s `radiusY`.
        - *
        - * The fourth parameter, `detailX`, is also optional. If a `Number` is passed,
        - * as in `ellipsoid(20, 30, 40, 5)`, it sets the number of triangle
        - * subdivisions to use along the x-axis. All 3D shapes are made by connecting
        - * triangles to form their surfaces. By default, `detailX` is 24.
        - *
        - * The fifth parameter, `detailY`, is also optional. If a `Number` is passed,
        - * as in `ellipsoid(20, 30, 40, 5, 7)`, it sets the number of triangle
        - * subdivisions to use along the y-axis. All 3D shapes are made by connecting
        - * triangles to form their surfaces. By default, `detailY` is 16.
        - *
        - * Note: `ellipsoid()` can only be used in WebGL mode.
        - *
        - * @method ellipsoid
        - * @param  {Number} [radiusX]  radius of the ellipsoid along the x-axis. Defaults to 50.
        - * @param  {Number} [radiusY]  radius of the ellipsoid along the y-axis. Defaults to `radiusX`.
        - * @param  {Number} [radiusZ]  radius of the ellipsoid along the z-axis. Defaults to `radiusY`.
        - * @param  {Integer} [detailX] number of triangle subdivisions along the x-axis. Defaults to 24.
        - * @param  {Integer} [detailY] number of triangle subdivisions along the y-axis. Defaults to 16.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white sphere on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the ellipsoid.
        - *   // Set its radiusX to 30.
        - *   ellipsoid(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white ellipsoid on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the ellipsoid.
        - *   // Set its radiusX to 30.
        - *   // Set its radiusY to 40.
        - *   ellipsoid(30, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white ellipsoid on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the ellipsoid.
        - *   // Set its radiusX to 30.
        - *   // Set its radiusY to 40.
        - *   // Set its radiusZ to 50.
        - *   ellipsoid(30, 40, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white ellipsoid on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the ellipsoid.
        - *   // Set its radiusX to 30.
        - *   // Set its radiusY to 40.
        - *   // Set its radiusZ to 50.
        - *   // Set its detailX to 4.
        - *   ellipsoid(30, 40, 50, 4);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white ellipsoid on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the ellipsoid.
        - *   // Set its radiusX to 30.
        - *   // Set its radiusY to 40.
        - *   // Set its radiusZ to 50.
        - *   // Set its detailX to 4.
        - *   // Set its detailY to 3.
        - *   ellipsoid(30, 40, 50, 4, 3);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.ellipsoid = function(
        -  radiusX = 50,
        -  radiusY = radiusX,
        -  radiusZ = radiusX,
        -  detailX = 24,
        -  detailY = 16
        -) {
        -  this._assert3d('ellipsoid');
        -  p5._validateParameters('ellipsoid', arguments);
        -
        -  const gId = `ellipsoid|${detailX}|${detailY}`;
        -
        -  if (!this._renderer.geometryInHash(gId)) {
        -    const _ellipsoid = function() {
        -      for (let i = 0; i <= this.detailY; i++) {
        -        const v = i / this.detailY;
        -        const phi = Math.PI * v - Math.PI / 2;
        -        const cosPhi = Math.cos(phi);
        -        const sinPhi = Math.sin(phi);
        -
        -        for (let j = 0; j <= this.detailX; j++) {
        -          const u = j / this.detailX;
        -          const theta = 2 * Math.PI * u;
        -          const cosTheta = Math.cos(theta);
        -          const sinTheta = Math.sin(theta);
        -          const p = new p5.Vector(cosPhi * sinTheta, sinPhi, cosPhi * cosTheta);
        -          this.vertices.push(p);
        -          this.vertexNormals.push(p);
        -          this.uvs.push(u, v);
        -        }
        -      }
        -    };
        -    const ellipsoidGeom = new p5.Geometry(detailX, detailY, _ellipsoid);
        -    ellipsoidGeom.computeFaces();
        -    if (detailX <= 24 && detailY <= 24) {
        -      ellipsoidGeom._makeTriangleEdges()._edgesToVertices();
        -    } else if (this._renderer._doStroke) {
        -      console.log(
        -        'Cannot draw stroke on ellipsoids with more' +
        -        ' than 24 detailX or 24 detailY'
        -      );
        -    }
        -    this._renderer.createBuffers(gId, ellipsoidGeom);
        -  }
        -
        -  this._renderer.drawBuffersScaled(gId, radiusX, radiusY, radiusZ);
        -
        -  return this;
        -};
        -
        -/**
        - * Draws a torus.
        - *
        - * A torus is a 3D shape with triangular faces that connect to form a ring.
        - * Toruses with few faces look flattened. Toruses with many faces have smooth
        - * surfaces.
        - *
        - * The first parameter, `radius`, is optional. If a `Number` is passed, as in
        - * `torus(30)`, it sets the radius of the ring. By default, `radius` is 50.
        - *
        - * The second parameter, `tubeRadius`, is also optional. If a `Number` is
        - * passed, as in `torus(30, 15)`, it sets the radius of the tube. By default,
        - * `tubeRadius` is 10.
        - *
        - * The third parameter, `detailX`, is also optional. If a `Number` is passed,
        - * as in `torus(30, 15, 5)`, it sets the number of edges used to draw the hole
        - * of the torus. Using more edges makes the hole look more like a circle. By
        - * default, `detailX` is 24.
        - *
        - * The fourth parameter, `detailY`, is also optional. If a `Number` is passed,
        - * as in `torus(30, 15, 5, 7)`, it sets the number of triangle subdivisions to
        - * use while filling in the torus’ height. By default, `detailY` is 16.
        - *
        - * Note: `torus()` can only be used in WebGL mode.
        - *
        - * @method torus
        - * @param  {Number} [radius]      radius of the torus. Defaults to 50.
        - * @param  {Number} [tubeRadius]  radius of the tube. Defaults to 10.
        - * @param  {Integer} [detailX]    number of edges that form the hole. Defaults to 24.
        - * @param  {Integer} [detailY]    number of triangle subdivisions along the y-axis. Defaults to 16.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white torus on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the torus.
        - *   torus();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white torus on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the torus.
        - *   // Set its radius to 30.
        - *   torus(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white torus on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the torus.
        - *   // Set its radius to 30 and tubeRadius to 15.
        - *   torus(30, 15);
        - * }
        - * </code>
        - * </div>
        + * @method strokeMode
        + * @param {string} mode - The stroke mode to set. Possible values are:
        + *   - `'SIMPLE'`: Fast rendering without caps, joins, or stroke color.
        + *   - `'FULL'`: Detailed rendering with caps, joins, and stroke color.
          *
        + * @example
          * <div>
          * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
          * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        + *   createCanvas(300, 300, WEBGL);
          *
        - *   describe('A white torus on a gray background.');
        + *   describe('A sphere with red stroke and a red, wavy line on a gray background.');
          * }
          *
          * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        + *   background(128);
        + *   strokeMode(FULL); // Enables detailed rendering with caps, joins, and stroke color.
        + *   push();
        + *   strokeWeight(1);
        + *   translate(0, -50, 0);
        + *   sphere(50);
        + *   pop();
          *
        - *   // Draw the torus.
        - *   // Set its radius to 30 and tubeRadius to 15.
        - *   // Set its detailX to 5.
        - *   torus(30, 15, 5);
        + *   noFill();
        + *   strokeWeight(15);
        + *   beginShape();
        + *   vertex(-150, 100);
        + *   stroke('red');
        + *   bezierVertex(-50, -100, 30, 300, 130, 50);
        + *   endShape();
          * }
          * </code>
          * </div>
          *
          * <div>
          * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
          * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        + *   createCanvas(300, 300, WEBGL);
          *
        - *   describe('A white torus on a gray background.');
        + *   describe('A sphere with red stroke and a  wavy line without full curve decorations without caps and color on a gray background.');
          * }
          *
          * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        + *   background(128);
        + *   strokeMode(SIMPLE); // Enables simple rendering without caps, joins, and stroke color.
        + *   push();
        + *   strokeWeight(1);
        + *   translate(0, -50, 0);
        + *   sphere(50);
        + *   pop();
          *
        - *   // Draw the torus.
        - *   // Set its radius to 30 and tubeRadius to 15.
        - *   // Set its detailX to 5.
        - *   // Set its detailY to 3.
        - *   torus(30, 15, 5, 3);
        + *   noFill();
        + *   strokeWeight(15);
        + *   beginShape();
        + *   vertex(-150, 100);
        + *   stroke('red');
        + *   bezierVertex(-50, -100, 30, 300, 130, 50);
        + *   endShape();
          * }
          * </code>
          * </div>
          */
        -p5.prototype.torus = function(radius, tubeRadius, detailX, detailY) {
        -  this._assert3d('torus');
        -  p5._validateParameters('torus', arguments);
        -  if (typeof radius === 'undefined') {
        -    radius = 50;
        -  } else if (!radius) {
        -    return; // nothing to draw
        -  }
         
        -  if (typeof tubeRadius === 'undefined') {
        -    tubeRadius = 10;
        -  } else if (!tubeRadius) {
        -    return; // nothing to draw
        +  fn.strokeMode = function (mode) {
        +    if (mode === undefined) {
        +      return this._renderer._simpleLines ? constants.SIMPLE : constants.FULL;
        +    } else if (mode === constants.SIMPLE) {
        +      this._renderer._simpleLines = true;
        +    } else if (mode === constants.FULL) {
        +      this._renderer._simpleLines = false;
        +    } else {
        +      throw Error('no such parameter');
        +    }
           }
        +  /**
        +   * Creates a custom <a href="#/p5.Geometry">p5.Geometry</a> object from
        +   * simpler 3D shapes.
        +   *
        +   * `buildGeometry()` helps with creating complex 3D shapes from simpler ones
        +   * such as <a href="#/p5/sphere">sphere()</a>. It can help to make sketches
        +   * more performant. For example, if a complex 3D shape doesn’t change while a
        +   * sketch runs, then it can be created with `buildGeometry()`. Creating a
        +   * <a href="#/p5.Geometry">p5.Geometry</a> object once and then drawing it
        +   * will run faster than repeatedly drawing the individual pieces.
        +   *
        +   * The parameter, `callback`, is a function with the drawing instructions for
        +   * the new <a href="#/p5.Geometry">p5.Geometry</a> object. It will be called
        +   * once to create the new 3D shape.
        +   *
        +   * See <a href="#/p5/beginGeometry">beginGeometry()</a> and
        +   * <a href="#/p5/endGeometry">endGeometry()</a> for another way to build 3D
        +   * shapes.
        +   *
        +   * Note: `buildGeometry()` can only be used in WebGL mode.
        +   *
        +   * @method buildGeometry
        +   * @param {Function} callback function that draws the shape.
        +   * @returns {p5.Geometry} new 3D shape.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the p5.Geometry object.
        +   *   shape = buildGeometry(createShape);
        +   *
        +   *   describe('A white cone drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the p5.Geometry object.
        +   *   noStroke();
        +   *
        +   *   // Draw the p5.Geometry object.
        +   *   model(shape);
        +   * }
        +   *
        +   * // Create p5.Geometry object from a single cone.
        +   * function createShape() {
        +   *   cone();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the arrow.
        +   *   shape = buildGeometry(createArrow);
        +   *
        +   *   describe('A white arrow drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the arrow.
        +   *   noStroke();
        +   *
        +   *   // Draw the arrow.
        +   *   model(shape);
        +   * }
        +   *
        +   * function createArrow() {
        +   *   // Add shapes to the p5.Geometry object.
        +   *   push();
        +   *   rotateX(PI);
        +   *   cone(10);
        +   *   translate(0, -10, 0);
        +   *   cylinder(3, 20);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the p5.Geometry object.
        +   *   shape = buildGeometry(createArrow);
        +   *
        +   *   describe('Two white arrows drawn on a gray background. The arrow on the right rotates slowly.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the arrows.
        +   *   noStroke();
        +   *
        +   *   // Draw the p5.Geometry object.
        +   *   model(shape);
        +   *
        +   *   // Translate and rotate the coordinate system.
        +   *   translate(30, 0, 0);
        +   *   rotateZ(frameCount * 0.01);
        +   *
        +   *   // Draw the p5.Geometry object again.
        +   *   model(shape);
        +   * }
        +   *
        +   * function createArrow() {
        +   *   // Add shapes to the p5.Geometry object.
        +   *   push();
        +   *   rotateX(PI);
        +   *   cone(10);
        +   *   translate(0, -10, 0);
        +   *   cylinder(3, 20);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let button;
        +   * let particles;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a button to reset the particle system.
        +   *   button = createButton('Reset');
        +   *
        +   *   // Call resetModel() when the user presses the button.
        +   *   button.mousePressed(resetModel);
        +   *
        +   *   // Add the original set of particles.
        +   *   resetModel();
        +   *
        +   *   describe('A set of white spheres on a gray background. The spheres are positioned randomly. Their positions reset when the user presses the Reset button.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the particles.
        +   *   noStroke();
        +   *
        +   *   // Draw the particles.
        +   *   model(particles);
        +   * }
        +   *
        +   * function resetModel() {
        +   *   // If the p5.Geometry object has already been created,
        +   *   // free those resources.
        +   *   if (particles) {
        +   *     freeGeometry(particles);
        +   *   }
        +   *
        +   *   // Create a new p5.Geometry object with random spheres.
        +   *   particles = buildGeometry(createParticles);
        +   * }
        +   *
        +   * function createParticles() {
        +   *   for (let i = 0; i < 60; i += 1) {
        +   *     // Calculate random coordinates.
        +   *     let x = randomGaussian(0, 20);
        +   *     let y = randomGaussian(0, 20);
        +   *     let z = randomGaussian(0, 20);
        +   *
        +   *     push();
        +   *     // Translate to the particle's coordinates.
        +   *     translate(x, y, z);
        +   *     // Draw the particle.
        +   *     sphere(5);
        +   *     pop();
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.buildGeometry = function(callback) {
        +    return this._renderer.buildGeometry(callback);
        +  };
        +
        +  /**
        +   * Clears a <a href="#/p5.Geometry">p5.Geometry</a> object from the graphics
        +   * processing unit (GPU) memory.
        +   *
        +   * <a href="#/p5.Geometry">p5.Geometry</a> objects can contain lots of data
        +   * about their vertices, surface normals, colors, and so on. Complex 3D shapes
        +   * can use lots of memory which is a limited resource in many GPUs. Calling
        +   * `freeGeometry()` can improve performance by freeing a
        +   * <a href="#/p5.Geometry">p5.Geometry</a> object’s resources from GPU memory.
        +   * `freeGeometry()` works with <a href="#/p5.Geometry">p5.Geometry</a> objects
        +   * created with <a href="#/p5/beginGeometry">beginGeometry()</a> and
        +   * <a href="#/p5/endGeometry">endGeometry()</a>,
        +   * <a href="#/p5/buildGeometry">buildGeometry()</a>, and
        +   * <a href="#/p5/loadModel">loadModel()</a>.
        +   *
        +   * The parameter, `geometry`, is the <a href="#/p5.Geometry">p5.Geometry</a>
        +   * object to be freed.
        +   *
        +   * Note: A <a href="#/p5.Geometry">p5.Geometry</a> object can still be drawn
        +   * after its resources are cleared from GPU memory. It may take longer to draw
        +   * the first time it’s redrawn.
        +   *
        +   * Note: `freeGeometry()` can only be used in WebGL mode.
        +   *
        +   * @method freeGeometry
        +   * @param {p5.Geometry} geometry 3D shape whose resources should be freed.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Geometry object.
        +   *   beginGeometry();
        +   *   cone();
        +   *   let shape = endGeometry();
        +   *
        +   *   // Draw the shape.
        +   *   model(shape);
        +   *
        +   *   // Free the shape's resources.
        +   *   freeGeometry(shape);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let button;
        +   * let particles;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a button to reset the particle system.
        +   *   button = createButton('Reset');
        +   *
        +   *   // Call resetModel() when the user presses the button.
        +   *   button.mousePressed(resetModel);
        +   *
        +   *   // Add the original set of particles.
        +   *   resetModel();
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the particles.
        +   *   noStroke();
        +   *
        +   *   // Draw the particles.
        +   *   model(particles);
        +   * }
        +   *
        +   * function resetModel() {
        +   *   // If the p5.Geometry object has already been created,
        +   *   // free those resources.
        +   *   if (particles) {
        +   *     freeGeometry(particles);
        +   *   }
        +   *
        +   *   // Create a new p5.Geometry object with random spheres.
        +   *   particles = buildGeometry(createParticles);
        +   * }
        +   *
        +   * function createParticles() {
        +   *   for (let i = 0; i < 60; i += 1) {
        +   *     // Calculate random coordinates.
        +   *     let x = randomGaussian(0, 20);
        +   *     let y = randomGaussian(0, 20);
        +   *     let z = randomGaussian(0, 20);
        +   *
        +   *     push();
        +   *     // Translate to the particle's coordinates.
        +   *     translate(x, y, z);
        +   *     // Draw the particle.
        +   *     sphere(5);
        +   *     pop();
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.freeGeometry = function(geometry) {
        +    this._renderer.geometryBufferCache.freeBuffers(geometry.gid);
        +  };
        +
        +  /**
        +   * Draws a plane.
        +   *
        +   * A plane is a four-sided, flat shape with every angle measuring 90˚. It’s
        +   * similar to a rectangle and offers advanced drawing features in WebGL mode.
        +   *
        +   * The first parameter, `width`, is optional. If a `Number` is passed, as in
        +   * `plane(20)`, it sets the plane’s width and height. By default, `width` is
        +   * 50.
        +   *
        +   * The second parameter, `height`, is also optional. If a `Number` is passed,
        +   * as in `plane(20, 30)`, it sets the plane’s height. By default, `height` is
        +   * set to the plane’s `width`.
        +   *
        +   * The third parameter, `detailX`, is also optional. If a `Number` is passed,
        +   * as in `plane(20, 30, 5)` it sets the number of triangle subdivisions to use
        +   * along the x-axis. All 3D shapes are made by connecting triangles to form
        +   * their surfaces. By default, `detailX` is 1.
        +   *
        +   * The fourth parameter, `detailY`, is also optional. If a `Number` is passed,
        +   * as in `plane(20, 30, 5, 7)` it sets the number of triangle subdivisions to
        +   * use along the y-axis. All 3D shapes are made by connecting triangles to
        +   * form their surfaces. By default, `detailY` is 1.
        +   *
        +   * Note: `plane()` can only be used in WebGL mode.
        +   *
        +   * @method plane
        +   * @param  {Number} [width]    width of the plane.
        +   * @param  {Number} [height]   height of the plane.
        +   * @param  {Integer} [detailX] number of triangle subdivisions along the x-axis.
        +   * @param {Integer} [detailY]  number of triangle subdivisions along the y-axis.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white plane on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the plane.
        +   *   plane();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white plane on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the plane.
        +   *   // Set its width and height to 30.
        +   *   plane(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white plane on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the plane.
        +   *   // Set its width to 30 and height to 50.
        +   *   plane(30, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.plane = function(
        +    width = 50,
        +    height = width,
        +    detailX = 1,
        +    detailY = 1
        +  ) {
        +    this._assert3d('plane');
        +    // p5._validateParameters('plane', arguments);
        +
        +    this._renderer.plane(width, height, detailX, detailY);
        +    return this;
        +  };
        +
        +  /**
        +   * Draws a box (rectangular prism).
        +   *
        +   * A box is a 3D shape with six faces. Each face makes a 90˚ with four
        +   * neighboring faces.
        +   *
        +   * The first parameter, `width`, is optional. If a `Number` is passed, as in
        +   * `box(20)`, it sets the box’s width and height. By default, `width` is 50.
        +   *
        +   * The second parameter, `height`, is also optional. If a `Number` is passed,
        +   * as in `box(20, 30)`, it sets the box’s height. By default, `height` is set
        +   * to the box’s `width`.
        +   *
        +   * The third parameter, `depth`, is also optional. If a `Number` is passed, as
        +   * in `box(20, 30, 40)`, it sets the box’s depth. By default, `depth` is set
        +   * to the box’s `height`.
        +   *
        +   * The fourth parameter, `detailX`, is also optional. If a `Number` is passed,
        +   * as in `box(20, 30, 40, 5)`, it sets the number of triangle subdivisions to
        +   * use along the x-axis. All 3D shapes are made by connecting triangles to
        +   * form their surfaces. By default, `detailX` is 1.
        +   *
        +   * The fifth parameter, `detailY`, is also optional. If a number is passed, as
        +   * in `box(20, 30, 40, 5, 7)`, it sets the number of triangle subdivisions to
        +   * use along the y-axis. All 3D shapes are made by connecting triangles to
        +   * form their surfaces. By default, `detailY` is 1.
        +   *
        +   * Note: `box()` can only be used in WebGL mode.
        +   *
        +   * @method  box
        +   * @param  {Number} [width]     width of the box.
        +   * @param  {Number} [height]    height of the box.
        +   * @param  {Number} [depth]     depth of the box.
        +   * @param {Integer} [detailX]   number of triangle subdivisions along the x-axis.
        +   * @param {Integer} [detailY]   number of triangle subdivisions along the y-axis.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white box on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white box on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the box.
        +   *   // Set its width and height to 30.
        +   *   box(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white box on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the box.
        +   *   // Set its width to 30 and height to 50.
        +   *   box(30, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white box on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the box.
        +   *   // Set its width to 30, height to 50, and depth to 10.
        +   *   box(30, 50, 10);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.box = function(width, height, depth, detailX, detailY) {
        +    this._assert3d('box');
        +    // p5._validateParameters('box', arguments);
        +
        +    this._renderer.box(width, height, depth, detailX, detailY);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Draws a sphere.
        +   *
        +   * A sphere is a 3D shape with triangular faces that connect to form a round
        +   * surface. Spheres with few faces look like crystals. Spheres with many faces
        +   * have smooth surfaces and look like balls.
        +   *
        +   * The first parameter, `radius`, is optional. If a `Number` is passed, as in
        +   * `sphere(20)`, it sets the radius of the sphere. By default, `radius` is 50.
        +   *
        +   * The second parameter, `detailX`, is also optional. If a `Number` is passed,
        +   * as in `sphere(20, 5)`, it sets the number of triangle subdivisions to use
        +   * along the x-axis. All 3D shapes are made by connecting triangles to form
        +   * their surfaces. By default, `detailX` is 24.
        +   *
        +   * The third parameter, `detailY`, is also optional. If a `Number` is passed,
        +   * as in `sphere(20, 5, 2)`, it sets the number of triangle subdivisions to
        +   * use along the y-axis. All 3D shapes are made by connecting triangles to
        +   * form their surfaces. By default, `detailY` is 16.
        +   *
        +   * Note: `sphere()` can only be used in WebGL mode.
        +   *
        +   * @method sphere
        +   * @param  {Number} [radius]   radius of the sphere. Defaults to 50.
        +   * @param  {Integer} [detailX] number of triangle subdivisions along the x-axis. Defaults to 24.
        +   * @param  {Integer} [detailY] number of triangle subdivisions along the y-axis. Defaults to 16.
        +   *
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white sphere on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the sphere.
        +   *   sphere();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white sphere on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the sphere.
        +   *   // Set its radius to 30.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white sphere on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the sphere.
        +   *   // Set its radius to 30.
        +   *   // Set its detailX to 6.
        +   *   sphere(30, 6);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white sphere on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the sphere.
        +   *   // Set its radius to 30.
        +   *   // Set its detailX to 24.
        +   *   // Set its detailY to 4.
        +   *   sphere(30, 24, 4);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.sphere = function(radius = 50, detailX = 24, detailY = 16) {
        +    this._assert3d('sphere');
        +    // p5._validateParameters('sphere', arguments);
        +
        +    this._renderer.sphere(radius, detailX, detailY);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Draws a cylinder.
        +   *
        +   * A cylinder is a 3D shape with triangular faces that connect a flat bottom
        +   * to a flat top. Cylinders with few faces look like boxes. Cylinders with
        +   * many faces have smooth surfaces.
        +   *
        +   * The first parameter, `radius`, is optional. If a `Number` is passed, as in
        +   * `cylinder(20)`, it sets the radius of the cylinder’s base. By default,
        +   * `radius` is 50.
        +   *
        +   * The second parameter, `height`, is also optional. If a `Number` is passed,
        +   * as in `cylinder(20, 30)`, it sets the cylinder’s height. By default,
        +   * `height` is set to the cylinder’s `radius`.
        +   *
        +   * The third parameter, `detailX`, is also optional. If a `Number` is passed,
        +   * as in `cylinder(20, 30, 5)`, it sets the number of edges used to form the
        +   * cylinder's top and bottom. Using more edges makes the top and bottom look
        +   * more like circles. By default, `detailX` is 24.
        +   *
        +   * The fourth parameter, `detailY`, is also optional. If a `Number` is passed,
        +   * as in `cylinder(20, 30, 5, 2)`, it sets the number of triangle subdivisions
        +   * to use along the y-axis, between cylinder's the top and bottom. All 3D
        +   * shapes are made by connecting triangles to form their surfaces. By default,
        +   * `detailY` is 1.
        +   *
        +   * The fifth parameter, `bottomCap`, is also optional. If a `false` is passed,
        +   * as in `cylinder(20, 30, 5, 2, false)` the cylinder’s bottom won’t be drawn.
        +   * By default, `bottomCap` is `true`.
        +   *
        +   * The sixth parameter, `topCap`, is also optional. If a `false` is passed, as
        +   * in `cylinder(20, 30, 5, 2, false, false)` the cylinder’s top won’t be
        +   * drawn. By default, `topCap` is `true`.
        +   *
        +   * Note: `cylinder()` can only be used in WebGL mode.
        +   *
        +   * @method cylinder
        +   * @param  {Number}  [radius]    radius of the cylinder. Defaults to 50.
        +   * @param  {Number}  [height]    height of the cylinder. Defaults to the value of `radius`.
        +   * @param  {Integer} [detailX]   number of edges along the top and bottom. Defaults to 24.
        +   * @param  {Integer} [detailY]   number of triangle subdivisions along the y-axis. Defaults to 1.
        +   * @param  {Boolean} [bottomCap] whether to draw the cylinder's bottom. Defaults to `true`.
        +   * @param  {Boolean} [topCap]    whether to draw the cylinder's top. Defaults to `true`.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cylinder on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cylinder.
        +   *   cylinder();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cylinder on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cylinder.
        +   *   // Set its radius and height to 30.
        +   *   cylinder(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cylinder on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cylinder.
        +   *   // Set its radius to 30 and height to 50.
        +   *   cylinder(30, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white box on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cylinder.
        +   *   // Set its radius to 30 and height to 50.
        +   *   // Set its detailX to 5.
        +   *   cylinder(30, 50, 5);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cylinder on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cylinder.
        +   *   // Set its radius to 30 and height to 50.
        +   *   // Set its detailX to 24 and detailY to 2.
        +   *   cylinder(30, 50, 24, 2);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cylinder on a gray background. Its top is missing.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cylinder.
        +   *   // Set its radius to 30 and height to 50.
        +   *   // Set its detailX to 24 and detailY to 1.
        +   *   // Don't draw its bottom.
        +   *   cylinder(30, 50, 24, 1, false);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cylinder on a gray background. Its top and bottom are missing.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cylinder.
        +   *   // Set its radius to 30 and height to 50.
        +   *   // Set its detailX to 24 and detailY to 1.
        +   *   // Don't draw its bottom or top.
        +   *   cylinder(30, 50, 24, 1, false, false);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.cylinder = function(
        +    radius = 50,
        +    height = radius,
        +    detailX = 24,
        +    detailY = 1,
        +    bottomCap = true,
        +    topCap = true
        +  ) {
        +    this._assert3d('cylinder');
        +    // p5._validateParameters('cylinder', arguments);
        +
        +    this._renderer.cylinder(radius, height, detailX, detailY, bottomCap, topCap);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Draws a cone.
        +   *
        +   * A cone is a 3D shape with triangular faces that connect a flat bottom to a
        +   * single point. Cones with few faces look like pyramids. Cones with many
        +   * faces have smooth surfaces.
        +   *
        +   * The first parameter, `radius`, is optional. If a `Number` is passed, as in
        +   * `cone(20)`, it sets the radius of the cone’s base. By default, `radius` is
        +   * 50.
        +   *
        +   * The second parameter, `height`, is also optional. If a `Number` is passed,
        +   * as in `cone(20, 30)`, it sets the cone’s height. By default, `height` is
        +   * set to the cone’s `radius`.
        +   *
        +   * The third parameter, `detailX`, is also optional. If a `Number` is passed,
        +   * as in `cone(20, 30, 5)`, it sets the number of edges used to form the
        +   * cone's base. Using more edges makes the base look more like a circle. By
        +   * default, `detailX` is 24.
        +   *
        +   * The fourth parameter, `detailY`, is also optional. If a `Number` is passed,
        +   * as in `cone(20, 30, 5, 7)`, it sets the number of triangle subdivisions to
        +   * use along the y-axis connecting the base to the tip. All 3D shapes are made
        +   * by connecting triangles to form their surfaces. By default, `detailY` is 1.
        +   *
        +   * The fifth parameter, `cap`, is also optional. If a `false` is passed, as
        +   * in `cone(20, 30, 5, 7, false)` the cone’s base won’t be drawn. By default,
        +   * `cap` is `true`.
        +   *
        +   * Note: `cone()` can only be used in WebGL mode.
        +   *
        +   * @method cone
        +   * @param  {Number}  [radius]  radius of the cone's base. Defaults to 50.
        +   * @param  {Number}  [height]  height of the cone. Defaults to the value of `radius`.
        +   * @param  {Integer} [detailX] number of edges used to draw the base. Defaults to 24.
        +   * @param  {Integer} [detailY] number of triangle subdivisions along the y-axis. Defaults to 1.
        +   * @param  {Boolean} [cap]     whether to draw the cone's base.  Defaults to `true`.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cone on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cone.
        +   *   cone();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cone on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cone.
        +   *   // Set its radius and height to 30.
        +   *   cone(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cone on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cone.
        +   *   // Set its radius to 30 and height to 50.
        +   *   cone(30, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cone on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cone.
        +   *   // Set its radius to 30 and height to 50.
        +   *   // Set its detailX to 5.
        +   *   cone(30, 50, 5);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white pyramid on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cone.
        +   *   // Set its radius to 30 and height to 50.
        +   *   // Set its detailX to 5.
        +   *   cone(30, 50, 5);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cone on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cone.
        +   *   // Set its radius to 30 and height to 50.
        +   *   // Set its detailX to 24 and detailY to 2.
        +   *   cone(30, 50, 24, 2);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cone on a gray background. Its base is missing.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the cone.
        +   *   // Set its radius to 30 and height to 50.
        +   *   // Set its detailX to 24 and detailY to 1.
        +   *   // Don't draw its base.
        +   *   cone(30, 50, 24, 1, false);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.cone = function(
        +    radius = 50,
        +    height = radius,
        +    detailX = 24,
        +    detailY = 1,
        +    cap = true
        +  ) {
        +    this._assert3d('cone');
        +    // p5._validateParameters('cone', arguments);
        +
        +    this._renderer.cone(radius, height, detailX, detailY, cap);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Draws an ellipsoid.
        +   *
        +   * An ellipsoid is a 3D shape with triangular faces that connect to form a
        +   * round surface. Ellipsoids with few faces look like crystals. Ellipsoids
        +   * with many faces have smooth surfaces and look like eggs. `ellipsoid()`
        +   * defines a shape by its radii. This is different from
        +   * <a href="#/p5/ellipse">ellipse()</a> which uses diameters
        +   * (width and height).
        +   *
        +   * The first parameter, `radiusX`, is optional. If a `Number` is passed, as in
        +   * `ellipsoid(20)`, it sets the radius of the ellipsoid along the x-axis. By
        +   * default, `radiusX` is 50.
        +   *
        +   * The second parameter, `radiusY`, is also optional. If a `Number` is passed,
        +   * as in `ellipsoid(20, 30)`, it sets the ellipsoid’s radius along the y-axis.
        +   * By default, `radiusY` is set to the ellipsoid’s `radiusX`.
        +   *
        +   * The third parameter, `radiusZ`, is also optional. If a `Number` is passed,
        +   * as in `ellipsoid(20, 30, 40)`, it sets the ellipsoid’s radius along the
        +   * z-axis. By default, `radiusZ` is set to the ellipsoid’s `radiusY`.
        +   *
        +   * The fourth parameter, `detailX`, is also optional. If a `Number` is passed,
        +   * as in `ellipsoid(20, 30, 40, 5)`, it sets the number of triangle
        +   * subdivisions to use along the x-axis. All 3D shapes are made by connecting
        +   * triangles to form their surfaces. By default, `detailX` is 24.
        +   *
        +   * The fifth parameter, `detailY`, is also optional. If a `Number` is passed,
        +   * as in `ellipsoid(20, 30, 40, 5, 7)`, it sets the number of triangle
        +   * subdivisions to use along the y-axis. All 3D shapes are made by connecting
        +   * triangles to form their surfaces. By default, `detailY` is 16.
        +   *
        +   * Note: `ellipsoid()` can only be used in WebGL mode.
        +   *
        +   * @method ellipsoid
        +   * @param  {Number} [radiusX]  radius of the ellipsoid along the x-axis. Defaults to 50.
        +   * @param  {Number} [radiusY]  radius of the ellipsoid along the y-axis. Defaults to `radiusX`.
        +   * @param  {Number} [radiusZ]  radius of the ellipsoid along the z-axis. Defaults to `radiusY`.
        +   * @param  {Integer} [detailX] number of triangle subdivisions along the x-axis. Defaults to 24.
        +   * @param  {Integer} [detailY] number of triangle subdivisions along the y-axis. Defaults to 16.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white sphere on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the ellipsoid.
        +   *   // Set its radiusX to 30.
        +   *   ellipsoid(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white ellipsoid on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the ellipsoid.
        +   *   // Set its radiusX to 30.
        +   *   // Set its radiusY to 40.
        +   *   ellipsoid(30, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white ellipsoid on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the ellipsoid.
        +   *   // Set its radiusX to 30.
        +   *   // Set its radiusY to 40.
        +   *   // Set its radiusZ to 50.
        +   *   ellipsoid(30, 40, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white ellipsoid on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the ellipsoid.
        +   *   // Set its radiusX to 30.
        +   *   // Set its radiusY to 40.
        +   *   // Set its radiusZ to 50.
        +   *   // Set its detailX to 4.
        +   *   ellipsoid(30, 40, 50, 4);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white ellipsoid on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the ellipsoid.
        +   *   // Set its radiusX to 30.
        +   *   // Set its radiusY to 40.
        +   *   // Set its radiusZ to 50.
        +   *   // Set its detailX to 4.
        +   *   // Set its detailY to 3.
        +   *   ellipsoid(30, 40, 50, 4, 3);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.ellipsoid = function(
        +    radiusX = 50,
        +    radiusY = radiusX,
        +    radiusZ = radiusX,
        +    detailX = 24,
        +    detailY = 16
        +  ) {
        +    this._assert3d('ellipsoid');
        +    // p5._validateParameters('ellipsoid', arguments);
        +
        +    this._renderer.ellipsoid(radiusX, radiusY, radiusZ, detailX, detailY);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Draws a torus.
        +   *
        +   * A torus is a 3D shape with triangular faces that connect to form a ring.
        +   * Toruses with few faces look flattened. Toruses with many faces have smooth
        +   * surfaces.
        +   *
        +   * The first parameter, `radius`, is optional. If a `Number` is passed, as in
        +   * `torus(30)`, it sets the radius of the ring. By default, `radius` is 50.
        +   *
        +   * The second parameter, `tubeRadius`, is also optional. If a `Number` is
        +   * passed, as in `torus(30, 15)`, it sets the radius of the tube. By default,
        +   * `tubeRadius` is 10.
        +   *
        +   * The third parameter, `detailX`, is also optional. If a `Number` is passed,
        +   * as in `torus(30, 15, 5)`, it sets the number of edges used to draw the hole
        +   * of the torus. Using more edges makes the hole look more like a circle. By
        +   * default, `detailX` is 24.
        +   *
        +   * The fourth parameter, `detailY`, is also optional. If a `Number` is passed,
        +   * as in `torus(30, 15, 5, 7)`, it sets the number of triangle subdivisions to
        +   * use while filling in the torus’ height. By default, `detailY` is 16.
        +   *
        +   * Note: `torus()` can only be used in WebGL mode.
        +   *
        +   * @method torus
        +   * @param  {Number} [radius]      radius of the torus. Defaults to 50.
        +   * @param  {Number} [tubeRadius]  radius of the tube. Defaults to 10.
        +   * @param  {Integer} [detailX]    number of edges that form the hole. Defaults to 24.
        +   * @param  {Integer} [detailY]    number of triangle subdivisions along the y-axis. Defaults to 16.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white torus on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the torus.
        +   *   torus();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white torus on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the torus.
        +   *   // Set its radius to 30.
        +   *   torus(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white torus on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the torus.
        +   *   // Set its radius to 30 and tubeRadius to 15.
        +   *   torus(30, 15);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white torus on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the torus.
        +   *   // Set its radius to 30 and tubeRadius to 15.
        +   *   // Set its detailX to 5.
        +   *   torus(30, 15, 5);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white torus on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the torus.
        +   *   // Set its radius to 30 and tubeRadius to 15.
        +   *   // Set its detailX to 5.
        +   *   // Set its detailY to 3.
        +   *   torus(30, 15, 5, 3);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.torus = function(radius, tubeRadius, detailX, detailY) {
        +    this._assert3d('torus');
        +    // p5._validateParameters('torus', arguments);
        +
        +    this._renderer.torus(radius, tubeRadius, detailX, detailY);
        +
        +    return this;
        +  };
        +
        +  ///////////////////////
        +  ///  2D primitives  ///
        +  ///////////////////////
        +  //
        +  // Note: Documentation is not generated on the p5.js website for functions on
        +  // the p5.RendererGL prototype.
        +
        +  /**
        +   * Draws a point, a coordinate in space at the dimension of one pixel,
        +   * given x, y and z coordinates. The color of the point is determined
        +   * by the current stroke, while the point size is determined by current
        +   * stroke weight.
        +   * @private
        +   * @param {Number} x x-coordinate of point
        +   * @param {Number} y y-coordinate of point
        +   * @param {Number} z z-coordinate of point
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *   stroke(255);
        +   *   strokeWeight(4);
        +   *   point(25, 0);
        +   *   strokeWeight(3);
        +   *   point(-25, 0);
        +   *   strokeWeight(2);
        +   *   point(0, 25);
        +   *   strokeWeight(1);
        +   *   point(0, -25);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  RendererGL.prototype.point = function(x, y, z = 0) {
        +
        +    const _vertex = [];
        +    _vertex.push(new Vector(x, y, z));
        +    this._drawPoints(_vertex, this.buffers.point);
        +
        +    return this;
        +  };
        +
        +  RendererGL.prototype.triangle = function(args) {
        +    const x1 = args[0],
        +      y1 = args[1];
        +    const x2 = args[2],
        +      y2 = args[3];
        +    const x3 = args[4],
        +      y3 = args[5];
        +
        +    const gid = 'tri';
        +    if (!this.geometryInHash(gid)) {
        +      const _triangle = function() {
        +        const vertices = [];
        +        vertices.push(new Vector(0, 0, 0));
        +        vertices.push(new Vector(1, 0, 0));
        +        vertices.push(new Vector(0, 1, 0));
        +        this.edges = [[0, 1], [1, 2], [2, 0]];
        +        this.vertices = vertices;
        +        this.faces = [[0, 1, 2]];
        +        this.uvs = [0, 0, 1, 0, 1, 1];
        +      };
        +      const triGeom = new Geometry(1, 1, _triangle, this);
        +      triGeom._edgesToVertices();
        +      triGeom.computeNormals();
        +      triGeom.gid = gid;
        +      this.geometryBufferCache.ensureCached(triGeom);
        +    }
         
        -  if (typeof detailX === 'undefined') {
        -    detailX = 24;
        -  }
        -  if (typeof detailY === 'undefined') {
        -    detailY = 16;
        -  }
        +    // only one triangle is cached, one point is at the origin, and the
        +    // two adjacent sides are tne unit vectors along the X & Y axes.
        +    //
        +    // this matrix multiplication transforms those two unit vectors
        +    // onto the required vector prior to rendering, and moves the
        +    // origin appropriately.
        +    const uModelMatrix = this.states.uModelMatrix.copy();
        +    try {
        +      // triangle orientation.
        +      const orientation = Math.sign(x1*y2-x2*y1 + x2*y3-x3*y2 + x3*y1-x1*y3);
        +      const mult = new Matrix([
        +        x2 - x1, y2 - y1, 0, 0, // the resulting unit X-axis
        +        x3 - x1, y3 - y1, 0, 0, // the resulting unit Y-axis
        +        0, 0, orientation, 0,   // the resulting unit Z-axis (Reflect the specified order of vertices)
        +        x1, y1, 0, 1            // the resulting origin
        +      ]).mult(this.states.uModelMatrix);
        +
        +      this.states.uModelMatrix = mult;
        +
        +      this._drawGeometry(this.geometryBufferCache.getGeometryByID(gid));
        +    } finally {
        +      this.states.uModelMatrix = uModelMatrix;
        +    }
         
        -  const tubeRatio = (tubeRadius / radius).toPrecision(4);
        -  const gId = `torus|${tubeRatio}|${detailX}|${detailY}`;
        -
        -  if (!this._renderer.geometryInHash(gId)) {
        -    const _torus = function() {
        -      for (let i = 0; i <= this.detailY; i++) {
        -        const v = i / this.detailY;
        -        const phi = 2 * Math.PI * v;
        -        const cosPhi = Math.cos(phi);
        -        const sinPhi = Math.sin(phi);
        -        const r = 1 + tubeRatio * cosPhi;
        -
        -        for (let j = 0; j <= this.detailX; j++) {
        -          const u = j / this.detailX;
        -          const theta = 2 * Math.PI * u;
        -          const cosTheta = Math.cos(theta);
        -          const sinTheta = Math.sin(theta);
        -
        -          const p = new p5.Vector(
        -            r * cosTheta,
        -            r * sinTheta,
        -            tubeRatio * sinPhi
        -          );
        -
        -          const n = new p5.Vector(cosPhi * cosTheta, cosPhi * sinTheta, sinPhi);
        -
        -          this.vertices.push(p);
        -          this.vertexNormals.push(n);
        -          this.uvs.push(u, v);
        -        }
        -      }
        -    };
        -    const torusGeom = new p5.Geometry(detailX, detailY, _torus);
        -    torusGeom.computeFaces();
        -    if (detailX <= 24 && detailY <= 16) {
        -      torusGeom._makeTriangleEdges()._edgesToVertices();
        -    } else if (this._renderer._doStroke) {
        -      console.log(
        -        'Cannot draw strokes on torus object with more' +
        -        ' than 24 detailX or 16 detailY'
        -      );
        +    return this;
        +  };
        +
        +  RendererGL.prototype.ellipse = function(args) {
        +    this.arc(
        +      args[0],
        +      args[1],
        +      args[2],
        +      args[3],
        +      0,
        +      constants.TWO_PI,
        +      constants.OPEN,
        +      args[4]
        +    );
        +  };
        +
        +  RendererGL.prototype.arc = function(...args) {
        +    const x = args[0];
        +    const y = args[1];
        +    const width = args[2];
        +    const height = args[3];
        +    const start = args[4];
        +    const stop = args[5];
        +    const mode = args[6];
        +    const detail = args[7] || 25;
        +
        +    let shape;
        +    let gid;
        +
        +    // check if it is an ellipse or an arc
        +    if (Math.abs(stop - start) >= constants.TWO_PI) {
        +      shape = 'ellipse';
        +      gid = `${shape}|${detail}|`;
        +    } else {
        +      shape = 'arc';
        +      gid = `${shape}|${start}|${stop}|${mode}|${detail}|`;
             }
        -    this._renderer.createBuffers(gId, torusGeom);
        -  }
        -  this._renderer.drawBuffersScaled(gId, radius, radius, radius);
         
        -  return this;
        -};
        +    if (!this.geometryInHash(gid)) {
        +      const _arc = function() {
         
        -///////////////////////
        -/// 2D primitives
        -/////////////////////////
        -//
        -// Note: Documentation is not generated on the p5.js website for functions on
        -// the p5.RendererGL prototype.
        +        // if the start and stop angles are not the same, push vertices to the array
        +        if (start.toFixed(10) !== stop.toFixed(10)) {
        +          // if the mode specified is PIE or null, push the mid point of the arc in vertices
        +          if (mode === constants.PIE || typeof mode === 'undefined') {
        +            this.vertices.push(new Vector(0.5, 0.5, 0));
        +            this.uvs.push([0.5, 0.5]);
        +          }
         
        -/**
        - * Draws a point, a coordinate in space at the dimension of one pixel,
        - * given x, y and z coordinates. The color of the point is determined
        - * by the current stroke, while the point size is determined by current
        - * stroke weight.
        - * @private
        - * @param {Number} x x-coordinate of point
        - * @param {Number} y y-coordinate of point
        - * @param {Number} z z-coordinate of point
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *   stroke(255);
        - *   strokeWeight(4);
        - *   point(25, 0);
        - *   strokeWeight(3);
        - *   point(-25, 0);
        - *   strokeWeight(2);
        - *   point(0, 25);
        - *   strokeWeight(1);
        - *   point(0, -25);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.RendererGL.prototype.point = function(x, y, z = 0) {
        -
        -  const _vertex = [];
        -  _vertex.push(new p5.Vector(x, y, z));
        -  this._drawPoints(_vertex, this.immediateMode.buffers.point);
        -
        -  return this;
        -};
        -
        -p5.RendererGL.prototype.triangle = function(args) {
        -  const x1 = args[0],
        -    y1 = args[1];
        -  const x2 = args[2],
        -    y2 = args[3];
        -  const x3 = args[4],
        -    y3 = args[5];
        -
        -  const gId = 'tri';
        -  if (!this.geometryInHash(gId)) {
        -    const _triangle = function() {
        -      const vertices = [];
        -      vertices.push(new p5.Vector(0, 0, 0));
        -      vertices.push(new p5.Vector(1, 0, 0));
        -      vertices.push(new p5.Vector(0, 1, 0));
        -      this.edges = [[0, 1], [1, 2], [2, 0]];
        -      this.vertices = vertices;
        -      this.faces = [[0, 1, 2]];
        -      this.uvs = [0, 0, 1, 0, 1, 1];
        -    };
        -    const triGeom = new p5.Geometry(1, 1, _triangle);
        -    triGeom._edgesToVertices();
        -    triGeom.computeNormals();
        -    this.createBuffers(gId, triGeom);
        -  }
        +          // vertices for the perimeter of the circle
        +          for (let i = 0; i <= detail; i++) {
        +            const u = i / detail;
        +            const theta = (stop - start) * u + start;
         
        -  // only one triangle is cached, one point is at the origin, and the
        -  // two adjacent sides are tne unit vectors along the X & Y axes.
        -  //
        -  // this matrix multiplication transforms those two unit vectors
        -  // onto the required vector prior to rendering, and moves the
        -  // origin appropriately.
        -  const uModelMatrix = this.uModelMatrix.copy();
        -  try {
        -    // triangle orientation.
        -    const orientation = Math.sign(x1*y2-x2*y1 + x2*y3-x3*y2 + x3*y1-x1*y3);
        -    const mult = new p5.Matrix([
        -      x2 - x1, y2 - y1, 0, 0, // the resulting unit X-axis
        -      x3 - x1, y3 - y1, 0, 0, // the resulting unit Y-axis
        -      0, 0, orientation, 0,   // the resulting unit Z-axis (Reflect the specified order of vertices)
        -      x1, y1, 0, 1            // the resulting origin
        -    ]).mult(this.uModelMatrix);
        -
        -    this.uModelMatrix = mult;
        -
        -    this.drawBuffers(gId);
        -  } finally {
        -    this.uModelMatrix = uModelMatrix;
        -  }
        +            const _x = 0.5 + Math.cos(theta) / 2;
        +            const _y = 0.5 + Math.sin(theta) / 2;
         
        -  return this;
        -};
        -
        -p5.RendererGL.prototype.ellipse = function(args) {
        -  this.arc(
        -    args[0],
        -    args[1],
        -    args[2],
        -    args[3],
        -    0,
        -    constants.TWO_PI,
        -    constants.OPEN,
        -    args[4]
        -  );
        -};
        -
        -p5.RendererGL.prototype.arc = function(...args) {
        -  const x = args[0];
        -  const y = args[1];
        -  const width = args[2];
        -  const height = args[3];
        -  const start = args[4];
        -  const stop = args[5];
        -  const mode = args[6];
        -  const detail = args[7] || 25;
        -
        -  let shape;
        -  let gId;
        -
        -  // check if it is an ellipse or an arc
        -  if (Math.abs(stop - start) >= constants.TWO_PI) {
        -    shape = 'ellipse';
        -    gId = `${shape}|${detail}|`;
        -  } else {
        -    shape = 'arc';
        -    gId = `${shape}|${start}|${stop}|${mode}|${detail}|`;
        -  }
        +            this.vertices.push(new Vector(_x, _y, 0));
        +            this.uvs.push([_x, _y]);
         
        -  if (!this.geometryInHash(gId)) {
        -    const _arc = function() {
        +            if (i < detail - 1) {
        +              this.faces.push([0, i + 1, i + 2]);
        +              this.edges.push([i + 1, i + 2]);
        +            }
        +          }
         
        -      // if the start and stop angles are not the same, push vertices to the array
        -      if (start.toFixed(10) !== stop.toFixed(10)) {
        -        // if the mode specified is PIE or null, push the mid point of the arc in vertices
        -        if (mode === constants.PIE || typeof mode === 'undefined') {
        -          this.vertices.push(new p5.Vector(0.5, 0.5, 0));
        -          this.uvs.push([0.5, 0.5]);
        +          // check the mode specified in order to push vertices and faces, different for each mode
        +          switch (mode) {
        +            case constants.PIE:
        +              this.faces.push([
        +                0,
        +                this.vertices.length - 2,
        +                this.vertices.length - 1
        +              ]);
        +              this.edges.push([0, 1]);
        +              this.edges.push([
        +                this.vertices.length - 2,
        +                this.vertices.length - 1
        +              ]);
        +              this.edges.push([0, this.vertices.length - 1]);
        +              break;
        +
        +            case constants.CHORD:
        +              this.edges.push([0, 1]);
        +              this.edges.push([0, this.vertices.length - 1]);
        +              break;
        +
        +            case constants.OPEN:
        +              this.edges.push([0, 1]);
        +              break;
        +
        +            default:
        +              this.faces.push([
        +                0,
        +                this.vertices.length - 2,
        +                this.vertices.length - 1
        +              ]);
        +              this.edges.push([
        +                this.vertices.length - 2,
        +                this.vertices.length - 1
        +              ]);
        +          }
                 }
        +      };
         
        -        // vertices for the perimeter of the circle
        -        for (let i = 0; i <= detail; i++) {
        -          const u = i / detail;
        -          const theta = (stop - start) * u + start;
        -
        -          const _x = 0.5 + Math.cos(theta) / 2;
        -          const _y = 0.5 + Math.sin(theta) / 2;
        +      const arcGeom = new Geometry(detail, 1, _arc, this);
        +      arcGeom.computeNormals();
         
        -          this.vertices.push(new p5.Vector(_x, _y, 0));
        -          this.uvs.push([_x, _y]);
        +      if (detail <= 50) {
        +        arcGeom._edgesToVertices(arcGeom);
        +      } else if (this.states.strokeColor) {
        +        console.log(
        +          `Cannot apply a stroke to an ${shape} with more than 50 detail`
        +        );
        +      }
         
        -          if (i < detail - 1) {
        -            this.faces.push([0, i + 1, i + 2]);
        -            this.edges.push([i + 1, i + 2]);
        -          }
        -        }
        +      arcGeom.gid = gid;
        +      this.geometryBufferCache.ensureCached(arcGeom);
        +    }
         
        -        // check the mode specified in order to push vertices and faces, different for each mode
        -        switch (mode) {
        -          case constants.PIE:
        -            this.faces.push([
        -              0,
        -              this.vertices.length - 2,
        -              this.vertices.length - 1
        -            ]);
        -            this.edges.push([0, 1]);
        -            this.edges.push([
        -              this.vertices.length - 2,
        -              this.vertices.length - 1
        -            ]);
        -            this.edges.push([0, this.vertices.length - 1]);
        -            break;
        -
        -          case constants.CHORD:
        -            this.edges.push([0, 1]);
        -            this.edges.push([0, this.vertices.length - 1]);
        -            break;
        -
        -          case constants.OPEN:
        -            this.edges.push([0, 1]);
        -            break;
        -
        -          default:
        -            this.faces.push([
        -              0,
        -              this.vertices.length - 2,
        -              this.vertices.length - 1
        -            ]);
        -            this.edges.push([
        -              this.vertices.length - 2,
        -              this.vertices.length - 1
        -            ]);
        -        }
        -      }
        -    };
        +    const uModelMatrix = this.states.uModelMatrix.copy();
         
        -    const arcGeom = new p5.Geometry(detail, 1, _arc);
        -    arcGeom.computeNormals();
        +    try {
        +      this.states.uModelMatrix.translate([x, y, 0]);
        +      this.states.uModelMatrix.scale(width, height, 1);
         
        -    if (detail <= 50) {
        -      arcGeom._edgesToVertices(arcGeom);
        -    } else if (this._doStroke) {
        -      console.log(
        -        `Cannot apply a stroke to an ${shape} with more than 50 detail`
        -      );
        +      this._drawGeometry(this.geometryBufferCache.getGeometryByID(gid));
        +    } finally {
        +      this.states.uModelMatrix = uModelMatrix;
             }
         
        -    this.createBuffers(gId, arcGeom);
        -  }
        +    return this;
        +  };
        +
        +  RendererGL.prototype.rect = function(args) {
        +    const x = args[0];
        +    const y = args[1];
        +    const width = args[2];
        +    const height = args[3];
        +
        +    if (typeof args[4] === 'undefined') {
        +      // Use the retained mode for drawing rectangle,
        +      // if args for rounding rectangle is not provided by user.
        +      const perPixelLighting = this._pInst._glAttributes.perPixelLighting;
        +      const detailX = args[4] || (perPixelLighting ? 1 : 24);
        +      const detailY = args[5] || (perPixelLighting ? 1 : 16);
        +      const gid = `rect|${detailX}|${detailY}`;
        +      if (!this.geometryInHash(gid)) {
        +        const _rect = function() {
        +          for (let i = 0; i <= this.detailY; i++) {
        +            const v = i / this.detailY;
        +            for (let j = 0; j <= this.detailX; j++) {
        +              const u = j / this.detailX;
        +              const p = new Vector(u, v, 0);
        +              this.vertices.push(p);
        +              this.uvs.push(u, v);
        +            }
        +          }
        +          // using stroke indices to avoid stroke over face(s) of rectangle
        +          if (detailX > 0 && detailY > 0) {
        +            this.edges = [
        +              [0, detailX],
        +              [detailX, (detailX + 1) * (detailY + 1) - 1],
        +              [(detailX + 1) * (detailY + 1) - 1, (detailX + 1) * detailY],
        +              [(detailX + 1) * detailY, 0]
        +            ];
        +          }
        +        };
        +        const rectGeom = new Geometry(detailX, detailY, _rect, this);
        +        rectGeom
        +          .computeFaces()
        +          .computeNormals()
        +          ._edgesToVertices();
        +        rectGeom.gid = gid;
        +        this.geometryBufferCache.ensureCached(rectGeom);
        +      }
         
        -  const uModelMatrix = this.uModelMatrix.copy();
        +      // only a single rectangle (of a given detail) is cached: a square with
        +      // opposite corners at (0,0) & (1,1).
        +      //
        +      // before rendering, this square is scaled & moved to the required location.
        +      const uModelMatrix = this.states.uModelMatrix.copy();
        +      try {
        +        this.states.uModelMatrix.translate([x, y, 0]);
        +        this.states.uModelMatrix.scale(width, height, 1);
        +
        +        this._drawGeometry(this.geometryBufferCache.getGeometryByID(gid));
        +      } finally {
        +        this.states.uModelMatrix = uModelMatrix;
        +      }
        +    } else {
        +      // Use Immediate mode to round the rectangle corner,
        +      // if args for rounding corners is provided by user
        +      let tl = args[4];
        +      let tr = typeof args[5] === 'undefined' ? tl : args[5];
        +      let br = typeof args[6] === 'undefined' ? tr : args[6];
        +      let bl = typeof args[7] === 'undefined' ? br : args[7];
        +
        +      let a = x;
        +      let b = y;
        +      let c = width;
        +      let d = height;
        +
        +      c += a;
        +      d += b;
        +
        +      if (a > c) {
        +        const temp = a;
        +        a = c;
        +        c = temp;
        +      }
         
        -  try {
        -    this.uModelMatrix.translate([x, y, 0]);
        -    this.uModelMatrix.scale(width, height, 1);
        +      if (b > d) {
        +        const temp = b;
        +        b = d;
        +        d = temp;
        +      }
         
        -    this.drawBuffers(gId);
        -  } finally {
        -    this.uModelMatrix = uModelMatrix;
        -  }
        +      const maxRounding = Math.min((c - a) / 2, (d - b) / 2);
        +      if (tl > maxRounding) tl = maxRounding;
        +      if (tr > maxRounding) tr = maxRounding;
        +      if (br > maxRounding) br = maxRounding;
        +      if (bl > maxRounding) bl = maxRounding;
        +
        +      let x1 = a;
        +      let y1 = b;
        +      let x2 = c;
        +      let y2 = d;
        +
        +      const prevMode = this.states.textureMode;
        +      this.states.textureMode = constants.NORMAL;
        +      const prevOrder = this.bezierOrder();
        +      this.bezierOrder(2);
        +      this.beginShape();
        +      const addUVs = (x, y) => [x, y, (x - x1)/width, (y - y1)/height];
        +      if (tr !== 0) {
        +        this.vertex(...addUVs(x2 - tr, y1));
        +        this.bezierVertex(...addUVs(x2, y1))
        +        this.bezierVertex(...addUVs(x2, y1 + tr));
        +      } else {
        +        this.vertex(...addUVs(x2, y1));
        +      }
        +      if (br !== 0) {
        +        this.vertex(...addUVs(x2, y2 - br));
        +        this.bezierVertex(...addUVs(x2, y2));
        +        this.bezierVertex(...addUVs(x2 - br, y2))
        +      } else {
        +        this.vertex(...addUVs(x2, y2));
        +      }
        +      if (bl !== 0) {
        +        this.vertex(...addUVs(x1 + bl, y2));
        +        this.bezierVertex(...addUVs(x1, y2));
        +        this.bezierVertex(...addUVs(x1, y2 - bl));
        +      } else {
        +        this.vertex(...addUVs(x1, y2));
        +      }
        +      if (tl !== 0) {
        +        this.vertex(...addUVs(x1, y1 + tl));
        +        this.bezierVertex(...addUVs(x1, y1));
        +        this.bezierVertex(...addUVs(x1 + tl, y1));
        +      } else {
        +        this.vertex(...addUVs(x1, y1));
        +      }
         
        -  return this;
        -};
        -
        -p5.RendererGL.prototype.rect = function(args) {
        -  const x = args[0];
        -  const y = args[1];
        -  const width = args[2];
        -  const height = args[3];
        -
        -  if (typeof args[4] === 'undefined') {
        -    // Use the retained mode for drawing rectangle,
        -    // if args for rounding rectangle is not provided by user.
        -    const perPixelLighting = this._pInst._glAttributes.perPixelLighting;
        -    const detailX = args[4] || (perPixelLighting ? 1 : 24);
        -    const detailY = args[5] || (perPixelLighting ? 1 : 16);
        -    const gId = `rect|${detailX}|${detailY}`;
        -    if (!this.geometryInHash(gId)) {
        -      const _rect = function() {
        -        for (let i = 0; i <= this.detailY; i++) {
        -          const v = i / this.detailY;
        -          for (let j = 0; j <= this.detailX; j++) {
        -            const u = j / this.detailX;
        -            const p = new p5.Vector(u, v, 0);
        -            this.vertices.push(p);
        -            this.uvs.push(u, v);
        +      this.endShape(constants.CLOSE);
        +      this.states.textureMode = prevMode;
        +      this.bezierOrder(prevOrder);
        +    }
        +    return this;
        +  };
        +
        +  /* eslint-disable max-len */
        +  RendererGL.prototype.quad = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, detailX=2, detailY=2) {
        +    /* eslint-enable max-len */
        +
        +    const gid =
        +      `quad|${x1}|${y1}|${z1}|${x2}|${y2}|${z2}|${x3}|${y3}|${z3}|${x4}|${y4}|${z4}|${detailX}|${detailY}`;
        +
        +    if (!this.geometryInHash(gid)) {
        +      const quadGeom = new Geometry(detailX, detailY, function() {
        +        //algorithm adapted from c++ to js
        +        //https://stackoverflow.com/questions/16989181/whats-the-correct-way-to-draw-a-distorted-plane-in-opengl/16993202#16993202
        +        let xRes = 1.0 / (this.detailX - 1);
        +        let yRes = 1.0 / (this.detailY - 1);
        +        for (let y = 0; y < this.detailY; y++) {
        +          for (let x = 0; x < this.detailX; x++) {
        +            let pctx = x * xRes;
        +            let pcty = y * yRes;
        +
        +            let linePt0x = (1 - pcty) * x1 + pcty * x4;
        +            let linePt0y = (1 - pcty) * y1 + pcty * y4;
        +            let linePt0z = (1 - pcty) * z1 + pcty * z4;
        +            let linePt1x = (1 - pcty) * x2 + pcty * x3;
        +            let linePt1y = (1 - pcty) * y2 + pcty * y3;
        +            let linePt1z = (1 - pcty) * z2 + pcty * z3;
        +
        +            let ptx = (1 - pctx) * linePt0x + pctx * linePt1x;
        +            let pty = (1 - pctx) * linePt0y + pctx * linePt1y;
        +            let ptz = (1 - pctx) * linePt0z + pctx * linePt1z;
        +
        +            this.vertices.push(new Vector(ptx, pty, ptz));
        +            this.uvs.push([pctx, pcty]);
                   }
                 }
        -        // using stroke indices to avoid stroke over face(s) of rectangle
        -        if (detailX > 0 && detailY > 0) {
        -          this.edges = [
        -            [0, detailX],
        -            [detailX, (detailX + 1) * (detailY + 1) - 1],
        -            [(detailX + 1) * (detailY + 1) - 1, (detailX + 1) * detailY],
        -            [(detailX + 1) * detailY, 0]
        -          ];
        +      }, this);
        +
        +      quadGeom.faces = [];
        +      for(let y = 0; y < detailY-1; y++){
        +        for(let x = 0; x < detailX-1; x++){
        +          let pt0 = x + y * detailX;
        +          let pt1 = (x + 1) + y * detailX;
        +          let pt2 = (x + 1) + (y + 1) * detailX;
        +          let pt3 = x + (y + 1) * detailX;
        +          quadGeom.faces.push([pt0, pt1, pt2]);
        +          quadGeom.faces.push([pt0, pt2, pt3]);
                 }
        -      };
        -      const rectGeom = new p5.Geometry(detailX, detailY, _rect);
        -      rectGeom
        -        .computeFaces()
        -        .computeNormals()
        -        ._edgesToVertices();
        -      this.createBuffers(gId, rectGeom);
        +      }
        +      quadGeom.computeNormals();
        +      quadGeom.edges.length = 0;
        +      const vertexOrder = [0, 2, 3, 1];
        +      for (let i = 0; i < vertexOrder.length; i++) {
        +        const startVertex = vertexOrder[i];
        +        const endVertex = vertexOrder[(i + 1) % vertexOrder.length];
        +        quadGeom.edges.push([startVertex, endVertex]);
        +      }
        +      quadGeom._edgesToVertices();
        +      quadGeom.gid = gid;
        +      this.geometryBufferCache.ensureCached(quadGeom);
             }
        -
        -    // only a single rectangle (of a given detail) is cached: a square with
        -    // opposite corners at (0,0) & (1,1).
        -    //
        -    // before rendering, this square is scaled & moved to the required location.
        -    const uModelMatrix = this.uModelMatrix.copy();
        -    try {
        -      this.uModelMatrix.translate([x, y, 0]);
        -      this.uModelMatrix.scale(width, height, 1);
        -
        -      this.drawBuffers(gId);
        -    } finally {
        -      this.uModelMatrix = uModelMatrix;
        +    this._drawGeometry(this.geometryBufferCache.getGeometryByID(gid));
        +    return this;
        +  };
        +
        +  //this implementation of bezier curve
        +  //is based on Bernstein polynomial
        +  // pretier-ignore
        +  RendererGL.prototype.bezier = function(
        +    x1,
        +    y1,
        +    z1, // x2
        +    x2, // y2
        +    y2, // x3
        +    z2, // y3
        +    x3, // x4
        +    y3, // y4
        +    z3,
        +    x4,
        +    y4,
        +    z4
        +  ) {
        +    if (arguments.length === 8) {
        +      y4 = y3;
        +      x4 = x3;
        +      y3 = z2;
        +      x3 = y2;
        +      y2 = x2;
        +      x2 = z1;
        +      z1 = z2 = z3 = z4 = 0;
        +    }
        +    // TODO: handle quadratic?
        +    const prevOrder = this.bezierOrder();
        +    this.bezierOrder(3);
        +    this.beginShape();
        +    this.vertex(x1, y1, z1);
        +    this.bezierVertex(x2, y2, z2);
        +    this.bezierVertex(x3, y3, z3);
        +    this.bezierVertex(x4, y4, z4);
        +    this.endShape();
        +  };
        +
        +  // pretier-ignore
        +  RendererGL.prototype.curve = function(
        +    x1,
        +    y1,
        +    z1, // x2
        +    x2, // y2
        +    y2, // x3
        +    z2, // y3
        +    x3, // x4
        +    y3, // y4
        +    z3,
        +    x4,
        +    y4,
        +    z4
        +  ) {
        +    if (arguments.length === 8) {
        +      x4 = x3;
        +      y4 = y3;
        +      x3 = y2;
        +      y3 = x2;
        +      x2 = z1;
        +      y2 = x2;
        +      z1 = z2 = z3 = z4 = 0;
             }
        -  } else {
        -    // Use Immediate mode to round the rectangle corner,
        -    // if args for rounding corners is provided by user
        -    let tl = args[4];
        -    let tr = typeof args[5] === 'undefined' ? tl : args[5];
        -    let br = typeof args[6] === 'undefined' ? tr : args[6];
        -    let bl = typeof args[7] === 'undefined' ? br : args[7];
        -
        -    let a = x;
        -    let b = y;
        -    let c = width;
        -    let d = height;
        -
        -    c += a;
        -    d += b;
        -
        -    if (a > c) {
        -      const temp = a;
        -      a = c;
        -      c = temp;
        +    this.beginShape();
        +    this.splineVertex(x1, y1, z1);
        +    this.splineVertex(x2, y2, z2);
        +    this.splineVertex(x3, y3, z3);
        +    this.splineVertex(x4, y4, z4);
        +    this.endShape();
        +  };
        +
        +  /**
        +   * Draw a line given two points
        +   * @private
        +   * @param {Number} x0 x-coordinate of first vertex
        +   * @param {Number} y0 y-coordinate of first vertex
        +   * @param {Number} z0 z-coordinate of first vertex
        +   * @param {Number} x1 x-coordinate of second vertex
        +   * @param {Number} y1 y-coordinate of second vertex
        +   * @param {Number} z1 z-coordinate of second vertex
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * //draw a line
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *   // Use fill instead of stroke to change the color of shape.
        +   *   fill(255, 0, 0);
        +   *   line(10, 10, 0, 60, 60, 20);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  RendererGL.prototype.line = function(...args) {
        +    if (args.length === 6) {
        +      // TODO shapes refactor
        +      this.beginShape(constants.LINES);
        +      this.vertex(args[0], args[1], args[2]);
        +      this.vertex(args[3], args[4], args[5]);
        +      this.endShape();
        +    } else if (args.length === 4) {
        +      this.beginShape(constants.LINES);
        +      this.vertex(args[0], args[1], 0);
        +      this.vertex(args[2], args[3], 0);
        +      this.endShape();
             }
        -
        -    if (b > d) {
        -      const temp = b;
        -      b = d;
        -      d = temp;
        +    return this;
        +  };
        +
        +  RendererGL.prototype.image = function(
        +    img,
        +    sx,
        +    sy,
        +    sWidth,
        +    sHeight,
        +    dx,
        +    dy,
        +    dWidth,
        +    dHeight
        +  ) {
        +    // console.log(arguments);
        +    if (this._isErasing) {
        +      this.blendMode(this._cachedBlendMode);
             }
         
        -    const maxRounding = Math.min((c - a) / 2, (d - b) / 2);
        -    if (tl > maxRounding) tl = maxRounding;
        -    if (tr > maxRounding) tr = maxRounding;
        -    if (br > maxRounding) br = maxRounding;
        -    if (bl > maxRounding) bl = maxRounding;
        +    this.push();
        +    this.noLights();
        +    this.states.strokeColor = null;;
         
        -    let x1 = a;
        -    let y1 = b;
        -    let x2 = c;
        -    let y2 = d;
        +    this.texture(img);
        +    this.states.textureMode = constants.NORMAL;
         
        -    this.beginShape();
        -    if (tr !== 0) {
        -      this.vertex(x2 - tr, y1);
        -      this.quadraticVertex(x2, y1, x2, y1 + tr);
        -    } else {
        -      this.vertex(x2, y1);
        -    }
        -    if (br !== 0) {
        -      this.vertex(x2, y2 - br);
        -      this.quadraticVertex(x2, y2, x2 - br, y2);
        -    } else {
        -      this.vertex(x2, y2);
        +    let u0 = 0;
        +    if (sx <= img.width) {
        +      u0 = sx / img.width;
             }
        -    if (bl !== 0) {
        -      this.vertex(x1 + bl, y2);
        -      this.quadraticVertex(x1, y2, x1, y2 - bl);
        -    } else {
        -      this.vertex(x1, y2);
        +
        +    let u1 = 1;
        +    if (sx + sWidth <= img.width) {
        +      u1 = (sx + sWidth) / img.width;
             }
        -    if (tl !== 0) {
        -      this.vertex(x1, y1 + tl);
        -      this.quadraticVertex(x1, y1, x1 + tl, y1);
        -    } else {
        -      this.vertex(x1, y1);
        +
        +    let v0 = 0;
        +    if (sy <= img.height) {
        +      v0 = sy / img.height;
             }
         
        -    this.immediateMode.geometry.uvs.length = 0;
        -    for (const vert of this.immediateMode.geometry.vertices) {
        -      const u = (vert.x - x1) / width;
        -      const v = (vert.y - y1) / height;
        -      this.immediateMode.geometry.uvs.push(u, v);
        +    let v1 = 1;
        +    if (sy + sHeight <= img.height) {
        +      v1 = (sy + sHeight) / img.height;
             }
         
        +    this._drawingImage = true;
        +    this.beginShape();
        +    this.vertex(dx, dy, 0, u0, v0);
        +    this.vertex(dx + dWidth, dy, 0, u1, v0);
        +    this.vertex(dx + dWidth, dy + dHeight, 0, u1, v1);
        +    this.vertex(dx, dy + dHeight, 0, u0, v1);
             this.endShape(constants.CLOSE);
        -  }
        -  return this;
        -};
        -
        -/* eslint-disable max-len */
        -p5.RendererGL.prototype.quad = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, detailX=2, detailY=2) {
        -  /* eslint-enable max-len */
        -
        -  const gId =
        -    `quad|${x1}|${y1}|${z1}|${x2}|${y2}|${z2}|${x3}|${y3}|${z3}|${x4}|${y4}|${z4}|${detailX}|${detailY}`;
        -
        -  if (!this.geometryInHash(gId)) {
        -    const quadGeom = new p5.Geometry(detailX, detailY, function() {
        -      //algorithm adapted from c++ to js
        -      //https://stackoverflow.com/questions/16989181/whats-the-correct-way-to-draw-a-distorted-plane-in-opengl/16993202#16993202
        -      let xRes = 1.0 / (this.detailX - 1);
        -      let yRes = 1.0 / (this.detailY - 1);
        -      for (let y = 0; y < this.detailY; y++) {
        -        for (let x = 0; x < this.detailX; x++) {
        -          let pctx = x * xRes;
        -          let pcty = y * yRes;
        -
        -          let linePt0x = (1 - pcty) * x1 + pcty * x4;
        -          let linePt0y = (1 - pcty) * y1 + pcty * y4;
        -          let linePt0z = (1 - pcty) * z1 + pcty * z4;
        -          let linePt1x = (1 - pcty) * x2 + pcty * x3;
        -          let linePt1y = (1 - pcty) * y2 + pcty * y3;
        -          let linePt1z = (1 - pcty) * z2 + pcty * z3;
        -
        -          let ptx = (1 - pctx) * linePt0x + pctx * linePt1x;
        -          let pty = (1 - pctx) * linePt0y + pctx * linePt1y;
        -          let ptz = (1 - pctx) * linePt0z + pctx * linePt1z;
        -
        -          this.vertices.push(new p5.Vector(ptx, pty, ptz));
        -          this.uvs.push([pctx, pcty]);
        -        }
        +    this._drawingImage = false;
        +
        +    this.pop();
        +
        +    if (this._isErasing) {
        +      this.blendMode(constants.REMOVE);
        +    }
        +  };
        +
        +  ///////////////////////
        +  ///  3D primitives  ///
        +  ///////////////////////
        +  /**
        +   * @private
        +   * Helper function for creating both cones and cylinders
        +   * Will only generate well-defined geometry when bottomRadius, height > 0
        +   * and topRadius >= 0
        +   * If topRadius == 0, topCap should be false
        +   */
        +  const _truncatedCone = function(
        +    bottomRadius,
        +    topRadius,
        +    height,
        +    detailX,
        +    detailY,
        +    bottomCap,
        +    topCap
        +  ) {
        +    bottomRadius = bottomRadius <= 0 ? 1 : bottomRadius;
        +    topRadius = topRadius < 0 ? 0 : topRadius;
        +    height = height <= 0 ? bottomRadius : height;
        +    detailX = detailX < 3 ? 3 : detailX;
        +    detailY = detailY < 1 ? 1 : detailY;
        +    bottomCap = bottomCap === undefined ? true : bottomCap;
        +    topCap = topCap === undefined ? topRadius !== 0 : topCap;
        +    const start = bottomCap ? -2 : 0;
        +    const end = detailY + (topCap ? 2 : 0);
        +    //ensure constant slant for interior vertex normals
        +    const slant = Math.atan2(bottomRadius - topRadius, height);
        +    const sinSlant = Math.sin(slant);
        +    const cosSlant = Math.cos(slant);
        +    let yy, ii, jj;
        +    for (yy = start; yy <= end; ++yy) {
        +      let v = yy / detailY;
        +      let y = height * v;
        +      let ringRadius;
        +      if (yy < 0) {
        +        //for the bottomCap edge
        +        y = 0;
        +        v = 0;
        +        ringRadius = bottomRadius;
        +      } else if (yy > detailY) {
        +        //for the topCap edge
        +        y = height;
        +        v = 1;
        +        ringRadius = topRadius;
        +      } else {
        +        //for the middle
        +        ringRadius = bottomRadius + (topRadius - bottomRadius) * v;
               }
        -    });
        -
        -    quadGeom.faces = [];
        -    for(let y = 0; y < detailY-1; y++){
        -      for(let x = 0; x < detailX-1; x++){
        -        let pt0 = x + y * detailX;
        -        let pt1 = (x + 1) + y * detailX;
        -        let pt2 = (x + 1) + (y + 1) * detailX;
        -        let pt3 = x + (y + 1) * detailX;
        -        quadGeom.faces.push([pt0, pt1, pt2]);
        -        quadGeom.faces.push([pt0, pt2, pt3]);
        +      if (yy === -2 || yy === detailY + 2) {
        +        //center of bottom or top caps
        +        ringRadius = 0;
               }
        -    }
        -    quadGeom.computeNormals();
        -    quadGeom.edges.length = 0;
        -    const vertexOrder = [0, 2, 3, 1];
        -    for (let i = 0; i < vertexOrder.length; i++) {
        -      const startVertex = vertexOrder[i];
        -      const endVertex = vertexOrder[(i + 1) % vertexOrder.length];
        -      quadGeom.edges.push([startVertex, endVertex]);
        -    }
        -    quadGeom._edgesToVertices();
        -    this.createBuffers(gId, quadGeom);
        -  }
        -  this.drawBuffers(gId);
        -  return this;
        -};
        -
        -//this implementation of bezier curve
        -//is based on Bernstein polynomial
        -// pretier-ignore
        -p5.RendererGL.prototype.bezier = function(
        -  x1,
        -  y1,
        -  z1, // x2
        -  x2, // y2
        -  y2, // x3
        -  z2, // y3
        -  x3, // x4
        -  y3, // y4
        -  z3,
        -  x4,
        -  y4,
        -  z4
        -) {
        -  if (arguments.length === 8) {
        -    y4 = y3;
        -    x4 = x3;
        -    y3 = z2;
        -    x3 = y2;
        -    y2 = x2;
        -    x2 = z1;
        -    z1 = z2 = z3 = z4 = 0;
        -  }
        -  const bezierDetail = this._pInst._bezierDetail || 20; //value of Bezier detail
        -  this.beginShape();
        -  for (let i = 0; i <= bezierDetail; i++) {
        -    const c1 = Math.pow(1 - i / bezierDetail, 3);
        -    const c2 = 3 * (i / bezierDetail) * Math.pow(1 - i / bezierDetail, 2);
        -    const c3 = 3 * Math.pow(i / bezierDetail, 2) * (1 - i / bezierDetail);
        -    const c4 = Math.pow(i / bezierDetail, 3);
        -    this.vertex(
        -      x1 * c1 + x2 * c2 + x3 * c3 + x4 * c4,
        -      y1 * c1 + y2 * c2 + y3 * c3 + y4 * c4,
        -      z1 * c1 + z2 * c2 + z3 * c3 + z4 * c4
        -    );
        -  }
        -  this.endShape();
        -  return this;
        -};
        -
        -// pretier-ignore
        -p5.RendererGL.prototype.curve = function(
        -  x1,
        -  y1,
        -  z1, // x2
        -  x2, // y2
        -  y2, // x3
        -  z2, // y3
        -  x3, // x4
        -  y3, // y4
        -  z3,
        -  x4,
        -  y4,
        -  z4
        -) {
        -  if (arguments.length === 8) {
        -    x4 = x3;
        -    y4 = y3;
        -    x3 = y2;
        -    y3 = x2;
        -    x2 = z1;
        -    y2 = x2;
        -    z1 = z2 = z3 = z4 = 0;
        -  }
        -  const curveDetail = this._pInst._curveDetail;
        -  this.beginShape();
        -  for (let i = 0; i <= curveDetail; i++) {
        -    const c1 = Math.pow(i / curveDetail, 3) * 0.5;
        -    const c2 = Math.pow(i / curveDetail, 2) * 0.5;
        -    const c3 = i / curveDetail * 0.5;
        -    const c4 = 0.5;
        -    const vx =
        -      c1 * (-x1 + 3 * x2 - 3 * x3 + x4) +
        -      c2 * (2 * x1 - 5 * x2 + 4 * x3 - x4) +
        -      c3 * (-x1 + x3) +
        -      c4 * (2 * x2);
        -    const vy =
        -      c1 * (-y1 + 3 * y2 - 3 * y3 + y4) +
        -      c2 * (2 * y1 - 5 * y2 + 4 * y3 - y4) +
        -      c3 * (-y1 + y3) +
        -      c4 * (2 * y2);
        -    const vz =
        -      c1 * (-z1 + 3 * z2 - 3 * z3 + z4) +
        -      c2 * (2 * z1 - 5 * z2 + 4 * z3 - z4) +
        -      c3 * (-z1 + z3) +
        -      c4 * (2 * z2);
        -    this.vertex(vx, vy, vz);
        -  }
        -  this.endShape();
        -  return this;
        -};
         
        -/**
        - * Draw a line given two points
        - * @private
        - * @param {Number} x0 x-coordinate of first vertex
        - * @param {Number} y0 y-coordinate of first vertex
        - * @param {Number} z0 z-coordinate of first vertex
        - * @param {Number} x1 x-coordinate of second vertex
        - * @param {Number} y1 y-coordinate of second vertex
        - * @param {Number} z1 z-coordinate of second vertex
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * //draw a line
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   // Use fill instead of stroke to change the color of shape.
        - *   fill(255, 0, 0);
        - *   line(10, 10, 0, 60, 60, 20);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.RendererGL.prototype.line = function(...args) {
        -  if (args.length === 6) {
        -    this.beginShape(constants.LINES);
        -    this.vertex(args[0], args[1], args[2]);
        -    this.vertex(args[3], args[4], args[5]);
        -    this.endShape();
        -  } else if (args.length === 4) {
        -    this.beginShape(constants.LINES);
        -    this.vertex(args[0], args[1], 0);
        -    this.vertex(args[2], args[3], 0);
        -    this.endShape();
        -  }
        -  return this;
        -};
        -
        -p5.RendererGL.prototype.bezierVertex = function(...args) {
        -  if (this.immediateMode._bezierVertex.length === 0) {
        -    throw Error('vertex() must be used once before calling bezierVertex()');
        -  } else {
        -    let w_x = [];
        -    let w_y = [];
        -    let w_z = [];
        -    let t, _x, _y, _z, i, k, m;
        -    // variable i for bezierPoints, k for components, and m for anchor points.
        -    const argLength = args.length;
        -
        -    t = 0;
        -
        -    if (
        -      this._lookUpTableBezier.length === 0 ||
        -      this._lutBezierDetail !== this._pInst._curveDetail
        -    ) {
        -      this._lookUpTableBezier = [];
        -      this._lutBezierDetail = this._pInst._curveDetail;
        -      const step = 1 / this._lutBezierDetail;
        -      let start = step;
        -      let end = 1;
        -      let j = 0;
        -      while (start < 1) {
        -        t = parseFloat(start.toFixed(6));
        -        this._lookUpTableBezier[j] = this._bezierCoefficients(t);
        -        if (end.toFixed(6) === step.toFixed(6)) {
        -          t = parseFloat(end.toFixed(6)) + parseFloat(start.toFixed(6));
        -          ++j;
        -          this._lookUpTableBezier[j] = this._bezierCoefficients(t);
        -          break;
        +      y -= height / 2; //shift coordiate origin to the center of object
        +      for (ii = 0; ii < detailX; ++ii) {
        +        const u = ii / (detailX - 1);
        +        const ur = 2 * Math.PI * u;
        +        const sur = Math.sin(ur);
        +        const cur = Math.cos(ur);
        +
        +        //VERTICES
        +        this.vertices.push(new Vector(sur * ringRadius, y, cur * ringRadius));
        +
        +        //VERTEX NORMALS
        +        let vertexNormal;
        +        if (yy < 0) {
        +          vertexNormal = new Vector(0, -1, 0);
        +        } else if (yy > detailY && topRadius) {
        +          vertexNormal = new Vector(0, 1, 0);
        +        } else {
        +          vertexNormal = new Vector(sur * cosSlant, sinSlant, cur * cosSlant);
                 }
        -        start += step;
        -        end -= step;
        -        ++j;
        +        this.vertexNormals.push(vertexNormal);
        +        //UVs
        +        this.uvs.push(u, v);
               }
             }
         
        -    const LUTLength = this._lookUpTableBezier.length;
        -
        -    // fillColors[0]: start point color
        -    // fillColors[1],[2]: control point color
        -    // fillColors[3]: end point color
        -    const fillColors = [];
        -    for (m = 0; m < 4; m++) fillColors.push([]);
        -    fillColors[0] = this.immediateMode.geometry.vertexColors.slice(-4);
        -    fillColors[3] = this.curFillColor.slice();
        -
        -    // Do the same for strokeColor.
        -    const strokeColors = [];
        -    for (m = 0; m < 4; m++) strokeColors.push([]);
        -    strokeColors[0] = this.immediateMode.geometry.vertexStrokeColors.slice(-4);
        -    strokeColors[3] = this.curStrokeColor.slice();
        -
        -    if (argLength === 6) {
        -      this.isBezier = true;
        -
        -      w_x = [this.immediateMode._bezierVertex[0], args[0], args[2], args[4]];
        -      w_y = [this.immediateMode._bezierVertex[1], args[1], args[3], args[5]];
        -      // The ratio of the distance between the start point, the two control-
        -      // points, and the end point determines the intermediate color.
        -      let d0 = Math.hypot(w_x[0]-w_x[1], w_y[0]-w_y[1]);
        -      let d1 = Math.hypot(w_x[1]-w_x[2], w_y[1]-w_y[2]);
        -      let d2 = Math.hypot(w_x[2]-w_x[3], w_y[2]-w_y[3]);
        -      const totalLength = d0 + d1 + d2;
        -      d0 /= totalLength;
        -      d2 /= totalLength;
        -      for (k = 0; k < 4; k++) {
        -        fillColors[1].push(
        -          fillColors[0][k] * (1-d0) + fillColors[3][k] * d0
        -        );
        -        fillColors[2].push(
        -          fillColors[0][k] * d2 + fillColors[3][k] * (1-d2)
        -        );
        -        strokeColors[1].push(
        -          strokeColors[0][k] * (1-d0) + strokeColors[3][k] * d0
        -        );
        -        strokeColors[2].push(
        -          strokeColors[0][k] * d2 + strokeColors[3][k] * (1-d2)
        -        );
        +    let startIndex = 0;
        +    if (bottomCap) {
        +      for (jj = 0; jj < detailX; ++jj) {
        +        const nextjj = (jj + 1) % detailX;
        +        this.faces.push([
        +          startIndex + jj,
        +          startIndex + detailX + nextjj,
        +          startIndex + detailX + jj
        +        ]);
               }
        -
        -      for (i = 0; i < LUTLength; i++) {
        -        // Interpolate colors using control points
        -        this.curFillColor = [0, 0, 0, 0];
        -        this.curStrokeColor = [0, 0, 0, 0];
        -        _x = _y = 0;
        -        for (m = 0; m < 4; m++) {
        -          for (k = 0; k < 4; k++) {
        -            this.curFillColor[k] +=
        -              this._lookUpTableBezier[i][m] * fillColors[m][k];
        -            this.curStrokeColor[k] +=
        -              this._lookUpTableBezier[i][m] * strokeColors[m][k];
        -          }
        -          _x += w_x[m] * this._lookUpTableBezier[i][m];
        -          _y += w_y[m] * this._lookUpTableBezier[i][m];
        -        }
        -        this.vertex(_x, _y);
        +      startIndex += detailX * 2;
        +    }
        +    for (yy = 0; yy < detailY; ++yy) {
        +      for (ii = 0; ii < detailX; ++ii) {
        +        const nextii = (ii + 1) % detailX;
        +        this.faces.push([
        +          startIndex + ii,
        +          startIndex + nextii,
        +          startIndex + detailX + nextii
        +        ]);
        +        this.faces.push([
        +          startIndex + ii,
        +          startIndex + detailX + nextii,
        +          startIndex + detailX + ii
        +        ]);
               }
        -      // so that we leave currentColor with the last value the user set it to
        -      this.curFillColor = fillColors[3];
        -      this.curStrokeColor = strokeColors[3];
        -      this.immediateMode._bezierVertex[0] = args[4];
        -      this.immediateMode._bezierVertex[1] = args[5];
        -    } else if (argLength === 9) {
        -      this.isBezier = true;
        -
        -      w_x = [this.immediateMode._bezierVertex[0], args[0], args[3], args[6]];
        -      w_y = [this.immediateMode._bezierVertex[1], args[1], args[4], args[7]];
        -      w_z = [this.immediateMode._bezierVertex[2], args[2], args[5], args[8]];
        -      // The ratio of the distance between the start point, the two control-
        -      // points, and the end point determines the intermediate color.
        -      let d0 = Math.hypot(w_x[0]-w_x[1], w_y[0]-w_y[1], w_z[0]-w_z[1]);
        -      let d1 = Math.hypot(w_x[1]-w_x[2], w_y[1]-w_y[2], w_z[1]-w_z[2]);
        -      let d2 = Math.hypot(w_x[2]-w_x[3], w_y[2]-w_y[3], w_z[2]-w_z[3]);
        -      const totalLength = d0 + d1 + d2;
        -      d0 /= totalLength;
        -      d2 /= totalLength;
        -      for (k = 0; k < 4; k++) {
        -        fillColors[1].push(
        -          fillColors[0][k] * (1-d0) + fillColors[3][k] * d0
        -        );
        -        fillColors[2].push(
        -          fillColors[0][k] * d2 + fillColors[3][k] * (1-d2)
        -        );
        -        strokeColors[1].push(
        -          strokeColors[0][k] * (1-d0) + strokeColors[3][k] * d0
        -        );
        -        strokeColors[2].push(
        -          strokeColors[0][k] * d2 + strokeColors[3][k] * (1-d2)
        -        );
        +      startIndex += detailX;
        +    }
        +    if (topCap) {
        +      startIndex += detailX;
        +      for (ii = 0; ii < detailX; ++ii) {
        +        this.faces.push([
        +          startIndex + ii,
        +          startIndex + (ii + 1) % detailX,
        +          startIndex + detailX
        +        ]);
               }
        -      for (i = 0; i < LUTLength; i++) {
        -        // Interpolate colors using control points
        -        this.curFillColor = [0, 0, 0, 0];
        -        this.curStrokeColor = [0, 0, 0, 0];
        -        _x = _y = _z = 0;
        -        for (m = 0; m < 4; m++) {
        -          for (k = 0; k < 4; k++) {
        -            this.curFillColor[k] +=
        -              this._lookUpTableBezier[i][m] * fillColors[m][k];
        -            this.curStrokeColor[k] +=
        -              this._lookUpTableBezier[i][m] * strokeColors[m][k];
        +    }
        +  };
        +
        +  RendererGL.prototype.plane = function(
        +    width = 50,
        +    height = width,
        +    detailX = 1,
        +    detailY = 1
        +  ) {
        +    const gid = `plane|${detailX}|${detailY}`;
        +
        +    if (!this.geometryInHash(gid)) {
        +      const _plane = function() {
        +        let u, v, p;
        +        for (let i = 0; i <= this.detailY; i++) {
        +          v = i / this.detailY;
        +          for (let j = 0; j <= this.detailX; j++) {
        +            u = j / this.detailX;
        +            p = new Vector(u - 0.5, v - 0.5, 0);
        +            this.vertices.push(p);
        +            this.uvs.push(u, v);
                   }
        -          _x += w_x[m] * this._lookUpTableBezier[i][m];
        -          _y += w_y[m] * this._lookUpTableBezier[i][m];
        -          _z += w_z[m] * this._lookUpTableBezier[i][m];
                 }
        -        this.vertex(_x, _y, _z);
        +      };
        +      const planeGeom = new Geometry(detailX, detailY, _plane, this);
        +      planeGeom.computeFaces().computeNormals();
        +      if (detailX <= 1 && detailY <= 1) {
        +        planeGeom._makeTriangleEdges()._edgesToVertices();
        +      } else if (this.states.strokeColor) {
        +        console.log(
        +          'Cannot draw stroke on plane objects with more' +
        +          ' than 1 detailX or 1 detailY'
        +        );
               }
        -      // so that we leave currentColor with the last value the user set it to
        -      this.curFillColor = fillColors[3];
        -      this.curStrokeColor = strokeColors[3];
        -      this.immediateMode._bezierVertex[0] = args[6];
        -      this.immediateMode._bezierVertex[1] = args[7];
        -      this.immediateMode._bezierVertex[2] = args[8];
        +      planeGeom.gid = gid;
        +      this.geometryBufferCache.ensureCached(planeGeom);
             }
        +
        +    this._drawGeometryScaled(this.geometryBufferCache.getGeometryByID(gid), width, height, 1);
           }
        -};
        -
        -p5.RendererGL.prototype.quadraticVertex = function(...args) {
        -  if (this.immediateMode._quadraticVertex.length === 0) {
        -    throw Error('vertex() must be used once before calling quadraticVertex()');
        -  } else {
        -    let w_x = [];
        -    let w_y = [];
        -    let w_z = [];
        -    let t, _x, _y, _z, i, k, m;
        -    // variable i for bezierPoints, k for components, and m for anchor points.
        -    const argLength = args.length;
        -
        -    t = 0;
        -
        -    if (
        -      this._lookUpTableQuadratic.length === 0 ||
        -      this._lutQuadraticDetail !== this._pInst._curveDetail
        -    ) {
        -      this._lookUpTableQuadratic = [];
        -      this._lutQuadraticDetail = this._pInst._curveDetail;
        -      const step = 1 / this._lutQuadraticDetail;
        -      let start = step;
        -      let end = 1;
        -      let j = 0;
        -      while (start < 1) {
        -        t = parseFloat(start.toFixed(6));
        -        this._lookUpTableQuadratic[j] = this._quadraticCoefficients(t);
        -        if (end.toFixed(6) === step.toFixed(6)) {
        -          t = parseFloat(end.toFixed(6)) + parseFloat(start.toFixed(6));
        -          ++j;
        -          this._lookUpTableQuadratic[j] = this._quadraticCoefficients(t);
        -          break;
        -        }
        -        start += step;
        -        end -= step;
        -        ++j;
        -      }
        +
        +  RendererGL.prototype.box = function(
        +    width = 50,
        +    height = width,
        +    depth = height,
        +    detailX,
        +    detailY
        +  ){
        +    const perPixelLighting =
        +      this.attributes && this.attributes.perPixelLighting;
        +    if (typeof detailX === 'undefined') {
        +      detailX = perPixelLighting ? 1 : 4;
        +    }
        +    if (typeof detailY === 'undefined') {
        +      detailY = perPixelLighting ? 1 : 4;
             }
         
        -    const LUTLength = this._lookUpTableQuadratic.length;
        -
        -    // fillColors[0]: start point color
        -    // fillColors[1]: control point color
        -    // fillColors[2]: end point color
        -    const fillColors = [];
        -    for (m = 0; m < 3; m++) fillColors.push([]);
        -    fillColors[0] = this.immediateMode.geometry.vertexColors.slice(-4);
        -    fillColors[2] = this.curFillColor.slice();
        -
        -    // Do the same for strokeColor.
        -    const strokeColors = [];
        -    for (m = 0; m < 3; m++) strokeColors.push([]);
        -    strokeColors[0] = this.immediateMode.geometry.vertexStrokeColors.slice(-4);
        -    strokeColors[2] = this.curStrokeColor.slice();
        -
        -    if (argLength === 4) {
        -      this.isQuadratic = true;
        -
        -      w_x = [this.immediateMode._quadraticVertex[0], args[0], args[2]];
        -      w_y = [this.immediateMode._quadraticVertex[1], args[1], args[3]];
        -
        -      // The ratio of the distance between the start point, the control-
        -      // point, and the end point determines the intermediate color.
        -      let d0 = Math.hypot(w_x[0]-w_x[1], w_y[0]-w_y[1]);
        -      let d1 = Math.hypot(w_x[1]-w_x[2], w_y[1]-w_y[2]);
        -      const totalLength = d0 + d1;
        -      d0 /= totalLength;
        -      for (k = 0; k < 4; k++) {
        -        fillColors[1].push(
        -          fillColors[0][k] * (1-d0) + fillColors[2][k] * d0
        -        );
        -        strokeColors[1].push(
        -          strokeColors[0][k] * (1-d0) + strokeColors[2][k] * d0
        +    const gid = `box|${detailX}|${detailY}`;
        +    if (!this.geometryInHash(gid)) {
        +      const _box = function() {
        +        const cubeIndices = [
        +          [0, 4, 2, 6], // -1, 0, 0],// -x
        +          [1, 3, 5, 7], // +1, 0, 0],// +x
        +          [0, 1, 4, 5], // 0, -1, 0],// -y
        +          [2, 6, 3, 7], // 0, +1, 0],// +y
        +          [0, 2, 1, 3], // 0, 0, -1],// -z
        +          [4, 5, 6, 7] // 0, 0, +1] // +z
        +        ];
        +        //using custom edges
        +        //to avoid diagonal stroke lines across face of box
        +        this.edges = [
        +          [0, 1],
        +          [1, 3],
        +          [3, 2],
        +          [6, 7],
        +          [8, 9],
        +          [9, 11],
        +          [14, 15],
        +          [16, 17],
        +          [17, 19],
        +          [18, 19],
        +          [20, 21],
        +          [22, 23]
        +        ];
        +
        +        cubeIndices.forEach((cubeIndex, i) => {
        +          const v = i * 4;
        +          for (let j = 0; j < 4; j++) {
        +            const d = cubeIndex[j];
        +            //inspired by lightgl:
        +            //https://github.com/evanw/lightgl.js
        +            //octants:https://en.wikipedia.org/wiki/Octant_(solid_geometry)
        +            const octant = new Vector(
        +              ((d & 1) * 2 - 1) / 2,
        +              ((d & 2) - 1) / 2,
        +              ((d & 4) / 2 - 1) / 2
        +            );
        +            this.vertices.push(octant);
        +            this.uvs.push(j & 1, (j & 2) / 2);
        +          }
        +          this.faces.push([v, v + 1, v + 2]);
        +          this.faces.push([v + 2, v + 1, v + 3]);
        +        });
        +      };
        +      const boxGeom = new Geometry(detailX, detailY, _box, this);
        +      boxGeom.computeNormals();
        +      if (detailX <= 4 && detailY <= 4) {
        +        boxGeom._edgesToVertices();
        +      } else if (this.states.strokeColor) {
        +        console.log(
        +          'Cannot draw stroke on box objects with more' +
        +          ' than 4 detailX or 4 detailY'
                 );
               }
        +      //initialize our geometry buffer with
        +      //the key val pair:
        +      //geometry Id, Geom object
        +      boxGeom.gid = gid;
        +      this.geometryBufferCache.ensureCached(boxGeom);
        +    }
        +    this._drawGeometryScaled(this.geometryBufferCache.getGeometryByID(gid), width, height, depth);
        +  }
         
        -      for (i = 0; i < LUTLength; i++) {
        -        // Interpolate colors using control points
        -        this.curFillColor = [0, 0, 0, 0];
        -        this.curStrokeColor = [0, 0, 0, 0];
        -        _x = _y = 0;
        -        for (m = 0; m < 3; m++) {
        -          for (k = 0; k < 4; k++) {
        -            this.curFillColor[k] +=
        -              this._lookUpTableQuadratic[i][m] * fillColors[m][k];
        -            this.curStrokeColor[k] +=
        -              this._lookUpTableQuadratic[i][m] * strokeColors[m][k];
        -          }
        -          _x += w_x[m] * this._lookUpTableQuadratic[i][m];
        -          _y += w_y[m] * this._lookUpTableQuadratic[i][m];
        -        }
        -        this.vertex(_x, _y);
        -      }
        +  RendererGL.prototype.sphere = function(
        +    radius = 50,
        +    detailX = 24,
        +    detailY = 16
        +  ) {
        +    this.ellipsoid(radius, radius, radius, detailX, detailY);
        +  }
         
        -      // so that we leave currentColor with the last value the user set it to
        -      this.curFillColor = fillColors[2];
        -      this.curStrokeColor = strokeColors[2];
        -      this.immediateMode._quadraticVertex[0] = args[2];
        -      this.immediateMode._quadraticVertex[1] = args[3];
        -    } else if (argLength === 6) {
        -      this.isQuadratic = true;
        -
        -      w_x = [this.immediateMode._quadraticVertex[0], args[0], args[3]];
        -      w_y = [this.immediateMode._quadraticVertex[1], args[1], args[4]];
        -      w_z = [this.immediateMode._quadraticVertex[2], args[2], args[5]];
        -
        -      // The ratio of the distance between the start point, the control-
        -      // point, and the end point determines the intermediate color.
        -      let d0 = Math.hypot(w_x[0]-w_x[1], w_y[0]-w_y[1], w_z[0]-w_z[1]);
        -      let d1 = Math.hypot(w_x[1]-w_x[2], w_y[1]-w_y[2], w_z[1]-w_z[2]);
        -      const totalLength = d0 + d1;
        -      d0 /= totalLength;
        -      for (k = 0; k < 4; k++) {
        -        fillColors[1].push(
        -          fillColors[0][k] * (1-d0) + fillColors[2][k] * d0
        -        );
        -        strokeColors[1].push(
        -          strokeColors[0][k] * (1-d0) + strokeColors[2][k] * d0
        -        );
        -      }
        +  RendererGL.prototype.ellipsoid = function(
        +    radiusX = 50,
        +    radiusY = radiusX,
        +    radiusZ = radiusX,
        +    detailX = 24,
        +    detailY = 16
        +  ) {
        +    const gid = `ellipsoid|${detailX}|${detailY}`;
        +
        +    if (!this.geometryInHash(gid)) {
        +      const _ellipsoid = function() {
        +        for (let i = 0; i <= this.detailY; i++) {
        +          const v = i / this.detailY;
        +          const phi = Math.PI * v - Math.PI / 2;
        +          const cosPhi = Math.cos(phi);
        +          const sinPhi = Math.sin(phi);
         
        -      for (i = 0; i < LUTLength; i++) {
        -        // Interpolate colors using control points
        -        this.curFillColor = [0, 0, 0, 0];
        -        this.curStrokeColor = [0, 0, 0, 0];
        -        _x = _y = _z = 0;
        -        for (m = 0; m < 3; m++) {
        -          for (k = 0; k < 4; k++) {
        -            this.curFillColor[k] +=
        -              this._lookUpTableQuadratic[i][m] * fillColors[m][k];
        -            this.curStrokeColor[k] +=
        -              this._lookUpTableQuadratic[i][m] * strokeColors[m][k];
        +          for (let j = 0; j <= this.detailX; j++) {
        +            const u = j / this.detailX;
        +            const theta = 2 * Math.PI * u;
        +            const cosTheta = Math.cos(theta);
        +            const sinTheta = Math.sin(theta);
        +            const p = new p5.Vector(cosPhi * sinTheta, sinPhi, cosPhi * cosTheta);
        +            this.vertices.push(p);
        +            this.vertexNormals.push(p);
        +            this.uvs.push(u, v);
                   }
        -          _x += w_x[m] * this._lookUpTableQuadratic[i][m];
        -          _y += w_y[m] * this._lookUpTableQuadratic[i][m];
        -          _z += w_z[m] * this._lookUpTableQuadratic[i][m];
                 }
        -        this.vertex(_x, _y, _z);
        +      };
        +      const ellipsoidGeom = new Geometry(detailX, detailY, _ellipsoid, this);
        +      ellipsoidGeom.computeFaces();
        +      if (detailX <= 24 && detailY <= 24) {
        +        ellipsoidGeom._makeTriangleEdges()._edgesToVertices();
        +      } else if (this.states.strokeColor) {
        +        console.log(
        +          'Cannot draw stroke on ellipsoids with more' +
        +          ' than 24 detailX or 24 detailY'
        +        );
               }
        -
        -      // so that we leave currentColor with the last value the user set it to
        -      this.curFillColor = fillColors[2];
        -      this.curStrokeColor = strokeColors[2];
        -      this.immediateMode._quadraticVertex[0] = args[3];
        -      this.immediateMode._quadraticVertex[1] = args[4];
        -      this.immediateMode._quadraticVertex[2] = args[5];
        +      ellipsoidGeom.gid = gid;
        +      this.geometryBufferCache.ensureCached(ellipsoidGeom);
             }
        +
        +    this._drawGeometryScaled(this.geometryBufferCache.getGeometryByID(gid), radiusX, radiusY, radiusZ);
           }
        -};
        -
        -p5.RendererGL.prototype.curveVertex = function(...args) {
        -  let w_x = [];
        -  let w_y = [];
        -  let w_z = [];
        -  let t, _x, _y, _z, i;
        -  t = 0;
        -  const argLength = args.length;
        -
        -  if (
        -    this._lookUpTableBezier.length === 0 ||
        -    this._lutBezierDetail !== this._pInst._curveDetail
        +
        +  RendererGL.prototype.cylinder = function(
        +    radius = 50,
        +    height = radius,
        +    detailX = 24,
        +    detailY = 1,
        +    bottomCap = true,
        +    topCap = true
           ) {
        -    this._lookUpTableBezier = [];
        -    this._lutBezierDetail = this._pInst._curveDetail;
        -    const step = 1 / this._lutBezierDetail;
        -    let start = step;
        -    let end = 1;
        -    let j = 0;
        -    while (start < 1) {
        -      t = parseFloat(start.toFixed(6));
        -      this._lookUpTableBezier[j] = this._bezierCoefficients(t);
        -      if (end.toFixed(6) === step.toFixed(6)) {
        -        t = parseFloat(end.toFixed(6)) + parseFloat(start.toFixed(6));
        -        ++j;
        -        this._lookUpTableBezier[j] = this._bezierCoefficients(t);
        -        break;
        +    const gid = `cylinder|${detailX}|${detailY}|${bottomCap}|${topCap}`;
        +    if (!this.geometryInHash(gid)) {
        +      const cylinderGeom = new p5.Geometry(detailX, detailY, function() {
        +        _truncatedCone.call(
        +          this,
        +          1,
        +          1,
        +          1,
        +          detailX,
        +          detailY,
        +          bottomCap,
        +          topCap
        +        );
        +      }, this);
        +      // normals are computed in call to _truncatedCone
        +      if (detailX <= 24 && detailY <= 16) {
        +        cylinderGeom._makeTriangleEdges()._edgesToVertices();
        +      } else if (this.states.strokeColor) {
        +        console.log(
        +          'Cannot draw stroke on cylinder objects with more' +
        +          ' than 24 detailX or 16 detailY'
        +        );
               }
        -      start += step;
        -      end -= step;
        -      ++j;
        +      cylinderGeom.gid = gid;
        +      this.geometryBufferCache.ensureCached(cylinderGeom);
             }
        +
        +    this._drawGeometryScaled(this.geometryBufferCache.getGeometryByID(gid), radius, height, radius);
           }
         
        -  const LUTLength = this._lookUpTableBezier.length;
        -
        -  if (argLength === 2) {
        -    this.immediateMode._curveVertex.push(args[0]);
        -    this.immediateMode._curveVertex.push(args[1]);
        -    if (this.immediateMode._curveVertex.length === 8) {
        -      this.isCurve = true;
        -      w_x = this._bezierToCatmull([
        -        this.immediateMode._curveVertex[0],
        -        this.immediateMode._curveVertex[2],
        -        this.immediateMode._curveVertex[4],
        -        this.immediateMode._curveVertex[6]
        -      ]);
        -      w_y = this._bezierToCatmull([
        -        this.immediateMode._curveVertex[1],
        -        this.immediateMode._curveVertex[3],
        -        this.immediateMode._curveVertex[5],
        -        this.immediateMode._curveVertex[7]
        -      ]);
        -      for (i = 0; i < LUTLength; i++) {
        -        _x =
        -          w_x[0] * this._lookUpTableBezier[i][0] +
        -          w_x[1] * this._lookUpTableBezier[i][1] +
        -          w_x[2] * this._lookUpTableBezier[i][2] +
        -          w_x[3] * this._lookUpTableBezier[i][3];
        -        _y =
        -          w_y[0] * this._lookUpTableBezier[i][0] +
        -          w_y[1] * this._lookUpTableBezier[i][1] +
        -          w_y[2] * this._lookUpTableBezier[i][2] +
        -          w_y[3] * this._lookUpTableBezier[i][3];
        -        this.vertex(_x, _y);
        -      }
        -      for (i = 0; i < argLength; i++) {
        -        this.immediateMode._curveVertex.shift();
        -      }
        -    }
        -  } else if (argLength === 3) {
        -    this.immediateMode._curveVertex.push(args[0]);
        -    this.immediateMode._curveVertex.push(args[1]);
        -    this.immediateMode._curveVertex.push(args[2]);
        -    if (this.immediateMode._curveVertex.length === 12) {
        -      this.isCurve = true;
        -      w_x = this._bezierToCatmull([
        -        this.immediateMode._curveVertex[0],
        -        this.immediateMode._curveVertex[3],
        -        this.immediateMode._curveVertex[6],
        -        this.immediateMode._curveVertex[9]
        -      ]);
        -      w_y = this._bezierToCatmull([
        -        this.immediateMode._curveVertex[1],
        -        this.immediateMode._curveVertex[4],
        -        this.immediateMode._curveVertex[7],
        -        this.immediateMode._curveVertex[10]
        -      ]);
        -      w_z = this._bezierToCatmull([
        -        this.immediateMode._curveVertex[2],
        -        this.immediateMode._curveVertex[5],
        -        this.immediateMode._curveVertex[8],
        -        this.immediateMode._curveVertex[11]
        -      ]);
        -      for (i = 0; i < LUTLength; i++) {
        -        _x =
        -          w_x[0] * this._lookUpTableBezier[i][0] +
        -          w_x[1] * this._lookUpTableBezier[i][1] +
        -          w_x[2] * this._lookUpTableBezier[i][2] +
        -          w_x[3] * this._lookUpTableBezier[i][3];
        -        _y =
        -          w_y[0] * this._lookUpTableBezier[i][0] +
        -          w_y[1] * this._lookUpTableBezier[i][1] +
        -          w_y[2] * this._lookUpTableBezier[i][2] +
        -          w_y[3] * this._lookUpTableBezier[i][3];
        -        _z =
        -          w_z[0] * this._lookUpTableBezier[i][0] +
        -          w_z[1] * this._lookUpTableBezier[i][1] +
        -          w_z[2] * this._lookUpTableBezier[i][2] +
        -          w_z[3] * this._lookUpTableBezier[i][3];
        -        this.vertex(_x, _y, _z);
        -      }
        -      for (i = 0; i < argLength; i++) {
        -        this.immediateMode._curveVertex.shift();
        +  RendererGL.prototype.cone = function(
        +    radius = 50,
        +    height = radius,
        +    detailX = 24,
        +    detailY = 1,
        +    cap = true
        +  ) {
        +    const gid = `cone|${detailX}|${detailY}|${cap}`;
        +    if (!this.geometryInHash(gid)) {
        +      const coneGeom = new Geometry(detailX, detailY, function() {
        +        _truncatedCone.call(
        +          this,
        +          1,
        +          0,
        +          1,
        +          detailX,
        +          detailY,
        +          cap,
        +          false
        +        );
        +      }, this);
        +      if (detailX <= 24 && detailY <= 16) {
        +        coneGeom._makeTriangleEdges()._edgesToVertices();
        +      } else if (this.states.strokeColor) {
        +        console.log(
        +          'Cannot draw stroke on cone objects with more' +
        +          ' than 24 detailX or 16 detailY'
        +        );
               }
        +      coneGeom.gid = gid;
        +      this.geometryBufferCache.ensureCached(coneGeom);
             }
        -  }
        -};
        -
        -p5.RendererGL.prototype.image = function(
        -  img,
        -  sx,
        -  sy,
        -  sWidth,
        -  sHeight,
        -  dx,
        -  dy,
        -  dWidth,
        -  dHeight
        -) {
        -  if (this._isErasing) {
        -    this.blendMode(this._cachedBlendMode);
        +
        +    this._drawGeometryScaled(this.geometryBufferCache.getGeometryByID(gid), radius, height, radius);
           }
         
        -  this._pInst.push();
        +  RendererGL.prototype.torus = function(
        +    radius = 50,
        +    tubeRadius = 10,
        +    detailX = 24,
        +    detailY = 16
        +  ) {
        +    if (radius === 0) {
        +      return; // nothing to draw
        +    }
         
        -  this._pInst.noLights();
        -  this._pInst.noStroke();
        +    if (tubeRadius === 0) {
        +      return; // nothing to draw
        +    }
         
        -  this._pInst.texture(img);
        -  this._pInst.textureMode(constants.NORMAL);
        +    const tubeRatio = (tubeRadius / radius).toPrecision(4);
        +    const gid = `torus|${tubeRatio}|${detailX}|${detailY}`;
         
        -  let u0 = 0;
        -  if (sx <= img.width) {
        -    u0 = sx / img.width;
        -  }
        +    if (!this.geometryInHash(gid)) {
        +      const _torus = function() {
        +        for (let i = 0; i <= this.detailY; i++) {
        +          const v = i / this.detailY;
        +          const phi = 2 * Math.PI * v;
        +          const cosPhi = Math.cos(phi);
        +          const sinPhi = Math.sin(phi);
        +          const r = 1 + tubeRatio * cosPhi;
         
        -  let u1 = 1;
        -  if (sx + sWidth <= img.width) {
        -    u1 = (sx + sWidth) / img.width;
        -  }
        +          for (let j = 0; j <= this.detailX; j++) {
        +            const u = j / this.detailX;
        +            const theta = 2 * Math.PI * u;
        +            const cosTheta = Math.cos(theta);
        +            const sinTheta = Math.sin(theta);
         
        -  let v0 = 0;
        -  if (sy <= img.height) {
        -    v0 = sy / img.height;
        -  }
        +            const p = new Vector(
        +              r * cosTheta,
        +              r * sinTheta,
        +              tubeRatio * sinPhi
        +            );
         
        -  let v1 = 1;
        -  if (sy + sHeight <= img.height) {
        -    v1 = (sy + sHeight) / img.height;
        -  }
        +            const n = new Vector(cosPhi * cosTheta, cosPhi * sinTheta, sinPhi);
         
        -  this.beginShape();
        -  this.vertex(dx, dy, 0, u0, v0);
        -  this.vertex(dx + dWidth, dy, 0, u1, v0);
        -  this.vertex(dx + dWidth, dy + dHeight, 0, u1, v1);
        -  this.vertex(dx, dy + dHeight, 0, u0, v1);
        -  this.endShape(constants.CLOSE);
        +            this.vertices.push(p);
        +            this.vertexNormals.push(n);
        +            this.uvs.push(u, v);
        +          }
        +        }
        +      };
        +      const torusGeom = new Geometry(detailX, detailY, _torus, this);
        +      torusGeom.computeFaces();
        +      if (detailX <= 24 && detailY <= 16) {
        +        torusGeom._makeTriangleEdges()._edgesToVertices();
        +      } else if (this.states.strokeColor) {
        +        console.log(
        +          'Cannot draw strokes on torus object with more' +
        +          ' than 24 detailX or 16 detailY'
        +        );
        +      }
        +      torusGeom.gid = gid;
        +      this.geometryBufferCache.ensureCached(torusGeom);
        +    }
        +    this._drawGeometryScaled(this.geometryBufferCache.getGeometryByID(gid), radius, radius, radius);
        +  }
         
        -  this._pInst.pop();
        +  /**
        +   * Sets the number of segments used to draw spline curves in WebGL mode.
        +   *
        +   * In WebGL mode, smooth shapes are drawn using many flat segments. Adding
        +   * more flat segments makes shapes appear smoother.
        +   *
        +   * The parameter, `detail`, is the number of segments to use while drawing a
        +   * spline curve. For example, calling `curveDetail(5)` will use 5 segments to
        +   * draw curves with the <a href="#/p5/curve">curve()</a> function. By
        +   * default,`detail` is 20.
        +   *
        +   * Note: `curveDetail()` has no effect in 2D mode.
        +   *
        +   * @method curveDetail
        +   * @param {Number} resolution number of segments to use. Defaults to 20.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100);
        +   *
        +   *   background(200);
        +   *
        +   *   // Draw a black spline curve.
        +   *   noFill();
        +   *   strokeWeight(1);
        +   *   stroke(0);
        +   *   curve(5, 26, 73, 24, 73, 61, 15, 65);
        +   *
        +   *   // Draw red spline curves from the anchor points to the control points.
        +   *   stroke(255, 0, 0);
        +   *   curve(5, 26, 5, 26, 73, 24, 73, 61);
        +   *   curve(73, 24, 73, 61, 15, 65, 15, 65);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   strokeWeight(5);
        +   *   stroke(0);
        +   *   point(73, 24);
        +   *   point(73, 61);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(5, 26);
        +   *   point(15, 65);
        +   *
        +   *   describe(
        +   *     'A gray square with a curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Set the curveDetail() to 3.
        +   *   curveDetail(3);
        +   *
        +   *   // Draw a black spline curve.
        +   *   noFill();
        +   *   strokeWeight(1);
        +   *   stroke(0);
        +   *   curve(-45, -24, 0, 23, -26, 0, 23, 11, 0, -35, 15, 0);
        +   *
        +   *   // Draw red spline curves from the anchor points to the control points.
        +   *   stroke(255, 0, 0);
        +   *   curve(-45, -24, 0, -45, -24, 0, 23, -26, 0, 23, 11, 0);
        +   *   curve(23, -26, 0, 23, 11, 0, -35, 15, 0, -35, 15, 0);
        +   *
        +   *   // Draw the anchor points in black.
        +   *   strokeWeight(5);
        +   *   stroke(0);
        +   *   point(23, -26);
        +   *   point(23, 11);
        +   *
        +   *   // Draw the control points in red.
        +   *   stroke(255, 0, 0);
        +   *   point(-45, -24);
        +   *   point(-35, 15);
        +   *
        +   *   describe(
        +   *     'A gray square with a jagged curve drawn in three segments. The curve is a sideways U shape with red segments on top and bottom, and a black segment on the right. The endpoints of all the segments are marked with dots.'
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.curveDetail = function(d) {
        +    if (!(this._renderer instanceof RendererGL)) {
        +      throw new Error(
        +        'curveDetail() only works in WebGL mode. Did you mean to call createCanvas(width, height, WEBGL)?'
        +      );
        +    }
        +    return this._renderer.curveDetail(d);
        +  };
        +}
         
        -  if (this._isErasing) {
        -    this.blendMode(constants.REMOVE);
        -  }
        -};
        +export default primitives3D;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  primitives3D(p5, p5.prototype);
        +}
        diff --git a/src/webgl/GeometryBufferCache.js b/src/webgl/GeometryBufferCache.js
        new file mode 100644
        index 0000000000..289a1daaff
        --- /dev/null
        +++ b/src/webgl/GeometryBufferCache.js
        @@ -0,0 +1,109 @@
        +export class GeometryBufferCache {
        +  constructor(renderer) {
        +    this.renderer = renderer;
        +    this.cache = {};
        +  }
        +
        +  numCached() {
        +    return Object.keys(this.cache).length;
        +  }
        +
        +  isCached(gid) {
        +    return this.cache[gid] !== undefined;
        +  }
        +
        +  getGeometryByID(gid) {
        +    return this.cache[gid]?.geometry;
        +  }
        +
        +  getCached(model) {
        +    return this.getCachedID(model.gid);
        +  }
        +
        +  getCachedID(gid) {
        +    return this.cache[gid];
        +  }
        +
        +  ensureCached(geometry) {
        +    const gid = geometry.gid;
        +    if (!gid) {
        +      throw new Error('The p5.Geometry you passed in has no gid property!');
        +    }
        +
        +    if (this.isCached(geometry.gid)) return this.getCached(geometry);
        +
        +    const gl = this.renderer.GL;
        +
        +    //initialize the gl buffers for our geom groups
        +    this.freeBuffers(gid);
        +
        +    if (Object.keys(this.cache).length > 1000) {
        +      const key = Object.keys(this.cache)[0];
        +      this.freeBuffers(key);
        +    }
        +
        +    //create a new entry in our cache
        +    const buffers = {};
        +    this.cache[gid] = buffers;
        +
        +    buffers.geometry = geometry;
        +
        +    let indexBuffer = buffers.indexBuffer;
        +
        +    if (geometry.faces.length) {
        +      // allocate space for faces
        +      if (!indexBuffer) indexBuffer = buffers.indexBuffer = gl.createBuffer();
        +      const vals = geometry.faces.flat();
        +
        +      // If any face references a vertex with an index greater than the maximum
        +      // un-singed 16 bit integer, then we need to use a Uint32Array instead of a
        +      // Uint16Array
        +      const hasVertexIndicesOverMaxUInt16 = vals.some(v => v > 65535);
        +      let type = hasVertexIndicesOverMaxUInt16 ? Uint32Array : Uint16Array;
        +      this.renderer._bindBuffer(indexBuffer, gl.ELEMENT_ARRAY_BUFFER, vals, type);
        +
        +      // If we're using a Uint32Array for our indexBuffer we will need to pass a
        +      // different enum value to WebGL draw triangles. This happens in
        +      // the _drawElements function.
        +      buffers.indexBufferType = hasVertexIndicesOverMaxUInt16
        +        ? gl.UNSIGNED_INT
        +        : gl.UNSIGNED_SHORT;
        +    } else {
        +      // the index buffer is unused, remove it
        +      if (indexBuffer) {
        +        gl.deleteBuffer(indexBuffer);
        +        buffers.indexBuffer = null;
        +      }
        +    }
        +
        +    return buffers;
        +  }
        +
        +  freeBuffers(gid) {
        +    const buffers = this.cache[gid];
        +    if (!buffers) {
        +      return;
        +    }
        +
        +    delete this.cache[gid];
        +
        +    const gl = this.renderer.GL;
        +    if (buffers.indexBuffer) {
        +      gl.deleteBuffer(buffers.indexBuffer);
        +    }
        +
        +    function freeBuffers(defs) {
        +      for (const def of defs) {
        +        if (buffers[def.dst]) {
        +          gl.deleteBuffer(buffers[def.dst]);
        +          buffers[def.dst] = null;
        +        }
        +      }
        +    }
        +
        +    // free all the buffers
        +    freeBuffers(this.renderer.buffers.stroke);
        +    freeBuffers(this.renderer.buffers.fill);
        +    freeBuffers(this.renderer.buffers.user);
        +  }
        +}
        diff --git a/src/webgl/GeometryBuilder.js b/src/webgl/GeometryBuilder.js
        index ac78ec7e94..5cb4862168 100644
        --- a/src/webgl/GeometryBuilder.js
        +++ b/src/webgl/GeometryBuilder.js
        @@ -1,5 +1,6 @@
        -import p5 from '../core/main';
         import * as constants from '../core/constants';
        +import { Matrix } from '../math/p5.Matrix';
        +import { Geometry } from './p5.Geometry';
         
         /**
          * @private
        @@ -10,9 +11,9 @@ class GeometryBuilder {
           constructor(renderer) {
             this.renderer = renderer;
             renderer._pInst.push();
        -    this.identityMatrix = new p5.Matrix();
        -    renderer.uModelMatrix = new p5.Matrix();
        -    this.geometry = new p5.Geometry();
        +    this.identityMatrix = new Matrix(4);
        +    renderer.states.uModelMatrix = new Matrix(4);
        +    this.geometry = new Geometry(undefined, undefined, undefined, this.renderer);
             this.geometry.gid = `_p5_GeometryBuilder_${GeometryBuilder.nextGeometryId}`;
             GeometryBuilder.nextGeometryId++;
             this.hasTransform = false;
        @@ -25,7 +26,7 @@ class GeometryBuilder {
           transformVertices(vertices) {
             if (!this.hasTransform) return vertices;
         
        -    return vertices.map(v => this.renderer.uModelMatrix.multiplyPoint(v));
        +    return vertices.map(v => this.renderer.states.uModelMatrix.multiplyPoint(v));
           }
         
           /**
        @@ -36,7 +37,7 @@ class GeometryBuilder {
             if (!this.hasTransform) return normals;
         
             return normals.map(
        -      v => this.renderer.uNMatrix.multiplyVec3(v)
        +      v => this.renderer.scratchMat3.multiplyVec(v) // this is a vec3
             );
           }
         
        @@ -46,11 +47,11 @@ class GeometryBuilder {
            * transformations.
            */
           addGeometry(input) {
        -    this.hasTransform = !this.renderer.uModelMatrix.mat4
        +    this.hasTransform = !this.renderer.states.uModelMatrix.mat4
               .every((v, i) => v === this.identityMatrix.mat4[i]);
         
             if (this.hasTransform) {
        -      this.renderer.uNMatrix.inverseTranspose(this.renderer.uModelMatrix);
        +      this.renderer.scratchMat3.inverseTranspose4x4(this.renderer.states.uModelMatrix);
             }
         
             let startIdx = this.geometry.vertices.length;
        @@ -60,19 +61,45 @@ class GeometryBuilder {
             );
             this.geometry.uvs.push(...input.uvs);
         
        -    if (this.renderer._doFill) {
        +    const inputUserVertexProps = input.userVertexProperties;
        +    const builtUserVertexProps = this.geometry.userVertexProperties;
        +    const numPreviousVertices = this.geometry.vertices.length - input.vertices.length;
        +
        +    for (const propName in builtUserVertexProps){
        +      if (propName in inputUserVertexProps){
        +        continue;
        +      }
        +      const prop = builtUserVertexProps[propName]
        +      const size = prop.getDataSize();
        +      const numMissingValues = size * input.vertices.length;
        +      const missingValues = Array(numMissingValues).fill(0);
        +      prop.pushDirect(missingValues);
        +    }
        +    for (const propName in inputUserVertexProps){
        +      const prop = inputUserVertexProps[propName];
        +      const data = prop.getSrcArray();
        +      const size = prop.getDataSize();
        +      if (numPreviousVertices > 0 && !(propName in builtUserVertexProps)){
        +        const numMissingValues = size * numPreviousVertices;
        +        const missingValues = Array(numMissingValues).fill(0);
        +        this.geometry.vertexProperty(propName, missingValues, size);
        +      }
        +      this.geometry.vertexProperty(propName, data, size);
        +    }
        +
        +    if (this.renderer.states.fillColor) {
               this.geometry.faces.push(
                 ...input.faces.map(f => f.map(idx => idx + startIdx))
               );
             }
        -    if (this.renderer._doStroke) {
        +    if (this.renderer.states.strokeColor) {
               this.geometry.edges.push(
                 ...input.edges.map(edge => edge.map(idx => idx + startIdx))
               );
             }
             const vertexColors = [...input.vertexColors];
             while (vertexColors.length < input.vertices.length * 4) {
        -      vertexColors.push(...this.renderer.curFillColor);
        +      vertexColors.push(...this.renderer.states.curFillColor);
             }
             this.geometry.vertexColors.push(...vertexColors);
           }
        @@ -81,12 +108,10 @@ class GeometryBuilder {
            * Adds geometry from the renderer's immediate mode into the builder's
            * combined geometry.
            */
        -  addImmediate() {
        -    const geometry = this.renderer.immediateMode.geometry;
        -    const shapeMode = this.renderer.immediateMode.shapeMode;
        +  addImmediate(geometry, shapeMode) {
             const faces = [];
         
        -    if (this.renderer._doFill) {
        +    if (this.renderer.states.fillColor) {
               if (
                 shapeMode === constants.TRIANGLE_STRIP ||
                 shapeMode === constants.QUAD_STRIP
        @@ -116,7 +141,7 @@ class GeometryBuilder {
            * combined geometry.
            */
           addRetained(geometry) {
        -    this.addGeometry(geometry.model);
        +    this.addGeometry(geometry);
           }
         
           /**
        diff --git a/src/webgl/ShapeBuilder.js b/src/webgl/ShapeBuilder.js
        new file mode 100644
        index 0000000000..41535345e7
        --- /dev/null
        +++ b/src/webgl/ShapeBuilder.js
        @@ -0,0 +1,484 @@
        +import * as constants from '../core/constants';
        +import { Geometry } from './p5.Geometry';
        +import libtess from 'libtess'; // Fixed with exporting module from libtess
        +import { Vector } from '../math/p5.Vector';
        +import { RenderBuffer } from './p5.RenderBuffer';
        +
        +const INITIAL_BUFFER_STRIDES = {
        +  vertices: 1,
        +  vertexNormals: 1,
        +  vertexColors: 4,
        +  vertexStrokeColors: 4,
        +  uvs: 2
        +};
        +
        +// The total number of properties per vertex, before additional
        +// user attributes are added.
        +const INITIAL_VERTEX_SIZE =
        +  Object.values(INITIAL_BUFFER_STRIDES).reduce((acc, next) => acc + next);
        +
        +export class ShapeBuilder {
        +  constructor(renderer) {
        +    this.renderer = renderer;
        +    this.shapeMode = constants.PATH;
        +    this.geometry = new Geometry(undefined, undefined, undefined, this.renderer);
        +    this.geometry.gid = '__IMMEDIATE_MODE_GEOMETRY__';
        +
        +    this.contourIndices = [];
        +    this._useUserVertexProperties = undefined;
        +
        +    this._bezierVertex = [];
        +    this._quadraticVertex = [];
        +    this._curveVertex = [];
        +
        +    // Used to distinguish between user calls to vertex() and internal calls
        +    this.isProcessingVertices = false;
        +
        +    // Used for converting shape outlines into triangles for rendering
        +    this._tessy = this._initTessy();
        +    this.tessyVertexSize = INITIAL_VERTEX_SIZE;
        +    this.bufferStrides = { ...INITIAL_BUFFER_STRIDES };
        +  }
        +
        +  constructFromContours(shape, contours) {
        +    if (this._useUserVertexProperties){
        +      this._resetUserVertexProperties();
        +    }
        +    this.geometry.reset();
        +    this.contourIndices = [];
        +    // TODO: handle just some contours having non-PATH mode
        +    this.shapeMode = shape.contours[0].kind;
        +    const shouldProcessEdges = !!this.renderer.states.strokeColor;
        +
        +    const userVertexPropertyHelpers = {};
        +    if (shape.userVertexProperties) {
        +      this._useUserVertexProperties = true;
        +      for (const key in shape.userVertexProperties) {
        +        const name = shape.vertexPropertyName(key);
        +        const prop = this.geometry._userVertexPropertyHelper(name, [], shape.userVertexProperties[key]);
        +        userVertexPropertyHelpers[key] = prop;
        +        this.tessyVertexSize += prop.getDataSize();
        +        this.bufferStrides[prop.getSrcName()] = prop.getDataSize();
        +        this.renderer.buffers.user.push(
        +          new RenderBuffer(prop.getDataSize(), prop.getSrcName(), prop.getDstName(), name, this.renderer)
        +        );
        +      }
        +    } else {
        +      this._useUserVertexProperties = false;
        +    }
        +
        +    for (const contour of contours) {
        +      this.contourIndices.push(this.geometry.vertices.length);
        +      for (const vertex of contour) {
        +        // WebGL doesn't support QUADS or QUAD_STRIP, so we duplicate data to turn
        +        // QUADS into TRIANGLES and QUAD_STRIP into TRIANGLE_STRIP. (There is no extra
        +        // work to convert QUAD_STRIP here, since the only difference is in how edges
        +        // are rendered.)
        +        if (this.shapeMode === constants.QUADS) {
        +          // A finished quad turned into triangles should leave 6 vertices in the
        +          // buffer:
        +          // 0--3     0   3--5
        +          // |  | --> | \  \ |
        +          // 1--2     1--2   4
        +          // When vertex index 3 is being added, add the necessary duplicates.
        +          if (this.geometry.vertices.length % 6 === 3) {
        +            for (const key in this.bufferStrides) {
        +              const stride = this.bufferStrides[key];
        +              const buffer = this.geometry[key];
        +              buffer.push(
        +                ...buffer.slice(
        +                  buffer.length - 3 * stride,
        +                  buffer.length - 2 * stride
        +                ),
        +                ...buffer.slice(buffer.length - stride, buffer.length),
        +              );
        +            }
        +          }
        +        }
        +
        +        this.geometry.vertices.push(vertex.position);
        +        this.geometry.vertexNormals.push(vertex.normal || new Vector(0, 0, 0));
        +        this.geometry.uvs.push(vertex.textureCoordinates.x, vertex.textureCoordinates.y);
        +        if (this.renderer.states.fillColor) {
        +          this.geometry.vertexColors.push(...vertex.fill.array());
        +        } else {
        +          this.geometry.vertexColors.push(0, 0, 0, 0);
        +        }
        +        if (this.renderer.states.strokeColor) {
        +          this.geometry.vertexStrokeColors.push(...vertex.stroke.array());
        +        } else {
        +          this.geometry.vertexStrokeColors.push(0, 0, 0, 0);
        +        }
        +        for (const key in userVertexPropertyHelpers) {
        +          const prop = userVertexPropertyHelpers[key];
        +          if (key in vertex) {
        +            prop.setCurrentData(vertex[key]);
        +          }
        +          prop.pushCurrentData();
        +        }
        +      }
        +    }
        +
        +    if (shouldProcessEdges) {
        +      this.geometry.edges = this._calculateEdges(this.shapeMode, this.geometry.vertices);
        +    }
        +    if (shouldProcessEdges && !this.renderer.geometryBuilder) {
        +      this.geometry._edgesToVertices();
        +    }
        +
        +    if (this.shapeMode === constants.PATH) {
        +      this.isProcessingVertices = true;
        +      this._tesselateShape();
        +      this.isProcessingVertices = false;
        +    } else if (this.shapeMode === constants.QUAD_STRIP) {
        +      // The only difference between these two modes is which edges are
        +      // displayed, so after we've updated the edges, we switch the mode
        +      // to one that native WebGL knows how to render.
        +      this.shapeMode = constants.TRIANGLE_STRIP;
        +    } else if (this.shapeMode === constants.QUADS) {
        +      // We translate QUADS to TRIANGLES when vertices are being added,
        +      // since QUADS is just a p5 mode, whereas TRIANGLES is also a mode
        +      // that native WebGL knows how to render. Once we've processed edges,
        +      // everything should be set up for TRIANGLES mode.
        +      this.shapeMode = constants.TRIANGLES;
        +    }
        +
        +    if (
        +      this.renderer.states.textureMode === constants.IMAGE &&
        +      this.renderer.states._tex !== null &&
        +      this.renderer.states._tex.width > 0 &&
        +      this.renderer.states._tex.height > 0
        +    ) {
        +      this.geometry.uvs = this.geometry.uvs.map((val, i) => {
        +        if (i % 2 === 0) {
        +          return val / this.renderer.states._tex.width;
        +        } else {
        +          return val / this.renderer.states._tex.height;
        +        }
        +      })
        +    }
        +  }
        +
        +  _resetUserVertexProperties() {
        +    const properties = this.geometry.userVertexProperties;
        +    for (const propName in properties){
        +      const prop = properties[propName];
        +      delete this.bufferStrides[propName];
        +      prop.delete();
        +    }
        +    this._useUserVertexProperties = false;
        +    this.tessyVertexSize = INITIAL_VERTEX_SIZE;
        +    this.geometry.userVertexProperties = {};
        +  }
        +
        +  /**
        +   * Called from _processVertices(). This function calculates the stroke vertices for custom shapes and
        +   * tesselates shapes when applicable.
        +   * @private
        +   * @returns  {Number[]} indices for custom shape vertices indicating edges.
        +   */
        +  _calculateEdges(
        +    shapeMode,
        +    verts,
        +  ) {
        +    const res = [];
        +    let i = 0;
        +    const contourIndices = this.contourIndices.slice();
        +    let contourStart = -1;
        +    switch (shapeMode) {
        +      case constants.TRIANGLE_STRIP:
        +        for (i = 0; i < verts.length - 2; i++) {
        +          res.push([i, i + 1]);
        +          res.push([i, i + 2]);
        +        }
        +        res.push([i, i + 1]);
        +        break;
        +      case constants.TRIANGLE_FAN:
        +        for (i = 1; i < verts.length - 1; i++) {
        +          res.push([0, i]);
        +          res.push([i, i + 1]);
        +        }
        +        res.push([0, verts.length - 1]);
        +        break;
        +      case constants.TRIANGLES:
        +        for (i = 0; i < verts.length - 2; i = i + 3) {
        +          res.push([i, i + 1]);
        +          res.push([i + 1, i + 2]);
        +          res.push([i + 2, i]);
        +        }
        +        break;
        +      case constants.LINES:
        +        for (i = 0; i < verts.length - 1; i = i + 2) {
        +          res.push([i, i + 1]);
        +        }
        +        break;
        +      case constants.QUADS:
        +        // Quads have been broken up into two triangles by `vertex()`:
        +        // 0   3--5
        +        // | \  \ |
        +        // 1--2   4
        +        for (i = 0; i < verts.length - 5; i += 6) {
        +          res.push([i, i + 1]);
        +          res.push([i + 1, i + 2]);
        +          res.push([i + 2, i + 5]);
        +          res.push([i + 5, i]);
        +        }
        +        break;
        +      case constants.QUAD_STRIP:
        +        // 0---2---4
        +        // |   |   |
        +        // 1---3---5
        +        for (i = 0; i < verts.length - 2; i += 2) {
        +          res.push([i, i + 1]);
        +          res.push([i + 1, i + 3]);
        +          res.push([i, i + 2]);
        +        }
        +        res.push([i, i + 1]);
        +        break;
        +      default:
        +        // TODO: handle contours in other modes too
        +        for (i = 0; i < verts.length; i++) {
        +          if (i === contourIndices[0]) {
        +            contourStart = contourIndices.shift();
        +          } else if (
        +            verts[contourStart] &&
        +            verts[i].equals(verts[contourStart])
        +          ) {
        +            res.push([i - 1, contourStart]);
        +          } else {
        +            res.push([i - 1, i]);
        +          }
        +        }
        +        break;
        +    }
        +    return res;
        +  }
        +
        +  /**
        +   * Called from _processVertices() when applicable. This function tesselates immediateMode.geometry.
        +   * @private
        +   */
        +  _tesselateShape() {
        +    // TODO: handle non-PATH shape modes that have contours
        +    this.shapeMode = constants.TRIANGLES;
        +    // const contours = [[]];
        +    const contours = [];
        +    for (let i = 0; i < this.geometry.vertices.length; i++) {
        +      if (
        +        this.contourIndices.length > 0 &&
        +        this.contourIndices[0] === i
        +      ) {
        +        this.contourIndices.shift();
        +        contours.push([]);
        +      }
        +      contours[contours.length-1].push(
        +        this.geometry.vertices[i].x,
        +        this.geometry.vertices[i].y,
        +        this.geometry.vertices[i].z,
        +        this.geometry.uvs[i * 2],
        +        this.geometry.uvs[i * 2 + 1],
        +        this.geometry.vertexColors[i * 4],
        +        this.geometry.vertexColors[i * 4 + 1],
        +        this.geometry.vertexColors[i * 4 + 2],
        +        this.geometry.vertexColors[i * 4 + 3],
        +        this.geometry.vertexNormals[i].x,
        +        this.geometry.vertexNormals[i].y,
        +        this.geometry.vertexNormals[i].z
        +      );
        +      for (const propName in this.geometry.userVertexProperties) {
        +        const prop = this.geometry.userVertexProperties[propName];
        +        const start = i * prop.getDataSize();
        +        const end = start + prop.getDataSize();
        +        const vals = prop.getSrcArray().slice(start, end);
        +        contours[contours.length-1].push(...vals);
        +      }
        +    }
        +
        +    const polyTriangles = this._triangulate(contours);
        +    const originalVertices = this.geometry.vertices;
        +    this.geometry.vertices = [];
        +    this.geometry.vertexNormals = [];
        +    this.geometry.uvs = [];
        +    for (const propName in this.geometry.userVertexProperties){
        +      const prop = this.geometry.userVertexProperties[propName];
        +      prop.resetSrcArray();
        +    }
        +    const colors = [];
        +    for (
        +      let j = 0, polyTriLength = polyTriangles.length;
        +      j < polyTriLength;
        +      j = j + this.tessyVertexSize
        +    ) {
        +      colors.push(...polyTriangles.slice(j + 5, j + 9));
        +      this.geometry.vertexNormals.push(new Vector(...polyTriangles.slice(j + 9, j + 12)));
        +      {
        +        let offset = 12;
        +        for (const propName in this.geometry.userVertexProperties){
        +          const prop = this.geometry.userVertexProperties[propName];
        +          const size = prop.getDataSize();
        +          const start = j + offset;
        +          const end = start + size;
        +          prop.setCurrentData(polyTriangles.slice(start, end));
        +          prop.pushCurrentData();
        +          offset += size;
        +        }
        +      }
        +      this.geometry.vertices.push(new Vector(...polyTriangles.slice(j, j + 3)));
        +      this.geometry.uvs.push(...polyTriangles.slice(j + 3, j + 5));
        +    }
        +    if (this.renderer.geometryBuilder) {
        +      // Tesselating the face causes the indices of edge vertices to stop being
        +      // correct. When rendering, this is not a problem, since _edgesToVertices
        +      // will have been called before this, and edge vertex indices are no longer
        +      // needed. However, the geometry builder still needs this information, so
        +      // when one is active, we need to update the indices.
        +      //
        +      // We record index mappings in a Map so that once we have found a
        +      // corresponding vertex, we don't need to loop to find it again.
        +      const newIndex = new Map();
        +      this.geometry.edges =
        +        this.geometry.edges.map(edge => edge.map(origIdx => {
        +          if (!newIndex.has(origIdx)) {
        +            const orig = originalVertices[origIdx];
        +            let newVertIndex = this.geometry.vertices.findIndex(
        +              v =>
        +                orig.x === v.x &&
        +                orig.y === v.y &&
        +                orig.z === v.z
        +            );
        +            if (newVertIndex === -1) {
        +              // The tesselation process didn't output a vertex with the exact
        +              // coordinate as before, potentially due to numerical issues. This
        +              // doesn't happen often, but in this case, pick the closest point
        +              let closestDist = Infinity;
        +              let closestIndex = 0;
        +              for (
        +                let i = 0;
        +                i < this.geometry.vertices.length;
        +                i++
        +              ) {
        +                const vert = this.geometry.vertices[i];
        +                const dX = orig.x - vert.x;
        +                const dY = orig.y - vert.y;
        +                const dZ = orig.z - vert.z;
        +                const dist = dX*dX + dY*dY + dZ*dZ;
        +                if (dist < closestDist) {
        +                  closestDist = dist;
        +                  closestIndex = i;
        +                }
        +              }
        +              newVertIndex = closestIndex;
        +            }
        +            newIndex.set(origIdx, newVertIndex);
        +          }
        +          return newIndex.get(origIdx);
        +        }));
        +    }
        +    this.geometry.vertexColors = colors;
        +  }
        +
        +  _initTessy() {
        +    // function called for each vertex of tesselator output
        +    function vertexCallback(data, polyVertArray) {
        +      for (const element of data) {
        +        polyVertArray.push(element);
        +      }
        +    }
        +
        +    function begincallback(type) {
        +      if (type !== libtess.primitiveType.GL_TRIANGLES) {
        +        console.log(`expected TRIANGLES but got type: ${type}`);
        +      }
        +    }
        +
        +    function errorcallback(errno) {
        +      console.log('error callback');
        +      console.log(`error number: ${errno}`);
        +    }
        +
        +    // callback for when segments intersect and must be split
        +    const combinecallback = (coords, data, weight) => {
        +      const result = new Array(this.tessyVertexSize).fill(0);
        +      for (let i = 0; i < weight.length; i++) {
        +        for (let j = 0; j < result.length; j++) {
        +          if (weight[i] === 0 || !data[i]) continue;
        +          result[j] += data[i][j] * weight[i];
        +        }
        +      }
        +      return result;
        +    };
        +
        +    function edgeCallback(flag) {
        +      // don't really care about the flag, but need no-strip/no-fan behavior
        +    }
        +
        +    const tessy = new libtess.GluTesselator();
        +    tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_VERTEX_DATA, vertexCallback);
        +    tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_BEGIN, begincallback);
        +    tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_ERROR, errorcallback);
        +    tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_COMBINE, combinecallback);
        +    tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_EDGE_FLAG, edgeCallback);
        +    tessy.gluTessProperty(
        +      libtess.gluEnum.GLU_TESS_WINDING_RULE,
        +      libtess.windingRule.GLU_TESS_WINDING_NONZERO
        +    );
        +
        +    return tessy;
        +  }
        +
        +  /**
        +   * Runs vertices through libtess to convert them into triangles
        +   * @private
        +   */
        +  _triangulate(contours) {
        +    // libtess will take 3d verts and flatten to a plane for tesselation.
        +    // libtess is capable of calculating a plane to tesselate on, but
        +    // if all of the vertices have the same z values, we'll just
        +    // assume the face is facing the camera, letting us skip any performance
        +    // issues or bugs in libtess's automatic calculation.
        +    const z = contours[0] ? contours[0][2] : undefined;
        +    let allSameZ = true;
        +    for (const contour of contours) {
        +      for (
        +        let j = 0;
        +        j < contour.length;
        +        j += this.tessyVertexSize
        +      ) {
        +        if (contour[j + 2] !== z) {
        +          allSameZ = false;
        +          break;
        +        }
        +      }
        +    }
        +    if (allSameZ) {
        +      this._tessy.gluTessNormal(0, 0, 1);
        +    } else {
        +      // Let libtess pick a plane for us
        +      this._tessy.gluTessNormal(0, 0, 0);
        +    }
        +
        +    const triangleVerts = [];
        +    this._tessy.gluTessBeginPolygon(triangleVerts);
        +
        +    for (const contour of contours) {
        +      this._tessy.gluTessBeginContour();
        +      for (
        +        let j = 0;
        +        j < contour.length;
        +        j += this.tessyVertexSize
        +      ) {
        +        const coords = contour.slice(
        +          j,
        +          j + this.tessyVertexSize
        +        );
        +        this._tessy.gluTessVertex(coords, coords);
        +      }
        +      this._tessy.gluTessEndContour();
        +    }
        +
        +    // finish polygon
        +    this._tessy.gluTessEndPolygon();
        +
        +    return triangleVerts;
        +  }
        +};
        diff --git a/src/webgl/index.js b/src/webgl/index.js
        new file mode 100644
        index 0000000000..c2515fce5a
        --- /dev/null
        +++ b/src/webgl/index.js
        @@ -0,0 +1,35 @@
        +import primitives3D from './3d_primitives';
        +import interaction from './interaction';
        +import light from './light';
        +import loading from './loading';
        +import material from './material';
        +import text from './text';
        +import renderBuffer from './p5.RenderBuffer';
        +import quat from './p5.Quat';
        +import matrix from '../math/p5.Matrix';
        +import geometry from './p5.Geometry';
        +import framebuffer from './p5.Framebuffer';
        +import dataArray from './p5.DataArray';
        +import shader from './p5.Shader';
        +import camera from './p5.Camera';
        +import texture from './p5.Texture';
        +import rendererGL from './p5.RendererGL';
        +
        +export default function(p5){
        +  rendererGL(p5, p5.prototype);
        +  primitives3D(p5, p5.prototype);
        +  interaction(p5, p5.prototype);
        +  light(p5, p5.prototype);
        +  loading(p5, p5.prototype);
        +  material(p5, p5.prototype);
        +  text(p5, p5.prototype);
        +  renderBuffer(p5, p5.prototype);
        +  quat(p5, p5.prototype);
        +  matrix(p5, p5.prototype);
        +  geometry(p5, p5.prototype);
        +  camera(p5, p5.prototype);
        +  framebuffer(p5, p5.prototype);
        +  dataArray(p5, p5.prototype);
        +  shader(p5, p5.prototype);
        +  texture(p5, p5.prototype);
        +}
        diff --git a/src/webgl/interaction.js b/src/webgl/interaction.js
        index 012f7347c5..1b257ce6fd 100644
        --- a/src/webgl/interaction.js
        +++ b/src/webgl/interaction.js
        @@ -5,884 +5,890 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
         import * as constants from '../core/constants';
        +import { Vector } from '../math/p5.Vector';
        +
        +function interaction(p5, fn){
        +  /**
        +   * Allows the user to orbit around a 3D sketch using a mouse, trackpad, or
        +   * touchscreen.
        +   *
        +   * 3D sketches are viewed through an imaginary camera. Calling
        +   * `orbitControl()` within the <a href="#/p5/draw">draw()</a> function allows
        +   * the user to change the camera’s position:
        +   *
        +   * ```js
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Rest of sketch.
        +   * }
        +   * ```
        +   *
        +   * Left-clicking and dragging or swipe motion will rotate the camera position
        +   * about the center of the sketch. Right-clicking and dragging or multi-swipe
        +   * will pan the camera position without rotation. Using the mouse wheel
        +   * (scrolling) or pinch in/out will move the camera further or closer from the
        +   * center of the sketch.
        +   *
        +   * The first three parameters, `sensitivityX`, `sensitivityY`, and
        +   * `sensitivityZ`, are optional. They’re numbers that set the sketch’s
        +   * sensitivity to movement along each axis. For example, calling
        +   * `orbitControl(1, 2, -1)` keeps movement along the x-axis at its default
        +   * value, makes the sketch twice as sensitive to movement along the y-axis,
        +   * and reverses motion along the z-axis. By default, all sensitivity values
        +   * are 1.
        +   *
        +   * The fourth parameter, `options`, is also optional. It’s an object that
        +   * changes the behavior of orbiting. For example, calling
        +   * `orbitControl(1, 1, 1, options)` keeps the default sensitivity values while
        +   * changing the behaviors set with `options`. The object can have the
        +   * following properties:
        +   *
        +   * ```js
        +   * let options = {
        +   *   // Setting this to false makes mobile interactions smoother by
        +   *   // preventing accidental interactions with the page while orbiting.
        +   *   // By default, it's true.
        +   *   disableTouchActions: true,
        +   *
        +   *   // Setting this to true makes the camera always rotate in the
        +   *   // direction the mouse/touch is moving.
        +   *   // By default, it's false.
        +   *   freeRotation: false
        +   * };
        +   *
        +   * orbitControl(1, 1, 1, options);
        +   * ```
        +   *
        +   * @method orbitControl
        +   * @for p5
        +   * @param  {Number} [sensitivityX] sensitivity to movement along the x-axis. Defaults to 1.
        +   * @param  {Number} [sensitivityY] sensitivity to movement along the y-axis. Defaults to 1.
        +   * @param  {Number} [sensitivityZ] sensitivity to movement along the z-axis. Defaults to 1.
        +   * @param  {Object} [options] object with two optional properties, `disableTouchActions`
        +   *                            and `freeRotation`. Both are `Boolean`s. `disableTouchActions`
        +   *                            defaults to `true` and `freeRotation` defaults to `false`.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A multicolor box on a gray background. The camera angle changes when the user interacts using a mouse, trackpad, or touchscreen.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the box.
        +   *   normalMaterial();
        +   *
        +   *   // Draw the box.
        +   *   box(30, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A multicolor box on a gray background. The camera angle changes when the user interacts using a mouse, trackpad, or touchscreen.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   // Make the interactions 3X sensitive.
        +   *   orbitControl(3, 3, 3);
        +   *
        +   *   // Style the box.
        +   *   normalMaterial();
        +   *
        +   *   // Draw the box.
        +   *   box(30, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A multicolor box on a gray background. The camera angle changes when the user interacts using a mouse, trackpad, or touchscreen.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Create an options object.
        +   *   let options = {
        +   *     disableTouchActions: false,
        +   *     freeRotation: true
        +   *   };
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   // Prevent accidental touch actions on touchscreen devices
        +   *   // and enable free rotation.
        +   *   orbitControl(1, 1, 1, options);
        +   *
        +   *   // Style the box.
        +   *   normalMaterial();
        +   *
        +   *   // Draw the box.
        +   *   box(30, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  // implementation based on three.js 'orbitControls':
        +  // https://github.com/mrdoob/three.js/blob/6afb8595c0bf8b2e72818e42b64e6fe22707d896/examples/jsm/controls/OrbitControls.js#L22
        +  fn.orbitControl = function(
        +    sensitivityX,
        +    sensitivityY,
        +    sensitivityZ,
        +    options
        +  ) {
        +    this._assert3d('orbitControl');
        +    // p5._validateParameters('orbitControl', arguments);
        +
        +    const cam = this._renderer.states.curCamera;
        +
        +    if (typeof sensitivityX === 'undefined') {
        +      sensitivityX = 1;
        +    }
        +    if (typeof sensitivityY === 'undefined') {
        +      sensitivityY = sensitivityX;
        +    }
        +    if (typeof sensitivityZ === 'undefined') {
        +      sensitivityZ = 1;
        +    }
        +    if (typeof options !== 'object') {
        +      options = {};
        +    }
         
        -/**
        - * Allows the user to orbit around a 3D sketch using a mouse, trackpad, or
        - * touchscreen.
        - *
        - * 3D sketches are viewed through an imaginary camera. Calling
        - * `orbitControl()` within the <a href="#/p5/draw">draw()</a> function allows
        - * the user to change the camera’s position:
        - *
        - * ```js
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Rest of sketch.
        - * }
        - * ```
        - *
        - * Left-clicking and dragging or swipe motion will rotate the camera position
        - * about the center of the sketch. Right-clicking and dragging or multi-swipe
        - * will pan the camera position without rotation. Using the mouse wheel
        - * (scrolling) or pinch in/out will move the camera further or closer from the
        - * center of the sketch.
        - *
        - * The first three parameters, `sensitivityX`, `sensitivityY`, and
        - * `sensitivityZ`, are optional. They’re numbers that set the sketch’s
        - * sensitivity to movement along each axis. For example, calling
        - * `orbitControl(1, 2, -1)` keeps movement along the x-axis at its default
        - * value, makes the sketch twice as sensitive to movement along the y-axis,
        - * and reverses motion along the z-axis. By default, all sensitivity values
        - * are 1.
        - *
        - * The fourth parameter, `options`, is also optional. It’s an object that
        - * changes the behavior of orbiting. For example, calling
        - * `orbitControl(1, 1, 1, options)` keeps the default sensitivity values while
        - * changing the behaviors set with `options`. The object can have the
        - * following properties:
        - *
        - * ```js
        - * let options = {
        - *   // Setting this to false makes mobile interactions smoother by
        - *   // preventing accidental interactions with the page while orbiting.
        - *   // By default, it's true.
        - *   disableTouchActions: true,
        - *
        - *   // Setting this to true makes the camera always rotate in the
        - *   // direction the mouse/touch is moving.
        - *   // By default, it's false.
        - *   freeRotation: false
        - * };
        - *
        - * orbitControl(1, 1, 1, options);
        - * ```
        - *
        - * @method orbitControl
        - * @for p5
        - * @param  {Number} [sensitivityX] sensitivity to movement along the x-axis. Defaults to 1.
        - * @param  {Number} [sensitivityY] sensitivity to movement along the y-axis. Defaults to 1.
        - * @param  {Number} [sensitivityZ] sensitivity to movement along the z-axis. Defaults to 1.
        - * @param  {Object} [options] object with two optional properties, `disableTouchActions`
        - *                            and `freeRotation`. Both are `Boolean`s. `disableTouchActions`
        - *                            defaults to `true` and `freeRotation` defaults to `false`.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A multicolor box on a gray background. The camera angle changes when the user interacts using a mouse, trackpad, or touchscreen.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the box.
        - *   normalMaterial();
        - *
        - *   // Draw the box.
        - *   box(30, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A multicolor box on a gray background. The camera angle changes when the user interacts using a mouse, trackpad, or touchscreen.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   // Make the interactions 3X sensitive.
        - *   orbitControl(3, 3, 3);
        - *
        - *   // Style the box.
        - *   normalMaterial();
        - *
        - *   // Draw the box.
        - *   box(30, 50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A multicolor box on a gray background. The camera angle changes when the user interacts using a mouse, trackpad, or touchscreen.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Create an options object.
        - *   let options = {
        - *     disableTouchActions: false,
        - *     freeRotation: true
        - *   };
        - *
        - *   // Enable orbiting with the mouse.
        - *   // Prevent accidental touch actions on touchscreen devices
        - *   // and enable free rotation.
        - *   orbitControl(1, 1, 1, options);
        - *
        - *   // Style the box.
        - *   normalMaterial();
        - *
        - *   // Draw the box.
        - *   box(30, 50);
        - * }
        - * </code>
        - * </div>
        - */
        +    // default right-mouse and mouse-wheel behaviors (context menu and scrolling,
        +    // respectively) are disabled here to allow use of those events for panning and
        +    // zooming. However, whether or not to disable touch actions is an option.
         
        -// implementation based on three.js 'orbitControls':
        -// https://github.com/mrdoob/three.js/blob/6afb8595c0bf8b2e72818e42b64e6fe22707d896/examples/jsm/controls/OrbitControls.js#L22
        -p5.prototype.orbitControl = function(
        -  sensitivityX,
        -  sensitivityY,
        -  sensitivityZ,
        -  options
        -) {
        -  this._assert3d('orbitControl');
        -  p5._validateParameters('orbitControl', arguments);
        -
        -  const cam = this._renderer._curCamera;
        -
        -  if (typeof sensitivityX === 'undefined') {
        -    sensitivityX = 1;
        -  }
        -  if (typeof sensitivityY === 'undefined') {
        -    sensitivityY = sensitivityX;
        -  }
        -  if (typeof sensitivityZ === 'undefined') {
        -    sensitivityZ = 1;
        -  }
        -  if (typeof options !== 'object') {
        -    options = {};
        -  }
        -
        -  // default right-mouse and mouse-wheel behaviors (context menu and scrolling,
        -  // respectively) are disabled here to allow use of those events for panning and
        -  // zooming. However, whether or not to disable touch actions is an option.
        -
        -  // disable context menu for canvas element and add 'contextMenuDisabled'
        -  // flag to p5 instance
        -  if (this.contextMenuDisabled !== true) {
        -    this.canvas.oncontextmenu = () => false;
        -    this._setProperty('contextMenuDisabled', true);
        -  }
        -
        -  // disable default scrolling behavior on the canvas element and add
        -  // 'wheelDefaultDisabled' flag to p5 instance
        -  if (this.wheelDefaultDisabled !== true) {
        -    this.canvas.onwheel = () => false;
        -    this._setProperty('wheelDefaultDisabled', true);
        -  }
        -
        -  // disable default touch behavior on the canvas element and add
        -  // 'touchActionsDisabled' flag to p5 instance
        -  const { disableTouchActions = true } = options;
        -  if (this.touchActionsDisabled !== true && disableTouchActions) {
        -    this.canvas.style['touch-action'] = 'none';
        -    this._setProperty('touchActionsDisabled', true);
        -  }
        -
        -  // If option.freeRotation is true, the camera always rotates freely in the direction
        -  // the pointer moves. default value is false (normal behavior)
        -  const { freeRotation = false } = options;
        -
        -  // get moved touches.
        -  const movedTouches = [];
        -
        -  this.touches.forEach(curTouch => {
        -    this._renderer.prevTouches.forEach(prevTouch => {
        -      if (curTouch.id === prevTouch.id) {
        -        const movedTouch = {
        -          x: curTouch.x,
        -          y: curTouch.y,
        -          px: prevTouch.x,
        -          py: prevTouch.y
        -        };
        -        movedTouches.push(movedTouch);
        -      }
        -    });
        -  });
        -
        -  this._renderer.prevTouches = this.touches;
        -
        -  // The idea of using damping is based on the following website. thank you.
        -  // https://github.com/freshfork/p5.EasyCam/blob/9782964680f6a5c4c9bee825c475d9f2021d5134/p5.easycam.js#L1124
        -
        -  // variables for interaction
        -  let deltaRadius = 0;
        -  let deltaTheta = 0;
        -  let deltaPhi = 0;
        -  let moveDeltaX = 0;
        -  let moveDeltaY = 0;
        -  // constants for dampingProcess
        -  const damping = 0.85;
        -  const rotateAccelerationFactor = 0.6;
        -  const moveAccelerationFactor = 0.15;
        -  // For touches, the appropriate scale is different
        -  // because the distance difference is multiplied.
        -  const mouseZoomScaleFactor = 0.01;
        -  const touchZoomScaleFactor = 0.0004;
        -  const scaleFactor = this.height < this.width ? this.height : this.width;
        -  // Flag whether the mouse or touch pointer is inside the canvas
        -  let pointersInCanvas = false;
        -
        -  // calculate and determine flags and variables.
        -  if (movedTouches.length > 0) {
        -    /* for touch */
        -    // if length === 1, rotate
        -    // if length > 1, zoom and move
        -
        -    // for touch, it is calculated based on one moved touch pointer position.
        -    pointersInCanvas =
        -      movedTouches[0].x > 0 && movedTouches[0].x < this.width &&
        -      movedTouches[0].y > 0 && movedTouches[0].y < this.height;
        -
        -    if (movedTouches.length === 1) {
        -      const t = movedTouches[0];
        -      deltaTheta = -sensitivityX * (t.x - t.px) / scaleFactor;
        -      deltaPhi = sensitivityY * (t.y - t.py) / scaleFactor;
        -    } else {
        -      const t0 = movedTouches[0];
        -      const t1 = movedTouches[1];
        -      const distWithTouches = Math.hypot(t0.x - t1.x, t0.y - t1.y);
        -      const prevDistWithTouches = Math.hypot(t0.px - t1.px, t0.py - t1.py);
        -      const changeDist = distWithTouches - prevDistWithTouches;
        -      // move the camera farther when the distance between the two touch points
        -      // decreases, move the camera closer when it increases.
        -      deltaRadius = -changeDist * sensitivityZ * touchZoomScaleFactor;
        -      // Move the center of the camera along with the movement of
        -      // the center of gravity of the two touch points.
        -      moveDeltaX = 0.5 * (t0.x + t1.x) - 0.5 * (t0.px + t1.px);
        -      moveDeltaY = 0.5 * (t0.y + t1.y) - 0.5 * (t0.py + t1.py);
        +    // disable context menu for canvas element and add 'contextMenuDisabled'
        +    // flag to p5 instance
        +    if (this.contextMenuDisabled !== true) {
        +      this.canvas.oncontextmenu = () => false;
        +      this.contextMenuDisabled = true;
        +    }
        +
        +    // disable default scrolling behavior on the canvas element and add
        +    // 'wheelDefaultDisabled' flag to p5 instance
        +    if (this.wheelDefaultDisabled !== true) {
        +      this.canvas.onwheel = () => false;
        +      this.wheelDefaultDisabled = true;
        +    }
        +
        +    // disable default touch behavior on the canvas element and add
        +    // 'touchActionsDisabled' flag to p5 instance
        +    const { disableTouchActions = true } = options;
        +    if (this.touchActionsDisabled !== true && disableTouchActions) {
        +      this.canvas.style['touch-action'] = 'none';
        +      this.touchActionsDisabled = true;
             }
        -    if (this.touches.length > 0) {
        -      if (pointersInCanvas) {
        -        // Initiate an interaction if touched in the canvas
        -        this._renderer.executeRotateAndMove = true;
        -        this._renderer.executeZoom = true;
        +
        +    // If option.freeRotation is true, the camera always rotates freely in the direction
        +    // the pointer moves. default value is false (normal behavior)
        +    const { freeRotation = false } = options;
        +
        +    // get moved touches.
        +    const movedTouches = [];
        +
        +    this.touches.forEach(curTouch => {
        +      this._renderer.prevTouches.forEach(prevTouch => {
        +        if (curTouch.id === prevTouch.id) {
        +          const movedTouch = {
        +            x: curTouch.x,
        +            y: curTouch.y,
        +            px: prevTouch.x,
        +            py: prevTouch.y
        +          };
        +          movedTouches.push(movedTouch);
        +        }
        +      });
        +    });
        +
        +    this._renderer.prevTouches = this.touches;
        +
        +    // The idea of using damping is based on the following website. thank you.
        +    // https://github.com/freshfork/p5.EasyCam/blob/9782964680f6a5c4c9bee825c475d9f2021d5134/p5.easycam.js#L1124
        +
        +    // variables for interaction
        +    let deltaRadius = 0;
        +    let deltaTheta = 0;
        +    let deltaPhi = 0;
        +    let moveDeltaX = 0;
        +    let moveDeltaY = 0;
        +    // constants for dampingProcess
        +    const damping = 0.85;
        +    const rotateAccelerationFactor = 0.6;
        +    const moveAccelerationFactor = 0.15;
        +    // For touches, the appropriate scale is different
        +    // because the distance difference is multiplied.
        +    const mouseZoomScaleFactor = 0.01;
        +    const touchZoomScaleFactor = 0.0004;
        +    const scaleFactor = this.height < this.width ? this.height : this.width;
        +    // Flag whether the mouse or touch pointer is inside the canvas
        +    let pointersInCanvas = false;
        +
        +    // calculate and determine flags and variables.
        +    if (movedTouches.length > 0) {
        +      /* for touch */
        +      // if length === 1, rotate
        +      // if length > 1, zoom and move
        +
        +      // for touch, it is calculated based on one moved touch pointer position.
        +      pointersInCanvas =
        +        movedTouches[0].x > 0 && movedTouches[0].x < this.width &&
        +        movedTouches[0].y > 0 && movedTouches[0].y < this.height;
        +
        +      if (movedTouches.length === 1) {
        +        const t = movedTouches[0];
        +        deltaTheta = -sensitivityX * (t.x - t.px) / scaleFactor;
        +        deltaPhi = sensitivityY * (t.y - t.py) / scaleFactor;
        +      } else {
        +        const t0 = movedTouches[0];
        +        const t1 = movedTouches[1];
        +        const distWithTouches = Math.hypot(t0.x - t1.x, t0.y - t1.y);
        +        const prevDistWithTouches = Math.hypot(t0.px - t1.px, t0.py - t1.py);
        +        const changeDist = distWithTouches - prevDistWithTouches;
        +        // move the camera farther when the distance between the two touch points
        +        // decreases, move the camera closer when it increases.
        +        deltaRadius = -changeDist * sensitivityZ * touchZoomScaleFactor;
        +        // Move the center of the camera along with the movement of
        +        // the center of gravity of the two touch points.
        +        moveDeltaX = 0.5 * (t0.x + t1.x) - 0.5 * (t0.px + t1.px);
        +        moveDeltaY = 0.5 * (t0.y + t1.y) - 0.5 * (t0.py + t1.py);
        +      }
        +      if (this.touches.length > 0) {
        +        if (pointersInCanvas) {
        +          // Initiate an interaction if touched in the canvas
        +          this._renderer.executeRotateAndMove = true;
        +          this._renderer.executeZoom = true;
        +        }
        +      } else {
        +        // End an interaction when the touch is released
        +        this._renderer.executeRotateAndMove = false;
        +        this._renderer.executeZoom = false;
               }
             } else {
        -      // End an interaction when the touch is released
        -      this._renderer.executeRotateAndMove = false;
        -      this._renderer.executeZoom = false;
        +      /* for mouse */
        +      // if wheelDeltaY !== 0, zoom
        +      // if mouseLeftButton is down, rotate
        +      // if mouseRightButton is down, move
        +
        +      // For mouse, it is calculated based on the mouse position.
        +      pointersInCanvas =
        +        (this.mouseX > 0 && this.mouseX < this.width) &&
        +        (this.mouseY > 0 && this.mouseY < this.height);
        +
        +      if (this._mouseWheelDeltaY !== 0) {
        +        // zoom the camera depending on the value of _mouseWheelDeltaY.
        +        // move away if positive, move closer if negative
        +        deltaRadius = Math.sign(this._mouseWheelDeltaY) * sensitivityZ;
        +        deltaRadius *= mouseZoomScaleFactor;
        +        this._mouseWheelDeltaY = 0;
        +        // start zoom when the mouse is wheeled within the canvas.
        +        if (pointersInCanvas) this._renderer.executeZoom = true;
        +      } else {
        +        // quit zoom when you stop wheeling.
        +        this._renderer.executeZoom = false;
        +      }
        +      if (this.mouseIsPressed) {
        +        if (this.mouseButton.left) {
        +          deltaTheta = -sensitivityX * this.movedX / scaleFactor;
        +          deltaPhi = sensitivityY * this.movedY / scaleFactor;
        +        } else if (this.mouseButton.right) {
        +          moveDeltaX = this.movedX;
        +          moveDeltaY =  this.movedY * cam.yScale;
        +        }
        +        // start rotate and move when mouse is pressed within the canvas.
        +        if (pointersInCanvas) this._renderer.executeRotateAndMove = true;
        +      } else {
        +        // quit rotate and move if mouse is released.
        +        this._renderer.executeRotateAndMove = false;
        +      }
             }
        -  } else {
        -    /* for mouse */
        -    // if wheelDeltaY !== 0, zoom
        -    // if mouseLeftButton is down, rotate
        -    // if mouseRightButton is down, move
        -
        -    // For mouse, it is calculated based on the mouse position.
        -    pointersInCanvas =
        -      (this.mouseX > 0 && this.mouseX < this.width) &&
        -      (this.mouseY > 0 && this.mouseY < this.height);
        -
        -    if (this._mouseWheelDeltaY !== 0) {
        -      // zoom the camera depending on the value of _mouseWheelDeltaY.
        -      // move away if positive, move closer if negative
        -      deltaRadius = Math.sign(this._mouseWheelDeltaY) * sensitivityZ;
        -      deltaRadius *= mouseZoomScaleFactor;
        -      this._mouseWheelDeltaY = 0;
        -      // start zoom when the mouse is wheeled within the canvas.
        -      if (pointersInCanvas) this._renderer.executeZoom = true;
        -    } else {
        -      // quit zoom when you stop wheeling.
        -      this._renderer.executeZoom = false;
        +
        +    // interactions
        +
        +    // zoom process
        +    if (deltaRadius !== 0 && this._renderer.executeZoom) {
        +      // accelerate zoom velocity
        +      this._renderer.zoomVelocity += deltaRadius;
             }
        -    if (this.mouseIsPressed) {
        -      if (this.mouseButton === this.LEFT) {
        -        deltaTheta = -sensitivityX * this.movedX / scaleFactor;
        -        deltaPhi = sensitivityY * this.movedY / scaleFactor;
        -      } else if (this.mouseButton === this.RIGHT) {
        -        moveDeltaX = this.movedX;
        -        moveDeltaY =  this.movedY * cam.yScale;
        +    if (Math.abs(this._renderer.zoomVelocity) > 0.001) {
        +      // if freeRotation is true, we use _orbitFree() instead of _orbit()
        +      if (freeRotation) {
        +        cam._orbitFree(
        +          0, 0, this._renderer.zoomVelocity
        +        );
        +      } else {
        +        cam._orbit(
        +          0, 0, this._renderer.zoomVelocity
        +        );
        +      }
        +      // In orthogonal projection, the scale does not change even if
        +      // the distance to the gaze point is changed, so the projection matrix
        +      // needs to be modified.
        +      if (cam.projMatrix.mat4[15] !== 0) {
        +        cam.projMatrix.mat4[0] *= Math.pow(
        +          10, -this._renderer.zoomVelocity
        +        );
        +        cam.projMatrix.mat4[5] *= Math.pow(
        +          10, -this._renderer.zoomVelocity
        +        );
        +        // modify uPMatrix
        +        this._renderer.states.uPMatrix.mat4[0] = cam.projMatrix.mat4[0];
        +        this._renderer.states.uPMatrix.mat4[5] = cam.projMatrix.mat4[5];
               }
        -      // start rotate and move when mouse is pressed within the canvas.
        -      if (pointersInCanvas) this._renderer.executeRotateAndMove = true;
        +      // damping
        +      this._renderer.zoomVelocity *= damping;
             } else {
        -      // quit rotate and move if mouse is released.
        -      this._renderer.executeRotateAndMove = false;
        +      this._renderer.zoomVelocity = 0;
             }
        -  }
        -
        -  // interactions
        -
        -  // zoom process
        -  if (deltaRadius !== 0 && this._renderer.executeZoom) {
        -    // accelerate zoom velocity
        -    this._renderer.zoomVelocity += deltaRadius;
        -  }
        -  if (Math.abs(this._renderer.zoomVelocity) > 0.001) {
        -    // if freeRotation is true, we use _orbitFree() instead of _orbit()
        -    if (freeRotation) {
        -      cam._orbitFree(
        -        0, 0, this._renderer.zoomVelocity
        +
        +    // rotate process
        +    if ((deltaTheta !== 0 || deltaPhi !== 0) &&
        +    this._renderer.executeRotateAndMove) {
        +      // accelerate rotate velocity
        +      this._renderer.rotateVelocity.add(
        +        deltaTheta * rotateAccelerationFactor,
        +        deltaPhi * rotateAccelerationFactor
               );
        +    }
        +    if (this._renderer.rotateVelocity.magSq() > 0.000001) {
        +      // if freeRotation is true, the camera always rotates freely in the direction the pointer moves
        +      if (freeRotation) {
        +        cam._orbitFree(
        +          -this._renderer.rotateVelocity.x,
        +          this._renderer.rotateVelocity.y,
        +          0
        +        );
        +      } else {
        +        cam._orbit(
        +          this._renderer.rotateVelocity.x,
        +          this._renderer.rotateVelocity.y,
        +          0
        +        );
        +      }
        +      // damping
        +      this._renderer.rotateVelocity.mult(damping);
             } else {
        -      cam._orbit(
        -        0, 0, this._renderer.zoomVelocity
        -      );
        +      this._renderer.rotateVelocity.set(0, 0);
             }
        -    // In orthogonal projection, the scale does not change even if
        -    // the distance to the gaze point is changed, so the projection matrix
        -    // needs to be modified.
        -    if (cam.projMatrix.mat4[15] !== 0) {
        -      cam.projMatrix.mat4[0] *= Math.pow(
        -        10, -this._renderer.zoomVelocity
        -      );
        -      cam.projMatrix.mat4[5] *= Math.pow(
        -        10, -this._renderer.zoomVelocity
        +
        +    // move process
        +    if ((moveDeltaX !== 0 || moveDeltaY !== 0) &&
        +    this._renderer.executeRotateAndMove) {
        +      // Normalize movement distance
        +      const ndcX = moveDeltaX * 2/this.width;
        +      const ndcY = -moveDeltaY * 2/this.height;
        +      // accelerate move velocity
        +      this._renderer.moveVelocity.add(
        +        ndcX * moveAccelerationFactor,
        +        ndcY * moveAccelerationFactor
               );
        -      // modify uPMatrix
        -      this._renderer.uPMatrix.mat4[0] = cam.projMatrix.mat4[0];
        -      this._renderer.uPMatrix.mat4[5] = cam.projMatrix.mat4[5];
             }
        -    // damping
        -    this._renderer.zoomVelocity *= damping;
        -  } else {
        -    this._renderer.zoomVelocity = 0;
        -  }
        -
        -  // rotate process
        -  if ((deltaTheta !== 0 || deltaPhi !== 0) &&
        -  this._renderer.executeRotateAndMove) {
        -    // accelerate rotate velocity
        -    this._renderer.rotateVelocity.add(
        -      deltaTheta * rotateAccelerationFactor,
        -      deltaPhi * rotateAccelerationFactor
        -    );
        -  }
        -  if (this._renderer.rotateVelocity.magSq() > 0.000001) {
        -    // if freeRotation is true, the camera always rotates freely in the direction the pointer moves
        -    if (freeRotation) {
        -      cam._orbitFree(
        -        -this._renderer.rotateVelocity.x,
        -        this._renderer.rotateVelocity.y,
        -        0
        -      );
        -    } else {
        -      cam._orbit(
        -        this._renderer.rotateVelocity.x,
        -        this._renderer.rotateVelocity.y,
        -        0
        +    if (this._renderer.moveVelocity.magSq() > 0.000001) {
        +      // Translate the camera so that the entire object moves
        +      // perpendicular to the line of sight when the mouse is moved
        +      // or when the centers of gravity of the two touch pointers move.
        +      const local = cam._getLocalAxes();
        +
        +      // Calculate the z coordinate in the view coordinates of
        +      // the center, that is, the distance to the view point
        +      const diffX = cam.eyeX - cam.centerX;
        +      const diffY = cam.eyeY - cam.centerY;
        +      const diffZ = cam.eyeZ - cam.centerZ;
        +      const viewZ = Math.sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ);
        +
        +      // position vector of the center.
        +      let cv = new Vector(cam.centerX, cam.centerY, cam.centerZ);
        +
        +      // Calculate the normalized device coordinates of the center.
        +      cv = cam.cameraMatrix.multiplyPoint(cv);
        +      cv = this._renderer.states.uPMatrix.multiplyAndNormalizePoint(cv);
        +
        +      // Move the center by this distance
        +      // in the normalized device coordinate system.
        +      cv.x -= this._renderer.moveVelocity.x;
        +      cv.y -= this._renderer.moveVelocity.y;
        +
        +      // Calculate the translation vector
        +      // in the direction perpendicular to the line of sight of center.
        +      let dx, dy;
        +      const uP = this._renderer.states.uPMatrix.mat4;
        +
        +      if (uP[15] === 0) {
        +        dx = ((uP[8] + cv.x)/uP[0]) * viewZ;
        +        dy = ((uP[9] + cv.y)/uP[5]) * viewZ;
        +      } else {
        +        dx = (cv.x - uP[12])/uP[0];
        +        dy = (cv.y - uP[13])/uP[5];
        +      }
        +
        +      // translate the camera.
        +      cam.setPosition(
        +        cam.eyeX + dx * local.x[0] + dy * local.y[0],
        +        cam.eyeY + dx * local.x[1] + dy * local.y[1],
        +        cam.eyeZ + dx * local.x[2] + dy * local.y[2]
               );
        -    }
        -    // damping
        -    this._renderer.rotateVelocity.mult(damping);
        -  } else {
        -    this._renderer.rotateVelocity.set(0, 0);
        -  }
        -
        -  // move process
        -  if ((moveDeltaX !== 0 || moveDeltaY !== 0) &&
        -  this._renderer.executeRotateAndMove) {
        -    // Normalize movement distance
        -    const ndcX = moveDeltaX * 2/this.width;
        -    const ndcY = -moveDeltaY * 2/this.height;
        -    // accelerate move velocity
        -    this._renderer.moveVelocity.add(
        -      ndcX * moveAccelerationFactor,
        -      ndcY * moveAccelerationFactor
        -    );
        -  }
        -  if (this._renderer.moveVelocity.magSq() > 0.000001) {
        -    // Translate the camera so that the entire object moves
        -    // perpendicular to the line of sight when the mouse is moved
        -    // or when the centers of gravity of the two touch pointers move.
        -    const local = cam._getLocalAxes();
        -
        -    // Calculate the z coordinate in the view coordinates of
        -    // the center, that is, the distance to the view point
        -    const diffX = cam.eyeX - cam.centerX;
        -    const diffY = cam.eyeY - cam.centerY;
        -    const diffZ = cam.eyeZ - cam.centerZ;
        -    const viewZ = Math.sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ);
        -
        -    // position vector of the center.
        -    let cv = new p5.Vector(cam.centerX, cam.centerY, cam.centerZ);
        -
        -    // Calculate the normalized device coordinates of the center.
        -    cv = cam.cameraMatrix.multiplyPoint(cv);
        -    cv = this._renderer.uPMatrix.multiplyAndNormalizePoint(cv);
        -
        -    // Move the center by this distance
        -    // in the normalized device coordinate system.
        -    cv.x -= this._renderer.moveVelocity.x;
        -    cv.y -= this._renderer.moveVelocity.y;
        -
        -    // Calculate the translation vector
        -    // in the direction perpendicular to the line of sight of center.
        -    let dx, dy;
        -    const uP = this._renderer.uPMatrix.mat4;
        -
        -    if (uP[15] === 0) {
        -      dx = ((uP[8] + cv.x)/uP[0]) * viewZ;
        -      dy = ((uP[9] + cv.y)/uP[5]) * viewZ;
        +      // damping
        +      this._renderer.moveVelocity.mult(damping);
             } else {
        -      dx = (cv.x - uP[12])/uP[0];
        -      dy = (cv.y - uP[13])/uP[5];
        +      this._renderer.moveVelocity.set(0, 0);
             }
         
        -    // translate the camera.
        -    cam.setPosition(
        -      cam.eyeX + dx * local.x[0] + dy * local.y[0],
        -      cam.eyeY + dx * local.x[1] + dy * local.y[1],
        -      cam.eyeZ + dx * local.x[2] + dy * local.y[2]
        -    );
        -    // damping
        -    this._renderer.moveVelocity.mult(damping);
        -  } else {
        -    this._renderer.moveVelocity.set(0, 0);
        -  }
        +    return this;
        +  };
         
        -  return this;
        -};
         
        +  /**
        +   * Adds a grid and an axes icon to clarify orientation in 3D sketches.
        +   *
        +   * `debugMode()` adds a grid that shows where the “ground” is in a sketch. By
        +   * default, the grid will run through the origin `(0, 0, 0)` of the sketch
        +   * along the XZ plane. `debugMode()` also adds an axes icon that points along
        +   * the positive x-, y-, and z-axes. Calling `debugMode()` displays the grid
        +   * and axes icon with their default size and position.
        +   *
        +   * There are four ways to call `debugMode()` with optional parameters to
        +   * customize the debugging environment.
        +   *
        +   * The first way to call `debugMode()` has one parameter, `mode`. If the
        +   * system constant `GRID` is passed, as in `debugMode(GRID)`, then the grid
        +   * will be displayed and the axes icon will be hidden. If the constant `AXES`
        +   * is passed, as in `debugMode(AXES)`, then the axes icon will be displayed
        +   * and the grid will be hidden.
        +   *
        +   * The second way to call `debugMode()` has six parameters. The first
        +   * parameter, `mode`, selects either `GRID` or `AXES` to be displayed. The
        +   * next five parameters, `gridSize`, `gridDivisions`, `xOff`, `yOff`, and
        +   * `zOff` are optional. They’re numbers that set the appearance of the grid
        +   * (`gridSize` and `gridDivisions`) and the placement of the axes icon
        +   * (`xOff`, `yOff`, and `zOff`). For example, calling
        +   * `debugMode(20, 5, 10, 10, 10)` sets the `gridSize` to 20 pixels, the number
        +   * of `gridDivisions` to 5, and offsets the axes icon by 10 pixels along the
        +   * x-, y-, and z-axes.
        +   *
        +   * The third way to call `debugMode()` has five parameters. The first
        +   * parameter, `mode`, selects either `GRID` or `AXES` to be displayed. The
        +   * next four parameters, `axesSize`, `xOff`, `yOff`, and `zOff` are optional.
        +   * They’re numbers that set the appearance of the size of the axes icon
        +   * (`axesSize`) and its placement (`xOff`, `yOff`, and `zOff`).
        +   *
        +   * The fourth way to call `debugMode()` has nine optional parameters. The
        +   * first five parameters, `gridSize`, `gridDivisions`, `gridXOff`, `gridYOff`,
        +   * and `gridZOff` are numbers that set the appearance of the grid. For
        +   * example, calling `debugMode(100, 5, 0, 0, 0)` sets the `gridSize` to 100,
        +   * the number of `gridDivisions` to 5, and sets all the offsets to 0 so that
        +   * the grid is centered at the origin. The next four parameters, `axesSize`,
        +   * `xOff`, `yOff`, and `zOff` are numbers that set the appearance of the size
        +   * of the axes icon (`axesSize`) and its placement (`axesXOff`, `axesYOff`,
        +   * and `axesZOff`). For example, calling
        +   * `debugMode(100, 5, 0, 0, 0, 50, 10, 10, 10)` sets the `gridSize` to 100,
        +   * the number of `gridDivisions` to 5, and sets all the offsets to 0 so that
        +   * the grid is centered at the origin. It then sets the `axesSize` to 50 and
        +   * offsets the icon 10 pixels along each axis.
        +   *
        +   * @method debugMode
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Enable debug mode.
        +   *   debugMode();
        +   *
        +   *   describe('A multicolor box on a gray background. A grid and axes icon are displayed near the box.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the box.
        +   *   normalMaterial();
        +   *
        +   *   // Draw the box.
        +   *   box(20, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Enable debug mode.
        +   *   // Only display the axes icon.
        +   *   debugMode(AXES);
        +   *
        +   *   describe('A multicolor box on a gray background. A grid and axes icon are displayed near the box.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the box.
        +   *   normalMaterial();
        +   *
        +   *   // Draw the box.
        +   *   box(20, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Enable debug mode.
        +   *   // Only display the grid and customize it:
        +   *   // - size: 50
        +   *   // - divisions: 10
        +   *   // - offsets: 0, 20, 0
        +   *   debugMode(GRID, 50, 10, 0, 20, 0);
        +   *
        +   *   describe('A multicolor box on a gray background. A grid is displayed below the box.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the box.
        +   *   normalMaterial();
        +   *
        +   *   // Draw the box.
        +   *   box(20, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Enable debug mode.
        +   *   // Display the grid and axes icon and customize them:
        +   *   // Grid
        +   *   // ----
        +   *   // - size: 50
        +   *   // - divisions: 10
        +   *   // - offsets: 0, 20, 0
        +   *   // Axes
        +   *   // ----
        +   *   // - size: 50
        +   *   // - offsets: 0, 0, 0
        +   *   debugMode(50, 10, 0, 20, 0, 50, 0, 0, 0);
        +   *
        +   *   describe('A multicolor box on a gray background. A grid is displayed below the box. An axes icon is displayed at the center of the box.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the box.
        +   *   normalMaterial();
        +   *
        +   *   // Draw the box.
        +   *   box(20, 40);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method debugMode
        +   * @param {(GRID|AXES)} mode either GRID or AXES
        +   */
        +
        +  /**
        +   * @method debugMode
        +   * @param {(GRID|AXES)} mode
        +   * @param {Number} [gridSize] side length of the grid.
        +   * @param {Number} [gridDivisions] number of divisions in the grid.
        +   * @param {Number} [xOff] offset from origin along the x-axis.
        +   * @param {Number} [yOff] offset from origin along the y-axis.
        +   * @param {Number} [zOff] offset from origin along the z-axis.
        +   */
        +
        +  /**
        +   * @method debugMode
        +   * @param {(GRID|AXES)} mode
        +   * @param {Number} [axesSize] length of axes icon markers.
        +   * @param {Number} [xOff]
        +   * @param {Number} [yOff]
        +   * @param {Number} [zOff]
        +   */
        +
        +  /**
        +   * @method debugMode
        +   * @param {Number} [gridSize]
        +   * @param {Number} [gridDivisions]
        +   * @param {Number} [gridXOff] grid offset from the origin along the x-axis.
        +   * @param {Number} [gridYOff] grid offset from the origin along the y-axis.
        +   * @param {Number} [gridZOff] grid offset from the origin along the z-axis.
        +   * @param {Number} [axesSize]
        +   * @param {Number} [axesXOff] axes icon offset from the origin along the x-axis.
        +   * @param {Number} [axesYOff] axes icon offset from the origin along the y-axis.
        +   * @param {Number} [axesZOff] axes icon offset from the origin along the z-axis.
        +   */
        +
        +  fn.debugMode = function(...args) {
        +    this._assert3d('debugMode');
        +    // p5._validateParameters('debugMode', args);
        +
        +    // start by removing existing 'post' registered debug methods
        +    for (let i = this._registeredMethods.post.length - 1; i >= 0; i--) {
        +      // test for equality...
        +      if (
        +        this._registeredMethods.post[i].toString() === this._grid().toString() ||
        +        this._registeredMethods.post[i].toString() === this._axesIcon().toString()
        +      ) {
        +        this._registeredMethods.post.splice(i, 1);
        +      }
        +    }
         
        -/**
        - * Adds a grid and an axes icon to clarify orientation in 3D sketches.
        - *
        - * `debugMode()` adds a grid that shows where the “ground” is in a sketch. By
        - * default, the grid will run through the origin `(0, 0, 0)` of the sketch
        - * along the XZ plane. `debugMode()` also adds an axes icon that points along
        - * the positive x-, y-, and z-axes. Calling `debugMode()` displays the grid
        - * and axes icon with their default size and position.
        - *
        - * There are four ways to call `debugMode()` with optional parameters to
        - * customize the debugging environment.
        - *
        - * The first way to call `debugMode()` has one parameter, `mode`. If the
        - * system constant `GRID` is passed, as in `debugMode(GRID)`, then the grid
        - * will be displayed and the axes icon will be hidden. If the constant `AXES`
        - * is passed, as in `debugMode(AXES)`, then the axes icon will be displayed
        - * and the grid will be hidden.
        - *
        - * The second way to call `debugMode()` has six parameters. The first
        - * parameter, `mode`, selects either `GRID` or `AXES` to be displayed. The
        - * next five parameters, `gridSize`, `gridDivisions`, `xOff`, `yOff`, and
        - * `zOff` are optional. They’re numbers that set the appearance of the grid
        - * (`gridSize` and `gridDivisions`) and the placement of the axes icon
        - * (`xOff`, `yOff`, and `zOff`). For example, calling
        - * `debugMode(20, 5, 10, 10, 10)` sets the `gridSize` to 20 pixels, the number
        - * of `gridDivisions` to 5, and offsets the axes icon by 10 pixels along the
        - * x-, y-, and z-axes.
        - *
        - * The third way to call `debugMode()` has five parameters. The first
        - * parameter, `mode`, selects either `GRID` or `AXES` to be displayed. The
        - * next four parameters, `axesSize`, `xOff`, `yOff`, and `zOff` are optional.
        - * They’re numbers that set the appearance of the size of the axes icon
        - * (`axesSize`) and its placement (`xOff`, `yOff`, and `zOff`).
        - *
        - * The fourth way to call `debugMode()` has nine optional parameters. The
        - * first five parameters, `gridSize`, `gridDivisions`, `gridXOff`, `gridYOff`,
        - * and `gridZOff` are numbers that set the appearance of the grid. For
        - * example, calling `debugMode(100, 5, 0, 0, 0)` sets the `gridSize` to 100,
        - * the number of `gridDivisions` to 5, and sets all the offsets to 0 so that
        - * the grid is centered at the origin. The next four parameters, `axesSize`,
        - * `xOff`, `yOff`, and `zOff` are numbers that set the appearance of the size
        - * of the axes icon (`axesSize`) and its placement (`axesXOff`, `axesYOff`,
        - * and `axesZOff`). For example, calling
        - * `debugMode(100, 5, 0, 0, 0, 50, 10, 10, 10)` sets the `gridSize` to 100,
        - * the number of `gridDivisions` to 5, and sets all the offsets to 0 so that
        - * the grid is centered at the origin. It then sets the `axesSize` to 50 and
        - * offsets the icon 10 pixels along each axis.
        - *
        - * @method debugMode
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Enable debug mode.
        - *   debugMode();
        - *
        - *   describe('A multicolor box on a gray background. A grid and axes icon are displayed near the box.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the box.
        - *   normalMaterial();
        - *
        - *   // Draw the box.
        - *   box(20, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Enable debug mode.
        - *   // Only display the axes icon.
        - *   debugMode(AXES);
        - *
        - *   describe('A multicolor box on a gray background. A grid and axes icon are displayed near the box.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the box.
        - *   normalMaterial();
        - *
        - *   // Draw the box.
        - *   box(20, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Enable debug mode.
        - *   // Only display the grid and customize it:
        - *   // - size: 50
        - *   // - divisions: 10
        - *   // - offsets: 0, 20, 0
        - *   debugMode(GRID, 50, 10, 0, 20, 0);
        - *
        - *   describe('A multicolor box on a gray background. A grid is displayed below the box.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the box.
        - *   normalMaterial();
        - *
        - *   // Draw the box.
        - *   box(20, 40);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Enable debug mode.
        - *   // Display the grid and axes icon and customize them:
        - *   // Grid
        - *   // ----
        - *   // - size: 50
        - *   // - divisions: 10
        - *   // - offsets: 0, 20, 0
        - *   // Axes
        - *   // ----
        - *   // - size: 50
        - *   // - offsets: 0, 0, 0
        - *   debugMode(50, 10, 0, 20, 0, 50, 0, 0, 0);
        - *
        - *   describe('A multicolor box on a gray background. A grid is displayed below the box. An axes icon is displayed at the center of the box.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the box.
        - *   normalMaterial();
        - *
        - *   // Draw the box.
        - *   box(20, 40);
        - * }
        - * </code>
        - * </div>
        - */
        +    // then add new debugMode functions according to the argument list
        +    if (args[0] === constants.GRID) {
        +      this.registerMethod(
        +        'post',
        +        this._grid(args[1], args[2], args[3], args[4], args[5])
        +      );
        +    } else if (args[0] === constants.AXES) {
        +      this.registerMethod(
        +        'post',
        +        this._axesIcon(args[1], args[2], args[3], args[4])
        +      );
        +    } else {
        +      this.registerMethod(
        +        'post',
        +        this._grid(args[0], args[1], args[2], args[3], args[4])
        +      );
        +      this.registerMethod(
        +        'post',
        +        this._axesIcon(args[5], args[6], args[7], args[8])
        +      );
        +    }
        +  };
         
        -/**
        - * @method debugMode
        - * @param {Constant} mode either GRID or AXES
        - */
        +  /**
        +   * Turns off <a href="#/p5/debugMode">debugMode()</a> in a 3D sketch.
        +   *
        +   * @method noDebugMode
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Enable debug mode.
        +   *   debugMode();
        +   *
        +   *   describe('A multicolor box on a gray background. A grid and axes icon are displayed near the box. They disappear when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the box.
        +   *   normalMaterial();
        +   *
        +   *   // Draw the box.  box(20, 40);
        +   * }
        +   *
        +   * // Disable debug mode when the user double-clicks.
        +   * function doubleClicked() {
        +   *   noDebugMode();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noDebugMode = function() {
        +    this._assert3d('noDebugMode');
        +
        +    // start by removing existing 'post' registered debug methods
        +    for (let i = this._registeredMethods.post.length - 1; i >= 0; i--) {
        +      // test for equality...
        +      if (
        +        this._registeredMethods.post[i].toString() === this._grid().toString() ||
        +        this._registeredMethods.post[i].toString() === this._axesIcon().toString()
        +      ) {
        +        this._registeredMethods.post.splice(i, 1);
        +      }
        +    }
        +  };
         
        -/**
        - * @method debugMode
        - * @param {Constant} mode
        - * @param {Number} [gridSize] side length of the grid.
        - * @param {Number} [gridDivisions] number of divisions in the grid.
        - * @param {Number} [xOff] offset from origin along the x-axis.
        - * @param {Number} [yOff] offset from origin along the y-axis.
        - * @param {Number} [zOff] offset from origin along the z-axis.
        - */
        +  /**
        +   * For use with debugMode
        +   * @private
        +   * @method _grid
        +   * @param {Number} [size] size of grid sides
        +   * @param {Number} [div] number of grid divisions
        +   * @param {Number} [xOff] offset of grid center from origin in X axis
        +   * @param {Number} [yOff] offset of grid center from origin in Y axis
        +   * @param {Number} [zOff] offset of grid center from origin in Z axis
        +   */
        +  fn._grid = function(size, numDivs, xOff, yOff, zOff) {
        +    if (typeof size === 'undefined') {
        +      size = this.width / 2;
        +    }
        +    if (typeof numDivs === 'undefined') {
        +      // ensure at least 2 divisions
        +      numDivs = Math.round(size / 30) < 4 ? 4 : Math.round(size / 30);
        +    }
        +    if (typeof xOff === 'undefined') {
        +      xOff = 0;
        +    }
        +    if (typeof yOff === 'undefined') {
        +      yOff = 0;
        +    }
        +    if (typeof zOff === 'undefined') {
        +      zOff = 0;
        +    }
         
        -/**
        - * @method debugMode
        - * @param {Constant} mode
        - * @param {Number} [axesSize] length of axes icon markers.
        - * @param {Number} [xOff]
        - * @param {Number} [yOff]
        - * @param {Number} [zOff]
        - */
        +    const spacing = size / numDivs;
        +    const halfSize = size / 2;
         
        -/**
        - * @method debugMode
        - * @param {Number} [gridSize]
        - * @param {Number} [gridDivisions]
        - * @param {Number} [gridXOff] grid offset from the origin along the x-axis.
        - * @param {Number} [gridYOff] grid offset from the origin along the y-axis.
        - * @param {Number} [gridZOff] grid offset from the origin along the z-axis.
        - * @param {Number} [axesSize]
        - * @param {Number} [axesXOff] axes icon offset from the origin along the x-axis.
        - * @param {Number} [axesYOff] axes icon offset from the origin along the y-axis.
        - * @param {Number} [axesZOff] axes icon offset from the origin along the z-axis.
        - */
        +    return function() {
        +      this.push();
        +      this.stroke(
        +        this._renderer.states.curStrokeColor[0] * 255,
        +        this._renderer.states.curStrokeColor[1] * 255,
        +        this._renderer.states.curStrokeColor[2] * 255
        +      );
        +      this._renderer.states.uModelMatrix.reset();
        +
        +      // Lines along X axis
        +      for (let q = 0; q <= numDivs; q++) {
        +        this.beginShape(this.LINES);
        +        this.vertex(-halfSize + xOff, yOff, q * spacing - halfSize + zOff);
        +        this.vertex(+halfSize + xOff, yOff, q * spacing - halfSize + zOff);
        +        this.endShape();
        +      }
         
        -p5.prototype.debugMode = function(...args) {
        -  this._assert3d('debugMode');
        -  p5._validateParameters('debugMode', args);
        -
        -  // start by removing existing 'post' registered debug methods
        -  for (let i = this._registeredMethods.post.length - 1; i >= 0; i--) {
        -    // test for equality...
        -    if (
        -      this._registeredMethods.post[i].toString() === this._grid().toString() ||
        -      this._registeredMethods.post[i].toString() === this._axesIcon().toString()
        -    ) {
        -      this._registeredMethods.post.splice(i, 1);
        -    }
        -  }
        -
        -  // then add new debugMode functions according to the argument list
        -  if (args[0] === constants.GRID) {
        -    this.registerMethod(
        -      'post',
        -      this._grid(args[1], args[2], args[3], args[4], args[5])
        -    );
        -  } else if (args[0] === constants.AXES) {
        -    this.registerMethod(
        -      'post',
        -      this._axesIcon(args[1], args[2], args[3], args[4])
        -    );
        -  } else {
        -    this.registerMethod(
        -      'post',
        -      this._grid(args[0], args[1], args[2], args[3], args[4])
        -    );
        -    this.registerMethod(
        -      'post',
        -      this._axesIcon(args[5], args[6], args[7], args[8])
        -    );
        -  }
        -};
        +      // Lines along Z axis
        +      for (let i = 0; i <= numDivs; i++) {
        +        this.beginShape(this.LINES);
        +        this.vertex(i * spacing - halfSize + xOff, yOff, -halfSize + zOff);
        +        this.vertex(i * spacing - halfSize + xOff, yOff, +halfSize + zOff);
        +        this.endShape();
        +      }
         
        -/**
        - * Turns off <a href="#/p5/debugMode">debugMode()</a> in a 3D sketch.
        - *
        - * @method noDebugMode
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Enable debug mode.
        - *   debugMode();
        - *
        - *   describe('A multicolor box on a gray background. A grid and axes icon are displayed near the box. They disappear when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the box.
        - *   normalMaterial();
        - *
        - *   // Draw the box.  box(20, 40);
        - * }
        - *
        - * // Disable debug mode when the user double-clicks.
        - * function doubleClicked() {
        - *   noDebugMode();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noDebugMode = function() {
        -  this._assert3d('noDebugMode');
        -
        -  // start by removing existing 'post' registered debug methods
        -  for (let i = this._registeredMethods.post.length - 1; i >= 0; i--) {
        -    // test for equality...
        -    if (
        -      this._registeredMethods.post[i].toString() === this._grid().toString() ||
        -      this._registeredMethods.post[i].toString() === this._axesIcon().toString()
        -    ) {
        -      this._registeredMethods.post.splice(i, 1);
        +      this.pop();
        +    };
        +  };
        +
        +  /**
        +   * For use with debugMode
        +   * @private
        +   * @method _axesIcon
        +   * @param {Number} [size] size of axes icon lines
        +   * @param {Number} [xOff] offset of icon from origin in X axis
        +   * @param {Number} [yOff] offset of icon from origin in Y axis
        +   * @param {Number} [zOff] offset of icon from origin in Z axis
        +   */
        +  fn._axesIcon = function(size, xOff, yOff, zOff) {
        +    if (typeof size === 'undefined') {
        +      size = this.width / 20 > 40 ? this.width / 20 : 40;
        +    }
        +    if (typeof xOff === 'undefined') {
        +      xOff = -this.width / 4;
        +    }
        +    if (typeof yOff === 'undefined') {
        +      yOff = xOff;
        +    }
        +    if (typeof zOff === 'undefined') {
        +      zOff = xOff;
             }
        -  }
        -};
         
        -/**
        - * For use with debugMode
        - * @private
        - * @method _grid
        - * @param {Number} [size] size of grid sides
        - * @param {Number} [div] number of grid divisions
        - * @param {Number} [xOff] offset of grid center from origin in X axis
        - * @param {Number} [yOff] offset of grid center from origin in Y axis
        - * @param {Number} [zOff] offset of grid center from origin in Z axis
        - */
        -p5.prototype._grid = function(size, numDivs, xOff, yOff, zOff) {
        -  if (typeof size === 'undefined') {
        -    size = this.width / 2;
        -  }
        -  if (typeof numDivs === 'undefined') {
        -    // ensure at least 2 divisions
        -    numDivs = Math.round(size / 30) < 4 ? 4 : Math.round(size / 30);
        -  }
        -  if (typeof xOff === 'undefined') {
        -    xOff = 0;
        -  }
        -  if (typeof yOff === 'undefined') {
        -    yOff = 0;
        -  }
        -  if (typeof zOff === 'undefined') {
        -    zOff = 0;
        -  }
        -
        -  const spacing = size / numDivs;
        -  const halfSize = size / 2;
        -
        -  return function() {
        -    this.push();
        -    this.stroke(
        -      this._renderer.curStrokeColor[0] * 255,
        -      this._renderer.curStrokeColor[1] * 255,
        -      this._renderer.curStrokeColor[2] * 255
        -    );
        -    this._renderer.uModelMatrix.reset();
        -
        -    // Lines along X axis
        -    for (let q = 0; q <= numDivs; q++) {
        +    return function() {
        +      this.push();
        +      this._renderer.states.uModelMatrix.reset();
        +
        +      // X axis
        +      this.strokeWeight(2);
        +      this.stroke(255, 0, 0);
               this.beginShape(this.LINES);
        -      this.vertex(-halfSize + xOff, yOff, q * spacing - halfSize + zOff);
        -      this.vertex(+halfSize + xOff, yOff, q * spacing - halfSize + zOff);
        +      this.vertex(xOff, yOff, zOff);
        +      this.vertex(xOff + size, yOff, zOff);
               this.endShape();
        -    }
        -
        -    // Lines along Z axis
        -    for (let i = 0; i <= numDivs; i++) {
        +      // Y axis
        +      this.stroke(0, 255, 0);
               this.beginShape(this.LINES);
        -      this.vertex(i * spacing - halfSize + xOff, yOff, -halfSize + zOff);
        -      this.vertex(i * spacing - halfSize + xOff, yOff, +halfSize + zOff);
        +      this.vertex(xOff, yOff, zOff);
        +      this.vertex(xOff, yOff + size, zOff);
               this.endShape();
        -    }
        -
        -    this.pop();
        +      // Z axis
        +      this.stroke(0, 0, 255);
        +      this.beginShape(this.LINES);
        +      this.vertex(xOff, yOff, zOff);
        +      this.vertex(xOff, yOff, zOff + size);
        +      this.endShape();
        +      this.pop();
        +    };
           };
        -};
        +}
         
        -/**
        - * For use with debugMode
        - * @private
        - * @method _axesIcon
        - * @param {Number} [size] size of axes icon lines
        - * @param {Number} [xOff] offset of icon from origin in X axis
        - * @param {Number} [yOff] offset of icon from origin in Y axis
        - * @param {Number} [zOff] offset of icon from origin in Z axis
        - */
        -p5.prototype._axesIcon = function(size, xOff, yOff, zOff) {
        -  if (typeof size === 'undefined') {
        -    size = this.width / 20 > 40 ? this.width / 20 : 40;
        -  }
        -  if (typeof xOff === 'undefined') {
        -    xOff = -this.width / 4;
        -  }
        -  if (typeof yOff === 'undefined') {
        -    yOff = xOff;
        -  }
        -  if (typeof zOff === 'undefined') {
        -    zOff = xOff;
        -  }
        -
        -  return function() {
        -    this.push();
        -    this._renderer.uModelMatrix.reset();
        -
        -    // X axis
        -    this.strokeWeight(2);
        -    this.stroke(255, 0, 0);
        -    this.beginShape(this.LINES);
        -    this.vertex(xOff, yOff, zOff);
        -    this.vertex(xOff + size, yOff, zOff);
        -    this.endShape();
        -    // Y axis
        -    this.stroke(0, 255, 0);
        -    this.beginShape(this.LINES);
        -    this.vertex(xOff, yOff, zOff);
        -    this.vertex(xOff, yOff + size, zOff);
        -    this.endShape();
        -    // Z axis
        -    this.stroke(0, 0, 255);
        -    this.beginShape(this.LINES);
        -    this.vertex(xOff, yOff, zOff);
        -    this.vertex(xOff, yOff, zOff + size);
        -    this.endShape();
        -    this.pop();
        -  };
        -};
        +export default interaction;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  interaction(p5, p5.prototype);
        +}
        diff --git a/src/webgl/light.js b/src/webgl/light.js
        index 3163bc6a65..ba125d3784 100644
        --- a/src/webgl/light.js
        +++ b/src/webgl/light.js
        @@ -5,1775 +5,1841 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        -
        -/**
        - * Creates a light that shines from all directions.
        - *
        - * Ambient light does not come from one direction. Instead, 3D shapes are
        - * lit evenly from all sides. Ambient lights are almost always used in
        - * combination with other types of lights.
        - *
        - * There are three ways to call `ambientLight()` with optional parameters to
        - * set the light’s color.
        - *
        - * The first way to call `ambientLight()` has two parameters, `gray` and
        - * `alpha`. `alpha` is optional. Grayscale and alpha values between 0 and 255
        - * can be passed to set the ambient light’s color, as in `ambientLight(50)` or
        - * `ambientLight(50, 30)`.
        - *
        - * The second way to call `ambientLight()` has one parameter, color. A
        - * <a href="#/p5.Color">p5.Color</a> object, an array of color values, or a
        - * CSS color string, as in `ambientLight('magenta')`, can be passed to set the
        - * ambient light’s color.
        - *
        - * The third way to call `ambientLight()` has four parameters, `v1`, `v2`,
        - * `v3`, and `alpha`. `alpha` is optional. RGBA, HSBA, or HSLA values can be
        - * passed to set the ambient light’s colors, as in `ambientLight(255, 0, 0)`
        - * or `ambientLight(255, 0, 0, 30)`. Color values will be interpreted using
        - * the current <a href="#/p5/colorMode">colorMode()</a>.
        - *
        - * @method ambientLight
        - * @param  {Number}        v1 red or hue value in the current
        - *                            <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}        v2 green or saturation value in the current
        - *                            <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}        v3 blue, brightness, or lightness value in the current
        - *                            <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}        [alpha] alpha (transparency) value in the current
        - *                                 <a href="#/p5/colorMode">colorMode()</a>.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click the canvas to turn on the light.
        - *
        - * let isLit = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A sphere drawn against a gray background. The sphere appears to change color when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Control the light.
        - *   if (isLit === true) {
        - *     // Use a grayscale value of 80.
        - *     ambientLight(80);
        - *   }
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - *
        - * // Turn on the ambient light when the user double-clicks.
        - * function doubleClicked() {
        - *   isLit = true;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A faded magenta sphere drawn against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   // Use a p5.Color object.
        - *   let c = color('orchid');
        - *   ambientLight(c);
        - *
        - *   // Draw the sphere.
        - *   sphere();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A faded magenta sphere drawn against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   // Use a CSS color string.
        - *   ambientLight('#DA70D6');
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A faded magenta sphere drawn against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   // Use RGB values
        - *   ambientLight(218, 112, 214);
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method ambientLight
        - * @param  {Number}        gray  grayscale value between 0 and 255.
        - * @param  {Number}        [alpha]
        - * @chainable
        - */
        -
        -/**
        - * @method ambientLight
        - * @param  {String}        value color as a CSS string.
        - * @chainable
        - */
        -
        -/**
        - * @method ambientLight
        - * @param  {Number[]}      values color as an array of RGBA, HSBA, or HSLA
        - *                                 values.
        - * @chainable
        - */
        -
        -/**
        - * @method ambientLight
        - * @param  {p5.Color}      color color as a <a href="#/p5.Color">p5.Color</a> object.
        - * @chainable
        - */
        -p5.prototype.ambientLight = function (v1, v2, v3, a) {
        -  this._assert3d('ambientLight');
        -  p5._validateParameters('ambientLight', arguments);
        -  const color = this.color(...arguments);
        -
        -  this._renderer.ambientLightColors.push(
        -    color._array[0],
        -    color._array[1],
        -    color._array[2]
        -  );
        -
        -  this._renderer._enableLighting = true;
        -
        -  return this;
        -};
        -
        -/**
        - * Sets the specular color for lights.
        - *
        - * `specularColor()` affects lights that bounce off a surface in a preferred
        - * direction. These lights include
        - * <a href="#/p5/directionalLight">directionalLight()</a>,
        - * <a href="#/p5/pointLight">pointLight()</a>, and
        - * <a href="#/p5/spotLight">spotLight()</a>. The function helps to create
        - * highlights on <a href="#/p5.Geometry">p5.Geometry</a> objects that are
        - * styled with <a href="#/p5/specularMaterial">specularMaterial()</a>. If a
        - * geometry does not use
        - * <a href="#/p5/specularMaterial">specularMaterial()</a>, then
        - * `specularColor()` will have no effect.
        - *
        - * Note: `specularColor()` doesn’t affect lights that bounce in all
        - * directions, including <a href="#/p5/ambientLight">ambientLight()</a> and
        - * <a href="#/p5/imageLight">imageLight()</a>.
        - *
        - * There are three ways to call `specularColor()` with optional parameters to
        - * set the specular highlight color.
        - *
        - * The first way to call `specularColor()` has two optional parameters, `gray`
        - * and `alpha`. Grayscale and alpha values between 0 and 255, as in
        - * `specularColor(50)` or `specularColor(50, 80)`, can be passed to set the
        - * specular highlight color.
        - *
        - * The second way to call `specularColor()` has one optional parameter,
        - * `color`. A <a href="#/p5.Color">p5.Color</a> object, an array of color
        - * values, or a CSS color string can be passed to set the specular highlight
        - * color.
        - *
        - * The third way to call `specularColor()` has four optional parameters, `v1`,
        - * `v2`, `v3`, and `alpha`. RGBA, HSBA, or HSLA values, as in
        - * `specularColor(255, 0, 0, 80)`, can be passed to set the specular highlight
        - * color. Color values will be interpreted using the current
        - * <a href="#/p5/colorMode">colorMode()</a>.
        - *
        - * @method specularColor
        - * @param  {Number}        v1 red or hue value in the current
        - *                            <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}        v2 green or saturation value in the current
        - *                            <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}        v3 blue, brightness, or lightness value in the current
        - *                            <a href="#/p5/colorMode">colorMode()</a>.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white sphere drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // No specular color.
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click the canvas to add a point light.
        - *
        - * let isLit = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A sphere drawn on a gray background. A spotlight starts shining when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *   specularColor(100);
        - *   specularMaterial(255, 255, 255);
        - *
        - *   // Control the light.
        - *   if (isLit === true) {
        - *     // Add a white point light from the top-right.
        - *     pointLight(255, 255, 255, 30, -20, 40);
        - *   }
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - *
        - * // Turn on the point light when the user double-clicks.
        - * function doubleClicked() {
        - *   isLit = true;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A black sphere drawn on a gray background. An area on the surface of the sphere is highlighted in blue.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Add a specular highlight.
        - *   // Use a p5.Color object.
        - *   let c = color('dodgerblue');
        - *   specularColor(c);
        - *
        - *   // Add a white point light from the top-right.
        - *   pointLight(255, 255, 255, 30, -20, 40);
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *
        - *   // Add a white specular material.
        - *   specularMaterial(255, 255, 255);
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A black sphere drawn on a gray background. An area on the surface of the sphere is highlighted in blue.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Add a specular highlight.
        - *   // Use a CSS color string.
        - *   specularColor('#1E90FF');
        - *
        - *   // Add a white point light from the top-right.
        - *   pointLight(255, 255, 255, 30, -20, 40);
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *
        - *   // Add a white specular material.
        - *   specularMaterial(255, 255, 255);
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A black sphere drawn on a gray background. An area on the surface of the sphere is highlighted in blue.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Add a specular highlight.
        - *   // Use RGB values.
        - *   specularColor(30, 144, 255);
        - *
        - *   // Add a white point light from the top-right.
        - *   pointLight(255, 255, 255, 30, -20, 40);
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *
        - *   // Add a white specular material.
        - *   specularMaterial(255, 255, 255);
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method specularColor
        - * @param  {Number}        gray grayscale value between 0 and 255.
        - * @chainable
        - */
        -
        -/**
        - * @method specularColor
        - * @param  {String}        value color as a CSS string.
        - * @chainable
        - */
        -
        -/**
        - * @method specularColor
        - * @param  {Number[]}      values color as an array of RGBA, HSBA, or HSLA
        - *                                 values.
        - * @chainable
        - */
        -
        -/**
        - * @method specularColor
        - * @param  {p5.Color}      color color as a <a href="#/p5.Color">p5.Color</a> object.
        - * @chainable
        - */
        -p5.prototype.specularColor = function (v1, v2, v3) {
        -  this._assert3d('specularColor');
        -  p5._validateParameters('specularColor', arguments);
        -  const color = this.color(...arguments);
        -
        -  this._renderer.specularColors = [
        -    color._array[0],
        -    color._array[1],
        -    color._array[2]
        -  ];
        -
        -  return this;
        -};
        -
        -/**
        - * Creates a light that shines in one direction.
        - *
        - * Directional lights don’t shine from a specific point. They’re like a sun
        - * that shines from somewhere offscreen. The light’s direction is set using
        - * three `(x, y, z)` values between -1 and 1. For example, setting a light’s
        - * direction as `(1, 0, 0)` will light <a href="#/p5.Geometry">p5.Geometry</a>
        - * objects from the left since the light faces directly to the right. A
        - * maximum of 5 directional lights can be active at once.
        - *
        - * There are four ways to call `directionalLight()` with parameters to set the
        - * light’s color and direction.
        - *
        - * The first way to call `directionalLight()` has six parameters. The first
        - * three parameters, `v1`, `v2`, and `v3`, set the light’s color using the
        - * current <a href="#/p5/colorMode">colorMode()</a>. The last three
        - * parameters, `x`, `y`, and `z`, set the light’s direction. For example,
        - * `directionalLight(255, 0, 0, 1, 0, 0)` creates a red `(255, 0, 0)` light
        - * that shines to the right `(1, 0, 0)`.
        - *
        - * The second way to call `directionalLight()` has four parameters. The first
        - * three parameters, `v1`, `v2`, and `v3`, set the light’s color using the
        - * current <a href="#/p5/colorMode">colorMode()</a>. The last parameter,
        - * `direction` sets the light’s direction using a
        - * <a href="#/p5.Geometry">p5.Geometry</a> object. For example,
        - * `directionalLight(255, 0, 0, lightDir)` creates a red `(255, 0, 0)` light
        - * that shines in the direction the `lightDir` vector points.
        - *
        - * The third way to call `directionalLight()` has four parameters. The first
        - * parameter, `color`, sets the light’s color using a
        - * <a href="#/p5.Color">p5.Color</a> object or an array of color values. The
        - * last three parameters, `x`, `y`, and `z`, set the light’s direction. For
        - * example, `directionalLight(myColor, 1, 0, 0)` creates a light that shines
        - * to the right `(1, 0, 0)` with the color value of `myColor`.
        - *
        - * The fourth way to call `directionalLight()` has two parameters. The first
        - * parameter, `color`, sets the light’s color using a
        - * <a href="#/p5.Color">p5.Color</a> object or an array of color values. The
        - * second parameter, `direction`, sets the light’s direction using a
        - * <a href="#/p5.Color">p5.Color</a> object. For example,
        - * `directionalLight(myColor, lightDir)` creates a light that shines in the
        - * direction the `lightDir` vector points with the color value of `myColor`.
        - *
        - * @method directionalLight
        - * @param  {Number}    v1 red or hue value in the current
        - *                        <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}    v2 green or saturation value in the current
        - *                        <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}    v3 blue, brightness, or lightness value in the current
        - *                        <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}    x  x-component of the light's direction between -1 and 1.
        - * @param  {Number}    y  y-component of the light's direction between -1 and 1.
        - * @param  {Number}    z  z-component of the light's direction between -1 and 1.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click to turn on the directional light.
        - *
        - * let isLit = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A sphere drawn on a gray background. A red light starts shining from above when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Control the light.
        - *   if (isLit === true) {
        - *     // Add a red directional light from above.
        - *     // Use RGB values and XYZ directions.
        - *     directionalLight(255, 0, 0, 0, 1, 0);
        - *   }
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A sphere drawn on a gray background. The top of the sphere appears bright red. The color gets darker toward the bottom.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Add a red directional light from above.
        - *   // Use a p5.Color object and XYZ directions.
        - *   let c = color(255, 0, 0);
        - *   directionalLight(c, 0, 1, 0);
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A sphere drawn on a gray background. The top of the sphere appears bright red. The color gets darker toward the bottom.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Add a red directional light from above.
        - *   // Use a p5.Color object and a p5.Vector object.
        - *   let c = color(255, 0, 0);
        - *   let lightDir = createVector(0, 1, 0);
        - *   directionalLight(c, lightDir);
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - */
        -
        -/**
        - * @method directionalLight
        - * @param  {Number}    v1
        - * @param  {Number}    v2
        - * @param  {Number}    v3
        - * @param  {p5.Vector} direction direction of the light as a
        - *                               <a href="#/p5.Vector">p5.Vector</a> object.
        - * @chainable
        - */
        +import { RendererGL } from './p5.RendererGL';
        +import { Vector } from '../math/p5.Vector';
        +import { Color } from '../color/p5.Color';
        +
        +function light(p5, fn){
        +  /**
        +   * Creates a light that shines from all directions.
        +   *
        +   * Ambient light does not come from one direction. Instead, 3D shapes are
        +   * lit evenly from all sides. Ambient lights are almost always used in
        +   * combination with other types of lights.
        +   *
        +   * There are three ways to call `ambientLight()` with optional parameters to
        +   * set the light’s color.
        +   *
        +   * The first way to call `ambientLight()` has two parameters, `gray` and
        +   * `alpha`. `alpha` is optional. Grayscale and alpha values between 0 and 255
        +   * can be passed to set the ambient light’s color, as in `ambientLight(50)` or
        +   * `ambientLight(50, 30)`.
        +   *
        +   * The second way to call `ambientLight()` has one parameter, color. A
        +   * <a href="#/p5.Color">p5.Color</a> object, an array of color values, or a
        +   * CSS color string, as in `ambientLight('magenta')`, can be passed to set the
        +   * ambient light’s color.
        +   *
        +   * The third way to call `ambientLight()` has four parameters, `v1`, `v2`,
        +   * `v3`, and `alpha`. `alpha` is optional. RGBA, HSBA, or HSLA values can be
        +   * passed to set the ambient light’s colors, as in `ambientLight(255, 0, 0)`
        +   * or `ambientLight(255, 0, 0, 30)`. Color values will be interpreted using
        +   * the current <a href="#/p5/colorMode">colorMode()</a>.
        +   *
        +   * @method ambientLight
        +   * @param  {Number}        v1 red or hue value in the current
        +   *                            <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}        v2 green or saturation value in the current
        +   *                            <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}        v3 blue, brightness, or lightness value in the current
        +   *                            <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}        [alpha] alpha (transparency) value in the current
        +   *                                 <a href="#/p5/colorMode">colorMode()</a>.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click the canvas to turn on the light.
        +   *
        +   * let isLit = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A sphere drawn against a gray background. The sphere appears to change color when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Control the light.
        +   *   if (isLit === true) {
        +   *     // Use a grayscale value of 80.
        +   *     ambientLight(80);
        +   *   }
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   *
        +   * // Turn on the ambient light when the user double-clicks.
        +   * function doubleClicked() {
        +   *   isLit = true;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A faded magenta sphere drawn against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   // Use a p5.Color object.
        +   *   let c = color('orchid');
        +   *   ambientLight(c);
        +   *
        +   *   // Draw the sphere.
        +   *   sphere();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A faded magenta sphere drawn against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   // Use a CSS color string.
        +   *   ambientLight('#DA70D6');
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A faded magenta sphere drawn against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   // Use RGB values
        +   *   ambientLight(218, 112, 214);
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method ambientLight
        +   * @param  {Number}        gray  grayscale value between 0 and 255.
        +   * @param  {Number}        [alpha]
        +   * @chainable
        +   */
        +
        +  /**
        +   * @method ambientLight
        +   * @param  {String}        value color as a CSS string.
        +   * @chainable
        +   */
        +
        +  /**
        +   * @method ambientLight
        +   * @param  {Number[]}      values color as an array of RGBA, HSBA, or HSLA
        +   *                                 values.
        +   * @chainable
        +   */
        +
        +  /**
        +   * @method ambientLight
        +   * @param  {p5.Color}      color color as a <a href="#/p5.Color">p5.Color</a> object.
        +   * @chainable
        +   */
        +  fn.ambientLight = function (v1, v2, v3, a) {
        +    this._assert3d('ambientLight');
        +    // p5._validateParameters('ambientLight', arguments);
        +
        +    this._renderer.ambientLight(...arguments);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Sets the specular color for lights.
        +   *
        +   * `specularColor()` affects lights that bounce off a surface in a preferred
        +   * direction. These lights include
        +   * <a href="#/p5/directionalLight">directionalLight()</a>,
        +   * <a href="#/p5/pointLight">pointLight()</a>, and
        +   * <a href="#/p5/spotLight">spotLight()</a>. The function helps to create
        +   * highlights on <a href="#/p5.Geometry">p5.Geometry</a> objects that are
        +   * styled with <a href="#/p5/specularMaterial">specularMaterial()</a>. If a
        +   * geometry does not use
        +   * <a href="#/p5/specularMaterial">specularMaterial()</a>, then
        +   * `specularColor()` will have no effect.
        +   *
        +   * Note: `specularColor()` doesn’t affect lights that bounce in all
        +   * directions, including <a href="#/p5/ambientLight">ambientLight()</a> and
        +   * <a href="#/p5/imageLight">imageLight()</a>.
        +   *
        +   * There are three ways to call `specularColor()` with optional parameters to
        +   * set the specular highlight color.
        +   *
        +   * The first way to call `specularColor()` has two optional parameters, `gray`
        +   * and `alpha`. Grayscale and alpha values between 0 and 255, as in
        +   * `specularColor(50)` or `specularColor(50, 80)`, can be passed to set the
        +   * specular highlight color.
        +   *
        +   * The second way to call `specularColor()` has one optional parameter,
        +   * `color`. A <a href="#/p5.Color">p5.Color</a> object, an array of color
        +   * values, or a CSS color string can be passed to set the specular highlight
        +   * color.
        +   *
        +   * The third way to call `specularColor()` has four optional parameters, `v1`,
        +   * `v2`, `v3`, and `alpha`. RGBA, HSBA, or HSLA values, as in
        +   * `specularColor(255, 0, 0, 80)`, can be passed to set the specular highlight
        +   * color. Color values will be interpreted using the current
        +   * <a href="#/p5/colorMode">colorMode()</a>.
        +   *
        +   * @method specularColor
        +   * @param  {Number}        v1 red or hue value in the current
        +   *                            <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}        v2 green or saturation value in the current
        +   *                            <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}        v3 blue, brightness, or lightness value in the current
        +   *                            <a href="#/p5/colorMode">colorMode()</a>.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white sphere drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // No specular color.
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click the canvas to add a point light.
        +   *
        +   * let isLit = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A sphere drawn on a gray background. A spotlight starts shining when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *   specularColor(100);
        +   *   specularMaterial(255, 255, 255);
        +   *
        +   *   // Control the light.
        +   *   if (isLit === true) {
        +   *     // Add a white point light from the top-right.
        +   *     pointLight(255, 255, 255, 30, -20, 40);
        +   *   }
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   *
        +   * // Turn on the point light when the user double-clicks.
        +   * function doubleClicked() {
        +   *   isLit = true;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A black sphere drawn on a gray background. An area on the surface of the sphere is highlighted in blue.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Add a specular highlight.
        +   *   // Use a p5.Color object.
        +   *   let c = color('dodgerblue');
        +   *   specularColor(c);
        +   *
        +   *   // Add a white point light from the top-right.
        +   *   pointLight(255, 255, 255, 30, -20, 40);
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *
        +   *   // Add a white specular material.
        +   *   specularMaterial(255, 255, 255);
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A black sphere drawn on a gray background. An area on the surface of the sphere is highlighted in blue.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Add a specular highlight.
        +   *   // Use a CSS color string.
        +   *   specularColor('#1E90FF');
        +   *
        +   *   // Add a white point light from the top-right.
        +   *   pointLight(255, 255, 255, 30, -20, 40);
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *
        +   *   // Add a white specular material.
        +   *   specularMaterial(255, 255, 255);
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A black sphere drawn on a gray background. An area on the surface of the sphere is highlighted in blue.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Add a specular highlight.
        +   *   // Use RGB values.
        +   *   specularColor(30, 144, 255);
        +   *
        +   *   // Add a white point light from the top-right.
        +   *   pointLight(255, 255, 255, 30, -20, 40);
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *
        +   *   // Add a white specular material.
        +   *   specularMaterial(255, 255, 255);
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method specularColor
        +   * @param  {Number}        gray grayscale value between 0 and 255.
        +   * @chainable
        +   */
        +
        +  /**
        +   * @method specularColor
        +   * @param  {String}        value color as a CSS string.
        +   * @chainable
        +   */
        +
        +  /**
        +   * @method specularColor
        +   * @param  {Number[]}      values color as an array of RGBA, HSBA, or HSLA
        +   *                                 values.
        +   * @chainable
        +   */
        +
        +  /**
        +   * @method specularColor
        +   * @param  {p5.Color}      color color as a <a href="#/p5.Color">p5.Color</a> object.
        +   * @chainable
        +   */
        +  fn.specularColor = function (v1, v2, v3) {
        +    this._assert3d('specularColor');
        +    // p5._validateParameters('specularColor', arguments);
        +
        +    this._renderer.specularColor(...arguments);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Creates a light that shines in one direction.
        +   *
        +   * Directional lights don’t shine from a specific point. They’re like a sun
        +   * that shines from somewhere offscreen. The light’s direction is set using
        +   * three `(x, y, z)` values between -1 and 1. For example, setting a light’s
        +   * direction as `(1, 0, 0)` will light <a href="#/p5.Geometry">p5.Geometry</a>
        +   * objects from the left since the light faces directly to the right. A
        +   * maximum of 5 directional lights can be active at once.
        +   *
        +   * There are four ways to call `directionalLight()` with parameters to set the
        +   * light’s color and direction.
        +   *
        +   * The first way to call `directionalLight()` has six parameters. The first
        +   * three parameters, `v1`, `v2`, and `v3`, set the light’s color using the
        +   * current <a href="#/p5/colorMode">colorMode()</a>. The last three
        +   * parameters, `x`, `y`, and `z`, set the light’s direction. For example,
        +   * `directionalLight(255, 0, 0, 1, 0, 0)` creates a red `(255, 0, 0)` light
        +   * that shines to the right `(1, 0, 0)`.
        +   *
        +   * The second way to call `directionalLight()` has four parameters. The first
        +   * three parameters, `v1`, `v2`, and `v3`, set the light’s color using the
        +   * current <a href="#/p5/colorMode">colorMode()</a>. The last parameter,
        +   * `direction` sets the light’s direction using a
        +   * <a href="#/p5.Geometry">p5.Geometry</a> object. For example,
        +   * `directionalLight(255, 0, 0, lightDir)` creates a red `(255, 0, 0)` light
        +   * that shines in the direction the `lightDir` vector points.
        +   *
        +   * The third way to call `directionalLight()` has four parameters. The first
        +   * parameter, `color`, sets the light’s color using a
        +   * <a href="#/p5.Color">p5.Color</a> object or an array of color values. The
        +   * last three parameters, `x`, `y`, and `z`, set the light’s direction. For
        +   * example, `directionalLight(myColor, 1, 0, 0)` creates a light that shines
        +   * to the right `(1, 0, 0)` with the color value of `myColor`.
        +   *
        +   * The fourth way to call `directionalLight()` has two parameters. The first
        +   * parameter, `color`, sets the light’s color using a
        +   * <a href="#/p5.Color">p5.Color</a> object or an array of color values. The
        +   * second parameter, `direction`, sets the light’s direction using a
        +   * <a href="#/p5.Color">p5.Color</a> object. For example,
        +   * `directionalLight(myColor, lightDir)` creates a light that shines in the
        +   * direction the `lightDir` vector points with the color value of `myColor`.
        +   *
        +   * @method directionalLight
        +   * @param  {Number}    v1 red or hue value in the current
        +   *                        <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}    v2 green or saturation value in the current
        +   *                        <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}    v3 blue, brightness, or lightness value in the current
        +   *                        <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}    x  x-component of the light's direction between -1 and 1.
        +   * @param  {Number}    y  y-component of the light's direction between -1 and 1.
        +   * @param  {Number}    z  z-component of the light's direction between -1 and 1.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click to turn on the directional light.
        +   *
        +   * let isLit = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A sphere drawn on a gray background. A red light starts shining from above when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Control the light.
        +   *   if (isLit === true) {
        +   *     // Add a red directional light from above.
        +   *     // Use RGB values and XYZ directions.
        +   *     directionalLight(255, 0, 0, 0, 1, 0);
        +   *   }
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A sphere drawn on a gray background. The top of the sphere appears bright red. The color gets darker toward the bottom.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Add a red directional light from above.
        +   *   // Use a p5.Color object and XYZ directions.
        +   *   let c = color(255, 0, 0);
        +   *   directionalLight(c, 0, 1, 0);
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A sphere drawn on a gray background. The top of the sphere appears bright red. The color gets darker toward the bottom.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Add a red directional light from above.
        +   *   // Use a p5.Color object and a p5.Vector object.
        +   *   let c = color(255, 0, 0);
        +   *   let lightDir = createVector(0, 1, 0);
        +   *   directionalLight(c, lightDir);
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method directionalLight
        +   * @param  {Number}    v1
        +   * @param  {Number}    v2
        +   * @param  {Number}    v3
        +   * @param  {p5.Vector} direction direction of the light as a
        +   *                               <a href="#/p5.Vector">p5.Vector</a> object.
        +   * @chainable
        +   */
        +
        +  /**
        +   * @method directionalLight
        +   * @param  {p5.Color|Number[]|String} color color as a <a href="#/p5.Color">p5.Color</a> object,
        +   *                                           an array of color values, or as a CSS string.
        +   * @param  {Number}                   x
        +   * @param  {Number}                   y
        +   * @param  {Number}                   z
        +   * @chainable
        +   */
        +
        +  /**
        +   * @method directionalLight
        +   * @param  {p5.Color|Number[]|String} color
        +   * @param  {p5.Vector}                direction
        +   * @chainable
        +   */
        +  fn.directionalLight = function (v1, v2, v3, x, y, z) {
        +    this._assert3d('directionalLight');
        +    // p5._validateParameters('directionalLight', arguments);
        +
        +    //@TODO: check parameters number
        +    this._renderer.directionalLight(...arguments);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Creates a light that shines from a point in all directions.
        +   *
        +   * Point lights are like light bulbs that shine in all directions. They can be
        +   * placed at different positions to achieve different lighting effects. A
        +   * maximum of 5 point lights can be active at once.
        +   *
        +   * There are four ways to call `pointLight()` with parameters to set the
        +   * light’s color and position.
        +   *
        +   * The first way to call `pointLight()` has six parameters. The first three
        +   * parameters, `v1`, `v2`, and `v3`, set the light’s color using the current
        +   * <a href="#/p5/colorMode">colorMode()</a>. The last three parameters, `x`,
        +   * `y`, and `z`, set the light’s position. For example,
        +   * `pointLight(255, 0, 0, 50, 0, 0)` creates a red `(255, 0, 0)` light that
        +   * shines from the coordinates `(50, 0, 0)`.
        +   *
        +   * The second way to call `pointLight()` has four parameters. The first three
        +   * parameters, `v1`, `v2`, and `v3`, set the light’s color using the current
        +   * <a href="#/p5/colorMode">colorMode()</a>. The last parameter, position sets
        +   * the light’s position using a <a href="#/p5.Vector">p5.Vector</a> object.
        +   * For example, `pointLight(255, 0, 0, lightPos)` creates a red `(255, 0, 0)`
        +   * light that shines from the position set by the `lightPos` vector.
        +   *
        +   * The third way to call `pointLight()` has four parameters. The first
        +   * parameter, `color`, sets the light’s color using a
        +   * <a href="#/p5.Color">p5.Color</a> object or an array of color values. The
        +   * last three parameters, `x`, `y`, and `z`, set the light’s position. For
        +   * example, `directionalLight(myColor, 50, 0, 0)` creates a light that shines
        +   * from the coordinates `(50, 0, 0)` with the color value of `myColor`.
        +   *
        +   * The fourth way to call `pointLight()` has two parameters. The first
        +   * parameter, `color`, sets the light’s color using a
        +   * <a href="#/p5.Color">p5.Color</a> object or an array of color values. The
        +   * second parameter, `position`, sets the light’s position using a
        +   * <a href="#/p5.Vector">p5.Vector</a> object. For example,
        +   * `directionalLight(myColor, lightPos)` creates a light that shines from the
        +   * position set by the `lightPos` vector with the color value of `myColor`.
        +   *
        +   * @method pointLight
        +   * @param  {Number}    v1 red or hue value in the current
        +   *                        <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}    v2 green or saturation value in the current
        +   *                        <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}    v3 blue, brightness, or lightness value in the current
        +   *                        <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}    x  x-coordinate of the light.
        +   * @param  {Number}    y  y-coordinate of the light.
        +   * @param  {Number}    z  z-coordinate of the light.
        +   * @chainable
        +   *
        +   * @example
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click to turn on the point light.
        +   *
        +   * let isLit = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A sphere drawn on a gray background. A red light starts shining from above when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Control the light.
        +   *   if (isLit === true) {
        +   *     // Add a red point light from above.
        +   *     // Use RGB values and XYZ coordinates.
        +   *     pointLight(255, 0, 0, 0, -150, 0);
        +   *   }
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   *
        +   * // Turn on the point light when the user double-clicks.
        +   * function doubleClicked() {
        +   *   isLit = true;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A sphere drawn on a gray background. The top of the sphere appears bright red. The color gets darker toward the bottom.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Add a red point light from above.
        +   *   // Use a p5.Color object and XYZ directions.
        +   *   let c = color(255, 0, 0);
        +   *   pointLight(c, 0, -150, 0);
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A sphere drawn on a gray background. The top of the sphere appears bright red. The color gets darker toward the bottom.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Add a red point light from above.
        +   *   // Use a p5.Color object and a p5.Vector object.
        +   *   let c = color(255, 0, 0);
        +   *   let lightPos = createVector(0, -150, 0);
        +   *   pointLight(c, lightPos);
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('Four spheres arranged in a square and drawn on a gray background. The spheres appear bright red toward the center of the square. The color gets darker toward the corners of the square.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Add a red point light that points to the center of the scene.
        +   *   // Use a p5.Color object and a p5.Vector object.
        +   *   let c = color(255, 0, 0);
        +   *   let lightPos = createVector(0, 0, 65);
        +   *   pointLight(c, lightPos);
        +   *
        +   *   // Style the spheres.
        +   *   noStroke();
        +   *
        +   *   // Draw a sphere up and to the left.
        +   *   push();
        +   *   translate(-25, -25, 25);
        +   *   sphere(10);
        +   *   pop();
        +   *
        +   *   // Draw a box up and to the right.
        +   *   push();
        +   *   translate(25, -25, 25);
        +   *   sphere(10);
        +   *   pop();
        +   *
        +   *   // Draw a sphere down and to the left.
        +   *   push();
        +   *   translate(-25, 25, 25);
        +   *   sphere(10);
        +   *   pop();
        +   *
        +   *   // Draw a box down and to the right.
        +   *   push();
        +   *   translate(25, 25, 25);
        +   *   sphere(10);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * @method pointLight
        +   * @param  {Number}     v1
        +   * @param  {Number}     v2
        +   * @param  {Number}     v3
        +   * @param  {p5.Vector}  position position of the light as a
        +   *                               <a href="#/p5.Vector">p5.Vector</a> object.
        +   * @chainable
        +   */
        +
        +  /**
        +   * @method pointLight
        +   * @param  {p5.Color|Number[]|String} color color as a <a href="#/p5.Color">p5.Color</a> object,
        +   *                                          an array of color values, or a CSS string.
        +   * @param  {Number}                   x
        +   * @param  {Number}                   y
        +   * @param  {Number}                   z
        +   * @chainable
        +   */
        +
        +  /**
        +   * @method pointLight
        +   * @param  {p5.Color|Number[]|String} color
        +   * @param  {p5.Vector}                position
        +   * @chainable
        +   */
        +  fn.pointLight = function (v1, v2, v3, x, y, z) {
        +    this._assert3d('pointLight');
        +    // p5._validateParameters('pointLight', arguments);
        +
        +    //@TODO: check parameters number
        +    this._renderer.pointLight(...arguments);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Creates an ambient light from an image.
        +   *
        +   * `imageLight()` simulates a light shining from all directions. The effect is
        +   * like placing the sketch at the center of a giant sphere that uses the image
        +   * as its texture. The image's diffuse light will be affected by
        +   * <a href="#/p5/fill">fill()</a> and the specular reflections will be
        +   * affected by <a href="#/p5/specularMaterial">specularMaterial()</a> and
        +   * <a href="#/p5/shininess">shininess()</a>.
        +   *
        +   * The parameter, `img`, is the <a href="#/p5.Image">p5.Image</a> object to
        +   * use as the light source.
        +   *
        +   * @method imageLight
        +   * @param  {p5.image}    img image to use as the light source.
        +   *
        +   * @example
        +   * <div class="notest">
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let img;
        +   *
        +   * // Load an image and create a p5.Image object.
        +   * function preload() {
        +   *   img = loadImage('assets/outdoor_spheremap.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A sphere floating above a landscape. The surface of the sphere reflects the landscape.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the image as a panorama (360˚ background).
        +   *   panorama(img);
        +   *
        +   *   // Add a soft ambient light.
        +   *   ambientLight(50);
        +   *
        +   *   // Add light from the image.
        +   *   imageLight(img);
        +   *
        +   *   // Style the sphere.
        +   *   specularMaterial(20);
        +   *   shininess(100);
        +   *   noStroke();
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.imageLight = function (img) {
        +    this._renderer.imageLight(img);
        +  };
        +
        +  /**
        +   * Creates an immersive 3D background.
        +   *
        +   * `panorama()` transforms images containing 360˚ content, such as maps or
        +   * HDRIs, into immersive 3D backgrounds that surround a sketch. Exploring the
        +   * space requires changing the camera's perspective with functions such as
        +   * <a href="#/p5/orbitControl">orbitControl()</a> or
        +   * <a href="#/p5/camera">camera()</a>.
        +   *
        +   * @method panorama
        +   * @param {p5.Image} img 360˚ image to use as the background.
        +   *
        +   * @example
        +   * <div class="notest">
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let img;
        +   *
        +   * // Load an image and create a p5.Image object.
        +   * function preload() {
        +   *   img = loadImage('assets/outdoor_spheremap.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100 ,100 ,WEBGL);
        +   *
        +   *   describe('A sphere floating above a landscape. The surface of the sphere reflects the landscape. The full landscape is viewable in 3D as the user drags the mouse.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Add the panorama.
        +   *   panorama(img);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Use the image as a light source.
        +   *   imageLight(img);
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *   specularMaterial(50);
        +   *   shininess(200);
        +   *   metalness(100);
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.panorama = function (img) {
        +    this.filter(this._renderer._getSphereMapping(img));
        +  };
        +
        +  /**
        +   * Places an ambient and directional light in the scene.
        +   * The lights are set to ambientLight(128, 128, 128) and
        +   * directionalLight(128, 128, 128, 0, 0, -1).
        +   *
        +   * Note: lights need to be called (whether directly or indirectly)
        +   * within draw() to remain persistent in a looping program.
        +   * Placing them in setup() will cause them to only have an effect
        +   * the first time through the loop.
        +   *
        +   * @method lights
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click to turn on the lights.
        +   *
        +   * let isLit = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white box drawn against a gray background. The quality of the light changes when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Control the lights.
        +   *   if (isLit === true) {
        +   *     lights();
        +   *   }
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   *
        +   * // Turn on the lights when the user double-clicks.
        +   * function doubleClicked() {
        +   *   isLit = true;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white box drawn against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   ambientLight(128, 128, 128);
        +   *   directionalLight(128, 128, 128, 0, 0, -1);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.lights = function () {
        +    this._assert3d('lights');
        +    // Both specify gray by default.
        +    this._renderer.lights();
        +    return this;
        +  };
        +
        +  /**
        +   * Sets the falloff rate for <a href="#/p5/pointLight">pointLight()</a>
        +   * and <a href="#/p5/spotLight">spotLight()</a>.
        +   *
        +   * A light’s falloff describes the intensity of its beam at a distance. For
        +   * example, a lantern has a slow falloff, a flashlight has a medium falloff,
        +   * and a laser pointer has a sharp falloff.
        +   *
        +   * `lightFalloff()` has three parameters, `constant`, `linear`, and
        +   * `quadratic`. They’re numbers used to calculate falloff at a distance, `d`,
        +   * as follows:
        +   *
        +   * `falloff = 1 / (constant + d * linear + (d * d) * quadratic)`
        +   *
        +   * Note: `constant`, `linear`, and `quadratic` should always be set to values
        +   * greater than 0.
        +   *
        +   * @method lightFalloff
        +   * @param {Number} constant  constant value for calculating falloff.
        +   * @param {Number} linear    linear value for calculating falloff.
        +   * @param {Number} quadratic quadratic value for calculating falloff.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click to change the falloff rate.
        +   *
        +   * let useFalloff = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A sphere drawn against a gray background. The intensity of the light changes when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Set the light falloff.
        +   *   if (useFalloff === true) {
        +   *     lightFalloff(2, 0, 0);
        +   *   }
        +   *
        +   *   // Add a white point light from the front.
        +   *   pointLight(255, 255, 255, 0, 0, 100);
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   *
        +   * // Change the falloff value when the user double-clicks.
        +   * function doubleClicked() {
        +   *   useFalloff = true;
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.lightFalloff = function (
        +    constantAttenuation,
        +    linearAttenuation,
        +    quadraticAttenuation
        +  ) {
        +    this._assert3d('lightFalloff');
        +    // p5._validateParameters('lightFalloff', arguments);
         
        -/**
        - * @method directionalLight
        - * @param  {p5.Color|Number[]|String} color color as a <a href="#/p5.Color">p5.Color</a> object,
        - *                                           an array of color values, or as a CSS string.
        - * @param  {Number}                   x
        - * @param  {Number}                   y
        - * @param  {Number}                   z
        - * @chainable
        - */
        +    this._renderer.lightFalloff(
        +      constantAttenuation,
        +      linearAttenuation,
        +      quadraticAttenuation
        +    );
         
        -/**
        - * @method directionalLight
        - * @param  {p5.Color|Number[]|String} color
        - * @param  {p5.Vector}                direction
        - * @chainable
        - */
        -p5.prototype.directionalLight = function (v1, v2, v3, x, y, z) {
        -  this._assert3d('directionalLight');
        -  p5._validateParameters('directionalLight', arguments);
        -
        -  //@TODO: check parameters number
        -  let color;
        -  if (v1 instanceof p5.Color) {
        -    color = v1;
        -  } else {
        -    color = this.color(v1, v2, v3);
        -  }
        +    return this;
        +  };
        +
        +  /**
        +   * Creates a light that shines from a point in one direction.
        +   *
        +   * Spot lights are like flashlights that shine in one direction creating a
        +   * cone of light. The shape of the cone can be controlled using the angle and
        +   * concentration parameters. A maximum of 5 spot lights can be active at once.
        +   *
        +   * There are eight ways to call `spotLight()` with parameters to set the
        +   * light’s color, position, direction. For example,
        +   * `spotLight(255, 0, 0, 0, 0, 0, 1, 0, 0)` creates a red `(255, 0, 0)` light
        +   * at the origin `(0, 0, 0)` that points to the right `(1, 0, 0)`.
        +   *
        +   * The `angle` parameter is optional. It sets the radius of the light cone.
        +   * For example, `spotLight(255, 0, 0, 0, 0, 0, 1, 0, 0, PI / 16)` creates a
        +   * red `(255, 0, 0)` light at the origin `(0, 0, 0)` that points to the right
        +   * `(1, 0, 0)` with an angle of `PI / 16` radians. By default, `angle` is
        +   * `PI / 3` radians.
        +   *
        +   * The `concentration` parameter is also optional. It focuses the light
        +   * towards the center of the light cone. For example,
        +   * `spotLight(255, 0, 0, 0, 0, 0, 1, 0, 0, PI / 16, 50)` creates a red
        +   * `(255, 0, 0)` light at the origin `(0, 0, 0)` that points to the right
        +   * `(1, 0, 0)` with an angle of `PI / 16` radians at concentration of 50. By
        +   * default, `concentration` is 100.
        +   *
        +   * @method spotLight
        +   * @param  {Number}    v1               red or hue value in the current
        +   *                                      <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}    v2               green or saturation value in the current
        +   *                                      <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}    v3               blue, brightness, or lightness value in the current
        +   *                                      <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}    x                x-coordinate of the light.
        +   * @param  {Number}    y                y-coordinate of the light.
        +   * @param  {Number}    z                z-coordinate of the light.
        +   * @param  {Number}    rx               x-component of light direction between -1 and 1.
        +   * @param  {Number}    ry               y-component of light direction between -1 and 1.
        +   * @param  {Number}    rz               z-component of light direction between -1 and 1.
        +   * @param  {Number}    [angle]          angle of the light cone. Defaults to `PI / 3`.
        +   * @param  {Number}    [concentration]  concentration of the light. Defaults to 100.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click to adjust the spotlight.
        +   *
        +   * let isLit = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white sphere drawn on a gray background. A red spotlight starts shining when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Control the spotlight.
        +   *   if (isLit === true) {
        +   *     // Add a red spot light that shines into the screen.
        +   *     // Set its angle to PI / 32 radians.
        +   *     spotLight(255, 0, 0, 0, 0, 100, 0, 0, -1, PI / 32);
        +   *   }
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   *
        +   * // Turn on the spotlight when the user double-clicks.
        +   * function doubleClicked() {
        +   *   isLit = true;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click to adjust the spotlight.
        +   *
        +   * let isLit = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white sphere drawn on a gray background. A red spotlight starts shining when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Control the spotlight.
        +   *   if (isLit === true) {
        +   *     // Add a red spot light that shines into the screen.
        +   *     // Set its angle to PI / 3 radians (default).
        +   *     // Set its concentration to 1000.
        +   *     let c = color(255, 0, 0);
        +   *     let position = createVector(0, 0, 100);
        +   *     let direction = createVector(0, 0, -1);
        +   *     spotLight(c, position, direction, PI / 3, 1000);
        +   *   }
        +   *
        +   *   // Draw the sphere.
        +   *   sphere(30);
        +   * }
        +   *
        +   * // Turn on the spotlight when the user double-clicks.
        +   * function doubleClicked() {
        +   *   isLit = true;
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method spotLight
        +   * @param  {p5.Color|Number[]|String} color     color as a <a href="#/p5.Color">p5.Color</a> object,
        +   *                                              an array of color values, or a CSS string.
        +   * @param  {p5.Vector}                position  position of the light as a <a href="#/p5.Vector">p5.Vector</a> object.
        +   * @param  {p5.Vector}                direction direction of light as a <a href="#/p5.Vector">p5.Vector</a> object.
        +   * @param  {Number}                   [angle]
        +   * @param  {Number}                   [concentration]
        +   */
        +  /**
        +   * @method spotLight
        +   * @param  {Number}     v1
        +   * @param  {Number}     v2
        +   * @param  {Number}     v3
        +   * @param  {p5.Vector}  position
        +   * @param  {p5.Vector}  direction
        +   * @param  {Number}     [angle]
        +   * @param  {Number}     [concentration]
        +   */
        +  /**
        +   * @method spotLight
        +   * @param  {p5.Color|Number[]|String} color
        +   * @param  {Number}                   x
        +   * @param  {Number}                   y
        +   * @param  {Number}                   z
        +   * @param  {p5.Vector}                direction
        +   * @param  {Number}                   [angle]
        +   * @param  {Number}                   [concentration]
        +   */
        +  /**
        +   * @method spotLight
        +   * @param  {p5.Color|Number[]|String} color
        +   * @param  {p5.Vector}                position
        +   * @param  {Number}                   rx
        +   * @param  {Number}                   ry
        +   * @param  {Number}                   rz
        +   * @param  {Number}                   [angle]
        +   * @param  {Number}                   [concentration]
        +   */
        +  /**
        +   * @method spotLight
        +   * @param  {Number}     v1
        +   * @param  {Number}     v2
        +   * @param  {Number}     v3
        +   * @param  {Number}     x
        +   * @param  {Number}     y
        +   * @param  {Number}     z
        +   * @param  {p5.Vector}  direction
        +   * @param  {Number}     [angle]
        +   * @param  {Number}     [concentration]
        +   */
        +  /**
        +   * @method spotLight
        +   * @param  {Number}     v1
        +   * @param  {Number}     v2
        +   * @param  {Number}     v3
        +   * @param  {p5.Vector}  position
        +   * @param  {Number}     rx
        +   * @param  {Number}     ry
        +   * @param  {Number}     rz
        +   * @param  {Number}     [angle]
        +   * @param  {Number}     [concentration]
        +   */
        +  /**
        +   * @method spotLight
        +   * @param  {p5.Color|Number[]|String} color
        +   * @param  {Number}                   x
        +   * @param  {Number}                   y
        +   * @param  {Number}                   z
        +   * @param  {Number}                   rx
        +   * @param  {Number}                   ry
        +   * @param  {Number}                   rz
        +   * @param  {Number}                   [angle]
        +   * @param  {Number}                   [concentration]
        +   */
        +  fn.spotLight = function (
        +    v1,
        +    v2,
        +    v3,
        +    x,
        +    y,
        +    z,
        +    nx,
        +    ny,
        +    nz,
        +    angle,
        +    concentration
        +  ) {
        +    this._assert3d('spotLight');
        +    // p5._validateParameters('spotLight', arguments);
        +
        +    this._renderer.spotLight(...arguments);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Removes all lights from the sketch.
        +   *
        +   * Calling `noLights()` removes any lights created with
        +   * <a href="#/p5/lights">lights()</a>,
        +   * <a href="#/p5/ambientLight">ambientLight()</a>,
        +   * <a href="#/p5/directionalLight">directionalLight()</a>,
        +   * <a href="#/p5/pointLight">pointLight()</a>, or
        +   * <a href="#/p5/spotLight">spotLight()</a>. These functions may be called
        +   * after `noLights()` to create a new lighting scheme.
        +   *
        +   * @method noLights
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('Two spheres drawn against a gray background. The top sphere is white and the bottom sphere is red.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the spheres.
        +   *   noStroke();
        +   *
        +   *   // Draw the top sphere.
        +   *   push();
        +   *   translate(0, -25, 0);
        +   *   sphere(20);
        +   *   pop();
        +   *
        +   *   // Turn off the lights.
        +   *   noLights();
        +   *
        +   *   // Add a red directional light that points into the screen.
        +   *   directionalLight(255, 0, 0, 0, 0, -1);
        +   *
        +   *   // Draw the bottom sphere.
        +   *   push();
        +   *   translate(0, 25, 0);
        +   *   sphere(20);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.noLights = function (...args) {
        +    this._assert3d('noLights');
        +    // p5._validateParameters('noLights', args);
        +
        +    this._renderer.noLights();
        +
        +    return this;
        +  };
        +
        +
        +  RendererGL.prototype.ambientLight = function(v1, v2, v3, a) {
        +    const color = this._pInst.color(...arguments);
        +
        +    this.states.ambientLightColors.push(
        +      color._array[0],
        +      color._array[1],
        +      color._array[2]
        +    );
         
        -  let _x, _y, _z;
        -  const v = arguments[arguments.length - 1];
        -  if (typeof v === 'number') {
        -    _x = arguments[arguments.length - 3];
        -    _y = arguments[arguments.length - 2];
        -    _z = arguments[arguments.length - 1];
        -  } else {
        -    _x = v.x;
        -    _y = v.y;
        -    _z = v.z;
        +    this.states.enableLighting = true;
           }
         
        -  // normalize direction
        -  const l = Math.sqrt(_x * _x + _y * _y + _z * _z);
        -  this._renderer.directionalLightDirections.push(_x / l, _y / l, _z / l);
        -
        -  this._renderer.directionalLightDiffuseColors.push(
        -    color._array[0],
        -    color._array[1],
        -    color._array[2]
        -  );
        -  Array.prototype.push.apply(
        -    this._renderer.directionalLightSpecularColors,
        -    this._renderer.specularColors
        -  );
        -
        -  this._renderer._enableLighting = true;
        +  RendererGL.prototype.specularColor = function(v1, v2, v3) {
        +    const color = this._pInst.color(...arguments);
         
        -  return this;
        -};
        +    this.states.specularColors = [
        +      color._array[0],
        +      color._array[1],
        +      color._array[2]
        +    ];
        +  }
         
        -/**
        - * Creates a light that shines from a point in all directions.
        - *
        - * Point lights are like light bulbs that shine in all directions. They can be
        - * placed at different positions to achieve different lighting effects. A
        - * maximum of 5 point lights can be active at once.
        - *
        - * There are four ways to call `pointLight()` with parameters to set the
        - * light’s color and position.
        - *
        - * The first way to call `pointLight()` has six parameters. The first three
        - * parameters, `v1`, `v2`, and `v3`, set the light’s color using the current
        - * <a href="#/p5/colorMode">colorMode()</a>. The last three parameters, `x`,
        - * `y`, and `z`, set the light’s position. For example,
        - * `pointLight(255, 0, 0, 50, 0, 0)` creates a red `(255, 0, 0)` light that
        - * shines from the coordinates `(50, 0, 0)`.
        - *
        - * The second way to call `pointLight()` has four parameters. The first three
        - * parameters, `v1`, `v2`, and `v3`, set the light’s color using the current
        - * <a href="#/p5/colorMode">colorMode()</a>. The last parameter, position sets
        - * the light’s position using a <a href="#/p5.Vector">p5.Vector</a> object.
        - * For example, `pointLight(255, 0, 0, lightPos)` creates a red `(255, 0, 0)`
        - * light that shines from the position set by the `lightPos` vector.
        - *
        - * The third way to call `pointLight()` has four parameters. The first
        - * parameter, `color`, sets the light’s color using a
        - * <a href="#/p5.Color">p5.Color</a> object or an array of color values. The
        - * last three parameters, `x`, `y`, and `z`, set the light’s position. For
        - * example, `directionalLight(myColor, 50, 0, 0)` creates a light that shines
        - * from the coordinates `(50, 0, 0)` with the color value of `myColor`.
        - *
        - * The fourth way to call `pointLight()` has two parameters. The first
        - * parameter, `color`, sets the light’s color using a
        - * <a href="#/p5.Color">p5.Color</a> object or an array of color values. The
        - * second parameter, `position`, sets the light’s position using a
        - * <a href="#/p5.Vector">p5.Vector</a> object. For example,
        - * `directionalLight(myColor, lightPos)` creates a light that shines from the
        - * position set by the `lightPos` vector with the color value of `myColor`.
        - *
        - * @method pointLight
        - * @param  {Number}    v1 red or hue value in the current
        - *                        <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}    v2 green or saturation value in the current
        - *                        <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}    v3 blue, brightness, or lightness value in the current
        - *                        <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}    x  x-coordinate of the light.
        - * @param  {Number}    y  y-coordinate of the light.
        - * @param  {Number}    z  z-coordinate of the light.
        - * @chainable
        - *
        - * @example
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click to turn on the point light.
        - *
        - * let isLit = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A sphere drawn on a gray background. A red light starts shining from above when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Control the light.
        - *   if (isLit === true) {
        - *     // Add a red point light from above.
        - *     // Use RGB values and XYZ coordinates.
        - *     pointLight(255, 0, 0, 0, -150, 0);
        - *   }
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - *
        - * // Turn on the point light when the user double-clicks.
        - * function doubleClicked() {
        - *   isLit = true;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A sphere drawn on a gray background. The top of the sphere appears bright red. The color gets darker toward the bottom.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Add a red point light from above.
        - *   // Use a p5.Color object and XYZ directions.
        - *   let c = color(255, 0, 0);
        - *   pointLight(c, 0, -150, 0);
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A sphere drawn on a gray background. The top of the sphere appears bright red. The color gets darker toward the bottom.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Add a red point light from above.
        - *   // Use a p5.Color object and a p5.Vector object.
        - *   let c = color(255, 0, 0);
        - *   let lightPos = createVector(0, -150, 0);
        - *   pointLight(c, lightPos);
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('Four spheres arranged in a square and drawn on a gray background. The spheres appear bright red toward the center of the square. The color gets darker toward the corners of the square.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Add a red point light that points to the center of the scene.
        - *   // Use a p5.Color object and a p5.Vector object.
        - *   let c = color(255, 0, 0);
        - *   let lightPos = createVector(0, 0, 65);
        - *   pointLight(c, lightPos);
        - *
        - *   // Style the spheres.
        - *   noStroke();
        - *
        - *   // Draw a sphere up and to the left.
        - *   push();
        - *   translate(-25, -25, 25);
        - *   sphere(10);
        - *   pop();
        - *
        - *   // Draw a box up and to the right.
        - *   push();
        - *   translate(25, -25, 25);
        - *   sphere(10);
        - *   pop();
        - *
        - *   // Draw a sphere down and to the left.
        - *   push();
        - *   translate(-25, 25, 25);
        - *   sphere(10);
        - *   pop();
        - *
        - *   // Draw a box down and to the right.
        - *   push();
        - *   translate(25, 25, 25);
        - *   sphere(10);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        +  RendererGL.prototype.directionalLight = function(v1, v2, v3, x, y, z) {
        +    let color;
        +    if (v1 instanceof Color) {
        +      color = v1;
        +    } else {
        +      color = this._pInst.color(v1, v2, v3);
        +    }
        +
        +    let _x, _y, _z;
        +    const v = arguments[arguments.length - 1];
        +    if (typeof v === 'number') {
        +      _x = arguments[arguments.length - 3];
        +      _y = arguments[arguments.length - 2];
        +      _z = arguments[arguments.length - 1];
        +    } else {
        +      _x = v.x;
        +      _y = v.y;
        +      _z = v.z;
        +    }
        +
        +    // normalize direction
        +    const l = Math.sqrt(_x * _x + _y * _y + _z * _z);
        +    this.states.directionalLightDirections.push(_x / l, _y / l, _z / l);
        +
        +    this.states.directionalLightDiffuseColors.push(
        +      color._array[0],
        +      color._array[1],
        +      color._array[2]
        +    );
        +    Array.prototype.push.apply(
        +      this.states.directionalLightSpecularColors,
        +      this.states.specularColors
        +    );
         
        -/**
        - * @method pointLight
        - * @param  {Number}     v1
        - * @param  {Number}     v2
        - * @param  {Number}     v3
        - * @param  {p5.Vector}  position position of the light as a
        - *                               <a href="#/p5.Vector">p5.Vector</a> object.
        - * @chainable
        - */
        +    this.states.enableLighting = true;
        +  }
         
        -/**
        - * @method pointLight
        - * @param  {p5.Color|Number[]|String} color color as a <a href="#/p5.Color">p5.Color</a> object,
        - *                                          an array of color values, or a CSS string.
        - * @param  {Number}                   x
        - * @param  {Number}                   y
        - * @param  {Number}                   z
        - * @chainable
        - */
        +  RendererGL.prototype.pointLight = function(v1, v2, v3, x, y, z) {
        +    let color;
        +    if (v1 instanceof Color) {
        +      color = v1;
        +    } else {
        +      color = this._pInst.color(v1, v2, v3);
        +    }
        +
        +    let _x, _y, _z;
        +    const v = arguments[arguments.length - 1];
        +    if (typeof v === 'number') {
        +      _x = arguments[arguments.length - 3];
        +      _y = arguments[arguments.length - 2];
        +      _z = arguments[arguments.length - 1];
        +    } else {
        +      _x = v.x;
        +      _y = v.y;
        +      _z = v.z;
        +    }
        +
        +    this.states.pointLightPositions.push(_x, _y, _z);
        +    this.states.pointLightDiffuseColors.push(
        +      color._array[0],
        +      color._array[1],
        +      color._array[2]
        +    );
        +    Array.prototype.push.apply(
        +      this.states.pointLightSpecularColors,
        +      this.states.specularColors
        +    );
         
        -/**
        - * @method pointLight
        - * @param  {p5.Color|Number[]|String} color
        - * @param  {p5.Vector}                position
        - * @chainable
        - */
        -p5.prototype.pointLight = function (v1, v2, v3, x, y, z) {
        -  this._assert3d('pointLight');
        -  p5._validateParameters('pointLight', arguments);
        -
        -  //@TODO: check parameters number
        -  let color;
        -  if (v1 instanceof p5.Color) {
        -    color = v1;
        -  } else {
        -    color = this.color(v1, v2, v3);
        +    this.states.enableLighting = true;
           }
         
        -  let _x, _y, _z;
        -  const v = arguments[arguments.length - 1];
        -  if (typeof v === 'number') {
        -    _x = arguments[arguments.length - 3];
        -    _y = arguments[arguments.length - 2];
        -    _z = arguments[arguments.length - 1];
        -  } else {
        -    _x = v.x;
        -    _y = v.y;
        -    _z = v.z;
        +  RendererGL.prototype.imageLight = function(img) {
        +    // activeImageLight property is checked by _setFillUniforms
        +    // for sending uniforms to the fillshader
        +    this.states.activeImageLight = img;
        +    this.states.enableLighting = true;
           }
         
        -  this._renderer.pointLightPositions.push(_x, _y, _z);
        -  this._renderer.pointLightDiffuseColors.push(
        -    color._array[0],
        -    color._array[1],
        -    color._array[2]
        -  );
        -  Array.prototype.push.apply(
        -    this._renderer.pointLightSpecularColors,
        -    this._renderer.specularColors
        -  );
        -
        -  this._renderer._enableLighting = true;
        -
        -  return this;
        -};
        -
        -/**
        - * Creates an ambient light from an image.
        - *
        - * `imageLight()` simulates a light shining from all directions. The effect is
        - * like placing the sketch at the center of a giant sphere that uses the image
        - * as its texture. The image's diffuse light will be affected by
        - * <a href="#/p5/fill">fill()</a> and the specular reflections will be
        - * affected by <a href="#/p5/specularMaterial">specularMaterial()</a> and
        - * <a href="#/p5/shininess">shininess()</a>.
        - *
        - * The parameter, `img`, is the <a href="#/p5.Image">p5.Image</a> object to
        - * use as the light source.
        - *
        - * @method imageLight
        - * @param  {p5.image}    img image to use as the light source.
        - *
        - * @example
        - * <div class="notest">
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let img;
        - *
        - * // Load an image and create a p5.Image object.
        - * function preload() {
        - *   img = loadImage('assets/outdoor_spheremap.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A sphere floating above a landscape. The surface of the sphere reflects the landscape.');
        - * }
        - *
        - * function draw() {
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the image as a panorama (360˚ background).
        - *   panorama(img);
        - *
        - *   // Add a soft ambient light.
        - *   ambientLight(50);
        - *
        - *   // Add light from the image.
        - *   imageLight(img);
        - *
        - *   // Style the sphere.
        - *   specularMaterial(20);
        - *   shininess(100);
        - *   noStroke();
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.imageLight = function (img) {
        -  // activeImageLight property is checked by _setFillUniforms
        -  // for sending uniforms to the fillshader
        -  this._renderer.activeImageLight = img;
        -  this._renderer._enableLighting = true;
        -};
        +  RendererGL.prototype.lights = function() {
        +    const grayColor = this._pInst.color('rgb(128,128,128)');
        +    this.ambientLight(grayColor);
        +    this.directionalLight(grayColor, 0, 0, -1);
        +  }
         
        -/**
        - * Creates an immersive 3D background.
        - *
        - * `panorama()` transforms images containing 360˚ content, such as maps or
        - * HDRIs, into immersive 3D backgrounds that surround a sketch. Exploring the
        - * space requires changing the camera's perspective with functions such as
        - * <a href="#/p5/orbitControl">orbitControl()</a> or
        - * <a href="#/p5/camera">camera()</a>.
        - *
        - * @method panorama
        - * @param {p5.Image} img 360˚ image to use as the background.
        - *
        - * @example
        - * <div class="notest">
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let img;
        - *
        - * // Load an image and create a p5.Image object.
        - * function preload() {
        - *   img = loadImage('assets/outdoor_spheremap.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100 ,100 ,WEBGL);
        - *
        - *   describe('A sphere floating above a landscape. The surface of the sphere reflects the landscape. The full landscape is viewable in 3D as the user drags the mouse.');
        - * }
        - *
        - * function draw() {
        - *   // Add the panorama.
        - *   panorama(img);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Use the image as a light source.
        - *   imageLight(img);
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *   specularMaterial(50);
        - *   shininess(200);
        - *   metalness(100);
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.panorama = function (img) {
        -  this.filter(this._renderer._getSphereMapping(img));
        -};
        +  RendererGL.prototype.lightFalloff = function(
        +    constantAttenuation,
        +    linearAttenuation,
        +    quadraticAttenuation
        +  ) {
        +    if (constantAttenuation < 0) {
        +      constantAttenuation = 0;
        +      console.warn(
        +        'Value of constant argument in lightFalloff() should be never be negative. Set to 0.'
        +      );
        +    }
         
        -/**
        - * Places an ambient and directional light in the scene.
        - * The lights are set to ambientLight(128, 128, 128) and
        - * directionalLight(128, 128, 128, 0, 0, -1).
        - *
        - * Note: lights need to be called (whether directly or indirectly)
        - * within draw() to remain persistent in a looping program.
        - * Placing them in setup() will cause them to only have an effect
        - * the first time through the loop.
        - *
        - * @method lights
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click to turn on the lights.
        - *
        - * let isLit = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white box drawn against a gray background. The quality of the light changes when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Control the lights.
        - *   if (isLit === true) {
        - *     lights();
        - *   }
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - *
        - * // Turn on the lights when the user double-clicks.
        - * function doubleClicked() {
        - *   isLit = true;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white box drawn against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   ambientLight(128, 128, 128);
        - *   directionalLight(128, 128, 128, 0, 0, -1);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.lights = function () {
        -  this._assert3d('lights');
        -  // Both specify gray by default.
        -  const grayColor = this.color('rgb(128,128,128)');
        -  this.ambientLight(grayColor);
        -  this.directionalLight(grayColor, 0, 0, -1);
        -  return this;
        -};
        +    if (linearAttenuation < 0) {
        +      linearAttenuation = 0;
        +      console.warn(
        +        'Value of linear argument in lightFalloff() should be never be negative. Set to 0.'
        +      );
        +    }
         
        -/**
        - * Sets the falloff rate for <a href="#/p5/pointLight">pointLight()</a>
        - * and <a href="#/p5/spotLight">spotLight()</a>.
        - *
        - * A light’s falloff describes the intensity of its beam at a distance. For
        - * example, a lantern has a slow falloff, a flashlight has a medium falloff,
        - * and a laser pointer has a sharp falloff.
        - *
        - * `lightFalloff()` has three parameters, `constant`, `linear`, and
        - * `quadratic`. They’re numbers used to calculate falloff at a distance, `d`,
        - * as follows:
        - *
        - * `falloff = 1 / (constant + d * linear + (d * d) * quadratic)`
        - *
        - * Note: `constant`, `linear`, and `quadratic` should always be set to values
        - * greater than 0.
        - *
        - * @method lightFalloff
        - * @param {Number} constant  constant value for calculating falloff.
        - * @param {Number} linear    linear value for calculating falloff.
        - * @param {Number} quadratic quadratic value for calculating falloff.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click to change the falloff rate.
        - *
        - * let useFalloff = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A sphere drawn against a gray background. The intensity of the light changes when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Set the light falloff.
        - *   if (useFalloff === true) {
        - *     lightFalloff(2, 0, 0);
        - *   }
        - *
        - *   // Add a white point light from the front.
        - *   pointLight(255, 255, 255, 0, 0, 100);
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - *
        - * // Change the falloff value when the user double-clicks.
        - * function doubleClicked() {
        - *   useFalloff = true;
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.lightFalloff = function (
        -  constantAttenuation,
        -  linearAttenuation,
        -  quadraticAttenuation
        -) {
        -  this._assert3d('lightFalloff');
        -  p5._validateParameters('lightFalloff', arguments);
        -
        -  if (constantAttenuation < 0) {
        -    constantAttenuation = 0;
        -    console.warn(
        -      'Value of constant argument in lightFalloff() should be never be negative. Set to 0.'
        -    );
        -  }
        +    if (quadraticAttenuation < 0) {
        +      quadraticAttenuation = 0;
        +      console.warn(
        +        'Value of quadratic argument in lightFalloff() should be never be negative. Set to 0.'
        +      );
        +    }
         
        -  if (linearAttenuation < 0) {
        -    linearAttenuation = 0;
        -    console.warn(
        -      'Value of linear argument in lightFalloff() should be never be negative. Set to 0.'
        -    );
        -  }
        +    if (
        +      constantAttenuation === 0 &&
        +      (linearAttenuation === 0 && quadraticAttenuation === 0)
        +    ) {
        +      constantAttenuation = 1;
        +      console.warn(
        +        'Either one of the three arguments in lightFalloff() should be greater than zero. Set constant argument to 1.'
        +      );
        +    }
         
        -  if (quadraticAttenuation < 0) {
        -    quadraticAttenuation = 0;
        -    console.warn(
        -      'Value of quadratic argument in lightFalloff() should be never be negative. Set to 0.'
        -    );
        +    this.states.constantAttenuation = constantAttenuation;
        +    this.states.linearAttenuation = linearAttenuation;
        +    this.states.quadraticAttenuation = quadraticAttenuation;
           }
         
        -  if (
        -    constantAttenuation === 0 &&
        -    (linearAttenuation === 0 && quadraticAttenuation === 0)
        +  RendererGL.prototype.spotLight = function(
        +    v1,
        +    v2,
        +    v3,
        +    x,
        +    y,
        +    z,
        +    nx,
        +    ny,
        +    nz,
        +    angle,
        +    concentration
           ) {
        -    constantAttenuation = 1;
        -    console.warn(
        -      'Either one of the three arguments in lightFalloff() should be greater than zero. Set constant argument to 1.'
        -    );
        -  }
        -
        -  this._renderer.constantAttenuation = constantAttenuation;
        -  this._renderer.linearAttenuation = linearAttenuation;
        -  this._renderer.quadraticAttenuation = quadraticAttenuation;
        -
        -  return this;
        -};
        -
        -/**
        - * Creates a light that shines from a point in one direction.
        - *
        - * Spot lights are like flashlights that shine in one direction creating a
        - * cone of light. The shape of the cone can be controlled using the angle and
        - * concentration parameters. A maximum of 5 spot lights can be active at once.
        - *
        - * There are eight ways to call `spotLight()` with parameters to set the
        - * light’s color, position, direction. For example,
        - * `spotLight(255, 0, 0, 0, 0, 0, 1, 0, 0)` creates a red `(255, 0, 0)` light
        - * at the origin `(0, 0, 0)` that points to the right `(1, 0, 0)`.
        - *
        - * The `angle` parameter is optional. It sets the radius of the light cone.
        - * For example, `spotLight(255, 0, 0, 0, 0, 0, 1, 0, 0, PI / 16)` creates a
        - * red `(255, 0, 0)` light at the origin `(0, 0, 0)` that points to the right
        - * `(1, 0, 0)` with an angle of `PI / 16` radians. By default, `angle` is
        - * `PI / 3` radians.
        - *
        - * The `concentration` parameter is also optional. It focuses the light
        - * towards the center of the light cone. For example,
        - * `spotLight(255, 0, 0, 0, 0, 0, 1, 0, 0, PI / 16, 50)` creates a red
        - * `(255, 0, 0)` light at the origin `(0, 0, 0)` that points to the right
        - * `(1, 0, 0)` with an angle of `PI / 16` radians at concentration of 50. By
        - * default, `concentration` is 100.
        - *
        - * @method spotLight
        - * @param  {Number}    v1               red or hue value in the current
        - *                                      <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}    v2               green or saturation value in the current
        - *                                      <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}    v3               blue, brightness, or lightness value in the current
        - *                                      <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}    x                x-coordinate of the light.
        - * @param  {Number}    y                y-coordinate of the light.
        - * @param  {Number}    z                z-coordinate of the light.
        - * @param  {Number}    rx               x-component of light direction between -1 and 1.
        - * @param  {Number}    ry               y-component of light direction between -1 and 1.
        - * @param  {Number}    rz               z-component of light direction between -1 and 1.
        - * @param  {Number}    [angle]          angle of the light cone. Defaults to `PI / 3`.
        - * @param  {Number}    [concentration]  concentration of the light. Defaults to 100.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click to adjust the spotlight.
        - *
        - * let isLit = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white sphere drawn on a gray background. A red spotlight starts shining when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Control the spotlight.
        - *   if (isLit === true) {
        - *     // Add a red spot light that shines into the screen.
        - *     // Set its angle to PI / 32 radians.
        - *     spotLight(255, 0, 0, 0, 0, 100, 0, 0, -1, PI / 32);
        - *   }
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - *
        - * // Turn on the spotlight when the user double-clicks.
        - * function doubleClicked() {
        - *   isLit = true;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click to adjust the spotlight.
        - *
        - * let isLit = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white sphere drawn on a gray background. A red spotlight starts shining when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Control the spotlight.
        - *   if (isLit === true) {
        - *     // Add a red spot light that shines into the screen.
        - *     // Set its angle to PI / 3 radians (default).
        - *     // Set its concentration to 1000.
        - *     let c = color(255, 0, 0);
        - *     let position = createVector(0, 0, 100);
        - *     let direction = createVector(0, 0, -1);
        - *     spotLight(c, position, direction, PI / 3, 1000);
        - *   }
        - *
        - *   // Draw the sphere.
        - *   sphere(30);
        - * }
        - *
        - * // Turn on the spotlight when the user double-clicks.
        - * function doubleClicked() {
        - *   isLit = true;
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method spotLight
        - * @param  {p5.Color|Number[]|String} color     color as a <a href="#/p5.Color">p5.Color</a> object,
        - *                                              an array of color values, or a CSS string.
        - * @param  {p5.Vector}                position  position of the light as a <a href="#/p5.Vector">p5.Vector</a> object.
        - * @param  {p5.Vector}                direction direction of light as a <a href="#/p5.Vector">p5.Vector</a> object.
        - * @param  {Number}                   [angle]
        - * @param  {Number}                   [concentration]
        - */
        -/**
        - * @method spotLight
        - * @param  {Number}     v1
        - * @param  {Number}     v2
        - * @param  {Number}     v3
        - * @param  {p5.Vector}  position
        - * @param  {p5.Vector}  direction
        - * @param  {Number}     [angle]
        - * @param  {Number}     [concentration]
        - */
        -/**
        - * @method spotLight
        - * @param  {p5.Color|Number[]|String} color
        - * @param  {Number}                   x
        - * @param  {Number}                   y
        - * @param  {Number}                   z
        - * @param  {p5.Vector}                direction
        - * @param  {Number}                   [angle]
        - * @param  {Number}                   [concentration]
        - */
        -/**
        - * @method spotLight
        - * @param  {p5.Color|Number[]|String} color
        - * @param  {p5.Vector}                position
        - * @param  {Number}                   rx
        - * @param  {Number}                   ry
        - * @param  {Number}                   rz
        - * @param  {Number}                   [angle]
        - * @param  {Number}                   [concentration]
        - */
        -/**
        - * @method spotLight
        - * @param  {Number}     v1
        - * @param  {Number}     v2
        - * @param  {Number}     v3
        - * @param  {Number}     x
        - * @param  {Number}     y
        - * @param  {Number}     z
        - * @param  {p5.Vector}  direction
        - * @param  {Number}     [angle]
        - * @param  {Number}     [concentration]
        - */
        -/**
        - * @method spotLight
        - * @param  {Number}     v1
        - * @param  {Number}     v2
        - * @param  {Number}     v3
        - * @param  {p5.Vector}  position
        - * @param  {Number}     rx
        - * @param  {Number}     ry
        - * @param  {Number}     rz
        - * @param  {Number}     [angle]
        - * @param  {Number}     [concentration]
        - */
        -/**
        - * @method spotLight
        - * @param  {p5.Color|Number[]|String} color
        - * @param  {Number}                   x
        - * @param  {Number}                   y
        - * @param  {Number}                   z
        - * @param  {Number}                   rx
        - * @param  {Number}                   ry
        - * @param  {Number}                   rz
        - * @param  {Number}                   [angle]
        - * @param  {Number}                   [concentration]
        - */
        -p5.prototype.spotLight = function (
        -  v1,
        -  v2,
        -  v3,
        -  x,
        -  y,
        -  z,
        -  nx,
        -  ny,
        -  nz,
        -  angle,
        -  concentration
        -) {
        -  this._assert3d('spotLight');
        -  p5._validateParameters('spotLight', arguments);
        -
        -  let color, position, direction;
        -  const length = arguments.length;
        -
        -  switch (length) {
        -    case 11:
        -    case 10:
        -      color = this.color(v1, v2, v3);
        -      position = new p5.Vector(x, y, z);
        -      direction = new p5.Vector(nx, ny, nz);
        -      break;
        -
        -    case 9:
        -      if (v1 instanceof p5.Color) {
        -        color = v1;
        -        position = new p5.Vector(v2, v3, x);
        -        direction = new p5.Vector(y, z, nx);
        -        angle = ny;
        -        concentration = nz;
        -      } else if (x instanceof p5.Vector) {
        -        color = this.color(v1, v2, v3);
        -        position = x;
        -        direction = new p5.Vector(y, z, nx);
        -        angle = ny;
        -        concentration = nz;
        -      } else if (nx instanceof p5.Vector) {
        -        color = this.color(v1, v2, v3);
        -        position = new p5.Vector(x, y, z);
        -        direction = nx;
        -        angle = ny;
        -        concentration = nz;
        -      } else {
        -        color = this.color(v1, v2, v3);
        -        position = new p5.Vector(x, y, z);
        -        direction = new p5.Vector(nx, ny, nz);
        -      }
        -      break;
        -
        -    case 8:
        -      if (v1 instanceof p5.Color) {
        -        color = v1;
        -        position = new p5.Vector(v2, v3, x);
        -        direction = new p5.Vector(y, z, nx);
        -        angle = ny;
        -      } else if (x instanceof p5.Vector) {
        -        color = this.color(v1, v2, v3);
        -        position = x;
        -        direction = new p5.Vector(y, z, nx);
        -        angle = ny;
        -      } else {
        -        color = this.color(v1, v2, v3);
        -        position = new p5.Vector(x, y, z);
        -        direction = nx;
        -        angle = ny;
        -      }
        -      break;
        -
        -    case 7:
        -      if (v1 instanceof p5.Color && v2 instanceof p5.Vector) {
        -        color = v1;
        -        position = v2;
        -        direction = new p5.Vector(v3, x, y);
        -        angle = z;
        -        concentration = nx;
        -      } else if (v1 instanceof p5.Color && y instanceof p5.Vector) {
        -        color = v1;
        -        position = new p5.Vector(v2, v3, x);
        -        direction = y;
        -        angle = z;
        -        concentration = nx;
        -      } else if (x instanceof p5.Vector && y instanceof p5.Vector) {
        -        color = this.color(v1, v2, v3);
        -        position = x;
        -        direction = y;
        -        angle = z;
        -        concentration = nx;
        -      } else if (v1 instanceof p5.Color) {
        -        color = v1;
        -        position = new p5.Vector(v2, v3, x);
        -        direction = new p5.Vector(y, z, nx);
        -      } else if (x instanceof p5.Vector) {
        -        color = this.color(v1, v2, v3);
        -        position = x;
        -        direction = new p5.Vector(y, z, nx);
        -      } else {
        -        color = this.color(v1, v2, v3);
        -        position = new p5.Vector(x, y, z);
        -        direction = nx;
        -      }
        -      break;
        -
        -    case 6:
        -      if (x instanceof p5.Vector && y instanceof p5.Vector) {
        -        color = this.color(v1, v2, v3);
        -        position = x;
        -        direction = y;
        -        angle = z;
        -      } else if (v1 instanceof p5.Color && y instanceof p5.Vector) {
        -        color = v1;
        -        position = new p5.Vector(v2, v3, x);
        -        direction = y;
        -        angle = z;
        -      } else if (v1 instanceof p5.Color && v2 instanceof p5.Vector) {
        -        color = v1;
        -        position = v2;
        -        direction = new p5.Vector(v3, x, y);
        -        angle = z;
        -      }
        -      break;
        -
        -    case 5:
        -      if (
        -        v1 instanceof p5.Color &&
        -        v2 instanceof p5.Vector &&
        -        v3 instanceof p5.Vector
        -      ) {
        +    let color, position, direction;
        +    const length = arguments.length;
        +
        +    switch (length) {
        +      case 11:
        +      case 10:
        +        color = this._pInst.color(v1, v2, v3);
        +        position = new Vector(x, y, z);
        +        direction = new Vector(nx, ny, nz);
        +        break;
        +
        +      case 9:
        +        if (v1 instanceof Color) {
        +          color = v1;
        +          position = new Vector(v2, v3, x);
        +          direction = new Vector(y, z, nx);
        +          angle = ny;
        +          concentration = nz;
        +        } else if (x instanceof Vector) {
        +          color = this._pInst.color(v1, v2, v3);
        +          position = x;
        +          direction = new Vector(y, z, nx);
        +          angle = ny;
        +          concentration = nz;
        +        } else if (nx instanceof Vector) {
        +          color = this._pInst.color(v1, v2, v3);
        +          position = new Vector(x, y, z);
        +          direction = nx;
        +          angle = ny;
        +          concentration = nz;
        +        } else {
        +          color = this._pInst.color(v1, v2, v3);
        +          position = new Vector(x, y, z);
        +          direction = new Vector(nx, ny, nz);
        +        }
        +        break;
        +
        +      case 8:
        +        if (v1 instanceof Color) {
        +          color = v1;
        +          position = new Vector(v2, v3, x);
        +          direction = new Vector(y, z, nx);
        +          angle = ny;
        +        } else if (x instanceof Vector) {
        +          color = this._pInst.color(v1, v2, v3);
        +          position = x;
        +          direction = new Vector(y, z, nx);
        +          angle = ny;
        +        } else {
        +          color = this._pInst.color(v1, v2, v3);
        +          position = new Vector(x, y, z);
        +          direction = nx;
        +          angle = ny;
        +        }
        +        break;
        +
        +      case 7:
        +        if (v1 instanceof Color && v2 instanceof Vector) {
        +          color = v1;
        +          position = v2;
        +          direction = new Vector(v3, x, y);
        +          angle = z;
        +          concentration = nx;
        +        } else if (v1 instanceof Color && y instanceof Vector) {
        +          color = v1;
        +          position = new Vector(v2, v3, x);
        +          direction = y;
        +          angle = z;
        +          concentration = nx;
        +        } else if (x instanceof Vector && y instanceof Vector) {
        +          color = this._pInst.color(v1, v2, v3);
        +          position = x;
        +          direction = y;
        +          angle = z;
        +          concentration = nx;
        +        } else if (v1 instanceof Color) {
        +          color = v1;
        +          position = new Vector(v2, v3, x);
        +          direction = new Vector(y, z, nx);
        +        } else if (x instanceof Vector) {
        +          color = this._pInst.color(v1, v2, v3);
        +          position = x;
        +          direction = new Vector(y, z, nx);
        +        } else {
        +          color = this._pInst.color(v1, v2, v3);
        +          position = new Vector(x, y, z);
        +          direction = nx;
        +        }
        +        break;
        +
        +      case 6:
        +        if (x instanceof Vector && y instanceof Vector) {
        +          color = this._pInst.color(v1, v2, v3);
        +          position = x;
        +          direction = y;
        +          angle = z;
        +        } else if (v1 instanceof Color && y instanceof Vector) {
        +          color = v1;
        +          position = new Vector(v2, v3, x);
        +          direction = y;
        +          angle = z;
        +        } else if (v1 instanceof Color && v2 instanceof Vector) {
        +          color = v1;
        +          position = v2;
        +          direction = new Vector(v3, x, y);
        +          angle = z;
        +        }
        +        break;
        +
        +      case 5:
        +        if (
        +          v1 instanceof Color &&
        +          v2 instanceof Vector &&
        +          v3 instanceof Vector
        +        ) {
        +          color = v1;
        +          position = v2;
        +          direction = v3;
        +          angle = x;
        +          concentration = y;
        +        } else if (x instanceof Vector && y instanceof Vector) {
        +          color = this._pInst.color(v1, v2, v3);
        +          position = x;
        +          direction = y;
        +        } else if (v1 instanceof Color && y instanceof Vector) {
        +          color = v1;
        +          position = new Vector(v2, v3, x);
        +          direction = y;
        +        } else if (v1 instanceof Color && v2 instanceof Vector) {
        +          color = v1;
        +          position = v2;
        +          direction = new Vector(v3, x, y);
        +        }
        +        break;
        +
        +      case 4:
                 color = v1;
                 position = v2;
                 direction = v3;
                 angle = x;
        -        concentration = y;
        -      } else if (x instanceof p5.Vector && y instanceof p5.Vector) {
        -        color = this.color(v1, v2, v3);
        -        position = x;
        -        direction = y;
        -      } else if (v1 instanceof p5.Color && y instanceof p5.Vector) {
        -        color = v1;
        -        position = new p5.Vector(v2, v3, x);
        -        direction = y;
        -      } else if (v1 instanceof p5.Color && v2 instanceof p5.Vector) {
        +        break;
        +
        +      case 3:
                 color = v1;
                 position = v2;
        -        direction = new p5.Vector(v3, x, y);
        -      }
        -      break;
        -
        -    case 4:
        -      color = v1;
        -      position = v2;
        -      direction = v3;
        -      angle = x;
        -      break;
        -
        -    case 3:
        -      color = v1;
        -      position = v2;
        -      direction = v3;
        -      break;
        -
        -    default:
        +        direction = v3;
        +        break;
        +
        +      default:
        +        console.warn(
        +          `Sorry, input for spotlight() is not in prescribed format. Too ${
        +            length < 3 ? 'few' : 'many'
        +          } arguments were provided`
        +        );
        +        return;
        +    }
        +    this.states.spotLightDiffuseColors = [
        +      color._array[0],
        +      color._array[1],
        +      color._array[2]
        +    ];
        +
        +    this.states.spotLightSpecularColors = [
        +      ...this.states.specularColors
        +    ];
        +
        +    this.states.spotLightPositions = [position.x, position.y, position.z];
        +    direction.normalize();
        +    this.states.spotLightDirections = [
        +      direction.x,
        +      direction.y,
        +      direction.z
        +    ];
        +
        +    if (angle === undefined) {
        +      angle = Math.PI / 3;
        +    }
        +
        +    if (concentration !== undefined && concentration < 1) {
        +      concentration = 1;
               console.warn(
        -        `Sorry, input for spotlight() is not in prescribed format. Too ${
        -          length < 3 ? 'few' : 'many'
        -        } arguments were provided`
        +        'Value of concentration needs to be greater than 1. Setting it to 1'
               );
        -      return this;
        -  }
        -  this._renderer.spotLightDiffuseColors.push(
        -    color._array[0],
        -    color._array[1],
        -    color._array[2]
        -  );
        -
        -  Array.prototype.push.apply(
        -    this._renderer.spotLightSpecularColors,
        -    this._renderer.specularColors
        -  );
        -
        -  this._renderer.spotLightPositions.push(position.x, position.y, position.z);
        -  direction.normalize();
        -  this._renderer.spotLightDirections.push(
        -    direction.x,
        -    direction.y,
        -    direction.z
        -  );
        -
        -  if (angle === undefined) {
        -    angle = Math.PI / 3;
        -  }
        -
        -  if (concentration !== undefined && concentration < 1) {
        -    concentration = 1;
        -    console.warn(
        -      'Value of concentration needs to be greater than 1. Setting it to 1'
        -    );
        -  } else if (concentration === undefined) {
        -    concentration = 100;
        -  }
        -
        -  angle = this._renderer._pInst._toRadians(angle);
        -  this._renderer.spotLightAngle.push(Math.cos(angle));
        -  this._renderer.spotLightConc.push(concentration);
        -
        -  this._renderer._enableLighting = true;
        +    } else if (concentration === undefined) {
        +      concentration = 100;
        +    }
         
        -  return this;
        -};
        +    angle = this._pInst._toRadians(angle);
        +    this.states.spotLightAngle = [Math.cos(angle)];
        +    this.states.spotLightConc = [concentration];
         
        -/**
        - * Removes all lights from the sketch.
        - *
        - * Calling `noLights()` removes any lights created with
        - * <a href="#/p5/lights">lights()</a>,
        - * <a href="#/p5/ambientLight">ambientLight()</a>,
        - * <a href="#/p5/directionalLight">directionalLight()</a>,
        - * <a href="#/p5/pointLight">pointLight()</a>, or
        - * <a href="#/p5/spotLight">spotLight()</a>. These functions may be called
        - * after `noLights()` to create a new lighting scheme.
        - *
        - * @method noLights
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('Two spheres drawn against a gray background. The top sphere is white and the bottom sphere is red.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the spheres.
        - *   noStroke();
        - *
        - *   // Draw the top sphere.
        - *   push();
        - *   translate(0, -25, 0);
        - *   sphere(20);
        - *   pop();
        - *
        - *   // Turn off the lights.
        - *   noLights();
        - *
        - *   // Add a red directional light that points into the screen.
        - *   directionalLight(255, 0, 0, 0, 0, -1);
        - *
        - *   // Draw the bottom sphere.
        - *   push();
        - *   translate(0, 25, 0);
        - *   sphere(20);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.noLights = function (...args) {
        -  this._assert3d('noLights');
        -  p5._validateParameters('noLights', args);
        -
        -  this._renderer.activeImageLight = null;
        -  this._renderer._enableLighting = false;
        -
        -  this._renderer.ambientLightColors.length = 0;
        -  this._renderer.specularColors = [1, 1, 1];
        -
        -  this._renderer.directionalLightDirections.length = 0;
        -  this._renderer.directionalLightDiffuseColors.length = 0;
        -  this._renderer.directionalLightSpecularColors.length = 0;
        -
        -  this._renderer.pointLightPositions.length = 0;
        -  this._renderer.pointLightDiffuseColors.length = 0;
        -  this._renderer.pointLightSpecularColors.length = 0;
        -
        -  this._renderer.spotLightPositions.length = 0;
        -  this._renderer.spotLightDirections.length = 0;
        -  this._renderer.spotLightDiffuseColors.length = 0;
        -  this._renderer.spotLightSpecularColors.length = 0;
        -  this._renderer.spotLightAngle.length = 0;
        -  this._renderer.spotLightConc.length = 0;
        +    this.states.enableLighting = true;
        +  }
         
        -  this._renderer.constantAttenuation = 1;
        -  this._renderer.linearAttenuation = 0;
        -  this._renderer.quadraticAttenuation = 0;
        -  this._renderer._useShininess = 1;
        -  this._renderer._useMetalness = 0;
        +  RendererGL.prototype.noLights = function() {
        +    this.states.activeImageLight = null;
        +    this.states.enableLighting = false;
        +
        +    this.states.ambientLightColors.length = 0;
        +    this.states.specularColors = [1, 1, 1];
        +
        +    this.states.directionalLightDirections.length = 0;
        +    this.states.directionalLightDiffuseColors.length = 0;
        +    this.states.directionalLightSpecularColors.length = 0;
        +
        +    this.states.pointLightPositions.length = 0;
        +    this.states.pointLightDiffuseColors.length = 0;
        +    this.states.pointLightSpecularColors.length = 0;
        +
        +    this.states.spotLightPositions.length = 0;
        +    this.states.spotLightDirections.length = 0;
        +    this.states.spotLightDiffuseColors.length = 0;
        +    this.states.spotLightSpecularColors.length = 0;
        +    this.states.spotLightAngle.length = 0;
        +    this.states.spotLightConc.length = 0;
        +
        +    this.states.constantAttenuation = 1;
        +    this.states.linearAttenuation = 0;
        +    this.states.quadraticAttenuation = 0;
        +    this.states._useShininess = 1;
        +    this.states._useMetalness = 0;
        +  }
        +}
         
        -  return this;
        -};
        +export default light;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  light(p5, p5.prototype);
        +}
        diff --git a/src/webgl/loading.js b/src/webgl/loading.js
        index 9e22824da4..d664c8b8bf 100755
        --- a/src/webgl/loading.js
        +++ b/src/webgl/loading.js
        @@ -6,432 +6,433 @@
          * @requires p5.Geometry
          */
         
        -import p5 from '../core/main';
        -import './p5.Geometry';
        -
        -
        -/**
        - * Loads a 3D model to create a
        - * <a href="#/p5.Geometry">p5.Geometry</a> object.
        - *
        - * `loadModel()` can load 3D models from OBJ and STL files. Once the model is
        - * loaded, it can be displayed with the
        - * <a href="#/p5/model">model()</a> function, as in `model(shape)`.
        - *
        - * There are three ways to call `loadModel()` with optional parameters to help
        - * process the model.
        - *
        - * The first parameter, `path`, is always a `String` with the path to the
        - * file. Paths to local files should be relative, as in
        - * `loadModel('assets/model.obj')`. URLs such as
        - * `'https://example.com/model.obj'` may be blocked due to browser security.
        - *
        - * Note: When loading a `.obj` file that references materials stored in
        - * `.mtl` files, p5.js will attempt to load and apply those materials.
        - * To ensure that the `.obj` file reads the `.mtl` file correctly include the
        - * `.mtl` file alongside it.
        - *
        - * The first way to call `loadModel()` has three optional parameters after the
        - * file path. The first optional parameter, `successCallback`, is a function
        - * to call once the model loads. For example,
        - * `loadModel('assets/model.obj', handleModel)` will call the `handleModel()`
        - * function once the model loads. The second optional parameter,
        - * `failureCallback`, is a function to call if the model fails to load. For
        - * example, `loadModel('assets/model.obj', handleModel, handleFailure)` will
        - * call the `handleFailure()` function if an error occurs while loading. The
        - * third optional parameter, `fileType`, is the model’s file extension as a
        - * string. For example,
        - * `loadModel('assets/model', handleModel, handleFailure, '.obj')` will try to
        - * load the file model as a `.obj` file.
        - *
        - * The second way to call `loadModel()` has four optional parameters after the
        - * file path. The first optional parameter is a `Boolean` value. If `true` is
        - * passed, as in `loadModel('assets/model.obj', true)`, then the model will be
        - * resized to ensure it fits the canvas. The next three parameters are
        - * `successCallback`, `failureCallback`, and `fileType` as described above.
        - *
        - * The third way to call `loadModel()` has one optional parameter after the
        - * file path. The optional parameter, `options`, is an `Object` with options,
        - * as in `loadModel('assets/model.obj', options)`. The `options` object can
        - * have the following properties:
        - *
        - * ```js
        - * let options = {
        - *   // Enables standardized size scaling during loading if set to true.
        - *   normalize: true,
        - *
        - *   // Function to call once the model loads.
        - *   successCallback: handleModel,
        - *
        - *   // Function to call if an error occurs while loading.
        - *   failureCallback: handleError,
        - *
        - *   // Model's file extension.
        - *   fileType: '.stl',
        - *
        - *   // Flips the U texture coordinates of the model.
        - *   flipU: false,
        - *
        - *   // Flips the V texture coordinates of the model.
        - *   flipV: false
        - * };
        - *
        - * // Pass the options object to loadModel().
        - * loadModel('assets/model.obj', options);
        - * ```
        - *
        - * Models can take time to load. Calling `loadModel()` in
        - * <a href="#/p5/preload">preload()</a> ensures models load before they're
        - * used in <a href="#/p5/setup">setup()</a> or <a href="#/p5/draw">draw()</a>.
        - *
        - * Note: There’s no support for colored STL files. STL files with color will
        - * be rendered without color.
        - *
        - * @method loadModel
        - * @param  {String} path              path of the model to be loaded.
        - * @param  {Boolean} normalize        if `true`, scale the model to fit the canvas.
        - * @param  {function(p5.Geometry)} [successCallback] function to call once the model is loaded. Will be passed
        - *                                                   the <a href="#/p5.Geometry">p5.Geometry</a> object.
        - * @param  {function(Event)} [failureCallback] function to call if the model fails to load. Will be passed an `Error` event object.
        - * @param  {String} [fileType]          model’s file extension. Either `'.obj'` or `'.stl'`.
        - * @return {p5.Geometry} the <a href="#/p5.Geometry">p5.Geometry</a> object
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * // Load the file and create a p5.Geometry object.
        - * function preload() {
        - *   shape = loadModel('assets/teapot.obj');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white teapot drawn against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the shape.
        - *   model(shape);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * // Load the file and create a p5.Geometry object.
        - * // Normalize the geometry's size to fit the canvas.
        - * function preload() {
        - *   shape = loadModel('assets/teapot.obj', true);
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white teapot drawn against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the shape.
        - *   model(shape);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * // Load the file and create a p5.Geometry object.
        - * function preload() {
        - *   loadModel('assets/teapot.obj', true, handleModel);
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white teapot drawn against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the shape.
        - *   model(shape);
        - * }
        - *
        - * // Set the shape variable and log the geometry's
        - * // ID to the console.
        - * function handleModel(data) {
        - *   shape = data;
        - *   console.log(shape.gid);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div class='notest'>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * // Load the file and create a p5.Geometry object.
        - * function preload() {
        - *   loadModel('assets/wrong.obj', true, handleModel, handleError);
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white teapot drawn against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the shape.
        - *   model(shape);
        - * }
        - *
        - * // Set the shape variable and print the geometry's
        - * // ID to the console.
        - * function handleModel(data) {
        - *   shape = data;
        - *   console.log(shape.gid);
        - * }
        - *
        - * // Print an error message if the file doesn't load.
        - * function handleError(error) {
        - *   console.error('Oops!', error);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * // Load the file and create a p5.Geometry object.
        - * function preload() {
        - *   loadModel('assets/teapot.obj', true, handleModel, handleError, '.obj');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white teapot drawn against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the shape.
        - *   model(shape);
        - * }
        - *
        - * // Set the shape variable and print the geometry's
        - * // ID to the console.
        - * function handleModel(data) {
        - *   shape = data;
        - *   console.log(shape.gid);
        - * }
        - *
        - * // Print an error message if the file doesn't load.
        - * function handleError(error) {
        - *   console.error('Oops!', error);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - * let options = {
        - *   normalize: true,
        - *   successCallback: handleModel,
        - *   failureCallback: handleError,
        - *   fileType: '.obj'
        - * };
        - *
        - * // Load the file and create a p5.Geometry object.
        - * function preload() {
        - *   loadModel('assets/teapot.obj', options);
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white teapot drawn against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the shape.
        - *   model(shape);
        - * }
        - *
        - * // Set the shape variable and print the geometry's
        - * // ID to the console.
        - * function handleModel(data) {
        - *   shape = data;
        - *   console.log(shape.gid);
        - * }
        - *
        - * // Print an error message if the file doesn't load.
        - * function handleError(error) {
        - *   console.error('Oops!', error);
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method loadModel
        - * @param  {String} path
        - * @param  {function(p5.Geometry)} [successCallback]
        - * @param  {function(Event)} [failureCallback]
        - * @param  {String} [fileType]
        - * @return {p5.Geometry} new <a href="#/p5.Geometry">p5.Geometry</a> object.
        - */
        -/**
        - * @method loadModel
        - * @param  {String} path
        - * @param  {Object} [options] loading options.
        - * @param  {function(p5.Geometry)} [options.successCallback]
        - * @param  {function(Event)} [options.failureCallback]
        - * @param  {String} [options.fileType]
        - * @param  {boolean} [options.normalize]
        - * @param  {boolean} [options.flipU]
        - * @param  {boolean} [options.flipV]
        - * @return {p5.Geometry} new <a href="#/p5.Geometry">p5.Geometry</a> object.
        - */
        -p5.prototype.loadModel = function(path,options) {
        -  p5._validateParameters('loadModel', arguments);
        -  let normalize= false;
        -  let successCallback;
        -  let failureCallback;
        -  let flipU = false;
        -  let flipV = false;
        -  let fileType = path.slice(-4);
        -  if (options && typeof options === 'object') {
        -    normalize = options.normalize || false;
        -    successCallback = options.successCallback;
        -    failureCallback = options.failureCallback;
        -    fileType = options.fileType || fileType;
        -    flipU = options.flipU || false;
        -    flipV = options.flipV || false;
        -  } else if (typeof options === 'boolean') {
        -    normalize = options;
        -    successCallback = arguments[2];
        -    failureCallback = arguments[3];
        -    if (typeof arguments[4] !== 'undefined') {
        -      fileType = arguments[4];
        -    }
        -  } else {
        -    successCallback = typeof arguments[1] === 'function' ? arguments[1] : undefined;
        -    failureCallback = arguments[2];
        -    if (typeof arguments[3] !== 'undefined') {
        -      fileType = arguments[3];
        -    }
        +import { Geometry } from './p5.Geometry';
        +import { Vector } from '../math/p5.Vector';
        +import { request } from '../io/files';
        +
        +async function fileExists(url) {
        +  try {
        +    const response = await fetch(url, { method: 'HEAD' });
        +    return response.ok;
        +  } catch (error) {
        +    return false;
           }
        +}
         
        -  const model = new p5.Geometry();
        -  model.gid = `${path}|${normalize}`;
        -  const self = this;
        -
        -  async function getMaterials(lines){
        -    const parsedMaterialPromises=[];
        -
        -    for (let i = 0; i < lines.length; i++) {
        -      const mtllibMatch = lines[i].match(/^mtllib (.+)/);
        -      if (mtllibMatch) {
        -        let mtlPath='';
        -        const mtlFilename = mtllibMatch[1];
        -        const objPathParts = path.split('/');
        -        if(objPathParts.length > 1){
        -          objPathParts.pop();
        -          const objFolderPath = objPathParts.join('/');
        -          mtlPath = objFolderPath + '/' + mtlFilename;
        +function loading(p5, fn){
        +  /**
        +   * Loads a 3D model to create a
        +   * <a href="#/p5.Geometry">p5.Geometry</a> object.
        +   *
        +   * `loadModel()` can load 3D models from OBJ and STL files. Once the model is
        +   * loaded, it can be displayed with the
        +   * <a href="#/p5/model">model()</a> function, as in `model(shape)`.
        +   *
        +   * There are three ways to call `loadModel()` with optional parameters to help
        +   * process the model.
        +   *
        +   * The first parameter, `path`, is a `String` with the path to the file. Paths
        +   * to local files should be relative, as in `loadModel('assets/model.obj')`.
        +   * URLs such as `'https://example.com/model.obj'` may be blocked due to browser
        +   * security. The `path` parameter can also be defined as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
        +   * object for more advanced usage.
        +   * Note: When loading a `.obj` file that references materials stored in
        +   * `.mtl` files, p5.js will attempt to load and apply those materials.
        +   * To ensure that the `.obj` file reads the `.mtl` file correctly include the
        +   * `.mtl` file alongside it.
        +   *
        +   * The first way to call `loadModel()` has three optional parameters after the
        +   * file path. The first optional parameter, `successCallback`, is a function
        +   * to call once the model loads. For example,
        +   * `loadModel('assets/model.obj', handleModel)` will call the `handleModel()`
        +   * function once the model loads. The second optional parameter,
        +   * `failureCallback`, is a function to call if the model fails to load. For
        +   * example, `loadModel('assets/model.obj', handleModel, handleFailure)` will
        +   * call the `handleFailure()` function if an error occurs while loading. The
        +   * third optional parameter, `fileType`, is the model’s file extension as a
        +   * string. For example,
        +   * `loadModel('assets/model', handleModel, handleFailure, '.obj')` will try to
        +   * load the file model as a `.obj` file.
        +   *
        +   * The second way to call `loadModel()` has four optional parameters after the
        +   * file path. The first optional parameter is a `Boolean` value. If `true` is
        +   * passed, as in `loadModel('assets/model.obj', true)`, then the model will be
        +   * resized to ensure it fits the canvas. The next three parameters are
        +   * `successCallback`, `failureCallback`, and `fileType` as described above.
        +   *
        +   * The third way to call `loadModel()` has one optional parameter after the
        +   * file path. The optional parameter, `options`, is an `Object` with options,
        +   * as in `loadModel('assets/model.obj', options)`. The `options` object can
        +   * have the following properties:
        +   *
        +   * ```js
        +   * let options = {
        +   *   // Enables standardized size scaling during loading if set to true.
        +   *   normalize: true,
        +   *
        +   *   // Function to call once the model loads.
        +   *   successCallback: handleModel,
        +   *
        +   *   // Function to call if an error occurs while loading.
        +   *   failureCallback: handleError,
        +   *
        +   *   // Model's file extension.
        +   *   fileType: '.stl',
        +   *
        +   *   // Flips the U texture coordinates of the model.
        +   *   flipU: false,
        +   *
        +   *   // Flips the V texture coordinates of the model.
        +   *   flipV: false
        +   * };
        +   *
        +   * // Pass the options object to loadModel().
        +   * loadModel('assets/model.obj', options);
        +   * ```
        +   *
        +   * This function returns a `Promise` and should be used in an `async` setup with
        +   * `await`. See the examples for the usage syntax.
        +   *
        +   * Note: There’s no support for colored STL files. STL files with color will
        +   * be rendered without color.
        +   *
        +   * @method loadModel
        +   * @param  {String|Request} path      path of the model to be loaded.
        +   * @param  {Boolean} normalize        if `true`, scale the model to fit the canvas.
        +   * @param  {function(p5.Geometry)} [successCallback] function to call once the model is loaded. Will be passed
        +   *                                                   the <a href="#/p5.Geometry">p5.Geometry</a> object.
        +   * @param  {function(Event)} [failureCallback] function to call if the model fails to load. Will be passed an `Error` event object.
        +   * @param  {String} [fileType]          model’s file extension. Either `'.obj'` or `'.stl'`.
        +   * @return {Promise<p5.Geometry>} the <a href="#/p5.Geometry">p5.Geometry</a> object
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   *
        +   * // Load the file and create a p5.Geometry object.
        +   * async function setup() {
        +   *   shape = await loadModel('assets/teapot.obj');
        +   *
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white teapot drawn against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the shape.
        +   *   model(shape);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   *
        +   * // Load the file and create a p5.Geometry object.
        +   * // Normalize the geometry's size to fit the canvas.
        +   * async function setup() {
        +   *   shape = await loadModel('assets/teapot.obj', true);
        +   *
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white teapot drawn against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the shape.
        +   *   model(shape);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   *
        +   * // Load the file and create a p5.Geometry object.
        +   * function setup() {
        +   *   loadModel('assets/teapot.obj', true, handleModel);
        +   *
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white teapot drawn against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the shape.
        +   *   model(shape);
        +   * }
        +   *
        +   * // Set the shape variable and log the geometry's
        +   * // ID to the console.
        +   * function handleModel(data) {
        +   *   shape = data;
        +   *   console.log(shape.gid);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div class='notest'>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   *
        +   * // Load the file and create a p5.Geometry object.
        +   * function setup() {
        +   *   loadModel('assets/wrong.obj', true, handleModel, handleError);
        +   *
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white teapot drawn against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the shape.
        +   *   model(shape);
        +   * }
        +   *
        +   * // Set the shape variable and print the geometry's
        +   * // ID to the console.
        +   * function handleModel(data) {
        +   *   shape = data;
        +   *   console.log(shape.gid);
        +   * }
        +   *
        +   * // Print an error message if the file doesn't load.
        +   * function handleError(error) {
        +   *   console.error('Oops!', error);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   *
        +   * // Load the file and create a p5.Geometry object.
        +   * function setup() {
        +   *   loadModel('assets/teapot.obj', true, handleModel, handleError, '.obj');
        +   *
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white teapot drawn against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the shape.
        +   *   model(shape);
        +   * }
        +   *
        +   * // Set the shape variable and print the geometry's
        +   * // ID to the console.
        +   * function handleModel(data) {
        +   *   shape = data;
        +   *   console.log(shape.gid);
        +   * }
        +   *
        +   * // Print an error message if the file doesn't load.
        +   * function handleError(error) {
        +   *   console.error('Oops!', error);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   * let options = {
        +   *   normalize: true,
        +   *   successCallback: handleModel,
        +   *   failureCallback: handleError,
        +   *   fileType: '.obj'
        +   * };
        +   *
        +   * // Load the file and create a p5.Geometry object.
        +   * function setup() {
        +   *   loadModel('assets/teapot.obj', options);
        +   *
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white teapot drawn against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the shape.
        +   *   model(shape);
        +   * }
        +   *
        +   * // Set the shape variable and print the geometry's
        +   * // ID to the console.
        +   * function handleModel(data) {
        +   *   shape = data;
        +   *   console.log(shape.gid);
        +   * }
        +   *
        +   * // Print an error message if the file doesn't load.
        +   * function handleError(error) {
        +   *   console.error('Oops!', error);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method loadModel
        +   * @param  {String|Request} path
        +   * @param  {function(p5.Geometry)} [successCallback]
        +   * @param  {function(Event)} [failureCallback]
        +   * @param  {String} [fileType]
        +   * @return {Promise<p5.Geometry>} new <a href="#/p5.Geometry">p5.Geometry</a> object.
        +   */
        +  /**
        +   * @method loadModel
        +   * @param  {String|Request} path
        +   * @param  {Object} [options] loading options.
        +   * @param  {function(p5.Geometry)} [options.successCallback]
        +   * @param  {function(Event)} [options.failureCallback]
        +   * @param  {String} [options.fileType]
        +   * @param  {Boolean} [options.normalize]
        +   * @param  {Boolean} [options.flipU]
        +   * @param  {Boolean} [options.flipV]
        +   * @return {Promise<p5.Geometry>} new <a href="#/p5.Geometry">p5.Geometry</a> object.
        +   */
        +  fn.loadModel = async function (path, fileType, normalize, successCallback, failureCallback) {
        +    // p5._validateParameters('loadModel', arguments);
        +
        +    let flipU = false;
        +    let flipV = false;
        +
        +    if (typeof fileType === 'object') {
        +      // Passing in options object
        +      normalize = fileType.normalize || false;
        +      successCallback = fileType.successCallback;
        +      failureCallback = fileType.failureCallback;
        +      fileType = fileType.fileType || fileType;
        +      flipU = fileType.flipU || false;
        +      flipV = fileType.flipV || false;
        +
        +    } else {
        +      // Passing in individual parameters
        +      if(typeof arguments[arguments.length-1] === 'function'){
        +        if(typeof arguments[arguments.length-2] === 'function'){
        +          successCallback = arguments[arguments.length-2];
        +          failureCallback = arguments[arguments.length-1];
                 }else{
        -          mtlPath = mtlFilename;
        +          successCallback = arguments[arguments.length-1];
                 }
        -        parsedMaterialPromises.push(
        -          fileExists(mtlPath).then(exists => {
        -            if (exists) {
        -              return parseMtl(self, mtlPath);
        -            } else {
        -              console.warn(`MTL file not found or error in parsing; proceeding without materials: ${mtlPath}`);
        -              return {};
        +      }
         
        -            }
        -          }).catch(error => {
        -            console.warn(`Error loading MTL file: ${mtlPath}`, error);
        -            return {};
        -          })
        -        );
        +      if (typeof fileType === 'string') {
        +        if(typeof normalize !== 'boolean') normalize = false;
        +
        +      } else if (typeof fileType === 'boolean') {
        +        normalize = fileType;
        +        fileType = path.slice(-4);
        +
        +      } else {
        +        fileType = path.slice(-4);
        +        normalize = false;
               }
             }
        -    try {
        -      const parsedMaterials = await Promise.all(parsedMaterialPromises);
        -      const materials= Object.assign({}, ...parsedMaterials);
        -      return materials ;
        -    } catch (error) {
        -      return {};
        +
        +    if (fileType.toLowerCase() !== '.obj' && fileType.toLowerCase() !== '.stl') {
        +      fileType = '.obj';
             }
        -  }
         
        +    const model = new Geometry(undefined, undefined, undefined, this._renderer);
        +    model.gid = `${path}|${normalize}`;
        +
        +    async function getMaterials(lines) {
        +      const parsedMaterialPromises = [];
        +
        +      for (let line of lines) {
        +        const mtllibMatch = line.match(/^mtllib (.+)/);
        +
        +        if (mtllibMatch) {
        +          // Object has material
        +          let mtlPath = '';
        +          const mtlFilename = mtllibMatch[1];
        +          const objPathParts = path.split('/');
        +          if (objPathParts.length > 1) {
        +            objPathParts.pop();
        +            const objFolderPath = objPathParts.join('/');
        +            mtlPath = objFolderPath + '/' + mtlFilename;
        +          } else {
        +            mtlPath = mtlFilename;
        +          }
         
        -  async function fileExists(url) {
        -    try {
        -      const response = await fetch(url, { method: 'HEAD' });
        -      return response.ok;
        -    } catch (error) {
        -      return false;
        +          parsedMaterialPromises.push(
        +            fileExists(mtlPath).then(exists => {
        +              if (exists) {
        +                return parseMtl(mtlPath);
        +              } else {
        +                console.warn(`MTL file not found or error in parsing; proceeding without materials: ${mtlPath}`);
        +                return {};
        +
        +              }
        +            }).catch(error => {
        +              console.warn(`Error loading MTL file: ${mtlPath}`, error);
        +              return {};
        +            })
        +          );
        +        }
        +      }
        +
        +      try {
        +        const parsedMaterials = await Promise.all(parsedMaterialPromises);
        +        const materials = Object.assign({}, ...parsedMaterials);
        +        return materials;
        +      } catch (error) {
        +        return {};
        +      }
             }
        -  }
        -  if (fileType.match(/\.stl$/i)) {
        -    this.httpDo(
        -      path,
        -      'GET',
        -      'arrayBuffer',
        -      arrayBuffer => {
        -        parseSTL(model, arrayBuffer);
        +
        +    try{
        +      if (fileType.match(/\.stl$/i)) {
        +        const { data } = await request(path, 'arrayBuffer');
        +        parseSTL(model, data);
         
                 if (normalize) {
                   model.normalize();
        @@ -444,863 +445,828 @@ p5.prototype.loadModel = function(path,options) {
                 if (flipV) {
                   model.flipV();
                 }
        +        model._makeTriangleEdges();
         
        -        self._decrementPreload();
        -        if (typeof successCallback === 'function') {
        -          successCallback(model);
        -        }
        -      },
        -      failureCallback
        -    );
        -  } else if (fileType.match(/\.obj$/i)) {
        -    this.loadStrings(
        -      path,
        -      async lines => {
        -        try{
        -          const parsedMaterials=await getMaterials(lines);
        -
        -          parseObj(model, lines, parsedMaterials);
        -
        -        }catch (error) {
        -          if (failureCallback) {
        -            failureCallback(error);
        -          } else {
        -            p5._friendlyError('Error during parsing: ' + error.message);
        -          }
        -          return;
        +        if (successCallback) {
        +          return successCallback(model);
        +        } else {
        +          return model;
                 }
        -        finally{
        -          if (normalize) {
        -            model.normalize();
        -          }
        -          if (flipU) {
        -            model.flipU();
        -          }
        -          if (flipV) {
        -            model.flipV();
        -          }
        -          model._makeTriangleEdges();
         
        -          self._decrementPreload();
        -          if (typeof successCallback === 'function') {
        -            successCallback(model);
        -          }
        -        }
        -      },
        -      failureCallback
        -    );
        -  } else {
        -    p5._friendlyFileLoadError(3, path);
        -    if (failureCallback) {
        -      failureCallback();
        -    } else {
        -      p5._friendlyError(
        -        'Sorry, the file type is invalid. Only OBJ and STL files are supported.'
        -      );
        -    }
        -  }
        -  return model;
        -};
        +      } else if (fileType.match(/\.obj$/i)) {
        +        const { data } = await request(path, 'text');
        +        const lines = data.split('\n');
         
        -function parseMtl(p5,mtlPath){
        -  return new Promise((resolve, reject)=>{
        -    let currentMaterial = null;
        -    let materials= {};
        -    p5.loadStrings(
        -      mtlPath,
        -      lines => {
        -        for (let line = 0; line < lines.length; ++line){
        -          const tokens = lines[line].trim().split(/\s+/);
        -          if(tokens[0] === 'newmtl') {
        -            const materialName = tokens[1];
        -            currentMaterial = materialName;
        -            materials[currentMaterial] = {};
        -          }else if (tokens[0] === 'Kd'){
        -          //Diffuse color
        -            materials[currentMaterial].diffuseColor = [
        -              parseFloat(tokens[1]),
        -              parseFloat(tokens[2]),
        -              parseFloat(tokens[3])
        -            ];
        -          } else if (tokens[0] === 'Ka'){
        -          //Ambient Color
        -            materials[currentMaterial].ambientColor = [
        -              parseFloat(tokens[1]),
        -              parseFloat(tokens[2]),
        -              parseFloat(tokens[3])
        -            ];
        -          }else if (tokens[0] === 'Ks'){
        -          //Specular color
        -            materials[currentMaterial].specularColor = [
        -              parseFloat(tokens[1]),
        -              parseFloat(tokens[2]),
        -              parseFloat(tokens[3])
        -            ];
        -
        -          }else if (tokens[0] === 'map_Kd') {
        -          //Texture path
        -            materials[currentMaterial].texturePath = tokens[1];
        -          }
        +        const parsedMaterials = await getMaterials(lines);
        +        parseObj(model, lines, parsedMaterials);
        +
        +        if (normalize) {
        +          model.normalize();
                 }
        -        resolve(materials);
        -      },reject
        -    );
        -  });
        -}
        +        if (flipU) {
        +          model.flipU();
        +        }
        +        if (flipV) {
        +          model.flipV();
        +        }
        +        model._makeTriangleEdges();
         
        -/**
        - * Parse OBJ lines into model. For reference, this is what a simple model of a
        - * square might look like:
        - *
        - * v -0.5 -0.5 0.5
        - * v -0.5 -0.5 -0.5
        - * v -0.5 0.5 -0.5
        - * v -0.5 0.5 0.5
        - *
        - * f 4 3 2 1
        - */
        -function parseObj(model, lines, materials= {}) {
        -  // OBJ allows a face to specify an index for a vertex (in the above example),
        -  // but it also allows you to specify a custom combination of vertex, UV
        -  // coordinate, and vertex normal. So, "3/4/3" would mean, "use vertex 3 with
        -  // UV coordinate 4 and vertex normal 3". In WebGL, every vertex with different
        -  // parameters must be a different vertex, so loadedVerts is used to
        -  // temporarily store the parsed vertices, normals, etc., and indexedVerts is
        -  // used to map a specific combination (keyed on, for example, the string
        -  // "3/4/3"), to the actual index of the newly created vertex in the final
        -  // object.
        -  const loadedVerts = {
        -    v: [],
        -    vt: [],
        -    vn: []
        +        if (successCallback) {
        +          return successCallback(model);
        +        } else {
        +          return model;
        +        }
        +      }
        +    } catch(err) {
        +      p5._friendlyFileLoadError(3, path);
        +      if(failureCallback) {
        +        return failureCallback(err);
        +      } else {
        +        throw err;
        +      }
        +    }
           };
         
        -
        -  // Map from source index → Map of material → destination index
        -  const usedVerts = {}; // Track colored vertices
        -  let currentMaterial = null;
        -  const coloredVerts = new Set(); //unique vertices with color
        -  let hasColoredVertices = false;
        -  let hasColorlessVertices = false;
        -  for (let line = 0; line < lines.length; ++line) {
        -    // Each line is a separate object (vertex, face, vertex normal, etc)
        -    // For each line, split it into tokens on whitespace. The first token
        -    // describes the type.
        -    const tokens = lines[line].trim().split(/\b\s+/);
        -
        -    if (tokens.length > 0) {
        -      if (tokens[0] === 'usemtl') {
        -        // Switch to a new material
        -        currentMaterial = tokens[1];
        -      }else if (tokens[0] === 'v' || tokens[0] === 'vn') {
        -        // Check if this line describes a vertex or vertex normal.
        -        // It will have three numeric parameters.
        -        const vertex = new p5.Vector(
        +  async function parseMtl(mtlPath) {
        +    let currentMaterial = null;
        +    let materials = {};
        +
        +    const { data } = await request(mtlPath, "text");
        +    const lines = data.split('\n');
        +
        +    for (let line = 0; line < lines.length; ++line) {
        +      const tokens = lines[line].trim().split(/\s+/);
        +      if (tokens[0] === 'newmtl') {
        +        const materialName = tokens[1];
        +        currentMaterial = materialName;
        +        materials[currentMaterial] = {};
        +      } else if (tokens[0] === 'Kd') {
        +        //Diffuse color
        +        materials[currentMaterial].diffuseColor = [
                   parseFloat(tokens[1]),
                   parseFloat(tokens[2]),
                   parseFloat(tokens[3])
        -        );
        -        loadedVerts[tokens[0]].push(vertex);
        -      } else if (tokens[0] === 'vt') {
        -        // Check if this line describes a texture coordinate.
        -        // It will have two numeric parameters U and V (W is omitted).
        -        // Because of WebGL texture coordinates rendering behaviour, the V
        -        // coordinate is inversed.
        -        const texVertex = [parseFloat(tokens[1]), 1 - parseFloat(tokens[2])];
        -        loadedVerts[tokens[0]].push(texVertex);
        -      } else if (tokens[0] === 'f') {
        -        // Check if this line describes a face.
        -        // OBJ faces can have more than three points. Triangulate points.
        -        for (let tri = 3; tri < tokens.length; ++tri) {
        -          const face = [];
        -          const vertexTokens = [1, tri - 1, tri];
        -
        -          for (let tokenInd = 0; tokenInd < vertexTokens.length; ++tokenInd) {
        -            // Now, convert the given token into an index
        -            const vertString = tokens[vertexTokens[tokenInd]];
        -            let vertParts=vertString.split('/');
        -
        -            // TODO: Faces can technically use negative numbers to refer to the
        -            // previous nth vertex. I haven't seen this used in practice, but
        -            // it might be good to implement this in the future.
        -
        -            for (let i = 0; i < vertParts.length; i++) {
        -              vertParts[i] = parseInt(vertParts[i]) - 1;
        -            }
        +        ];
        +      } else if (tokens[0] === 'Ka') {
        +        //Ambient Color
        +        materials[currentMaterial].ambientColor = [
        +          parseFloat(tokens[1]),
        +          parseFloat(tokens[2]),
        +          parseFloat(tokens[3])
        +        ];
        +      } else if (tokens[0] === 'Ks') {
        +        //Specular color
        +        materials[currentMaterial].specularColor = [
        +          parseFloat(tokens[1]),
        +          parseFloat(tokens[2]),
        +          parseFloat(tokens[3])
        +        ];
         
        -            if (!usedVerts[vertString]) {
        -              usedVerts[vertString] = {};
        -            }
        +      } else if (tokens[0] === 'map_Kd') {
        +        //Texture path
        +        materials[currentMaterial].texturePath = tokens[1];
        +      }
        +    }
         
        -            if (usedVerts[vertString][currentMaterial] === undefined) {
        -              const vertIndex = model.vertices.length;
        -              model.vertices.push(loadedVerts.v[vertParts[0]].copy());
        -              model.uvs.push(loadedVerts.vt[vertParts[1]] ?
        -                loadedVerts.vt[vertParts[1]].slice() : [0, 0]);
        -              model.vertexNormals.push(loadedVerts.vn[vertParts[2]] ?
        -                loadedVerts.vn[vertParts[2]].copy() : new p5.Vector());
        -
        -              usedVerts[vertString][currentMaterial] = vertIndex;
        -              face.push(vertIndex);
        -              if (currentMaterial
        -                && materials[currentMaterial]
        -                && materials[currentMaterial].diffuseColor) {
        -                // Mark this vertex as colored
        -                coloredVerts.add(loadedVerts.v[vertParts[0]]); //since a set would only push unique values
        +    return materials;
        +  }
        +
        +  /**
        +   * Parse OBJ lines into model. For reference, this is what a simple model of a
        +   * square might look like:
        +   *
        +   * v -0.5 -0.5 0.5
        +   * v -0.5 -0.5 -0.5
        +   * v -0.5 0.5 -0.5
        +   * v -0.5 0.5 0.5
        +   *
        +   * f 4 3 2 1
        +   */
        +  function parseObj(model, lines, materials = {}) {
        +    // OBJ allows a face to specify an index for a vertex (in the above example),
        +    // but it also allows you to specify a custom combination of vertex, UV
        +    // coordinate, and vertex normal. So, "3/4/3" would mean, "use vertex 3 with
        +    // UV coordinate 4 and vertex normal 3". In WebGL, every vertex with different
        +    // parameters must be a different vertex, so loadedVerts is used to
        +    // temporarily store the parsed vertices, normals, etc., and indexedVerts is
        +    // used to map a specific combination (keyed on, for example, the string
        +    // "3/4/3"), to the actual index of the newly created vertex in the final
        +    // object.
        +    const loadedVerts = {
        +      v: [],
        +      vt: [],
        +      vn: []
        +    };
        +
        +
        +    // Map from source index → Map of material → destination index
        +    const usedVerts = {}; // Track colored vertices
        +    let currentMaterial = null;
        +    let hasColoredVertices = false;
        +    let hasColorlessVertices = false;
        +    for (let line = 0; line < lines.length; ++line) {
        +      // Each line is a separate object (vertex, face, vertex normal, etc)
        +      // For each line, split it into tokens on whitespace. The first token
        +      // describes the type.
        +      const tokens = lines[line].trim().split(/\b\s+/);
        +
        +      if (tokens.length > 0) {
        +        if (tokens[0] === 'usemtl') {
        +          // Switch to a new material
        +          currentMaterial = tokens[1];
        +        } else if (tokens[0] === 'v' || tokens[0] === 'vn') {
        +          // Check if this line describes a vertex or vertex normal.
        +          // It will have three numeric parameters.
        +          const vertex = new Vector(
        +            parseFloat(tokens[1]),
        +            parseFloat(tokens[2]),
        +            parseFloat(tokens[3])
        +          );
        +          loadedVerts[tokens[0]].push(vertex);
        +        } else if (tokens[0] === 'vt') {
        +          // Check if this line describes a texture coordinate.
        +          // It will have two numeric parameters U and V (W is omitted).
        +          // Because of WebGL texture coordinates rendering behaviour, the V
        +          // coordinate is inversed.
        +          const texVertex = [parseFloat(tokens[1]), 1 - parseFloat(tokens[2])];
        +          loadedVerts[tokens[0]].push(texVertex);
        +        } else if (tokens[0] === 'f') {
        +          // Check if this line describes a face.
        +          // OBJ faces can have more than three points. Triangulate points.
        +          for (let tri = 3; tri < tokens.length; ++tri) {
        +            const face = [];
        +            const vertexTokens = [1, tri - 1, tri];
        +
        +            for (let tokenInd = 0; tokenInd < vertexTokens.length; ++tokenInd) {
        +              // Now, convert the given token into an index
        +              const vertString = tokens[vertexTokens[tokenInd]];
        +              let vertParts = vertString.split('/');
        +
        +              // TODO: Faces can technically use negative numbers to refer to the
        +              // previous nth vertex. I haven't seen this used in practice, but
        +              // it might be good to implement this in the future.
        +
        +              for (let i = 0; i < vertParts.length; i++) {
        +                vertParts[i] = parseInt(vertParts[i]) - 1;
        +              }
        +
        +              if (!usedVerts[vertString]) {
        +                usedVerts[vertString] = {};
                       }
        -            } else {
        -              face.push(usedVerts[vertString][currentMaterial]);
        -            }
        -          }
         
        -          if (
        -            face[0] !== face[1] &&
        -            face[0] !== face[2] &&
        -            face[1] !== face[2]
        -          ) {
        -            model.faces.push(face);
        -            //same material for all vertices in a particular face
        -            if (currentMaterial
        -              && materials[currentMaterial]
        -              && materials[currentMaterial].diffuseColor) {
        -              hasColoredVertices=true;
        -              //flag to track color or no color model
        -              hasColoredVertices = true;
        -              const materialDiffuseColor =
        -              materials[currentMaterial].diffuseColor;
        -              for (let i = 0; i < face.length; i++) {
        -                model.vertexColors.push(materialDiffuseColor[0]);
        -                model.vertexColors.push(materialDiffuseColor[1]);
        -                model.vertexColors.push(materialDiffuseColor[2]);
        +              if (usedVerts[vertString][currentMaterial] === undefined) {
        +                const vertIndex = model.vertices.length;
        +                model.vertices.push(loadedVerts.v[vertParts[0]].copy());
        +                model.uvs.push(loadedVerts.vt[vertParts[1]] ?
        +                  loadedVerts.vt[vertParts[1]].slice() : [0, 0]);
        +                model.vertexNormals.push(loadedVerts.vn[vertParts[2]] ?
        +                  loadedVerts.vn[vertParts[2]].copy() : new Vector());
        +
        +                usedVerts[vertString][currentMaterial] = vertIndex;
        +                face.push(vertIndex);
        +                if (currentMaterial
        +                  && materials[currentMaterial]
        +                  && materials[currentMaterial].diffuseColor) {
        +                  hasColoredVertices = true;
        +                  const materialDiffuseColor =
        +                    materials[currentMaterial].diffuseColor;
        +                  model.vertexColors.push(materialDiffuseColor[0]);
        +                  model.vertexColors.push(materialDiffuseColor[1]);
        +                  model.vertexColors.push(materialDiffuseColor[2]);
        +                  model.vertexColors.push(1);
        +                } else {
        +                  hasColorlessVertices = true;
        +                }
        +              } else {
        +                face.push(usedVerts[vertString][currentMaterial]);
                       }
        -            }else{
        -              hasColorlessVertices=true;
        +            }
        +
        +            if (
        +              face[0] !== face[1] &&
        +              face[0] !== face[2] &&
        +              face[1] !== face[2]
        +            ) {
        +              model.faces.push(face);
                     }
                   }
                 }
               }
             }
        +    // If the model doesn't have normals, compute the normals
        +    if (model.vertexNormals.length === 0) {
        +      model.computeNormals();
        +    }
        +    if (hasColoredVertices === hasColorlessVertices) {
        +      // If both are true or both are false, throw an error because the model is inconsistent
        +      throw new Error('Model coloring is inconsistent. Either all vertices should have colors or none should.');
        +    }
        +
        +    return model;
           }
        -  // If the model doesn't have normals, compute the normals
        -  if (model.vertexNormals.length === 0) {
        -    model.computeNormals();
        -  }
        -  if (hasColoredVertices === hasColorlessVertices) {
        -    // If both are true or both are false, throw an error because the model is inconsistent
        -    throw new Error('Model coloring is inconsistent. Either all vertices should have colors or none should.');
        -  }
        -  return model;
        -}
         
        -/**
        - * STL files can be of two types, ASCII and Binary,
        - *
        - * We need to convert the arrayBuffer to an array of strings,
        - * to parse it as an ASCII file.
        - */
        -function parseSTL(model, buffer) {
        -  if (isBinary(buffer)) {
        -    parseBinarySTL(model, buffer);
        -  } else {
        -    const reader = new DataView(buffer);
        +  /**
        +   * STL files can be of two types, ASCII and Binary,
        +   *
        +   * We need to convert the arrayBuffer to an array of strings,
        +   * to parse it as an ASCII file.
        +   */
        +  function parseSTL(model, buffer) {
        +    if (isBinary(buffer)) {
        +      parseBinarySTL(model, buffer);
        +    } else {
        +      const reader = new DataView(buffer);
         
        -    if (!('TextDecoder' in window)) {
        -      console.warn(
        -        'Sorry, ASCII STL loading only works in browsers that support TextDecoder (https://caniuse.com/#feat=textencoder)'
        -      );
        -      return model;
        -    }
        +      if (!('TextDecoder' in window)) {
        +        console.warn(
        +          'Sorry, ASCII STL loading only works in browsers that support TextDecoder (https://caniuse.com/#feat=textencoder)'
        +        );
        +        return model;
        +      }
         
        -    const decoder = new TextDecoder('utf-8');
        -    const lines = decoder.decode(reader);
        -    const lineArray = lines.split('\n');
        -    parseASCIISTL(model, lineArray);
        +      const decoder = new TextDecoder('utf-8');
        +      const lines = decoder.decode(reader);
        +      const lineArray = lines.split('\n');
        +      parseASCIISTL(model, lineArray);
        +    }
        +    return model;
           }
        -  return model;
        -}
         
        -/**
        - * This function checks if the file is in ASCII format or in Binary format
        - *
        - * It is done by searching keyword `solid` at the start of the file.
        - *
        - * An ASCII STL data must begin with `solid` as the first six bytes.
        - * However, ASCII STLs lacking the SPACE after the `d` are known to be
        - * plentiful. So, check the first 5 bytes for `solid`.
        - *
        - * Several encodings, such as UTF-8, precede the text with up to 5 bytes:
        - * https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding
        - * Search for `solid` to start anywhere after those prefixes.
        - */
        -function isBinary(data) {
        -  const reader = new DataView(data);
        -
        -  // US-ASCII ordinal values for `s`, `o`, `l`, `i`, `d`
        -  const solid = [115, 111, 108, 105, 100];
        -  for (let off = 0; off < 5; off++) {
        -    // If "solid" text is matched to the current offset, declare it to be an ASCII STL.
        -    if (matchDataViewAt(solid, reader, off)) return false;
        +  /**
        +   * This function checks if the file is in ASCII format or in Binary format
        +   *
        +   * It is done by searching keyword `solid` at the start of the file.
        +   *
        +   * An ASCII STL data must begin with `solid` as the first six bytes.
        +   * However, ASCII STLs lacking the SPACE after the `d` are known to be
        +   * plentiful. So, check the first 5 bytes for `solid`.
        +   *
        +   * Several encodings, such as UTF-8, precede the text with up to 5 bytes:
        +   * https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding
        +   * Search for `solid` to start anywhere after those prefixes.
        +   */
        +  function isBinary(data) {
        +    const reader = new DataView(data);
        +
        +    // US-ASCII ordinal values for `s`, `o`, `l`, `i`, `d`
        +    const solid = [115, 111, 108, 105, 100];
        +    for (let off = 0; off < 5; off++) {
        +      // If "solid" text is matched to the current offset, declare it to be an ASCII STL.
        +      if (matchDataViewAt(solid, reader, off)) return false;
        +    }
        +
        +    // Couldn't find "solid" text at the beginning; it is binary STL.
        +    return true;
           }
         
        -  // Couldn't find "solid" text at the beginning; it is binary STL.
        -  return true;
        -}
        +  /**
        +   * This function matches the `query` at the provided `offset`
        +   */
        +  function matchDataViewAt(query, reader, offset) {
        +    // Check if each byte in query matches the corresponding byte from the current offset
        +    for (let i = 0, il = query.length; i < il; i++) {
        +      if (query[i] !== reader.getUint8(offset + i, false)) return false;
        +    }
         
        -/**
        - * This function matches the `query` at the provided `offset`
        - */
        -function matchDataViewAt(query, reader, offset) {
        -  // Check if each byte in query matches the corresponding byte from the current offset
        -  for (let i = 0, il = query.length; i < il; i++) {
        -    if (query[i] !== reader.getUint8(offset + i, false)) return false;
        +    return true;
           }
         
        -  return true;
        -}
        +  /**
        +   * This function parses the Binary STL files.
        +   * https://en.wikipedia.org/wiki/STL_%28file_format%29#Binary_STL
        +   *
        +   * Currently there is no support for the colors provided in STL files.
        +   */
        +  function parseBinarySTL(model, buffer) {
        +    const reader = new DataView(buffer);
         
        -/**
        - * This function parses the Binary STL files.
        - * https://en.wikipedia.org/wiki/STL_%28file_format%29#Binary_STL
        - *
        - * Currently there is no support for the colors provided in STL files.
        - */
        -function parseBinarySTL(model, buffer) {
        -  const reader = new DataView(buffer);
        -
        -  // Number of faces is present following the header
        -  const faces = reader.getUint32(80, true);
        -  let r,
        -    g,
        -    b,
        -    hasColors = false,
        -    colors;
        -  let defaultR, defaultG, defaultB;
        -
        -  // Binary files contain 80-byte header, which is generally ignored.
        -  for (let index = 0; index < 80 - 10; index++) {
        -    // Check for `COLOR=`
        -    if (
        -      reader.getUint32(index, false) === 0x434f4c4f /*COLO*/ &&
        -      reader.getUint8(index + 4) === 0x52 /*'R'*/ &&
        -      reader.getUint8(index + 5) === 0x3d /*'='*/
        -    ) {
        -      hasColors = true;
        -      colors = [];
        -
        -      defaultR = reader.getUint8(index + 6) / 255;
        -      defaultG = reader.getUint8(index + 7) / 255;
        -      defaultB = reader.getUint8(index + 8) / 255;
        -      // To be used when color support is added
        -      // alpha = reader.getUint8(index + 9) / 255;
        +    // Number of faces is present following the header
        +    const faces = reader.getUint32(80, true);
        +    let r,
        +      g,
        +      b,
        +      hasColors = false,
        +      colors;
        +    let defaultR, defaultG, defaultB;
        +
        +    // Binary files contain 80-byte header, which is generally ignored.
        +    for (let index = 0; index < 80 - 10; index++) {
        +      // Check for `COLOR=`
        +      if (
        +        reader.getUint32(index, false) === 0x434f4c4f /*COLO*/ &&
        +        reader.getUint8(index + 4) === 0x52 /*'R'*/ &&
        +        reader.getUint8(index + 5) === 0x3d /*'='*/
        +      ) {
        +        hasColors = true;
        +        colors = [];
        +
        +        defaultR = reader.getUint8(index + 6) / 255;
        +        defaultG = reader.getUint8(index + 7) / 255;
        +        defaultB = reader.getUint8(index + 8) / 255;
        +        // To be used when color support is added
        +        // alpha = reader.getUint8(index + 9) / 255;
        +      }
             }
        -  }
        -  const dataOffset = 84;
        -  const faceLength = 12 * 4 + 2;
        +    const dataOffset = 84;
        +    const faceLength = 12 * 4 + 2;
         
        -  // Iterate the faces
        -  for (let face = 0; face < faces; face++) {
        -    const start = dataOffset + face * faceLength;
        -    const normalX = reader.getFloat32(start, true);
        -    const normalY = reader.getFloat32(start + 4, true);
        -    const normalZ = reader.getFloat32(start + 8, true);
        +    // Iterate the faces
        +    for (let face = 0; face < faces; face++) {
        +      const start = dataOffset + face * faceLength;
        +      const normalX = reader.getFloat32(start, true);
        +      const normalY = reader.getFloat32(start + 4, true);
        +      const normalZ = reader.getFloat32(start + 8, true);
         
        -    if (hasColors) {
        -      const packedColor = reader.getUint16(start + 48, true);
        +      if (hasColors) {
        +        const packedColor = reader.getUint16(start + 48, true);
         
        -      if ((packedColor & 0x8000) === 0) {
        -        // facet has its own unique color
        -        r = (packedColor & 0x1f) / 31;
        -        g = ((packedColor >> 5) & 0x1f) / 31;
        -        b = ((packedColor >> 10) & 0x1f) / 31;
        -      } else {
        -        r = defaultR;
        -        g = defaultG;
        -        b = defaultB;
        +        if ((packedColor & 0x8000) === 0) {
        +          // facet has its own unique color
        +          r = (packedColor & 0x1f) / 31;
        +          g = ((packedColor >> 5) & 0x1f) / 31;
        +          b = ((packedColor >> 10) & 0x1f) / 31;
        +        } else {
        +          r = defaultR;
        +          g = defaultG;
        +          b = defaultB;
        +        }
               }
        -    }
        -    const newNormal = new p5.Vector(normalX, normalY, normalZ);
        +      const newNormal = new Vector(normalX, normalY, normalZ);
         
        -    for (let i = 1; i <= 3; i++) {
        -      const vertexstart = start + i * 12;
        +      for (let i = 1; i <= 3; i++) {
        +        const vertexstart = start + i * 12;
         
        -      const newVertex = new p5.Vector(
        -        reader.getFloat32(vertexstart, true),
        -        reader.getFloat32(vertexstart + 4, true),
        -        reader.getFloat32(vertexstart + 8, true)
        -      );
        +        const newVertex = new Vector(
        +          reader.getFloat32(vertexstart, true),
        +          reader.getFloat32(vertexstart + 4, true),
        +          reader.getFloat32(vertexstart + 8, true)
        +        );
         
        -      model.vertices.push(newVertex);
        -      model.vertexNormals.push(newNormal);
        +        model.vertices.push(newVertex);
        +        model.vertexNormals.push(newNormal);
         
        -      if (hasColors) {
        -        colors.push(r, g, b);
        +        if (hasColors) {
        +          colors.push(r, g, b);
        +        }
               }
        -    }
         
        -    model.faces.push([3 * face, 3 * face + 1, 3 * face + 2]);
        -    model.uvs.push([0, 0], [0, 0], [0, 0]);
        -  }
        -  if (hasColors) {
        -    // add support for colors here.
        +      model.faces.push([3 * face, 3 * face + 1, 3 * face + 2]);
        +      model.uvs.push([0, 0], [0, 0], [0, 0]);
        +    }
        +    if (hasColors) {
        +      // add support for colors here.
        +    }
        +    return model;
           }
        -  return model;
        -}
         
        -/**
        - * ASCII STL file starts with `solid 'nameOfFile'`
        - * Then contain the normal of the face, starting with `facet normal`
        - * Next contain a keyword indicating the start of face vertex, `outer loop`
        - * Next comes the three vertex, starting with `vertex x y z`
        - * Vertices ends with `endloop`
        - * Face ends with `endfacet`
        - * Next face starts with `facet normal`
        - * The end of the file is indicated by `endsolid`
        - */
        -function parseASCIISTL(model, lines) {
        -  let state = '';
        -  let curVertexIndex = [];
        -  let newNormal, newVertex;
        -
        -  for (let iterator = 0; iterator < lines.length; ++iterator) {
        -    const line = lines[iterator].trim();
        -    const parts = line.split(' ');
        -
        -    for (let partsiterator = 0; partsiterator < parts.length; ++partsiterator) {
        -      if (parts[partsiterator] === '') {
        -        // Ignoring multiple whitespaces
        -        parts.splice(partsiterator, 1);
        +  /**
        +   * ASCII STL file starts with `solid 'nameOfFile'`
        +   * Then contain the normal of the face, starting with `facet normal`
        +   * Next contain a keyword indicating the start of face vertex, `outer loop`
        +   * Next comes the three vertex, starting with `vertex x y z`
        +   * Vertices ends with `endloop`
        +   * Face ends with `endfacet`
        +   * Next face starts with `facet normal`
        +   * The end of the file is indicated by `endsolid`
        +   */
        +  function parseASCIISTL(model, lines) {
        +    let state = '';
        +    let curVertexIndex = [];
        +    let newNormal, newVertex;
        +
        +    for (let iterator = 0; iterator < lines.length; ++iterator) {
        +      const line = lines[iterator].trim();
        +      const parts = line.split(' ');
        +
        +      for (let partsiterator = 0; partsiterator < parts.length; ++partsiterator) {
        +        if (parts[partsiterator] === '') {
        +          // Ignoring multiple whitespaces
        +          parts.splice(partsiterator, 1);
        +        }
               }
        -    }
         
        -    if (parts.length === 0) {
        -      // Remove newline
        -      continue;
        -    }
        +      if (parts.length === 0) {
        +        // Remove newline
        +        continue;
        +      }
         
        -    switch (state) {
        -      case '': // First run
        -        if (parts[0] !== 'solid') {
        -          // Invalid state
        -          console.error(line);
        -          console.error(`Invalid state "${parts[0]}", should be "solid"`);
        -          return;
        -        } else {
        -          state = 'solid';
        -        }
        -        break;
        -
        -      case 'solid': // First face
        -        if (parts[0] !== 'facet' || parts[1] !== 'normal') {
        -          // Invalid state
        -          console.error(line);
        -          console.error(
        -            `Invalid state "${parts[0]}", should be "facet normal"`
        -          );
        -          return;
        -        } else {
        -          // Push normal for first face
        -          newNormal = new p5.Vector(
        -            parseFloat(parts[2]),
        -            parseFloat(parts[3]),
        -            parseFloat(parts[4])
        -          );
        -          model.vertexNormals.push(newNormal, newNormal, newNormal);
        -          state = 'facet normal';
        -        }
        -        break;
        -
        -      case 'facet normal': // After normal is defined
        -        if (parts[0] !== 'outer' || parts[1] !== 'loop') {
        -          // Invalid State
        -          console.error(line);
        -          console.error(`Invalid state "${parts[0]}", should be "outer loop"`);
        -          return;
        -        } else {
        -          // Next should be vertices
        -          state = 'vertex';
        -        }
        -        break;
        -
        -      case 'vertex':
        -        if (parts[0] === 'vertex') {
        -          //Vertex of triangle
        -          newVertex = new p5.Vector(
        -            parseFloat(parts[1]),
        -            parseFloat(parts[2]),
        -            parseFloat(parts[3])
        -          );
        -          model.vertices.push(newVertex);
        -          model.uvs.push([0, 0]);
        -          curVertexIndex.push(model.vertices.indexOf(newVertex));
        -        } else if (parts[0] === 'endloop') {
        -          // End of vertices
        -          model.faces.push(curVertexIndex);
        -          curVertexIndex = [];
        -          state = 'endloop';
        -        } else {
        -          // Invalid State
        -          console.error(line);
        -          console.error(
        -            `Invalid state "${parts[0]}", should be "vertex" or "endloop"`
        -          );
        -          return;
        -        }
        -        break;
        -
        -      case 'endloop':
        -        if (parts[0] !== 'endfacet') {
        -          // End of face
        -          console.error(line);
        -          console.error(`Invalid state "${parts[0]}", should be "endfacet"`);
        -          return;
        -        } else {
        -          state = 'endfacet';
        -        }
        -        break;
        -
        -      case 'endfacet':
        -        if (parts[0] === 'endsolid') {
        -          // End of solid
        -        } else if (parts[0] === 'facet' && parts[1] === 'normal') {
        -          // Next face
        -          newNormal = new p5.Vector(
        -            parseFloat(parts[2]),
        -            parseFloat(parts[3]),
        -            parseFloat(parts[4])
        -          );
        -          model.vertexNormals.push(newNormal, newNormal, newNormal);
        -          state = 'facet normal';
        -        } else {
        -          // Invalid State
        -          console.error(line);
        -          console.error(
        -            `Invalid state "${
        -              parts[0]
        -            }", should be "endsolid" or "facet normal"`
        -          );
        -          return;
        -        }
        -        break;
        +      switch (state) {
        +        case '': // First run
        +          if (parts[0] !== 'solid') {
        +            // Invalid state
        +            console.error(line);
        +            console.error(`Invalid state "${parts[0]}", should be "solid"`);
        +            return;
        +          } else {
        +            state = 'solid';
        +          }
        +          break;
        +
        +        case 'solid': // First face
        +          if (parts[0] !== 'facet' || parts[1] !== 'normal') {
        +            // Invalid state
        +            console.error(line);
        +            console.error(
        +              `Invalid state "${parts[0]}", should be "facet normal"`
        +            );
        +            return;
        +          } else {
        +            // Push normal for first face
        +            newNormal = new Vector(
        +              parseFloat(parts[2]),
        +              parseFloat(parts[3]),
        +              parseFloat(parts[4])
        +            );
        +            model.vertexNormals.push(newNormal, newNormal, newNormal);
        +            state = 'facet normal';
        +          }
        +          break;
        +
        +        case 'facet normal': // After normal is defined
        +          if (parts[0] !== 'outer' || parts[1] !== 'loop') {
        +            // Invalid State
        +            console.error(line);
        +            console.error(`Invalid state "${parts[0]}", should be "outer loop"`);
        +            return;
        +          } else {
        +            // Next should be vertices
        +            state = 'vertex';
        +          }
        +          break;
        +
        +        case 'vertex':
        +          if (parts[0] === 'vertex') {
        +            //Vertex of triangle
        +            newVertex = new Vector(
        +              parseFloat(parts[1]),
        +              parseFloat(parts[2]),
        +              parseFloat(parts[3])
        +            );
        +            model.vertices.push(newVertex);
        +            model.uvs.push([0, 0]);
        +            curVertexIndex.push(model.vertices.indexOf(newVertex));
        +          } else if (parts[0] === 'endloop') {
        +            // End of vertices
        +            model.faces.push(curVertexIndex);
        +            curVertexIndex = [];
        +            state = 'endloop';
        +          } else {
        +            // Invalid State
        +            console.error(line);
        +            console.error(
        +              `Invalid state "${parts[0]}", should be "vertex" or "endloop"`
        +            );
        +            return;
        +          }
        +          break;
        +
        +        case 'endloop':
        +          if (parts[0] !== 'endfacet') {
        +            // End of face
        +            console.error(line);
        +            console.error(`Invalid state "${parts[0]}", should be "endfacet"`);
        +            return;
        +          } else {
        +            state = 'endfacet';
        +          }
        +          break;
        +
        +        case 'endfacet':
        +          if (parts[0] === 'endsolid') {
        +            // End of solid
        +          } else if (parts[0] === 'facet' && parts[1] === 'normal') {
        +            // Next face
        +            newNormal = new Vector(
        +              parseFloat(parts[2]),
        +              parseFloat(parts[3]),
        +              parseFloat(parts[4])
        +            );
        +            model.vertexNormals.push(newNormal, newNormal, newNormal);
        +            state = 'facet normal';
        +          } else {
        +            // Invalid State
        +            console.error(line);
        +            console.error(
        +              `Invalid state "${parts[0]
        +              }", should be "endsolid" or "facet normal"`
        +            );
        +            return;
        +          }
        +          break;
         
        -      default:
        -        console.error(`Invalid state "${state}"`);
        -        break;
        +        default:
        +          console.error(`Invalid state "${state}"`);
        +          break;
        +      }
             }
        +    return model;
           }
        -  return model;
        -}
         
        -/**
        - * Draws a <a href="#/p5.Geometry">p5.Geometry</a> object to the canvas.
        - *
        - * The parameter, `model`, is the
        - * <a href="#/p5.Geometry">p5.Geometry</a> object to draw.
        - * <a href="#/p5.Geometry">p5.Geometry</a> objects can be built with
        - * <a href="#/p5/buildGeometry">buildGeometry()</a>, or
        - * <a href="#/p5/beginGeometry">beginGeometry()</a> and
        - * <a href="#/p5/endGeometry">endGeometry()</a>. They can also be loaded from
        - * a file with <a href="#/p5/loadGeometry">loadGeometry()</a>.
        - *
        - * Note: `model()` can only be used in WebGL mode.
        - *
        - * @method model
        - * @param  {p5.Geometry} model 3D shape to be drawn.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the p5.Geometry object.
        - *   shape = buildGeometry(createShape);
        - *
        - *   describe('A white cone drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(shape);
        - * }
        - *
        - * // Create p5.Geometry object from a single cone.
        - * function createShape() {
        - *   cone();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the p5.Geometry object.
        - *   shape = buildGeometry(createArrow);
        - *
        - *   describe('Two white arrows drawn on a gray background. The arrow on the right rotates slowly.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the arrows.
        - *   noStroke();
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(shape);
        - *
        - *   // Translate and rotate the coordinate system.
        - *   translate(30, 0, 0);
        - *   rotateZ(frameCount * 0.01);
        - *
        - *   // Draw the p5.Geometry object again.
        - *   model(shape);
        - * }
        - *
        - * function createArrow() {
        - *   // Add shapes to the p5.Geometry object.
        - *   push();
        - *   rotateX(PI);
        - *   cone(10);
        - *   translate(0, -10, 0);
        - *   cylinder(3, 20);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let shape;
        - *
        - * // Load the file and create a p5.Geometry object.
        - * function preload() {
        - *   shape = loadModel('assets/octahedron.obj');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white octahedron drawn against a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the shape.
        - *   model(shape);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.model = function(model) {
        -  this._assert3d('model');
        -  p5._validateParameters('model', arguments);
        -  if (model.vertices.length > 0) {
        -    if (!this._renderer.geometryInHash(model.gid)) {
        -      model._edgesToVertices();
        -      this._renderer.createBuffers(model.gid, model);
        -    }
        +  /**
        +   * Draws a <a href="#/p5.Geometry">p5.Geometry</a> object to the canvas.
        +   *
        +   * The parameter, `model`, is the
        +   * <a href="#/p5.Geometry">p5.Geometry</a> object to draw.
        +   * <a href="#/p5.Geometry">p5.Geometry</a> objects can be built with
        +   * <a href="#/p5/buildGeometry">buildGeometry()</a>, or
        +   * <a href="#/p5/beginGeometry">beginGeometry()</a> and
        +   * <a href="#/p5/endGeometry">endGeometry()</a>. They can also be loaded from
        +   * a file with <a href="#/p5/loadGeometry">loadGeometry()</a>.
        +   *
        +   * Note: `model()` can only be used in WebGL mode.
        +   *
        +   * @method model
        +   * @param  {p5.Geometry} model 3D shape to be drawn.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the p5.Geometry object.
        +   *   shape = buildGeometry(createShape);
        +   *
        +   *   describe('A white cone drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the p5.Geometry object.
        +   *   model(shape);
        +   * }
        +   *
        +   * // Create p5.Geometry object from a single cone.
        +   * function createShape() {
        +   *   cone();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the p5.Geometry object.
        +   *   shape = buildGeometry(createArrow);
        +   *
        +   *   describe('Two white arrows drawn on a gray background. The arrow on the right rotates slowly.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the arrows.
        +   *   noStroke();
        +   *
        +   *   // Draw the p5.Geometry object.
        +   *   model(shape);
        +   *
        +   *   // Translate and rotate the coordinate system.
        +   *   translate(30, 0, 0);
        +   *   rotateZ(frameCount * 0.01);
        +   *
        +   *   // Draw the p5.Geometry object again.
        +   *   model(shape);
        +   * }
        +   *
        +   * function createArrow() {
        +   *   // Add shapes to the p5.Geometry object.
        +   *   push();
        +   *   rotateX(PI);
        +   *   cone(10);
        +   *   translate(0, -10, 0);
        +   *   cylinder(3, 20);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let shape;
        +   *
        +   * async function setup() {
        +   *   shape = await loadModel('assets/octahedron.obj');
        +   *
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white octahedron drawn against a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the shape.
        +   *   model(shape);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.model = function (model, count = 1) {
        +    this._assert3d('model');
        +    // p5._validateParameters('model', arguments);
        +    this._renderer.model(model, count);
        +  };
         
        -    this._renderer.drawBuffers(model.gid);
        -  }
        -};
        +  /**
        +   * Load a 3d model from an OBJ or STL string.
        +   *
        +   * OBJ and STL files lack a built-in sense of scale, causing models exported from different programs to vary in size.
        +   * If your model doesn't display correctly, consider using `loadModel()` with `normalize` set to `true` to standardize its size.
        +   * Further adjustments can be made using the `scale()` function.
        +   *
        +   * Also, the support for colored STL files is not present. STL files with color will be
        +   * rendered without color properties.
        +   *
        +   * * Options can include:
        +   * - `modelString`: Specifies the plain text string of either an stl or obj file to be loaded.
        +   * - `fileType`: Defines the file extension of the model.
        +   * - `normalize`: Enables standardized size scaling during loading if set to true.
        +   * - `successCallback`: Callback for post-loading actions with the 3D model object.
        +   * - `failureCallback`: Handles errors if model loading fails, receiving an event error.
        +   * - `flipU`: Flips the U texture coordinates of the model.
        +   * - `flipV`: Flips the V texture coordinates of the model.
        +   *
        +   *
        +   * @method createModel
        +   * @param  {String} modelString         String of the object to be loaded
        +   * @param  {String} [fileType]          The file extension of the model
        +   *                                      (<code>.stl</code>, <code>.obj</code>).
        +   * @param  {Boolean} normalize        If true, scale the model to a
        +   *                                      standardized size when loading
        +   * @param  {function(p5.Geometry)} [successCallback] Function to be called
        +   *                                     once the model is loaded. Will be passed
        +   *                                     the 3D model object.
        +   * @param  {function(Event)} [failureCallback] called with event error if
        +   *                                         the model fails to load.
        +   * @return {p5.Geometry} the <a href="#/p5.Geometry">p5.Geometry</a> object
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * const octahedron_model = `
        +   * v 0.000000E+00 0.000000E+00 40.0000
        +   * v 22.5000 22.5000 0.000000E+00
        +   * v 22.5000 -22.5000 0.000000E+00
        +   * v -22.5000 -22.5000 0.000000E+00
        +   * v -22.5000 22.5000 0.000000E+00
        +   * v 0.000000E+00 0.000000E+00 -40.0000
        +   * f     1 2 3
        +   * f     1 3 4
        +   * f     1 4 5
        +   * f     1 5 2
        +   * f     6 5 4
        +   * f     6 4 3
        +   * f     6 3 2
        +   * f     6 2 5
        +   * `;
        +   * //draw a spinning octahedron
        +   * let octahedron;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *   octahedron = createModel(octahedron_model, '.obj');
        +   *   describe('Vertically rotating 3D octahedron.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *   model(octahedron);
        +   *}
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method createModel
        +   * @param  {String} modelString
        +   * @param  {String} [fileType]
        +   * @param  {function(p5.Geometry)} [successCallback]
        +   * @param  {function(Event)} [failureCallback]
        +   * @return {p5.Geometry} the <a href="#/p5.Geometry">p5.Geometry</a> object
        +   */
        +  /**
        +   * @method createModel
        +   * @param  {String} modelString
        +   * @param  {String} [fileType]
        +   * @param  {Object} [options]
        +   * @param  {function(p5.Geometry)} [options.successCallback]
        +   * @param  {function(Event)} [options.failureCallback]
        +   * @param  {boolean} [options.normalize]
        +   * @param  {boolean} [options.flipU]
        +   * @param  {boolean} [options.flipV]
        +   * @return {p5.Geometry} the <a href="#/p5.Geometry">p5.Geometry</a> object
        +   */
        +  let modelCounter = 0;
        +  fn.createModel = function(modelString, fileType=' ', options) {
        +    // p5._validateParameters('createModel', arguments);
        +    let normalize= false;
        +    let successCallback;
        +    let failureCallback;
        +    let flipU = false;
        +    let flipV = false;
        +    if (options && typeof options === 'object') {
        +      normalize = options.normalize || false;
        +      successCallback = options.successCallback;
        +      failureCallback = options.failureCallback;
        +      flipU = options.flipU || false;
        +      flipV = options.flipV || false;
        +    } else if (typeof options === 'boolean') {
        +      normalize = options;
        +      successCallback = arguments[3];
        +      failureCallback = arguments[4];
        +    } else {
        +      successCallback = typeof arguments[2] === 'function' ? arguments[2] : undefined;
        +      failureCallback = arguments[3];
        +    }
        +    const model = new p5.Geometry();
        +    model.gid = `${fileType}|${normalize}|${modelCounter++}`;
         
        -/**
        - * Load a 3d model from an OBJ or STL string.
        - *
        - * OBJ and STL files lack a built-in sense of scale, causing models exported from different programs to vary in size.
        - * If your model doesn't display correctly, consider using `loadModel()` with `normalize` set to `true` to standardize its size.
        - * Further adjustments can be made using the `scale()` function.
        - *
        - * Also, the support for colored STL files is not present. STL files with color will be
        - * rendered without color properties.
        - *
        - * * Options can include:
        - * - `modelString`: Specifies the plain text string of either an stl or obj file to be loaded.
        - * - `fileType`: Defines the file extension of the model.
        - * - `normalize`: Enables standardized size scaling during loading if set to true.
        - * - `successCallback`: Callback for post-loading actions with the 3D model object.
        - * - `failureCallback`: Handles errors if model loading fails, receiving an event error.
        - * - `flipU`: Flips the U texture coordinates of the model.
        - * - `flipV`: Flips the V texture coordinates of the model.
        - *
        - *
        - * @method createModel
        - * @param  {String} modelString         String of the object to be loaded
        - * @param  {String} [fileType]          The file extension of the model
        - *                                      (<code>.stl</code>, <code>.obj</code>).
        - * @param  {Boolean} normalize        If true, scale the model to a
        - *                                      standardized size when loading
        - * @param  {function(p5.Geometry)} [successCallback] Function to be called
        - *                                     once the model is loaded. Will be passed
        - *                                     the 3D model object.
        - * @param  {function(Event)} [failureCallback] called with event error if
        - *                                         the model fails to load.
        - * @return {p5.Geometry} the <a href="#/p5.Geometry">p5.Geometry</a> object
        - *
        - * @example
        - * <div>
        - * <code>
        - * const octahedron_model = `
        - * v 0.000000E+00 0.000000E+00 40.0000
        - * v 22.5000 22.5000 0.000000E+00
        - * v 22.5000 -22.5000 0.000000E+00
        - * v -22.5000 -22.5000 0.000000E+00
        - * v -22.5000 22.5000 0.000000E+00
        - * v 0.000000E+00 0.000000E+00 -40.0000
        - * f     1 2 3
        - * f     1 3 4
        - * f     1 4 5
        - * f     1 5 2
        - * f     6 5 4
        - * f     6 4 3
        - * f     6 3 2
        - * f     6 2 5
        - * `;
        - * //draw a spinning octahedron
        - * let octahedron;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *   octahedron = createModel(octahedron_model, '.obj');
        - *   describe('Vertically rotating 3D octahedron.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   model(octahedron);
        - *}
        - * </code>
        - * </div>
        - */
        -/**
        - * @method createModel
        - * @param  {String} modelString
        - * @param  {String} [fileType]
        - * @param  {function(p5.Geometry)} [successCallback]
        - * @param  {function(Event)} [failureCallback]
        - * @return {p5.Geometry} the <a href="#/p5.Geometry">p5.Geometry</a> object
        - */
        -/**
        - * @method createModel
        - * @param  {String} modelString
        - * @param  {String} [fileType]
        - * @param  {Object} [options]
        - * @param  {function(p5.Geometry)} [options.successCallback]
        - * @param  {function(Event)} [options.failureCallback]
        - * @param  {boolean} [options.normalize]
        - * @param  {boolean} [options.flipU]
        - * @param  {boolean} [options.flipV]
        - * @return {p5.Geometry} the <a href="#/p5.Geometry">p5.Geometry</a> object
        - */
        -let modelCounter = 0;
        -p5.prototype.createModel = function(modelString, fileType=' ', options) {
        -  p5._validateParameters('createModel', arguments);
        -  let normalize= false;
        -  let successCallback;
        -  let failureCallback;
        -  let flipU = false;
        -  let flipV = false;
        -  if (options && typeof options === 'object') {
        -    normalize = options.normalize || false;
        -    successCallback = options.successCallback;
        -    failureCallback = options.failureCallback;
        -    flipU = options.flipU || false;
        -    flipV = options.flipV || false;
        -  } else if (typeof options === 'boolean') {
        -    normalize = options;
        -    successCallback = arguments[3];
        -    failureCallback = arguments[4];
        -  } else {
        -    successCallback = typeof arguments[2] === 'function' ? arguments[2] : undefined;
        -    failureCallback = arguments[3];
        -  }
        -  const model = new p5.Geometry();
        -  model.gid = `${fileType}|${normalize}|${modelCounter++}`;
        -
        -  if (fileType.match(/\.stl$/i)) {
        -    try {
        -      let uint8array = new TextEncoder().encode(modelString);
        -      let arrayBuffer = uint8array.buffer;
        -      parseSTL(model, arrayBuffer);
        -    } catch (error) {
        -      if (failureCallback) {
        -        failureCallback(error);
        -      } else {
        -        p5._friendlyError('Error during parsing: ' + error.message);
        +    if (fileType.match(/\.stl$/i)) {
        +      try {
        +        let uint8array = new TextEncoder().encode(modelString);
        +        let arrayBuffer = uint8array.buffer;
        +        parseSTL(model, arrayBuffer);
        +      } catch (error) {
        +        if (failureCallback) {
        +          failureCallback(error);
        +        } else {
        +          p5._friendlyError('Error during parsing: ' + error.message);
        +        }
        +        return;
               }
        -      return;
        -    }
        -  } else if (fileType.match(/\.obj$/i)) {
        -    try {
        -      const lines = modelString.split('\n');
        -      parseObj(model, lines);
        -    } catch (error) {
        +    } else if (fileType.match(/\.obj$/i)) {
        +      try {
        +        const lines = modelString.split('\n');
        +        parseObj(model, lines);
        +      } catch (error) {
        +        if (failureCallback) {
        +          failureCallback(error);
        +        } else {
        +          p5._friendlyError('Error during parsing: ' + error.message);
        +        }
        +        return;
        +      }
        +    } else {
        +      p5._friendlyFileLoadError(3, modelString);
               if (failureCallback) {
        -        failureCallback(error);
        +        failureCallback();
               } else {
        -        p5._friendlyError('Error during parsing: ' + error.message);
        +        p5._friendlyError(
        +          'Sorry, the file type is invalid. Only OBJ and STL files are supported.'
        +        );
               }
        -      return;
             }
        -  } else {
        -    p5._friendlyFileLoadError(3, modelString);
        -    if (failureCallback) {
        -      failureCallback();
        -    } else {
        -      p5._friendlyError(
        -        'Sorry, the file type is invalid. Only OBJ and STL files are supported.'
        -      );
        +    if (normalize) {
        +      model.normalize();
             }
        -  }
        -  if (normalize) {
        -    model.normalize();
        -  }
         
        -  if (flipU) {
        -    model.flipU();
        -  }
        +    if (flipU) {
        +      model.flipU();
        +    }
         
        -  if (flipV) {
        -    model.flipV();
        -  }
        +    if (flipV) {
        +      model.flipV();
        +    }
         
        -  model._makeTriangleEdges();
        +    model._makeTriangleEdges();
         
        -  if (typeof successCallback === 'function') {
        -    successCallback(model);
        -  }
        +    if (typeof successCallback === 'function') {
        +      successCallback(model);
        +    }
         
        -  return model;
        -};
        +    return model;
        +  };
        +}
         
        +export default loading;
         
        -export default p5;
        +if(typeof p5 !== 'undefined'){
        +  loading(p5, p5.prototype);
        +}
        diff --git a/src/webgl/material.js b/src/webgl/material.js
        index 5a8a7c427d..cd9b444e06 100644
        --- a/src/webgl/material.js
        +++ b/src/webgl/material.js
        @@ -5,3306 +5,3815 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
         import * as constants from '../core/constants';
        -import './p5.Texture';
        +import { RendererGL } from './p5.RendererGL';
        +import { Shader } from './p5.Shader';
        +import { request } from '../io/files';
        +import { Color } from '../color/p5.Color';
         
        -/**
        - * Loads vertex and fragment shaders to create a
        - * <a href="#/p5.Shader">p5.Shader</a> object.
        - *
        - * Shaders are programs that run on the graphics processing unit (GPU). They
        - * can process many pixels at the same time, making them fast for many
        - * graphics tasks. They’re written in a language called
        - * <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders" target="_blank">GLSL</a>
        - * and run along with the rest of the code in a sketch.
        - *
        - * Once the <a href="#/p5.Shader">p5.Shader</a> object is created, it can be
        - * used with the <a href="#/p5/shader">shader()</a> function, as in
        - * `shader(myShader)`. A shader program consists of two files, a vertex shader
        - * and a fragment shader. The vertex shader affects where 3D geometry is drawn
        - * on the screen and the fragment shader affects color.
        - *
        - * `loadShader()` loads the vertex and fragment shaders from their `.vert` and
        - * `.frag` files. For example, calling
        - * `loadShader('assets/shader.vert', 'assets/shader.frag')` loads both
        - * required shaders and returns a <a href="#/p5.Shader">p5.Shader</a> object.
        - *
        - * The third parameter, `successCallback`, is optional. If a function is
        - * passed, it will be called once the shader has loaded. The callback function
        - * can use the new <a href="#/p5.Shader">p5.Shader</a> object as its
        - * parameter.
        - *
        - * The fourth parameter, `failureCallback`, is also optional. If a function is
        - * passed, it will be called if the shader fails to load. The callback
        - * function can use the event error as its parameter.
        - *
        - * Shaders can take time to load. Calling `loadShader()` in
        - * <a href="#/p5/preload">preload()</a> ensures shaders load before they're
        - * used in <a href="#/p5/setup">setup()</a> or <a href="#/p5/draw">draw()</a>.
        - *
        - * Note: Shaders can only be used in WebGL mode.
        - *
        - * @method loadShader
        - * @param {String} vertFilename path of the vertex shader to be loaded.
        - * @param {String} fragFilename path of the fragment shader to be loaded.
        - * @param {function} [successCallback] function to call once the shader is loaded. Can be passed the
        - *                                     <a href="#/p5.Shader">p5.Shader</a> object.
        - * @param {function} [failureCallback] function to call if the shader fails to load. Can be passed an
        - *                                     `Error` event object.
        - * @return {p5.Shader} new shader created from the vertex and fragment shader files.
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * let mandelbrot;
        - *
        - * // Load the shader and create a p5.Shader object.
        - * function preload() {
        - *   mandelbrot = loadShader('assets/shader.vert', 'assets/shader.frag');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Compile and apply the p5.Shader object.
        - *   shader(mandelbrot);
        - *
        - *   // Set the shader uniform p to an array.
        - *   mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
        - *
        - *   // Set the shader uniform r to the value 1.5.
        - *   mandelbrot.setUniform('r', 1.5);
        - *
        - *   // Add a quad as a display surface for the shader.
        - *   quad(-1, -1, 1, -1, 1, 1, -1, 1);
        - *
        - *   describe('A black fractal image on a magenta background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * let mandelbrot;
        - *
        - * // Load the shader and create a p5.Shader object.
        - * function preload() {
        - *   mandelbrot = loadShader('assets/shader.vert', 'assets/shader.frag');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Use the p5.Shader object.
        - *   shader(mandelbrot);
        - *
        - *   // Set the shader uniform p to an array.
        - *   mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
        - *
        - *   describe('A fractal image zooms in and out of focus.');
        - * }
        - *
        - * function draw() {
        - *   // Set the shader uniform r to a value that oscillates between 0 and 2.
        - *   mandelbrot.setUniform('r', sin(frameCount * 0.01) + 1);
        - *
        - *   // Add a quad as a display surface for the shader.
        - *   quad(-1, -1, 1, -1, 1, 1, -1, 1);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.loadShader = function (
        -  vertFilename,
        -  fragFilename,
        -  successCallback,
        -  failureCallback
        -) {
        -  p5._validateParameters('loadShader', arguments);
        -  if (!failureCallback) {
        -    failureCallback = console.error;
        -  }
        +function material(p5, fn){
        +  /**
        +   * Loads vertex and fragment shaders to create a
        +   * <a href="#/p5.Shader">p5.Shader</a> object.
        +   *
        +   * Shaders are programs that run on the graphics processing unit (GPU). They
        +   * can process many pixels at the same time, making them fast for many
        +   * graphics tasks. They’re written in a language called
        +   * <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders" target="_blank">GLSL</a>
        +   * and run along with the rest of the code in a sketch.
        +   *
        +   * Once the <a href="#/p5.Shader">p5.Shader</a> object is created, it can be
        +   * used with the <a href="#/p5/shader">shader()</a> function, as in
        +   * `shader(myShader)`. A shader program consists of two files, a vertex shader
        +   * and a fragment shader. The vertex shader affects where 3D geometry is drawn
        +   * on the screen and the fragment shader affects color.
        +   *
        +   * `loadShader()` loads the vertex and fragment shaders from their `.vert` and
        +   * `.frag` files. For example, calling
        +   * `loadShader('assets/shader.vert', 'assets/shader.frag')` loads both
        +   * required shaders and returns a <a href="#/p5.Shader">p5.Shader</a> object.
        +   *
        +   * The third parameter, `successCallback`, is optional. If a function is
        +   * passed, it will be called once the shader has loaded. The callback function
        +   * can use the new <a href="#/p5.Shader">p5.Shader</a> object as its
        +   * parameter. The return value of the `successCallback()` function will be used
        +   * as the final return value of `loadShader()`.
        +   *
        +   * The fourth parameter, `failureCallback`, is also optional. If a function is
        +   * passed, it will be called if the shader fails to load. The callback
        +   * function can use the event error as its parameter. The return value of the `
        +   * failureCallback()` function will be used as the final return value of `loadShader()`.
        +   *
        +   * This function returns a `Promise` and should be used in an `async` setup with
        +   * `await`. See the examples for the usage syntax.
        +   *
        +   * Note: Shaders can only be used in WebGL mode.
        +   *
        +   * @method loadShader
        +   * @param {String|Request} vertFilename path of the vertex shader to be loaded.
        +   * @param {String|Request} fragFilename path of the fragment shader to be loaded.
        +   * @param {Function} [successCallback] function to call once the shader is loaded. Can be passed the
        +   *                                     <a href="#/p5.Shader">p5.Shader</a> object.
        +   * @param {Function} [failureCallback] function to call if the shader fails to load. Can be passed an
        +   *                                     `Error` event object.
        +   * @return {Promise<p5.Shader>} new shader created from the vertex and fragment shader files.
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * // Note: A "uniform" is a global variable within a shader program.
        +   *
        +   * let mandelbrot;
        +   *
        +   * // Load the shader and create a p5.Shader object.
        +   * async function setup() {
        +   *   mandelbrot = await loadShader('assets/shader.vert', 'assets/shader.frag');
        +   *
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Compile and apply the p5.Shader object.
        +   *   shader(mandelbrot);
        +   *
        +   *   // Set the shader uniform p to an array.
        +   *   mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
        +   *
        +   *   // Set the shader uniform r to the value 1.5.
        +   *   mandelbrot.setUniform('r', 1.5);
        +   *
        +   *   // Add a quad as a display surface for the shader.
        +   *   quad(-1, -1, 1, -1, 1, 1, -1, 1);
        +   *
        +   *   describe('A black fractal image on a magenta background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Note: A "uniform" is a global variable within a shader program.
        +   *
        +   * let mandelbrot;
        +   *
        +   * // Load the shader and create a p5.Shader object.
        +   * async function setup() {
        +   *   mandelbrot = await loadShader('assets/shader.vert', 'assets/shader.frag');
        +   *
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Use the p5.Shader object.
        +   *   shader(mandelbrot);
        +   *
        +   *   // Set the shader uniform p to an array.
        +   *   mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
        +   *
        +   *   describe('A fractal image zooms in and out of focus.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Set the shader uniform r to a value that oscillates between 0 and 2.
        +   *   mandelbrot.setUniform('r', sin(frameCount * 0.01) + 1);
        +   *
        +   *   // Add a quad as a display surface for the shader.
        +   *   quad(-1, -1, 1, -1, 1, 1, -1, 1);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.loadShader = async function (
        +    vertFilename,
        +    fragFilename,
        +    successCallback,
        +    failureCallback
        +  ) {
        +    // p5._validateParameters('loadShader', arguments);
         
        -  const loadedShader = new p5.Shader();
        +    const loadedShader = new Shader();
         
        -  const self = this;
        -  let loadedFrag = false;
        -  let loadedVert = false;
        +    try {
        +      loadedShader._vertSrc = await request(vertFilename, 'text');
        +      loadedShader._fragSrc = await request(fragFilename, 'text');
         
        -  const onLoad = () => {
        -    self._decrementPreload();
        -    if (successCallback) {
        -      successCallback(loadedShader);
        +      if (successCallback) {
        +        return successCallback(loadedShader);
        +      } else {
        +        return loadedShader
        +      }
        +    } catch(err) {
        +      if (failureCallback) {
        +        return failureCallback(err);
        +      } else {
        +        throw err;
        +      }
             }
           };
         
        -  this.loadStrings(
        -    vertFilename,
        -    result => {
        -      loadedShader._vertSrc = result.join('\n');
        -      loadedVert = true;
        -      if (loadedFrag) {
        -        onLoad();
        +  /**
        +   * Creates a new <a href="#/p5.Shader">p5.Shader</a> object.
        +   *
        +   * Shaders are programs that run on the graphics processing unit (GPU). They
        +   * can process many pixels at the same time, making them fast for many
        +   * graphics tasks. They’re written in a language called
        +   * <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders" target="_blank">GLSL</a>
        +   * and run along with the rest of the code in a sketch.
        +   *
        +   * Once the <a href="#/p5.Shader">p5.Shader</a> object is created, it can be
        +   * used with the <a href="#/p5/shader">shader()</a> function, as in
        +   * `shader(myShader)`. A shader program consists of two parts, a vertex shader
        +   * and a fragment shader. The vertex shader affects where 3D geometry is drawn
        +   * on the screen and the fragment shader affects color.
        +   *
        +   * The first parameter, `vertSrc`, sets the vertex shader. It’s a string that
        +   * contains the vertex shader program written in GLSL.
        +   *
        +   * The second parameter, `fragSrc`, sets the fragment shader. It’s a string
        +   * that contains the fragment shader program written in GLSL.
        +   *
        +   * A shader can optionally describe *hooks,* which are functions in GLSL that
        +   * users may choose to provide to customize the behavior of the shader using the
        +   * <a href="#/p5.Shader/modify">`modify()`</a> method of `p5.Shader`. These are added by
        +   * describing the hooks in a third parameter, `options`, and referencing the hooks in
        +   * your `vertSrc` or `fragSrc`. Hooks for the vertex or fragment shader are described under
        +   * the `vertex` and `fragment` keys of `options`. Each one is an object. where each key is
        +   * the type and name of a hook function, and each value is a string with the
        +   * parameter list and default implementation of the hook. For example, to let users
        +   * optionally run code at the start of the vertex shader, the options object could
        +   * include:
        +   *
        +   * ```js
        +   * {
        +   *   vertex: {
        +   *     'void beforeVertex': '() {}'
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * Then, in your vertex shader source, you can run a hook by calling a function
        +   * with the same name prefixed by `HOOK_`. If you want to check if the default
        +   * hook has been replaced, maybe to avoid extra overhead, you can check if the
        +   * same name prefixed by `AUGMENTED_HOOK_` has been defined:
        +   *
        +   * ```glsl
        +   * void main() {
        +   *   // In most cases, just calling the hook is fine:
        +   *   HOOK_beforeVertex();
        +   *
        +   *   // Alternatively, for more efficiency:
        +   *   #ifdef AUGMENTED_HOOK_beforeVertex
        +   *   HOOK_beforeVertex();
        +   *   #endif
        +   *
        +   *   // Add the rest of your shader code here!
        +   * }
        +   * ```
        +   *
        +   * Note: Only filter shaders can be used in 2D mode. All shaders can be used
        +   * in WebGL mode.
        +   *
        +   * @method createShader
        +   * @param {String} vertSrc source code for the vertex shader.
        +   * @param {String} fragSrc source code for the fragment shader.
        +   * @param {Object} [options] An optional object describing how this shader can
        +   * be augmented with hooks. It can include:
        +   *  - `vertex`: An object describing the available vertex shader hooks.
        +   *  - `fragment`: An object describing the available frament shader hooks.
        +   * @returns {p5.Shader} new shader object created from the
        +   * vertex and fragment shaders.
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * // Note: A "uniform" is a global variable within a shader program.
        +   *
        +   * // Create a string with the vertex shader program.
        +   * // The vertex shader is called for each vertex.
        +   * let vertSrc = `
        +   * precision highp float;
        +   * uniform mat4 uModelViewMatrix;
        +   * uniform mat4 uProjectionMatrix;
        +   * attribute vec3 aPosition;
        +   * attribute vec2 aTexCoord;
        +   * varying vec2 vTexCoord;
        +   *
        +   * void main() {
        +   *   vTexCoord = aTexCoord;
        +   *   vec4 positionVec4 = vec4(aPosition, 1.0);
        +   *   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        +   * }
        +   * `;
        +   *
        +   * // Create a string with the fragment shader program.
        +   * // The fragment shader is called for each pixel.
        +   * let fragSrc = `
        +   * precision highp float;
        +   *
        +   * void main() {
        +   *   // Set each pixel's RGBA value to yellow.
        +   *   gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
        +   * }
        +   * `;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Shader object.
        +   *   let shaderProgram = createShader(vertSrc, fragSrc);
        +   *
        +   *   // Compile and apply the p5.Shader object.
        +   *   shader(shaderProgram);
        +   *
        +   *   // Style the drawing surface.
        +   *   noStroke();
        +   *
        +   *   // Add a plane as a drawing surface.
        +   *   plane(100, 100);
        +   *
        +   *   describe('A yellow square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Note: A "uniform" is a global variable within a shader program.
        +   *
        +   * // Create a string with the vertex shader program.
        +   * // The vertex shader is called for each vertex.
        +   * let vertSrc = `
        +   * precision highp float;
        +   * uniform mat4 uModelViewMatrix;
        +   * uniform mat4 uProjectionMatrix;
        +   * attribute vec3 aPosition;
        +   * attribute vec2 aTexCoord;
        +   * varying vec2 vTexCoord;
        +   *
        +   * void main() {
        +   *   vTexCoord = aTexCoord;
        +   *   vec4 positionVec4 = vec4(aPosition, 1.0);
        +   *   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        +   * }
        +   * `;
        +   *
        +   * // Create a string with the fragment shader program.
        +   * // The fragment shader is called for each pixel.
        +   * let fragSrc = `
        +   * precision highp float;
        +   * uniform vec2 p;
        +   * uniform float r;
        +   * const int numIterations = 500;
        +   * varying vec2 vTexCoord;
        +   *
        +   * void main() {
        +   *   vec2 c = p + gl_FragCoord.xy * r;
        +   *   vec2 z = c;
        +   *   float n = 0.0;
        +   *
        +   *   for (int i = numIterations; i > 0; i--) {
        +   *     if (z.x * z.x + z.y * z.y > 4.0) {
        +   *       n = float(i) / float(numIterations);
        +   *       break;
        +   *     }
        +   *     z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
        +   *   }
        +   *
        +   *   gl_FragColor = vec4(
        +   *     0.5 - cos(n * 17.0) / 2.0,
        +   *     0.5 - cos(n * 13.0) / 2.0,
        +   *     0.5 - cos(n * 23.0) / 2.0,
        +   *     1.0
        +   *   );
        +   * }
        +   * `;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Shader object.
        +   *   let mandelbrot = createShader(vertSrc, fragSrc);
        +   *
        +   *   // Compile and apply the p5.Shader object.
        +   *   shader(mandelbrot);
        +   *
        +   *   // Set the shader uniform p to an array.
        +   *   // p is the center point of the Mandelbrot image.
        +   *   mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
        +   *
        +   *   // Set the shader uniform r to 0.005.
        +   *   // r is the size of the image in Mandelbrot-space.
        +   *   mandelbrot.setUniform('r', 0.005);
        +   *
        +   *   // Style the drawing surface.
        +   *   noStroke();
        +   *
        +   *   // Add a plane as a drawing surface.
        +   *   plane(100, 100);
        +   *
        +   *   describe('A black fractal image on a magenta background.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Note: A "uniform" is a global variable within a shader program.
        +   *
        +   * // Create a string with the vertex shader program.
        +   * // The vertex shader is called for each vertex.
        +   * let vertSrc = `
        +   * precision highp float;
        +   * uniform mat4 uModelViewMatrix;
        +   * uniform mat4 uProjectionMatrix;
        +   *
        +   * attribute vec3 aPosition;
        +   * attribute vec2 aTexCoord;
        +   * varying vec2 vTexCoord;
        +   *
        +   * void main() {
        +   *   vTexCoord = aTexCoord;
        +   *   vec4 positionVec4 = vec4(aPosition, 1.0);
        +   *   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        +   * }
        +   * `;
        +   *
        +   * // Create a string with the fragment shader program.
        +   * // The fragment shader is called for each pixel.
        +   * let fragSrc = `
        +   * precision highp float;
        +   * uniform vec2 p;
        +   * uniform float r;
        +   * const int numIterations = 500;
        +   * varying vec2 vTexCoord;
        +   *
        +   * void main() {
        +   *   vec2 c = p + gl_FragCoord.xy * r;
        +   *   vec2 z = c;
        +   *   float n = 0.0;
        +   *
        +   *   for (int i = numIterations; i > 0; i--) {
        +   *     if (z.x * z.x + z.y * z.y > 4.0) {
        +   *       n = float(i) / float(numIterations);
        +   *       break;
        +   *     }
        +   *
        +   *     z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
        +   *   }
        +   *
        +   *   gl_FragColor = vec4(
        +   *     0.5 - cos(n * 17.0) / 2.0,
        +   *     0.5 - cos(n * 13.0) / 2.0,
        +   *     0.5 - cos(n * 23.0) / 2.0,
        +   *     1.0
        +   *   );
        +   * }
        +   * `;
        +   *
        +   * let mandelbrot;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Shader object.
        +   *   mandelbrot = createShader(vertSrc, fragSrc);
        +   *
        +   *   // Apply the p5.Shader object.
        +   *   shader(mandelbrot);
        +   *
        +   *   // Set the shader uniform p to an array.
        +   *   // p is the center point of the Mandelbrot image.
        +   *   mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
        +   *
        +   *   describe('A fractal image zooms in and out of focus.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Set the shader uniform r to a value that oscillates
        +   *   // between 0 and 0.005.
        +   *   // r is the size of the image in Mandelbrot-space.
        +   *   let radius = 0.005 * (sin(frameCount * 0.01) + 1);
        +   *   mandelbrot.setUniform('r', radius);
        +   *
        +   *   // Style the drawing surface.
        +   *   noStroke();
        +   *
        +   *   // Add a plane as a drawing surface.
        +   *   plane(100, 100);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // A shader with hooks.
        +   * let myShader;
        +   *
        +   * // A shader with modified hooks.
        +   * let modifiedShader;
        +   *
        +   * // Create a string with the vertex shader program.
        +   * // The vertex shader is called for each vertex.
        +   * let vertSrc = `
        +   * precision highp float;
        +   * uniform mat4 uModelViewMatrix;
        +   * uniform mat4 uProjectionMatrix;
        +   *
        +   * attribute vec3 aPosition;
        +   * attribute vec2 aTexCoord;
        +   *
        +   * void main() {
        +   *   vec4 positionVec4 = vec4(aPosition, 1.0);
        +   *   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        +   * }
        +   * `;
        +   *
        +   * // Create a fragment shader that uses a hook.
        +   * let fragSrc = `
        +   * precision highp float;
        +   * void main() {
        +   *   // Let users override the color
        +   *   gl_FragColor = HOOK_getColor(vec4(1., 0., 0., 1.));
        +   * }
        +   * `;
        +   *
        +   * function setup() {
        +   *   createCanvas(50, 50, WEBGL);
        +   *
        +   *   // Create a shader with hooks
        +   *   myShader = createShader(vertSrc, fragSrc, {
        +   *     fragment: {
        +   *       'vec4 getColor': '(vec4 color) { return color; }'
        +   *     }
        +   *   });
        +   *
        +   *   // Make a version of the shader with a hook overridden
        +   *   modifiedShader = myShader.modify({
        +   *     'vec4 getColor': `(vec4 color) {
        +   *       return vec4(0., 0., 1., 1.);
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   noStroke();
        +   *
        +   *   push();
        +   *   shader(myShader);
        +   *   translate(-width/3, 0);
        +   *   sphere(10);
        +   *   pop();
        +   *
        +   *   push();
        +   *   shader(modifiedShader);
        +   *   translate(width/3, 0);
        +   *   sphere(10);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createShader = function (vertSrc, fragSrc, options) {
        +    // p5._validateParameters('createShader', arguments);
        +    return new Shader(this._renderer, vertSrc, fragSrc, options);
        +  };
        +
        +  /**
        +   * Creates and loads a filter shader from an external file.
        +   *
        +   * @method loadFilterShader
        +   * @param {String} fragFilename path to the fragment shader file
        +   * @param {Function} [successCallback] callback to be called once the shader is
        +   *                                     loaded. Will be passed the
        +   *                                     <a href="#/p5.Shader">p5.Shader</a> object.
        +   * @param {Function} [failureCallback] callback to be called if there is an error
        +   *                                     loading the shader. Will be passed the
        +   *                                     error event.
        +   * @return {Promise<p5.Shader>} a promise that resolves with a shader object
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * async function setup() {
        +   *   myShader = await loadFilterShader('assets/shader.frag');
        +   *   createCanvas(100, 100, WEBGL);
        +   *   noStroke();
        +   * }
        +   *
        +   * function draw() {
        +   *   // shader() sets the active shader with our shader
        +   *   shader(myShader);
        +   *
        +   *   // rect gives us some geometry on the screen
        +   *   rect(0, 0, width, height);
        +   * }
        +   * </code>
        +   * </div>
        +   * @alt
        +   * A rectangle with a shader applied to it.
        +   */
        +  fn.loadFilterShader = async function (fragFilename, successCallback, failureCallback) {
        +    p5._validateParameters('loadFilterShader', arguments);
        +    try {
        +      // Load the fragment shader
        +      const fragSrc = await this.loadStrings(fragFilename);
        +      const fragString = await fragSrc.join('\n');
        +
        +      // Create the shader using createFilterShader
        +      const loadedShader = this.createFilterShader(fragString, true);
        +
        +      if (successCallback) {
        +        successCallback(loadedShader);
               }
        -    },
        -    failureCallback
        -  );
         
        -  this.loadStrings(
        -    fragFilename,
        -    result => {
        -      loadedShader._fragSrc = result.join('\n');
        -      loadedFrag = true;
        -      if (loadedVert) {
        -        onLoad();
        +      return loadedShader;
        +    } catch (err) {
        +      if (failureCallback) {
        +        failureCallback(err);
        +      } else {
        +        console.error(err);
               }
        -    },
        -    failureCallback
        -  );
        +    }
        +  };
         
        -  return loadedShader;
        -};
        +  /**
        +   * Creates a <a href="#/p5.Shader">p5.Shader</a> object to be used with the
        +   * <a href="#/p5/filter">filter()</a> function.
        +   *
        +   * `createFilterShader()` works like
        +   * <a href="#/p5/createShader">createShader()</a> but has a default vertex
        +   * shader included. `createFilterShader()` is intended to be used along with
        +   * <a href="#/p5/filter">filter()</a> for filtering the contents of a canvas.
        +   * A filter shader will be applied to the whole canvas instead of just
        +   * <a href="#/p5.Geometry">p5.Geometry</a> objects.
        +   *
        +   * The parameter, `fragSrc`, sets the fragment shader. It’s a string that
        +   * contains the fragment shader program written in
        +   * <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders" target="_blank">GLSL</a>.
        +   *
        +   * The <a href="#/p5.Shader">p5.Shader</a> object that's created has some
        +   * uniforms that can be set:
        +   * - `sampler2D tex0`, which contains the canvas contents as a texture.
        +   * - `vec2 canvasSize`, which is the width and height of the canvas, not including pixel density.
        +   * - `vec2 texelSize`, which is the size of a physical pixel including pixel density. This is calculated as `1.0 / (width * density)` for the pixel width and `1.0 / (height * density)` for the pixel height.
        +   *
        +   * The <a href="#/p5.Shader">p5.Shader</a> that's created also provides
        +   * `varying vec2 vTexCoord`, a coordinate with values between 0 and 1.
        +   * `vTexCoord` describes where on the canvas the pixel will be drawn.
        +   *
        +   * For more info about filters and shaders, see Adam Ferriss' <a href="https://github.com/aferriss/p5jsShaderExamples">repo of shader examples</a>
        +   * or the <a href="https://p5js.org/learn/getting-started-in-webgl-shaders.html">Introduction to Shaders</a> tutorial.
        +   *
        +   * @method createFilterShader
        +   * @param {String} fragSrc source code for the fragment shader.
        +   * @returns {p5.Shader} new shader object created from the fragment shader.
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * function setup() {
        +   *   let fragSrc = `precision highp float;
        +   *   void main() {
        +   *     gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
        +   *   }`;
        +   *
        +   *   createCanvas(100, 100, WEBGL);
        +   *   let s = createFilterShader(fragSrc);
        +   *   filter(s);
        +   *   describe('a yellow canvas');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let img, s;
        +   * async function setup() {
        +   *   img = await loadImage('assets/bricks.jpg');
        +   *   let fragSrc = `precision highp float;
        +   *
        +   *   // x,y coordinates, given from the vertex shader
        +   *   varying vec2 vTexCoord;
        +   *
        +   *   // the canvas contents, given from filter()
        +   *   uniform sampler2D tex0;
        +   *   // other useful information from the canvas
        +   *   uniform vec2 texelSize;
        +   *   uniform vec2 canvasSize;
        +   *   // a custom variable from this sketch
        +   *   uniform float darkness;
        +   *
        +   *   void main() {
        +   *     // get the color at current pixel
        +   *     vec4 color = texture2D(tex0, vTexCoord);
        +   *     // set the output color
        +   *     color.b = 1.0;
        +   *     color *= darkness;
        +   *     gl_FragColor = vec4(color.rgb, 1.0);
        +   *   }`;
        +   *
        +   *   createCanvas(100, 100, WEBGL);
        +   *   s = createFilterShader(fragSrc);
        +   * }
        +   *
        +   * function draw() {
        +   *   image(img, -50, -50);
        +   *   s.setUniform('darkness', 0.5);
        +   *   filter(s);
        +   *   describe('a image of bricks tinted dark blue');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createFilterShader = function (fragSrc, skipContextCheck = false) {
        +    // p5._validateParameters('createFilterShader', arguments);
        +    let defaultVertV1 = `
        +      uniform mat4 uModelViewMatrix;
        +      uniform mat4 uProjectionMatrix;
         
        -/**
        - * Creates a new <a href="#/p5.Shader">p5.Shader</a> object.
        - *
        - * Shaders are programs that run on the graphics processing unit (GPU). They
        - * can process many pixels at the same time, making them fast for many
        - * graphics tasks. They’re written in a language called
        - * <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders" target="_blank">GLSL</a>
        - * and run along with the rest of the code in a sketch.
        - *
        - * Once the <a href="#/p5.Shader">p5.Shader</a> object is created, it can be
        - * used with the <a href="#/p5/shader">shader()</a> function, as in
        - * `shader(myShader)`. A shader program consists of two parts, a vertex shader
        - * and a fragment shader. The vertex shader affects where 3D geometry is drawn
        - * on the screen and the fragment shader affects color.
        - *
        - * The first parameter, `vertSrc`, sets the vertex shader. It’s a string that
        - * contains the vertex shader program written in GLSL.
        - *
        - * The second parameter, `fragSrc`, sets the fragment shader. It’s a string
        - * that contains the fragment shader program written in GLSL.
        - *
        - * A shader can optionally describe *hooks,* which are functions in GLSL that
        - * users may choose to provide to customize the behavior of the shader using the
        - * <a href="#/p5.Shader/modify">`modify()`</a> method of `p5.Shader`. These are added by
        - * describing the hooks in a third parameter, `options`, and referencing the hooks in
        - * your `vertSrc` or `fragSrc`. Hooks for the vertex or fragment shader are described under
        - * the `vertex` and `fragment` keys of `options`. Each one is an object. where each key is
        - * the type and name of a hook function, and each value is a string with the
        - * parameter list and default implementation of the hook. For example, to let users
        - * optionally run code at the start of the vertex shader, the options object could
        - * include:
        - *
        - * ```js
        - * {
        - *   vertex: {
        - *     'void beforeVertex': '() {}'
        - *   }
        - * }
        - * ```
        - *
        - * Then, in your vertex shader source, you can run a hook by calling a function
        - * with the same name prefixed by `HOOK_`. If you want to check if the default
        - * hook has been replaced, maybe to avoid extra overhead, you can check if the
        - * same name prefixed by `AUGMENTED_HOOK_` has been defined:
        - *
        - * ```glsl
        - * void main() {
        - *   // In most cases, just calling the hook is fine:
        - *   HOOK_beforeVertex();
        - *
        - *   // Alternatively, for more efficiency:
        - *   #ifdef AUGMENTED_HOOK_beforeVertex
        - *   HOOK_beforeVertex();
        - *   #endif
        - *
        - *   // Add the rest of your shader code here!
        - * }
        - * ```
        - *
        - * Note: Only filter shaders can be used in 2D mode. All shaders can be used
        - * in WebGL mode.
        - *
        - * @method createShader
        - * @param {String} vertSrc source code for the vertex shader.
        - * @param {String} fragSrc source code for the fragment shader.
        - * @param {Object} [options] An optional object describing how this shader can
        - * be augmented with hooks. It can include:
        - *  - `vertex`: An object describing the available vertex shader hooks.
        - *  - `fragment`: An object describing the available frament shader hooks.
        - * @returns {p5.Shader} new shader object created from the
        - * vertex and fragment shaders.
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * // Create a string with the vertex shader program.
        - * // The vertex shader is called for each vertex.
        - * let vertSrc = `
        - * precision highp float;
        - * uniform mat4 uModelViewMatrix;
        - * uniform mat4 uProjectionMatrix;
        - * attribute vec3 aPosition;
        - * attribute vec2 aTexCoord;
        - * varying vec2 vTexCoord;
        - *
        - * void main() {
        - *   vTexCoord = aTexCoord;
        - *   vec4 positionVec4 = vec4(aPosition, 1.0);
        - *   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        - * }
        - * `;
        - *
        - * // Create a string with the fragment shader program.
        - * // The fragment shader is called for each pixel.
        - * let fragSrc = `
        - * precision highp float;
        - *
        - * void main() {
        - *   // Set each pixel's RGBA value to yellow.
        - *   gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
        - * }
        - * `;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Shader object.
        - *   let shaderProgram = createShader(vertSrc, fragSrc);
        - *
        - *   // Compile and apply the p5.Shader object.
        - *   shader(shaderProgram);
        - *
        - *   // Style the drawing surface.
        - *   noStroke();
        - *
        - *   // Add a plane as a drawing surface.
        - *   plane(100, 100);
        - *
        - *   describe('A yellow square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * // Create a string with the vertex shader program.
        - * // The vertex shader is called for each vertex.
        - * let vertSrc = `
        - * precision highp float;
        - * uniform mat4 uModelViewMatrix;
        - * uniform mat4 uProjectionMatrix;
        - * attribute vec3 aPosition;
        - * attribute vec2 aTexCoord;
        - * varying vec2 vTexCoord;
        - *
        - * void main() {
        - *   vTexCoord = aTexCoord;
        - *   vec4 positionVec4 = vec4(aPosition, 1.0);
        - *   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        - * }
        - * `;
        - *
        - * // Create a string with the fragment shader program.
        - * // The fragment shader is called for each pixel.
        - * let fragSrc = `
        - * precision highp float;
        - * uniform vec2 p;
        - * uniform float r;
        - * const int numIterations = 500;
        - * varying vec2 vTexCoord;
        - *
        - * void main() {
        - *   vec2 c = p + gl_FragCoord.xy * r;
        - *   vec2 z = c;
        - *   float n = 0.0;
        - *
        - *   for (int i = numIterations; i > 0; i--) {
        - *     if (z.x * z.x + z.y * z.y > 4.0) {
        - *       n = float(i) / float(numIterations);
        - *       break;
        - *     }
        - *     z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
        - *   }
        - *
        - *   gl_FragColor = vec4(
        - *     0.5 - cos(n * 17.0) / 2.0,
        - *     0.5 - cos(n * 13.0) / 2.0,
        - *     0.5 - cos(n * 23.0) / 2.0,
        - *     1.0
        - *   );
        - * }
        - * `;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Shader object.
        - *   let mandelbrot = createShader(vertSrc, fragSrc);
        - *
        - *   // Compile and apply the p5.Shader object.
        - *   shader(mandelbrot);
        - *
        - *   // Set the shader uniform p to an array.
        - *   // p is the center point of the Mandelbrot image.
        - *   mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
        - *
        - *   // Set the shader uniform r to 0.005.
        - *   // r is the size of the image in Mandelbrot-space.
        - *   mandelbrot.setUniform('r', 0.005);
        - *
        - *   // Style the drawing surface.
        - *   noStroke();
        - *
        - *   // Add a plane as a drawing surface.
        - *   plane(100, 100);
        - *
        - *   describe('A black fractal image on a magenta background.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * // Create a string with the vertex shader program.
        - * // The vertex shader is called for each vertex.
        - * let vertSrc = `
        - * precision highp float;
        - * uniform mat4 uModelViewMatrix;
        - * uniform mat4 uProjectionMatrix;
        - *
        - * attribute vec3 aPosition;
        - * attribute vec2 aTexCoord;
        - * varying vec2 vTexCoord;
        - *
        - * void main() {
        - *   vTexCoord = aTexCoord;
        - *   vec4 positionVec4 = vec4(aPosition, 1.0);
        - *   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        - * }
        - * `;
        - *
        - * // Create a string with the fragment shader program.
        - * // The fragment shader is called for each pixel.
        - * let fragSrc = `
        - * precision highp float;
        - * uniform vec2 p;
        - * uniform float r;
        - * const int numIterations = 500;
        - * varying vec2 vTexCoord;
        - *
        - * void main() {
        - *   vec2 c = p + gl_FragCoord.xy * r;
        - *   vec2 z = c;
        - *   float n = 0.0;
        - *
        - *   for (int i = numIterations; i > 0; i--) {
        - *     if (z.x * z.x + z.y * z.y > 4.0) {
        - *       n = float(i) / float(numIterations);
        - *       break;
        - *     }
        - *
        - *     z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
        - *   }
        - *
        - *   gl_FragColor = vec4(
        - *     0.5 - cos(n * 17.0) / 2.0,
        - *     0.5 - cos(n * 13.0) / 2.0,
        - *     0.5 - cos(n * 23.0) / 2.0,
        - *     1.0
        - *   );
        - * }
        - * `;
        - *
        - * let mandelbrot;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Shader object.
        - *   mandelbrot = createShader(vertSrc, fragSrc);
        - *
        - *   // Apply the p5.Shader object.
        - *   shader(mandelbrot);
        - *
        - *   // Set the shader uniform p to an array.
        - *   // p is the center point of the Mandelbrot image.
        - *   mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
        - *
        - *   describe('A fractal image zooms in and out of focus.');
        - * }
        - *
        - * function draw() {
        - *   // Set the shader uniform r to a value that oscillates
        - *   // between 0 and 0.005.
        - *   // r is the size of the image in Mandelbrot-space.
        - *   let radius = 0.005 * (sin(frameCount * 0.01) + 1);
        - *   mandelbrot.setUniform('r', radius);
        - *
        - *   // Style the drawing surface.
        - *   noStroke();
        - *
        - *   // Add a plane as a drawing surface.
        - *   plane(100, 100);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // A shader with hooks.
        - * let myShader;
        - *
        - * // A shader with modified hooks.
        - * let modifiedShader;
        - *
        - * // Create a string with the vertex shader program.
        - * // The vertex shader is called for each vertex.
        - * let vertSrc = `
        - * precision highp float;
        - * uniform mat4 uModelViewMatrix;
        - * uniform mat4 uProjectionMatrix;
        - *
        - * attribute vec3 aPosition;
        - * attribute vec2 aTexCoord;
        - *
        - * void main() {
        - *   vec4 positionVec4 = vec4(aPosition, 1.0);
        - *   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        - * }
        - * `;
        - *
        - * // Create a fragment shader that uses a hook.
        - * let fragSrc = `
        - * precision highp float;
        - * void main() {
        - *   // Let users override the color
        - *   gl_FragColor = HOOK_getColor(vec4(1., 0., 0., 1.));
        - * }
        - * `;
        - *
        - * function setup() {
        - *   createCanvas(50, 50, WEBGL);
        - *
        - *   // Create a shader with hooks
        - *   myShader = createShader(vertSrc, fragSrc, {
        - *     fragment: {
        - *       'vec4 getColor': '(vec4 color) { return color; }'
        - *     }
        - *   });
        - *
        - *   // Make a version of the shader with a hook overridden
        - *   modifiedShader = myShader.modify({
        - *     'vec4 getColor': `(vec4 color) {
        - *       return vec4(0., 0., 1., 1.);
        - *     }`
        - *   });
        - * }
        - *
        - * function draw() {
        - *   noStroke();
        - *
        - *   push();
        - *   shader(myShader);
        - *   translate(-width/3, 0);
        - *   sphere(10);
        - *   pop();
        - *
        - *   push();
        - *   shader(modifiedShader);
        - *   translate(width/3, 0);
        - *   sphere(10);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createShader = function (vertSrc, fragSrc, options) {
        -  p5._validateParameters('createShader', arguments);
        -  return new p5.Shader(this._renderer, vertSrc, fragSrc, options);
        -};
        +      attribute vec3 aPosition;
        +      // texcoords only come from p5 to vertex shader
        +      // so pass texcoords on to the fragment shader in a varying variable
        +      attribute vec2 aTexCoord;
        +      varying vec2 vTexCoord;
         
        -/**
        - * Creates a <a href="#/p5.Shader">p5.Shader</a> object to be used with the
        - * <a href="#/p5/filter">filter()</a> function.
        - *
        - * `createFilterShader()` works like
        - * <a href="#/p5/createShader">createShader()</a> but has a default vertex
        - * shader included. `createFilterShader()` is intended to be used along with
        - * <a href="#/p5/filter">filter()</a> for filtering the contents of a canvas.
        - * A filter shader will be applied to the whole canvas instead of just
        - * <a href="#/p5.Geometry">p5.Geometry</a> objects.
        - *
        - * The parameter, `fragSrc`, sets the fragment shader. It’s a string that
        - * contains the fragment shader program written in
        - * <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders" target="_blank">GLSL</a>.
        - *
        - * The <a href="#/p5.Shader">p5.Shader</a> object that's created has some
        - * uniforms that can be set:
        - * - `sampler2D tex0`, which contains the canvas contents as a texture.
        - * - `vec2 canvasSize`, which is the width and height of the canvas, not including pixel density.
        - * - `vec2 texelSize`, which is the size of a physical pixel including pixel density. This is calculated as `1.0 / (width * density)` for the pixel width and `1.0 / (height * density)` for the pixel height.
        - *
        - * The <a href="#/p5.Shader">p5.Shader</a> that's created also provides
        - * `varying vec2 vTexCoord`, a coordinate with values between 0 and 1.
        - * `vTexCoord` describes where on the canvas the pixel will be drawn.
        - *
        - * For more info about filters and shaders, see Adam Ferriss' <a href="https://github.com/aferriss/p5jsShaderExamples">repo of shader examples</a>
        - * or the <a href="https://p5js.org/tutorials/intro-to-shaders/">Introduction to Shaders</a> tutorial.
        - *
        - * @method createFilterShader
        - * @param {String} fragSrc source code for the fragment shader.
        - * @returns {p5.Shader} new shader object created from the fragment shader.
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * function setup() {
        - *   let fragSrc = `precision highp float;
        - *   void main() {
        - *     gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
        - *   }`;
        - *
        - *   createCanvas(100, 100, WEBGL);
        - *   let s = createFilterShader(fragSrc);
        - *   filter(s);
        - *   describe('a yellow canvas');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div modernizr='webgl'>
        - * <code>
        - * let img, s;
        - * function preload() {
        - *   img = loadImage('assets/bricks.jpg');
        - * }
        - * function setup() {
        - *   let fragSrc = `precision highp float;
        - *
        - *   // x,y coordinates, given from the vertex shader
        - *   varying vec2 vTexCoord;
        - *
        - *   // the canvas contents, given from filter()
        - *   uniform sampler2D tex0;
        - *   // other useful information from the canvas
        - *   uniform vec2 texelSize;
        - *   uniform vec2 canvasSize;
        - *   // a custom variable from this sketch
        - *   uniform float darkness;
        - *
        - *   void main() {
        - *     // get the color at current pixel
        - *     vec4 color = texture2D(tex0, vTexCoord);
        - *     // set the output color
        - *     color.b = 1.0;
        - *     color *= darkness;
        - *     gl_FragColor = vec4(color.rgb, 1.0);
        - *   }`;
        - *
        - *   createCanvas(100, 100, WEBGL);
        - *   s = createFilterShader(fragSrc);
        - * }
        - * function draw() {
        - *   image(img, -50, -50);
        - *   s.setUniform('darkness', 0.5);
        - *   filter(s);
        - *   describe('a image of bricks tinted dark blue');
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createFilterShader = function (fragSrc) {
        -  p5._validateParameters('createFilterShader', arguments);
        -  let defaultVertV1 = `
        -    uniform mat4 uModelViewMatrix;
        -    uniform mat4 uProjectionMatrix;
        -
        -    attribute vec3 aPosition;
        -    // texcoords only come from p5 to vertex shader
        -    // so pass texcoords on to the fragment shader in a varying variable
        -    attribute vec2 aTexCoord;
        -    varying vec2 vTexCoord;
        -
        -    void main() {
        -      // transferring texcoords for the frag shader
        -      vTexCoord = aTexCoord;
        -
        -      // copy position with a fourth coordinate for projection (1.0 is normal)
        -      vec4 positionVec4 = vec4(aPosition, 1.0);
        -
        -      // project to 3D space
        -      gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        -    }
        -  `;
        -  let defaultVertV2 = `#version 300 es
        -    uniform mat4 uModelViewMatrix;
        -    uniform mat4 uProjectionMatrix;
        +      void main() {
        +        // transferring texcoords for the frag shader
        +        vTexCoord = aTexCoord;
        +
        +        // copy position with a fourth coordinate for projection (1.0 is normal)
        +        vec4 positionVec4 = vec4(aPosition, 1.0);
         
        -    in vec3 aPosition;
        -    in vec2 aTexCoord;
        -    out vec2 vTexCoord;
        +        // project to 3D space
        +        gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        +      }
        +    `;
        +    let defaultVertV2 = `#version 300 es
        +      uniform mat4 uModelViewMatrix;
        +      uniform mat4 uProjectionMatrix;
         
        -    void main() {
        -      // transferring texcoords for the frag shader
        -      vTexCoord = aTexCoord;
        +      in vec3 aPosition;
        +      in vec2 aTexCoord;
        +      out vec2 vTexCoord;
         
        -      // copy position with a fourth coordinate for projection (1.0 is normal)
        -      vec4 positionVec4 = vec4(aPosition, 1.0);
        +      void main() {
        +        // transferring texcoords for the frag shader
        +        vTexCoord = aTexCoord;
         
        -      // project to 3D space
        -      gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        +        // copy position with a fourth coordinate for projection (1.0 is normal)
        +        vec4 positionVec4 = vec4(aPosition, 1.0);
        +
        +        // project to 3D space
        +        gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        +      }
        +    `;
        +    let vertSrc = fragSrc.includes('#version 300 es') ? defaultVertV2 : defaultVertV1;
        +    const shader = new Shader(this._renderer, vertSrc, fragSrc);
        +    if (!skipContextCheck) {
        +      if (this._renderer.GL) {
        +        shader.ensureCompiledOnContext(this._renderer);
        +      } else {
        +        shader.ensureCompiledOnContext(this);
        +      }
             }
        -  `;
        -  let vertSrc = fragSrc.includes('#version 300 es') ? defaultVertV2 : defaultVertV1;
        -  const shader = new p5.Shader(this._renderer, vertSrc, fragSrc);
        -  if (this._renderer.GL) {
        -    shader.ensureCompiledOnContext(this);
        -  } else {
        -    shader.ensureCompiledOnContext(this._renderer.getFilterGraphicsLayer());
        -  }
        -  return shader;
        -};
        +    return shader;
        +  };
         
        -/**
        - * Sets the <a href="#/p5.Shader">p5.Shader</a> object to apply while drawing.
        - *
        - * Shaders are programs that run on the graphics processing unit (GPU). They
        - * can process many pixels or vertices at the same time, making them fast for
        - * many graphics tasks. They’re written in a language called
        - * <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders" target="_blank">GLSL</a>
        - * and run along with the rest of the code in a sketch.
        - * <a href="#/p5.Shader">p5.Shader</a> objects can be created using the
        - * <a href="#/p5/createShader">createShader()</a> and
        - * <a href="#/p5/loadShader">loadShader()</a> functions.
        - *
        - * The parameter, `s`, is the <a href="#/p5.Shader">p5.Shader</a> object to
        - * apply. For example, calling `shader(myShader)` applies `myShader` to
        - * process each pixel on the canvas. The shader will be used for:
        - * - Fills when a texture is enabled if it includes a uniform `sampler2D`.
        - * - Fills when lights are enabled if it includes the attribute `aNormal`, or if it has any of the following uniforms: `uUseLighting`, `uAmbientLightCount`, `uDirectionalLightCount`, `uPointLightCount`, `uAmbientColor`, `uDirectionalDiffuseColors`, `uDirectionalSpecularColors`, `uPointLightLocation`, `uPointLightDiffuseColors`, `uPointLightSpecularColors`, `uLightingDirection`, or `uSpecular`.
        - * - Fills whenever there are no lights or textures.
        - * - Strokes if it includes the uniform `uStrokeWeight`.
        - *
        - * The source code from a <a href="#/p5.Shader">p5.Shader</a> object's
        - * fragment and vertex shaders will be compiled the first time it's passed to
        - * `shader()`. See
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/compileShader" target="_blank">MDN</a>
        - * for more information about compiling shaders.
        - *
        - * Calling <a href="#/p5/resetShader">resetShader()</a> restores a sketch’s
        - * default shaders.
        - *
        - * Note: Shaders can only be used in WebGL mode.
        - *
        - * @method shader
        - * @chainable
        - * @param {p5.Shader} s <a href="#/p5.Shader">p5.Shader</a> object
        - *                      to apply.
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * // Create a string with the vertex shader program.
        - * // The vertex shader is called for each vertex.
        - * let vertSrc = `
        - * precision highp float;
        - * uniform mat4 uModelViewMatrix;
        - * uniform mat4 uProjectionMatrix;
        - *
        - * attribute vec3 aPosition;
        - * attribute vec2 aTexCoord;
        - * varying vec2 vTexCoord;
        - *
        - * void main() {
        - *   vTexCoord = aTexCoord;
        - *   vec4 positionVec4 = vec4(aPosition, 1.0);
        - *   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        - * }
        - * `;
        - *
        - * // Create a string with the fragment shader program.
        - * // The fragment shader is called for each pixel.
        - * let fragSrc = `
        - * precision highp float;
        - *
        - * void main() {
        - *   // Set each pixel's RGBA value to yellow.
        - *   gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
        - * }
        - * `;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Shader object.
        - *   let shaderProgram = createShader(vertSrc, fragSrc);
        - *
        - *   // Apply the p5.Shader object.
        - *   shader(shaderProgram);
        - *
        - *   // Style the drawing surface.
        - *   noStroke();
        - *
        - *   // Add a plane as a drawing surface.
        - *   plane(100, 100);
        - *
        - *   describe('A yellow square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * let mandelbrot;
        - *
        - * // Load the shader and create a p5.Shader object.
        - * function preload() {
        - *   mandelbrot = loadShader('assets/shader.vert', 'assets/shader.frag');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Use the p5.Shader object.
        - *   shader(mandelbrot);
        - *
        - *   // Set the shader uniform p to an array.
        - *   mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
        - *
        - *   describe('A fractal image zooms in and out of focus.');
        - * }
        - *
        - * function draw() {
        - *   // Set the shader uniform r to a value that oscillates between 0 and 2.
        - *   mandelbrot.setUniform('r', sin(frameCount * 0.01) + 1);
        - *
        - *   // Add a quad as a display surface for the shader.
        - *   quad(-1, -1, 1, -1, 1, 1, -1, 1);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * let redGreen;
        - * let orangeBlue;
        - * let showRedGreen = false;
        - *
        - * // Load the shader and create two separate p5.Shader objects.
        - * function preload() {
        - *   redGreen = loadShader('assets/shader.vert', 'assets/shader-gradient.frag');
        - *   orangeBlue = loadShader('assets/shader.vert', 'assets/shader-gradient.frag');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Initialize the redGreen shader.
        - *   shader(redGreen);
        - *
        - *   // Set the redGreen shader's center and background color.
        - *   redGreen.setUniform('colorCenter', [1.0, 0.0, 0.0]);
        - *   redGreen.setUniform('colorBackground', [0.0, 1.0, 0.0]);
        - *
        - *   // Initialize the orangeBlue shader.
        - *   shader(orangeBlue);
        - *
        - *   // Set the orangeBlue shader's center and background color.
        - *   orangeBlue.setUniform('colorCenter', [1.0, 0.5, 0.0]);
        - *   orangeBlue.setUniform('colorBackground', [0.226, 0.0, 0.615]);
        - *
        - *   describe(
        - *     'The scene toggles between two circular gradients when the user double-clicks. An orange and blue gradient vertically, and red and green gradient moves horizontally.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   // Update the offset values for each shader.
        - *   // Move orangeBlue vertically.
        - *   // Move redGreen horizontally.
        - *   orangeBlue.setUniform('offset', [0, sin(frameCount * 0.01) + 1]);
        - *   redGreen.setUniform('offset', [sin(frameCount * 0.01), 1]);
        - *
        - *   if (showRedGreen === true) {
        - *     shader(redGreen);
        - *   } else {
        - *     shader(orangeBlue);
        - *   }
        - *
        - *   // Style the drawing surface.
        - *   noStroke();
        - *
        - *   // Add a quad as a drawing surface.
        - *   quad(-1, -1, 1, -1, 1, 1, -1, 1);
        - * }
        - *
        - * // Toggle between shaders when the user double-clicks.
        - * function doubleClicked() {
        - *   showRedGreen = !showRedGreen;
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.shader = function (s) {
        -  this._assert3d('shader');
        -  p5._validateParameters('shader', arguments);
        +  /**
        +   * Sets the <a href="#/p5.Shader">p5.Shader</a> object to apply while drawing.
        +   *
        +   * Shaders are programs that run on the graphics processing unit (GPU). They
        +   * can process many pixels or vertices at the same time, making them fast for
        +   * many graphics tasks. They’re written in a language called
        +   * <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders" target="_blank">GLSL</a>
        +   * and run along with the rest of the code in a sketch.
        +   * <a href="#/p5.Shader">p5.Shader</a> objects can be created using the
        +   * <a href="#/p5/createShader">createShader()</a> and
        +   * <a href="#/p5/loadShader">loadShader()</a> functions.
        +   *
        +   * The parameter, `s`, is the <a href="#/p5.Shader">p5.Shader</a> object to
        +   * apply. For example, calling `shader(myShader)` applies `myShader` to
        +   * process each pixel on the canvas. This only changes the fill (the inner part of shapes),
        +   * but does not affect the outlines (strokes) or any images drawn using the `image()` function.
        +   * The source code from a <a href="#/p5.Shader">p5.Shader</a> object's
        +   * fragment and vertex shaders will be compiled the first time it's passed to
        +   * `shader()`. See
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/compileShader" target="_blank">MDN</a>
        +   * for more information about compiling shaders.
        +   *
        +   * Calling <a href="#/p5/resetShader">resetShader()</a> restores a sketch’s
        +   * default shaders.
        +   *
        +   * Note: Shaders can only be used in WebGL mode.
        +   *
        +   * <div>
        +   * <p>
        +   *
        +   * If you want to apply shaders to strokes or images, use the following methods:
        +   * - **[strokeShader()](#/p5/strokeShader)**: Applies a shader to the stroke (outline) of shapes, allowing independent control over the stroke rendering using shaders.
        +   * - **[imageShader()](#/p5/imageShader)**: Applies a shader to images or textures, controlling how the shader modifies their appearance during rendering.
        +   *
        +   * </p>
        +   * </div>
        +   *
        +   *
        +   * @method shader
        +   * @chainable
        +   * @param {p5.Shader} s <a href="#/p5.Shader">p5.Shader</a> object
        +   *                      to apply.
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let fillShader;
        +   *
        +   * let vertSrc = `
        +   * precision highp float;
        +   * attribute vec3 aPosition;
        +   * uniform mat4 uModelViewMatrix;
        +   * uniform mat4 uProjectionMatrix;
        +   * varying vec3 vPosition;
        +   *
        +   * void main() {
        +   *   vPosition = aPosition;
        +   *   gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
        +   * }
        +   * `;
        +   *
        +   * let fragSrc = `
        +   * precision highp float;
        +   * uniform vec3 uLightDir;
        +   * varying vec3 vPosition;
        +   *
        +   * void main() {
        +   *   vec3 lightDir = normalize(uLightDir);
        +   *   float brightness = dot(lightDir, normalize(vPosition));
        +   *   brightness = clamp(brightness, 0.4, 1.0);
        +   *   vec3 color = vec3(0.3, 0.5, 1.0);
        +   *   color = color * brightness * 3.0;
        +   *   gl_FragColor = vec4(color, 1.0);
        +   * }
        +   * `;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *   fillShader = createShader(vertSrc, fragSrc);
        +   *   noStroke();
        +   *   describe('A rotating torus with simulated directional lighting.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(20, 20, 40);
        +   *   let lightDir = [0.5, 0.5, -1.0];
        +   *   fillShader.setUniform('uLightDir', lightDir);
        +   *   shader(fillShader);
        +   *   rotateY(frameCount * 0.02);
        +   *   rotateX(frameCount * 0.02);
        +   *   //lights();
        +   *   torus(25, 10, 30, 30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let fillShader;
        +   *
        +   * let vertSrc = `
        +   * precision highp float;
        +   * attribute vec3 aPosition;
        +   * uniform mat4 uProjectionMatrix;
        +   * uniform mat4 uModelViewMatrix;
        +   * varying vec3 vPosition;
        +   * void main() {
        +   *   vPosition = aPosition;
        +   *   gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
        +   * }
        +   * `;
        +   *
        +   * let fragSrc = `
        +   * precision highp float;
        +   * uniform vec3 uLightPos;
        +   * uniform vec3 uFillColor;
        +   * varying vec3 vPosition;
        +   * void main() {
        +   *   float brightness = dot(normalize(uLightPos), normalize(vPosition));
        +   *   brightness = clamp(brightness, 0.0, 1.0);
        +   *   vec3 color = uFillColor * brightness;
        +   *   gl_FragColor = vec4(color, 1.0);
        +   * }
        +   * `;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *   fillShader = createShader(vertSrc, fragSrc);
        +   *   shader(fillShader);
        +   *   noStroke();
        +   *   describe('A square affected by both fill color and lighting, with lights controlled by mouse.');
        +   * }
        +   *
        +   * function draw() {
        +   *   let lightPos = [(mouseX - width / 2) / width,
        +   *     (mouseY - height / 2) / height, 1.0];
        +   *   fillShader.setUniform('uLightPos', lightPos);
        +   *   let fillColor = [map(mouseX, 0, width, 0, 1),
        +   *     map(mouseY, 0, height, 0, 1), 0.5];
        +   *   fillShader.setUniform('uFillColor', fillColor);
        +   *   plane(100, 100);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   myShader = baseMaterialShader().modify({
        +   *     declarations: 'uniform float time;',
        +   *     'vec4 getFinalColor': `(vec4 color) {
        +   *       float r = 0.2 + 0.5 * abs(sin(time + 0.0));
        +   *       float g = 0.2 + 0.5 * abs(sin(time + 1.0));
        +   *       float b = 0.2 + 0.5 * abs(sin(time + 2.0));
        +   *       color.rgb = vec3(r, g, b);
        +   *       return color;
        +   *     }`
        +   *   });
        +   *
        +   *   noStroke();
        +   *   describe('A 3D cube with dynamically changing colors on a beige background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(245, 245, 220);
        +   *   shader(myShader);
        +   *   myShader.setUniform('time', millis() / 1000.0);
        +   *
        +   *   box(50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   */
        +  fn.shader = function (s) {
        +    this._assert3d('shader');
        +    // p5._validateParameters('shader', arguments);
         
        -  s.ensureCompiledOnContext(this);
        +    this._renderer.shader(s);
         
        -  if (s.isStrokeShader()) {
        -    this._renderer.userStrokeShader = s;
        -  } else {
        -    this._renderer.userFillShader = s;
        -    this._renderer._useNormalMaterial = false;
        -  }
        +    return this;
        +  };
         
        -  s.setDefaultUniforms();
        +  /**
        +   * Sets the <a href="#/p5.Shader">p5.Shader</a> object to apply for strokes.
        +   *
        +   * This method applies the given shader to strokes, allowing customization of
        +   * how lines and outlines are drawn in 3D space. The shader will be used for
        +   * strokes until <a href="#/p5/resetShader">resetShader()</a> is called or another
        +   * strokeShader is applied.
        +   *
        +   * The shader will be used for:
        +   * - Strokes only, regardless of whether the uniform `uStrokeWeight` is present.
        +   *
        +   * To further customize its behavior, refer to the various hooks provided by
        +   * the <a href="#/p5/baseStrokeShader">baseStrokeShader()</a> method, which allow
        +   * control over stroke weight, vertex positions, colors, and more.
        +   *
        +   * @method strokeShader
        +   * @chainable
        +   * @param {p5.Shader} s <a href="#/p5.Shader">p5.Shader</a> object
        +   *                      to apply for strokes.
        +   *
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let animatedStrokeShader;
        +   *
        +   * let vertSrc = `
        +   * precision mediump int;
        +   *
        +   * uniform mat4 uModelViewMatrix;
        +   * uniform mat4 uProjectionMatrix;
        +   * uniform float uStrokeWeight;
        +   *
        +   * uniform bool uUseLineColor;
        +   * uniform vec4 uMaterialColor;
        +   *
        +   * uniform vec4 uViewport;
        +   * uniform int uPerspective;
        +   * uniform int uStrokeJoin;
        +   *
        +   * attribute vec4 aPosition;
        +   * attribute vec3 aTangentIn;
        +   * attribute vec3 aTangentOut;
        +   * attribute float aSide;
        +   * attribute vec4 aVertexColor;
        +   *
        +   * void main() {
        +   *   vec4 posp = uModelViewMatrix * aPosition;
        +   *   vec4 posqIn = uModelViewMatrix * (aPosition + vec4(aTangentIn, 0));
        +   *   vec4 posqOut = uModelViewMatrix * (aPosition + vec4(aTangentOut, 0));
        +   *
        +   *   float facingCamera = pow(
        +   *     abs(normalize(posqIn-posp).z),
        +   *     0.25
        +   *   );
        +   *
        +   *   float scale = mix(1., 0.995, facingCamera);
        +   *
        +   *   posp.xyz = posp.xyz * scale;
        +   *   posqIn.xyz = posqIn.xyz * scale;
        +   *   posqOut.xyz = posqOut.xyz * scale;
        +   *
        +   *   vec4 p = uProjectionMatrix * posp;
        +   *   vec4 qIn = uProjectionMatrix * posqIn;
        +   *   vec4 qOut = uProjectionMatrix * posqOut;
        +   *
        +   *   vec2 tangentIn = normalize((qIn.xy*p.w - p.xy*qIn.w) * uViewport.zw);
        +   *   vec2 tangentOut = normalize((qOut.xy*p.w - p.xy*qOut.w) * uViewport.zw);
        +   *
        +   *   vec2 curPerspScale;
        +   *   if(uPerspective == 1) {
        +   *     curPerspScale = (uProjectionMatrix * vec4(1, sign(uProjectionMatrix[1][1]), 0, 0)).xy;
        +   *   } else {
        +   *     curPerspScale = p.w / (0.5 * uViewport.zw);
        +   *   }
        +   *
        +   *   vec2 offset;
        +   *   vec2 tangent = aTangentIn == vec3(0.) ? tangentOut : tangentIn;
        +   *   vec2 normal = vec2(-tangent.y, tangent.x);
        +   *   float normalOffset = sign(aSide);
        +   *   float tangentOffset = abs(aSide) - 1.;
        +   *   offset = (normal * normalOffset + tangent * tangentOffset) *
        +   *     uStrokeWeight * 0.5;
        +   *
        +   *   gl_Position.xy = p.xy + offset.xy * curPerspScale;
        +   *   gl_Position.zw = p.zw;
        +   * }
        +   * `;
        +   *
        +   * let fragSrc = `
        +   * precision mediump float;
        +   * uniform float uTime;
        +   *
        +   * void main() {
        +   *   float wave = sin(gl_FragCoord.x * 0.1 + uTime) * 0.5 + 0.5;
        +   *   gl_FragColor = vec4(wave, 0.5, 1.0, 1.0);  // Animated color based on time
        +   * }
        +   * `;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *   animatedStrokeShader = createShader(vertSrc, fragSrc);
        +   *   strokeShader(animatedStrokeShader);
        +   *   strokeWeight(4);
        +   *
        +   *   describe('A hollow cube rotating continuously with its stroke colors changing dynamically over time against a static gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   animatedStrokeShader.setUniform('uTime', millis() / 1000.0);
        +   *   background(250);
        +   *   rotateY(frameCount * 0.02);
        +   *   noFill();
        +   *   orbitControl();
        +   *   box(50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   *   myShader = baseStrokeShader().modify({
        +   *     'float random': `(vec2 p) {
        +   *       vec3 p3  = fract(vec3(p.xyx) * .1471);
        +   *       p3 += dot(p3, p3.yzx + 32.33);
        +   *       return fract((p3.x + p3.y) * p3.z);
        +   *     }`,
        +   *     'Inputs getPixelInputs': `(Inputs inputs) {
        +   *       // Modify alpha with dithering effect
        +   *       float a = inputs.color.a;
        +   *       inputs.color.a = 1.0;
        +   *       inputs.color *= random(inputs.position.xy) > a ? 0.0 : 1.0;
        +   *       return inputs;
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   strokeShader(myShader);
        +   *   strokeWeight(12);
        +   *   beginShape();
        +   *   for (let i = 0; i <= 50; i++) {
        +   *     stroke(
        +   *       map(i, 0, 50, 150, 255),
        +   *       100 + 155 * sin(i / 5),
        +   *       255 * map(i, 0, 50, 1, 0)
        +   *     );
        +   *     vertex(
        +   *       map(i, 0, 50, 1, -1) * width / 3,
        +   *       50 * cos(i / 10 + frameCount / 80)
        +   *     );
        +   *   }
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.strokeShader = function (s) {
        +    this._assert3d('strokeShader');
        +    // p5._validateParameters('strokeShader', arguments);
         
        -  return this;
        -};
        +    this._renderer.strokeShader(s);
         
        -/**
        - * Get the default shader used with lights, materials,
        - * and textures.
        - *
        - * You can call <a href="#/p5.Shader/modify">`baseMaterialShader().modify()`</a>
        - * and change any of the following hooks:
        - *
        - * <table>
        - * <tr><th>Hook</th><th>Description</th></tr>
        - * <tr><td>
        - *
        - * `void beforeVertex`
        - *
        - * </td><td>
        - *
        - * Called at the start of the vertex shader.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec3 getLocalPosition`
        - *
        - * </td><td>
        - *
        - * Update the position of vertices before transforms are applied. It takes in `vec3 position` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec3 getWorldPosition`
        - *
        - * </td><td>
        - *
        - * Update the position of vertices after transforms are applied. It takes in `vec3 position` and pust return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec3 getLocalNormal`
        - *
        - * </td><td>
        - *
        - * Update the normal before transforms are applied. It takes in `vec3 normal` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec3 getWorldNormal`
        - *
        - * </td><td>
        - *
        - * Update the normal after transforms are applied. It takes in `vec3 normal` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec2 getUV`
        - *
        - * </td><td>
        - *
        - * Update the texture coordinates. It takes in `vec2 uv` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec4 getVertexColor`
        - *
        - * </td><td>
        - *
        - * Update the color of each vertex. It takes in a `vec4 color` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `void afterVertex`
        - *
        - * </td><td>
        - *
        - * Called at the end of the vertex shader.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `void beforeFragment`
        - *
        - * </td><td>
        - *
        - * Called at the start of the fragment shader.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `Inputs getPixelInputs`
        - *
        - * </td><td>
        - *
        - * Update the per-pixel inputs of the material. It takes in an `Inputs` struct, which includes:
        - * - `vec3 normal`, the direction pointing out of the surface
        - * - `vec2 texCoord`, a vector where `x` and `y` are between 0 and 1 describing the spot on a texture the pixel is mapped to, as a fraction of the texture size
        - * - `vec3 ambientLight`, the ambient light color on the vertex
        - * - `vec4 color`, the base material color of the pixel
        - * - `vec3 ambientMaterial`, the color of the pixel when affected by ambient light
        - * - `vec3 specularMaterial`, the color of the pixel when reflecting specular highlights
        - * - `vec3 emissiveMaterial`, the light color emitted by the pixel
        - * - `float shininess`, a number representing how sharp specular reflections should be, from 1 to infinity
        - * - `float metalness`, a number representing how mirrorlike the material should be, between 0 and 1
        - * The struct can be modified and returned.
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec4 combineColors`
        - *
        - * </td><td>
        - *
        - * Take in a `ColorComponents` struct containing all the different components of light, and combining them into
        - * a single final color. The struct contains:
        - * - `vec3 baseColor`, the base color of the pixel
        - * - `float opacity`, the opacity between 0 and 1 that it should be drawn at
        - * - `vec3 ambientColor`, the color of the pixel when affected by ambient light
        - * - `vec3 specularColor`, the color of the pixel when affected by specular reflections
        - * - `vec3 diffuse`, the amount of diffused light hitting the pixel
        - * - `vec3 ambient`, the amount of ambient light hitting the pixel
        - * - `vec3 specular`, the amount of specular reflection hitting the pixel
        - * - `vec3 emissive`, the amount of light emitted by the pixel
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec4 getFinalColor`
        - *
        - * </td><td>
        - *
        - * Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `void afterFragment`
        - *
        - * </td><td>
        - *
        - * Called at the end of the fragment shader.
        - *
        - * </td></tr>
        - * </table>
        - *
        - * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
        - * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
        - *
        - * Call `baseMaterialShader().inspectHooks()` to see all the possible hooks and
        - * their default implementations.
        - *
        - * @method baseMaterialShader
        - * @beta
        - * @returns {p5.Shader} The material shader
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * let myShader;
        - *
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - *   myShader = baseMaterialShader().modify({
        - *     uniforms: {
        - *       'float time': () => millis()
        - *     },
        - *     'vec3 getWorldPosition': `(vec3 pos) {
        - *       pos.y += 20.0 * sin(time * 0.001 + pos.x * 0.05);
        - *       return pos;
        - *     }`
        - *   });
        - * }
        - *
        - * function draw() {
        - *   background(255);
        - *   shader(myShader);
        - *   lights();
        - *   noStroke();
        - *   fill('red');
        - *   sphere(50);
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * let myShader;
        - *
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - *   myShader = baseMaterialShader().modify({
        - *     declarations: 'vec3 myNormal;',
        - *     'Inputs getPixelInputs': `(Inputs inputs) {
        - *       myNormal = inputs.normal;
        - *       return inputs;
        - *     }`,
        - *     'vec4 getFinalColor': `(vec4 color) {
        - *       return mix(
        - *         vec4(1.0, 1.0, 1.0, 1.0),
        - *         color,
        - *         abs(dot(myNormal, vec3(0.0, 0.0, 1.0)))
        - *       );
        - *     }`
        - *   });
        - * }
        - *
        - * function draw() {
        - *   background(255);
        - *   rotateY(millis() * 0.001);
        - *   shader(myShader);
        - *   lights();
        - *   noStroke();
        - *   fill('red');
        - *   torus(30);
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * let myShader;
        - * let environment;
        - *
        - * function preload() {
        - *   environment = loadImage('assets/outdoor_spheremap.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - *   myShader = baseMaterialShader().modify({
        - *     'Inputs getPixelInputs': `(Inputs inputs) {
        - *       float factor =
        - *         sin(
        - *           inputs.texCoord.x * ${TWO_PI} +
        - *           inputs.texCoord.y * ${TWO_PI}
        - *         ) * 0.4 + 0.5;
        - *       inputs.shininess = mix(1., 100., factor);
        - *       inputs.metalness = factor;
        - *       return inputs;
        - *     }`
        - *   });
        - * }
        - *
        - * function draw() {
        - *   panorama(environment);
        - *   ambientLight(100);
        - *   imageLight(environment);
        - *   rotateY(millis() * 0.001);
        - *   shader(myShader);
        - *   noStroke();
        - *   fill(255);
        - *   specularMaterial(150);
        - *   sphere(50);
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * let myShader;
        - *
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - *   myShader = baseMaterialShader().modify({
        - *     'Inputs getPixelInputs': `(Inputs inputs) {
        - *       vec3 newNormal = inputs.normal;
        - *       // Simple bump mapping: adjust the normal based on position
        - *       newNormal.x += 0.2 * sin(
        - *           sin(
        - *             inputs.texCoord.y * ${TWO_PI} * 10.0 +
        - *             inputs.texCoord.x * ${TWO_PI} * 25.0
        - *           )
        - *         );
        - *       newNormal.y += 0.2 * sin(
        - *         sin(
        - *             inputs.texCoord.x * ${TWO_PI} * 10.0 +
        - *             inputs.texCoord.y * ${TWO_PI} * 25.0
        - *           )
        - *       );
        - *       inputs.normal = normalize(newNormal);
        - *       return inputs;
        - *     }`
        - *   });
        - * }
        - *
        - * function draw() {
        - *   background(255);
        - *   shader(myShader);
        - *   ambientLight(150);
        - *   pointLight(
        - *     255, 255, 255,
        - *     100*cos(frameCount*0.04), -50, 100*sin(frameCount*0.04)
        - *   );
        - *   noStroke();
        - *   fill('red');
        - *   shininess(200);
        - *   specularMaterial(255);
        - *   sphere(50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.baseMaterialShader = function() {
        -  this._assert3d('baseMaterialShader');
        -  return this._renderer.baseMaterialShader();
        -};
        +    return this;
        +  };
         
        -/**
        - * Get the shader used by <a href="#/p5/normalMaterial">`normalMaterial()`</a>.
        - *
        - * You can call <a href="#/p5.Shader/modify">`baseNormalShader().modify()`</a>
        - * and change any of the following hooks:
        - *
        - * Hook | Description
        - * -----|------------
        - * `void beforeVertex` | Called at the start of the vertex shader.
        - * `vec3 getLocalPosition` | Update the position of vertices before transforms are applied. It takes in `vec3 position` and must return a modified version.
        - * `vec3 getWorldPosition` | Update the position of vertices after transforms are applied. It takes in `vec3 position` and pust return a modified version.
        - * `vec3 getLocalNormal` | Update the normal before transforms are applied. It takes in `vec3 normal` and must return a modified version.
        - * `vec3 getWorldNormal` | Update the normal after transforms are applied. It takes in `vec3 normal` and must return a modified version.
        - * `vec2 getUV` | Update the texture coordinates. It takes in `vec2 uv` and must return a modified version.
        - * `vec4 getVertexColor` | Update the color of each vertex. It takes in a `vec4 color` and must return a modified version.
        - * `void afterVertex` | Called at the end of the vertex shader.
        - * `void beforeFragment` | Called at the start of the fragment shader.
        - * `vec4 getFinalColor` | Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
        - * `void afterFragment` | Called at the end of the fragment shader.
        - *
        - * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
        - * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
        - *
        - * Call `baseNormalShader().inspectHooks()` to see all the possible hooks and
        - * their default implementations.
        - *
        - * @method baseNormalShader
        - * @beta
        - * @returns {p5.Shader} The `normalMaterial` shader
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * let myShader;
        - *
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - *   myShader = baseNormalShader().modify({
        - *     uniforms: {
        - *       'float time': () => millis()
        - *     },
        - *     'vec3 getWorldPosition': `(vec3 pos) {
        - *       pos.y += 20. * sin(time * 0.001 + pos.x * 0.05);
        - *       return pos;
        - *     }`
        - *   });
        - * }
        - *
        - * function draw() {
        - *   background(255);
        - *   shader(myShader);
        - *   noStroke();
        - *   sphere(50);
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * let myShader;
        - *
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - *   myShader = baseNormalShader().modify({
        - *     'vec3 getWorldNormal': '(vec3 normal) { return abs(normal); }',
        - *     'vec4 getFinalColor': `(vec4 color) {
        - *       // Map the r, g, and b values of the old normal to new colors
        - *       // instead of just red, green, and blue:
        - *       vec3 newColor =
        - *         color.r * vec3(89.0, 240.0, 232.0) / 255.0 +
        - *         color.g * vec3(240.0, 237.0, 89.0) / 255.0 +
        - *         color.b * vec3(205.0, 55.0, 222.0) / 255.0;
        - *       newColor = newColor / (color.r + color.g + color.b);
        - *       return vec4(newColor, 1.0) * color.a;
        - *     }`
        - *   });
        - * }
        - *
        - * function draw() {
        - *   background(255);
        - *   shader(myShader);
        - *   noStroke();
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.015);
        - *   box(100);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.baseNormalShader = function() {
        -  this._assert3d('baseNormalShader');
        -  return this._renderer.baseNormalShader();
        -};
        +  /**
        +   * Sets the <a href="#/p5.Shader">p5.Shader</a> object to apply for images.
        +   *
        +   * This method allows the user to apply a custom shader to images, enabling
        +   * advanced visual effects such as pixel manipulation, color adjustments,
        +   * or dynamic behavior. The shader will be applied to the image drawn using
        +   * the <a href="#/p5/image">image()</a> function.
        +   *
        +   * The shader will be used exclusively for:
        +   * - `image()` calls, applying only when drawing 2D images.
        +   * - This shader will NOT apply to images used in <a href="#/p5/texture">texture()</a> or other 3D contexts.
        +   *   Any attempts to use the imageShader in these cases will be ignored.
        +   *
        +   * @method imageShader
        +   * @chainable
        +   * @param {p5.Shader} s <a href="#/p5.Shader">p5.Shader</a> object
        +   *                      to apply for images.
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let img;
        +   * let imgShader;
        +   *
        +   * function preload() {
        +   *   img = loadImage('assets/outdoor_image.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *   noStroke();
        +   *
        +   *   imgShader = createShader(`
        +   *     precision mediump float;
        +   *     attribute vec3 aPosition;
        +   *     attribute vec2 aTexCoord;
        +   *     varying vec2 vTexCoord;
        +   *     uniform mat4 uModelViewMatrix;
        +   *     uniform mat4 uProjectionMatrix;
        +   *
        +   *     void main() {
        +   *       vTexCoord = aTexCoord;
        +   *       gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
        +   *     }
        +   *   `, `
        +   *     precision mediump float;
        +   *     varying vec2 vTexCoord;
        +   *     uniform sampler2D uTexture;
        +   *     uniform vec2 uMousePos;
        +   *
        +   *     void main() {
        +   *       vec4 texColor = texture2D(uTexture, vTexCoord);
        +   *       // Adjust the color based on mouse position
        +   *       float r = uMousePos.x * texColor.r;
        +   *       float g = uMousePos.y * texColor.g;
        +   *       gl_FragColor = vec4(r, g, texColor.b, texColor.a);
        +   *     }
        +   *   `);
        +   *
        +   *   describe(
        +   *     'An image on a gray background where the colors change based on the mouse position.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(220);
        +   *
        +   *   imageShader(imgShader);
        +   *
        +   *   // Map the mouse position to a range between 0 and 1
        +   *   let mousePosX = map(mouseX, 0, width, 0, 1);
        +   *   let mousePosY = map(mouseY, 0, height, 0, 1);
        +   *
        +   *   // Pass the mouse position to the shader as a uniform
        +   *   imgShader.setUniform('uMousePos', [mousePosX, mousePosY]);
        +   *
        +   *   // Bind the image texture to the shader
        +   *   imgShader.setUniform('uTexture', img);
        +   *
        +   *   image(img, -width / 2, -height / 2, width, height);
        +   * }
        +   *
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let img;
        +   * let imgShader;
        +   *
        +   * function preload() {
        +   *   img = loadImage('assets/outdoor_image.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *   noStroke();
        +   *
        +   *   imgShader = createShader(`
        +   *     precision mediump float;
        +   *     attribute vec3 aPosition;
        +   *     attribute vec2 aTexCoord;
        +   *     varying vec2 vTexCoord;
        +   *     uniform mat4 uModelViewMatrix;
        +   *     uniform mat4 uProjectionMatrix;
        +   *
        +   *     void main() {
        +   *       vTexCoord = aTexCoord;
        +   *       gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
        +   *     }
        +   *   `, `
        +   *     precision mediump float;
        +   *     varying vec2 vTexCoord;
        +   *     uniform sampler2D uTexture;
        +   *     uniform vec2 uMousePos;
        +   *
        +   *     void main() {
        +   *       // Distance from the current pixel to the mouse
        +   *       float distFromMouse = distance(vTexCoord, uMousePos);
        +   *
        +   *       // Adjust pixelation based on distance (closer = more detail, farther = blockier)
        +   *       float pixelSize = mix(0.002, 0.05, distFromMouse);
        +   *       vec2 pixelatedCoord = vec2(floor(vTexCoord.x / pixelSize) * pixelSize,
        +   *                                  floor(vTexCoord.y / pixelSize) * pixelSize);
        +   *
        +   *       vec4 texColor = texture2D(uTexture, pixelatedCoord);
        +   *       gl_FragColor = texColor;
        +   *     }
        +   *   `);
        +   *
        +   *   describe('A static image with a grid-like, pixelated effect created by the shader. Each cell in the grid alternates visibility, producing a dithered visual effect.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(220);
        +   *   imageShader(imgShader);
        +   *
        +   *   let mousePosX = map(mouseX, 0, width, 0, 1);
        +   *   let mousePosY = map(mouseY, 0, height, 0, 1);
        +   *
        +   *   imgShader.setUniform('uMousePos', [mousePosX, mousePosY]);
        +   *   imgShader.setUniform('uTexture', img);
        +   *   image(img, -width / 2, -height / 2, width, height);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.imageShader = function (s) {
        +    this._assert3d('imageShader');
        +    // p5._validateParameters('imageShader', arguments);
         
        -/**
        - * Get the shader used when no lights or materials are applied.
        - *
        - * You can call <a href="#/p5.Shader/modify">`baseColorShader().modify()`</a>
        - * and change any of the following hooks:
        - *
        - * Hook | Description
        - * -------|-------------
        - * `void beforeVertex` | Called at the start of the vertex shader.
        - * `vec3 getLocalPosition` | Update the position of vertices before transforms are applied. It takes in `vec3 position` and must return a modified version.
        - * `vec3 getWorldPosition` | Update the position of vertices after transforms are applied. It takes in `vec3 position` and pust return a modified version.
        - * `vec3 getLocalNormal` | Update the normal before transforms are applied. It takes in `vec3 normal` and must return a modified version.
        - * `vec3 getWorldNormal` | Update the normal after transforms are applied. It takes in `vec3 normal` and must return a modified version.
        - * `vec2 getUV` | Update the texture coordinates. It takes in `vec2 uv` and must return a modified version.
        - * `vec4 getVertexColor` | Update the color of each vertex. It takes in a `vec4 color` and must return a modified version.
        - * `void afterVertex` | Called at the end of the vertex shader.
        - * `void beforeFragment` | Called at the start of the fragment shader.
        - * `vec4 getFinalColor` | Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
        - * `void afterFragment` | Called at the end of the fragment shader.
        - *
        - * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
        - * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
        - *
        - * Call `baseColorShader().inspectHooks()` to see all the possible hooks and
        - * their default implementations.
        - *
        - * @method baseColorShader
        - * @beta
        - * @returns {p5.Shader} The color shader
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * let myShader;
        - *
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - *   myShader = baseColorShader().modify({
        - *     uniforms: {
        - *       'float time': () => millis()
        - *     },
        - *     'vec3 getWorldPosition': `(vec3 pos) {
        - *       pos.y += 20. * sin(time * 0.001 + pos.x * 0.05);
        - *       return pos;
        - *     }`
        - *   });
        - * }
        - *
        - * function draw() {
        - *   background(255);
        - *   shader(myShader);
        - *   noStroke();
        - *   fill('red');
        - *   circle(0, 0, 50);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.baseColorShader = function() {
        -  this._assert3d('baseColorShader');
        -  return this._renderer.baseColorShader();
        -};
        +    this._renderer.imageShader(s);
         
        -/**
        - * Get the shader used when drawing the strokes of shapes.
        - *
        - * You can call <a href="#/p5.Shader/modify">`baseStrokeShader().modify()`</a>
        - * and change any of the following hooks:
        - *
        - * <table>
        - * <tr><th>Hook</th><th>Description</th></tr>
        - * <tr><td>
        - *
        - * `void beforeVertex`
        - *
        - * </td><td>
        - *
        - * Called at the start of the vertex shader.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec3 getLocalPosition`
        - *
        - * </td><td>
        - *
        - * Update the position of vertices before transforms are applied. It takes in `vec3 position` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec3 getWorldPosition`
        - *
        - * </td><td>
        - *
        - * Update the position of vertices after transforms are applied. It takes in `vec3 position` and pust return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `float getStrokeWeight`
        - *
        - * </td><td>
        - *
        - * Update the stroke weight. It takes in `float weight` and pust return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec2 getLineCenter`
        - *
        - * </td><td>
        - *
        - * Update the center of the line. It takes in `vec2 center` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec2 getLinePosition`
        - *
        - * </td><td>
        - *
        - * Update the position of each vertex on the edge of the line. It takes in `vec2 position` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec4 getVertexColor`
        - *
        - * </td><td>
        - *
        - * Update the color of each vertex. It takes in a `vec4 color` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `void afterVertex`
        - *
        - * </td><td>
        - *
        - * Called at the end of the vertex shader.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `void beforeFragment`
        - *
        - * </td><td>
        - *
        - * Called at the start of the fragment shader.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `Inputs getPixelInputs`
        - *
        - * </td><td>
        - *
        - * Update the inputs to the shader. It takes in a struct `Inputs inputs`, which includes:
        - * - `vec4 color`, the color of the stroke
        - * - `vec2 tangent`, the direction of the stroke in screen space
        - * - `vec2 center`, the coordinate of the center of the stroke in screen space p5.js pixels
        - * - `vec2 position`, the coordinate of the current pixel in screen space p5.js pixels
        - * - `float strokeWeight`, the thickness of the stroke in p5.js pixels
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `bool shouldDiscard`
        - *
        - * </td><td>
        - *
        - * Caps and joins are made by discarded pixels in the fragment shader to carve away unwanted areas. Use this to change this logic. It takes in a `bool willDiscard` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `vec4 getFinalColor`
        - *
        - * </td><td>
        - *
        - * Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
        - *
        - * </td></tr>
        - * <tr><td>
        - *
        - * `void afterFragment`
        - *
        - * </td><td>
        - *
        - * Called at the end of the fragment shader.
        - *
        - * </td></tr>
        - * </table>
        - *
        - * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
        - * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
        - *
        - * Call `baseStrokeShader().inspectHooks()` to see all the possible hooks and
        - * their default implementations.
        - *
        - * @method baseStrokeShader
        - * @beta
        - * @returns {p5.Shader} The stroke shader
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * let myShader;
        - *
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - *   myShader = baseStrokeShader().modify({
        - *     'Inputs getPixelInputs': `(Inputs inputs) {
        - *       float opacity = 1.0 - smoothstep(
        - *         0.0,
        - *         15.0,
        - *         length(inputs.position - inputs.center)
        - *       );
        - *       inputs.color *= opacity;
        - *       return inputs;
        - *     }`
        - *   });
        - * }
        - *
        - * function draw() {
        - *   background(255);
        - *   shader(myShader);
        - *   strokeWeight(30);
        - *   line(
        - *     -width/3,
        - *     sin(millis()*0.001) * height/4,
        - *     width/3,
        - *     sin(millis()*0.001 + 1) * height/4
        - *   );
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * let myShader;
        - *
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - *   myShader = baseStrokeShader().modify({
        - *     uniforms: {
        - *       'float time': () => millis()
        - *     },
        - *     declarations: 'vec3 myPosition;',
        - *     'vec3 getWorldPosition': `(vec3 pos) {
        - *       myPosition = pos;
        - *       return pos;
        - *     }`,
        - *     'float getStrokeWeight': `(float w) {
        - *       // Add a somewhat random offset to the weight
        - *       // that varies based on position and time
        - *       float scale = 0.8 + 0.2*sin(10.0 * sin(
        - *         floor(time/250.) +
        - *         myPosition.x*0.01 +
        - *         myPosition.y*0.01
        - *       ));
        - *       return w * scale;
        - *     }`
        - *   });
        - * }
        - *
        - * function draw() {
        - *   background(255);
        - *   shader(myShader);
        - *   myShader.setUniform('time', millis());
        - *   strokeWeight(10);
        - *   beginShape();
        - *   for (let i = 0; i <= 50; i++) {
        - *     let r = map(i, 0, 50, 0, width/3);
        - *     let x = r*cos(i*0.2);
        - *     let y = r*sin(i*0.2);
        - *     vertex(x, y);
        - *   }
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - *
        - * @example
        - * <div modernizr='webgl'>
        - * <code>
        - * let myShader;
        - *
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - *   myShader = baseStrokeShader().modify({
        - *     'float random': `(vec2 p) {
        - *       vec3 p3  = fract(vec3(p.xyx) * .1031);
        - *       p3 += dot(p3, p3.yzx + 33.33);
        - *       return fract((p3.x + p3.y) * p3.z);
        - *     }`,
        - *     'Inputs getPixelInputs': `(Inputs inputs) {
        - *       // Replace alpha in the color with dithering by
        - *       // randomly setting pixel colors to 0 based on opacity
        - *       float a = inputs.color.a;
        - *       inputs.color.a = 1.0;
        - *       inputs.color *= random(inputs.position.xy) > a ? 0.0 : 1.0;
        - *       return inputs;
        - *     }`
        - *   });
        - * }
        - *
        - * function draw() {
        - *   background(255);
        - *   shader(myShader);
        - *   strokeWeight(10);
        - *   beginShape();
        - *   for (let i = 0; i <= 50; i++) {
        - *     stroke(
        - *       0,
        - *       255
        - *         * map(i, 0, 20, 0, 1, true)
        - *         * map(i, 30, 50, 1, 0, true)
        - *     );
        - *     vertex(
        - *       map(i, 0, 50, -1, 1) * width/3,
        - *       50 * sin(i/10 + frameCount/100)
        - *     );
        - *   }
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.baseStrokeShader = function() {
        -  this._assert3d('baseStrokeShader');
        -  return this._renderer.baseStrokeShader();
        -};
        +    return this;
        +  };
         
        -/**
        - * Restores the default shaders.
        - *
        - * `resetShader()` deactivates any shaders previously applied by
        - * <a href="#/p5/shader">shader()</a>.
        - *
        - * Note: Shaders can only be used in WebGL mode.
        - *
        - * @method resetShader
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Create a string with the vertex shader program.
        - * // The vertex shader is called for each vertex.
        - * let vertSrc = `
        - * attribute vec3 aPosition;
        - * attribute vec2 aTexCoord;
        - * uniform mat4 uProjectionMatrix;
        - * uniform mat4 uModelViewMatrix;
        - * varying vec2 vTexCoord;
        - *
        - * void main() {
        - *   vTexCoord = aTexCoord;
        - *   vec4 position = vec4(aPosition, 1.0);
        - *   gl_Position = uProjectionMatrix * uModelViewMatrix * position;
        - * }
        - * `;
        - *
        - * // Create a string with the fragment shader program.
        - * // The fragment shader is called for each pixel.
        - * let fragSrc = `
        - * precision mediump float;
        - * varying vec2 vTexCoord;
        - *
        - * void main() {
        - *   vec2 uv = vTexCoord;
        - *   vec3 color = vec3(uv.x, uv.y, min(uv.x + uv.y, 1.0));
        - *   gl_FragColor = vec4(color, 1.0);
        - * }
        - * `;
        - *
        - * let myShader;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Shader object.
        - *   myShader = createShader(vertSrc, fragSrc);
        - *
        - *   describe(
        - *     'Two rotating cubes on a gray background. The left one has a blue-purple gradient on each face. The right one is red.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw a box using the p5.Shader.
        - *   // shader() sets the active shader to myShader.
        - *   shader(myShader);
        - *   push();
        - *   translate(-25, 0, 0);
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   box(width / 4);
        - *   pop();
        - *
        - *   // Draw a box using the default fill shader.
        - *   // resetShader() restores the default fill shader.
        - *   resetShader();
        - *   fill(255, 0, 0);
        - *   push();
        - *   translate(25, 0, 0);
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   box(width / 4);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.resetShader = function () {
        -  this._renderer.userFillShader = this._renderer.userStrokeShader = null;
        -  return this;
        -};
        +  /**
        +   * Get the default shader used with lights, materials,
        +   * and textures.
        +   *
        +   * You can call <a href="#/p5.Shader/modify">`baseMaterialShader().modify()`</a>
        +   * and change any of the following hooks:
        +   *
        +   * <table>
        +   * <tr><th>Hook</th><th>Description</th></tr>
        +   * <tr><td>
        +   *
        +   * `void beforeVertex`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the start of the vertex shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `Vertex getObjectInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the model being drawn before any positioning has been applied. It takes in a `Vertex` struct, which includes:
        +   * - `vec3 position`, the position of the vertex
        +   * - `vec3 normal`, the direction facing out of the surface
        +   * - `vec2 uv`, the texture coordinates associeted with the vertex
        +   * - `vec4 color`, the per-vertex color
        +   * The struct can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `Vertex getWorldInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the model being drawn after transformations such as `translate()` and `scale()` have been applied, but before the camera has been applied. It takes in a `Vertex` struct like, in the `getObjectInputs` hook above, that can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `Vertex getCameraInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the model being drawn as they appear relative to the camera. It takes in a `Vertex` struct like, in the `getObjectInputs` hook above, that can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void afterVertex`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the end of the vertex shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void beforeFragment`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the start of the fragment shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `Inputs getPixelInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the per-pixel inputs of the material. It takes in an `Inputs` struct, which includes:
        +   * - `vec3 normal`, the direction pointing out of the surface
        +   * - `vec2 texCoord`, a vector where `x` and `y` are between 0 and 1 describing the spot on a texture the pixel is mapped to, as a fraction of the texture size
        +   * - `vec3 ambientLight`, the ambient light color on the vertex
        +   * - `vec4 color`, the base material color of the pixel
        +   * - `vec3 ambientMaterial`, the color of the pixel when affected by ambient light
        +   * - `vec3 specularMaterial`, the color of the pixel when reflecting specular highlights
        +   * - `vec3 emissiveMaterial`, the light color emitted by the pixel
        +   * - `float shininess`, a number representing how sharp specular reflections should be, from 1 to infinity
        +   * - `float metalness`, a number representing how mirrorlike the material should be, between 0 and 1
        +   * The struct can be modified and returned.
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `vec4 combineColors`
        +   *
        +   * </td><td>
        +   *
        +   * Take in a `ColorComponents` struct containing all the different components of light, and combining them into
        +   * a single final color. The struct contains:
        +   * - `vec3 baseColor`, the base color of the pixel
        +   * - `float opacity`, the opacity between 0 and 1 that it should be drawn at
        +   * - `vec3 ambientColor`, the color of the pixel when affected by ambient light
        +   * - `vec3 specularColor`, the color of the pixel when affected by specular reflections
        +   * - `vec3 diffuse`, the amount of diffused light hitting the pixel
        +   * - `vec3 ambient`, the amount of ambient light hitting the pixel
        +   * - `vec3 specular`, the amount of specular reflection hitting the pixel
        +   * - `vec3 emissive`, the amount of light emitted by the pixel
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `vec4 getFinalColor`
        +   *
        +   * </td><td>
        +   *
        +   * Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void afterFragment`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the end of the fragment shader.
        +   *
        +   * </td></tr>
        +   * </table>
        +   *
        +   * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
        +   * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
        +   *
        +   * Call `baseMaterialShader().inspectHooks()` to see all the possible hooks and
        +   * their default implementations.
        +   *
        +   * @method baseMaterialShader
        +   * @beta
        +   * @returns {p5.Shader} The material shader
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   *   myShader = baseMaterialShader().modify({
        +   *     uniforms: {
        +   *       'float time': () => millis()
        +   *     },
        +   *     'Vertex getWorldInputs': `(Vertex inputs) {
        +   *       inputs.position.y +=
        +   *         20.0 * sin(time * 0.001 + inputs.position.x * 0.05);
        +   *       return inputs;
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   shader(myShader);
        +   *   lights();
        +   *   noStroke();
        +   *   fill('red');
        +   *   sphere(50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   *   myShader = baseMaterialShader().modify({
        +   *     declarations: 'vec3 myNormal;',
        +   *     'Inputs getPixelInputs': `(Inputs inputs) {
        +   *       myNormal = inputs.normal;
        +   *       return inputs;
        +   *     }`,
        +   *     'vec4 getFinalColor': `(vec4 color) {
        +   *       return mix(
        +   *         vec4(1.0, 1.0, 1.0, 1.0),
        +   *         color,
        +   *         abs(dot(myNormal, vec3(0.0, 0.0, 1.0)))
        +   *       );
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   rotateY(millis() * 0.001);
        +   *   shader(myShader);
        +   *   lights();
        +   *   noStroke();
        +   *   fill('red');
        +   *   torus(30);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   * let environment;
        +   *
        +   * function preload() {
        +   *   environment = loadImage('assets/outdoor_spheremap.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   *   myShader = baseMaterialShader().modify({
        +   *     'Inputs getPixelInputs': `(Inputs inputs) {
        +   *       float factor =
        +   *         sin(
        +   *           inputs.texCoord.x * ${TWO_PI} +
        +   *           inputs.texCoord.y * ${TWO_PI}
        +   *         ) * 0.4 + 0.5;
        +   *       inputs.shininess = mix(1., 100., factor);
        +   *       inputs.metalness = factor;
        +   *       return inputs;
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   panorama(environment);
        +   *   ambientLight(100);
        +   *   imageLight(environment);
        +   *   rotateY(millis() * 0.001);
        +   *   shader(myShader);
        +   *   noStroke();
        +   *   fill(255);
        +   *   specularMaterial(150);
        +   *   sphere(50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   *   myShader = baseMaterialShader().modify({
        +   *     'Inputs getPixelInputs': `(Inputs inputs) {
        +   *       vec3 newNormal = inputs.normal;
        +   *       // Simple bump mapping: adjust the normal based on position
        +   *       newNormal.x += 0.2 * sin(
        +   *           sin(
        +   *             inputs.texCoord.y * ${TWO_PI} * 10.0 +
        +   *             inputs.texCoord.x * ${TWO_PI} * 25.0
        +   *           )
        +   *         );
        +   *       newNormal.y += 0.2 * sin(
        +   *         sin(
        +   *             inputs.texCoord.x * ${TWO_PI} * 10.0 +
        +   *             inputs.texCoord.y * ${TWO_PI} * 25.0
        +   *           )
        +   *       );
        +   *       inputs.normal = normalize(newNormal);
        +   *       return inputs;
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   shader(myShader);
        +   *   ambientLight(150);
        +   *   pointLight(
        +   *     255, 255, 255,
        +   *     100*cos(frameCount*0.04), -50, 100*sin(frameCount*0.04)
        +   *   );
        +   *   noStroke();
        +   *   fill('red');
        +   *   shininess(200);
        +   *   specularMaterial(255);
        +   *   sphere(50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.baseMaterialShader = function() {
        +    this._assert3d('baseMaterialShader');
        +    return this._renderer.baseMaterialShader();
        +  };
         
        -/**
        - * Sets the texture that will be used on shapes.
        - *
        - * A texture is like a skin that wraps around a shape. `texture()` works with
        - * built-in shapes, such as <a href="#/p5/square">square()</a> and
        - * <a href="#/p5/sphere">sphere()</a>, and custom shapes created with
        - * functions such as <a href="#/p5/buildGeometry">buildGeometry()</a>. To
        - * texture a geometry created with <a href="#/p5/beginShape">beginShape()</a>,
        - * uv coordinates must be passed to each
        - * <a href="#/p5/vertex">vertex()</a> call.
        - *
        - * The parameter, `tex`, is the texture to apply. `texture()` can use a range
        - * of sources including images, videos, and offscreen renderers such as
        - * <a href="#/p5.Graphics">p5.Graphics</a> and
        - * <a href="#/p5.Framebuffer">p5.Framebuffer</a> objects.
        - *
        - * To texture a geometry created with <a href="#/p5/beginShape">beginShape()</a>,
        - * you will need to specify uv coordinates in <a href="#/p5/vertex">vertex()</a>.
        - *
        - * Note: `texture()` can only be used in WebGL mode.
        - *
        - * @method texture
        - * @param {p5.Image|p5.MediaElement|p5.Graphics|p5.Texture|p5.Framebuffer|p5.FramebufferTexture} tex media to use as the texture.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load an image and create a p5.Image object.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A spinning cube with an image of a ceiling on each face.');
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Rotate around the x-, y-, and z-axes.
        - *   rotateZ(frameCount * 0.01);
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Apply the image as a texture.
        - *   texture(img);
        - *
        - *   // Draw the box.
        - *   box(50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let pg;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Graphics object.
        - *   pg = createGraphics(100, 100);
        - *
        - *   // Draw a circle to the p5.Graphics object.
        - *   pg.background(200);
        - *   pg.circle(50, 50, 30);
        - *
        - *   describe('A spinning cube with circle at the center of each face.');
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Rotate around the x-, y-, and z-axes.
        - *   rotateZ(frameCount * 0.01);
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Apply the p5.Graphics object as a texture.
        - *   texture(pg);
        - *
        - *   // Draw the box.
        - *   box(50);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let vid;
        - *
        - * // Load a video and create a p5.MediaElement object.
        - * function preload() {
        - *   vid = createVideo('assets/fingers.mov');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Hide the video.
        - *   vid.hide();
        - *
        - *   // Set the video to loop.
        - *   vid.loop();
        - *
        - *   describe('A rectangle with video as texture');
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Apply the video as a texture.
        - *   texture(vid);
        - *
        - *   // Draw the rectangle.
        - *   rect(-40, -40, 80, 80);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let vid;
        - *
        - * // Load a video and create a p5.MediaElement object.
        - * function preload() {
        - *   vid = createVideo('assets/fingers.mov');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Hide the video.
        - *   vid.hide();
        - *
        - *   // Set the video to loop.
        - *   vid.loop();
        - *
        - *   describe('A rectangle with video as texture');
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Set the texture mode.
        - *   textureMode(NORMAL);
        - *
        - *   // Apply the video as a texture.
        - *   texture(vid);
        - *
        - *   // Draw a custom shape using uv coordinates.
        - *   beginShape();
        - *   vertex(-40, -40, 0, 0);
        - *   vertex(40, -40, 1, 0);
        - *   vertex(40, 40, 1, 1);
        - *   vertex(-40, 40, 0, 1);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.texture = function (tex) {
        -  this._assert3d('texture');
        -  p5._validateParameters('texture', arguments);
        -  if (tex.gifProperties) {
        -    tex._animateGif(this);
        -  }
        +  /**
        +   * Get the shader used by <a href="#/p5/normalMaterial">`normalMaterial()`</a>.
        +   *
        +   * You can call <a href="#/p5.Shader/modify">`baseNormalShader().modify()`</a>
        +   * and change any of the following hooks:
        +   *
        +   * <table>
        +   * <tr><th>Hook</th><th>Description</th></tr>
        +   * <tr><td>
        +   *
        +   * `void beforeVertex`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the start of the vertex shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `Vertex getObjectInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the model being drawn before any positioning has been applied. It takes in a `Vertex` struct, which includes:
        +   * - `vec3 position`, the position of the vertex
        +   * - `vec3 normal`, the direction facing out of the surface
        +   * - `vec2 uv`, the texture coordinates associeted with the vertex
        +   * - `vec4 color`, the per-vertex color
        +   * The struct can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `Vertex getWorldInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the model being drawn after transformations such as `translate()` and `scale()` have been applied, but before the camera has been applied. It takes in a `Vertex` struct like, in the `getObjectInputs` hook above, that can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `Vertex getCameraInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the model being drawn as they appear relative to the camera. It takes in a `Vertex` struct like, in the `getObjectInputs` hook above, that can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void afterVertex`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the end of the vertex shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void beforeFragment`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the start of the fragment shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `vec4 getFinalColor`
        +   *
        +   * </td><td>
        +   *
        +   * Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void afterFragment`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the end of the fragment shader.
        +   *
        +   * </td></tr>
        +   * </table>
        +   *
        +   * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
        +   * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
        +   *
        +   * Call `baseNormalShader().inspectHooks()` to see all the possible hooks and
        +   * their default implementations.
        +   *
        +   * @method baseNormalShader
        +   * @beta
        +   * @returns {p5.Shader} The `normalMaterial` shader
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   *   myShader = baseNormalShader().modify({
        +   *     uniforms: {
        +   *       'float time': () => millis()
        +   *     },
        +   *     'Vertex getWorldInputs': `(Vertex inputs) {
        +   *       inputs.position.y +=
        +   *         20. * sin(time * 0.001 + inputs.position.x * 0.05);
        +   *       return inputs;
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   shader(myShader);
        +   *   noStroke();
        +   *   sphere(50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   *   myShader = baseNormalShader().modify({
        +   *     'Vertex getCameraInputs': `(Vertex inputs) {
        +   *       inputs.normal = abs(inputs.normal);
        +   *       return inputs;
        +   *     }`,
        +   *     'vec4 getFinalColor': `(vec4 color) {
        +   *       // Map the r, g, and b values of the old normal to new colors
        +   *       // instead of just red, green, and blue:
        +   *       vec3 newColor =
        +   *         color.r * vec3(89.0, 240.0, 232.0) / 255.0 +
        +   *         color.g * vec3(240.0, 237.0, 89.0) / 255.0 +
        +   *         color.b * vec3(205.0, 55.0, 222.0) / 255.0;
        +   *       newColor = newColor / (color.r + color.g + color.b);
        +   *       return vec4(newColor, 1.0) * color.a;
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   shader(myShader);
        +   *   noStroke();
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.015);
        +   *   box(100);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.baseNormalShader = function() {
        +    this._assert3d('baseNormalShader');
        +    return this._renderer.baseNormalShader();
        +  };
         
        -  this._renderer.drawMode = constants.TEXTURE;
        -  this._renderer._useNormalMaterial = false;
        -  this._renderer._tex = tex;
        -  this._renderer._setProperty('_doFill', true);
        +  /**
        +   * Get the shader used when no lights or materials are applied.
        +   *
        +   * You can call <a href="#/p5.Shader/modify">`baseColorShader().modify()`</a>
        +   * and change any of the following hooks:
        +   *
        +   * <table>
        +   * <tr><th>Hook</th><th>Description</th></tr>
        +   * <tr><td>
        +   *
        +   * `void beforeVertex`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the start of the vertex shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `Vertex getObjectInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the model being drawn before any positioning has been applied. It takes in a `Vertex` struct, which includes:
        +   * - `vec3 position`, the position of the vertex
        +   * - `vec3 normal`, the direction facing out of the surface
        +   * - `vec2 uv`, the texture coordinates associeted with the vertex
        +   * - `vec4 color`, the per-vertex color
        +   * The struct can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `Vertex getWorldInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the model being drawn after transformations such as `translate()` and `scale()` have been applied, but before the camera has been applied. It takes in a `Vertex` struct like, in the `getObjectInputs` hook above, that can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `Vertex getCameraInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the model being drawn as they appear relative to the camera. It takes in a `Vertex` struct like, in the `getObjectInputs` hook above, that can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void afterVertex`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the end of the vertex shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void beforeFragment`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the start of the fragment shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `vec4 getFinalColor`
        +   *
        +   * </td><td>
        +   *
        +   * Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void afterFragment`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the end of the fragment shader.
        +   *
        +   * </td></tr>
        +   * </table>
        +   *
        +   * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
        +   * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
        +   *
        +   * Call `baseColorShader().inspectHooks()` to see all the possible hooks and
        +   * their default implementations.
        +   *
        +   * @method baseColorShader
        +   * @beta
        +   * @returns {p5.Shader} The color shader
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   *   myShader = baseColorShader().modify({
        +   *     uniforms: {
        +   *       'float time': () => millis()
        +   *     },
        +   *     'Vertex getWorldInputs': `(Vertex inputs) {
        +   *       inputs.position.y +=
        +   *         20. * sin(time * 0.001 + inputs.position.x * 0.05);
        +   *       return inputs;
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   shader(myShader);
        +   *   noStroke();
        +   *   fill('red');
        +   *   circle(0, 0, 50);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.baseColorShader = function() {
        +    this._assert3d('baseColorShader');
        +    return this._renderer.baseColorShader();
        +  };
         
        -  return this;
        -};
        +  /**
        +   * Get the shader used when drawing the strokes of shapes.
        +   *
        +   * You can call <a href="#/p5.Shader/modify">`baseStrokeShader().modify()`</a>
        +   * and change any of the following hooks:
        +   *
        +   * <table>
        +   * <tr><th>Hook</th><th>Description</th></tr>
        +   * <tr><td>
        +   *
        +   * `void beforeVertex`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the start of the vertex shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `StrokeVertex getObjectInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the stroke being drawn before any positioning has been applied. It takes in a `StrokeVertex` struct, which includes:
        +   * - `vec3 position`, the position of the vertex
        +   * - `vec3 tangentIn`, the tangent coming in to the vertex
        +   * - `vec3 tangentOut`, the tangent coming out of the vertex. In straight segments, this will be the same as `tangentIn`. In joins, it will be different. In caps, one of the tangents will be 0.
        +   * - `vec4 color`, the per-vertex color
        +   * - `float weight`, the stroke weight
        +   * The struct can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `StrokeVertex getWorldInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the model being drawn after transformations such as `translate()` and `scale()` have been applied, but before the camera has been applied. It takes in a `StrokeVertex` struct like, in the `getObjectInputs` hook above, that can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `StrokeVertex getCameraInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the vertex data of the model being drawn as they appear relative to the camera. It takes in a `StrokeVertex` struct like, in the `getObjectInputs` hook above, that can be modified and returned.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void afterVertex`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the end of the vertex shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void beforeFragment`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the start of the fragment shader.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `Inputs getPixelInputs`
        +   *
        +   * </td><td>
        +   *
        +   * Update the inputs to the shader. It takes in a struct `Inputs inputs`, which includes:
        +   * - `vec4 color`, the color of the stroke
        +   * - `vec2 tangent`, the direction of the stroke in screen space
        +   * - `vec2 center`, the coordinate of the center of the stroke in screen space p5.js pixels
        +   * - `vec2 position`, the coordinate of the current pixel in screen space p5.js pixels
        +   * - `float strokeWeight`, the thickness of the stroke in p5.js pixels
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `bool shouldDiscard`
        +   *
        +   * </td><td>
        +   *
        +   * Caps and joins are made by discarded pixels in the fragment shader to carve away unwanted areas. Use this to change this logic. It takes in a `bool willDiscard` and must return a modified version.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `vec4 getFinalColor`
        +   *
        +   * </td><td>
        +   *
        +   * Update the final color after mixing. It takes in a `vec4 color` and must return a modified version.
        +   *
        +   * </td></tr>
        +   * <tr><td>
        +   *
        +   * `void afterFragment`
        +   *
        +   * </td><td>
        +   *
        +   * Called at the end of the fragment shader.
        +   *
        +   * </td></tr>
        +   * </table>
        +   *
        +   * Most of the time, you will need to write your hooks in GLSL ES version 300. If you
        +   * are using WebGL 1 instead of 2, write your hooks in GLSL ES 100 instead.
        +   *
        +   * Call `baseStrokeShader().inspectHooks()` to see all the possible hooks and
        +   * their default implementations.
        +   *
        +   * @method baseStrokeShader
        +   * @beta
        +   * @returns {p5.Shader} The stroke shader
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   *   myShader = baseStrokeShader().modify({
        +   *     'Inputs getPixelInputs': `(Inputs inputs) {
        +   *       float opacity = 1.0 - smoothstep(
        +   *         0.0,
        +   *         15.0,
        +   *         length(inputs.position - inputs.center)
        +   *       );
        +   *       inputs.color *= opacity;
        +   *       return inputs;
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   strokeShader(myShader);
        +   *   strokeWeight(30);
        +   *   line(
        +   *     -width/3,
        +   *     sin(millis()*0.001) * height/4,
        +   *     width/3,
        +   *     sin(millis()*0.001 + 1) * height/4
        +   *   );
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   *   myShader = baseStrokeShader().modify({
        +   *     uniforms: {
        +   *       'float time': () => millis()
        +   *     },
        +   *     'StrokeVertex getWorldInputs': `(StrokeVertex inputs) {
        +   *       // Add a somewhat random offset to the weight
        +   *       // that varies based on position and time
        +   *       float scale = 0.8 + 0.2*sin(10.0 * sin(
        +   *         floor(time/250.) +
        +   *         inputs.position.x*0.01 +
        +   *         inputs.position.y*0.01
        +   *       ));
        +   *       inputs.weight *= scale;
        +   *       return inputs;
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   strokeShader(myShader);
        +   *   myShader.setUniform('time', millis());
        +   *   strokeWeight(10);
        +   *   beginShape();
        +   *   for (let i = 0; i <= 50; i++) {
        +   *     let r = map(i, 0, 50, 0, width/3);
        +   *     let x = r*cos(i*0.2);
        +   *     let y = r*sin(i*0.2);
        +   *     vertex(x, y);
        +   *   }
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @example
        +   * <div modernizr='webgl'>
        +   * <code>
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   *   myShader = baseStrokeShader().modify({
        +   *     'float random': `(vec2 p) {
        +   *       vec3 p3  = fract(vec3(p.xyx) * .1031);
        +   *       p3 += dot(p3, p3.yzx + 33.33);
        +   *       return fract((p3.x + p3.y) * p3.z);
        +   *     }`,
        +   *     'Inputs getPixelInputs': `(Inputs inputs) {
        +   *       // Replace alpha in the color with dithering by
        +   *       // randomly setting pixel colors to 0 based on opacity
        +   *       float a = inputs.color.a;
        +   *       inputs.color.a = 1.0;
        +   *       inputs.color *= random(inputs.position.xy) > a ? 0.0 : 1.0;
        +   *       return inputs;
        +   *     }`
        +   *   });
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   shader(myShader);
        +   *   strokeWeight(10);
        +   *   beginShape();
        +   *   for (let i = 0; i <= 50; i++) {
        +   *     stroke(
        +   *       0,
        +   *       255
        +   *         * map(i, 0, 20, 0, 1, true)
        +   *         * map(i, 30, 50, 1, 0, true)
        +   *     );
        +   *     vertex(
        +   *       map(i, 0, 50, -1, 1) * width/3,
        +   *       50 * sin(i/10 + frameCount/100)
        +   *     );
        +   *   }
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.baseStrokeShader = function() {
        +    this._assert3d('baseStrokeShader');
        +    return this._renderer.baseStrokeShader();
        +  };
         
        -/**
        - * Changes the coordinate system used for textures when they’re applied to
        - * custom shapes.
        - *
        - * In order for <a href="#/p5/texture">texture()</a> to work, a shape needs a
        - * way to map the points on its surface to the pixels in an image. Built-in
        - * shapes such as <a href="#/p5/rect">rect()</a> and
        - * <a href="#/p5/box">box()</a> already have these texture mappings based on
        - * their vertices. Custom shapes created with
        - * <a href="#/p5/vertex">vertex()</a> require texture mappings to be passed as
        - * uv coordinates.
        - *
        - * Each call to <a href="#/p5/vertex">vertex()</a> must include 5 arguments,
        - * as in `vertex(x, y, z, u, v)`, to map the vertex at coordinates `(x, y, z)`
        - * to the pixel at coordinates `(u, v)` within an image. For example, the
        - * corners of a rectangular image are mapped to the corners of a rectangle by default:
        - *
        - * <code>
        - * // Apply the image as a texture.
        - * texture(img);
        - *
        - * // Draw the rectangle.
        - * rect(0, 0, 30, 50);
        - * </code>
        - *
        - * If the image in the code snippet above has dimensions of 300 x 500 pixels,
        - * the same result could be achieved as follows:
        - *
        - * <code>
        - * // Apply the image as a texture.
        - * texture(img);
        - *
        - * // Draw the rectangle.
        - * beginShape();
        - *
        - * // Top-left.
        - * // u: 0, v: 0
        - * vertex(0, 0, 0, 0, 0);
        - *
        - * // Top-right.
        - * // u: 300, v: 0
        - * vertex(30, 0, 0, 300, 0);
        - *
        - * // Bottom-right.
        - * // u: 300, v: 500
        - * vertex(30, 50, 0, 300, 500);
        - *
        - * // Bottom-left.
        - * // u: 0, v: 500
        - * vertex(0, 50, 0, 0, 500);
        - *
        - * endShape();
        - * </code>
        - *
        - * `textureMode()` changes the coordinate system for uv coordinates.
        - *
        - * The parameter, `mode`, accepts two possible constants. If `NORMAL` is
        - * passed, as in `textureMode(NORMAL)`, then the texture’s uv coordinates can
        - * be provided in the range 0 to 1 instead of the image’s dimensions. This can
        - * be helpful for using the same code for multiple images of different sizes.
        - * For example, the code snippet above could be rewritten as follows:
        - *
        - * <code>
        - * // Set the texture mode to use normalized coordinates.
        - * textureMode(NORMAL);
        - *
        - * // Apply the image as a texture.
        - * texture(img);
        - *
        - * // Draw the rectangle.
        - * beginShape();
        - *
        - * // Top-left.
        - * // u: 0, v: 0
        - * vertex(0, 0, 0, 0, 0);
        - *
        - * // Top-right.
        - * // u: 1, v: 0
        - * vertex(30, 0, 0, 1, 0);
        - *
        - * // Bottom-right.
        - * // u: 1, v: 1
        - * vertex(30, 50, 0, 1, 1);
        - *
        - * // Bottom-left.
        - * // u: 0, v: 1
        - * vertex(0, 50, 0, 0, 1);
        - *
        - * endShape();
        - * </code>
        - *
        - * By default, `mode` is `IMAGE`, which scales uv coordinates to the
        - * dimensions of the image. Calling `textureMode(IMAGE)` applies the default.
        - *
        - * Note: `textureMode()` can only be used in WebGL mode.
        - *
        - * @method  textureMode
        - * @param {Constant} mode either IMAGE or NORMAL.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load an image and create a p5.Image object.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('An image of a ceiling against a black background.');
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Apply the image as a texture.
        - *   texture(img);
        - *
        - *   // Draw the custom shape.
        - *   // Use the image's width and height as uv coordinates.
        - *   beginShape();
        - *   vertex(-30, -30, 0, 0);
        - *   vertex(30, -30, img.width, 0);
        - *   vertex(30, 30, img.width, img.height);
        - *   vertex(-30, 30, 0, img.height);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * // Load an image and create a p5.Image object.
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('An image of a ceiling against a black background.');
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Set the texture mode.
        - *   textureMode(NORMAL);
        - *
        - *   // Apply the image as a texture.
        - *   texture(img);
        - *
        - *   // Draw the custom shape.
        - *   // Use normalized uv coordinates.
        - *   beginShape();
        - *   vertex(-30, -30, 0, 0);
        - *   vertex(30, -30, 1, 0);
        - *   vertex(30, 30, 1, 1);
        - *   vertex(-30, 30, 0, 1);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.textureMode = function (mode) {
        -  if (mode !== constants.IMAGE && mode !== constants.NORMAL) {
        -    console.warn(
        -      `You tried to set ${mode} textureMode only supports IMAGE & NORMAL `
        -    );
        -  } else {
        -    this._renderer.textureMode = mode;
        -  }
        -};
        +  /**
        +   * Restores the default shaders.
        +   *
        +   * `resetShader()` deactivates any shaders previously applied by
        +   * <a href="#/p5/shader">shader()</a>, <a href="#/p5/strokeShader">strokeShader()</a>,
        +   * or <a href="#/p5/imageShader">imageShader()</a>.
        +   *
        +   * Note: Shaders can only be used in WebGL mode.
        +   *
        +   * @method resetShader
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Create a string with the vertex shader program.
        +   * // The vertex shader is called for each vertex.
        +   * let vertSrc = `
        +   * attribute vec3 aPosition;
        +   * attribute vec2 aTexCoord;
        +   * uniform mat4 uProjectionMatrix;
        +   * uniform mat4 uModelViewMatrix;
        +   * varying vec2 vTexCoord;
        +   *
        +   * void main() {
        +   *   vTexCoord = aTexCoord;
        +   *   vec4 position = vec4(aPosition, 1.0);
        +   *   gl_Position = uProjectionMatrix * uModelViewMatrix * position;
        +   * }
        +   * `;
        +   *
        +   * // Create a string with the fragment shader program.
        +   * // The fragment shader is called for each pixel.
        +   * let fragSrc = `
        +   * precision mediump float;
        +   * varying vec2 vTexCoord;
        +   *
        +   * void main() {
        +   *   vec2 uv = vTexCoord;
        +   *   vec3 color = vec3(uv.x, uv.y, min(uv.x + uv.y, 1.0));
        +   *   gl_FragColor = vec4(color, 1.0);
        +   * }
        +   * `;
        +   *
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Shader object.
        +   *   myShader = createShader(vertSrc, fragSrc);
        +   *
        +   *   describe(
        +   *     'Two rotating cubes on a gray background. The left one has a blue-purple gradient on each face. The right one is red.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw a box using the p5.Shader.
        +   *   // shader() sets the active shader to myShader.
        +   *   shader(myShader);
        +   *   push();
        +   *   translate(-25, 0, 0);
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *   box(width / 4);
        +   *   pop();
        +   *
        +   *   // Draw a box using the default fill shader.
        +   *   // resetShader() restores the default fill shader.
        +   *   resetShader();
        +   *   fill(255, 0, 0);
        +   *   push();
        +   *   translate(25, 0, 0);
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *   box(width / 4);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.resetShader = function () {
        +    this._renderer.resetShader();
        +    return this;
        +  };
         
        -/**
        - * Changes the way textures behave when a shape’s uv coordinates go beyond the
        - * texture.
        - *
        - * In order for <a href="#/p5/texture">texture()</a> to work, a shape needs a
        - * way to map the points on its surface to the pixels in an image. Built-in
        - * shapes such as <a href="#/p5/rect">rect()</a> and
        - * <a href="#/p5/box">box()</a> already have these texture mappings based on
        - * their vertices. Custom shapes created with
        - * <a href="#/p5/vertex">vertex()</a> require texture mappings to be passed as
        - * uv coordinates.
        - *
        - * Each call to <a href="#/p5/vertex">vertex()</a> must include 5 arguments,
        - * as in `vertex(x, y, z, u, v)`, to map the vertex at coordinates `(x, y, z)`
        - * to the pixel at coordinates `(u, v)` within an image. For example, the
        - * corners of a rectangular image are mapped to the corners of a rectangle by default:
        - *
        - * ```js
        - * // Apply the image as a texture.
        - * texture(img);
        - *
        - * // Draw the rectangle.
        - * rect(0, 0, 30, 50);
        - * ```
        - *
        - * If the image in the code snippet above has dimensions of 300 x 500 pixels,
        - * the same result could be achieved as follows:
        - *
        - * ```js
        - * // Apply the image as a texture.
        - * texture(img);
        - *
        - * // Draw the rectangle.
        - * beginShape();
        - *
        - * // Top-left.
        - * // u: 0, v: 0
        - * vertex(0, 0, 0, 0, 0);
        - *
        - * // Top-right.
        - * // u: 300, v: 0
        - * vertex(30, 0, 0, 300, 0);
        - *
        - * // Bottom-right.
        - * // u: 300, v: 500
        - * vertex(30, 50, 0, 300, 500);
        - *
        - * // Bottom-left.
        - * // u: 0, v: 500
        - * vertex(0, 50, 0, 0, 500);
        - *
        - * endShape();
        - * ```
        - *
        - * `textureWrap()` controls how textures behave when their uv's go beyond the
        - * texture. Doing so can produce interesting visual effects such as tiling.
        - * For example, the custom shape above could have u-coordinates are greater
        - * than the image’s width:
        - *
        - * ```js
        - * // Apply the image as a texture.
        - * texture(img);
        - *
        - * // Draw the rectangle.
        - * beginShape();
        - * vertex(0, 0, 0, 0, 0);
        - *
        - * // Top-right.
        - * // u: 600
        - * vertex(30, 0, 0, 600, 0);
        - *
        - * // Bottom-right.
        - * // u: 600
        - * vertex(30, 50, 0, 600, 500);
        - *
        - * vertex(0, 50, 0, 0, 500);
        - * endShape();
        - * ```
        - *
        - * The u-coordinates of 600 are greater than the texture image’s width of 300.
        - * This creates interesting possibilities.
        - *
        - * The first parameter, `wrapX`, accepts three possible constants. If `CLAMP`
        - * is passed, as in `textureWrap(CLAMP)`, the pixels at the edge of the
        - * texture will extend to the shape’s edges. If `REPEAT` is passed, as in
        - * `textureWrap(REPEAT)`, the texture will tile repeatedly until reaching the
        - * shape’s edges. If `MIRROR` is passed, as in `textureWrap(MIRROR)`, the
        - * texture will tile repeatedly until reaching the shape’s edges, flipping
        - * its orientation between tiles. By default, textures `CLAMP`.
        - *
        - * The second parameter, `wrapY`, is optional. It accepts the same three
        - * constants, `CLAMP`, `REPEAT`, and `MIRROR`. If one of these constants is
        - * passed, as in `textureWRAP(MIRROR, REPEAT)`, then the texture will `MIRROR`
        - * horizontally and `REPEAT` vertically. By default, `wrapY` will be set to
        - * the same value as `wrapX`.
        - *
        - * Note: `textureWrap()` can only be used in WebGL mode.
        - *
        - * @method textureWrap
        - * @param {Constant} wrapX either CLAMP, REPEAT, or MIRROR
        - * @param {Constant} [wrapY] either CLAMP, REPEAT, or MIRROR
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * function preload() {
        - *   img = loadImage('assets/rockies128.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'An image of a landscape occupies the top-left corner of a square. Its edge colors smear to cover the other thre quarters of the square.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Set the texture mode.
        - *   textureMode(NORMAL);
        - *
        - *   // Set the texture wrapping.
        - *   // Note: CLAMP is the default mode.
        - *   textureWrap(CLAMP);
        - *
        - *   // Apply the image as a texture.
        - *   texture(img);
        - *
        - *   // Style the shape.
        - *   noStroke();
        - *
        - *   // Draw the shape.
        - *   // Use uv coordinates > 1.
        - *   beginShape();
        - *   vertex(-30, -30, 0, 0, 0);
        - *   vertex(30, -30, 0, 2, 0);
        - *   vertex(30, 30, 0, 2, 2);
        - *   vertex(-30, 30, 0, 0, 2);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * function preload() {
        - *   img = loadImage('assets/rockies128.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('Four identical images of a landscape arranged in a grid.');
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Set the texture mode.
        - *   textureMode(NORMAL);
        - *
        - *   // Set the texture wrapping.
        - *   textureWrap(REPEAT);
        - *
        - *   // Apply the image as a texture.
        - *   texture(img);
        - *
        - *   // Style the shape.
        - *   noStroke();
        - *
        - *   // Draw the shape.
        - *   // Use uv coordinates > 1.
        - *   beginShape();
        - *   vertex(-30, -30, 0, 0, 0);
        - *   vertex(30, -30, 0, 2, 0);
        - *   vertex(30, 30, 0, 2, 2);
        - *   vertex(-30, 30, 0, 0, 2);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * function preload() {
        - *   img = loadImage('assets/rockies128.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'Four identical images of a landscape arranged in a grid. The images are reflected horizontally and vertically, creating a kaleidoscope effect.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Set the texture mode.
        - *   textureMode(NORMAL);
        - *
        - *   // Set the texture wrapping.
        - *   textureWrap(MIRROR);
        - *
        - *   // Apply the image as a texture.
        - *   texture(img);
        - *
        - *   // Style the shape.
        - *   noStroke();
        - *
        - *   // Draw the shape.
        - *   // Use uv coordinates > 1.
        - *   beginShape();
        - *   vertex(-30, -30, 0, 0, 0);
        - *   vertex(30, -30, 0, 2, 0);
        - *   vertex(30, 30, 0, 2, 2);
        - *   vertex(-30, 30, 0, 0, 2);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let img;
        - *
        - * function preload() {
        - *   img = loadImage('assets/rockies128.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'Four identical images of a landscape arranged in a grid. The top row and bottom row are reflections of each other.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *
        - *   // Set the texture mode.
        - *   textureMode(NORMAL);
        - *
        - *   // Set the texture wrapping.
        - *   textureWrap(REPEAT, MIRROR);
        - *
        - *   // Apply the image as a texture.
        - *   texture(img);
        - *
        - *   // Style the shape.
        - *   noStroke();
        - *
        - *   // Draw the shape.
        - *   // Use uv coordinates > 1.
        - *   beginShape();
        - *   vertex(-30, -30, 0, 0, 0);
        - *   vertex(30, -30, 0, 2, 0);
        - *   vertex(30, 30, 0, 2, 2);
        - *   vertex(-30, 30, 0, 0, 2);
        - *   endShape();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.textureWrap = function (wrapX, wrapY = wrapX) {
        -  this._renderer.textureWrapX = wrapX;
        -  this._renderer.textureWrapY = wrapY;
        +  /**
        +   * Sets the texture that will be used on shapes.
        +   *
        +   * A texture is like a skin that wraps around a shape. `texture()` works with
        +   * built-in shapes, such as <a href="#/p5/square">square()</a> and
        +   * <a href="#/p5/sphere">sphere()</a>, and custom shapes created with
        +   * functions such as <a href="#/p5/buildGeometry">buildGeometry()</a>. To
        +   * texture a geometry created with <a href="#/p5/beginShape">beginShape()</a>,
        +   * uv coordinates must be passed to each
        +   * <a href="#/p5/vertex">vertex()</a> call.
        +   *
        +   * The parameter, `tex`, is the texture to apply. `texture()` can use a range
        +   * of sources including images, videos, and offscreen renderers such as
        +   * <a href="#/p5.Graphics">p5.Graphics</a> and
        +   * <a href="#/p5.Framebuffer">p5.Framebuffer</a> objects.
        +   *
        +   * To texture a geometry created with <a href="#/p5/beginShape">beginShape()</a>,
        +   * you will need to specify uv coordinates in <a href="#/p5/vertex">vertex()</a>.
        +   *
        +   * Note: `texture()` can only be used in WebGL mode.
        +   *
        +   * @method texture
        +   * @param {p5.Image|p5.MediaElement|p5.Graphics|p5.Texture|p5.Framebuffer|p5.FramebufferTexture} tex media to use as the texture.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load an image and create a p5.Image object.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A spinning cube with an image of a ceiling on each face.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Rotate around the x-, y-, and z-axes.
        +   *   rotateZ(frameCount * 0.01);
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Apply the image as a texture.
        +   *   texture(img);
        +   *
        +   *   // Draw the box.
        +   *   box(50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let pg;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Graphics object.
        +   *   pg = createGraphics(100, 100);
        +   *
        +   *   // Draw a circle to the p5.Graphics object.
        +   *   pg.background(200);
        +   *   pg.circle(50, 50, 30);
        +   *
        +   *   describe('A spinning cube with circle at the center of each face.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Rotate around the x-, y-, and z-axes.
        +   *   rotateZ(frameCount * 0.01);
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Apply the p5.Graphics object as a texture.
        +   *   texture(pg);
        +   *
        +   *   // Draw the box.
        +   *   box(50);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let vid;
        +   *
        +   * // Load a video and create a p5.MediaElement object.
        +   * function preload() {
        +   *   vid = createVideo('assets/fingers.mov');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Hide the video.
        +   *   vid.hide();
        +   *
        +   *   // Set the video to loop.
        +   *   vid.loop();
        +   *
        +   *   describe('A rectangle with video as texture');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Apply the video as a texture.
        +   *   texture(vid);
        +   *
        +   *   // Draw the rectangle.
        +   *   rect(-40, -40, 80, 80);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let vid;
        +   *
        +   * // Load a video and create a p5.MediaElement object.
        +   * function preload() {
        +   *   vid = createVideo('assets/fingers.mov');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Hide the video.
        +   *   vid.hide();
        +   *
        +   *   // Set the video to loop.
        +   *   vid.loop();
        +   *
        +   *   describe('A rectangle with video as texture');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Set the texture mode.
        +   *   textureMode(NORMAL);
        +   *
        +   *   // Apply the video as a texture.
        +   *   texture(vid);
        +   *
        +   *   // Draw a custom shape using uv coordinates.
        +   *   beginShape();
        +   *   vertex(-40, -40, 0, 0);
        +   *   vertex(40, -40, 1, 0);
        +   *   vertex(40, 40, 1, 1);
        +   *   vertex(-40, 40, 0, 1);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.texture = function (tex) {
        +    this._assert3d('texture');
        +    // p5._validateParameters('texture', arguments);
         
        -  for (const texture of this._renderer.textures.values()) {
        -    texture.setWrapMode(wrapX, wrapY);
        -  }
        -};
        +    // NOTE: make generic or remove need for
        +    if (tex.gifProperties) {
        +      tex._animateGif(this);
        +    }
         
        -/**
        - * Sets the current material as a normal material.
        - *
        - * A normal material sets surfaces facing the x-axis to red, those facing the
        - * y-axis to green, and those facing the z-axis to blue. Normal material isn't
        - * affected by light. It’s often used as a placeholder material when debugging.
        - *
        - * Note: `normalMaterial()` can only be used in WebGL mode.
        - *
        - * @method normalMaterial
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A multicolor torus drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Style the torus.
        - *   normalMaterial();
        - *
        - *   // Draw the torus.
        - *   torus(30);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.normalMaterial = function (...args) {
        -  this._assert3d('normalMaterial');
        -  p5._validateParameters('normalMaterial', args);
        -  this._renderer.drawMode = constants.FILL;
        -  this._renderer._useSpecularMaterial = false;
        -  this._renderer._useEmissiveMaterial = false;
        -  this._renderer._useNormalMaterial = true;
        -  this._renderer.curFillColor = [1, 1, 1, 1];
        -  this._renderer._setProperty('_doFill', true);
        -  this.noStroke();
        -  return this;
        -};
        +    this._renderer.texture(tex);
         
        -/**
        - * Sets the ambient color of shapes’ surface material.
        - *
        - * The `ambientMaterial()` color sets the components of the
        - * <a href="#/p5/ambientLight">ambientLight()</a> color that shapes will
        - * reflect. For example, calling `ambientMaterial(255, 255, 0)` would cause a
        - * shape to reflect red and green light, but not blue light.
        - *
        - * `ambientMaterial()` can be called three ways with different parameters to
        - * set the material’s color.
        - *
        - * The first way to call `ambientMaterial()` has one parameter, `gray`.
        - * Grayscale values between 0 and 255, as in `ambientMaterial(50)`, can be
        - * passed to set the material’s color. Higher grayscale values make shapes
        - * appear brighter.
        - *
        - * The second way to call `ambientMaterial()` has one parameter, `color`. A
        - * <a href="#/p5.Color">p5.Color</a> object, an array of color values, or a
        - * CSS color string, as in `ambientMaterial('magenta')`, can be passed to set
        - * the material’s color.
        - *
        - * The third way to call `ambientMaterial()` has three parameters, `v1`, `v2`,
        - * and `v3`. RGB, HSB, or HSL values, as in `ambientMaterial(255, 0, 0)`, can
        - * be passed to set the material’s colors. Color values will be interpreted
        - * using the current <a href="#/p5/colorMode">colorMode()</a>.
        - *
        - * Note: `ambientMaterial()` can only be used in WebGL mode.
        - *
        - * @method ambientMaterial
        - * @param  {Number} v1  red or hue value in the current
        - *                       <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number} v2  green or saturation value in the
        - *                      current <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number} v3  blue, brightness, or lightness value in the
        - *                      current <a href="#/p5/colorMode">colorMode()</a>.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A magenta cube drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on a magenta ambient light.
        - *   ambientLight(255, 0, 255);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A purple cube drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on a magenta ambient light.
        - *   ambientLight(255, 0, 255);
        - *
        - *   // Add a dark gray ambient material.
        - *   ambientMaterial(150);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A red cube drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on a magenta ambient light.
        - *   ambientLight(255, 0, 255);
        - *
        - *   // Add a yellow ambient material using RGB values.
        - *   ambientMaterial(255, 255, 0);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A red cube drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on a magenta ambient light.
        - *   ambientLight(255, 0, 255);
        - *
        - *   // Add a yellow ambient material using a p5.Color object.
        - *   let c = color(255, 255, 0);
        - *   ambientMaterial(c);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A red cube drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on a magenta ambient light.
        - *   ambientLight(255, 0, 255);
        - *
        - *   // Add a yellow ambient material using a color string.
        - *   ambientMaterial('yellow');
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A yellow cube drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on a white ambient light.
        - *   ambientLight(255, 255, 255);
        - *
        - *   // Add a yellow ambient material using a color string.
        - *   ambientMaterial('yellow');
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        +    return this;
        +  };
         
        -/**
        - * @method ambientMaterial
        - * @param  {Number} gray grayscale value between 0 (black) and 255 (white).
        - * @chainable
        - */
        +  /**
        +   * Changes the coordinate system used for textures when they’re applied to
        +   * custom shapes.
        +   *
        +   * In order for <a href="#/p5/texture">texture()</a> to work, a shape needs a
        +   * way to map the points on its surface to the pixels in an image. Built-in
        +   * shapes such as <a href="#/p5/rect">rect()</a> and
        +   * <a href="#/p5/box">box()</a> already have these texture mappings based on
        +   * their vertices. Custom shapes created with
        +   * <a href="#/p5/vertex">vertex()</a> require texture mappings to be passed as
        +   * uv coordinates.
        +   *
        +   * Each call to <a href="#/p5/vertex">vertex()</a> must include 5 arguments,
        +   * as in `vertex(x, y, z, u, v)`, to map the vertex at coordinates `(x, y, z)`
        +   * to the pixel at coordinates `(u, v)` within an image. For example, the
        +   * corners of a rectangular image are mapped to the corners of a rectangle by default:
        +   *
        +   * <code>
        +   * // Apply the image as a texture.
        +   * texture(img);
        +   *
        +   * // Draw the rectangle.
        +   * rect(0, 0, 30, 50);
        +   * </code>
        +   *
        +   * If the image in the code snippet above has dimensions of 300 x 500 pixels,
        +   * the same result could be achieved as follows:
        +   *
        +   * <code>
        +   * // Apply the image as a texture.
        +   * texture(img);
        +   *
        +   * // Draw the rectangle.
        +   * beginShape();
        +   *
        +   * // Top-left.
        +   * // u: 0, v: 0
        +   * vertex(0, 0, 0, 0, 0);
        +   *
        +   * // Top-right.
        +   * // u: 300, v: 0
        +   * vertex(30, 0, 0, 300, 0);
        +   *
        +   * // Bottom-right.
        +   * // u: 300, v: 500
        +   * vertex(30, 50, 0, 300, 500);
        +   *
        +   * // Bottom-left.
        +   * // u: 0, v: 500
        +   * vertex(0, 50, 0, 0, 500);
        +   *
        +   * endShape();
        +   * </code>
        +   *
        +   * `textureMode()` changes the coordinate system for uv coordinates.
        +   *
        +   * The parameter, `mode`, accepts two possible constants. If `NORMAL` is
        +   * passed, as in `textureMode(NORMAL)`, then the texture’s uv coordinates can
        +   * be provided in the range 0 to 1 instead of the image’s dimensions. This can
        +   * be helpful for using the same code for multiple images of different sizes.
        +   * For example, the code snippet above could be rewritten as follows:
        +   *
        +   * <code>
        +   * // Set the texture mode to use normalized coordinates.
        +   * textureMode(NORMAL);
        +   *
        +   * // Apply the image as a texture.
        +   * texture(img);
        +   *
        +   * // Draw the rectangle.
        +   * beginShape();
        +   *
        +   * // Top-left.
        +   * // u: 0, v: 0
        +   * vertex(0, 0, 0, 0, 0);
        +   *
        +   * // Top-right.
        +   * // u: 1, v: 0
        +   * vertex(30, 0, 0, 1, 0);
        +   *
        +   * // Bottom-right.
        +   * // u: 1, v: 1
        +   * vertex(30, 50, 0, 1, 1);
        +   *
        +   * // Bottom-left.
        +   * // u: 0, v: 1
        +   * vertex(0, 50, 0, 0, 1);
        +   *
        +   * endShape();
        +   * </code>
        +   *
        +   * By default, `mode` is `IMAGE`, which scales uv coordinates to the
        +   * dimensions of the image. Calling `textureMode(IMAGE)` applies the default.
        +   *
        +   * Note: `textureMode()` can only be used in WebGL mode.
        +   *
        +   * @method  textureMode
        +   * @param {(IMAGE|NORMAL)} mode either IMAGE or NORMAL.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load an image and create a p5.Image object.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('An image of a ceiling against a black background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Apply the image as a texture.
        +   *   texture(img);
        +   *
        +   *   // Draw the custom shape.
        +   *   // Use the image's width and height as uv coordinates.
        +   *   beginShape();
        +   *   vertex(-30, -30, 0, 0);
        +   *   vertex(30, -30, img.width, 0);
        +   *   vertex(30, 30, img.width, img.height);
        +   *   vertex(-30, 30, 0, img.height);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * // Load an image and create a p5.Image object.
        +   * function preload() {
        +   *   img = loadImage('assets/laDefense.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('An image of a ceiling against a black background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Set the texture mode.
        +   *   textureMode(NORMAL);
        +   *
        +   *   // Apply the image as a texture.
        +   *   texture(img);
        +   *
        +   *   // Draw the custom shape.
        +   *   // Use normalized uv coordinates.
        +   *   beginShape();
        +   *   vertex(-30, -30, 0, 0);
        +   *   vertex(30, -30, 1, 0);
        +   *   vertex(30, 30, 1, 1);
        +   *   vertex(-30, 30, 0, 1);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.textureMode = function (mode) {
        +    if (mode !== constants.IMAGE && mode !== constants.NORMAL) {
        +      console.warn(
        +        `You tried to set ${mode} textureMode only supports IMAGE & NORMAL `
        +      );
        +    } else {
        +      this._renderer.states.textureMode = mode;
        +    }
        +  };
         
        -/**
        - * @method ambientMaterial
        - * @param  {p5.Color|Number[]|String} color
        - *           color as a <a href="#/p5.Color">p5.Color</a> object,
        - *            an array of color values, or a CSS string.
        - * @chainable
        - */
        -p5.prototype.ambientMaterial = function (v1, v2, v3) {
        -  this._assert3d('ambientMaterial');
        -  p5._validateParameters('ambientMaterial', arguments);
        -
        -  const color = p5.prototype.color.apply(this, arguments);
        -  this._renderer._hasSetAmbient = true;
        -  this._renderer.curAmbientColor = color._array;
        -  this._renderer._useNormalMaterial = false;
        -  this._renderer._enableLighting = true;
        -  this._renderer._setProperty('_doFill', true);
        -  return this;
        -};
        +  /**
        +   * Changes the way textures behave when a shape’s uv coordinates go beyond the
        +   * texture.
        +   *
        +   * In order for <a href="#/p5/texture">texture()</a> to work, a shape needs a
        +   * way to map the points on its surface to the pixels in an image. Built-in
        +   * shapes such as <a href="#/p5/rect">rect()</a> and
        +   * <a href="#/p5/box">box()</a> already have these texture mappings based on
        +   * their vertices. Custom shapes created with
        +   * <a href="#/p5/vertex">vertex()</a> require texture mappings to be passed as
        +   * uv coordinates.
        +   *
        +   * Each call to <a href="#/p5/vertex">vertex()</a> must include 5 arguments,
        +   * as in `vertex(x, y, z, u, v)`, to map the vertex at coordinates `(x, y, z)`
        +   * to the pixel at coordinates `(u, v)` within an image. For example, the
        +   * corners of a rectangular image are mapped to the corners of a rectangle by default:
        +   *
        +   * ```js
        +   * // Apply the image as a texture.
        +   * texture(img);
        +   *
        +   * // Draw the rectangle.
        +   * rect(0, 0, 30, 50);
        +   * ```
        +   *
        +   * If the image in the code snippet above has dimensions of 300 x 500 pixels,
        +   * the same result could be achieved as follows:
        +   *
        +   * ```js
        +   * // Apply the image as a texture.
        +   * texture(img);
        +   *
        +   * // Draw the rectangle.
        +   * beginShape();
        +   *
        +   * // Top-left.
        +   * // u: 0, v: 0
        +   * vertex(0, 0, 0, 0, 0);
        +   *
        +   * // Top-right.
        +   * // u: 300, v: 0
        +   * vertex(30, 0, 0, 300, 0);
        +   *
        +   * // Bottom-right.
        +   * // u: 300, v: 500
        +   * vertex(30, 50, 0, 300, 500);
        +   *
        +   * // Bottom-left.
        +   * // u: 0, v: 500
        +   * vertex(0, 50, 0, 0, 500);
        +   *
        +   * endShape();
        +   * ```
        +   *
        +   * `textureWrap()` controls how textures behave when their uv's go beyond the
        +   * texture. Doing so can produce interesting visual effects such as tiling.
        +   * For example, the custom shape above could have u-coordinates are greater
        +   * than the image’s width:
        +   *
        +   * ```js
        +   * // Apply the image as a texture.
        +   * texture(img);
        +   *
        +   * // Draw the rectangle.
        +   * beginShape();
        +   * vertex(0, 0, 0, 0, 0);
        +   *
        +   * // Top-right.
        +   * // u: 600
        +   * vertex(30, 0, 0, 600, 0);
        +   *
        +   * // Bottom-right.
        +   * // u: 600
        +   * vertex(30, 50, 0, 600, 500);
        +   *
        +   * vertex(0, 50, 0, 0, 500);
        +   * endShape();
        +   * ```
        +   *
        +   * The u-coordinates of 600 are greater than the texture image’s width of 300.
        +   * This creates interesting possibilities.
        +   *
        +   * The first parameter, `wrapX`, accepts three possible constants. If `CLAMP`
        +   * is passed, as in `textureWrap(CLAMP)`, the pixels at the edge of the
        +   * texture will extend to the shape’s edges. If `REPEAT` is passed, as in
        +   * `textureWrap(REPEAT)`, the texture will tile repeatedly until reaching the
        +   * shape’s edges. If `MIRROR` is passed, as in `textureWrap(MIRROR)`, the
        +   * texture will tile repeatedly until reaching the shape’s edges, flipping
        +   * its orientation between tiles. By default, textures `CLAMP`.
        +   *
        +   * The second parameter, `wrapY`, is optional. It accepts the same three
        +   * constants, `CLAMP`, `REPEAT`, and `MIRROR`. If one of these constants is
        +   * passed, as in `textureWRAP(MIRROR, REPEAT)`, then the texture will `MIRROR`
        +   * horizontally and `REPEAT` vertically. By default, `wrapY` will be set to
        +   * the same value as `wrapX`.
        +   *
        +   * Note: `textureWrap()` can only be used in WebGL mode.
        +   *
        +   * @method textureWrap
        +   * @param {(CLAMP|REPEAT|MIRROR)} wrapX either CLAMP, REPEAT, or MIRROR
        +   * @param {(CLAMP|REPEAT|MIRROR)} [wrapY=wrapX] either CLAMP, REPEAT, or MIRROR
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * function preload() {
        +   *   img = loadImage('assets/rockies128.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'An image of a landscape occupies the top-left corner of a square. Its edge colors smear to cover the other thre quarters of the square.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Set the texture mode.
        +   *   textureMode(NORMAL);
        +   *
        +   *   // Set the texture wrapping.
        +   *   // Note: CLAMP is the default mode.
        +   *   textureWrap(CLAMP);
        +   *
        +   *   // Apply the image as a texture.
        +   *   texture(img);
        +   *
        +   *   // Style the shape.
        +   *   noStroke();
        +   *
        +   *   // Draw the shape.
        +   *   // Use uv coordinates > 1.
        +   *   beginShape();
        +   *   vertex(-30, -30, 0, 0, 0);
        +   *   vertex(30, -30, 0, 2, 0);
        +   *   vertex(30, 30, 0, 2, 2);
        +   *   vertex(-30, 30, 0, 0, 2);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * function preload() {
        +   *   img = loadImage('assets/rockies128.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('Four identical images of a landscape arranged in a grid.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Set the texture mode.
        +   *   textureMode(NORMAL);
        +   *
        +   *   // Set the texture wrapping.
        +   *   textureWrap(REPEAT);
        +   *
        +   *   // Apply the image as a texture.
        +   *   texture(img);
        +   *
        +   *   // Style the shape.
        +   *   noStroke();
        +   *
        +   *   // Draw the shape.
        +   *   // Use uv coordinates > 1.
        +   *   beginShape();
        +   *   vertex(-30, -30, 0, 0, 0);
        +   *   vertex(30, -30, 0, 2, 0);
        +   *   vertex(30, 30, 0, 2, 2);
        +   *   vertex(-30, 30, 0, 0, 2);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * function preload() {
        +   *   img = loadImage('assets/rockies128.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'Four identical images of a landscape arranged in a grid. The images are reflected horizontally and vertically, creating a kaleidoscope effect.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Set the texture mode.
        +   *   textureMode(NORMAL);
        +   *
        +   *   // Set the texture wrapping.
        +   *   textureWrap(MIRROR);
        +   *
        +   *   // Apply the image as a texture.
        +   *   texture(img);
        +   *
        +   *   // Style the shape.
        +   *   noStroke();
        +   *
        +   *   // Draw the shape.
        +   *   // Use uv coordinates > 1.
        +   *   beginShape();
        +   *   vertex(-30, -30, 0, 0, 0);
        +   *   vertex(30, -30, 0, 2, 0);
        +   *   vertex(30, 30, 0, 2, 2);
        +   *   vertex(-30, 30, 0, 0, 2);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * function preload() {
        +   *   img = loadImage('assets/rockies128.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'Four identical images of a landscape arranged in a grid. The top row and bottom row are reflections of each other.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *
        +   *   // Set the texture mode.
        +   *   textureMode(NORMAL);
        +   *
        +   *   // Set the texture wrapping.
        +   *   textureWrap(REPEAT, MIRROR);
        +   *
        +   *   // Apply the image as a texture.
        +   *   texture(img);
        +   *
        +   *   // Style the shape.
        +   *   noStroke();
        +   *
        +   *   // Draw the shape.
        +   *   // Use uv coordinates > 1.
        +   *   beginShape();
        +   *   vertex(-30, -30, 0, 0, 0);
        +   *   vertex(30, -30, 0, 2, 0);
        +   *   vertex(30, 30, 0, 2, 2);
        +   *   vertex(-30, 30, 0, 0, 2);
        +   *   endShape();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.textureWrap = function (wrapX, wrapY = wrapX) {
        +    this._renderer.states.textureWrapX = wrapX;
        +    this._renderer.states.textureWrapY = wrapY;
         
        -/**
        - * Sets the emissive color of shapes’ surface material.
        - *
        - * The `emissiveMaterial()` color sets a color shapes display at full
        - * strength, regardless of lighting. This can give the appearance that a shape
        - * is glowing. However, emissive materials don’t actually emit light that
        - * can affect surrounding objects.
        - *
        - * `emissiveMaterial()` can be called three ways with different parameters to
        - * set the material’s color.
        - *
        - * The first way to call `emissiveMaterial()` has one parameter, `gray`.
        - * Grayscale values between 0 and 255, as in `emissiveMaterial(50)`, can be
        - * passed to set the material’s color. Higher grayscale values make shapes
        - * appear brighter.
        - *
        - * The second way to call `emissiveMaterial()` has one parameter, `color`. A
        - * <a href="#/p5.Color">p5.Color</a> object, an array of color values, or a
        - * CSS color string, as in `emissiveMaterial('magenta')`, can be passed to set
        - * the material’s color.
        - *
        - * The third way to call `emissiveMaterial()` has four parameters, `v1`, `v2`,
        - * `v3`, and `alpha`. `alpha` is optional. RGBA, HSBA, or HSLA values can be
        - * passed to set the material’s colors, as in `emissiveMaterial(255, 0, 0)` or
        - * `emissiveMaterial(255, 0, 0, 30)`. Color values will be interpreted using
        - * the current <a href="#/p5/colorMode">colorMode()</a>.
        - *
        - * Note: `emissiveMaterial()` can only be used in WebGL mode.
        - *
        - * @method emissiveMaterial
        - * @param  {Number} v1       red or hue value in the current
        - *                           <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number} v2       green or saturation value in the
        - *                           current <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number} v3       blue, brightness, or lightness value in the
        - *                           current <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number} [alpha]  alpha value in the current
        - *                           <a href="#/p5/colorMode">colorMode()</a>.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A red cube drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on a white ambient light.
        - *   ambientLight(255, 255, 255);
        - *
        - *   // Add a red emissive material using RGB values.
        - *   emissiveMaterial(255, 0, 0);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        +    for (const texture of this._renderer.textures.values()) {
        +      texture.setWrapMode(wrapX, wrapY);
        +    }
        +  };
         
        -/**
        - * @method emissiveMaterial
        - * @param  {Number} gray grayscale value between 0 (black) and 255 (white).
        - * @chainable
        - */
        +  /**
        +   * Sets the current material as a normal material.
        +   *
        +   * A normal material sets surfaces facing the x-axis to red, those facing the
        +   * y-axis to green, and those facing the z-axis to blue. Normal material isn't
        +   * affected by light. It’s often used as a placeholder material when debugging.
        +   *
        +   * Note: `normalMaterial()` can only be used in WebGL mode.
        +   *
        +   * @method normalMaterial
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A multicolor torus drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Style the torus.
        +   *   normalMaterial();
        +   *
        +   *   // Draw the torus.
        +   *   torus(30);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.normalMaterial = function (...args) {
        +    this._assert3d('normalMaterial');
        +    // p5._validateParameters('normalMaterial', args);
         
        -/**
        - * @method emissiveMaterial
        - * @param  {p5.Color|Number[]|String} color
        - *           color as a <a href="#/p5.Color">p5.Color</a> object,
        - *            an array of color values, or a CSS string.
        - * @chainable
        - */
        -p5.prototype.emissiveMaterial = function (v1, v2, v3, a) {
        -  this._assert3d('emissiveMaterial');
        -  p5._validateParameters('emissiveMaterial', arguments);
        +    this._renderer.normalMaterial(...args);
         
        -  const color = p5.prototype.color.apply(this, arguments);
        -  this._renderer.curEmissiveColor = color._array;
        -  this._renderer._useEmissiveMaterial = true;
        -  this._renderer._useNormalMaterial = false;
        -  this._renderer._enableLighting = true;
        +    return this;
        +  };
         
        -  return this;
        -};
        +  /**
        +   * Sets the ambient color of shapes’ surface material.
        +   *
        +   * The `ambientMaterial()` color sets the components of the
        +   * <a href="#/p5/ambientLight">ambientLight()</a> color that shapes will
        +   * reflect. For example, calling `ambientMaterial(255, 255, 0)` would cause a
        +   * shape to reflect red and green light, but not blue light.
        +   *
        +   * `ambientMaterial()` can be called three ways with different parameters to
        +   * set the material’s color.
        +   *
        +   * The first way to call `ambientMaterial()` has one parameter, `gray`.
        +   * Grayscale values between 0 and 255, as in `ambientMaterial(50)`, can be
        +   * passed to set the material’s color. Higher grayscale values make shapes
        +   * appear brighter.
        +   *
        +   * The second way to call `ambientMaterial()` has one parameter, `color`. A
        +   * <a href="#/p5.Color">p5.Color</a> object, an array of color values, or a
        +   * CSS color string, as in `ambientMaterial('magenta')`, can be passed to set
        +   * the material’s color.
        +   *
        +   * The third way to call `ambientMaterial()` has three parameters, `v1`, `v2`,
        +   * and `v3`. RGB, HSB, or HSL values, as in `ambientMaterial(255, 0, 0)`, can
        +   * be passed to set the material’s colors. Color values will be interpreted
        +   * using the current <a href="#/p5/colorMode">colorMode()</a>.
        +   *
        +   * Note: `ambientMaterial()` can only be used in WebGL mode.
        +   *
        +   * @method ambientMaterial
        +   * @param  {Number} v1  red or hue value in the current
        +   *                       <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number} v2  green or saturation value in the
        +   *                      current <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number} v3  blue, brightness, or lightness value in the
        +   *                      current <a href="#/p5/colorMode">colorMode()</a>.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A magenta cube drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on a magenta ambient light.
        +   *   ambientLight(255, 0, 255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A purple cube drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on a magenta ambient light.
        +   *   ambientLight(255, 0, 255);
        +   *
        +   *   // Add a dark gray ambient material.
        +   *   ambientMaterial(150);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A red cube drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on a magenta ambient light.
        +   *   ambientLight(255, 0, 255);
        +   *
        +   *   // Add a yellow ambient material using RGB values.
        +   *   ambientMaterial(255, 255, 0);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A red cube drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on a magenta ambient light.
        +   *   ambientLight(255, 0, 255);
        +   *
        +   *   // Add a yellow ambient material using a p5.Color object.
        +   *   let c = color(255, 255, 0);
        +   *   ambientMaterial(c);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A red cube drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on a magenta ambient light.
        +   *   ambientLight(255, 0, 255);
        +   *
        +   *   // Add a yellow ambient material using a color string.
        +   *   ambientMaterial('yellow');
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A yellow cube drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on a white ambient light.
        +   *   ambientLight(255, 255, 255);
        +   *
        +   *   // Add a yellow ambient material using a color string.
        +   *   ambientMaterial('yellow');
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -/**
        - * Sets the specular color of shapes’ surface material.
        - *
        - * The `specularMaterial()` color sets the components of light color that
        - * glossy coats on shapes will reflect. For example, calling
        - * `specularMaterial(255, 255, 0)` would cause a shape to reflect red and
        - * green light, but not blue light.
        - *
        - * Unlike <a href="#/p5/ambientMaterial">ambientMaterial()</a>,
        - * `specularMaterial()` will reflect the full color of light sources including
        - * <a href="#/p5/directionalLight">directionalLight()</a>,
        - * <a href="#/p5/pointLight">pointLight()</a>,
        - * and <a href="#/p5/spotLight">spotLight()</a>. This is what gives it shapes
        - * their "shiny" appearance. The material’s shininess can be controlled by the
        - * <a href="#/p5/shininess">shininess()</a> function.
        - *
        - * `specularMaterial()` can be called three ways with different parameters to
        - * set the material’s color.
        - *
        - * The first way to call `specularMaterial()` has one parameter, `gray`.
        - * Grayscale values between 0 and 255, as in `specularMaterial(50)`, can be
        - * passed to set the material’s color. Higher grayscale values make shapes
        - * appear brighter.
        - *
        - * The second way to call `specularMaterial()` has one parameter, `color`. A
        - * <a href="#/p5.Color">p5.Color> object, an array of color values, or a CSS
        - * color string, as in `specularMaterial('magenta')`, can be passed to set the
        - * material’s color.
        - *
        - * The third way to call `specularMaterial()` has four parameters, `v1`, `v2`,
        - * `v3`, and `alpha`. `alpha` is optional. RGBA, HSBA, or HSLA values can be
        - * passed to set the material’s colors, as in `specularMaterial(255, 0, 0)` or
        - * `specularMaterial(255, 0, 0, 30)`. Color values will be interpreted using
        - * the current <a href="#/p5/colorMode">colorMode()</a>.
        - *
        - * @method specularMaterial
        - * @param  {Number} gray grayscale value between 0 (black) and 255 (white).
        - * @param  {Number} [alpha] alpha value in the current current
        - *                          <a href="#/p5/colorMode">colorMode()</a>.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click the canvas to apply a specular material.
        - *
        - * let isGlossy = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A red torus drawn on a gray background. It becomes glossy when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on a white point light at the top-right.
        - *   pointLight(255, 255, 255, 30, -40, 30);
        - *
        - *   // Add a glossy coat if the user has double-clicked.
        - *   if (isGlossy === true) {
        - *     specularMaterial(255);
        - *     shininess(50);
        - *   }
        - *
        - *   // Style the torus.
        - *   noStroke();
        - *   fill(255, 0, 0);
        - *
        - *   // Draw the torus.
        - *   torus(30);
        - * }
        - *
        - * // Make the torus glossy when the user double-clicks.
        - * function doubleClicked() {
        - *   isGlossy = true;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click the canvas to apply a specular material.
        - *
        - * let isGlossy = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'A red torus drawn on a gray background. It becomes glossy and reflects green light when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on a white point light at the top-right.
        - *   pointLight(255, 255, 255, 30, -40, 30);
        - *
        - *   // Add a glossy green coat if the user has double-clicked.
        - *   if (isGlossy === true) {
        - *     specularMaterial(0, 255, 0);
        - *     shininess(50);
        - *   }
        - *
        - *   // Style the torus.
        - *   noStroke();
        - *   fill(255, 0, 0);
        - *
        - *   // Draw the torus.
        - *   torus(30);
        - * }
        - *
        - * // Make the torus glossy when the user double-clicks.
        - * function doubleClicked() {
        - *   isGlossy = true;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click the canvas to apply a specular material.
        - *
        - * let isGlossy = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'A red torus drawn on a gray background. It becomes glossy and reflects green light when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on a white point light at the top-right.
        - *   pointLight(255, 255, 255, 30, -40, 30);
        - *
        - *   // Add a glossy green coat if the user has double-clicked.
        - *   if (isGlossy === true) {
        - *     // Create a p5.Color object.
        - *     let c = color('green');
        - *     specularMaterial(c);
        - *     shininess(50);
        - *   }
        - *
        - *   // Style the torus.
        - *   noStroke();
        - *   fill(255, 0, 0);
        - *
        - *   // Draw the torus.
        - *   torus(30);
        - * }
        - *
        - * // Make the torus glossy when the user double-clicks.
        - * function doubleClicked() {
        - *   isGlossy = true;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - * // Double-click the canvas to apply a specular material.
        - *
        - * let isGlossy = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'A red torus drawn on a gray background. It becomes glossy and reflects green light when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on a white point light at the top-right.
        - *   pointLight(255, 255, 255, 30, -40, 30);
        - *
        - *   // Add a glossy green coat if the user has double-clicked.
        - *   if (isGlossy === true) {
        - *     specularMaterial('#00FF00');
        - *     shininess(50);
        - *   }
        - *
        - *   // Style the torus.
        - *   noStroke();
        - *   fill(255, 0, 0);
        - *
        - *   // Draw the torus.
        - *   torus(30);
        - * }
        - *
        - * // Make the torus glossy when the user double-clicks.
        - * function doubleClicked() {
        - *   isGlossy = true;
        - * }
        - * </code>
        - * </div>
        - */
        +  /**
        +   * @method ambientMaterial
        +   * @param  {Number} gray grayscale value between 0 (black) and 255 (white).
        +   * @chainable
        +   */
         
        -/**
        - * @method specularMaterial
        - * @param  {Number}        v1      red or hue value in
        - *                                 the current <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}        v2      green or saturation value
        - *                                 in the current <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}        v3      blue, brightness, or lightness value
        - *                                 in the current <a href="#/p5/colorMode">colorMode()</a>.
        - * @param  {Number}        [alpha]
        - * @chainable
        - */
        +  /**
        +   * @method ambientMaterial
        +   * @param  {p5.Color|Number[]|String} color
        +   *           color as a <a href="#/p5.Color">p5.Color</a> object,
        +   *            an array of color values, or a CSS string.
        +   * @chainable
        +   */
        +  fn.ambientMaterial = function (v1, v2, v3) {
        +    this._assert3d('ambientMaterial');
        +    // p5._validateParameters('ambientMaterial', arguments);
         
        -/**
        - * @method specularMaterial
        - * @param  {p5.Color|Number[]|String} color
        - *           color as a <a href="#/p5.Color">p5.Color</a> object,
        - *            an array of color values, or a CSS string.
        - * @chainable
        - */
        -p5.prototype.specularMaterial = function (v1, v2, v3, alpha) {
        -  this._assert3d('specularMaterial');
        -  p5._validateParameters('specularMaterial', arguments);
        +    const color = fn.color.apply(this, arguments);
        +    this._renderer.states._hasSetAmbient = true;
        +    this._renderer.states.curAmbientColor = color._array;
        +    this._renderer.states._useNormalMaterial = false;
        +    this._renderer.states.enableLighting = true;
        +    this._renderer.states.fillColor = true;
        +    return this;
        +  };
         
        -  const color = p5.prototype.color.apply(this, arguments);
        -  this._renderer.curSpecularColor = color._array;
        -  this._renderer._useSpecularMaterial = true;
        -  this._renderer._useNormalMaterial = false;
        -  this._renderer._enableLighting = true;
        +  /**
        +   * Sets the emissive color of shapes’ surface material.
        +   *
        +   * The `emissiveMaterial()` color sets a color shapes display at full
        +   * strength, regardless of lighting. This can give the appearance that a shape
        +   * is glowing. However, emissive materials don’t actually emit light that
        +   * can affect surrounding objects.
        +   *
        +   * `emissiveMaterial()` can be called three ways with different parameters to
        +   * set the material’s color.
        +   *
        +   * The first way to call `emissiveMaterial()` has one parameter, `gray`.
        +   * Grayscale values between 0 and 255, as in `emissiveMaterial(50)`, can be
        +   * passed to set the material’s color. Higher grayscale values make shapes
        +   * appear brighter.
        +   *
        +   * The second way to call `emissiveMaterial()` has one parameter, `color`. A
        +   * <a href="#/p5.Color">p5.Color</a> object, an array of color values, or a
        +   * CSS color string, as in `emissiveMaterial('magenta')`, can be passed to set
        +   * the material’s color.
        +   *
        +   * The third way to call `emissiveMaterial()` has four parameters, `v1`, `v2`,
        +   * `v3`, and `alpha`. `alpha` is optional. RGBA, HSBA, or HSLA values can be
        +   * passed to set the material’s colors, as in `emissiveMaterial(255, 0, 0)` or
        +   * `emissiveMaterial(255, 0, 0, 30)`. Color values will be interpreted using
        +   * the current <a href="#/p5/colorMode">colorMode()</a>.
        +   *
        +   * Note: `emissiveMaterial()` can only be used in WebGL mode.
        +   *
        +   * @method emissiveMaterial
        +   * @param  {Number} v1       red or hue value in the current
        +   *                           <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number} v2       green or saturation value in the
        +   *                           current <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number} v3       blue, brightness, or lightness value in the
        +   *                           current <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number} [alpha]  alpha value in the current
        +   *                           <a href="#/p5/colorMode">colorMode()</a>.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A red cube drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on a white ambient light.
        +   *   ambientLight(255, 255, 255);
        +   *
        +   *   // Add a red emissive material using RGB values.
        +   *   emissiveMaterial(255, 0, 0);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -  return this;
        -};
        +  /**
        +   * @method emissiveMaterial
        +   * @param  {Number} gray grayscale value between 0 (black) and 255 (white).
        +   * @chainable
        +   */
         
        -/**
        - * Sets the amount of gloss ("shininess") of a
        - * <a href="#/p5/specularMaterial">specularMaterial()</a>.
        - *
        - * Shiny materials focus reflected light more than dull materials.
        - * `shininess()` affects the way materials reflect light sources including
        - * <a href="#/p5/directionalLight">directionalLight()</a>,
        - * <a href="#/p5/pointLight">pointLight()</a>,
        - * and <a href="#/p5/spotLight">spotLight()</a>.
        - *
        - * The parameter, `shine`, is a number that sets the amount of shininess.
        - * `shine` must be greater than 1, which is its default value.
        - *
        - * @method shininess
        - * @param {Number} shine amount of shine.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'Two red spheres drawn on a gray background. White light reflects from their surfaces as the mouse moves. The right sphere is shinier than the left sphere.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Turn on a red ambient light.
        - *   ambientLight(255, 0, 0);
        - *
        - *   // Get the mouse's coordinates.
        - *   let mx = mouseX - 50;
        - *   let my = mouseY - 50;
        - *
        - *   // Turn on a white point light that follows the mouse.
        - *   pointLight(255, 255, 255, mx, my, 50);
        - *
        - *   // Style the sphere.
        - *   noStroke();
        - *
        - *   // Add a specular material with a grayscale value.
        - *   specularMaterial(255);
        - *
        - *   // Draw the left sphere with low shininess.
        - *   translate(-25, 0, 0);
        - *   shininess(10);
        - *   sphere(20);
        - *
        - *   // Draw the right sphere with high shininess.
        - *   translate(50, 0, 0);
        - *   shininess(100);
        - *   sphere(20);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.shininess = function (shine) {
        -  this._assert3d('shininess');
        -  p5._validateParameters('shininess', arguments);
        +  /**
        +   * @method emissiveMaterial
        +   * @param  {p5.Color|Number[]|String} color
        +   *           color as a <a href="#/p5.Color">p5.Color</a> object,
        +   *            an array of color values, or a CSS string.
        +   * @chainable
        +   */
        +  fn.emissiveMaterial = function (v1, v2, v3, a) {
        +    this._assert3d('emissiveMaterial');
        +    // p5._validateParameters('emissiveMaterial', arguments);
         
        -  if (shine < 1) {
        -    shine = 1;
        -  }
        -  this._renderer._useShininess = shine;
        -  return this;
        -};
        +    const color = fn.color.apply(this, arguments);
        +    this._renderer.states.curEmissiveColor = color._array;
        +    this._renderer.states._useEmissiveMaterial = true;
        +    this._renderer.states._useNormalMaterial = false;
        +    this._renderer.states.enableLighting = true;
         
        -/**
        - * Sets the amount of "metalness" of a
        - * <a href="#/p5/specularMaterial">specularMaterial()</a>.
        - *
        - * `metalness()` can make materials appear more metallic. It affects the way
        - * materials reflect light sources including
        - * affects the way materials reflect light sources including
        - * <a href="#/p5/directionalLight">directionalLight()</a>,
        - * <a href="#/p5/pointLight">pointLight()</a>,
        - * <a href="#/p5/spotLight">spotLight()</a>, and
        - * <a href="#/p5/imageLight">imageLight()</a>.
        - *
        - * The parameter, `metallic`, is a number that sets the amount of metalness.
        - * `metallic` must be greater than 1, which is its default value. Higher
        - * values, such as `metalness(100)`, make specular materials appear more
        - * metallic.
        - *
        - * @method metalness
        - * @param {Number} metallic amount of metalness.
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'Two blue spheres drawn on a gray background. White light reflects from their surfaces as the mouse moves. The right sphere is more metallic than the left sphere.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Turn on an ambient light.
        - *   ambientLight(200);
        - *
        - *   // Get the mouse's coordinates.
        - *   let mx = mouseX - 50;
        - *   let my = mouseY - 50;
        - *
        - *   // Turn on a white point light that follows the mouse.
        - *   pointLight(255, 255, 255, mx, my, 50);
        - *
        - *   // Style the spheres.
        - *   noStroke();
        - *   fill(30, 30, 255);
        - *   specularMaterial(255);
        - *   shininess(20);
        - *
        - *   // Draw the left sphere with low metalness.
        - *   translate(-25, 0, 0);
        - *   metalness(1);
        - *   sphere(20);
        - *
        - *   // Draw the right sphere with high metalness.
        - *   translate(50, 0, 0);
        - *   metalness(50);
        - *   sphere(20);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let img;
        - *
        - * function preload() {
        - *   img = loadImage('assets/outdoor_spheremap.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100 ,100 ,WEBGL);
        - *
        - *   describe(
        - *     'Two spheres floating above a landscape. The surface of the spheres reflect the landscape. The right sphere is more reflective than the left sphere.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   // Add the panorama.
        - *   panorama(img);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Use the image as a light source.
        - *   imageLight(img);
        - *
        - *   // Style the spheres.
        - *   noStroke();
        - *   specularMaterial(50);
        - *   shininess(200);
        - *
        - *   // Draw the left sphere with low metalness.
        - *   translate(-25, 0, 0);
        - *   metalness(1);
        - *   sphere(20);
        - *
        - *   // Draw the right sphere with high metalness.
        - *   translate(50, 0, 0);
        - *   metalness(50);
        - *   sphere(20);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.metalness = function (metallic) {
        -  this._assert3d('metalness');
        -  const metalMix = 1 - Math.exp(-metallic / 100);
        -  this._renderer._useMetalness = metalMix;
        -  return this;
        -};
        +    return this;
        +  };
         
        -/**
        - * @private blends colors according to color components.
        - * If alpha value is less than 1, or non-standard blendMode
        - * we need to enable blending on our gl context.
        - * @param  {Number[]} color The currently set color, with values in 0-1 range
        - * @param  {Boolean} [hasTransparency] Whether the shape being drawn has other
        - * transparency internally, e.g. via vertex colors
        - * @return {Number[]}  Normalized numbers array
        - */
        -p5.RendererGL.prototype._applyColorBlend = function(colors, hasTransparency) {
        -  const gl = this.GL;
        -
        -  const isTexture = this.drawMode === constants.TEXTURE;
        -  const doBlend =
        -    hasTransparency ||
        -    this.userFillShader ||
        -    this.userStrokeShader ||
        -    this.userPointShader ||
        -    isTexture ||
        -    this.curBlendMode !== constants.BLEND ||
        -    colors[colors.length - 1] < 1.0 ||
        -    this._isErasing;
        -
        -  if (doBlend !== this._isBlending) {
        -    if (
        -      doBlend ||
        -      (this.curBlendMode !== constants.BLEND &&
        -        this.curBlendMode !== constants.ADD)
        -    ) {
        -      gl.enable(gl.BLEND);
        -    } else {
        -      gl.disable(gl.BLEND);
        -    }
        -    gl.depthMask(true);
        -    this._isBlending = doBlend;
        -  }
        -  this._applyBlendMode();
        -  return colors;
        -};
        +  /**
        +   * Sets the specular color of shapes’ surface material.
        +   *
        +   * The `specularMaterial()` color sets the components of light color that
        +   * glossy coats on shapes will reflect. For example, calling
        +   * `specularMaterial(255, 255, 0)` would cause a shape to reflect red and
        +   * green light, but not blue light.
        +   *
        +   * Unlike <a href="#/p5/ambientMaterial">ambientMaterial()</a>,
        +   * `specularMaterial()` will reflect the full color of light sources including
        +   * <a href="#/p5/directionalLight">directionalLight()</a>,
        +   * <a href="#/p5/pointLight">pointLight()</a>,
        +   * and <a href="#/p5/spotLight">spotLight()</a>. This is what gives it shapes
        +   * their "shiny" appearance. The material’s shininess can be controlled by the
        +   * <a href="#/p5/shininess">shininess()</a> function.
        +   *
        +   * `specularMaterial()` can be called three ways with different parameters to
        +   * set the material’s color.
        +   *
        +   * The first way to call `specularMaterial()` has one parameter, `gray`.
        +   * Grayscale values between 0 and 255, as in `specularMaterial(50)`, can be
        +   * passed to set the material’s color. Higher grayscale values make shapes
        +   * appear brighter.
        +   *
        +   * The second way to call `specularMaterial()` has one parameter, `color`. A
        +   * <a href="#/p5.Color">p5.Color> object, an array of color values, or a CSS
        +   * color string, as in `specularMaterial('magenta')`, can be passed to set the
        +   * material’s color.
        +   *
        +   * The third way to call `specularMaterial()` has four parameters, `v1`, `v2`,
        +   * `v3`, and `alpha`. `alpha` is optional. RGBA, HSBA, or HSLA values can be
        +   * passed to set the material’s colors, as in `specularMaterial(255, 0, 0)` or
        +   * `specularMaterial(255, 0, 0, 30)`. Color values will be interpreted using
        +   * the current <a href="#/p5/colorMode">colorMode()</a>.
        +   *
        +   * @method specularMaterial
        +   * @param  {Number} gray grayscale value between 0 (black) and 255 (white).
        +   * @param  {Number} [alpha] alpha value in the current current
        +   *                          <a href="#/p5/colorMode">colorMode()</a>.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click the canvas to apply a specular material.
        +   *
        +   * let isGlossy = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A red torus drawn on a gray background. It becomes glossy when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on a white point light at the top-right.
        +   *   pointLight(255, 255, 255, 30, -40, 30);
        +   *
        +   *   // Add a glossy coat if the user has double-clicked.
        +   *   if (isGlossy === true) {
        +   *     specularMaterial(255);
        +   *     shininess(50);
        +   *   }
        +   *
        +   *   // Style the torus.
        +   *   noStroke();
        +   *   fill(255, 0, 0);
        +   *
        +   *   // Draw the torus.
        +   *   torus(30);
        +   * }
        +   *
        +   * // Make the torus glossy when the user double-clicks.
        +   * function doubleClicked() {
        +   *   isGlossy = true;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click the canvas to apply a specular material.
        +   *
        +   * let isGlossy = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'A red torus drawn on a gray background. It becomes glossy and reflects green light when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on a white point light at the top-right.
        +   *   pointLight(255, 255, 255, 30, -40, 30);
        +   *
        +   *   // Add a glossy green coat if the user has double-clicked.
        +   *   if (isGlossy === true) {
        +   *     specularMaterial(0, 255, 0);
        +   *     shininess(50);
        +   *   }
        +   *
        +   *   // Style the torus.
        +   *   noStroke();
        +   *   fill(255, 0, 0);
        +   *
        +   *   // Draw the torus.
        +   *   torus(30);
        +   * }
        +   *
        +   * // Make the torus glossy when the user double-clicks.
        +   * function doubleClicked() {
        +   *   isGlossy = true;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click the canvas to apply a specular material.
        +   *
        +   * let isGlossy = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'A red torus drawn on a gray background. It becomes glossy and reflects green light when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on a white point light at the top-right.
        +   *   pointLight(255, 255, 255, 30, -40, 30);
        +   *
        +   *   // Add a glossy green coat if the user has double-clicked.
        +   *   if (isGlossy === true) {
        +   *     // Create a p5.Color object.
        +   *     let c = color('green');
        +   *     specularMaterial(c);
        +   *     shininess(50);
        +   *   }
        +   *
        +   *   // Style the torus.
        +   *   noStroke();
        +   *   fill(255, 0, 0);
        +   *
        +   *   // Draw the torus.
        +   *   torus(30);
        +   * }
        +   *
        +   * // Make the torus glossy when the user double-clicks.
        +   * function doubleClicked() {
        +   *   isGlossy = true;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   * // Double-click the canvas to apply a specular material.
        +   *
        +   * let isGlossy = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'A red torus drawn on a gray background. It becomes glossy and reflects green light when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on a white point light at the top-right.
        +   *   pointLight(255, 255, 255, 30, -40, 30);
        +   *
        +   *   // Add a glossy green coat if the user has double-clicked.
        +   *   if (isGlossy === true) {
        +   *     specularMaterial('#00FF00');
        +   *     shininess(50);
        +   *   }
        +   *
        +   *   // Style the torus.
        +   *   noStroke();
        +   *   fill(255, 0, 0);
        +   *
        +   *   // Draw the torus.
        +   *   torus(30);
        +   * }
        +   *
        +   * // Make the torus glossy when the user double-clicks.
        +   * function doubleClicked() {
        +   *   isGlossy = true;
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -/**
        - * @private sets blending in gl context to curBlendMode
        - * @param  {Number[]} color [description]
        - * @return {Number[]}  Normalized numbers array
        - */
        -p5.RendererGL.prototype._applyBlendMode = function () {
        -  if (this._cachedBlendMode === this.curBlendMode) {
        -    return;
        -  }
        -  const gl = this.GL;
        -  switch (this.curBlendMode) {
        -    case constants.BLEND:
        -      gl.blendEquation(gl.FUNC_ADD);
        -      gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
        -      break;
        -    case constants.ADD:
        -      gl.blendEquation(gl.FUNC_ADD);
        -      gl.blendFunc(gl.ONE, gl.ONE);
        -      break;
        -    case constants.REMOVE:
        -      gl.blendEquation(gl.FUNC_ADD);
        -      gl.blendFunc(gl.ZERO, gl.ONE_MINUS_SRC_ALPHA);
        -      break;
        -    case constants.MULTIPLY:
        -      gl.blendEquation(gl.FUNC_ADD);
        -      gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA);
        -      break;
        -    case constants.SCREEN:
        -      gl.blendEquation(gl.FUNC_ADD);
        -      gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_COLOR);
        -      break;
        -    case constants.EXCLUSION:
        -      gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
        -      gl.blendFuncSeparate(
        -        gl.ONE_MINUS_DST_COLOR,
        -        gl.ONE_MINUS_SRC_COLOR,
        -        gl.ONE,
        -        gl.ONE
        -      );
        -      break;
        -    case constants.REPLACE:
        -      gl.blendEquation(gl.FUNC_ADD);
        -      gl.blendFunc(gl.ONE, gl.ZERO);
        -      break;
        -    case constants.SUBTRACT:
        -      gl.blendEquationSeparate(gl.FUNC_REVERSE_SUBTRACT, gl.FUNC_ADD);
        -      gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
        -      break;
        -    case constants.DARKEST:
        -      if (this.blendExt) {
        -        gl.blendEquationSeparate(
        -          this.blendExt.MIN || this.blendExt.MIN_EXT,
        -          gl.FUNC_ADD
        -        );
        -        gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
        +  /**
        +   * @method specularMaterial
        +   * @param  {Number}        v1      red or hue value in
        +   *                                 the current <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}        v2      green or saturation value
        +   *                                 in the current <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}        v3      blue, brightness, or lightness value
        +   *                                 in the current <a href="#/p5/colorMode">colorMode()</a>.
        +   * @param  {Number}        [alpha]
        +   * @chainable
        +   */
        +
        +  /**
        +   * @method specularMaterial
        +   * @param  {p5.Color|Number[]|String} color
        +   *           color as a <a href="#/p5.Color">p5.Color</a> object,
        +   *            an array of color values, or a CSS string.
        +   * @chainable
        +   */
        +  fn.specularMaterial = function (v1, v2, v3, alpha) {
        +    this._assert3d('specularMaterial');
        +    // p5._validateParameters('specularMaterial', arguments);
        +
        +    const color = fn.color.apply(this, arguments);
        +    this._renderer.states.curSpecularColor = color._array;
        +    this._renderer.states._useSpecularMaterial = true;
        +    this._renderer.states._useNormalMaterial = false;
        +    this._renderer.states.enableLighting = true;
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Sets the amount of gloss ("shininess") of a
        +   * <a href="#/p5/specularMaterial">specularMaterial()</a>.
        +   *
        +   * Shiny materials focus reflected light more than dull materials.
        +   * `shininess()` affects the way materials reflect light sources including
        +   * <a href="#/p5/directionalLight">directionalLight()</a>,
        +   * <a href="#/p5/pointLight">pointLight()</a>,
        +   * and <a href="#/p5/spotLight">spotLight()</a>.
        +   *
        +   * The parameter, `shine`, is a number that sets the amount of shininess.
        +   * `shine` must be greater than 1, which is its default value.
        +   *
        +   * @method shininess
        +   * @param {Number} shine amount of shine.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'Two red spheres drawn on a gray background. White light reflects from their surfaces as the mouse moves. The right sphere is shinier than the left sphere.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Turn on a red ambient light.
        +   *   ambientLight(255, 0, 0);
        +   *
        +   *   // Get the mouse's coordinates.
        +   *   let mx = mouseX - 50;
        +   *   let my = mouseY - 50;
        +   *
        +   *   // Turn on a white point light that follows the mouse.
        +   *   pointLight(255, 255, 255, mx, my, 50);
        +   *
        +   *   // Style the sphere.
        +   *   noStroke();
        +   *
        +   *   // Add a specular material with a grayscale value.
        +   *   specularMaterial(255);
        +   *
        +   *   // Draw the left sphere with low shininess.
        +   *   translate(-25, 0, 0);
        +   *   shininess(10);
        +   *   sphere(20);
        +   *
        +   *   // Draw the right sphere with high shininess.
        +   *   translate(50, 0, 0);
        +   *   shininess(100);
        +   *   sphere(20);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.shininess = function (shine) {
        +    this._assert3d('shininess');
        +    // p5._validateParameters('shininess', arguments);
        +
        +    this._renderer.shininess(shine);
        +
        +    return this;
        +  };
        +
        +  /**
        +   * Sets the amount of "metalness" of a
        +   * <a href="#/p5/specularMaterial">specularMaterial()</a>.
        +   *
        +   * `metalness()` can make materials appear more metallic. It affects the way
        +   * materials reflect light sources including
        +   * affects the way materials reflect light sources including
        +   * <a href="#/p5/directionalLight">directionalLight()</a>,
        +   * <a href="#/p5/pointLight">pointLight()</a>,
        +   * <a href="#/p5/spotLight">spotLight()</a>, and
        +   * <a href="#/p5/imageLight">imageLight()</a>.
        +   *
        +   * The parameter, `metallic`, is a number that sets the amount of metalness.
        +   * `metallic` must be greater than 1, which is its default value. Higher
        +   * values, such as `metalness(100)`, make specular materials appear more
        +   * metallic.
        +   *
        +   * @method metalness
        +   * @param {Number} metallic amount of metalness.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'Two blue spheres drawn on a gray background. White light reflects from their surfaces as the mouse moves. The right sphere is more metallic than the left sphere.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Turn on an ambient light.
        +   *   ambientLight(200);
        +   *
        +   *   // Get the mouse's coordinates.
        +   *   let mx = mouseX - 50;
        +   *   let my = mouseY - 50;
        +   *
        +   *   // Turn on a white point light that follows the mouse.
        +   *   pointLight(255, 255, 255, mx, my, 50);
        +   *
        +   *   // Style the spheres.
        +   *   noStroke();
        +   *   fill(30, 30, 255);
        +   *   specularMaterial(255);
        +   *   shininess(20);
        +   *
        +   *   // Draw the left sphere with low metalness.
        +   *   translate(-25, 0, 0);
        +   *   metalness(1);
        +   *   sphere(20);
        +   *
        +   *   // Draw the right sphere with high metalness.
        +   *   translate(50, 0, 0);
        +   *   metalness(50);
        +   *   sphere(20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let img;
        +   *
        +   * function preload() {
        +   *   img = loadImage('assets/outdoor_spheremap.jpg');
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100 ,100 ,WEBGL);
        +   *
        +   *   describe(
        +   *     'Two spheres floating above a landscape. The surface of the spheres reflect the landscape. The right sphere is more reflective than the left sphere.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   // Add the panorama.
        +   *   panorama(img);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Use the image as a light source.
        +   *   imageLight(img);
        +   *
        +   *   // Style the spheres.
        +   *   noStroke();
        +   *   specularMaterial(50);
        +   *   shininess(200);
        +   *
        +   *   // Draw the left sphere with low metalness.
        +   *   translate(-25, 0, 0);
        +   *   metalness(1);
        +   *   sphere(20);
        +   *
        +   *   // Draw the right sphere with high metalness.
        +   *   translate(50, 0, 0);
        +   *   metalness(50);
        +   *   sphere(20);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.metalness = function (metallic) {
        +    this._assert3d('metalness');
        +
        +    this._renderer.metalness(metallic);
        +
        +    return this;
        +  };
        +
        +
        +  /**
        +   * @private blends colors according to color components.
        +   * If alpha value is less than 1, or non-standard blendMode
        +   * we need to enable blending on our gl context.
        +   * @param  {Number[]} color The currently set color, with values in 0-1 range
        +   * @param  {Boolean} [hasTransparency] Whether the shape being drawn has other
        +   * transparency internally, e.g. via vertex colors
        +   * @return {Number[]}  Normalized numbers array
        +   */
        +  RendererGL.prototype._applyColorBlend = function (colors, hasTransparency) {
        +    const gl = this.GL;
        +
        +    const isTexture = this.states.drawMode === constants.TEXTURE;
        +    const doBlend =
        +      hasTransparency ||
        +      this.states.userFillShader ||
        +      this.states.userStrokeShader ||
        +      this.states.userPointShader ||
        +      isTexture ||
        +      this.states.curBlendMode !== constants.BLEND ||
        +      colors[colors.length - 1] < 1.0 ||
        +      this._isErasing;
        +
        +    if (doBlend !== this._isBlending) {
        +      if (
        +        doBlend ||
        +        (this.states.curBlendMode !== constants.BLEND &&
        +          this.states.curBlendMode !== constants.ADD)
        +      ) {
        +        gl.enable(gl.BLEND);
               } else {
        -        console.warn(
        -          'blendMode(DARKEST) does not work in your browser in WEBGL mode.'
        -        );
        +        gl.disable(gl.BLEND);
               }
        -      break;
        -    case constants.LIGHTEST:
        -      if (this.blendExt) {
        -        gl.blendEquationSeparate(
        -          this.blendExt.MAX || this.blendExt.MAX_EXT,
        -          gl.FUNC_ADD
        +      gl.depthMask(true);
        +      this._isBlending = doBlend;
        +    }
        +    this._applyBlendMode();
        +    return colors;
        +  };
        +
        +  /**
        +   * @private sets blending in gl context to curBlendMode
        +   * @param  {Number[]} color [description]
        +   * @return {Number[]}  Normalized numbers array
        +   */
        +  RendererGL.prototype._applyBlendMode = function () {
        +    if (this._cachedBlendMode === this.states.curBlendMode) {
        +      return;
        +    }
        +    const gl = this.GL;
        +    switch (this.states.curBlendMode) {
        +      case constants.BLEND:
        +        gl.blendEquation(gl.FUNC_ADD);
        +        gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
        +        break;
        +      case constants.ADD:
        +        gl.blendEquation(gl.FUNC_ADD);
        +        gl.blendFunc(gl.ONE, gl.ONE);
        +        break;
        +      case constants.REMOVE:
        +        gl.blendEquation(gl.FUNC_ADD);
        +        gl.blendFunc(gl.ZERO, gl.ONE_MINUS_SRC_ALPHA);
        +        break;
        +      case constants.MULTIPLY:
        +        gl.blendEquation(gl.FUNC_ADD);
        +        gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA);
        +        break;
        +      case constants.SCREEN:
        +        gl.blendEquation(gl.FUNC_ADD);
        +        gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_COLOR);
        +        break;
        +      case constants.EXCLUSION:
        +        gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
        +        gl.blendFuncSeparate(
        +          gl.ONE_MINUS_DST_COLOR,
        +          gl.ONE_MINUS_SRC_COLOR,
        +          gl.ONE,
        +          gl.ONE
                 );
        -        gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
        -      } else {
        -        console.warn(
        -          'blendMode(LIGHTEST) does not work in your browser in WEBGL mode.'
        +        break;
        +      case constants.REPLACE:
        +        gl.blendEquation(gl.FUNC_ADD);
        +        gl.blendFunc(gl.ONE, gl.ZERO);
        +        break;
        +      case constants.SUBTRACT:
        +        gl.blendEquationSeparate(gl.FUNC_REVERSE_SUBTRACT, gl.FUNC_ADD);
        +        gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
        +        break;
        +      case constants.DARKEST:
        +        if (this.blendExt) {
        +          gl.blendEquationSeparate(
        +            this.blendExt.MIN || this.blendExt.MIN_EXT,
        +            gl.FUNC_ADD
        +          );
        +          gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
        +        } else {
        +          console.warn(
        +            'blendMode(DARKEST) does not work in your browser in WEBGL mode.'
        +          );
        +        }
        +        break;
        +      case constants.LIGHTEST:
        +        if (this.blendExt) {
        +          gl.blendEquationSeparate(
        +            this.blendExt.MAX || this.blendExt.MAX_EXT,
        +            gl.FUNC_ADD
        +          );
        +          gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
        +        } else {
        +          console.warn(
        +            'blendMode(LIGHTEST) does not work in your browser in WEBGL mode.'
        +          );
        +        }
        +        break;
        +      default:
        +        console.error(
        +          'Oops! Somehow RendererGL set curBlendMode to an unsupported mode.'
                 );
        -      }
        -      break;
        -    default:
        -      console.error(
        -        'Oops! Somehow RendererGL set curBlendMode to an unsupported mode.'
        -      );
        -      break;
        +        break;
        +    }
        +    this._cachedBlendMode = this.states.curBlendMode;
        +  };
        +
        +  RendererGL.prototype.shader = function(s) {
        +    // Always set the shader as a fill shader
        +    this.states.userFillShader = s;
        +    this.states._useNormalMaterial = false;
        +    s.ensureCompiledOnContext(this);
        +    s.setDefaultUniforms();
        +  }
        +
        +  RendererGL.prototype.strokeShader = function(s) {
        +    this.states.userStrokeShader = s;
        +    s.ensureCompiledOnContext(this);
        +    s.setDefaultUniforms();
        +  }
        +
        +  RendererGL.prototype.imageShader = function(s) {
        +    this.states.userImageShader = s;
        +    s.ensureCompiledOnContext(this);
        +    s.setDefaultUniforms();
           }
        -  if (!this._isErasing) {
        -    this._cachedBlendMode = this.curBlendMode;
        +
        +  RendererGL.prototype.resetShader = function() {
        +    this.states.userFillShader = null;
        +    this.states.userStrokeShader = null;
        +    this.states.userImageShader = null;
        +  }
        +
        +  RendererGL.prototype.texture = function(tex) {
        +    this.states.drawMode = constants.TEXTURE;
        +    this.states._useNormalMaterial = false;
        +    this.states._tex = tex;
        +    this.states.fillColor = new Color([1, 1, 1]);
        +  };
        +
        +  RendererGL.prototype.normalMaterial = function(...args) {
        +    this.states.drawMode = constants.FILL;
        +    this.states._useSpecularMaterial = false;
        +    this.states._useEmissiveMaterial = false;
        +    this.states._useNormalMaterial = true;
        +    this.states.curFillColor = [1, 1, 1, 1];
        +    this.states.fillColor = new Color([1, 1, 1]);
        +    this.states.strokeColor = null;
           }
        -};
         
        -export default p5;
        +  // RendererGL.prototype.ambientMaterial = function(v1, v2, v3) {
        +  // }
        +
        +  // RendererGL.prototype.emissiveMaterial = function(v1, v2, v3, a) {
        +  // }
        +
        +  // RendererGL.prototype.specularMaterial = function(v1, v2, v3, alpha) {
        +  // }
        +
        +  RendererGL.prototype.shininess = function(shine) {
        +    if (shine < 1) {
        +      shine = 1;
        +    }
        +    this.states._useShininess = shine;
        +  }
        +
        +  RendererGL.prototype.metalness = function(metallic) {
        +    const metalMix = 1 - Math.exp(-metallic / 100);
        +    this.states._useMetalness = metalMix;
        +  }
        +}
        +
        +export default material;
        +
        +if(typeof p5 !== 'undefined'){
        +  loading(p5, p5.prototype);
        +}
        diff --git a/src/webgl/p5.Camera.js b/src/webgl/p5.Camera.js
        index 1fb6c5012d..aa0a41e40c 100644
        --- a/src/webgl/p5.Camera.js
        +++ b/src/webgl/p5.Camera.js
        @@ -4,2028 +4,1144 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        +import { Matrix } from '../math/p5.Matrix';
        +import { Vector } from '../math/p5.Vector';
        +import { Quat } from './p5.Quat';
        +import { RendererGL } from './p5.RendererGL';
         
        -////////////////////////////////////////////////////////////////////////////////
        -// p5.Prototype Methods
        -////////////////////////////////////////////////////////////////////////////////
        -
        -/**
        - * Sets the position and orientation of the current camera in a 3D sketch.
        - *
        - * `camera()` allows objects to be viewed from different angles. It has nine
        - * parameters that are all optional.
        - *
        - * The first three parameters, `x`, `y`, and `z`, are the coordinates of the
        - * camera’s position. For example, calling `camera(0, 0, 0)` places the camera
        - * at the origin `(0, 0, 0)`. By default, the camera is placed at
        - * `(0, 0, 800)`.
        - *
        - * The next three parameters, `centerX`, `centerY`, and `centerZ` are the
        - * coordinates of the point where the camera faces. For example, calling
        - * `camera(0, 0, 0, 10, 20, 30)` places the camera at the origin `(0, 0, 0)`
        - * and points it at `(10, 20, 30)`. By default, the camera points at the
        - * origin `(0, 0, 0)`.
        - *
        - * The last three parameters, `upX`, `upY`, and `upZ` are the components of
        - * the "up" vector. The "up" vector orients the camera’s y-axis. For example,
        - * calling `camera(0, 0, 0, 10, 20, 30, 0, -1, 0)` places the camera at the
        - * origin `(0, 0, 0)`, points it at `(10, 20, 30)`, and sets the "up" vector
        - * to `(0, -1, 0)` which is like holding it upside-down. By default, the "up"
        - * vector is `(0, 1, 0)`.
        - *
        - * Note: `camera()` can only be used in WebGL mode.
        - *
        - * @method camera
        - * @constructor
        - * @for p5
        - * @param  {Number} [x]        x-coordinate of the camera. Defaults to 0.
        - * @param  {Number} [y]        y-coordinate of the camera. Defaults to 0.
        - * @param  {Number} [z]        z-coordinate of the camera. Defaults to 800.
        - * @param  {Number} [centerX]  x-coordinate of the point the camera faces. Defaults to 0.
        - * @param  {Number} [centerY]  y-coordinate of the point the camera faces. Defaults to 0.
        - * @param  {Number} [centerZ]  z-coordinate of the point the camera faces. Defaults to 0.
        - * @param  {Number} [upX]      x-component of the camera’s "up" vector. Defaults to 0.
        - * @param  {Number} [upY]      y-component of the camera’s "up" vector. Defaults to 1.
        - * @param  {Number} [upZ]      z-component of the camera’s "up" vector. Defaults to 0.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Move the camera to the top-right.
        - *   camera(200, -400, 800);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube apperas to sway left and right on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the camera's x-coordinate.
        - *   let x = 400 * cos(frameCount * 0.01);
        - *
        - *   // Orbit the camera around the box.
        - *   camera(x, -400, 800);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Adjust the range sliders to change the camera's position.
        - *
        - * let xSlider;
        - * let ySlider;
        - * let zSlider;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create slider objects to set the camera's coordinates.
        - *   xSlider = createSlider(-400, 400, 400);
        - *   xSlider.position(0, 100);
        - *   xSlider.size(100);
        - *   ySlider = createSlider(-400, 400, -200);
        - *   ySlider.position(0, 120);
        - *   ySlider.size(100);
        - *   zSlider = createSlider(0, 1600, 800);
        - *   zSlider.position(0, 140);
        - *   zSlider.size(100);
        - *
        - *   describe(
        - *     'A white cube drawn against a gray background. Three range sliders appear beneath the image. The camera position changes when the user moves the sliders.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Get the camera's coordinates from the sliders.
        - *   let x = xSlider.value();
        - *   let y = ySlider.value();
        - *   let z = zSlider.value();
        - *
        - *   // Move the camera.
        - *   camera(x, y, z);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.camera = function (...args) {
        -  this._assert3d('camera');
        -  p5._validateParameters('camera', args);
        -  this._renderer._curCamera.camera(...args);
        -  return this;
        -};
        -
        -/**
        - * Sets a perspective projection for the current camera in a 3D sketch.
        - *
        - * In a perspective projection, shapes that are further from the camera appear
        - * smaller than shapes that are near the camera. This technique, called
        - * foreshortening, creates realistic 3D scenes. It’s applied by default in
        - * WebGL mode.
        - *
        - * `perspective()` changes the camera’s perspective by changing its viewing
        - * frustum. The frustum is the volume of space that’s visible to the camera.
        - * Its shape is a pyramid with its top cut off. The camera is placed where
        - * the top of the pyramid should be and views everything between the frustum’s
        - * top (near) plane and its bottom (far) plane.
        - *
        - * The first parameter, `fovy`, is the camera’s vertical field of view. It’s
        - * an angle that describes how tall or narrow a view the camera has. For
        - * example, calling `perspective(0.5)` sets the camera’s vertical field of
        - * view to 0.5 radians. By default, `fovy` is calculated based on the sketch’s
        - * height and the camera’s default z-coordinate, which is 800. The formula for
        - * the default `fovy` is `2 * atan(height / 2 / 800)`.
        - *
        - * The second parameter, `aspect`, is the camera’s aspect ratio. It’s a number
        - * that describes the ratio of the top plane’s width to its height. For
        - * example, calling `perspective(0.5, 1.5)` sets the camera’s field of view to
        - * 0.5 radians and aspect ratio to 1.5, which would make shapes appear thinner
        - * on a square canvas. By default, aspect is set to `width / height`.
        - *
        - * The third parameter, `near`, is the distance from the camera to the near
        - * plane. For example, calling `perspective(0.5, 1.5, 100)` sets the camera’s
        - * field of view to 0.5 radians, its aspect ratio to 1.5, and places the near
        - * plane 100 pixels from the camera. Any shapes drawn less than 100 pixels
        - * from the camera won’t be visible. By default, near is set to `0.1 * 800`,
        - * which is 1/10th the default distance between the camera and the origin.
        - *
        - * The fourth parameter, `far`, is the distance from the camera to the far
        - * plane. For example, calling `perspective(0.5, 1.5, 100, 10000)` sets the
        - * camera’s field of view to 0.5 radians, its aspect ratio to 1.5, places the
        - * near plane 100 pixels from the camera, and places the far plane 10,000
        - * pixels from the camera. Any shapes drawn more than 10,000 pixels from the
        - * camera won’t be visible. By default, far is set to `10 * 800`, which is 10
        - * times the default distance between the camera and the origin.
        - *
        - * Note: `perspective()` can only be used in WebGL mode.
        - *
        - * @method  perspective
        - * @for p5
        - * @param  {Number} [fovy]   camera frustum vertical field of view. Defaults to
        - *                           `2 * atan(height / 2 / 800)`.
        - * @param  {Number} [aspect] camera frustum aspect ratio. Defaults to
        - *                           `width / height`.
        - * @param  {Number} [near]   distance from the camera to the near clipping plane.
        - *                           Defaults to `0.1 * 800`.
        - * @param  {Number} [far]    distance from the camera to the far clipping plane.
        - *                           Defaults to `10 * 800`.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to squeeze the box.
        - *
        - * let isSqueezed = false;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white rectangular prism on a gray background. The box appears to become thinner when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Place the camera at the top-right.
        - *   camera(400, -400, 800);
        - *
        - *   if (isSqueezed === true) {
        - *     // Set fovy to 0.2.
        - *     // Set aspect to 1.5.
        - *     perspective(0.2, 1.5);
        - *   }
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - *
        - * // Change the camera's perspective when the user double-clicks.
        - * function doubleClicked() {
        - *   isSqueezed = true;
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white rectangular prism on a gray background. The prism moves away from the camera until it disappears.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Place the camera at the top-right.
        - *   camera(400, -400, 800);
        - *
        - *   // Set fovy to 0.2.
        - *   // Set aspect to 1.5.
        - *   // Set near to 600.
        - *   // Set far to 1200.
        - *   perspective(0.2, 1.5, 600, 1200);
        - *
        - *   // Move the origin away from the camera.
        - *   let x = -frameCount;
        - *   let y = frameCount;
        - *   let z = -2 * frameCount;
        - *   translate(x, y, z);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.perspective = function (...args) {
        -  this._assert3d('perspective');
        -  p5._validateParameters('perspective', args);
        -  this._renderer._curCamera.perspective(...args);
        -  return this;
        -};
        -
        -
        -/**
        - * Enables or disables perspective for lines in 3D sketches.
        - *
        - * In WebGL mode, lines can be drawn with a thinner stroke when they’re
        - * further from the camera. Doing so gives them a more realistic appearance.
        - *
        - * By default, lines are drawn differently based on the type of perspective
        - * being used:
        - * - `perspective()` and `frustum()` simulate a realistic perspective. In
        - * these modes, stroke weight is affected by the line’s distance from the
        - * camera. Doing so results in a more natural appearance. `perspective()` is
        - * the default mode for 3D sketches.
        - * - `ortho()` doesn’t simulate a realistic perspective. In this mode, stroke
        - * weights are consistent regardless of the line’s distance from the camera.
        - * Doing so results in a more predictable and consistent appearance.
        - *
        - * `linePerspective()` can override the default line drawing mode.
        - *
        - * The parameter, `enable`, is optional. It’s a `Boolean` value that sets the
        - * way lines are drawn. If `true` is passed, as in `linePerspective(true)`,
        - * then lines will appear thinner when they are further from the camera. If
        - * `false` is passed, as in `linePerspective(false)`, then lines will have
        - * consistent stroke weights regardless of their distance from the camera. By
        - * default, `linePerspective()` is enabled.
        - *
        - * Calling `linePerspective()` without passing an argument returns `true` if
        - * it's enabled and `false` if not.
        - *
        - * Note: `linePerspective()` can only be used in WebGL mode.
        - *
        - * @method linePerspective
        - * @for p5
        - * @param {boolean} enable whether to enable line perspective.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click the canvas to toggle the line perspective.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'A white cube with black edges on a gray background. Its edges toggle between thick and thin when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 600);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -40);
        - *     box(10);
        - *   }
        - * }
        - *
        - * // Toggle the line perspective when the user double-clicks.
        - * function doubleClicked() {
        - *   let isEnabled = linePerspective();
        - *   linePerspective(!isEnabled);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Double-click the canvas to toggle the line perspective.
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe(
        - *     'A row of cubes with black edges on a gray background. Their edges toggle between thick and thin when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Use an orthographic projection.
        - *   ortho();
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 600);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -40);
        - *     box(10);
        - *   }
        - * }
        - *
        - * // Toggle the line perspective when the user double-clicks.
        - * function doubleClicked() {
        - *   let isEnabled = linePerspective();
        - *   linePerspective(!isEnabled);
        - * }
        - * </code>
        - * </div>
        - */
        -/**
        - * @method linePerspective
        - * @return {boolean} whether line perspective is enabled.
        - */
        -
        -p5.prototype.linePerspective = function (enable) {
        -  p5._validateParameters('linePerspective', arguments);
        -  if (!(this._renderer instanceof p5.RendererGL)) {
        -    throw new Error('linePerspective() must be called in WebGL mode.');
        -  }
        -  if (enable !== undefined) {
        -    // Set the line perspective if enable is provided
        -    this._renderer._curCamera.useLinePerspective = enable;
        -  } else {
        -    // If no argument is provided, return the current value
        -    return this._renderer._curCamera.useLinePerspective;
        -  }
        -};
        -
        -
        -/**
        - * Sets an orthographic projection for the current camera in a 3D sketch.
        - *
        - * In an orthographic projection, shapes with the same size always appear the
        - * same size, regardless of whether they are near or far from the camera.
        - *
        - * `ortho()` changes the camera’s perspective by changing its viewing frustum
        - * from a truncated pyramid to a rectangular prism. The camera is placed in
        - * front of the frustum and views everything between the frustum’s near plane
        - * and its far plane. `ortho()` has six optional parameters to define the
        - * frustum.
        - *
        - * The first four parameters, `left`, `right`, `bottom`, and `top`, set the
        - * coordinates of the frustum’s sides, bottom, and top. For example, calling
        - * `ortho(-100, 100, 200, -200)` creates a frustum that’s 200 pixels wide and
        - * 400 pixels tall. By default, these coordinates are set based on the
        - * sketch’s width and height, as in
        - * `ortho(-width / 2, width / 2, -height / 2, height / 2)`.
        - *
        - * The last two parameters, `near` and `far`, set the distance of the
        - * frustum’s near and far plane from the camera. For example, calling
        - * `ortho(-100, 100, 200, 200, 50, 1000)` creates a frustum that’s 200 pixels
        - * wide, 400 pixels tall, starts 50 pixels from the camera, and ends 1,000
        - * pixels from the camera. By default, `near` and `far` are set to 0 and
        - * `max(width, height) + 800`, respectively.
        - *
        - * Note: `ortho()` can only be used in WebGL mode.
        - *
        - * @method  ortho
        - * @for p5
        - * @param  {Number} [left]   x-coordinate of the frustum’s left plane. Defaults to `-width / 2`.
        - * @param  {Number} [right]  x-coordinate of the frustum’s right plane. Defaults to `width / 2`.
        - * @param  {Number} [bottom] y-coordinate of the frustum’s bottom plane. Defaults to `height / 2`.
        - * @param  {Number} [top]    y-coordinate of the frustum’s top plane. Defaults to `-height / 2`.
        - * @param  {Number} [near]   z-coordinate of the frustum’s near plane. Defaults to 0.
        - * @param  {Number} [far]    z-coordinate of the frustum’s far plane. Defaults to `max(width, height) + 800`.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A row of tiny, white cubes on a gray background. All the cubes appear the same size.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Apply an orthographic projection.
        - *   ortho();
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 600);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -40);
        - *     box(10);
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Apply an orthographic projection.
        - *   // Center the frustum.
        - *   // Set its width and height to 20.
        - *   // Place its near plane 300 pixels from the camera.
        - *   // Place its far plane 350 pixels from the camera.
        - *   ortho(-10, 10, -10, 10, 300, 350);
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 600);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -40);
        - *     box(10);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.ortho = function (...args) {
        -  this._assert3d('ortho');
        -  p5._validateParameters('ortho', args);
        -  this._renderer._curCamera.ortho(...args);
        -  return this;
        -};
        -
        -/**
        - * Sets the frustum of the current camera in a 3D sketch.
        - *
        - * In a frustum projection, shapes that are further from the camera appear
        - * smaller than shapes that are near the camera. This technique, called
        - * foreshortening, creates realistic 3D scenes.
        - *
        - * `frustum()` changes the default camera’s perspective by changing its
        - * viewing frustum. The frustum is the volume of space that’s visible to the
        - * camera. The frustum’s shape is a pyramid with its top cut off. The camera
        - * is placed where the top of the pyramid should be and points towards the
        - * base of the pyramid. It views everything within the frustum.
        - *
        - * The first four parameters, `left`, `right`, `bottom`, and `top`, set the
        - * coordinates of the frustum’s sides, bottom, and top. For example, calling
        - * `frustum(-100, 100, 200, -200)` creates a frustum that’s 200 pixels wide
        - * and 400 pixels tall. By default, these coordinates are set based on the
        - * sketch’s width and height, as in
        - * `ortho(-width / 20, width / 20, height / 20, -height / 20)`.
        - *
        - * The last two parameters, `near` and `far`, set the distance of the
        - * frustum’s near and far plane from the camera. For example, calling
        - * `ortho(-100, 100, 200, -200, 50, 1000)` creates a frustum that’s 200 pixels
        - * wide, 400 pixels tall, starts 50 pixels from the camera, and ends 1,000
        - * pixels from the camera. By default, near is set to `0.1 * 800`, which is
        - * 1/10th the default distance between the camera and the origin. `far` is set
        - * to `10 * 800`, which is 10 times the default distance between the camera
        - * and the origin.
        - *
        - * Note: `frustum()` can only be used in WebGL mode.
        - *
        - * @method frustum
        - * @for p5
        - * @param  {Number} [left]   x-coordinate of the frustum’s left plane. Defaults to `-width / 20`.
        - * @param  {Number} [right]  x-coordinate of the frustum’s right plane. Defaults to `width / 20`.
        - * @param  {Number} [bottom] y-coordinate of the frustum’s bottom plane. Defaults to `height / 20`.
        - * @param  {Number} [top]    y-coordinate of the frustum’s top plane. Defaults to `-height / 20`.
        - * @param  {Number} [near]   z-coordinate of the frustum’s near plane. Defaults to `0.1 * 800`.
        - * @param  {Number} [far]    z-coordinate of the frustum’s far plane. Defaults to `10 * 800`.
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   describe('A row of white cubes on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Apply the default frustum projection.
        - *   frustum();
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 600);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -40);
        - *     box(10);
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *   describe('A white cube on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Adjust the frustum.
        - *   // Center it.
        - *   // Set its width and height to 20 pixels.
        - *   // Place its near plane 300 pixels from the camera.
        - *   // Place its far plane 350 pixels from the camera.
        - *   frustum(-10, 10, -10, 10, 300, 350);
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 600);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -40);
        - *     box(10);
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.frustum = function (...args) {
        -  this._assert3d('frustum');
        -  p5._validateParameters('frustum', args);
        -  this._renderer._curCamera.frustum(...args);
        -  return this;
        -};
        -
        -////////////////////////////////////////////////////////////////////////////////
        -// p5.Camera
        -////////////////////////////////////////////////////////////////////////////////
        -
        -/**
        - * Creates a new <a href="#/p5.Camera">p5.Camera</a> object and sets it
        - * as the current (active) camera.
        - *
        - * The new camera is initialized with a default position `(0, 0, 800)` and a
        - * default perspective projection. Its properties can be controlled with
        - * <a href="#/p5.Camera">p5.Camera</a> methods such as
        - * `myCamera.lookAt(0, 0, 0)`.
        - *
        - * Note: Every 3D sketch starts with a default camera initialized.
        - * This camera can be controlled with the functions
        - * <a href="#/p5/camera">camera()</a>,
        - * <a href="#/p5/perspective">perspective()</a>,
        - * <a href="#/p5/ortho">ortho()</a>, and
        - * <a href="#/p5/frustum">frustum()</a> if it's the only camera in the scene.
        - *
        - * Note: `createCamera()` can only be used in WebGL mode.
        - *
        - * @method createCamera
        - * @return {p5.Camera} the new camera.
        - * @for p5
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let usingCam1 = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   // Place it at the top-left.
        - *   // Point it at the origin.
        - *   cam2 = createCamera();
        - *   cam2.setPosition(400, -400, 800);
        - *   cam2.lookAt(0, 0, 0);
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe('A white cube on a gray background. The camera toggles between frontal and aerial views when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (usingCam1 === true) {
        - *     setCamera(cam2);
        - *     usingCam1 = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     usingCam1 = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.createCamera = function () {
        -  this._assert3d('createCamera');
        -  const _cam = new p5.Camera(this._renderer);
        -
        -  // compute default camera settings, then set a default camera
        -  _cam._computeCameraDefaultSettings();
        -  _cam._setDefaultCamera();
        -
        -  // set renderer current camera to the new camera
        -  this._renderer._curCamera = _cam;
        -
        -  return _cam;
        -};
        -
        -/**
        - * A class to describe a camera for viewing a 3D sketch.
        - *
        - * Each `p5.Camera` object represents a camera that views a section of 3D
        - * space. It stores information about the camera’s position, orientation, and
        - * projection.
        - *
        - * In WebGL mode, the default camera is a `p5.Camera` object that can be
        - * controlled with the <a href="#/p5/camera">camera()</a>,
        - * <a href="#/p5/perspective">perspective()</a>,
        - * <a href="#/p5/ortho">ortho()</a>, and
        - * <a href="#/p5/frustum">frustum()</a> functions. Additional cameras can be
        - * created with <a href="#/p5/createCamera">createCamera()</a> and activated
        - * with <a href="#/p5/setCamera">setCamera()</a>.
        - *
        - * Note: `p5.Camera`’s methods operate in two coordinate systems:
        - * - The “world” coordinate system describes positions in terms of their
        - * relationship to the origin along the x-, y-, and z-axes. For example,
        - * calling `myCamera.setPosition()` places the camera in 3D space using
        - * "world" coordinates.
        - * - The "local" coordinate system describes positions from the camera's point
        - * of view: left-right, up-down, and forward-backward. For example, calling
        - * `myCamera.move()` moves the camera along its own axes.
        - *
        - * @class p5.Camera
        - * @constructor
        - * @param {rendererGL} rendererGL instance of WebGL renderer
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let delta = 0.001;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at the origin.
        - *   cam.lookAt(0, 0, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube goes in and out of view as the camera pans left and right.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Turn the camera left and right, called "panning".
        - *   cam.pan(delta);
        - *
        - *   // Switch directions every 120 frames.
        - *   if (frameCount % 120 === 0) {
        - *     delta *= -1;
        - *   }
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let isDefaultCamera = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   // Place it at the top-left.
        - *   // Point it at the origin.
        - *   cam2 = createCamera();
        - *   cam2.setPosition(400, -400, 800);
        - *   cam2.lookAt(0, 0, 0);
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The camera toggles between frontal and aerial views when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isDefaultCamera === true) {
        - *     setCamera(cam2);
        - *     isDefaultCamera = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     isDefaultCamera = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Camera = class Camera {
        +class Camera {
           constructor(renderer) {
             this._renderer = renderer;
         
             this.cameraType = 'default';
             this.useLinePerspective = true;
        -    this.cameraMatrix = new p5.Matrix();
        -    this.projMatrix = new p5.Matrix();
        +    this.cameraMatrix = new Matrix(4);
        +    this.projMatrix = new Matrix(4);
             this.yScale = 1;
           }
           /**
        - * The camera’s x-coordinate.
        - *
        - * By default, the camera’s x-coordinate is set to 0 in "world" space.
        - *
        - * @property {Number} eyeX
        - * @readonly
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at the origin.
        - *   cam.lookAt(0, 0, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The text "eyeX: 0" is written in black beneath it.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Display the value of eyeX, rounded to the nearest integer.
        - *   text(`eyeX: ${round(cam.eyeX)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at the origin.
        - *   cam.lookAt(0, 0, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube appears to move left and right as the camera moves. The text "eyeX: X" is written in black beneath the cube. X oscillates between -25 and 25.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Calculate the new x-coordinate.
        - *   let x = 25 * sin(frameCount * 0.01);
        - *
        - *   // Set the camera's position.
        - *   cam.setPosition(x, -400, 800);
        - *
        - *   // Display the value of eyeX, rounded to the nearest integer.
        - *   text(`eyeX: ${round(cam.eyeX)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - */
        +   * The camera’s x-coordinate.
        +   *
        +   * By default, the camera’s x-coordinate is set to 0 in "world" space.
        +   *
        +   * @property {Number} eyeX
        +   * @readonly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at the origin.
        +   *   cam.lookAt(0, 0, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The text "eyeX: 0" is written in black beneath it.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Display the value of eyeX, rounded to the nearest integer.
        +   *   text(`eyeX: ${round(cam.eyeX)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at the origin.
        +   *   cam.lookAt(0, 0, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube appears to move left and right as the camera moves. The text "eyeX: X" is written in black beneath the cube. X oscillates between -25 and 25.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Calculate the new x-coordinate.
        +   *   let x = 25 * sin(frameCount * 0.01);
        +   *
        +   *   // Set the camera's position.
        +   *   cam.setPosition(x, -400, 800);
        +   *
        +   *   // Display the value of eyeX, rounded to the nearest integer.
        +   *   text(`eyeX: ${round(cam.eyeX)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
           /**
        - * The camera’s y-coordinate.
        - *
        - * By default, the camera’s y-coordinate is set to 0 in "world" space.
        - *
        - * @property {Number} eyeY
        - * @readonly
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at the origin.
        - *   cam.lookAt(0, 0, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The text "eyeY: -400" is written in black beneath it.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Display the value of eyeY, rounded to the nearest integer.
        - *   text(`eyeX: ${round(cam.eyeY)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at the origin.
        - *   cam.lookAt(0, 0, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube appears to move up and down as the camera moves. The text "eyeY: Y" is written in black beneath the cube. Y oscillates between -374 and -425.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Calculate the new y-coordinate.
        - *   let y = 25 * sin(frameCount * 0.01) - 400;
        - *
        - *   // Set the camera's position.
        - *   cam.setPosition(0, y, 800);
        - *
        - *   // Display the value of eyeY, rounded to the nearest integer.
        - *   text(`eyeY: ${round(cam.eyeY)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - */
        +   * The camera’s y-coordinate.
        +   *
        +   * By default, the camera’s y-coordinate is set to 0 in "world" space.
        +   *
        +   * @property {Number} eyeY
        +   * @readonly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at the origin.
        +   *   cam.lookAt(0, 0, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The text "eyeY: -400" is written in black beneath it.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Display the value of eyeY, rounded to the nearest integer.
        +   *   text(`eyeX: ${round(cam.eyeY)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at the origin.
        +   *   cam.lookAt(0, 0, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube appears to move up and down as the camera moves. The text "eyeY: Y" is written in black beneath the cube. Y oscillates between -374 and -425.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Calculate the new y-coordinate.
        +   *   let y = 25 * sin(frameCount * 0.01) - 400;
        +   *
        +   *   // Set the camera's position.
        +   *   cam.setPosition(0, y, 800);
        +   *
        +   *   // Display the value of eyeY, rounded to the nearest integer.
        +   *   text(`eyeY: ${round(cam.eyeY)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
           /**
        - * The camera’s z-coordinate.
        - *
        - * By default, the camera’s z-coordinate is set to 800 in "world" space.
        - *
        - * @property {Number} eyeZ
        - * @readonly
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at the origin.
        - *   cam.lookAt(0, 0, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The text "eyeZ: 800" is written in black beneath it.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Display the value of eyeZ, rounded to the nearest integer.
        - *   text(`eyeZ: ${round(cam.eyeZ)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at the origin.
        - *   cam.lookAt(0, 0, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube appears to move forward and back as the camera moves. The text "eyeZ: Z" is written in black beneath the cube. Z oscillates between 700 and 900.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Calculate the new z-coordinate.
        - *   let z = 100 * sin(frameCount * 0.01) + 800;
        - *
        - *   // Set the camera's position.
        - *   cam.setPosition(0, -400, z);
        - *
        - *   // Display the value of eyeZ, rounded to the nearest integer.
        - *   text(`eyeZ: ${round(cam.eyeZ)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - */
        +   * The camera’s z-coordinate.
        +   *
        +   * By default, the camera’s z-coordinate is set to 800 in "world" space.
        +   *
        +   * @property {Number} eyeZ
        +   * @readonly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at the origin.
        +   *   cam.lookAt(0, 0, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The text "eyeZ: 800" is written in black beneath it.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Display the value of eyeZ, rounded to the nearest integer.
        +   *   text(`eyeZ: ${round(cam.eyeZ)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at the origin.
        +   *   cam.lookAt(0, 0, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube appears to move forward and back as the camera moves. The text "eyeZ: Z" is written in black beneath the cube. Z oscillates between 700 and 900.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Calculate the new z-coordinate.
        +   *   let z = 100 * sin(frameCount * 0.01) + 800;
        +   *
        +   *   // Set the camera's position.
        +   *   cam.setPosition(0, -400, z);
        +   *
        +   *   // Display the value of eyeZ, rounded to the nearest integer.
        +   *   text(`eyeZ: ${round(cam.eyeZ)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
           /**
        - * The x-coordinate of the place where the camera looks.
        - *
        - * By default, the camera looks at the origin `(0, 0, 0)` in "world" space, so
        - * `myCamera.centerX` is 0.
        - *
        - * @property {Number} centerX
        - * @readonly
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at (10, 20, -30).
        - *   cam.lookAt(10, 20, -30);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The text "centerX: 10" is written in black beneath it.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Display the value of centerX, rounded to the nearest integer.
        - *   text(`centerX: ${round(cam.centerX)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-right.
        - *   cam.setPosition(100, -400, 800);
        - *
        - *   // Point the camera at (10, 20, -30).
        - *   cam.lookAt(10, 20, -30);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube appears to move left and right as the camera shifts its focus. The text "centerX: X" is written in black beneath the cube. X oscillates between -15 and 35.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Calculate the new x-coordinate.
        - *   let x = 25 * sin(frameCount * 0.01) + 10;
        - *
        - *   // Point the camera.
        - *   cam.lookAt(x, 20, -30);
        - *
        - *   // Display the value of centerX, rounded to the nearest integer.
        - *   text(`centerX: ${round(cam.centerX)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - */
        +   * The x-coordinate of the place where the camera looks.
        +   *
        +   * By default, the camera looks at the origin `(0, 0, 0)` in "world" space, so
        +   * `myCamera.centerX` is 0.
        +   *
        +   * @property {Number} centerX
        +   * @readonly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at (10, 20, -30).
        +   *   cam.lookAt(10, 20, -30);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The text "centerX: 10" is written in black beneath it.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Display the value of centerX, rounded to the nearest integer.
        +   *   text(`centerX: ${round(cam.centerX)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-right.
        +   *   cam.setPosition(100, -400, 800);
        +   *
        +   *   // Point the camera at (10, 20, -30).
        +   *   cam.lookAt(10, 20, -30);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube appears to move left and right as the camera shifts its focus. The text "centerX: X" is written in black beneath the cube. X oscillates between -15 and 35.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Calculate the new x-coordinate.
        +   *   let x = 25 * sin(frameCount * 0.01) + 10;
        +   *
        +   *   // Point the camera.
        +   *   cam.lookAt(x, 20, -30);
        +   *
        +   *   // Display the value of centerX, rounded to the nearest integer.
        +   *   text(`centerX: ${round(cam.centerX)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
           /**
        - * The y-coordinate of the place where the camera looks.
        - *
        - * By default, the camera looks at the origin `(0, 0, 0)` in "world" space, so
        - * `myCamera.centerY` is 0.
        - *
        - * @property {Number} centerY
        - * @readonly
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at (10, 20, -30).
        - *   cam.lookAt(10, 20, -30);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The text "centerY: 20" is written in black beneath it.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Display the value of centerY, rounded to the nearest integer.
        - *   text(`centerY: ${round(cam.centerY)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-right.
        - *   cam.setPosition(100, -400, 800);
        - *
        - *   // Point the camera at (10, 20, -30).
        - *   cam.lookAt(10, 20, -30);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube appears to move up and down as the camera shifts its focus. The text "centerY: Y" is written in black beneath the cube. Y oscillates between -5 and 45.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Calculate the new y-coordinate.
        - *   let y = 25 * sin(frameCount * 0.01) + 20;
        - *
        - *   // Point the camera.
        - *   cam.lookAt(10, y, -30);
        - *
        - *   // Display the value of centerY, rounded to the nearest integer.
        - *   text(`centerY: ${round(cam.centerY)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - */
        +   * The y-coordinate of the place where the camera looks.
        +   *
        +   * By default, the camera looks at the origin `(0, 0, 0)` in "world" space, so
        +   * `myCamera.centerY` is 0.
        +   *
        +   * @property {Number} centerY
        +   * @readonly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at (10, 20, -30).
        +   *   cam.lookAt(10, 20, -30);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The text "centerY: 20" is written in black beneath it.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Display the value of centerY, rounded to the nearest integer.
        +   *   text(`centerY: ${round(cam.centerY)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-right.
        +   *   cam.setPosition(100, -400, 800);
        +   *
        +   *   // Point the camera at (10, 20, -30).
        +   *   cam.lookAt(10, 20, -30);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube appears to move up and down as the camera shifts its focus. The text "centerY: Y" is written in black beneath the cube. Y oscillates between -5 and 45.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Calculate the new y-coordinate.
        +   *   let y = 25 * sin(frameCount * 0.01) + 20;
        +   *
        +   *   // Point the camera.
        +   *   cam.lookAt(10, y, -30);
        +   *
        +   *   // Display the value of centerY, rounded to the nearest integer.
        +   *   text(`centerY: ${round(cam.centerY)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
           /**
        - * The y-coordinate of the place where the camera looks.
        - *
        - * By default, the camera looks at the origin `(0, 0, 0)` in "world" space, so
        - * `myCamera.centerZ` is 0.
        - *
        - * @property {Number} centerZ
        - * @readonly
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at (10, 20, -30).
        - *   cam.lookAt(10, 20, -30);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The text "centerZ: -30" is written in black beneath it.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Display the value of centerZ, rounded to the nearest integer.
        - *   text(`centerZ: ${round(cam.centerZ)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-right.
        - *   cam.setPosition(100, -400, 800);
        - *
        - *   // Point the camera at (10, 20, -30).
        - *   cam.lookAt(10, 20, -30);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube appears to move forward and back as the camera shifts its focus. The text "centerZ: Z" is written in black beneath the cube. Z oscillates between -55 and -25.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Calculate the new z-coordinate.
        - *   let z = 25 * sin(frameCount * 0.01) - 30;
        - *
        - *   // Point the camera.
        - *   cam.lookAt(10, 20, z);
        - *
        - *   // Display the value of centerZ, rounded to the nearest integer.
        - *   text(`centerZ: ${round(cam.centerZ)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - */
        +   * The y-coordinate of the place where the camera looks.
        +   *
        +   * By default, the camera looks at the origin `(0, 0, 0)` in "world" space, so
        +   * `myCamera.centerZ` is 0.
        +   *
        +   * @property {Number} centerZ
        +   * @readonly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at (10, 20, -30).
        +   *   cam.lookAt(10, 20, -30);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The text "centerZ: -30" is written in black beneath it.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Display the value of centerZ, rounded to the nearest integer.
        +   *   text(`centerZ: ${round(cam.centerZ)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-right.
        +   *   cam.setPosition(100, -400, 800);
        +   *
        +   *   // Point the camera at (10, 20, -30).
        +   *   cam.lookAt(10, 20, -30);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube appears to move forward and back as the camera shifts its focus. The text "centerZ: Z" is written in black beneath the cube. Z oscillates between -55 and -25.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Calculate the new z-coordinate.
        +   *   let z = 25 * sin(frameCount * 0.01) - 30;
        +   *
        +   *   // Point the camera.
        +   *   cam.lookAt(10, 20, z);
        +   *
        +   *   // Display the value of centerZ, rounded to the nearest integer.
        +   *   text(`centerZ: ${round(cam.centerZ)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
           /**
        - * The x-component of the camera's "up" vector.
        - *
        - * The camera's "up" vector orients its y-axis. By default, the "up" vector is
        - * `(0, 1, 0)`, so its x-component is 0 in "local" space.
        - *
        - * @property {Number} upX
        - * @readonly
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-right: (100, -400, 800)
        - *   // Point it at the origin: (0, 0, 0)
        - *   // Set its "up" vector: (0, 1, 0).
        - *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The text "upX: 0" is written in black beneath it.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Display the value of upX, rounded to the nearest tenth.
        - *   text(`upX: ${round(cam.upX, 1)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-right: (100, -400, 800)
        - *   // Point it at the origin: (0, 0, 0)
        - *   // Set its "up" vector: (0, 1, 0).
        - *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube appears to rock back and forth. The text "upX: X" is written in black beneath it. X oscillates between -1 and 1.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Calculate the x-component.
        - *   let x = sin(frameCount * 0.01);
        - *
        - *   // Update the camera's "up" vector.
        - *   cam.camera(100, -400, 800, 0, 0, 0, x, 1, 0);
        - *
        - *   // Display the value of upX, rounded to the nearest tenth.
        - *   text(`upX: ${round(cam.upX, 1)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - */
        +   * The x-component of the camera's "up" vector.
        +   *
        +   * The camera's "up" vector orients its y-axis. By default, the "up" vector is
        +   * `(0, 1, 0)`, so its x-component is 0 in "local" space.
        +   *
        +   * @property {Number} upX
        +   * @readonly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-right: (100, -400, 800)
        +   *   // Point it at the origin: (0, 0, 0)
        +   *   // Set its "up" vector: (0, 1, 0).
        +   *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The text "upX: 0" is written in black beneath it.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Display the value of upX, rounded to the nearest tenth.
        +   *   text(`upX: ${round(cam.upX, 1)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-right: (100, -400, 800)
        +   *   // Point it at the origin: (0, 0, 0)
        +   *   // Set its "up" vector: (0, 1, 0).
        +   *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube appears to rock back and forth. The text "upX: X" is written in black beneath it. X oscillates between -1 and 1.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Calculate the x-component.
        +   *   let x = sin(frameCount * 0.01);
        +   *
        +   *   // Update the camera's "up" vector.
        +   *   cam.camera(100, -400, 800, 0, 0, 0, x, 1, 0);
        +   *
        +   *   // Display the value of upX, rounded to the nearest tenth.
        +   *   text(`upX: ${round(cam.upX, 1)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
           /**
        - * The y-component of the camera's "up" vector.
        - *
        - * The camera's "up" vector orients its y-axis. By default, the "up" vector is
        - * `(0, 1, 0)`, so its y-component is 1 in "local" space.
        - *
        - * @property {Number} upY
        - * @readonly
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-right: (100, -400, 800)
        - *   // Point it at the origin: (0, 0, 0)
        - *   // Set its "up" vector: (0, 1, 0).
        - *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The text "upY: 1" is written in black beneath it.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Display the value of upY, rounded to the nearest tenth.
        - *   text(`upY: ${round(cam.upY, 1)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-right: (100, -400, 800)
        - *   // Point it at the origin: (0, 0, 0)
        - *   // Set its "up" vector: (0, 1, 0).
        - *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube flips upside-down periodically. The text "upY: Y" is written in black beneath it. Y oscillates between -1 and 1.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Calculate the y-component.
        - *   let y = sin(frameCount * 0.01);
        - *
        - *   // Update the camera's "up" vector.
        - *   cam.camera(100, -400, 800, 0, 0, 0, 0, y, 0);
        - *
        - *   // Display the value of upY, rounded to the nearest tenth.
        - *   text(`upY: ${round(cam.upY, 1)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - */
        +   * The y-component of the camera's "up" vector.
        +   *
        +   * The camera's "up" vector orients its y-axis. By default, the "up" vector is
        +   * `(0, 1, 0)`, so its y-component is 1 in "local" space.
        +   *
        +   * @property {Number} upY
        +   * @readonly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-right: (100, -400, 800)
        +   *   // Point it at the origin: (0, 0, 0)
        +   *   // Set its "up" vector: (0, 1, 0).
        +   *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The text "upY: 1" is written in black beneath it.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Display the value of upY, rounded to the nearest tenth.
        +   *   text(`upY: ${round(cam.upY, 1)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-right: (100, -400, 800)
        +   *   // Point it at the origin: (0, 0, 0)
        +   *   // Set its "up" vector: (0, 1, 0).
        +   *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube flips upside-down periodically. The text "upY: Y" is written in black beneath it. Y oscillates between -1 and 1.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Calculate the y-component.
        +   *   let y = sin(frameCount * 0.01);
        +   *
        +   *   // Update the camera's "up" vector.
        +   *   cam.camera(100, -400, 800, 0, 0, 0, 0, y, 0);
        +   *
        +   *   // Display the value of upY, rounded to the nearest tenth.
        +   *   text(`upY: ${round(cam.upY, 1)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
           /**
        - * The z-component of the camera's "up" vector.
        - *
        - * The camera's "up" vector orients its y-axis. By default, the "up" vector is
        - * `(0, 1, 0)`, so its z-component is 0 in "local" space.
        - *
        - * @property {Number} upZ
        - * @readonly
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-right: (100, -400, 800)
        - *   // Point it at the origin: (0, 0, 0)
        - *   // Set its "up" vector: (0, 1, 0).
        - *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The text "upZ: 0" is written in black beneath it.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Display the value of upZ, rounded to the nearest tenth.
        - *   text(`upZ: ${round(cam.upZ, 1)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * let cam;
        - * let font;
        - *
        - * // Load a font and create a p5.Font object.
        - * function preload() {
        - *   font = loadFont('assets/inconsolata.otf');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-right: (100, -400, 800)
        - *   // Point it at the origin: (0, 0, 0)
        - *   // Set its "up" vector: (0, 1, 0).
        - *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube appears to rock back and forth. The text "upZ: Z" is written in black beneath it. Z oscillates between -1 and 1.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Style the box.
        - *   fill(255);
        - *
        - *   // Draw the box.
        - *   box();
        - *
        - *   // Style the text.
        - *   textAlign(CENTER);
        - *   textSize(16);
        - *   textFont(font);
        - *   fill(0);
        - *
        - *   // Calculate the z-component.
        - *   let z = sin(frameCount * 0.01);
        - *
        - *   // Update the camera's "up" vector.
        - *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, z);
        - *
        - *   // Display the value of upZ, rounded to the nearest tenth.
        - *   text(`upZ: ${round(cam.upZ, 1)}`, 0, 55);
        - * }
        - * </code>
        - * </div>
        - */
        +   * The z-component of the camera's "up" vector.
        +   *
        +   * The camera's "up" vector orients its y-axis. By default, the "up" vector is
        +   * `(0, 1, 0)`, so its z-component is 0 in "local" space.
        +   *
        +   * @property {Number} upZ
        +   * @readonly
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-right: (100, -400, 800)
        +   *   // Point it at the origin: (0, 0, 0)
        +   *   // Set its "up" vector: (0, 1, 0).
        +   *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The text "upZ: 0" is written in black beneath it.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Display the value of upZ, rounded to the nearest tenth.
        +   *   text(`upZ: ${round(cam.upZ, 1)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let font;
        +   *
        +   * async function setup() {
        +   *   // Load a font and create a p5.Font object.
        +   *   font = await loadFont('assets/inconsolata.otf');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-right: (100, -400, 800)
        +   *   // Point it at the origin: (0, 0, 0)
        +   *   // Set its "up" vector: (0, 1, 0).
        +   *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube appears to rock back and forth. The text "upZ: Z" is written in black beneath it. Z oscillates between -1 and 1.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Style the box.
        +   *   fill(255);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   *
        +   *   // Style the text.
        +   *   textAlign(CENTER);
        +   *   textSize(16);
        +   *   textFont(font);
        +   *   fill(0);
        +   *
        +   *   // Calculate the z-component.
        +   *   let z = sin(frameCount * 0.01);
        +   *
        +   *   // Update the camera's "up" vector.
        +   *   cam.camera(100, -400, 800, 0, 0, 0, 0, 1, z);
        +   *
        +   *   // Display the value of upZ, rounded to the nearest tenth.
        +   *   text(`upZ: ${round(cam.upZ, 1)}`, 0, 55);
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
           ////////////////////////////////////////////////////////////////////////////////
           // Camera Projection Methods
           ////////////////////////////////////////////////////////////////////////////////
         
           /**
        - * Sets a perspective projection for the camera.
        - *
        - * In a perspective projection, shapes that are further from the camera appear
        - * smaller than shapes that are near the camera. This technique, called
        - * foreshortening, creates realistic 3D scenes. It’s applied by default in new
        - * `p5.Camera` objects.
        - *
        - * `myCamera.perspective()` changes the camera’s perspective by changing its
        - * viewing frustum. The frustum is the volume of space that’s visible to the
        - * camera. The frustum’s shape is a pyramid with its top cut off. The camera
        - * is placed where the top of the pyramid should be and points towards the
        - * base of the pyramid. It views everything within the frustum.
        - *
        - * The first parameter, `fovy`, is the camera’s vertical field of view. It’s
        - * an angle that describes how tall or narrow a view the camera has. For
        - * example, calling `myCamera.perspective(0.5)` sets the camera’s vertical
        - * field of view to 0.5 radians. By default, `fovy` is calculated based on the
        - * sketch’s height and the camera’s default z-coordinate, which is 800. The
        - * formula for the default `fovy` is `2 * atan(height / 2 / 800)`.
        - *
        - * The second parameter, `aspect`, is the camera’s aspect ratio. It’s a number
        - * that describes the ratio of the top plane’s width to its height. For
        - * example, calling `myCamera.perspective(0.5, 1.5)` sets the camera’s field
        - * of view to 0.5 radians and aspect ratio to 1.5, which would make shapes
        - * appear thinner on a square canvas. By default, `aspect` is set to
        - * `width / height`.
        - *
        - * The third parameter, `near`, is the distance from the camera to the near
        - * plane. For example, calling `myCamera.perspective(0.5, 1.5, 100)` sets the
        - * camera’s field of view to 0.5 radians, its aspect ratio to 1.5, and places
        - * the near plane 100 pixels from the camera. Any shapes drawn less than 100
        - * pixels from the camera won’t be visible. By default, `near` is set to
        - * `0.1 * 800`, which is 1/10th the default distance between the camera and
        - * the origin.
        - *
        - * The fourth parameter, `far`, is the distance from the camera to the far
        - * plane. For example, calling `myCamera.perspective(0.5, 1.5, 100, 10000)`
        - * sets the camera’s field of view to 0.5 radians, its aspect ratio to 1.5,
        - * places the near plane 100 pixels from the camera, and places the far plane
        - * 10,000 pixels from the camera. Any shapes drawn more than 10,000 pixels
        - * from the camera won’t be visible. By default, `far` is set to `10 * 800`,
        - * which is 10 times the default distance between the camera and the origin.
        - *
        - * @method perspective
        - * @for p5.Camera
        - * @param  {Number} [fovy]   camera frustum vertical field of view. Defaults to
        - *                           `2 * atan(height / 2 / 800)`.
        - * @param  {Number} [aspect] camera frustum aspect ratio. Defaults to
        - *                           `width / height`.
        - * @param  {Number} [near]   distance from the camera to the near clipping plane.
        - *                           Defaults to `0.1 * 800`.
        - * @param  {Number} [far]    distance from the camera to the far clipping plane.
        - *                           Defaults to `10 * 800`.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let isDefaultCamera = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   cam2 = createCamera();
        - *
        - *   // Place it at the top-right.
        - *   cam2.camera(400, -400, 800);
        - *
        - *   // Set its fovy to 0.2.
        - *   // Set its aspect to 1.5.
        - *   // Set its near to 600.
        - *   // Set its far to 1200.
        - *   cam2.perspective(0.2, 1.5, 600, 1200);
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe('A white cube on a gray background. The camera toggles between a frontal view and a skewed aerial view when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isDefaultCamera === true) {
        - *     setCamera(cam2);
        - *     isDefaultCamera = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     isDefaultCamera = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let isDefaultCamera = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   cam2 = createCamera();
        - *
        - *   // Place it at the top-right.
        - *   cam2.camera(400, -400, 800);
        - *
        - *   // Set its fovy to 0.2.
        - *   // Set its aspect to 1.5.
        - *   // Set its near to 600.
        - *   // Set its far to 1200.
        - *   cam2.perspective(0.2, 1.5, 600, 1200);
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe('A white cube moves left and right on a gray background. The camera toggles between a frontal and a skewed aerial view when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin left and right.
        - *   let x = 100 * sin(frameCount * 0.01);
        - *   translate(x, 0, 0);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isDefaultCamera === true) {
        - *     setCamera(cam2);
        - *     isDefaultCamera = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     isDefaultCamera = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        +   * Sets a perspective projection for the camera.
        +   *
        +   * In a perspective projection, shapes that are further from the camera appear
        +   * smaller than shapes that are near the camera. This technique, called
        +   * foreshortening, creates realistic 3D scenes. It’s applied by default in new
        +   * `p5.Camera` objects.
        +   *
        +   * `myCamera.perspective()` changes the camera’s perspective by changing its
        +   * viewing frustum. The frustum is the volume of space that’s visible to the
        +   * camera. The frustum’s shape is a pyramid with its top cut off. The camera
        +   * is placed where the top of the pyramid should be and points towards the
        +   * base of the pyramid. It views everything within the frustum.
        +   *
        +   * The first parameter, `fovy`, is the camera’s vertical field of view. It’s
        +   * an angle that describes how tall or narrow a view the camera has. For
        +   * example, calling `myCamera.perspective(0.5)` sets the camera’s vertical
        +   * field of view to 0.5 radians. By default, `fovy` is calculated based on the
        +   * sketch’s height and the camera’s default z-coordinate, which is 800. The
        +   * formula for the default `fovy` is `2 * atan(height / 2 / 800)`.
        +   *
        +   * The second parameter, `aspect`, is the camera’s aspect ratio. It’s a number
        +   * that describes the ratio of the top plane’s width to its height. For
        +   * example, calling `myCamera.perspective(0.5, 1.5)` sets the camera’s field
        +   * of view to 0.5 radians and aspect ratio to 1.5, which would make shapes
        +   * appear thinner on a square canvas. By default, `aspect` is set to
        +   * `width / height`.
        +   *
        +   * The third parameter, `near`, is the distance from the camera to the near
        +   * plane. For example, calling `myCamera.perspective(0.5, 1.5, 100)` sets the
        +   * camera’s field of view to 0.5 radians, its aspect ratio to 1.5, and places
        +   * the near plane 100 pixels from the camera. Any shapes drawn less than 100
        +   * pixels from the camera won’t be visible. By default, `near` is set to
        +   * `0.1 * 800`, which is 1/10th the default distance between the camera and
        +   * the origin.
        +   *
        +   * The fourth parameter, `far`, is the distance from the camera to the far
        +   * plane. For example, calling `myCamera.perspective(0.5, 1.5, 100, 10000)`
        +   * sets the camera’s field of view to 0.5 radians, its aspect ratio to 1.5,
        +   * places the near plane 100 pixels from the camera, and places the far plane
        +   * 10,000 pixels from the camera. Any shapes drawn more than 10,000 pixels
        +   * from the camera won’t be visible. By default, `far` is set to `10 * 800`,
        +   * which is 10 times the default distance between the camera and the origin.
        +   *
        +   * @for p5.Camera
        +   * @param  {Number} [fovy]   camera frustum vertical field of view. Defaults to
        +   *                           `2 * atan(height / 2 / 800)`.
        +   * @param  {Number} [aspect] camera frustum aspect ratio. Defaults to
        +   *                           `width / height`.
        +   * @param  {Number} [near]   distance from the camera to the near clipping plane.
        +   *                           Defaults to `0.1 * 800`.
        +   * @param  {Number} [far]    distance from the camera to the far clipping plane.
        +   *                           Defaults to `10 * 800`.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let isDefaultCamera = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   cam2 = createCamera();
        +   *
        +   *   // Place it at the top-right.
        +   *   cam2.camera(400, -400, 800);
        +   *
        +   *   // Set its fovy to 0.2.
        +   *   // Set its aspect to 1.5.
        +   *   // Set its near to 600.
        +   *   // Set its far to 1200.
        +   *   cam2.perspective(0.2, 1.5, 600, 1200);
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe('A white cube on a gray background. The camera toggles between a frontal view and a skewed aerial view when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isDefaultCamera === true) {
        +   *     setCamera(cam2);
        +   *     isDefaultCamera = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     isDefaultCamera = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let isDefaultCamera = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   cam2 = createCamera();
        +   *
        +   *   // Place it at the top-right.
        +   *   cam2.camera(400, -400, 800);
        +   *
        +   *   // Set its fovy to 0.2.
        +   *   // Set its aspect to 1.5.
        +   *   // Set its near to 600.
        +   *   // Set its far to 1200.
        +   *   cam2.perspective(0.2, 1.5, 600, 1200);
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe('A white cube moves left and right on a gray background. The camera toggles between a frontal and a skewed aerial view when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin left and right.
        +   *   let x = 100 * sin(frameCount * 0.01);
        +   *   translate(x, 0, 0);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isDefaultCamera === true) {
        +   *     setCamera(cam2);
        +   *     isDefaultCamera = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     isDefaultCamera = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
           perspective(fovy, aspect, near, far) {
             this.cameraType = arguments.length > 0 ? 'custom' : 'default';
             if (typeof fovy === 'undefined') {
        @@ -2066,188 +1182,187 @@ p5.Camera = class Camera {
             this.cameraNear = near;
             this.cameraFar = far;
         
        -    this.projMatrix = p5.Matrix.identity();
        +    this.projMatrix = new Matrix(4);
         
             const f = 1.0 / Math.tan(this.cameraFOV / 2);
             const nf = 1.0 / (this.cameraNear - this.cameraFar);
         
             /* eslint-disable indent */
        -    this.projMatrix.set(f / aspect,  0,                     0,  0,
        -                        0,          -f * this.yScale,       0,  0,
        -                        0,           0,     (far + near) * nf, -1,
        -                        0,           0, (2 * far * near) * nf,  0);
        +    this.projMatrix.set(f / aspect, 0, 0, 0,
        +      0, -f * this.yScale, 0, 0,
        +      0, 0, (far + near) * nf, -1,
        +      0, 0, (2 * far * near) * nf, 0);
             /* eslint-enable indent */
         
             if (this._isActive()) {
        -      this._renderer.uPMatrix.set(this.projMatrix);
        +      this._renderer.states.uPMatrix.set(this.projMatrix);
             }
           }
         
           /**
        - * Sets an orthographic projection for the camera.
        - *
        - * In an orthographic projection, shapes with the same size always appear the
        - * same size, regardless of whether they are near or far from the camera.
        - *
        - * `myCamera.ortho()` changes the camera’s perspective by changing its viewing
        - * frustum from a truncated pyramid to a rectangular prism. The frustum is the
        - * volume of space that’s visible to the camera. The camera is placed in front
        - * of the frustum and views everything within the frustum. `myCamera.ortho()`
        - * has six optional parameters to define the viewing frustum.
        - *
        - * The first four parameters, `left`, `right`, `bottom`, and `top`, set the
        - * coordinates of the frustum’s sides, bottom, and top. For example, calling
        - * `myCamera.ortho(-100, 100, 200, -200)` creates a frustum that’s 200 pixels
        - * wide and 400 pixels tall. By default, these dimensions are set based on
        - * the sketch’s width and height, as in
        - * `myCamera.ortho(-width / 2, width / 2, -height / 2, height / 2)`.
        - *
        - * The last two parameters, `near` and `far`, set the distance of the
        - * frustum’s near and far plane from the camera. For example, calling
        - * `myCamera.ortho(-100, 100, 200, -200, 50, 1000)` creates a frustum that’s
        - * 200 pixels wide, 400 pixels tall, starts 50 pixels from the camera, and
        - * ends 1,000 pixels from the camera. By default, `near` and `far` are set to
        - * 0 and `max(width, height) + 800`, respectively.
        - *
        - * @method ortho
        - * @for p5.Camera
        - * @param  {Number} [left]   x-coordinate of the frustum’s left plane. Defaults to `-width / 2`.
        - * @param  {Number} [right]  x-coordinate of the frustum’s right plane. Defaults to `width / 2`.
        - * @param  {Number} [bottom] y-coordinate of the frustum’s bottom plane. Defaults to `height / 2`.
        - * @param  {Number} [top]    y-coordinate of the frustum’s top plane. Defaults to `-height / 2`.
        - * @param  {Number} [near]   z-coordinate of the frustum’s near plane. Defaults to 0.
        - * @param  {Number} [far]    z-coordinate of the frustum’s far plane. Defaults to `max(width, height) + 800`.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let isDefaultCamera = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   cam2 = createCamera();
        - *
        - *   // Apply an orthographic projection.
        - *   cam2.ortho();
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe('A row of white cubes against a gray background. The camera toggles between a perspective and an orthographic projection when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 500);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -40);
        - *     box(10);
        - *   }
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isDefaultCamera === true) {
        - *     setCamera(cam2);
        - *     isDefaultCamera = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     isDefaultCamera = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let isDefaultCamera = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   cam2 = createCamera();
        - *
        - *   // Apply an orthographic projection.
        - *   cam2.ortho();
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe('A row of white cubes slither like a snake against a gray background. The camera toggles between a perspective and an orthographic projection when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 500);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     push();
        - *     // Calculate the box's coordinates.
        - *     let x = 10 * sin(frameCount * 0.02 + i * 0.6);
        - *     let z = -40 * i;
        - *     // Translate the origin.
        - *     translate(x, 0, z);
        - *     // Draw the box.
        - *     box(10);
        - *     pop();
        - *   }
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isDefaultCamera === true) {
        - *     setCamera(cam2);
        - *     isDefaultCamera = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     isDefaultCamera = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        +   * Sets an orthographic projection for the camera.
        +   *
        +   * In an orthographic projection, shapes with the same size always appear the
        +   * same size, regardless of whether they are near or far from the camera.
        +   *
        +   * `myCamera.ortho()` changes the camera’s perspective by changing its viewing
        +   * frustum from a truncated pyramid to a rectangular prism. The frustum is the
        +   * volume of space that’s visible to the camera. The camera is placed in front
        +   * of the frustum and views everything within the frustum. `myCamera.ortho()`
        +   * has six optional parameters to define the viewing frustum.
        +   *
        +   * The first four parameters, `left`, `right`, `bottom`, and `top`, set the
        +   * coordinates of the frustum’s sides, bottom, and top. For example, calling
        +   * `myCamera.ortho(-100, 100, 200, -200)` creates a frustum that’s 200 pixels
        +   * wide and 400 pixels tall. By default, these dimensions are set based on
        +   * the sketch’s width and height, as in
        +   * `myCamera.ortho(-width / 2, width / 2, -height / 2, height / 2)`.
        +   *
        +   * The last two parameters, `near` and `far`, set the distance of the
        +   * frustum’s near and far plane from the camera. For example, calling
        +   * `myCamera.ortho(-100, 100, 200, -200, 50, 1000)` creates a frustum that’s
        +   * 200 pixels wide, 400 pixels tall, starts 50 pixels from the camera, and
        +   * ends 1,000 pixels from the camera. By default, `near` and `far` are set to
        +   * 0 and `max(width, height) + 800`, respectively.
        +   *
        +   * @for p5.Camera
        +   * @param  {Number} [left]   x-coordinate of the frustum’s left plane. Defaults to `-width / 2`.
        +   * @param  {Number} [right]  x-coordinate of the frustum’s right plane. Defaults to `width / 2`.
        +   * @param  {Number} [bottom] y-coordinate of the frustum’s bottom plane. Defaults to `height / 2`.
        +   * @param  {Number} [top]    y-coordinate of the frustum’s top plane. Defaults to `-height / 2`.
        +   * @param  {Number} [near]   z-coordinate of the frustum’s near plane. Defaults to 0.
        +   * @param  {Number} [far]    z-coordinate of the frustum’s far plane. Defaults to `max(width, height) + 800`.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let isDefaultCamera = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   cam2 = createCamera();
        +   *
        +   *   // Apply an orthographic projection.
        +   *   cam2.ortho();
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe('A row of white cubes against a gray background. The camera toggles between a perspective and an orthographic projection when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 500);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -40);
        +   *     box(10);
        +   *   }
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isDefaultCamera === true) {
        +   *     setCamera(cam2);
        +   *     isDefaultCamera = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     isDefaultCamera = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let isDefaultCamera = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   cam2 = createCamera();
        +   *
        +   *   // Apply an orthographic projection.
        +   *   cam2.ortho();
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe('A row of white cubes slither like a snake against a gray background. The camera toggles between a perspective and an orthographic projection when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 500);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     push();
        +   *     // Calculate the box's coordinates.
        +   *     let x = 10 * sin(frameCount * 0.02 + i * 0.6);
        +   *     let z = -40 * i;
        +   *     // Translate the origin.
        +   *     translate(x, 0, z);
        +   *     // Draw the box.
        +   *     box(10);
        +   *     pop();
        +   *   }
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isDefaultCamera === true) {
        +   *     setCamera(cam2);
        +   *     isDefaultCamera = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     isDefaultCamera = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
           ortho(left, right, bottom, top, near, far) {
        -    const source = this.fbo||this._renderer;
        +    const source = this.fbo || this._renderer;
             if (left === undefined) left = -source.width / 2;
             if (right === undefined) right = +source.width / 2;
             if (bottom === undefined) bottom = -source.height / 2;
             if (top === undefined) top = +source.height / 2;
             if (near === undefined) near = 0;
        -    if (far === undefined) far = Math.max(source.width, source.height)+800;
        +    if (far === undefined) far = Math.max(source.width, source.height) + 800;
             this.cameraNear = near;
             this.cameraFar = far;
             const w = right - left;
        @@ -2259,120 +1374,119 @@ p5.Camera = class Camera {
             const tx = -(right + left) / w;
             const ty = -(top + bottom) / h;
             const tz = -(far + near) / d;
        -    this.projMatrix = p5.Matrix.identity();
        +    this.projMatrix = new Matrix(4);
             /* eslint-disable indent */
        -    this.projMatrix.set(  x,  0,  0,  0,
        -                          0, -y,  0,  0,
        -                          0,  0,  z,  0,
        -                          tx, ty, tz,  1);
        +    this.projMatrix.set(x, 0, 0, 0,
        +      0, -y, 0, 0,
        +      0, 0, z, 0,
        +      tx, ty, tz, 1);
             /* eslint-enable indent */
             if (this._isActive()) {
        -      this._renderer.uPMatrix.set(this.projMatrix);
        +      this._renderer.states.uPMatrix.set(this.projMatrix);
             }
             this.cameraType = 'custom';
           }
           /**
        - * Sets the camera's frustum.
        - *
        - * In a frustum projection, shapes that are further from the camera appear
        - * smaller than shapes that are near the camera. This technique, called
        - * foreshortening, creates realistic 3D scenes.
        - *
        - * `myCamera.frustum()` changes the camera’s perspective by changing its
        - * viewing frustum. The frustum is the volume of space that’s visible to the
        - * camera. The frustum’s shape is a pyramid with its top cut off. The camera
        - * is placed where the top of the pyramid should be and points towards the
        - * base of the pyramid. It views everything within the frustum.
        - *
        - * The first four parameters, `left`, `right`, `bottom`, and `top`, set the
        - * coordinates of the frustum’s sides, bottom, and top. For example, calling
        - * `myCamera.frustum(-100, 100, 200, -200)` creates a frustum that’s 200
        - * pixels wide and 400 pixels tall. By default, these coordinates are set
        - * based on the sketch’s width and height, as in
        - * `myCamera.frustum(-width / 20, width / 20, height / 20, -height / 20)`.
        - *
        - * The last two parameters, `near` and `far`, set the distance of the
        - * frustum’s near and far plane from the camera. For example, calling
        - * `myCamera.frustum(-100, 100, 200, -200, 50, 1000)` creates a frustum that’s
        - * 200 pixels wide, 400 pixels tall, starts 50 pixels from the camera, and ends
        - * 1,000 pixels from the camera. By default, near is set to `0.1 * 800`, which
        - * is 1/10th the default distance between the camera and the origin. `far` is
        - * set to `10 * 800`, which is 10 times the default distance between the
        - * camera and the origin.
        - *
        - * @method frustum
        - * @for p5.Camera
        - * @param  {Number} [left]   x-coordinate of the frustum’s left plane. Defaults to `-width / 20`.
        - * @param  {Number} [right]  x-coordinate of the frustum’s right plane. Defaults to `width / 20`.
        - * @param  {Number} [bottom] y-coordinate of the frustum’s bottom plane. Defaults to `height / 20`.
        - * @param  {Number} [top]    y-coordinate of the frustum’s top plane. Defaults to `-height / 20`.
        - * @param  {Number} [near]   z-coordinate of the frustum’s near plane. Defaults to `0.1 * 800`.
        - * @param  {Number} [far]    z-coordinate of the frustum’s far plane. Defaults to `10 * 800`.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let isDefaultCamera = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   cam2 = createCamera();
        - *
        - *   // Adjust the frustum.
        - *   // Center it.
        - *   // Set its width and height to 20 pixels.
        - *   // Place its near plane 300 pixels from the camera.
        - *   // Place its far plane 350 pixels from the camera.
        - *   cam2.frustum(-10, 10, -10, 10, 300, 350);
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe(
        - *     'A row of white cubes against a gray background. The camera zooms in on one cube when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 600);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -40);
        - *     box(10);
        - *   }
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isDefaultCamera === true) {
        - *     setCamera(cam2);
        - *     isDefaultCamera = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     isDefaultCamera = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        +   * Sets the camera's frustum.
        +   *
        +   * In a frustum projection, shapes that are further from the camera appear
        +   * smaller than shapes that are near the camera. This technique, called
        +   * foreshortening, creates realistic 3D scenes.
        +   *
        +   * `myCamera.frustum()` changes the camera’s perspective by changing its
        +   * viewing frustum. The frustum is the volume of space that’s visible to the
        +   * camera. The frustum’s shape is a pyramid with its top cut off. The camera
        +   * is placed where the top of the pyramid should be and points towards the
        +   * base of the pyramid. It views everything within the frustum.
        +   *
        +   * The first four parameters, `left`, `right`, `bottom`, and `top`, set the
        +   * coordinates of the frustum’s sides, bottom, and top. For example, calling
        +   * `myCamera.frustum(-100, 100, 200, -200)` creates a frustum that’s 200
        +   * pixels wide and 400 pixels tall. By default, these coordinates are set
        +   * based on the sketch’s width and height, as in
        +   * `myCamera.frustum(-width / 20, width / 20, height / 20, -height / 20)`.
        +   *
        +   * The last two parameters, `near` and `far`, set the distance of the
        +   * frustum’s near and far plane from the camera. For example, calling
        +   * `myCamera.frustum(-100, 100, 200, -200, 50, 1000)` creates a frustum that’s
        +   * 200 pixels wide, 400 pixels tall, starts 50 pixels from the camera, and ends
        +   * 1,000 pixels from the camera. By default, near is set to `0.1 * 800`, which
        +   * is 1/10th the default distance between the camera and the origin. `far` is
        +   * set to `10 * 800`, which is 10 times the default distance between the
        +   * camera and the origin.
        +   *
        +   * @for p5.Camera
        +   * @param  {Number} [left]   x-coordinate of the frustum’s left plane. Defaults to `-width / 20`.
        +   * @param  {Number} [right]  x-coordinate of the frustum’s right plane. Defaults to `width / 20`.
        +   * @param  {Number} [bottom] y-coordinate of the frustum’s bottom plane. Defaults to `height / 20`.
        +   * @param  {Number} [top]    y-coordinate of the frustum’s top plane. Defaults to `-height / 20`.
        +   * @param  {Number} [near]   z-coordinate of the frustum’s near plane. Defaults to `0.1 * 800`.
        +   * @param  {Number} [far]    z-coordinate of the frustum’s far plane. Defaults to `10 * 800`.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let isDefaultCamera = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   cam2 = createCamera();
        +   *
        +   *   // Adjust the frustum.
        +   *   // Center it.
        +   *   // Set its width and height to 20 pixels.
        +   *   // Place its near plane 300 pixels from the camera.
        +   *   // Place its far plane 350 pixels from the camera.
        +   *   cam2.frustum(-10, 10, -10, 10, 300, 350);
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe(
        +   *     'A row of white cubes against a gray background. The camera zooms in on one cube when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 600);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -40);
        +   *     box(10);
        +   *   }
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isDefaultCamera === true) {
        +   *     setCamera(cam2);
        +   *     isDefaultCamera = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     isDefaultCamera = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
           frustum(left, right, bottom, top, near, far) {
             if (left === undefined) left = -this._renderer.width * 0.05;
             if (right === undefined) right = +this._renderer.width * 0.05;
        @@ -2396,17 +1510,17 @@ p5.Camera = class Camera {
             const ty = (top + bottom) / h;
             const tz = -(far + near) / d;
         
        -    this.projMatrix = p5.Matrix.identity();
        +    this.projMatrix = new Matrix(4);
         
             /* eslint-disable indent */
        -    this.projMatrix.set(  x,  0,  0,  0,
        -                          0,  -y,  0,  0,
        -                        tx, ty, tz, -1,
        -                          0,  0,  z,  0);
        +    this.projMatrix.set(x, 0, 0, 0,
        +      0, -y, 0, 0,
        +      tx, ty, tz, -1,
        +      0, 0, z, 0);
             /* eslint-enable indent */
         
             if (this._isActive()) {
        -      this._renderer.uPMatrix.set(this.projMatrix);
        +      this._renderer.states.uPMatrix.set(this.projMatrix);
             }
         
             this.cameraType = 'custom';
        @@ -2417,11 +1531,10 @@ p5.Camera = class Camera {
           ////////////////////////////////////////////////////////////////////////////////
         
           /**
        - * Rotate camera view about arbitrary axis defined by x,y,z
        - * based on http://learnwebgl.brown37.net/07_cameras/camera_rotating_motion.html
        - * @method _rotateView
        - * @private
        - */
        +   * Rotate camera view about arbitrary axis defined by x,y,z
        +   * based on http://learnwebgl.brown37.net/07_cameras/camera_rotating_motion.html
        +   * @private
        +   */
           _rotateView(a, x, y, z) {
             let centerX = this.centerX;
             let centerY = this.centerY;
        @@ -2432,8 +1545,8 @@ p5.Camera = class Camera {
             centerY -= this.eyeY;
             centerZ -= this.eyeZ;
         
        -    const rotation = p5.Matrix.identity(this._renderer._pInst);
        -    rotation.rotate(this._renderer._pInst._toRadians(a), x, y, z);
        +    const rotation = new Matrix(4); // TODO Maybe pass p5
        +    rotation.rotate4x4(this._renderer._pInst._toRadians(a), x, y, z);
         
             /* eslint-disable max-len */
             const rotatedCenter = [
        @@ -2462,70 +1575,70 @@ p5.Camera = class Camera {
           }
         
           /**
        - * Rotates the camera in a clockwise/counter-clockwise direction.
        - *
        - * Rolling rotates the camera without changing its orientation. The rotation
        - * happens in the camera’s "local" space.
        - *
        - * The parameter, `angle`, is the angle the camera should rotate. Passing a
        - * positive angle, as in `myCamera.roll(0.001)`, rotates the camera in counter-clockwise direction.
        - * Passing a negative angle, as in `myCamera.roll(-0.001)`, rotates the
        - * camera in clockwise direction.
        - *
        - * Note: Angles are interpreted based on the current
        - * <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * @method roll
        - * @param {Number} angle amount to rotate camera in current
        - * <a href="#/p5/angleMode">angleMode</a> units.
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let delta = 0.01;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *   normalMaterial();
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Roll camera according to angle 'delta'
        - *   cam.roll(delta);
        - *
        - *   translate(0, 0, 0);
        - *   box(20);
        - *   translate(0, 25, 0);
        - *   box(20);
        - *   translate(0, 26, 0);
        - *   box(20);
        - *   translate(0, 27, 0);
        - *   box(20);
        - *   translate(0, 28, 0);
        - *   box(20);
        - *   translate(0,29, 0);
        - *   box(20);
        - *   translate(0, 30, 0);
        - *   box(20);
        - * }
        - * </code>
        - * </div>
        - *
        - * @alt
        - * camera view rotates in counter clockwise direction with vertically stacked boxes in front of it.
        - */
        +   * Rotates the camera in a clockwise/counter-clockwise direction.
        +   *
        +   * Rolling rotates the camera without changing its orientation. The rotation
        +   * happens in the camera’s "local" space.
        +   *
        +   * The parameter, `angle`, is the angle the camera should rotate. Passing a
        +   * positive angle, as in `myCamera.roll(0.001)`, rotates the camera in counter-clockwise direction.
        +   * Passing a negative angle, as in `myCamera.roll(-0.001)`, rotates the
        +   * camera in clockwise direction.
        +   *
        +   * Note: Angles are interpreted based on the current
        +   * <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * @method roll
        +   * @param {Number} angle amount to rotate camera in current
        +   * <a href="#/p5/angleMode">angleMode</a> units.
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let delta = 0.01;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *   normalMaterial();
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Roll camera according to angle 'delta'
        +   *   cam.roll(delta);
        +   *
        +   *   translate(0, 0, 0);
        +   *   box(20);
        +   *   translate(0, 25, 0);
        +   *   box(20);
        +   *   translate(0, 26, 0);
        +   *   box(20);
        +   *   translate(0, 27, 0);
        +   *   box(20);
        +   *   translate(0, 28, 0);
        +   *   box(20);
        +   *   translate(0,29, 0);
        +   *   box(20);
        +   *   translate(0, 30, 0);
        +   *   box(20);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @alt
        +   * camera view rotates in counter clockwise direction with vertically stacked boxes in front of it.
        +   */
           roll(amount) {
             const local = this._getLocalAxes();
        -    const axisQuaternion = p5.Quat.fromAxisAngle(
        +    const axisQuaternion = Quat.fromAxisAngle(
               this._renderer._pInst._toRadians(amount),
               local.z[0], local.z[1], local.z[2]);
             // const upQuat = new p5.Quat(0, this.upX, this.upY, this.upZ);
             const newUpVector = axisQuaternion.rotateVector(
        -      new p5.Vector(this.upX, this.upY, this.upZ));
        +      new Vector(this.upX, this.upY, this.upZ));
             this.camera(
               this.eyeX,
               this.eyeY,
        @@ -2540,210 +1653,207 @@ p5.Camera = class Camera {
           }
         
           /**
        - * Rotates the camera left and right.
        - *
        - * Panning rotates the camera without changing its position. The rotation
        - * happens in the camera’s "local" space.
        - *
        - * The parameter, `angle`, is the angle the camera should rotate. Passing a
        - * positive angle, as in `myCamera.pan(0.001)`, rotates the camera to the
        - * right. Passing a negative angle, as in `myCamera.pan(-0.001)`, rotates the
        - * camera to the left.
        - *
        - * Note: Angles are interpreted based on the current
        - * <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * @method pan
        - * @param {Number} angle amount to rotate in the current
        - *                       <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let delta = 0.001;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at the origin.
        - *   cam.lookAt(0, 0, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube goes in and out of view as the camera pans left and right.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Pan with the camera.
        - *   cam.pan(delta);
        - *
        - *   // Switch directions every 120 frames.
        - *   if (frameCount % 120 === 0) {
        - *     delta *= -1;
        - *   }
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Rotates the camera left and right.
        +   *
        +   * Panning rotates the camera without changing its position. The rotation
        +   * happens in the camera’s "local" space.
        +   *
        +   * The parameter, `angle`, is the angle the camera should rotate. Passing a
        +   * positive angle, as in `myCamera.pan(0.001)`, rotates the camera to the
        +   * right. Passing a negative angle, as in `myCamera.pan(-0.001)`, rotates the
        +   * camera to the left.
        +   *
        +   * Note: Angles are interpreted based on the current
        +   * <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * @param {Number} angle amount to rotate in the current
        +   *                       <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let delta = 0.001;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at the origin.
        +   *   cam.lookAt(0, 0, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube goes in and out of view as the camera pans left and right.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Pan with the camera.
        +   *   cam.pan(delta);
        +   *
        +   *   // Switch directions every 120 frames.
        +   *   if (frameCount % 120 === 0) {
        +   *     delta *= -1;
        +   *   }
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           pan(amount) {
             const local = this._getLocalAxes();
             this._rotateView(amount, local.y[0], local.y[1], local.y[2]);
           }
         
           /**
        - * Rotates the camera up and down.
        - *
        - * Tilting rotates the camera without changing its position. The rotation
        - * happens in the camera’s "local" space.
        - *
        - * The parameter, `angle`, is the angle the camera should rotate. Passing a
        - * positive angle, as in `myCamera.tilt(0.001)`, rotates the camera down.
        - * Passing a negative angle, as in `myCamera.tilt(-0.001)`, rotates the camera
        - * up.
        - *
        - * Note: Angles are interpreted based on the current
        - * <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * @method tilt
        - * @param {Number} angle amount to rotate in the current
        - *                       <a href="#/p5/angleMode">angleMode()</a>.
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let delta = 0.001;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at the origin.
        - *   cam.lookAt(0, 0, 0);
        - *
        - *   describe(
        - *     'A white cube on a gray background. The cube goes in and out of view as the camera tilts up and down.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Pan with the camera.
        - *   cam.tilt(delta);
        - *
        - *   // Switch directions every 120 frames.
        - *   if (frameCount % 120 === 0) {
        - *     delta *= -1;
        - *   }
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Rotates the camera up and down.
        +   *
        +   * Tilting rotates the camera without changing its position. The rotation
        +   * happens in the camera’s "local" space.
        +   *
        +   * The parameter, `angle`, is the angle the camera should rotate. Passing a
        +   * positive angle, as in `myCamera.tilt(0.001)`, rotates the camera down.
        +   * Passing a negative angle, as in `myCamera.tilt(-0.001)`, rotates the camera
        +   * up.
        +   *
        +   * Note: Angles are interpreted based on the current
        +   * <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * @param {Number} angle amount to rotate in the current
        +   *                       <a href="#/p5/angleMode">angleMode()</a>.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let delta = 0.001;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at the origin.
        +   *   cam.lookAt(0, 0, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube goes in and out of view as the camera tilts up and down.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Pan with the camera.
        +   *   cam.tilt(delta);
        +   *
        +   *   // Switch directions every 120 frames.
        +   *   if (frameCount % 120 === 0) {
        +   *     delta *= -1;
        +   *   }
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           tilt(amount) {
             const local = this._getLocalAxes();
             this._rotateView(amount, local.x[0], local.x[1], local.x[2]);
           }
         
           /**
        - * Points the camera at a location.
        - *
        - * `myCamera.lookAt()` changes the camera’s orientation without changing its
        - * position.
        - *
        - * The parameters, `x`, `y`, and `z`, are the coordinates in "world" space
        - * where the camera should point. For example, calling
        - * `myCamera.lookAt(10, 20, 30)` points the camera at the coordinates
        - * `(10, 20, 30)`.
        - *
        - * @method lookAt
        - * @for p5.Camera
        - * @param {Number} x x-coordinate of the position where the camera should look in "world" space.
        - * @param {Number} y y-coordinate of the position where the camera should look in "world" space.
        - * @param {Number} z z-coordinate of the position where the camera should look in "world" space.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to look at a different cube.
        - *
        - * let cam;
        - * let isLookingLeft = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Camera object.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-center.
        - *   cam.setPosition(0, -400, 800);
        - *
        - *   // Point the camera at the origin.
        - *   cam.lookAt(-30, 0, 0);
        - *
        - *   describe(
        - *     'A red cube and a blue cube on a gray background. The camera switches focus between the cubes when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the box on the left.
        - *   push();
        - *   // Translate the origin to the left.
        - *   translate(-30, 0, 0);
        - *   // Style the box.
        - *   fill(255, 0, 0);
        - *   // Draw the box.
        - *   box(20);
        - *   pop();
        - *
        - *   // Draw the box on the right.
        - *   push();
        - *   // Translate the origin to the right.
        - *   translate(30, 0, 0);
        - *   // Style the box.
        - *   fill(0, 0, 255);
        - *   // Draw the box.
        - *   box(20);
        - *   pop();
        - * }
        - *
        - * // Change the camera's focus when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isLookingLeft === true) {
        - *     cam.lookAt(30, 0, 0);
        - *     isLookingLeft = false;
        - *   } else {
        - *     cam.lookAt(-30, 0, 0);
        - *     isLookingLeft = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        +   * Points the camera at a location.
        +   *
        +   * `myCamera.lookAt()` changes the camera’s orientation without changing its
        +   * position.
        +   *
        +   * The parameters, `x`, `y`, and `z`, are the coordinates in "world" space
        +   * where the camera should point. For example, calling
        +   * `myCamera.lookAt(10, 20, 30)` points the camera at the coordinates
        +   * `(10, 20, 30)`.
        +   *
        +   * @for p5.Camera
        +   * @param {Number} x x-coordinate of the position where the camera should look in "world" space.
        +   * @param {Number} y y-coordinate of the position where the camera should look in "world" space.
        +   * @param {Number} z z-coordinate of the position where the camera should look in "world" space.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to look at a different cube.
        +   *
        +   * let cam;
        +   * let isLookingLeft = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at the origin.
        +   *   cam.lookAt(-30, 0, 0);
        +   *
        +   *   describe(
        +   *     'A red cube and a blue cube on a gray background. The camera switches focus between the cubes when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the box on the left.
        +   *   push();
        +   *   // Translate the origin to the left.
        +   *   translate(-30, 0, 0);
        +   *   // Style the box.
        +   *   fill(255, 0, 0);
        +   *   // Draw the box.
        +   *   box(20);
        +   *   pop();
        +   *
        +   *   // Draw the box on the right.
        +   *   push();
        +   *   // Translate the origin to the right.
        +   *   translate(30, 0, 0);
        +   *   // Style the box.
        +   *   fill(0, 0, 255);
        +   *   // Draw the box.
        +   *   box(20);
        +   *   pop();
        +   * }
        +   *
        +   * // Change the camera's focus when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isLookingLeft === true) {
        +   *     cam.lookAt(30, 0, 0);
        +   *     isLookingLeft = false;
        +   *   } else {
        +   *     cam.lookAt(-30, 0, 0);
        +   *     isLookingLeft = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
           lookAt(x, y, z) {
             this.camera(
               this.eyeX,
        @@ -2763,170 +1873,169 @@ p5.Camera = class Camera {
           ////////////////////////////////////////////////////////////////////////////////
         
           /**
        - * Sets the position and orientation of the camera.
        - *
        - * `myCamera.camera()` allows objects to be viewed from different angles. It
        - * has nine parameters that are all optional.
        - *
        - * The first three parameters, `x`, `y`, and `z`, are the coordinates of the
        - * camera’s position in "world" space. For example, calling
        - * `myCamera.camera(0, 0, 0)` places the camera at the origin `(0, 0, 0)`. By
        - * default, the camera is placed at `(0, 0, 800)`.
        - *
        - * The next three parameters, `centerX`, `centerY`, and `centerZ` are the
        - * coordinates of the point where the camera faces in "world" space. For
        - * example, calling `myCamera.camera(0, 0, 0, 10, 20, 30)` places the camera
        - * at the origin `(0, 0, 0)` and points it at `(10, 20, 30)`. By default, the
        - * camera points at the origin `(0, 0, 0)`.
        - *
        - * The last three parameters, `upX`, `upY`, and `upZ` are the components of
        - * the "up" vector in "local" space. The "up" vector orients the camera’s
        - * y-axis. For example, calling
        - * `myCamera.camera(0, 0, 0, 10, 20, 30, 0, -1, 0)` places the camera at the
        - * origin `(0, 0, 0)`, points it at `(10, 20, 30)`, and sets the "up" vector
        - * to `(0, -1, 0)` which is like holding it upside-down. By default, the "up"
        - * vector is `(0, 1, 0)`.
        - *
        - * @method camera
        - * @for p5.Camera
        - * @param  {Number} [x]        x-coordinate of the camera. Defaults to 0.
        - * @param  {Number} [y]        y-coordinate of the camera. Defaults to 0.
        - * @param  {Number} [z]        z-coordinate of the camera. Defaults to 800.
        - * @param  {Number} [centerX]  x-coordinate of the point the camera faces. Defaults to 0.
        - * @param  {Number} [centerY]  y-coordinate of the point the camera faces. Defaults to 0.
        - * @param  {Number} [centerZ]  z-coordinate of the point the camera faces. Defaults to 0.
        - * @param  {Number} [upX]      x-component of the camera’s "up" vector. Defaults to 0.
        - * @param  {Number} [upY]      x-component of the camera’s "up" vector. Defaults to 1.
        - * @param  {Number} [upZ]      z-component of the camera’s "up" vector. Defaults to 0.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let isDefaultCamera = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   cam2 = createCamera();
        - *
        - *   // Place it at the top-right: (1200, -600, 100)
        - *   // Point it at the row of boxes: (-10, -10, 400)
        - *   // Set its "up" vector to the default: (0, 1, 0)
        - *   cam2.camera(1200, -600, 100, -10, -10, 400, 0, 1, 0);
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe(
        - *     'A row of white cubes against a gray background. The camera toggles between a frontal and an aerial view when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 500);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -30);
        - *     box(10);
        - *   }
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isDefaultCamera === true) {
        - *     setCamera(cam2);
        - *     isDefaultCamera = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     isDefaultCamera = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let isDefaultCamera = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   cam2 = createCamera();
        - *
        - *   // Place it at the right: (1200, 0, 100)
        - *   // Point it at the row of boxes: (-10, -10, 400)
        - *   // Set its "up" vector to the default: (0, 1, 0)
        - *   cam2.camera(1200, 0, 100, -10, -10, 400, 0, 1, 0);
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe(
        - *     'A row of white cubes against a gray background. The camera toggles between a static frontal view and an orbiting view when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Update cam2's position.
        - *   let x = 1200 * cos(frameCount * 0.01);
        - *   let y = -600 * sin(frameCount * 0.01);
        - *   cam2.camera(x, y, 100, -10, -10, 400, 0, 1, 0);
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 500);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -30);
        - *     box(10);
        - *   }
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isDefaultCamera === true) {
        - *     setCamera(cam2);
        - *     isDefaultCamera = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     isDefaultCamera = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        +   * Sets the position and orientation of the camera.
        +   *
        +   * `myCamera.camera()` allows objects to be viewed from different angles. It
        +   * has nine parameters that are all optional.
        +   *
        +   * The first three parameters, `x`, `y`, and `z`, are the coordinates of the
        +   * camera’s position in "world" space. For example, calling
        +   * `myCamera.camera(0, 0, 0)` places the camera at the origin `(0, 0, 0)`. By
        +   * default, the camera is placed at `(0, 0, 800)`.
        +   *
        +   * The next three parameters, `centerX`, `centerY`, and `centerZ` are the
        +   * coordinates of the point where the camera faces in "world" space. For
        +   * example, calling `myCamera.camera(0, 0, 0, 10, 20, 30)` places the camera
        +   * at the origin `(0, 0, 0)` and points it at `(10, 20, 30)`. By default, the
        +   * camera points at the origin `(0, 0, 0)`.
        +   *
        +   * The last three parameters, `upX`, `upY`, and `upZ` are the components of
        +   * the "up" vector in "local" space. The "up" vector orients the camera’s
        +   * y-axis. For example, calling
        +   * `myCamera.camera(0, 0, 0, 10, 20, 30, 0, -1, 0)` places the camera at the
        +   * origin `(0, 0, 0)`, points it at `(10, 20, 30)`, and sets the "up" vector
        +   * to `(0, -1, 0)` which is like holding it upside-down. By default, the "up"
        +   * vector is `(0, 1, 0)`.
        +   *
        +   * @for p5.Camera
        +   * @param  {Number} [x]        x-coordinate of the camera. Defaults to 0.
        +   * @param  {Number} [y]        y-coordinate of the camera. Defaults to 0.
        +   * @param  {Number} [z]        z-coordinate of the camera. Defaults to 800.
        +   * @param  {Number} [centerX]  x-coordinate of the point the camera faces. Defaults to 0.
        +   * @param  {Number} [centerY]  y-coordinate of the point the camera faces. Defaults to 0.
        +   * @param  {Number} [centerZ]  z-coordinate of the point the camera faces. Defaults to 0.
        +   * @param  {Number} [upX]      x-component of the camera’s "up" vector. Defaults to 0.
        +   * @param  {Number} [upY]      x-component of the camera’s "up" vector. Defaults to 1.
        +   * @param  {Number} [upZ]      z-component of the camera’s "up" vector. Defaults to 0.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let isDefaultCamera = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   cam2 = createCamera();
        +   *
        +   *   // Place it at the top-right: (1200, -600, 100)
        +   *   // Point it at the row of boxes: (-10, -10, 400)
        +   *   // Set its "up" vector to the default: (0, 1, 0)
        +   *   cam2.camera(1200, -600, 100, -10, -10, 400, 0, 1, 0);
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe(
        +   *     'A row of white cubes against a gray background. The camera toggles between a frontal and an aerial view when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 500);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -30);
        +   *     box(10);
        +   *   }
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isDefaultCamera === true) {
        +   *     setCamera(cam2);
        +   *     isDefaultCamera = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     isDefaultCamera = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let isDefaultCamera = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   cam2 = createCamera();
        +   *
        +   *   // Place it at the right: (1200, 0, 100)
        +   *   // Point it at the row of boxes: (-10, -10, 400)
        +   *   // Set its "up" vector to the default: (0, 1, 0)
        +   *   cam2.camera(1200, 0, 100, -10, -10, 400, 0, 1, 0);
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe(
        +   *     'A row of white cubes against a gray background. The camera toggles between a static frontal view and an orbiting view when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Update cam2's position.
        +   *   let x = 1200 * cos(frameCount * 0.01);
        +   *   let y = -600 * sin(frameCount * 0.01);
        +   *   cam2.camera(x, y, 100, -10, -10, 400, 0, 1, 0);
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 500);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -30);
        +   *     box(10);
        +   *   }
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isDefaultCamera === true) {
        +   *     setCamera(cam2);
        +   *     isDefaultCamera = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     isDefaultCamera = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
           camera(
             eyeX,
             eyeY,
        @@ -2973,9 +2082,9 @@ p5.Camera = class Camera {
             // and rotates it.
             /* eslint-disable indent */
             this.cameraMatrix.set(local.x[0], local.y[0], local.z[0], 0,
        -                          local.x[1], local.y[1], local.z[1], 0,
        -                          local.x[2], local.y[2], local.z[2], 0,
        -                                  0,          0,          0, 1);
        +      local.x[1], local.y[1], local.z[1], 0,
        +      local.x[2], local.y[2], local.z[2], 0,
        +      0, 0, 0, 1);
             /* eslint-enable indent */
         
             const tx = -eyeX;
        @@ -2985,86 +2094,85 @@ p5.Camera = class Camera {
             this.cameraMatrix.translate([tx, ty, tz]);
         
             if (this._isActive()) {
        -      this._renderer.uViewMatrix.set(this.cameraMatrix);
        +      this._renderer.states.uViewMatrix.set(this.cameraMatrix);
             }
             return this;
           }
         
           /**
        - * Moves the camera along its "local" axes without changing its orientation.
        - *
        - * The parameters, `x`, `y`, and `z`, are the distances the camera should
        - * move. For example, calling `myCamera.move(10, 20, 30)` moves the camera 10
        - * pixels to the right, 20 pixels down, and 30 pixels backward in its "local"
        - * space.
        - *
        - * @method move
        - * @param {Number} x distance to move along the camera’s "local" x-axis.
        - * @param {Number} y distance to move along the camera’s "local" y-axis.
        - * @param {Number} z distance to move along the camera’s "local" z-axis.
        - * @example
        - * <div>
        - * <code>
        - * // Click the canvas to begin detecting key presses.
        - *
        - * let cam;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam = createCamera();
        - *
        - *   // Place the camera at the top-right.
        - *   cam.setPosition(400, -400, 800);
        - *
        - *   // Point it at the origin.
        - *   cam.lookAt(0, 0, 0);
        - *
        - *   describe(
        - *     'A white cube drawn against a gray background. The cube appears to move when the user presses certain keys.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Move the camera along its "local" axes
        - *   // when the user presses certain keys.
        - *   if (keyIsPressed === true) {
        - *
        - *     // Move horizontally.
        - *     if (keyCode === LEFT_ARROW) {
        - *       cam.move(-1, 0, 0);
        - *     }
        - *     if (keyCode === RIGHT_ARROW) {
        - *       cam.move(1, 0, 0);
        - *     }
        - *
        - *     // Move vertically.
        - *     if (keyCode === UP_ARROW) {
        - *       cam.move(0, -1, 0);
        - *     }
        - *     if (keyCode === DOWN_ARROW) {
        - *       cam.move(0, 1, 0);
        - *     }
        - *
        - *     // Move in/out of the screen.
        - *     if (key === 'i') {
        - *       cam.move(0, 0, -1);
        - *     }
        - *     if (key === 'o') {
        - *       cam.move(0, 0, 1);
        - *     }
        - *   }
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Moves the camera along its "local" axes without changing its orientation.
        +   *
        +   * The parameters, `x`, `y`, and `z`, are the distances the camera should
        +   * move. For example, calling `myCamera.move(10, 20, 30)` moves the camera 10
        +   * pixels to the right, 20 pixels down, and 30 pixels backward in its "local"
        +   * space.
        +   *
        +   * @param {Number} x distance to move along the camera’s "local" x-axis.
        +   * @param {Number} y distance to move along the camera’s "local" y-axis.
        +   * @param {Number} z distance to move along the camera’s "local" z-axis.
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click the canvas to begin detecting key presses.
        +   *
        +   * let cam;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-right.
        +   *   cam.setPosition(400, -400, 800);
        +   *
        +   *   // Point it at the origin.
        +   *   cam.lookAt(0, 0, 0);
        +   *
        +   *   describe(
        +   *     'A white cube drawn against a gray background. The cube appears to move when the user presses certain keys.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Move the camera along its "local" axes
        +   *   // when the user presses certain keys.
        +   *   if (keyIsPressed === true) {
        +   *
        +   *     // Move horizontally.
        +   *     if (keyCode === LEFT_ARROW) {
        +   *       cam.move(-1, 0, 0);
        +   *     }
        +   *     if (keyCode === RIGHT_ARROW) {
        +   *       cam.move(1, 0, 0);
        +   *     }
        +   *
        +   *     // Move vertically.
        +   *     if (keyCode === UP_ARROW) {
        +   *       cam.move(0, -1, 0);
        +   *     }
        +   *     if (keyCode === DOWN_ARROW) {
        +   *       cam.move(0, 1, 0);
        +   *     }
        +   *
        +   *     // Move in/out of the screen.
        +   *     if (key === 'i') {
        +   *       cam.move(0, 0, -1);
        +   *     }
        +   *     if (key === 'o') {
        +   *       cam.move(0, 0, 1);
        +   *     }
        +   *   }
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           move(x, y, z) {
             const local = this._getLocalAxes();
         
        @@ -3088,141 +2196,140 @@ p5.Camera = class Camera {
           }
         
           /**
        - * Sets the camera’s position in "world" space without changing its
        - * orientation.
        - *
        - * The parameters, `x`, `y`, and `z`, are the coordinates where the camera
        - * should be placed. For example, calling `myCamera.setPosition(10, 20, 30)`
        - * places the camera at coordinates `(10, 20, 30)` in "world" space.
        - *
        - * @method setPosition
        - * @param {Number} x x-coordinate in "world" space.
        - * @param {Number} y y-coordinate in "world" space.
        - * @param {Number} z z-coordinate in "world" space.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let isDefaultCamera = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   cam2 = createCamera();
        - *
        - *   // Place it closer to the origin.
        - *   cam2.setPosition(0, 0, 600);
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe(
        - *     'A row of white cubes against a gray background. The camera toggles the amount of zoom when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 500);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -30);
        - *     box(10);
        - *   }
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isDefaultCamera === true) {
        - *     setCamera(cam2);
        - *     isDefaultCamera = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     isDefaultCamera = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let isDefaultCamera = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   cam2 = createCamera();
        - *
        - *   // Place it closer to the origin.
        - *   cam2.setPosition(0, 0, 600);
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe(
        - *     'A row of white cubes against a gray background. The camera toggles between a static view and a view that zooms in and out when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Update cam2's z-coordinate.
        - *   let z = 100 * sin(frameCount * 0.01) + 700;
        - *   cam2.setPosition(0, 0, z);
        - *
        - *   // Translate the origin toward the camera.
        - *   translate(-10, 10, 500);
        - *
        - *   // Rotate the coordinate system.
        - *   rotateY(-0.1);
        - *   rotateX(-0.1);
        - *
        - *   // Draw the row of boxes.
        - *   for (let i = 0; i < 6; i += 1) {
        - *     translate(0, 0, -30);
        - *     box(10);
        - *   }
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (isDefaultCamera === true) {
        - *     setCamera(cam2);
        - *     isDefaultCamera = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     isDefaultCamera = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        +   * Sets the camera’s position in "world" space without changing its
        +   * orientation.
        +   *
        +   * The parameters, `x`, `y`, and `z`, are the coordinates where the camera
        +   * should be placed. For example, calling `myCamera.setPosition(10, 20, 30)`
        +   * places the camera at coordinates `(10, 20, 30)` in "world" space.
        +   *
        +   * @param {Number} x x-coordinate in "world" space.
        +   * @param {Number} y y-coordinate in "world" space.
        +   * @param {Number} z z-coordinate in "world" space.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let isDefaultCamera = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   cam2 = createCamera();
        +   *
        +   *   // Place it closer to the origin.
        +   *   cam2.setPosition(0, 0, 600);
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe(
        +   *     'A row of white cubes against a gray background. The camera toggles the amount of zoom when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 500);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -30);
        +   *     box(10);
        +   *   }
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isDefaultCamera === true) {
        +   *     setCamera(cam2);
        +   *     isDefaultCamera = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     isDefaultCamera = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let isDefaultCamera = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   cam2 = createCamera();
        +   *
        +   *   // Place it closer to the origin.
        +   *   cam2.setPosition(0, 0, 600);
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe(
        +   *     'A row of white cubes against a gray background. The camera toggles between a static view and a view that zooms in and out when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Update cam2's z-coordinate.
        +   *   let z = 100 * sin(frameCount * 0.01) + 700;
        +   *   cam2.setPosition(0, 0, z);
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 500);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -30);
        +   *     box(10);
        +   *   }
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isDefaultCamera === true) {
        +   *     setCamera(cam2);
        +   *     isDefaultCamera = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     isDefaultCamera = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
           setPosition(x, y, z) {
             const diffX = x - this.eyeX;
             const diffY = y - this.eyeY;
        @@ -3242,61 +2349,60 @@ p5.Camera = class Camera {
           }
         
           /**
        - * Sets the camera’s position, orientation, and projection by copying another
        - * camera.
        - *
        - * The parameter, `cam`, is the `p5.Camera` object to copy. For example, calling
        - * `cam2.set(cam1)` will set `cam2` using `cam1`’s configuration.
        - *
        - * @method set
        - * @param {p5.Camera} cam camera to copy.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to "reset" the camera zoom.
        - *
        - * let cam1;
        - * let cam2;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   cam1 = createCamera();
        - *
        - *   // Place the camera at the top-right.
        - *   cam1.setPosition(400, -400, 800);
        - *
        - *   // Point it at the origin.
        - *   cam1.lookAt(0, 0, 0);
        - *
        - *   // Create the second camera.
        - *   cam2 = createCamera();
        - *
        - *   // Copy cam1's configuration.
        - *   cam2.set(cam1);
        - *
        - *   describe(
        - *     'A white cube drawn against a gray background. The camera slowly moves forward. The camera resets when the user double-clicks.'
        - *   );
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Update cam2's position.
        - *   cam2.move(0, 0, -1);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - *
        - * // "Reset" the camera when the user double-clicks.
        - * function doubleClicked() {
        - *   cam2.set(cam1);
        - * }
        - */
        +   * Sets the camera’s position, orientation, and projection by copying another
        +   * camera.
        +   *
        +   * The parameter, `cam`, is the `p5.Camera` object to copy. For example, calling
        +   * `cam2.set(cam1)` will set `cam2` using `cam1`’s configuration.
        +   *
        +   * @param {p5.Camera} cam camera to copy.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to "reset" the camera zoom.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Place the camera at the top-right.
        +   *   cam1.setPosition(400, -400, 800);
        +   *
        +   *   // Point it at the origin.
        +   *   cam1.lookAt(0, 0, 0);
        +   *
        +   *   // Create the second camera.
        +   *   cam2 = createCamera();
        +   *
        +   *   // Copy cam1's configuration.
        +   *   cam2.set(cam1);
        +   *
        +   *   describe(
        +   *     'A white cube drawn against a gray background. The camera slowly moves forward. The camera resets when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Update cam2's position.
        +   *   cam2.move(0, 0, -1);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   *
        +   * // "Reset" the camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   cam2.set(cam1);
        +   * }
        +   */
           set(cam) {
             const keyNamesOfThePropToCopy = [
               'eyeX', 'eyeY', 'eyeZ',
        @@ -3313,86 +2419,85 @@ p5.Camera = class Camera {
             this.projMatrix = cam.projMatrix.copy();
         
             if (this._isActive()) {
        -      this._renderer.uModelMatrix.reset();
        -      this._renderer.uViewMatrix.set(this.cameraMatrix);
        -      this._renderer.uPMatrix.set(this.projMatrix);
        +      this._renderer.states.uModelMatrix.reset();
        +      this._renderer.states.uViewMatrix.set(this.cameraMatrix);
        +      this._renderer.states.uPMatrix.set(this.projMatrix);
             }
           }
           /**
        - * Sets the camera’s position and orientation to values that are in-between
        - * those of two other cameras.
        - *
        - * `myCamera.slerp()` uses spherical linear interpolation to calculate a
        - * position and orientation that’s in-between two other cameras. Doing so is
        - * helpful for transitioning smoothly between two perspectives.
        - *
        - * The first two parameters, `cam0` and `cam1`, are the `p5.Camera` objects
        - * that should be used to set the current camera.
        - *
        - * The third parameter, `amt`, is the amount to interpolate between `cam0` and
        - * `cam1`. 0.0 keeps the camera’s position and orientation equal to `cam0`’s,
        - * 0.5 sets them halfway between `cam0`’s and `cam1`’s , and 1.0 sets the
        - * position and orientation equal to `cam1`’s.
        - *
        - * For example, calling `myCamera.slerp(cam0, cam1, 0.1)` sets cam’s position
        - * and orientation very close to `cam0`’s. Calling
        - * `myCamera.slerp(cam0, cam1, 0.9)` sets cam’s position and orientation very
        - * close to `cam1`’s.
        - *
        - * Note: All of the cameras must use the same projection.
        - *
        - * @method slerp
        - * @param {p5.Camera} cam0 first camera.
        - * @param {p5.Camera} cam1 second camera.
        - * @param {Number} amt amount of interpolation between 0.0 (`cam0`) and 1.0 (`cam1`).
        - *
        - * @example
        - * <div>
        - * <code>
        - * let cam;
        - * let cam0;
        - * let cam1;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the main camera.
        - *   // Keep its default settings.
        - *   cam = createCamera();
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam0 = createCamera();
        - *
        - *   // Create the second camera.
        - *   cam1 = createCamera();
        - *
        - *   // Place it at the top-right.
        - *   cam1.setPosition(400, -400, 800);
        - *
        - *   // Point it at the origin.
        - *   cam1.lookAt(0, 0, 0);
        - *
        - *   // Set the current camera to cam.
        - *   setCamera(cam);
        - *
        - *   describe('A white cube drawn against a gray background. The camera slowly oscillates between a frontal view and an aerial view.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Calculate the amount to interpolate between cam0 and cam1.
        - *   let amt = 0.5 * sin(frameCount * 0.01) + 0.5;
        - *
        - *   // Update the main camera's position and orientation.
        - *   cam.slerp(cam0, cam1, amt);
        - *
        - *   box();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Sets the camera’s position and orientation to values that are in-between
        +   * those of two other cameras.
        +   *
        +   * `myCamera.slerp()` uses spherical linear interpolation to calculate a
        +   * position and orientation that’s in-between two other cameras. Doing so is
        +   * helpful for transitioning smoothly between two perspectives.
        +   *
        +   * The first two parameters, `cam0` and `cam1`, are the `p5.Camera` objects
        +   * that should be used to set the current camera.
        +   *
        +   * The third parameter, `amt`, is the amount to interpolate between `cam0` and
        +   * `cam1`. 0.0 keeps the camera’s position and orientation equal to `cam0`’s,
        +   * 0.5 sets them halfway between `cam0`’s and `cam1`’s , and 1.0 sets the
        +   * position and orientation equal to `cam1`’s.
        +   *
        +   * For example, calling `myCamera.slerp(cam0, cam1, 0.1)` sets cam’s position
        +   * and orientation very close to `cam0`’s. Calling
        +   * `myCamera.slerp(cam0, cam1, 0.9)` sets cam’s position and orientation very
        +   * close to `cam1`’s.
        +   *
        +   * Note: All of the cameras must use the same projection.
        +   *
        +   * @param {p5.Camera} cam0 first camera.
        +   * @param {p5.Camera} cam1 second camera.
        +   * @param {Number} amt amount of interpolation between 0.0 (`cam0`) and 1.0 (`cam1`).
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let cam0;
        +   * let cam1;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the main camera.
        +   *   // Keep its default settings.
        +   *   cam = createCamera();
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam0 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Place it at the top-right.
        +   *   cam1.setPosition(400, -400, 800);
        +   *
        +   *   // Point it at the origin.
        +   *   cam1.lookAt(0, 0, 0);
        +   *
        +   *   // Set the current camera to cam.
        +   *   setCamera(cam);
        +   *
        +   *   describe('A white cube drawn against a gray background. The camera slowly oscillates between a frontal view and an aerial view.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the amount to interpolate between cam0 and cam1.
        +   *   let amt = 0.5 * sin(frameCount * 0.01) + 0.5;
        +   *
        +   *   // Update the main camera's position and orientation.
        +   *   cam.slerp(cam0, cam1, amt);
        +   *
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           slerp(cam0, cam1, amt) {
             // If t is 0 or 1, do not interpolate and set the argument camera.
             if (amt === 0) {
        @@ -3407,28 +2512,32 @@ p5.Camera = class Camera {
             // and interpolate the elements of the projection matrix.
             // Use logarithmic interpolation for interpolation.
             if (this.projMatrix.mat4[15] !== 0) {
        -      this.projMatrix.mat4[0] =
        -        cam0.projMatrix.mat4[0] *
        -        Math.pow(cam1.projMatrix.mat4[0] / cam0.projMatrix.mat4[0], amt);
        -      this.projMatrix.mat4[5] =
        -        cam0.projMatrix.mat4[5] *
        -        Math.pow(cam1.projMatrix.mat4[5] / cam0.projMatrix.mat4[5], amt);
        +        this.projMatrix.setElement(
        +          0,
        +          cam0.projMatrix.mat4[0] *
        +            Math.pow(cam1.projMatrix.mat4[0] / cam0.projMatrix.mat4[0], amt)
        +        );
        +        this.projMatrix.setElement(
        +          5,
        +          cam0.projMatrix.mat4[5] *
        +            Math.pow(cam1.projMatrix.mat4[5] / cam0.projMatrix.mat4[5], amt)
        +        );
               // If the camera is active, make uPMatrix reflect changes in projMatrix.
               if (this._isActive()) {
        -        this._renderer.uPMatrix.mat4 = this.projMatrix.mat4.slice();
        +        this._renderer.states.uPMatrix.mat4 = this.projMatrix.mat4.slice();
               }
             }
         
             // prepare eye vector and center vector of argument cameras.
        -    const eye0 = new p5.Vector(cam0.eyeX, cam0.eyeY, cam0.eyeZ);
        -    const eye1 = new p5.Vector(cam1.eyeX, cam1.eyeY, cam1.eyeZ);
        -    const center0 = new p5.Vector(cam0.centerX, cam0.centerY, cam0.centerZ);
        -    const center1 = new p5.Vector(cam1.centerX, cam1.centerY, cam1.centerZ);
        +    const eye0 = new Vector(cam0.eyeX, cam0.eyeY, cam0.eyeZ);
        +    const eye1 = new Vector(cam1.eyeX, cam1.eyeY, cam1.eyeZ);
        +    const center0 = new Vector(cam0.centerX, cam0.centerY, cam0.centerZ);
        +    const center1 = new Vector(cam1.centerX, cam1.centerY, cam1.centerZ);
         
             // Calculate the distance between eye and center for each camera.
             // Logarithmically interpolate these with amt.
        -    const dist0 = p5.Vector.dist(eye0, center0);
        -    const dist1 = p5.Vector.dist(eye1, center1);
        +    const dist0 = Vector.dist(eye0, center0);
        +    const dist1 = Vector.dist(eye1, center1);
             const lerpedDist = dist0 * Math.pow(dist1 / dist0, amt);
         
             // Next, calculate the ratio to interpolate the eye and center by a constant
        @@ -3439,7 +2548,7 @@ p5.Camera = class Camera {
             // at the viewpoint, and if the center is fixed, linear interpolation is performed
             // at the center, resulting in reasonable interpolation. If both move, the point
             // halfway between them is taken.
        -    const eyeDiff = p5.Vector.sub(eye0, eye1);
        +    const eyeDiff = Vector.sub(eye0, eye1);
             const diffDiff = eye0.copy().sub(eye1).sub(center0).add(center1);
             // Suppose there are two line segments. Consider the distance between the points
             // above them as if they were taken in the same ratio. This calculation figures out
        @@ -3448,16 +2557,16 @@ p5.Camera = class Camera {
             // for each camera.
             const divider = diffDiff.magSq();
             let ratio = 1; // default.
        -    if (divider > 0.000001){
        -      ratio = p5.Vector.dot(eyeDiff, diffDiff) / divider;
        +    if (divider > 0.000001) {
        +      ratio = Vector.dot(eyeDiff, diffDiff) / divider;
               ratio = Math.max(0, Math.min(ratio, 1));
             }
         
             // Take the appropriate proportions and work out the points
             // that are between the new viewpoint and the new center position.
        -    const lerpedMedium = p5.Vector.lerp(
        -      p5.Vector.lerp(eye0, center0, ratio),
        -      p5.Vector.lerp(eye1, center1, ratio),
        +    const lerpedMedium = Vector.lerp(
        +      Vector.lerp(eye0, center0, ratio),
        +      Vector.lerp(eye1, center1, ratio),
               amt
             );
         
        @@ -3472,16 +2581,16 @@ p5.Camera = class Camera {
             const up1 = rotMat1.row(1);
         
             // prepare new vectors.
        -    const newFront = new p5.Vector();
        -    const newUp = new p5.Vector();
        -    const newEye = new p5.Vector();
        -    const newCenter = new p5.Vector();
        +    const newFront = new Vector();
        +    const newUp = new Vector();
        +    const newEye = new Vector();
        +    const newCenter = new Vector();
         
             // Create the inverse matrix of mat0 by transposing mat0,
             // and multiply it to mat1 from the right.
             // This matrix represents the difference between the two.
             // 'deltaRot' means 'difference of rotation matrices'.
        -    const deltaRot = rotMat1.mult3x3(rotMat0.copy().transpose3x3());
        +    const deltaRot = rotMat1.mult(rotMat0.copy().transpose()); // mat1 is 3x3
         
             // Calculate the trace and from it the cos value of the angle.
             // An orthogonal matrix is just an orthonormal basis. If this is not the identity
        @@ -3497,12 +2606,12 @@ p5.Camera = class Camera {
               // Obtain the front vector and up vector by linear interpolation
               // and normalize them.
               // calculate newEye, newCenter with newFront vector.
        -      newFront.set(p5.Vector.lerp(front0, front1, amt)).normalize();
        +      newFront.set(Vector.lerp(front0, front1, amt)).normalize();
         
               newEye.set(newFront).mult(ratio * lerpedDist).add(lerpedMedium);
        -      newCenter.set(newFront).mult((ratio-1) * lerpedDist).add(lerpedMedium);
        +      newCenter.set(newFront).mult((ratio - 1) * lerpedDist).add(lerpedMedium);
         
        -      newUp.set(p5.Vector.lerp(up0, up1, amt)).normalize();
        +      newUp.set(Vector.lerp(up0, up1, amt)).normalize();
         
               // set the camera
               this.camera(
        @@ -3557,7 +2666,8 @@ p5.Camera = class Camera {
             const ab = a * b;
             const bc = b * c;
             const ca = c * a;
        -    const lerpedRotMat = new p5.Matrix('mat3', [
        +    // 3x3
        +    const lerpedRotMat = new Matrix( [
               cosAngle + oneMinusCosAngle * a * a,
               oneMinusCosAngle * ab + sinAngle * c,
               oneMinusCosAngle * ca - sinAngle * b,
        @@ -3571,12 +2681,12 @@ p5.Camera = class Camera {
         
             // Multiply this to mat0 from left to get the interpolated front vector.
             // calculate newEye, newCenter with newFront vector.
        -    lerpedRotMat.multiplyVec3(front0, newFront);
        +    lerpedRotMat.multiplyVec(front0, newFront); // this is vec3
         
             newEye.set(newFront).mult(ratio * lerpedDist).add(lerpedMedium);
        -    newCenter.set(newFront).mult((ratio-1) * lerpedDist).add(lerpedMedium);
        +    newCenter.set(newFront).mult((ratio - 1) * lerpedDist).add(lerpedMedium);
         
        -    lerpedRotMat.multiplyVec3(up0, newUp);
        +    lerpedRotMat.multiplyVec(up0, newUp); // this is vec3
         
             // We also get the up vector in the same way and set the camera.
             // The eye position and center position are calculated based on the front vector.
        @@ -3641,12 +2751,11 @@ p5.Camera = class Camera {
           }
         
           /**
        - * Returns a copy of a camera.
        - * @method copy
        - * @private
        - */
        +   * Returns a copy of a camera.
        +   * @private
        +   */
           copy() {
        -    const _cam = new p5.Camera(this._renderer);
        +    const _cam = new Camera(this._renderer);
             _cam.cameraFOV = this.cameraFOV;
             _cam.aspectRatio = this.aspectRatio;
             _cam.eyeX = this.eyeX;
        @@ -3671,12 +2780,15 @@ p5.Camera = class Camera {
             return _cam;
           }
         
        +  clone() {
        +    return this.copy();
        +  }
        +
           /**
        - * Returns a camera's local axes: left-right, up-down, and forward-backward,
        - * as defined by vectors in world-space.
        - * @method _getLocalAxes
        - * @private
        - */
        +   * Returns a camera's local axes: left-right, up-down, and forward-backward,
        +   * as defined by vectors in world-space.
        +   * @private
        +   */
           _getLocalAxes() {
             // calculate camera local Z vector
             let z0 = this.eyeX - this.centerX;
        @@ -3730,13 +2842,12 @@ p5.Camera = class Camera {
           }
         
           /**
        - * Orbits the camera about center point. For use with orbitControl().
        - * @method _orbit
        - * @private
        - * @param {Number} dTheta change in spherical coordinate theta
        - * @param {Number} dPhi change in spherical coordinate phi
        - * @param {Number} dRadius change in radius
        - */
        +   * Orbits the camera about center point. For use with orbitControl().
        +   * @private
        +   * @param {Number} dTheta change in spherical coordinate theta
        +   * @param {Number} dPhi change in spherical coordinate phi
        +   * @param {Number} dRadius change in radius
        +   */
           _orbit(dTheta, dPhi, dRadius) {
             // Calculate the vector and its magnitude from the center to the viewpoint
             const diffX = this.eyeX - this.centerX;
        @@ -3744,13 +2855,13 @@ p5.Camera = class Camera {
             const diffZ = this.eyeZ - this.centerZ;
             let camRadius = Math.hypot(diffX, diffY, diffZ);
             // front vector. unit vector from center to eye.
        -    const front = new p5.Vector(diffX, diffY, diffZ).normalize();
        +    const front = new Vector(diffX, diffY, diffZ).normalize();
             // up vector. normalized camera's up vector.
        -    const up = new p5.Vector(this.upX, this.upY, this.upZ).normalize(); // y-axis
        +    const up = new Vector(this.upX, this.upY, this.upZ).normalize(); // y-axis
             // side vector. Right when viewed from the front
        -    const side = p5.Vector.cross(up, front).normalize(); // x-axis
        +    const side = Vector.cross(up, front).normalize(); // x-axis
             // vertical vector. normalized vector of projection of front vector.
        -    const vertical = p5.Vector.cross(side, up); // z-axis
        +    const vertical = Vector.cross(side, up); // z-axis
         
             // update camRadius
             camRadius *= Math.pow(10, dRadius);
        @@ -3768,7 +2879,7 @@ p5.Camera = class Camera {
             // due to version updates, it cannot be adopted, so here we calculate using a method
             // that directly obtains the absolute value.
             const camPhi =
        -      Math.acos(Math.max(-1, Math.min(1, p5.Vector.dot(front, up)))) + dPhi;
        +      Math.acos(Math.max(-1, Math.min(1, Vector.dot(front, up)))) + dPhi;
             // Rotate by dTheta in the shortest direction from "vertical" to "side"
             const camTheta = dTheta;
         
        @@ -3799,14 +2910,13 @@ p5.Camera = class Camera {
           }
         
           /**
        - * Orbits the camera about center point. For use with orbitControl().
        - * Unlike _orbit(), the direction of rotation always matches the direction of pointer movement.
        - * @method _orbitFree
        - * @private
        - * @param {Number} dx the x component of the rotation vector.
        - * @param {Number} dy the y component of the rotation vector.
        - * @param {Number} dRadius change in radius
        - */
        +   * Orbits the camera about center point. For use with orbitControl().
        +   * Unlike _orbit(), the direction of rotation always matches the direction of pointer movement.
        +   * @private
        +   * @param {Number} dx the x component of the rotation vector.
        +   * @param {Number} dy the y component of the rotation vector.
        +   * @param {Number} dRadius change in radius
        +   */
           _orbitFree(dx, dy, dRadius) {
             // Calculate the vector and its magnitude from the center to the viewpoint
             const diffX = this.eyeX - this.centerX;
        @@ -3814,13 +2924,13 @@ p5.Camera = class Camera {
             const diffZ = this.eyeZ - this.centerZ;
             let camRadius = Math.hypot(diffX, diffY, diffZ);
             // front vector. unit vector from center to eye.
        -    const front = new p5.Vector(diffX, diffY, diffZ).normalize();
        +    const front = new Vector(diffX, diffY, diffZ).normalize();
             // up vector. camera's up vector.
        -    const up = new p5.Vector(this.upX, this.upY, this.upZ);
        +    const up = new Vector(this.upX, this.upY, this.upZ);
             // side vector. Right when viewed from the front. (like x-axis)
        -    const side = p5.Vector.cross(up, front).normalize();
        +    const side = Vector.cross(up, front).normalize();
             // down vector. Bottom when viewed from the front. (like y-axis)
        -    const down = p5.Vector.cross(front, side);
        +    const down = Vector.cross(front, side);
         
             // side vector and down vector are no longer used as-is.
             // Create a vector representing the direction of rotation
        @@ -3830,10 +2940,10 @@ p5.Camera = class Camera {
             down.mult(Math.sin(directionAngle));
             side.mult(Math.cos(directionAngle)).add(down);
             // The amount of rotation is the size of the vector (dx, dy).
        -    const rotAngle = Math.sqrt(dx*dx + dy*dy);
        +    const rotAngle = Math.sqrt(dx * dx + dy * dy);
             // The vector that is orthogonal to both the front vector and
             // the rotation direction vector is the rotation axis vector.
        -    const axis = p5.Vector.cross(front, side);
        +    const axis = Vector.cross(front, side);
         
             // update camRadius
             camRadius *= Math.pow(10, dRadius);
        @@ -3877,82 +2987,941 @@ p5.Camera = class Camera {
           }
         
           /**
        - * Returns true if camera is currently attached to renderer.
        - * @method _isActive
        - * @private
        - */
        +   * Returns true if camera is currently attached to renderer.
        +   * @private
        +   */
           _isActive() {
        -    return this === this._renderer._curCamera;
        +    return this === this._renderer.states.curCamera;
           }
         };
         
        -/**
        - * Sets the current (active) camera of a 3D sketch.
        - *
        - * `setCamera()` allows for switching between multiple cameras created with
        - * <a href="#/p5/createCamera">createCamera()</a>.
        - *
        - * Note: `setCamera()` can only be used in WebGL mode.
        - *
        - * @method setCamera
        - * @param  {p5.Camera} cam camera that should be made active.
        - * @for p5
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Double-click to toggle between cameras.
        - *
        - * let cam1;
        - * let cam2;
        - * let usingCam1 = true;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the first camera.
        - *   // Keep its default settings.
        - *   cam1 = createCamera();
        - *
        - *   // Create the second camera.
        - *   // Place it at the top-left.
        - *   // Point it at the origin.
        - *   cam2 = createCamera();
        - *   cam2.setPosition(400, -400, 800);
        - *   cam2.lookAt(0, 0, 0);
        - *
        - *   // Set the current camera to cam1.
        - *   setCamera(cam1);
        - *
        - *   describe('A white cube on a gray background. The camera toggles between frontal and aerial views when the user double-clicks.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Draw the box.
        - *   box();
        - * }
        - *
        - * // Toggle the current camera when the user double-clicks.
        - * function doubleClicked() {
        - *   if (usingCam1 === true) {
        - *     setCamera(cam2);
        - *     usingCam1 = false;
        - *   } else {
        - *     setCamera(cam1);
        - *     usingCam1 = true;
        - *   }
        - * }
        - * </code>
        - * </div>
        - */
        -p5.prototype.setCamera = function (cam) {
        -  this._renderer._curCamera = cam;
        +function camera(p5, fn){
        +  ////////////////////////////////////////////////////////////////////////////////
        +  // p5.Prototype Methods
        +  ////////////////////////////////////////////////////////////////////////////////
         
        -  // set the projection matrix (which is not normally updated each frame)
        -  this._renderer.uPMatrix.set(cam.projMatrix);
        -  this._renderer.uViewMatrix.set(cam.cameraMatrix);
        -};
        +  /**
        +   * Sets the position and orientation of the current camera in a 3D sketch.
        +   *
        +   * `camera()` allows objects to be viewed from different angles. It has nine
        +   * parameters that are all optional.
        +   *
        +   * The first three parameters, `x`, `y`, and `z`, are the coordinates of the
        +   * camera’s position. For example, calling `camera(0, 0, 0)` places the camera
        +   * at the origin `(0, 0, 0)`. By default, the camera is placed at
        +   * `(0, 0, 800)`.
        +   *
        +   * The next three parameters, `centerX`, `centerY`, and `centerZ` are the
        +   * coordinates of the point where the camera faces. For example, calling
        +   * `camera(0, 0, 0, 10, 20, 30)` places the camera at the origin `(0, 0, 0)`
        +   * and points it at `(10, 20, 30)`. By default, the camera points at the
        +   * origin `(0, 0, 0)`.
        +   *
        +   * The last three parameters, `upX`, `upY`, and `upZ` are the components of
        +   * the "up" vector. The "up" vector orients the camera’s y-axis. For example,
        +   * calling `camera(0, 0, 0, 10, 20, 30, 0, -1, 0)` places the camera at the
        +   * origin `(0, 0, 0)`, points it at `(10, 20, 30)`, and sets the "up" vector
        +   * to `(0, -1, 0)` which is like holding it upside-down. By default, the "up"
        +   * vector is `(0, 1, 0)`.
        +   *
        +   * Note: `camera()` can only be used in WebGL mode.
        +   *
        +   * @method camera
        +   * @for p5
        +   * @param  {Number} [x]        x-coordinate of the camera. Defaults to 0.
        +   * @param  {Number} [y]        y-coordinate of the camera. Defaults to 0.
        +   * @param  {Number} [z]        z-coordinate of the camera. Defaults to 800.
        +   * @param  {Number} [centerX]  x-coordinate of the point the camera faces. Defaults to 0.
        +   * @param  {Number} [centerY]  y-coordinate of the point the camera faces. Defaults to 0.
        +   * @param  {Number} [centerZ]  z-coordinate of the point the camera faces. Defaults to 0.
        +   * @param  {Number} [upX]      x-component of the camera’s "up" vector. Defaults to 0.
        +   * @param  {Number} [upY]      y-component of the camera’s "up" vector. Defaults to 1.
        +   * @param  {Number} [upZ]      z-component of the camera’s "up" vector. Defaults to 0.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Move the camera to the top-right.
        +   *   camera(200, -400, 800);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube apperas to sway left and right on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Calculate the camera's x-coordinate.
        +   *   let x = 400 * cos(frameCount * 0.01);
        +   *
        +   *   // Orbit the camera around the box.
        +   *   camera(x, -400, 800);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Adjust the range sliders to change the camera's position.
        +   *
        +   * let xSlider;
        +   * let ySlider;
        +   * let zSlider;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create slider objects to set the camera's coordinates.
        +   *   xSlider = createSlider(-400, 400, 400);
        +   *   xSlider.position(0, 100);
        +   *   xSlider.size(100);
        +   *   ySlider = createSlider(-400, 400, -200);
        +   *   ySlider.position(0, 120);
        +   *   ySlider.size(100);
        +   *   zSlider = createSlider(0, 1600, 800);
        +   *   zSlider.position(0, 140);
        +   *   zSlider.size(100);
        +   *
        +   *   describe(
        +   *     'A white cube drawn against a gray background. Three range sliders appear beneath the image. The camera position changes when the user moves the sliders.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Get the camera's coordinates from the sliders.
        +   *   let x = xSlider.value();
        +   *   let y = ySlider.value();
        +   *   let z = zSlider.value();
        +   *
        +   *   // Move the camera.
        +   *   camera(x, y, z);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.camera = function (...args) {
        +    this._assert3d('camera');
        +    // p5._validateParameters('camera', args);
        +    this._renderer.camera(...args);
        +    return this;
        +  };
        +
        +  /**
        +   * Sets a perspective projection for the current camera in a 3D sketch.
        +   *
        +   * In a perspective projection, shapes that are further from the camera appear
        +   * smaller than shapes that are near the camera. This technique, called
        +   * foreshortening, creates realistic 3D scenes. It’s applied by default in
        +   * WebGL mode.
        +   *
        +   * `perspective()` changes the camera’s perspective by changing its viewing
        +   * frustum. The frustum is the volume of space that’s visible to the camera.
        +   * Its shape is a pyramid with its top cut off. The camera is placed where
        +   * the top of the pyramid should be and views everything between the frustum’s
        +   * top (near) plane and its bottom (far) plane.
        +   *
        +   * The first parameter, `fovy`, is the camera’s vertical field of view. It’s
        +   * an angle that describes how tall or narrow a view the camera has. For
        +   * example, calling `perspective(0.5)` sets the camera’s vertical field of
        +   * view to 0.5 radians. By default, `fovy` is calculated based on the sketch’s
        +   * height and the camera’s default z-coordinate, which is 800. The formula for
        +   * the default `fovy` is `2 * atan(height / 2 / 800)`.
        +   *
        +   * The second parameter, `aspect`, is the camera’s aspect ratio. It’s a number
        +   * that describes the ratio of the top plane’s width to its height. For
        +   * example, calling `perspective(0.5, 1.5)` sets the camera’s field of view to
        +   * 0.5 radians and aspect ratio to 1.5, which would make shapes appear thinner
        +   * on a square canvas. By default, aspect is set to `width / height`.
        +   *
        +   * The third parameter, `near`, is the distance from the camera to the near
        +   * plane. For example, calling `perspective(0.5, 1.5, 100)` sets the camera’s
        +   * field of view to 0.5 radians, its aspect ratio to 1.5, and places the near
        +   * plane 100 pixels from the camera. Any shapes drawn less than 100 pixels
        +   * from the camera won’t be visible. By default, near is set to `0.1 * 800`,
        +   * which is 1/10th the default distance between the camera and the origin.
        +   *
        +   * The fourth parameter, `far`, is the distance from the camera to the far
        +   * plane. For example, calling `perspective(0.5, 1.5, 100, 10000)` sets the
        +   * camera’s field of view to 0.5 radians, its aspect ratio to 1.5, places the
        +   * near plane 100 pixels from the camera, and places the far plane 10,000
        +   * pixels from the camera. Any shapes drawn more than 10,000 pixels from the
        +   * camera won’t be visible. By default, far is set to `10 * 800`, which is 10
        +   * times the default distance between the camera and the origin.
        +   *
        +   * Note: `perspective()` can only be used in WebGL mode.
        +   *
        +   * @method  perspective
        +   * @for p5
        +   * @param  {Number} [fovy]   camera frustum vertical field of view. Defaults to
        +   *                           `2 * atan(height / 2 / 800)`.
        +   * @param  {Number} [aspect] camera frustum aspect ratio. Defaults to
        +   *                           `width / height`.
        +   * @param  {Number} [near]   distance from the camera to the near clipping plane.
        +   *                           Defaults to `0.1 * 800`.
        +   * @param  {Number} [far]    distance from the camera to the far clipping plane.
        +   *                           Defaults to `10 * 800`.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to squeeze the box.
        +   *
        +   * let isSqueezed = false;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white rectangular prism on a gray background. The box appears to become thinner when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Place the camera at the top-right.
        +   *   camera(400, -400, 800);
        +   *
        +   *   if (isSqueezed === true) {
        +   *     // Set fovy to 0.2.
        +   *     // Set aspect to 1.5.
        +   *     perspective(0.2, 1.5);
        +   *   }
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   *
        +   * // Change the camera's perspective when the user double-clicks.
        +   * function doubleClicked() {
        +   *   isSqueezed = true;
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white rectangular prism on a gray background. The prism moves away from the camera until it disappears.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Place the camera at the top-right.
        +   *   camera(400, -400, 800);
        +   *
        +   *   // Set fovy to 0.2.
        +   *   // Set aspect to 1.5.
        +   *   // Set near to 600.
        +   *   // Set far to 1200.
        +   *   perspective(0.2, 1.5, 600, 1200);
        +   *
        +   *   // Move the origin away from the camera.
        +   *   let x = -frameCount;
        +   *   let y = frameCount;
        +   *   let z = -2 * frameCount;
        +   *   translate(x, y, z);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.perspective = function (...args) {
        +    this._assert3d('perspective');
        +    // p5._validateParameters('perspective', args);
        +    this._renderer.perspective(...args);
        +    return this;
        +  };
        +
        +
        +  /**
        +   * Enables or disables perspective for lines in 3D sketches.
        +   *
        +   * In WebGL mode, lines can be drawn with a thinner stroke when they’re
        +   * further from the camera. Doing so gives them a more realistic appearance.
        +   *
        +   * By default, lines are drawn differently based on the type of perspective
        +   * being used:
        +   * - `perspective()` and `frustum()` simulate a realistic perspective. In
        +   * these modes, stroke weight is affected by the line’s distance from the
        +   * camera. Doing so results in a more natural appearance. `perspective()` is
        +   * the default mode for 3D sketches.
        +   * - `ortho()` doesn’t simulate a realistic perspective. In this mode, stroke
        +   * weights are consistent regardless of the line’s distance from the camera.
        +   * Doing so results in a more predictable and consistent appearance.
        +   *
        +   * `linePerspective()` can override the default line drawing mode.
        +   *
        +   * The parameter, `enable`, is optional. It’s a `Boolean` value that sets the
        +   * way lines are drawn. If `true` is passed, as in `linePerspective(true)`,
        +   * then lines will appear thinner when they are further from the camera. If
        +   * `false` is passed, as in `linePerspective(false)`, then lines will have
        +   * consistent stroke weights regardless of their distance from the camera. By
        +   * default, `linePerspective()` is enabled.
        +   *
        +   * Calling `linePerspective()` without passing an argument returns `true` if
        +   * it's enabled and `false` if not.
        +   *
        +   * Note: `linePerspective()` can only be used in WebGL mode.
        +   *
        +   * @method linePerspective
        +   * @for p5
        +   * @param {boolean} enable whether to enable line perspective.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click the canvas to toggle the line perspective.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'A white cube with black edges on a gray background. Its edges toggle between thick and thin when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 600);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -40);
        +   *     box(10);
        +   *   }
        +   * }
        +   *
        +   * // Toggle the line perspective when the user double-clicks.
        +   * function doubleClicked() {
        +   *   let isEnabled = linePerspective();
        +   *   linePerspective(!isEnabled);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Double-click the canvas to toggle the line perspective.
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe(
        +   *     'A row of cubes with black edges on a gray background. Their edges toggle between thick and thin when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Use an orthographic projection.
        +   *   ortho();
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 600);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -40);
        +   *     box(10);
        +   *   }
        +   * }
        +   *
        +   * // Toggle the line perspective when the user double-clicks.
        +   * function doubleClicked() {
        +   *   let isEnabled = linePerspective();
        +   *   linePerspective(!isEnabled);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  /**
        +   * @method linePerspective
        +   * @return {boolean} whether line perspective is enabled.
        +   */
        +  fn.linePerspective = function (enable) {
        +    // p5._validateParameters('linePerspective', arguments);
        +    if (!(this._renderer instanceof RendererGL)) {
        +      throw new Error('linePerspective() must be called in WebGL mode.');
        +    }
        +    this._renderer.linePerspective(enable);
        +  };
        +
        +
        +  /**
        +   * Sets an orthographic projection for the current camera in a 3D sketch.
        +   *
        +   * In an orthographic projection, shapes with the same size always appear the
        +   * same size, regardless of whether they are near or far from the camera.
        +   *
        +   * `ortho()` changes the camera’s perspective by changing its viewing frustum
        +   * from a truncated pyramid to a rectangular prism. The camera is placed in
        +   * front of the frustum and views everything between the frustum’s near plane
        +   * and its far plane. `ortho()` has six optional parameters to define the
        +   * frustum.
        +   *
        +   * The first four parameters, `left`, `right`, `bottom`, and `top`, set the
        +   * coordinates of the frustum’s sides, bottom, and top. For example, calling
        +   * `ortho(-100, 100, 200, -200)` creates a frustum that’s 200 pixels wide and
        +   * 400 pixels tall. By default, these coordinates are set based on the
        +   * sketch’s width and height, as in
        +   * `ortho(-width / 2, width / 2, -height / 2, height / 2)`.
        +   *
        +   * The last two parameters, `near` and `far`, set the distance of the
        +   * frustum’s near and far plane from the camera. For example, calling
        +   * `ortho(-100, 100, 200, 200, 50, 1000)` creates a frustum that’s 200 pixels
        +   * wide, 400 pixels tall, starts 50 pixels from the camera, and ends 1,000
        +   * pixels from the camera. By default, `near` and `far` are set to 0 and
        +   * `max(width, height) + 800`, respectively.
        +   *
        +   * Note: `ortho()` can only be used in WebGL mode.
        +   *
        +   * @method  ortho
        +   * @for p5
        +   * @param  {Number} [left]   x-coordinate of the frustum’s left plane. Defaults to `-width / 2`.
        +   * @param  {Number} [right]  x-coordinate of the frustum’s right plane. Defaults to `width / 2`.
        +   * @param  {Number} [bottom] y-coordinate of the frustum’s bottom plane. Defaults to `height / 2`.
        +   * @param  {Number} [top]    y-coordinate of the frustum’s top plane. Defaults to `-height / 2`.
        +   * @param  {Number} [near]   z-coordinate of the frustum’s near plane. Defaults to 0.
        +   * @param  {Number} [far]    z-coordinate of the frustum’s far plane. Defaults to `max(width, height) + 800`.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A row of tiny, white cubes on a gray background. All the cubes appear the same size.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Apply an orthographic projection.
        +   *   ortho();
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 600);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -40);
        +   *     box(10);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Apply an orthographic projection.
        +   *   // Center the frustum.
        +   *   // Set its width and height to 20.
        +   *   // Place its near plane 300 pixels from the camera.
        +   *   // Place its far plane 350 pixels from the camera.
        +   *   ortho(-10, 10, -10, 10, 300, 350);
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 600);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -40);
        +   *     box(10);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.ortho = function (...args) {
        +    this._assert3d('ortho');
        +    // p5._validateParameters('ortho', args);
        +    this._renderer.ortho(...args);
        +    return this;
        +  };
        +
        +  /**
        +   * Sets the frustum of the current camera in a 3D sketch.
        +   *
        +   * In a frustum projection, shapes that are further from the camera appear
        +   * smaller than shapes that are near the camera. This technique, called
        +   * foreshortening, creates realistic 3D scenes.
        +   *
        +   * `frustum()` changes the default camera’s perspective by changing its
        +   * viewing frustum. The frustum is the volume of space that’s visible to the
        +   * camera. The frustum’s shape is a pyramid with its top cut off. The camera
        +   * is placed where the top of the pyramid should be and points towards the
        +   * base of the pyramid. It views everything within the frustum.
        +   *
        +   * The first four parameters, `left`, `right`, `bottom`, and `top`, set the
        +   * coordinates of the frustum’s sides, bottom, and top. For example, calling
        +   * `frustum(-100, 100, 200, -200)` creates a frustum that’s 200 pixels wide
        +   * and 400 pixels tall. By default, these coordinates are set based on the
        +   * sketch’s width and height, as in
        +   * `ortho(-width / 20, width / 20, height / 20, -height / 20)`.
        +   *
        +   * The last two parameters, `near` and `far`, set the distance of the
        +   * frustum’s near and far plane from the camera. For example, calling
        +   * `ortho(-100, 100, 200, -200, 50, 1000)` creates a frustum that’s 200 pixels
        +   * wide, 400 pixels tall, starts 50 pixels from the camera, and ends 1,000
        +   * pixels from the camera. By default, near is set to `0.1 * 800`, which is
        +   * 1/10th the default distance between the camera and the origin. `far` is set
        +   * to `10 * 800`, which is 10 times the default distance between the camera
        +   * and the origin.
        +   *
        +   * Note: `frustum()` can only be used in WebGL mode.
        +   *
        +   * @method frustum
        +   * @for p5
        +   * @param  {Number} [left]   x-coordinate of the frustum’s left plane. Defaults to `-width / 20`.
        +   * @param  {Number} [right]  x-coordinate of the frustum’s right plane. Defaults to `width / 20`.
        +   * @param  {Number} [bottom] y-coordinate of the frustum’s bottom plane. Defaults to `height / 20`.
        +   * @param  {Number} [top]    y-coordinate of the frustum’s top plane. Defaults to `-height / 20`.
        +   * @param  {Number} [near]   z-coordinate of the frustum’s near plane. Defaults to `0.1 * 800`.
        +   * @param  {Number} [far]    z-coordinate of the frustum’s far plane. Defaults to `10 * 800`.
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   describe('A row of white cubes on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Apply the default frustum projection.
        +   *   frustum();
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 600);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -40);
        +   *     box(10);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *   describe('A white cube on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Adjust the frustum.
        +   *   // Center it.
        +   *   // Set its width and height to 20 pixels.
        +   *   // Place its near plane 300 pixels from the camera.
        +   *   // Place its far plane 350 pixels from the camera.
        +   *   frustum(-10, 10, -10, 10, 300, 350);
        +   *
        +   *   // Translate the origin toward the camera.
        +   *   translate(-10, 10, 600);
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(-0.1);
        +   *   rotateX(-0.1);
        +   *
        +   *   // Draw the row of boxes.
        +   *   for (let i = 0; i < 6; i += 1) {
        +   *     translate(0, 0, -40);
        +   *     box(10);
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.frustum = function (...args) {
        +    this._assert3d('frustum');
        +    // p5._validateParameters('frustum', args);
        +    this._renderer.frustum(...args);
        +    return this;
        +  };
        +
        +  /**
        +   * Creates a new <a href="#/p5.Camera">p5.Camera</a> object and sets it
        +   * as the current (active) camera.
        +   *
        +   * The new camera is initialized with a default position `(0, 0, 800)` and a
        +   * default perspective projection. Its properties can be controlled with
        +   * <a href="#/p5.Camera">p5.Camera</a> methods such as
        +   * `myCamera.lookAt(0, 0, 0)`.
        +   *
        +   * Note: Every 3D sketch starts with a default camera initialized.
        +   * This camera can be controlled with the functions
        +   * <a href="#/p5/camera">camera()</a>,
        +   * <a href="#/p5/perspective">perspective()</a>,
        +   * <a href="#/p5/ortho">ortho()</a>, and
        +   * <a href="#/p5/frustum">frustum()</a> if it's the only camera in the scene.
        +   *
        +   * Note: `createCamera()` can only be used in WebGL mode.
        +   *
        +   * @method createCamera
        +   * @return {p5.Camera} the new camera.
        +   * @for p5
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let usingCam1 = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   // Place it at the top-left.
        +   *   // Point it at the origin.
        +   *   cam2 = createCamera();
        +   *   cam2.setPosition(400, -400, 800);
        +   *   cam2.lookAt(0, 0, 0);
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe('A white cube on a gray background. The camera toggles between frontal and aerial views when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (usingCam1 === true) {
        +   *     setCamera(cam2);
        +   *     usingCam1 = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     usingCam1 = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.createCamera = function () {
        +    this._assert3d('createCamera');
        +
        +    return this._renderer.createCamera();
        +  };
        +
        +  /**
        +   * Sets the current (active) camera of a 3D sketch.
        +   *
        +   * `setCamera()` allows for switching between multiple cameras created with
        +   * <a href="#/p5/createCamera">createCamera()</a>.
        +   *
        +   * Note: `setCamera()` can only be used in WebGL mode.
        +   *
        +   * @method setCamera
        +   * @param  {p5.Camera} cam camera that should be made active.
        +   * @for p5
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let usingCam1 = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   // Place it at the top-left.
        +   *   // Point it at the origin.
        +   *   cam2 = createCamera();
        +   *   cam2.setPosition(400, -400, 800);
        +   *   cam2.lookAt(0, 0, 0);
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe('A white cube on a gray background. The camera toggles between frontal and aerial views when the user double-clicks.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (usingCam1 === true) {
        +   *     setCamera(cam2);
        +   *     usingCam1 = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     usingCam1 = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  fn.setCamera = function (cam) {
        +    this._renderer.setCamera(cam);
        +  };
        +
        +  /**
        +   * A class to describe a camera for viewing a 3D sketch.
        +   *
        +   * Each `p5.Camera` object represents a camera that views a section of 3D
        +   * space. It stores information about the camera’s position, orientation, and
        +   * projection.
        +   *
        +   * In WebGL mode, the default camera is a `p5.Camera` object that can be
        +   * controlled with the <a href="#/p5/camera">camera()</a>,
        +   * <a href="#/p5/perspective">perspective()</a>,
        +   * <a href="#/p5/ortho">ortho()</a>, and
        +   * <a href="#/p5/frustum">frustum()</a> functions. Additional cameras can be
        +   * created with <a href="#/p5/createCamera">createCamera()</a> and activated
        +   * with <a href="#/p5/setCamera">setCamera()</a>.
        +   *
        +   * Note: `p5.Camera`’s methods operate in two coordinate systems:
        +   * - The “world” coordinate system describes positions in terms of their
        +   * relationship to the origin along the x-, y-, and z-axes. For example,
        +   * calling `myCamera.setPosition()` places the camera in 3D space using
        +   * "world" coordinates.
        +   * - The "local" coordinate system describes positions from the camera's point
        +   * of view: left-right, up-down, and forward-backward. For example, calling
        +   * `myCamera.move()` moves the camera along its own axes.
        +   *
        +   * @class p5.Camera
        +   * @constructor
        +   * @param {rendererGL} rendererGL instance of WebGL renderer
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let cam;
        +   * let delta = 0.001;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Camera object.
        +   *   cam = createCamera();
        +   *
        +   *   // Place the camera at the top-center.
        +   *   cam.setPosition(0, -400, 800);
        +   *
        +   *   // Point the camera at the origin.
        +   *   cam.lookAt(0, 0, 0);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The cube goes in and out of view as the camera pans left and right.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Turn the camera left and right, called "panning".
        +   *   cam.pan(delta);
        +   *
        +   *   // Switch directions every 120 frames.
        +   *   if (frameCount % 120 === 0) {
        +   *     delta *= -1;
        +   *   }
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Double-click to toggle between cameras.
        +   *
        +   * let cam1;
        +   * let cam2;
        +   * let isDefaultCamera = true;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the first camera.
        +   *   // Keep its default settings.
        +   *   cam1 = createCamera();
        +   *
        +   *   // Create the second camera.
        +   *   // Place it at the top-left.
        +   *   // Point it at the origin.
        +   *   cam2 = createCamera();
        +   *   cam2.setPosition(400, -400, 800);
        +   *   cam2.lookAt(0, 0, 0);
        +   *
        +   *   // Set the current camera to cam1.
        +   *   setCamera(cam1);
        +   *
        +   *   describe(
        +   *     'A white cube on a gray background. The camera toggles between frontal and aerial views when the user double-clicks.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Draw the box.
        +   *   box();
        +   * }
        +   *
        +   * // Toggle the current camera when the user double-clicks.
        +   * function doubleClicked() {
        +   *   if (isDefaultCamera === true) {
        +   *     setCamera(cam2);
        +   *     isDefaultCamera = false;
        +   *   } else {
        +   *     setCamera(cam1);
        +   *     isDefaultCamera = true;
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  p5.Camera = Camera;
        +
        +  RendererGL.prototype.camera = function(...args) {
        +    this.states.curCamera.camera(...args);
        +  }
        +
        +  RendererGL.prototype.perspective = function(...args) {
        +    this.states.curCamera.perspective(...args);
        +  }
        +
        +  RendererGL.prototype.linePerspective = function(enable) {
        +    if (enable !== undefined) {
        +      // Set the line perspective if enable is provided
        +      this.states.curCamera.useLinePerspective = enable;
        +    } else {
        +      // If no argument is provided, return the current value
        +      return this.states.curCamera.useLinePerspective;
        +    }
        +  }
        +
        +  RendererGL.prototype.ortho = function(...args) {
        +    this.states.curCamera.ortho(...args);
        +  }
        +
        +  RendererGL.prototype.frustum = function(...args) {
        +    this.states.curCamera.frustum(...args);
        +  }
        +
        +  RendererGL.prototype.createCamera = function() {
        +    // compute default camera settings, then set a default camera
        +    const _cam = new Camera(this);
        +    _cam._computeCameraDefaultSettings();
        +    _cam._setDefaultCamera();
        +
        +    return _cam;
        +  }
        +
        +  RendererGL.prototype.setCamera = function(cam) {
        +    this.states.curCamera = cam;
        +
        +    // set the projection matrix (which is not normally updated each frame)
        +    this.states.uPMatrix.set(cam.projMatrix);
        +    this.states.uViewMatrix.set(cam.cameraMatrix);
        +  }
        +}
        +
        +export default camera;
        +export { Camera };
         
        -export default p5.Camera;
        +if(typeof p5 !== 'undefined'){
        +  camera(p5, p5.prototype);
        +}
        diff --git a/src/webgl/p5.DataArray.js b/src/webgl/p5.DataArray.js
        index 9ab0c2eaf2..00306105b1 100644
        --- a/src/webgl/p5.DataArray.js
        +++ b/src/webgl/p5.DataArray.js
        @@ -1,31 +1,4 @@
        -import p5 from '../core/main';
        -
        -/**
        - * An internal class to store data that will be sent to a p5.RenderBuffer.
        - * Those need to eventually go into a Float32Array, so this class provides a
        - * variable-length array container backed by a Float32Array so that it can be
        - * sent to the GPU without allocating a new array each frame.
        - *
        - * Like a C++ vector, its fixed-length Float32Array backing its contents will
        - * double in size when it goes over its capacity.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Initialize storage with a capacity of 4
        - * const storage = new DataArray(4);
        - * console.log(storage.data.length); // 4
        - * console.log(storage.length); // 0
        - * console.log(storage.dataArray()); // Empty Float32Array
        - *
        - * storage.push(1, 2, 3, 4, 5, 6);
        - * console.log(storage.data.length); // 8
        - * console.log(storage.length); // 6
        - * console.log(storage.dataArray()); // Float32Array{1, 2, 3, 4, 5, 6}
        - * </code>
        - * </div>
        - */
        -p5.DataArray = class DataArray {
        +class DataArray {
           constructor(initialLength = 128) {
             this.length = 0;
             this.data = new Float32Array(initialLength);
        @@ -107,4 +80,38 @@ p5.DataArray = class DataArray {
           }
         };
         
        -export default p5.DataArray;
        +function dataArray(p5, fn){
        +  /**
        +   * An internal class to store data that will be sent to a p5.RenderBuffer.
        +   * Those need to eventually go into a Float32Array, so this class provides a
        +   * variable-length array container backed by a Float32Array so that it can be
        +   * sent to the GPU without allocating a new array each frame.
        +   *
        +   * Like a C++ vector, its fixed-length Float32Array backing its contents will
        +   * double in size when it goes over its capacity.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Initialize storage with a capacity of 4
        +   * const storage = new DataArray(4);
        +   * console.log(storage.data.length); // 4
        +   * console.log(storage.length); // 0
        +   * console.log(storage.dataArray()); // Empty Float32Array
        +   *
        +   * storage.push(1, 2, 3, 4, 5, 6);
        +   * console.log(storage.data.length); // 8
        +   * console.log(storage.length); // 6
        +   * console.log(storage.dataArray()); // Float32Array{1, 2, 3, 4, 5, 6}
        +   * </code>
        +   * </div>
        +   */
        +  p5.DataArray = DataArray;
        +}
        +
        +export default dataArray;
        +export { DataArray }
        +
        +if(typeof p5 !== 'undefined'){
        +  dataArray(p5, p5.prototype);
        +}
        diff --git a/src/webgl/p5.Framebuffer.js b/src/webgl/p5.Framebuffer.js
        index ac32c872ed..7d46af86e8 100644
        --- a/src/webgl/p5.Framebuffer.js
        +++ b/src/webgl/p5.Framebuffer.js
        @@ -3,24 +3,19 @@
          * @requires constants
          */
         
        -import p5 from '../core/main';
         import * as constants from '../core/constants';
        +import { RGB, RGBA } from '../color/creating_reading';
         import { checkWebGLCapabilities } from './p5.Texture';
         import { readPixelsWebGL, readPixelWebGL } from './p5.RendererGL';
        +import { Camera } from './p5.Camera';
        +import { Texture } from './p5.Texture';
        +import { Image } from '../image/p5.Image';
         
        -class FramebufferCamera extends p5.Camera {
        -  /**
        -   * A <a href="#/p5.Camera">p5.Camera</a> attached to a
        -   * <a href="#/p5.Framebuffer">p5.Framebuffer</a>.
        -   *
        -   * @class p5.FramebufferCamera
        -   * @constructor
        -   * @param {p5.Framebuffer} framebuffer The framebuffer this camera is
        -   * attached to
        -   * @private
        -   */
        +const constrain = (n, low, high) => Math.max(Math.min(n, high), low);
        +
        +class FramebufferCamera extends Camera {
           constructor(framebuffer) {
        -    super(framebuffer.target._renderer);
        +    super(framebuffer.renderer);
             this.fbo = framebuffer;
         
             // WebGL textures are upside-down compared to textures that come from
        @@ -38,20 +33,7 @@ class FramebufferCamera extends p5.Camera {
           }
         }
         
        -p5.FramebufferCamera = FramebufferCamera;
        -
         class FramebufferTexture {
        -  /**
        -   * A <a href="#/p5.Texture">p5.Texture</a> corresponding to a property of a
        -   * <a href="#/p5.Framebuffer">p5.Framebuffer</a>.
        -   *
        -   * @class p5.FramebufferTexture
        -   * @param {p5.Framebuffer} framebuffer The framebuffer represented by this
        -   * texture
        -   * @param {String} property The property of the framebuffer represented by
        -   * this texture, either `color` or `depth`
        -   * @private
        -   */
           constructor(framebuffer, property) {
             this.framebuffer = framebuffer;
             this.property = property;
        @@ -70,109 +52,26 @@ class FramebufferTexture {
           }
         }
         
        -p5.FramebufferTexture = FramebufferTexture;
        -
         class Framebuffer {
        -  /**
        -   * A class to describe a high-performance drawing surface for textures.
        -   *
        -   * Each `p5.Framebuffer` object provides a dedicated drawing surface called
        -   * a *framebuffer*. They're similar to
        -   * <a href="#/p5.Graphics">p5.Graphics</a> objects but can run much faster.
        -   * Performance is improved because the framebuffer shares the same WebGL
        -   * context as the canvas used to create it.
        -   *
        -   * `p5.Framebuffer` objects have all the drawing features of the main
        -   * canvas. Drawing instructions meant for the framebuffer must be placed
        -   * between calls to
        -   * <a href="#/p5.Framebuffer/begin">myBuffer.begin()</a> and
        -   * <a href="#/p5.Framebuffer/end">myBuffer.end()</a>. The resulting image
        -   * can be applied as a texture by passing the `p5.Framebuffer` object to the
        -   * <a href="#/p5/texture">texture()</a> function, as in `texture(myBuffer)`.
        -   * It can also be displayed on the main canvas by passing it to the
        -   * <a href="#/p5/image">image()</a> function, as in `image(myBuffer, 0, 0)`.
        -   *
        -   * Note: <a href="#/p5/createFramebuffer">createFramebuffer()</a> is the
        -   * recommended way to create an instance of this class.
        -   *
        -   * @class p5.Framebuffer
        -   * @constructor
        -   * @param {p5.Graphics|p5} target sketch instance or
        -   *                                <a href="#/p5.Graphics">p5.Graphics</a>
        -   *                                object.
        -   * @param {Object} [settings] configuration options.
        -   */
        -  constructor(target, settings = {}) {
        -    this.target = target;
        -    this.target._renderer.framebuffers.add(this);
        +  constructor(renderer, settings = {}) {
        +    this.renderer = renderer;
        +    this.renderer.framebuffers.add(this);
         
             this._isClipApplied = false;
         
        -    /**
        -     * An array containing the color of each pixel in the framebuffer.
        -     *
        -     * <a href="#/p5.Framebuffer/loadPixels">myBuffer.loadPixels()</a> must be
        -     * called before accessing the `myBuffer.pixels` array.
        -     * <a href="#/p5.Framebuffer/updatePixels">myBuffer.updatePixels()</a>
        -     * must be called after any changes are made.
        -     *
        -     * Note: Updating pixels via this property is slower than drawing to the
        -     * framebuffer directly. Consider using a
        -     * <a href="#/p5.Shader">p5.Shader</a> object instead of looping over
        -     * `myBuffer.pixels`.
        -     *
        -     * @property {Number[]} pixels
        -     *
        -     * @example
        -     * <div>
        -     * <code>
        -     * function setup() {
        -     *   createCanvas(100, 100, WEBGL);
        -     *
        -     *   background(200);
        -     *
        -     *   // Create a p5.Framebuffer object.
        -     *   let myBuffer = createFramebuffer();
        -     *
        -     *   // Load the pixels array.
        -     *   myBuffer.loadPixels();
        -     *
        -     *   // Get the number of pixels in the
        -     *   // top half of the framebuffer.
        -     *   let numPixels = myBuffer.pixels.length / 2;
        -     *
        -     *   // Set the framebuffer's top half to pink.
        -     *   for (let i = 0; i < numPixels; i += 4) {
        -     *     myBuffer.pixels[i] = 255;
        -     *     myBuffer.pixels[i + 1] = 102;
        -     *     myBuffer.pixels[i + 2] = 204;
        -     *     myBuffer.pixels[i + 3] = 255;
        -     *   }
        -     *
        -     *   // Update the pixels array.
        -     *   myBuffer.updatePixels();
        -     *
        -     *   // Draw the p5.Framebuffer object to the canvas.
        -     *   image(myBuffer, -50, -50);
        -     *
        -     *   describe('A pink rectangle above a gray rectangle.');
        -     * }
        -     * </code>
        -     * </div>
        -     */
             this.pixels = [];
         
             this.format = settings.format || constants.UNSIGNED_BYTE;
             this.channels = settings.channels || (
        -      target._renderer._pInst._glAttributes.alpha
        -        ? constants.RGBA
        -        : constants.RGB
        +      this.renderer._pInst._glAttributes.alpha
        +        ? RGBA
        +        : RGB
             );
             this.useDepth = settings.depth === undefined ? true : settings.depth;
             this.depthFormat = settings.depthFormat || constants.FLOAT;
             this.textureFiltering = settings.textureFiltering || constants.LINEAR;
             if (settings.antialias === undefined) {
        -      this.antialiasSamples = target._renderer._pInst._glAttributes.antialias
        +      this.antialiasSamples = this.renderer._pInst._glAttributes.antialias
                 ? 2
                 : 0;
             } else if (typeof settings.antialias === 'number') {
        @@ -181,16 +80,16 @@ class Framebuffer {
               this.antialiasSamples = settings.antialias ? 2 : 0;
             }
             this.antialias = this.antialiasSamples > 0;
        -    if (this.antialias && target.webglVersion !== constants.WEBGL2) {
        +    if (this.antialias && this.renderer.webglVersion !== constants.WEBGL2) {
               console.warn('Antialiasing is unsupported in a WebGL 1 context');
               this.antialias = false;
             }
        -    this.density = settings.density || target.pixelDensity();
        -    const gl = target._renderer.GL;
        +    this.density = settings.density || this.renderer._pixelDensity;
        +    const gl = this.renderer.GL;
             this.gl = gl;
             if (settings.width && settings.height) {
               const dimensions =
        -        target._renderer._adjustDimensions(settings.width, settings.height);
        +        this.renderer._adjustDimensions(settings.width, settings.height);
               this.width = dimensions.adjustedWidth;
               this.height = dimensions.adjustedHeight;
               this._autoSized = false;
        @@ -202,8 +101,8 @@ class Framebuffer {
                     'of its canvas.'
                 );
               }
        -      this.width = target.width;
        -      this.height = target.height;
        +      this.width = this.renderer.width;
        +      this.height = this.renderer.height;
               this._autoSized = true;
             }
             this._checkIfFormatsAvailable();
        @@ -227,12 +126,12 @@ class Framebuffer {
         
             this._recreateTextures();
         
        -    const prevCam = this.target._renderer._curCamera;
        +    const prevCam = this.renderer.states.curCamera;
             this.defaultCamera = this.createCamera();
             this.filterCamera = this.createCamera();
        -    this.target._renderer._curCamera = prevCam;
        +    this.renderer.states.curCamera = prevCam;
         
        -    this.draw(() => this.target.clear());
        +    this.draw(() => this.renderer.clear());
           }
         
           /**
        @@ -243,7 +142,6 @@ class Framebuffer {
            * the framebuffer to 300×500 pixels, then sets `myBuffer.width` to 300
            * and `myBuffer.height` 500.
            *
        -   * @method resize
            * @param {Number} width width of the framebuffer.
            * @param {Number} height height of the framebuffer.
            *
        @@ -286,7 +184,7 @@ class Framebuffer {
           resize(width, height) {
             this._autoSized = false;
             const dimensions =
        -      this.target._renderer._adjustDimensions(width, height);
        +      this.renderer._adjustDimensions(width, height);
             width = dimensions.adjustedWidth;
             height = dimensions.adjustedHeight;
             this.width = width;
        @@ -311,7 +209,6 @@ class Framebuffer {
            * Calling `myBuffer.pixelDensity()` without an argument returns its current
            * pixel density.
            *
        -   * @method pixelDensity
            * @param {Number} [density] pixel density to set.
            * @returns {Number} current pixel density.
            *
        @@ -409,7 +306,6 @@ class Framebuffer {
            * Calling `myBuffer.autoSized()` without an argument returns `true` if
            * the framebuffer automatically resizes and `false` if not.
            *
        -   * @method autoSized
            * @param {Boolean} [autoSized] whether to automatically resize the framebuffer to match the canvas.
            * @returns {Boolean} current autosize setting.
            *
        @@ -481,7 +377,7 @@ class Framebuffer {
         
             if (
               this.useDepth &&
        -      this.target.webglVersion === constants.WEBGL &&
        +      this.renderer.webglVersion === constants.WEBGL &&
               !gl.getExtension('WEBGL_depth_texture')
             ) {
               console.warn(
        @@ -493,7 +389,7 @@ class Framebuffer {
         
             if (
               this.useDepth &&
        -      this.target.webglVersion === constants.WEBGL &&
        +      this.renderer.webglVersion === constants.WEBGL &&
               this.depthFormat === constants.FLOAT
             ) {
               console.warn(
        @@ -526,7 +422,7 @@ class Framebuffer {
               this.depthFormat = constants.FLOAT;
             }
         
        -    const support = checkWebGLCapabilities(this.target._renderer);
        +    const support = checkWebGLCapabilities(this.renderer);
             if (!support.float && this.format === constants.FLOAT) {
               console.warn(
                 'This environment does not support FLOAT textures. ' +
        @@ -554,14 +450,14 @@ class Framebuffer {
             }
         
             if (
        -      this.channels === constants.RGB &&
        +      this.channels === RGB &&
               [constants.FLOAT, constants.HALF_FLOAT].includes(this.format)
             ) {
               console.warn(
                 'FLOAT and HALF_FLOAT formats do not work cross-platform with only ' +
                   'RGB channels. Falling back to RGBA.'
               );
        -      this.channels = constants.RGBA;
        +      this.channels = RGBA;
             }
           }
         
        @@ -687,30 +583,30 @@ class Framebuffer {
             if (this.useDepth) {
               this.depth = new FramebufferTexture(this, 'depthTexture');
               const depthFilter = gl.NEAREST;
        -      this.depthP5Texture = new p5.Texture(
        -        this.target._renderer,
        +      this.depthP5Texture = new Texture(
        +        this.renderer,
                 this.depth,
                 {
                   minFilter: depthFilter,
                   magFilter: depthFilter
                 }
               );
        -      this.target._renderer.textures.set(this.depth, this.depthP5Texture);
        +      this.renderer.textures.set(this.depth, this.depthP5Texture);
             }
         
             this.color = new FramebufferTexture(this, 'colorTexture');
             const filter = this.textureFiltering === constants.LINEAR
               ? gl.LINEAR
               : gl.NEAREST;
        -    this.colorP5Texture = new p5.Texture(
        -      this.target._renderer,
        +    this.colorP5Texture = new Texture(
        +      this.renderer,
               this.color,
               {
                 minFilter: filter,
                 magFilter: filter
               }
             );
        -    this.target._renderer.textures.set(this.color, this.colorP5Texture);
        +    this.renderer.textures.set(this.color, this.colorP5Texture);
         
             gl.bindTexture(gl.TEXTURE_2D, prevBoundTexture);
             gl.bindFramebuffer(gl.FRAMEBUFFER, prevBoundFramebuffer);
        @@ -738,20 +634,20 @@ class Framebuffer {
             if (this.format === constants.FLOAT) {
               type = gl.FLOAT;
             } else if (this.format === constants.HALF_FLOAT) {
        -      type = this.target.webglVersion === constants.WEBGL2
        +      type = this.renderer.webglVersion === constants.WEBGL2
                 ? gl.HALF_FLOAT
                 : gl.getExtension('OES_texture_half_float').HALF_FLOAT_OES;
             } else {
               type = gl.UNSIGNED_BYTE;
             }
         
        -    if (this.channels === constants.RGBA) {
        +    if (this.channels === RGBA) {
               format = gl.RGBA;
             } else {
               format = gl.RGB;
             }
         
        -    if (this.target.webglVersion === constants.WEBGL2) {
        +    if (this.renderer.webglVersion === constants.WEBGL2) {
               // https://webgl2fundamentals.org/webgl/lessons/webgl-data-textures.html
               const table = {
                 [gl.FLOAT]: {
        @@ -798,7 +694,7 @@ class Framebuffer {
             if (this.useStencil) {
               if (this.depthFormat === constants.FLOAT) {
                 type = gl.FLOAT_32_UNSIGNED_INT_24_8_REV;
        -      } else if (this.target.webglVersion === constants.WEBGL2) {
        +      } else if (this.renderer.webglVersion === constants.WEBGL2) {
                 type = gl.UNSIGNED_INT_24_8;
               } else {
                 type = gl.getExtension('WEBGL_depth_texture').UNSIGNED_INT_24_8_WEBGL;
        @@ -820,12 +716,12 @@ class Framebuffer {
             if (this.useStencil) {
               if (this.depthFormat === constants.FLOAT) {
                 internalFormat = gl.DEPTH32F_STENCIL8;
        -      } else if (this.target.webglVersion === constants.WEBGL2) {
        +      } else if (this.renderer.webglVersion === constants.WEBGL2) {
                 internalFormat = gl.DEPTH24_STENCIL8;
               } else {
                 internalFormat = gl.DEPTH_STENCIL;
               }
        -    } else if (this.target.webglVersion === constants.WEBGL2) {
        +    } else if (this.renderer.webglVersion === constants.WEBGL2) {
               if (this.depthFormat === constants.FLOAT) {
                 internalFormat = gl.DEPTH_COMPONENT32F;
               } else {
        @@ -846,9 +742,9 @@ class Framebuffer {
            */
           _updateSize() {
             if (this._autoSized) {
        -      this.width = this.target.width;
        -      this.height = this.target.height;
        -      this.density = this.target.pixelDensity();
        +      this.width = this.renderer.width;
        +      this.height = this.renderer.height;
        +      this.density = this.renderer._pixelDensity;
             }
           }
         
        @@ -931,7 +827,6 @@ class Framebuffer {
            * myBuffer.end();
            * ```
            *
        -   * @method createCamera
            * @returns {p5.Camera} new camera.
            *
            * @example
        @@ -1010,7 +905,7 @@ class Framebuffer {
             const cam = new FramebufferCamera(this);
             cam._computeCameraDefaultSettings();
             cam._setDefaultCamera();
        -    this.target._renderer._curCamera = cam;
        +    this.renderer.states.curCamera = cam;
             return cam;
           }
         
        @@ -1025,7 +920,7 @@ class Framebuffer {
             const gl = this.gl;
             gl.deleteTexture(texture.rawTexture());
         
        -    this.target._renderer.textures.delete(texture);
        +    this.renderer.textures.delete(texture);
           }
         
           /**
        @@ -1048,8 +943,6 @@ class Framebuffer {
            * variable still refers to the framebuffer, then it won't be garbage
            * collected.
            *
        -   * @method remove
        -   *
            * @example
            * <div>
            * <code>
        @@ -1112,7 +1005,7 @@ class Framebuffer {
             if (this.colorRenderbuffer) {
               gl.deleteRenderbuffer(this.colorRenderbuffer);
             }
        -    this.target._renderer.framebuffers.delete(this);
        +    this.renderer.framebuffers.delete(this);
           }
         
           /**
        @@ -1125,8 +1018,6 @@ class Framebuffer {
            * framebuffer. Changes won't be visible until the framebuffer is displayed
            * as an image or texture.
            *
        -   * @method begin
        -   *
            * @example
            * <div>
            * <code>
        @@ -1165,22 +1056,27 @@ class Framebuffer {
            * </div>
            */
           begin() {
        -    this.prevFramebuffer = this.target._renderer.activeFramebuffer();
        +    this.prevFramebuffer = this.renderer.activeFramebuffer();
             if (this.prevFramebuffer) {
               this.prevFramebuffer._beforeEnd();
             }
        -    this.target._renderer.activeFramebuffers.push(this);
        +    this.renderer.activeFramebuffers.push(this);
             this._beforeBegin();
        -    this.target.push();
        +    this.renderer.push();
             // Apply the framebuffer's camera. This does almost what
             // RendererGL.reset() does, but this does not try to clear any buffers;
             // it only sets the camera.
        -    this.target.setCamera(this.defaultCamera);
        -    this.target.resetMatrix();
        -    this.target._renderer.uViewMatrix
        -      .set(this.target._renderer._curCamera.cameraMatrix);
        -    this.target._renderer.uModelMatrix.reset();
        -    this.target._renderer._applyStencilTestIfClipping();
        +    // this.renderer.setCamera(this.defaultCamera);
        +    this.renderer.states.curCamera = this.defaultCamera;
        +    // set the projection matrix (which is not normally updated each frame)
        +    this.renderer.states.uPMatrix.set(this.defaultCamera.projMatrix);
        +    this.renderer.states.uViewMatrix.set(this.defaultCamera.cameraMatrix);
        +
        +    this.renderer.resetMatrix();
        +    this.renderer.states.uViewMatrix
        +      .set(this.renderer.states.curCamera.cameraMatrix);
        +    this.renderer.states.uModelMatrix.reset();
        +    this.renderer._applyStencilTestIfClipping();
           }
         
           /**
        @@ -1190,7 +1086,6 @@ class Framebuffer {
            * renderbuffer, while other framebuffers can write directly to their main
            * framebuffers.
            *
        -   * @method _framebufferToBind
            * @private
            */
           _framebufferToBind() {
        @@ -1207,13 +1102,12 @@ class Framebuffer {
           /**
            * Ensures that the framebuffer is ready to be drawn to
            *
        -   * @method _beforeBegin
            * @private
            */
           _beforeBegin() {
             const gl = this.gl;
             gl.bindFramebuffer(gl.FRAMEBUFFER, this._framebufferToBind());
        -    this.target._renderer.viewport(
        +    this.renderer.viewport(
               this.width * this.density,
               this.height * this.density
             );
        @@ -1222,7 +1116,6 @@ class Framebuffer {
           /**
            * Ensures that the framebuffer is ready to be read by other framebuffers.
            *
        -   * @method _beforeEnd
            * @private
            */
           _beforeEnd() {
        @@ -1261,8 +1154,6 @@ class Framebuffer {
            * Changes won't be visible until the framebuffer is displayed as an image
            * or texture.
            *
        -   * @method end
        -   *
            * @example
            * <div>
            * <code>
        @@ -1302,8 +1193,8 @@ class Framebuffer {
            */
           end() {
             const gl = this.gl;
        -    this.target.pop();
        -    const fbo = this.target._renderer.activeFramebuffers.pop();
        +    this.renderer.pop();
        +    const fbo = this.renderer.activeFramebuffers.pop();
             if (fbo !== this) {
               throw new Error("It looks like you've called end() while another Framebuffer is active.");
             }
        @@ -1312,12 +1203,12 @@ class Framebuffer {
               this.prevFramebuffer._beforeBegin();
             } else {
               gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        -      this.target._renderer.viewport(
        -        this.target._renderer._origViewport.width,
        -        this.target._renderer._origViewport.height
        +      this.renderer.viewport(
        +        this.renderer._origViewport.width,
        +        this.renderer._origViewport.height
               );
             }
        -    this.target._renderer._applyStencilTestIfClipping();
        +    this.renderer._applyStencilTestIfClipping();
           }
         
           /**
        @@ -1335,7 +1226,6 @@ class Framebuffer {
            * myBuffer.end();
            * ```
            *
        -   * @method draw
            * @param {Function} callback function that draws to the framebuffer.
            *
            * @example
        @@ -1431,7 +1321,7 @@ class Framebuffer {
            */
           loadPixels() {
             const gl = this.gl;
        -    const prevFramebuffer = this.target._renderer.activeFramebuffer();
        +    const prevFramebuffer = this.renderer.activeFramebuffer();
             gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
             const colorFormat = this._glColorFormat();
             this.pixels = readPixelsWebGL(
        @@ -1473,7 +1363,6 @@ class Framebuffer {
            * the coordinates for the upper-left corner of the subsection. The last two
            * parameters are the width and height of the subsection.
            *
        -   * @method get
            * @param  {Number} x x-coordinate of the pixel. Defaults to 0.
            * @param  {Number} y y-coordinate of the pixel. Defaults to 0.
            * @param  {Number} w width of the subsection to be returned.
        @@ -1481,17 +1370,15 @@ class Framebuffer {
            * @return {p5.Image} subsection as a <a href="#/p5.Image">p5.Image</a> object.
            */
           /**
        -   * @method get
            * @return {p5.Image} entire framebuffer as a <a href="#/p5.Image">p5.Image</a> object.
            */
           /**
        -   * @method get
            * @param  {Number} x
            * @param  {Number} y
            * @return {Number[]}  color of the pixel at `(x, y)` as an array of color values `[R, G, B, A]`.
            */
           get(x, y, w, h) {
        -    p5._validateParameters('p5.Framebuffer.get', arguments);
        +    // p5._validateParameters('p5.Framebuffer.get', arguments);
             const colorFormat = this._glColorFormat();
             if (x === undefined && y === undefined) {
               x = 0;
        @@ -1503,8 +1390,8 @@ class Framebuffer {
                 console.warn(
                   'The x and y values passed to p5.Framebuffer.get are outside of its range and will be clamped.'
                 );
        -        x = this.target.constrain(x, 0, this.width - 1);
        -        y = this.target.constrain(y, 0, this.height - 1);
        +        x = constrain(x, 0, this.width - 1);
        +        y = constrain(y, 0, this.height - 1);
               }
         
               return readPixelWebGL(
        @@ -1517,10 +1404,10 @@ class Framebuffer {
               );
             }
         
        -    x = this.target.constrain(x, 0, this.width - 1);
        -    y = this.target.constrain(y, 0, this.height - 1);
        -    w = this.target.constrain(w, 1, this.width - x);
        -    h = this.target.constrain(h, 1, this.height - y);
        +    x = constrain(x, 0, this.width - 1);
        +    y = constrain(y, 0, this.height - 1);
        +    w = constrain(w, 1, this.width - x);
        +    h = constrain(h, 1, this.height - y);
         
             const rawData = readPixelsWebGL(
               undefined,
        @@ -1563,7 +1450,7 @@ class Framebuffer {
             }
         
             // Create an image from the data
        -    const region = new p5.Image(w * this.density, h * this.density);
        +    const region = new Image(w * this.density, h * this.density);
             region.imageData = region.canvas.getContext('2d').createImageData(
               region.width,
               region.height
        @@ -1658,7 +1545,7 @@ class Framebuffer {
             );
             this.colorP5Texture.unbindTexture();
         
        -    const prevFramebuffer = this.target._renderer.activeFramebuffer();
        +    const prevFramebuffer = this.renderer.activeFramebuffer();
             if (this.antialias) {
               // We need to make sure the antialiased framebuffer also has the updated
               // pixels so that if more is drawn to it, it goes on top of the updated
        @@ -1668,13 +1555,23 @@ class Framebuffer {
               // to use image() to put the framebuffer texture onto the antialiased
               // framebuffer.
               this.begin();
        -      this.target.push();
        -      this.target.imageMode(this.target.CENTER);
        -      this.target.resetMatrix();
        -      this.target.noStroke();
        -      this.target.clear();
        -      this.target.image(this, 0, 0);
        -      this.target.pop();
        +      this.renderer.push();
        +      // this.renderer.imageMode(constants.CENTER);
        +      this.renderer.states.imageMode = constants.CORNER;
        +      this.renderer.setCamera(this.filterCamera);
        +      this.renderer.resetMatrix();
        +      this.renderer.states.strokeColor = null;
        +      this.renderer.clear();
        +      this.renderer._drawingFilter = true;
        +      this.renderer.image(
        +        this,
        +        0, 0,
        +        this.width, this.height,
        +        -this.renderer.width / 2, -this.renderer.height / 2,
        +        this.renderer.width, this.renderer.height
        +      );
        +      this.renderer._drawingFilter = false;
        +      this.renderer.pop();
               if (this.useDepth) {
                 gl.clearDepth(1);
                 gl.clear(gl.DEPTH_BUFFER_BIT);
        @@ -1698,159 +1595,272 @@ class Framebuffer {
           }
         }
         
        -/**
        - * An object that stores the framebuffer's color data.
        - *
        - * Each framebuffer uses a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture" target="_blank">WebGLTexture</a>
        - * object internally to store its color data. The `myBuffer.color` property
        - * makes it possible to pass this data directly to other functions. For
        - * example, calling `texture(myBuffer.color)` or
        - * `myShader.setUniform('colorTexture', myBuffer.color)`  may be helpful for
        - * advanced use cases.
        - *
        - * Note: By default, a framebuffer's y-coordinates are flipped compared to
        - * images and videos. It's easy to flip a framebuffer's y-coordinates as
        - * needed when applying it as a texture. For example, calling
        - * `plane(myBuffer.width, -myBuffer.height)` will flip the framebuffer.
        - *
        - * @property {p5.FramebufferTexture} color
        - * @for p5.Framebuffer
        - *
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   // Create a p5.Framebuffer object.
        - *   let myBuffer = createFramebuffer();
        - *
        - *   // Start drawing to the p5.Framebuffer object.
        - *   myBuffer.begin();
        - *
        - *   triangle(-25, 25, 0, -25, 25, 25);
        - *
        - *   // Stop drawing to the p5.Framebuffer object.
        - *   myBuffer.end();
        - *
        - *   // Use the p5.Framebuffer object's WebGLTexture.
        - *   texture(myBuffer.color);
        - *
        - *   // Style the plane.
        - *   noStroke();
        - *
        - *   // Draw the plane.
        - *   plane(myBuffer.width, myBuffer.height);
        - *
        - *   describe('A white triangle on a gray background.');
        - * }
        - * </code>
        - * </div>
        - */
        +function framebuffer(p5, fn){
        +  /**
        +   * A <a href="#/p5.Camera">p5.Camera</a> attached to a
        +   * <a href="#/p5.Framebuffer">p5.Framebuffer</a>.
        +   *
        +   * @class p5.FramebufferCamera
        +   * @param {p5.Framebuffer} framebuffer The framebuffer this camera is
        +   * attached to
        +   * @private
        +   */
        +  p5.FramebufferCamera = FramebufferCamera;
         
        -/**
        - * An object that stores the framebuffer's depth data.
        - *
        - * Each framebuffer uses a
        - * <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture" target="_blank">WebGLTexture</a>
        - * object internally to store its depth data. The `myBuffer.depth` property
        - * makes it possible to pass this data directly to other functions. For
        - * example, calling `texture(myBuffer.depth)` or
        - * `myShader.setUniform('depthTexture', myBuffer.depth)`  may be helpful for
        - * advanced use cases.
        - *
        - * Note: By default, a framebuffer's y-coordinates are flipped compared to
        - * images and videos. It's easy to flip a framebuffer's y-coordinates as
        - * needed when applying it as a texture. For example, calling
        - * `plane(myBuffer.width, -myBuffer.height)` will flip the framebuffer.
        - *
        - * @property {p5.FramebufferTexture} depth
        - * @for p5.Framebuffer
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * // Create a string with the vertex shader program.
        - * // The vertex shader is called for each vertex.
        - * let vertSrc = `
        - * precision highp float;
        - * attribute vec3 aPosition;
        - * attribute vec2 aTexCoord;
        - * uniform mat4 uModelViewMatrix;
        - * uniform mat4 uProjectionMatrix;
        - * varying vec2 vTexCoord;
        - *
        - * void main() {
        - *   vec4 viewModelPosition = uModelViewMatrix * vec4(aPosition, 1.0);
        - *   gl_Position = uProjectionMatrix * viewModelPosition;
        - *   vTexCoord = aTexCoord;
        - * }
        - * `;
        - *
        - * // Create a string with the fragment shader program.
        - * // The fragment shader is called for each pixel.
        - * let fragSrc = `
        - * precision highp float;
        - * varying vec2 vTexCoord;
        - * uniform sampler2D depth;
        - *
        - * void main() {
        - *   // Get the pixel's depth value.
        - *   float depthVal = texture2D(depth, vTexCoord).r;
        - *
        - *   // Set the pixel's color based on its depth.
        - *   gl_FragColor = mix(
        - *     vec4(0., 0., 0., 1.),
        - *     vec4(1., 0., 1., 1.),
        - *     depthVal);
        - * }
        - * `;
        - *
        - * let myBuffer;
        - * let myShader;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Framebuffer object.
        - *   myBuffer = createFramebuffer();
        - *
        - *   // Create a p5.Shader object.
        - *   myShader = createShader(vertSrc, fragSrc);
        - *
        - *   // Compile and apply the shader.
        - *   shader(myShader);
        - *
        - *   describe('The shadow of a box rotates slowly against a magenta background.');
        - * }
        - *
        - * function draw() {
        - *   // Draw to the p5.Framebuffer object.
        - *   myBuffer.begin();
        - *   background(255);
        - *   rotateX(frameCount * 0.01);
        - *   box(20, 20, 80);
        - *   myBuffer.end();
        - *
        - *   // Set the shader's depth uniform using
        - *   // the framebuffer's depth texture.
        - *   myShader.setUniform('depth', myBuffer.depth);
        - *
        - *   // Style the plane.
        - *   noStroke();
        - *
        - *   // Draw the plane.
        - *   plane(myBuffer.width, myBuffer.height);
        - * }
        - * </code>
        - * </div>
        - */
        +  /**
        +   * A <a href="#/p5.Texture">p5.Texture</a> corresponding to a property of a
        +   * <a href="#/p5.Framebuffer">p5.Framebuffer</a>.
        +   *
        +   * @class p5.FramebufferTexture
        +   * @param {p5.Framebuffer} framebuffer The framebuffer represented by this
        +   * texture
        +   * @param {String} property The property of the framebuffer represented by
        +   * this texture, either `color` or `depth`
        +   * @private
        +   */
        +  p5.FramebufferTexture = FramebufferTexture;
        +
        +  /**
        +   * A class to describe a high-performance drawing surface for textures.
        +   *
        +   * Each `p5.Framebuffer` object provides a dedicated drawing surface called
        +   * a *framebuffer*. They're similar to
        +   * <a href="#/p5.Graphics">p5.Graphics</a> objects but can run much faster.
        +   * Performance is improved because the framebuffer shares the same WebGL
        +   * context as the canvas used to create it.
        +   *
        +   * `p5.Framebuffer` objects have all the drawing features of the main
        +   * canvas. Drawing instructions meant for the framebuffer must be placed
        +   * between calls to
        +   * <a href="#/p5.Framebuffer/begin">myBuffer.begin()</a> and
        +   * <a href="#/p5.Framebuffer/end">myBuffer.end()</a>. The resulting image
        +   * can be applied as a texture by passing the `p5.Framebuffer` object to the
        +   * <a href="#/p5/texture">texture()</a> function, as in `texture(myBuffer)`.
        +   * It can also be displayed on the main canvas by passing it to the
        +   * <a href="#/p5/image">image()</a> function, as in `image(myBuffer, 0, 0)`.
        +   *
        +   * Note: <a href="#/p5/createFramebuffer">createFramebuffer()</a> is the
        +   * recommended way to create an instance of this class.
        +   *
        +   * @class p5.Framebuffer
        +   * @param {p5.Graphics|p5} target sketch instance or
        +   *                                <a href="#/p5.Graphics">p5.Graphics</a>
        +   *                                object.
        +   * @param {Object} [settings] configuration options.
        +   */
        +  p5.Framebuffer = Framebuffer;
        +
        +  /**
        +   * An object that stores the framebuffer's color data.
        +   *
        +   * Each framebuffer uses a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture" target="_blank">WebGLTexture</a>
        +   * object internally to store its color data. The `myBuffer.color` property
        +   * makes it possible to pass this data directly to other functions. For
        +   * example, calling `texture(myBuffer.color)` or
        +   * `myShader.setUniform('colorTexture', myBuffer.color)`  may be helpful for
        +   * advanced use cases.
        +   *
        +   * Note: By default, a framebuffer's y-coordinates are flipped compared to
        +   * images and videos. It's easy to flip a framebuffer's y-coordinates as
        +   * needed when applying it as a texture. For example, calling
        +   * `plane(myBuffer.width, -myBuffer.height)` will flip the framebuffer.
        +   *
        +   * @property {p5.FramebufferTexture} color
        +   * @for p5.Framebuffer
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Framebuffer object.
        +   *   let myBuffer = createFramebuffer();
        +   *
        +   *   // Start drawing to the p5.Framebuffer object.
        +   *   myBuffer.begin();
        +   *
        +   *   triangle(-25, 25, 0, -25, 25, 25);
        +   *
        +   *   // Stop drawing to the p5.Framebuffer object.
        +   *   myBuffer.end();
        +   *
        +   *   // Use the p5.Framebuffer object's WebGLTexture.
        +   *   texture(myBuffer.color);
        +   *
        +   *   // Style the plane.
        +   *   noStroke();
        +   *
        +   *   // Draw the plane.
        +   *   plane(myBuffer.width, myBuffer.height);
        +   *
        +   *   describe('A white triangle on a gray background.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * An object that stores the framebuffer's depth data.
        +   *
        +   * Each framebuffer uses a
        +   * <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture" target="_blank">WebGLTexture</a>
        +   * object internally to store its depth data. The `myBuffer.depth` property
        +   * makes it possible to pass this data directly to other functions. For
        +   * example, calling `texture(myBuffer.depth)` or
        +   * `myShader.setUniform('depthTexture', myBuffer.depth)`  may be helpful for
        +   * advanced use cases.
        +   *
        +   * Note: By default, a framebuffer's y-coordinates are flipped compared to
        +   * images and videos. It's easy to flip a framebuffer's y-coordinates as
        +   * needed when applying it as a texture. For example, calling
        +   * `plane(myBuffer.width, -myBuffer.height)` will flip the framebuffer.
        +   *
        +   * @property {p5.FramebufferTexture} depth
        +   * @for p5.Framebuffer
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Note: A "uniform" is a global variable within a shader program.
        +   *
        +   * // Create a string with the vertex shader program.
        +   * // The vertex shader is called for each vertex.
        +   * let vertSrc = `
        +   * precision highp float;
        +   * attribute vec3 aPosition;
        +   * attribute vec2 aTexCoord;
        +   * uniform mat4 uModelViewMatrix;
        +   * uniform mat4 uProjectionMatrix;
        +   * varying vec2 vTexCoord;
        +   *
        +   * void main() {
        +   *   vec4 viewModelPosition = uModelViewMatrix * vec4(aPosition, 1.0);
        +   *   gl_Position = uProjectionMatrix * viewModelPosition;
        +   *   vTexCoord = aTexCoord;
        +   * }
        +   * `;
        +   *
        +   * // Create a string with the fragment shader program.
        +   * // The fragment shader is called for each pixel.
        +   * let fragSrc = `
        +   * precision highp float;
        +   * varying vec2 vTexCoord;
        +   * uniform sampler2D depth;
        +   *
        +   * void main() {
        +   *   // Get the pixel's depth value.
        +   *   float depthVal = texture2D(depth, vTexCoord).r;
        +   *
        +   *   // Set the pixel's color based on its depth.
        +   *   gl_FragColor = mix(
        +   *     vec4(0., 0., 0., 1.),
        +   *     vec4(1., 0., 1., 1.),
        +   *     depthVal);
        +   * }
        +   * `;
        +   *
        +   * let myBuffer;
        +   * let myShader;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Framebuffer object.
        +   *   myBuffer = createFramebuffer();
        +   *
        +   *   // Create a p5.Shader object.
        +   *   myShader = createShader(vertSrc, fragSrc);
        +   *
        +   *   // Compile and apply the shader.
        +   *   shader(myShader);
        +   *
        +   *   describe('The shadow of a box rotates slowly against a magenta background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Draw to the p5.Framebuffer object.
        +   *   myBuffer.begin();
        +   *   background(255);
        +   *   rotateX(frameCount * 0.01);
        +   *   box(20, 20, 80);
        +   *   myBuffer.end();
        +   *
        +   *   // Set the shader's depth uniform using
        +   *   // the framebuffer's depth texture.
        +   *   myShader.setUniform('depth', myBuffer.depth);
        +   *
        +   *   // Style the plane.
        +   *   noStroke();
        +   *
        +   *   // Draw the plane.
        +   *   plane(myBuffer.width, myBuffer.height);
        +   * }
        +   * </code>
        +   * </div>
        +   */
         
        -p5.Framebuffer = Framebuffer;
        +  /**
        +   * An array containing the color of each pixel in the framebuffer.
        +   *
        +   * <a href="#/p5.Framebuffer/loadPixels">myBuffer.loadPixels()</a> must be
        +   * called before accessing the `myBuffer.pixels` array.
        +   * <a href="#/p5.Framebuffer/updatePixels">myBuffer.updatePixels()</a>
        +   * must be called after any changes are made.
        +   *
        +   * Note: Updating pixels via this property is slower than drawing to the
        +   * framebuffer directly. Consider using a
        +   * <a href="#/p5.Shader">p5.Shader</a> object instead of looping over
        +   * `myBuffer.pixels`.
        +   *
        +   * @property {Number[]} pixels
        +   * @for p5.Framebuffer
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create a p5.Framebuffer object.
        +   *   let myBuffer = createFramebuffer();
        +   *
        +   *   // Load the pixels array.
        +   *   myBuffer.loadPixels();
        +   *
        +   *   // Get the number of pixels in the
        +   *   // top half of the framebuffer.
        +   *   let numPixels = myBuffer.pixels.length / 2;
        +   *
        +   *   // Set the framebuffer's top half to pink.
        +   *   for (let i = 0; i < numPixels; i += 4) {
        +   *     myBuffer.pixels[i] = 255;
        +   *     myBuffer.pixels[i + 1] = 102;
        +   *     myBuffer.pixels[i + 2] = 204;
        +   *     myBuffer.pixels[i + 3] = 255;
        +   *   }
        +   *
        +   *   // Update the pixels array.
        +   *   myBuffer.updatePixels();
        +   *
        +   *   // Draw the p5.Framebuffer object to the canvas.
        +   *   image(myBuffer, -50, -50);
        +   *
        +   *   describe('A pink rectangle above a gray rectangle.');
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +}
        +
        +export default framebuffer;
        +export { FramebufferTexture, FramebufferCamera, Framebuffer };
         
        -export default Framebuffer;
        +if(typeof p5 !== 'undefined'){
        +  framebuffer(p5, p5.prototype);
        +}
        diff --git a/src/webgl/p5.Geometry.js b/src/webgl/p5.Geometry.js
        index 739155301c..7b4ca9196c 100644
        --- a/src/webgl/p5.Geometry.js
        +++ b/src/webgl/p5.Geometry.js
        @@ -8,653 +8,39 @@
         
         //some of the functions are adjusted from Three.js(http://threejs.org)
         
        -import p5 from '../core/main';
         import * as constants from '../core/constants';
        -/**
        - * A class to describe a 3D shape.
        - *
        - * Each `p5.Geometry` object represents a 3D shape as a set of connected
        - * points called *vertices*. All 3D shapes are made by connecting vertices to
        - * form triangles that are stitched together. Each triangular patch on the
        - * geometry's surface is called a *face*. The geometry stores information
        - * about its vertices and faces for use with effects such as lighting and
        - * texture mapping.
        - *
        - * The first parameter, `detailX`, is optional. If a number is passed, as in
        - * `new p5.Geometry(24)`, it sets the number of triangle subdivisions to use
        - * along the geometry's x-axis. By default, `detailX` is 1.
        - *
        - * The second parameter, `detailY`, is also optional. If a number is passed,
        - * as in `new p5.Geometry(24, 16)`, it sets the number of triangle
        - * subdivisions to use along the geometry's y-axis. By default, `detailX` is
        - * 1.
        - *
        - * The third parameter, `callback`, is also optional. If a function is passed,
        - * as in `new p5.Geometry(24, 16, createShape)`, it will be called once to add
        - * vertices to the new 3D shape.
        - *
        - * @class p5.Geometry
        - * @constructor
        - * @param  {Integer} [detailX] number of vertices along the x-axis.
        - * @param  {Integer} [detailY] number of vertices along the y-axis.
        - * @param {function} [callback] function to call once the geometry is created.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let myGeometry;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Geometry object.
        - *   myGeometry = new p5.Geometry();
        - *
        - *   // Create p5.Vector objects to position the vertices.
        - *   let v0 = createVector(-40, 0, 0);
        - *   let v1 = createVector(0, -40, 0);
        - *   let v2 = createVector(40, 0, 0);
        - *
        - *   // Add the vertices to the p5.Geometry object's vertices array.
        - *   myGeometry.vertices.push(v0, v1, v2);
        - *
        - *   describe('A white triangle drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(myGeometry);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let myGeometry;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Geometry object using a callback function.
        - *   myGeometry = new p5.Geometry(1, 1, createShape);
        - *
        - *   describe('A white triangle drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(myGeometry);
        - * }
        - *
        - * function createShape() {
        - *   // Create p5.Vector objects to position the vertices.
        - *   let v0 = createVector(-40, 0, 0);
        - *   let v1 = createVector(0, -40, 0);
        - *   let v2 = createVector(40, 0, 0);
        - *
        - *   // "this" refers to the p5.Geometry object being created.
        - *
        - *   // Add the vertices to the p5.Geometry object's vertices array.
        - *   this.vertices.push(v0, v1, v2);
        - *
        - *   // Add an array to list which vertices belong to the face.
        - *   // Vertices are listed in clockwise "winding" order from
        - *   // left to top to right.
        - *   this.faces.push([0, 1, 2]);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let myGeometry;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Geometry object using a callback function.
        - *   myGeometry = new p5.Geometry(1, 1, createShape);
        - *
        - *   describe('A white triangle drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(myGeometry);
        - * }
        - *
        - * function createShape() {
        - *   // Create p5.Vector objects to position the vertices.
        - *   let v0 = createVector(-40, 0, 0);
        - *   let v1 = createVector(0, -40, 0);
        - *   let v2 = createVector(40, 0, 0);
        - *
        - *   // "this" refers to the p5.Geometry object being created.
        - *
        - *   // Add the vertices to the p5.Geometry object's vertices array.
        - *   this.vertices.push(v0, v1, v2);
        - *
        - *   // Add an array to list which vertices belong to the face.
        - *   // Vertices are listed in clockwise "winding" order from
        - *   // left to top to right.
        - *   this.faces.push([0, 1, 2]);
        - *
        - *   // Compute the surface normals to help with lighting.
        - *   this.computeNormals();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * // Adapted from Paul Wheeler's wonderful p5.Geometry tutorial.
        - * // https://www.paulwheeler.us/articles/custom-3d-geometry-in-p5js/
        - * // CC-BY-SA 4.0
        - *
        - * let myGeometry;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create the p5.Geometry object.
        - *   // Set detailX to 48 and detailY to 2.
        - *   // >>> try changing them.
        - *   myGeometry = new p5.Geometry(48, 2, createShape);
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the p5.Geometry object.
        - *   strokeWeight(0.2);
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(myGeometry);
        - * }
        - *
        - * function createShape() {
        - *   // "this" refers to the p5.Geometry object being created.
        - *
        - *   // Define the Möbius strip with a few parameters.
        - *   let spread = 0.1;
        - *   let radius = 30;
        - *   let stripWidth = 15;
        - *   let xInterval = 4 * PI / this.detailX;
        - *   let yOffset = -stripWidth / 2;
        - *   let yInterval = stripWidth / this.detailY;
        - *
        - *   for (let j = 0; j <= this.detailY; j += 1) {
        - *     // Calculate the "vertical" point along the strip.
        - *     let v = yOffset + yInterval * j;
        - *
        - *     for (let i = 0; i <= this.detailX; i += 1) {
        - *       // Calculate the angle of rotation around the strip.
        - *       let u = i * xInterval;
        - *
        - *       // Calculate the coordinates of the vertex.
        - *       let x = (radius + v * cos(u / 2)) * cos(u) - sin(u / 2) * 2 * spread;
        - *       let y = (radius + v * cos(u / 2)) * sin(u);
        - *       if (u < TWO_PI) {
        - *         y += sin(u) * spread;
        - *       } else {
        - *         y -= sin(u) * spread;
        - *       }
        - *       let z = v * sin(u / 2) + sin(u / 4) * 4 * spread;
        - *
        - *       // Create a p5.Vector object to position the vertex.
        - *       let vert = createVector(x, y, z);
        - *
        - *       // Add the vertex to the p5.Geometry object's vertices array.
        - *       this.vertices.push(vert);
        - *     }
        - *   }
        - *
        - *   // Compute the faces array.
        - *   this.computeFaces();
        - *
        - *   // Compute the surface normals to help with lighting.
        - *   this.computeNormals();
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Geometry = class Geometry {
        -  constructor(detailX, detailY, callback) {
        -    /**
        -    * An array with the geometry's vertices.
        -    *
        -    * The geometry's vertices are stored as
        -    * <a href="#/p5.Vector">p5.Vector</a> objects in the `myGeometry.vertices`
        -    * array. The geometry's first vertex is the
        -    * <a href="#/p5.Vector">p5.Vector</a> object at `myGeometry.vertices[0]`,
        -    * its second vertex is `myGeometry.vertices[1]`, its third vertex is
        -    * `myGeometry.vertices[2]`, and so on.
        -    *
        -    * @property vertices
        -    * @name vertices
        -    *
        -    * @example
        -    * <div>
        -    * <code>
        -    * // Click and drag the mouse to view the scene from different angles.
        -    *
        -    * let myGeometry;
        -    *
        -    * function setup() {
        -    *   createCanvas(100, 100, WEBGL);
        -    *
        -    *   // Create a p5.Geometry object.
        -    *   myGeometry = new p5.Geometry();
        -    *
        -    *   // Create p5.Vector objects to position the vertices.
        -    *   let v0 = createVector(-40, 0, 0);
        -    *   let v1 = createVector(0, -40, 0);
        -    *   let v2 = createVector(40, 0, 0);
        -    *
        -    *   // Add the vertices to the p5.Geometry object's vertices array.
        -    *   myGeometry.vertices.push(v0, v1, v2);
        -    *
        -    *   describe('A white triangle drawn on a gray background.');
        -    * }
        -    *
        -    * function draw() {
        -    *   background(200);
        -    *
        -    *   // Enable orbiting with the mouse.
        -    *   orbitControl();
        -    *
        -    *   // Draw the p5.Geometry object.
        -    *   model(myGeometry);
        -    * }
        -    * </code>
        -    * </div>
        -    *
        -    * <div>
        -    * <code>
        -    * // Click and drag the mouse to view the scene from different angles.
        -    *
        -    * let myGeometry;
        -    *
        -    * function setup() {
        -    *   createCanvas(100, 100, WEBGL);
        -    *
        -    *   // Create a p5.Geometry object.
        -    *   beginGeometry();
        -    *   torus(30, 15, 10, 8);
        -    *   myGeometry = endGeometry();
        -    *
        -    *   describe('A white torus rotates slowly against a dark gray background. Red spheres mark its vertices.');
        -    * }
        -    *
        -    * function draw() {
        -    *   background(50);
        -    *
        -    *   // Enable orbiting with the mouse.
        -    *   orbitControl();
        -    *
        -    *   // Turn on the lights.
        -    *   lights();
        -    *
        -    *   // Rotate the coordinate system.
        -    *   rotateY(frameCount * 0.01);
        -    *
        -    *   // Style the p5.Geometry object.
        -    *   fill(255);
        -    *   stroke(0);
        -    *
        -    *   // Display the p5.Geometry object.
        -    *   model(myGeometry);
        -    *
        -    *   // Style the vertices.
        -    *   fill(255, 0, 0);
        -    *   noStroke();
        -    *
        -    *   // Iterate over the vertices array.
        -    *   for (let v of myGeometry.vertices) {
        -    *     // Draw a sphere to mark the vertex.
        -    *     push();
        -    *     translate(v);
        -    *     sphere(2.5);
        -    *     pop();
        -    *   }
        -    * }
        -    * </code>
        -    * </div>
        -    */
        +import { DataArray } from './p5.DataArray';
        +import { Vector } from '../math/p5.Vector';
        +
        +class Geometry {
        +  constructor(detailX, detailY, callback, renderer) {
        +    this.renderer = renderer;
             this.vertices = [];
         
             this.boundingBoxCache = null;
         
         
             //an array containing every vertex for stroke drawing
        -    this.lineVertices = new p5.DataArray();
        +    this.lineVertices = new DataArray();
         
             // The tangents going into or out of a vertex on a line. Along a straight
             // line segment, both should be equal. At an endpoint, one or the other
             // will not exist and will be all 0. In joins between line segments, they
             // may be different, as they will be the tangents on either side of the join.
        -    this.lineTangentsIn = new p5.DataArray();
        -    this.lineTangentsOut = new p5.DataArray();
        +    this.lineTangentsIn = new DataArray();
        +    this.lineTangentsOut = new DataArray();
         
             // When drawing lines with thickness, entries in this buffer represent which
             // side of the centerline the vertex will be placed. The sign of the number
             // will represent the side of the centerline, and the absolute value will be
             // used as an enum to determine which part of the cap or join each vertex
             // represents. See the doc comments for _addCap and _addJoin for diagrams.
        -    this.lineSides = new p5.DataArray();
        -
        -    /**
        -    * An array with the vectors that are normal to the geometry's vertices.
        -    *
        -    * A face's orientation is defined by its *normal vector* which points out
        -    * of the face and is normal (perpendicular) to the surface. Calling
        -    * `myGeometry.computeNormals()` first calculates each face's normal
        -    * vector. Then it calculates the normal vector for each vertex by
        -    * averaging the normal vectors of the faces surrounding the vertex. The
        -    * vertex normals are stored as <a href="#/p5.Vector">p5.Vector</a>
        -    * objects in the `myGeometry.vertexNormals` array.
        -    *
        -    * @property vertexNormals
        -    * @name vertexNormals
        -    *
        -    * @example
        -    * <div>
        -    * <code>
        -    * // Click and drag the mouse to view the scene from different angles.
        -    *
        -    * let myGeometry;
        -    *
        -    * function setup() {
        -    *   createCanvas(100, 100, WEBGL);
        -    *
        -    *   // Create a p5.Geometry object.
        -    *   beginGeometry();
        -    *   torus(30, 15, 10, 8);
        -    *   myGeometry = endGeometry();
        -    *
        -    *   // Compute the vertex normals.
        -    *   myGeometry.computeNormals();
        -    *
        -    *   describe(
        -    *     'A white torus rotates against a dark gray background. Red lines extend outward from its vertices.'
        -    *   );
        -    * }
        -    *
        -    * function draw() {
        -    *   background(50);
        -    *
        -    *   // Enable orbiting with the mouse.
        -    *   orbitControl();
        -    *
        -    *   // Turn on the lights.
        -    *   lights();
        -    *
        -    *   // Rotate the coordinate system.
        -    *   rotateY(frameCount * 0.01);
        -    *
        -    *   // Style the p5.Geometry object.
        -    *   stroke(0);
        -    *
        -    *   // Display the p5.Geometry object.
        -    *   model(myGeometry);
        -    *
        -    *   // Style the normal vectors.
        -    *   stroke(255, 0, 0);
        -    *
        -    *   // Iterate over the vertices and vertexNormals arrays.
        -    *   for (let i = 0; i < myGeometry.vertices.length; i += 1) {
        -    *
        -    *     // Get the vertex p5.Vector object.
        -    *     let v = myGeometry.vertices[i];
        -    *
        -    *     // Get the vertex normal p5.Vector object.
        -    *     let n = myGeometry.vertexNormals[i];
        -    *
        -    *     // Calculate a point along the vertex normal.
        -    *     let p = p5.Vector.mult(n, 8);
        -    *
        -    *     // Draw the vertex normal as a red line.
        -    *     push();
        -    *     translate(v);
        -    *     line(0, 0, 0, p.x, p.y, p.z);
        -    *     pop();
        -    *   }
        -    * }
        -    * </code>
        -    * </div>
        -    *
        -    * <div>
        -    * <code>
        -    * // Click and drag the mouse to view the scene from different angles.
        -    *
        -    * let myGeometry;
        -    *
        -    * function setup() {
        -    *   createCanvas(100, 100, WEBGL);
        -    *
        -    *   // Create a p5.Geometry object.
        -    *   myGeometry = new p5.Geometry();
        -    *
        -    *   // Create p5.Vector objects to position the vertices.
        -    *   let v0 = createVector(-40, 0, 0);
        -    *   let v1 = createVector(0, -40, 0);
        -    *   let v2 = createVector(0, 40, 0);
        -    *   let v3 = createVector(40, 0, 0);
        -    *
        -    *   // Add the vertices to the p5.Geometry object's vertices array.
        -    *   myGeometry.vertices.push(v0, v1, v2, v3);
        -    *
        -    *   // Compute the faces array.
        -    *   myGeometry.computeFaces();
        -    *
        -    *   // Compute the surface normals.
        -    *   myGeometry.computeNormals();
        -    *
        -    *   describe('A red square drawn on a gray background.');
        -    * }
        -    *
        -    * function draw() {
        -    *   background(200);
        -    *
        -    *   // Enable orbiting with the mouse.
        -    *   orbitControl();
        -    *
        -    *   // Add a white point light.
        -    *   pointLight(255, 255, 255, 0, 0, 10);
        -    *
        -    *   // Style the p5.Geometry object.
        -    *   noStroke();
        -    *   fill(255, 0, 0);
        -    *
        -    *   // Display the p5.Geometry object.
        -    *   model(myGeometry);
        -    * }
        -    * </code>
        -    * </div>
        -    */
        +    this.lineSides = new DataArray();
        +
             this.vertexNormals = [];
        -    /**
        -    * An array that lists which of the geometry's vertices form each of its
        -    * faces.
        -    *
        -    * All 3D shapes are made by connecting sets of points called *vertices*. A
        -    * geometry's surface is formed by connecting vertices to form triangles
        -    * that are stitched together. Each triangular patch on the geometry's
        -    * surface is called a *face*.
        -    *
        -    * The geometry's vertices are stored as
        -    * <a href="#/p5.Vector">p5.Vector</a> objects in the
        -    * <a href="#/p5.Geometry/vertices">myGeometry.vertices</a> array. The
        -    * geometry's first vertex is the <a href="#/p5.Vector">p5.Vector</a>
        -    * object at `myGeometry.vertices[0]`, its second vertex is
        -    * `myGeometry.vertices[1]`, its third vertex is `myGeometry.vertices[2]`,
        -    * and so on.
        -    *
        -    * For example, a geometry made from a rectangle has two faces because a
        -    * rectangle is made by joining two triangles. `myGeometry.faces` for a
        -    * rectangle would be the two-dimensional array `[[0, 1, 2], [2, 1, 3]]`.
        -    * The first face, `myGeometry.faces[0]`, is the array `[0, 1, 2]` because
        -    * it's formed by connecting `myGeometry.vertices[0]`,
        -    * `myGeometry.vertices[1]`,and `myGeometry.vertices[2]`. The second face,
        -    * `myGeometry.faces[1]`, is the array `[2, 1, 3]` because it's formed by
        -    * connecting `myGeometry.vertices[2]`, `myGeometry.vertices[1]`,and
        -    * `myGeometry.vertices[3]`.
        -    *
        -    * @property faces
        -    * @name faces
        -    *
        -    * @example
        -    * <div>
        -    * <code>
        -    * // Click and drag the mouse to view the scene from different angles.
        -    *
        -    * let myGeometry;
        -    *
        -    * function setup() {
        -    *   createCanvas(100, 100, WEBGL);
        -    *
        -    *   // Create a p5.Geometry object.
        -    *   beginGeometry();
        -    *   sphere();
        -    *   myGeometry = endGeometry();
        -    *
        -    *   describe("A sphere drawn on a gray background. The sphere's surface is a grayscale patchwork of triangles.");
        -    * }
        -    *
        -    * function draw() {
        -    *   background(200);
        -    *
        -    *   // Enable orbiting with the mouse.
        -    *   orbitControl();
        -    *
        -    *   // Turn on the lights.
        -    *   lights();
        -    *
        -    *   // Style the p5.Geometry object.
        -    *   noStroke();
        -    *
        -    *   // Set a random seed.
        -    *   randomSeed(1234);
        -    *
        -    *   // Iterate over the faces array.
        -    *   for (let face of myGeometry.faces) {
        -    *
        -    *     // Style the face.
        -    *     let g = random(0, 255);
        -    *     fill(g);
        -    *
        -    *     // Draw the face.
        -    *     beginShape();
        -    *     // Iterate over the vertices that form the face.
        -    *     for (let f of face) {
        -    *       // Get the vertex's p5.Vector object.
        -    *       let v = myGeometry.vertices[f];
        -    *       vertex(v.x, v.y, v.z);
        -    *     }
        -    *     endShape();
        -    *
        -    *   }
        -    * }
        -    * </code>
        -    * </div>
        -    */
        +
             this.faces = [];
        -    /**
        -    * An array that lists the texture coordinates for each of the geometry's
        -    * vertices.
        -    *
        -    * In order for <a href="#/p5/texture">texture()</a> to work, the geometry
        -    * needs a way to map the points on its surface to the pixels in a
        -    * rectangular image that's used as a texture. The geometry's vertex at
        -    * coordinates `(x, y, z)` maps to the texture image's pixel at coordinates
        -    * `(u, v)`.
        -    *
        -    * The `myGeometry.uvs` array stores the `(u, v)` coordinates for each
        -    * vertex in the order it was added to the geometry. For example, the
        -    * first vertex, `myGeometry.vertices[0]`, has its `(u, v)` coordinates
        -    * stored at `myGeometry.uvs[0]` and `myGeometry.uvs[1]`.
        -    *
        -    * @property uvs
        -    * @name uvs
        -    *
        -    * @example
        -    * <div>
        -    * <code>
        -    * let img;
        -    *
        -    * // Load the image and create a p5.Image object.
        -    * function preload() {
        -    *   img = loadImage('assets/laDefense.jpg');
        -    * }
        -    *
        -    * function setup() {
        -    *   createCanvas(100, 100, WEBGL);
        -    *
        -    *   background(200);
        -    *
        -    *   // Create p5.Geometry objects.
        -    *   let geom1 = buildGeometry(createShape);
        -    *   let geom2 = buildGeometry(createShape);
        -    *
        -    *   // Left (original).
        -    *   push();
        -    *   translate(-25, 0, 0);
        -    *   texture(img);
        -    *   noStroke();
        -    *   model(geom1);
        -    *   pop();
        -    *
        -    *   // Set geom2's texture coordinates.
        -    *   geom2.uvs = [0.25, 0.25, 0.75, 0.25, 0.25, 0.75, 0.75, 0.75];
        -    *
        -    *   // Right (zoomed in).
        -    *   push();
        -    *   translate(25, 0, 0);
        -    *   texture(img);
        -    *   noStroke();
        -    *   model(geom2);
        -    *   pop();
        -    *
        -    *   describe(
        -    *     'Two photos of a ceiling on a gray background. The photo on the right zooms in to the center of the photo.'
        -    *   );
        -    * }
        -    *
        -    * function createShape() {
        -    *   plane(40);
        -    * }
        -    * </code>
        -    * </div>
        -    */
        +
             this.uvs = [];
             // a 2D array containing edge connectivity pattern for create line vertices
             //based on faces for most objects;
        @@ -664,9 +50,11 @@ p5.Geometry = class Geometry {
             // One color per vertex representing the stroke color at that vertex
             this.vertexStrokeColors = [];
         
        +    this.userVertexProperties = {};
        +
             // One color per line vertex, generated automatically based on
             // vertexStrokeColors in _edgesToVertices()
        -    this.lineVertexColors = new p5.DataArray();
        +    this.lineVertexColors = new DataArray();
             this.detailX = detailX !== undefined ? detailX : 1;
             this.detailY = detailY !== undefined ? detailY : 1;
             this.dirtyFlags = {};
        @@ -711,7 +99,6 @@ p5.Geometry = class Geometry {
          * // }
          * ```
          *
        - * @method calculateBoundingBox
          * @returns {Object} bounding box of the geometry.
          *
          * @example
        @@ -783,9 +170,9 @@ p5.Geometry = class Geometry {
               return this.boundingBoxCache; // Return cached result if available
             }
         
        -    let minVertex = new p5.Vector(
        +    let minVertex = new Vector(
               Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
        -    let maxVertex = new p5.Vector(
        +    let maxVertex = new Vector(
               Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE);
         
             for (let i = 0; i < this.vertices.length; i++) {
        @@ -799,9 +186,9 @@ p5.Geometry = class Geometry {
               maxVertex.z = Math.max(maxVertex.z, vertex.z);
             }
             // Calculate size and offset properties
        -    let size = new p5.Vector(maxVertex.x - minVertex.x,
        +    let size = new Vector(maxVertex.x - minVertex.x,
               maxVertex.y - minVertex.y, maxVertex.z - minVertex.z);
        -    let offset = new p5.Vector((minVertex.x + maxVertex.x) / 2,
        +    let offset = new Vector((minVertex.x + maxVertex.x) / 2,
               (minVertex.y + maxVertex.y) / 2, (minVertex.z + maxVertex.z) / 2);
         
             // Cache the result for future access
        @@ -832,6 +219,11 @@ p5.Geometry = class Geometry {
             this.vertexNormals.length = 0;
             this.uvs.length = 0;
         
        +    for (const propName in this.userVertexProperties){
        +      this.userVertexProperties[propName].delete();
        +    }
        +    this.userVertexProperties = {};
        +
             this.dirtyFlags = {};
           }
         
        @@ -869,8 +261,6 @@ p5.Geometry = class Geometry {
            * `myGeometry.clearColors()` allows the
            * <a href="#/p5/fill">fill()</a> function to apply color to the geometry.
            *
        -   * @method clearColors
        -   *
            * @example
            * <div>
            * <code>
        @@ -1021,7 +411,7 @@ p5.Geometry = class Geometry {
             });
         
             const blob = new Blob([objStr], { type: 'text/plain' });
        -    p5.prototype.downloadFile(blob, fileName , 'obj');
        +    fn.downloadFile(blob, fileName , 'obj');
         
           }
         
        @@ -1090,17 +480,17 @@ p5.Geometry = class Geometry {
             let name = fileName.substring(0, fileName.lastIndexOf('.'));
             let faceNormals = [];
             for (let f of this.faces) {
        -      const U = p5.Vector.sub(this.vertices[f[1]], this.vertices[f[0]]);
        -      const V = p5.Vector.sub(this.vertices[f[2]], this.vertices[f[0]]);
        +      const U = Vector.sub(this.vertices[f[1]], this.vertices[f[0]]);
        +      const V = Vector.sub(this.vertices[f[2]], this.vertices[f[0]]);
               const nx = U.y * V.z - U.z * V.y;
               const ny = U.z * V.x - U.x * V.z;
               const nz = U.x * V.y - U.y * V.x;
        -      faceNormals.push(new p5.Vector(nx, ny, nz).normalize());
        +      faceNormals.push(new Vector(nx, ny, nz).normalize());
             }
             if (binary) {
               let offset = 80;
               const bufferLength =
        -          this.faces.length * 2 + this.faces.length * 3 * 4 * 4 + 80 + 4;
        +        this.faces.length * 2 + this.faces.length * 3 * 4 * 4 + 80 + 4;
               const arrayBuffer = new ArrayBuffer(bufferLength);
               modelOutput = new DataView(arrayBuffer);
               modelOutput.setUint32(offset, this.faces.length, true);
        @@ -1144,94 +534,90 @@ p5.Geometry = class Geometry {
               modelOutput += 'endsolid ' + name + '\n';
             }
             const blob = new Blob([modelOutput], { type: 'text/plain' });
        -    p5.prototype.downloadFile(blob, fileName, 'stl');
        +    fn.downloadFile(blob, fileName, 'stl');
           }
         
           /**
        - * Flips the geometry’s texture u-coordinates.
        - *
        - * In order for <a href="#/p5/texture">texture()</a> to work, the geometry
        - * needs a way to map the points on its surface to the pixels in a rectangular
        - * image that's used as a texture. The geometry's vertex at coordinates
        - * `(x, y, z)` maps to the texture image's pixel at coordinates `(u, v)`.
        - *
        - * The <a href="#/p5.Geometry/uvs">myGeometry.uvs</a> array stores the
        - * `(u, v)` coordinates for each vertex in the order it was added to the
        - * geometry. Calling `myGeometry.flipU()` flips a geometry's u-coordinates
        - * so that the texture appears mirrored horizontally.
        - *
        - * For example, a plane's four vertices are added clockwise starting from the
        - * top-left corner. Here's how calling `myGeometry.flipU()` would change a
        - * plane's texture coordinates:
        - *
        - * ```js
        - * // Print the original texture coordinates.
        - * // Output: [0, 0, 1, 0, 0, 1, 1, 1]
        - * console.log(myGeometry.uvs);
        - *
        - * // Flip the u-coordinates.
        - * myGeometry.flipU();
        - *
        - * // Print the flipped texture coordinates.
        - * // Output: [1, 0, 0, 0, 1, 1, 0, 1]
        - * console.log(myGeometry.uvs);
        - *
        - * // Notice the swaps:
        - * // Top vertices: [0, 0, 1, 0] --> [1, 0, 0, 0]
        - * // Bottom vertices: [0, 1, 1, 1] --> [1, 1, 0, 1]
        - * ```
        - *
        - * @method flipU
        - * @for p5.Geometry
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   // Create p5.Geometry objects.
        - *   let geom1 = buildGeometry(createShape);
        - *   let geom2 = buildGeometry(createShape);
        - *
        - *   // Flip geom2's U texture coordinates.
        - *   geom2.flipU();
        - *
        - *   // Left (original).
        - *   push();
        - *   translate(-25, 0, 0);
        - *   texture(img);
        - *   noStroke();
        - *   model(geom1);
        - *   pop();
        - *
        - *   // Right (flipped).
        - *   push();
        - *   translate(25, 0, 0);
        - *   texture(img);
        - *   noStroke();
        - *   model(geom2);
        - *   pop();
        - *
        - *   describe(
        - *     'Two photos of a ceiling on a gray background. The photos are mirror images of each other.'
        - *   );
        - * }
        - *
        - * function createShape() {
        - *   plane(40);
        - * }
        - * </code>
        - * </div>
        - */
        +   * Flips the geometry’s texture u-coordinates.
        +   *
        +   * In order for <a href="#/p5/texture">texture()</a> to work, the geometry
        +   * needs a way to map the points on its surface to the pixels in a rectangular
        +   * image that's used as a texture. The geometry's vertex at coordinates
        +   * `(x, y, z)` maps to the texture image's pixel at coordinates `(u, v)`.
        +   *
        +   * The <a href="#/p5.Geometry/uvs">myGeometry.uvs</a> array stores the
        +   * `(u, v)` coordinates for each vertex in the order it was added to the
        +   * geometry. Calling `myGeometry.flipU()` flips a geometry's u-coordinates
        +   * so that the texture appears mirrored horizontally.
        +   *
        +   * For example, a plane's four vertices are added clockwise starting from the
        +   * top-left corner. Here's how calling `myGeometry.flipU()` would change a
        +   * plane's texture coordinates:
        +   *
        +   * ```js
        +   * // Print the original texture coordinates.
        +   * // Output: [0, 0, 1, 0, 0, 1, 1, 1]
        +   * console.log(myGeometry.uvs);
        +   *
        +   * // Flip the u-coordinates.
        +   * myGeometry.flipU();
        +   *
        +   * // Print the flipped texture coordinates.
        +   * // Output: [1, 0, 0, 0, 1, 1, 0, 1]
        +   * console.log(myGeometry.uvs);
        +   *
        +   * // Notice the swaps:
        +   * // Top vertices: [0, 0, 1, 0] --> [1, 0, 0, 0]
        +   * // Bottom vertices: [0, 1, 1, 1] --> [1, 1, 0, 1]
        +   * ```
        +   *
        +   * @for p5.Geometry
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * async function setup() {
        +   *   img = await loadImage('assets/laDefense.jpg');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create p5.Geometry objects.
        +   *   let geom1 = buildGeometry(createShape);
        +   *   let geom2 = buildGeometry(createShape);
        +   *
        +   *   // Flip geom2's U texture coordinates.
        +   *   geom2.flipU();
        +   *
        +   *   // Left (original).
        +   *   push();
        +   *   translate(-25, 0, 0);
        +   *   texture(img);
        +   *   noStroke();
        +   *   model(geom1);
        +   *   pop();
        +   *
        +   *   // Right (flipped).
        +   *   push();
        +   *   translate(25, 0, 0);
        +   *   texture(img);
        +   *   noStroke();
        +   *   model(geom2);
        +   *   pop();
        +   *
        +   *   describe(
        +   *     'Two photos of a ceiling on a gray background. The photos are mirror images of each other.'
        +   *   );
        +   * }
        +   *
        +   * function createShape() {
        +   *   plane(40);
        +   * }
        +   * </code>
        +   * </div>
        +   */
           flipU() {
             this.uvs = this.uvs.flat().map((val, index) => {
               if (index % 2 === 0) {
        @@ -1243,90 +629,87 @@ p5.Geometry = class Geometry {
           }
         
           /**
        - * Flips the geometry’s texture v-coordinates.
        - *
        - * In order for <a href="#/p5/texture">texture()</a> to work, the geometry
        - * needs a way to map the points on its surface to the pixels in a rectangular
        - * image that's used as a texture. The geometry's vertex at coordinates
        - * `(x, y, z)` maps to the texture image's pixel at coordinates `(u, v)`.
        - *
        - * The <a href="#/p5.Geometry/uvs">myGeometry.uvs</a> array stores the
        - * `(u, v)` coordinates for each vertex in the order it was added to the
        - * geometry. Calling `myGeometry.flipV()` flips a geometry's v-coordinates
        - * so that the texture appears mirrored vertically.
        - *
        - * For example, a plane's four vertices are added clockwise starting from the
        - * top-left corner. Here's how calling `myGeometry.flipV()` would change a
        - * plane's texture coordinates:
        - *
        - * ```js
        - * // Print the original texture coordinates.
        - * // Output: [0, 0, 1, 0, 0, 1, 1, 1]
        - * console.log(myGeometry.uvs);
        - *
        - * // Flip the v-coordinates.
        - * myGeometry.flipV();
        - *
        - * // Print the flipped texture coordinates.
        - * // Output: [0, 1, 1, 1, 0, 0, 1, 0]
        - * console.log(myGeometry.uvs);
        - *
        - * // Notice the swaps:
        - * // Left vertices: [0, 0] &lt;--&gt; [1, 0]
        - * // Right vertices: [1, 0] &lt;--&gt; [1, 1]
        - * ```
        - *
        - * @method flipV
        - * @for p5.Geometry
        - *
        - * @example
        - * <div>
        - * <code>
        - * let img;
        - *
        - * function preload() {
        - *   img = loadImage('assets/laDefense.jpg');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   background(200);
        - *
        - *   // Create p5.Geometry objects.
        - *   let geom1 = buildGeometry(createShape);
        - *   let geom2 = buildGeometry(createShape);
        - *
        - *   // Flip geom2's V texture coordinates.
        - *   geom2.flipV();
        - *
        - *   // Left (original).
        - *   push();
        - *   translate(-25, 0, 0);
        - *   texture(img);
        - *   noStroke();
        - *   model(geom1);
        - *   pop();
        - *
        - *   // Right (flipped).
        - *   push();
        - *   translate(25, 0, 0);
        - *   texture(img);
        - *   noStroke();
        - *   model(geom2);
        - *   pop();
        - *
        - *   describe(
        - *     'Two photos of a ceiling on a gray background. The photos are mirror images of each other.'
        - *   );
        - * }
        - *
        - * function createShape() {
        - *   plane(40);
        - * }
        - * </code>
        - * </div>
        - */
        +   * Flips the geometry’s texture v-coordinates.
        +   *
        +   * In order for <a href="#/p5/texture">texture()</a> to work, the geometry
        +   * needs a way to map the points on its surface to the pixels in a rectangular
        +   * image that's used as a texture. The geometry's vertex at coordinates
        +   * `(x, y, z)` maps to the texture image's pixel at coordinates `(u, v)`.
        +   *
        +   * The <a href="#/p5.Geometry/uvs">myGeometry.uvs</a> array stores the
        +   * `(u, v)` coordinates for each vertex in the order it was added to the
        +   * geometry. Calling `myGeometry.flipV()` flips a geometry's v-coordinates
        +   * so that the texture appears mirrored vertically.
        +   *
        +   * For example, a plane's four vertices are added clockwise starting from the
        +   * top-left corner. Here's how calling `myGeometry.flipV()` would change a
        +   * plane's texture coordinates:
        +   *
        +   * ```js
        +   * // Print the original texture coordinates.
        +   * // Output: [0, 0, 1, 0, 0, 1, 1, 1]
        +   * console.log(myGeometry.uvs);
        +   *
        +   * // Flip the v-coordinates.
        +   * myGeometry.flipV();
        +   *
        +   * // Print the flipped texture coordinates.
        +   * // Output: [0, 1, 1, 1, 0, 0, 1, 0]
        +   * console.log(myGeometry.uvs);
        +   *
        +   * // Notice the swaps:
        +   * // Left vertices: [0, 0] <--> [1, 0]
        +   * // Right vertices: [1, 0] <--> [1, 1]
        +   * ```
        +   *
        +   * @method flipV
        +   * @for p5.Geometry
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * async function setup() {
        +   *   img = await loadImage('assets/laDefense.jpg');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create p5.Geometry objects.
        +   *   let geom1 = buildGeometry(createShape);
        +   *   let geom2 = buildGeometry(createShape);
        +   *
        +   *   // Flip geom2's V texture coordinates.
        +   *   geom2.flipV();
        +   *
        +   *   // Left (original).
        +   *   push();
        +   *   translate(-25, 0, 0);
        +   *   texture(img);
        +   *   noStroke();
        +   *   model(geom1);
        +   *   pop();
        +   *
        +   *   // Right (flipped).
        +   *   push();
        +   *   translate(25, 0, 0);
        +   *   texture(img);
        +   *   noStroke();
        +   *   model(geom2);
        +   *   pop();
        +   *
        +   *   describe(
        +   *     'Two photos of a ceiling on a gray background. The photos are mirror images of each other.'
        +   *   );
        +   * }
        +   *
        +   * function createShape() {
        +   *   plane(40);
        +   * }
        +   * </code>
        +   * </div>
        +   */
           flipV() {
             this.uvs = this.uvs.flat().map((val, index) => {
               if (index % 2 === 0) {
        @@ -1338,135 +721,134 @@ p5.Geometry = class Geometry {
           }
         
           /**
        - * Computes the geometry's faces using its vertices.
        - *
        - * All 3D shapes are made by connecting sets of points called *vertices*. A
        - * geometry's surface is formed by connecting vertices to form triangles that
        - * are stitched together. Each triangular patch on the geometry's surface is
        - * called a *face*. `myGeometry.computeFaces()` performs the math needed to
        - * define each face based on the distances between vertices.
        - *
        - * The geometry's vertices are stored as <a href="#/p5.Vector">p5.Vector</a>
        - * objects in the <a href="#/p5.Geometry/vertices">myGeometry.vertices</a>
        - * array. The geometry's first vertex is the
        - * <a href="#/p5.Vector">p5.Vector</a> object at `myGeometry.vertices[0]`,
        - * its second vertex is `myGeometry.vertices[1]`, its third vertex is
        - * `myGeometry.vertices[2]`, and so on.
        - *
        - * Calling `myGeometry.computeFaces()` fills the
        - * <a href="#/p5.Geometry/faces">myGeometry.faces</a> array with three-element
        - * arrays that list the vertices that form each face. For example, a geometry
        - * made from a rectangle has two faces because a rectangle is made by joining
        - * two triangles. <a href="#/p5.Geometry/faces">myGeometry.faces</a> for a
        - * rectangle would be the two-dimensional array
        - * `[[0, 1, 2], [2, 1, 3]]`. The first face, `myGeometry.faces[0]`, is the
        - * array `[0, 1, 2]` because it's formed by connecting
        - * `myGeometry.vertices[0]`, `myGeometry.vertices[1]`,and
        - * `myGeometry.vertices[2]`. The second face, `myGeometry.faces[1]`, is the
        - * array `[2, 1, 3]` because it's formed by connecting
        - * `myGeometry.vertices[2]`, `myGeometry.vertices[1]`, and
        - * `myGeometry.vertices[3]`.
        - *
        - * Note: `myGeometry.computeFaces()` only works when geometries have four or more vertices.
        - *
        - * @method computeFaces
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let myGeometry;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Geometry object.
        - *   myGeometry = new p5.Geometry();
        - *
        - *   // Create p5.Vector objects to position the vertices.
        - *   let v0 = createVector(-40, 0, 0);
        - *   let v1 = createVector(0, -40, 0);
        - *   let v2 = createVector(0, 40, 0);
        - *   let v3 = createVector(40, 0, 0);
        - *
        - *   // Add the vertices to myGeometry's vertices array.
        - *   myGeometry.vertices.push(v0, v1, v2, v3);
        - *
        - *   // Compute myGeometry's faces array.
        - *   myGeometry.computeFaces();
        - *
        - *   describe('A red square drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the shape.
        - *   noStroke();
        - *   fill(255, 0, 0);
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(myGeometry);
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Click and drag the mouse to view the scene from different angles.
        - *
        - * let myGeometry;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Geometry object using a callback function.
        - *   myGeometry = new p5.Geometry(1, 1, createShape);
        - *
        - *   describe('A red square drawn on a gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(200);
        - *
        - *   // Enable orbiting with the mouse.
        - *   orbitControl();
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Style the shape.
        - *   noStroke();
        - *   fill(255, 0, 0);
        - *
        - *   // Draw the p5.Geometry object.
        - *   model(myGeometry);
        - * }
        - *
        - * function createShape() {
        - *   // Create p5.Vector objects to position the vertices.
        - *   let v0 = createVector(-40, 0, 0);
        - *   let v1 = createVector(0, -40, 0);
        - *   let v2 = createVector(0, 40, 0);
        - *   let v3 = createVector(40, 0, 0);
        - *
        - *   // Add the vertices to the p5.Geometry object's vertices array.
        - *   this.vertices.push(v0, v1, v2, v3);
        - *
        - *   // Compute the faces array.
        - *   this.computeFaces();
        - * }
        - * </code>
        - * </div>
        - */
        +   * Computes the geometry's faces using its vertices.
        +   *
        +   * All 3D shapes are made by connecting sets of points called *vertices*. A
        +   * geometry's surface is formed by connecting vertices to form triangles that
        +   * are stitched together. Each triangular patch on the geometry's surface is
        +   * called a *face*. `myGeometry.computeFaces()` performs the math needed to
        +   * define each face based on the distances between vertices.
        +   *
        +   * The geometry's vertices are stored as <a href="#/p5.Vector">p5.Vector</a>
        +   * objects in the <a href="#/p5.Geometry/vertices">myGeometry.vertices</a>
        +   * array. The geometry's first vertex is the
        +   * <a href="#/p5.Vector">p5.Vector</a> object at `myGeometry.vertices[0]`,
        +   * its second vertex is `myGeometry.vertices[1]`, its third vertex is
        +   * `myGeometry.vertices[2]`, and so on.
        +   *
        +   * Calling `myGeometry.computeFaces()` fills the
        +   * <a href="#/p5.Geometry/faces">myGeometry.faces</a> array with three-element
        +   * arrays that list the vertices that form each face. For example, a geometry
        +   * made from a rectangle has two faces because a rectangle is made by joining
        +   * two triangles. <a href="#/p5.Geometry/faces">myGeometry.faces</a> for a
        +   * rectangle would be the two-dimensional array
        +   * `[[0, 1, 2], [2, 1, 3]]`. The first face, `myGeometry.faces[0]`, is the
        +   * array `[0, 1, 2]` because it's formed by connecting
        +   * `myGeometry.vertices[0]`, `myGeometry.vertices[1]`,and
        +   * `myGeometry.vertices[2]`. The second face, `myGeometry.faces[1]`, is the
        +   * array `[2, 1, 3]` because it's formed by connecting
        +   * `myGeometry.vertices[2]`, `myGeometry.vertices[1]`, and
        +   * `myGeometry.vertices[3]`.
        +   *
        +   * Note: `myGeometry.computeFaces()` only works when geometries have four or more vertices.
        +   *
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Geometry object.
        +   *   myGeometry = new p5.Geometry();
        +   *
        +   *   // Create p5.Vector objects to position the vertices.
        +   *   let v0 = createVector(-40, 0, 0);
        +   *   let v1 = createVector(0, -40, 0);
        +   *   let v2 = createVector(0, 40, 0);
        +   *   let v3 = createVector(40, 0, 0);
        +   *
        +   *   // Add the vertices to myGeometry's vertices array.
        +   *   myGeometry.vertices.push(v0, v1, v2, v3);
        +   *
        +   *   // Compute myGeometry's faces array.
        +   *   myGeometry.computeFaces();
        +   *
        +   *   describe('A red square drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the shape.
        +   *   noStroke();
        +   *   fill(255, 0, 0);
        +   *
        +   *   // Draw the p5.Geometry object.
        +   *   model(myGeometry);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Geometry object using a callback function.
        +   *   myGeometry = new p5.Geometry(1, 1, createShape);
        +   *
        +   *   describe('A red square drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the shape.
        +   *   noStroke();
        +   *   fill(255, 0, 0);
        +   *
        +   *   // Draw the p5.Geometry object.
        +   *   model(myGeometry);
        +   * }
        +   *
        +   * function createShape() {
        +   *   // Create p5.Vector objects to position the vertices.
        +   *   let v0 = createVector(-40, 0, 0);
        +   *   let v1 = createVector(0, -40, 0);
        +   *   let v2 = createVector(0, 40, 0);
        +   *   let v3 = createVector(40, 0, 0);
        +   *
        +   *   // Add the vertices to the p5.Geometry object's vertices array.
        +   *   this.vertices.push(v0, v1, v2, v3);
        +   *
        +   *   // Compute the faces array.
        +   *   this.computeFaces();
        +   * }
        +   * </code>
        +   * </div>
        +   */
           computeFaces() {
             this.faces.length = 0;
             const sliceCount = this.detailX + 1;
        @@ -1490,11 +872,11 @@ p5.Geometry = class Geometry {
             const vA = this.vertices[face[0]];
             const vB = this.vertices[face[1]];
             const vC = this.vertices[face[2]];
        -    const ab = p5.Vector.sub(vB, vA);
        -    const ac = p5.Vector.sub(vC, vA);
        -    const n = p5.Vector.cross(ab, ac);
        -    const ln = p5.Vector.mag(n);
        -    let sinAlpha = ln / (p5.Vector.mag(ab) * p5.Vector.mag(ac));
        +    const ab = Vector.sub(vB, vA);
        +    const ac = Vector.sub(vC, vA);
        +    const n = Vector.cross(ab, ac);
        +    const ln = Vector.mag(n);
        +    let sinAlpha = ln / (Vector.mag(ab) * Vector.mag(ac));
             if (sinAlpha === 0 || isNaN(sinAlpha)) {
               console.warn(
                 'p5.Geometry.prototype._getFaceNormal:',
        @@ -1537,8 +919,7 @@ p5.Geometry = class Geometry {
            * number of decimal places to use for calculations. By default,
            * `roundToPrecision` uses 3 decimal places.
            *
        -   * @method computeNormals
        -   * @param {String} [shadingType] shading type. either FLAT or SMOOTH. Defaults to `FLAT`.
        +   * @param {(FLAT|SMOOTH)} [shadingType=FLAT] shading type. either FLAT or SMOOTH. Defaults to `FLAT`.
            * @param {Object} [options] shading options.
            * @chainable
            *
        @@ -1865,7 +1246,7 @@ p5.Geometry = class Geometry {
             // initialize the vertexNormals array with empty vectors
             vertexNormals.length = 0;
             for (iv = 0; iv < vertices.length; ++iv) {
        -      vertexNormals.push(new p5.Vector());
        +      vertexNormals.push(new Vector());
             }
         
             // loop through all the faces adding its normal to the normal
        @@ -1889,20 +1270,20 @@ p5.Geometry = class Geometry {
           }
         
           /**
        - * Averages the vertex normals. Used in curved
        - * surfaces
        - * @private
        - * @chainable
        - */
        +   * Averages the vertex normals. Used in curved
        +   * surfaces
        +   * @private
        +   * @chainable
        +   */
           averageNormals() {
             for (let i = 0; i <= this.detailY; i++) {
               const offset = this.detailX + 1;
        -      let temp = p5.Vector.add(
        +      let temp = Vector.add(
                 this.vertexNormals[i * offset],
                 this.vertexNormals[i * offset + this.detailX]
               );
         
        -      temp = p5.Vector.div(temp, 2);
        +      temp = Vector.div(temp, 2);
               this.vertexNormals[i * offset] = temp;
               this.vertexNormals[i * offset + this.detailX] = temp;
             }
        @@ -1910,24 +1291,24 @@ p5.Geometry = class Geometry {
           }
         
           /**
        - * Averages pole normals.  Used in spherical primitives
        - * @private
        - * @chainable
        - */
        +   * Averages pole normals.  Used in spherical primitives
        +   * @private
        +   * @chainable
        +   */
           averagePoleNormals() {
             //average the north pole
        -    let sum = new p5.Vector(0, 0, 0);
        +    let sum = new Vector(0, 0, 0);
             for (let i = 0; i < this.detailX; i++) {
               sum.add(this.vertexNormals[i]);
             }
        -    sum = p5.Vector.div(sum, this.detailX);
        +    sum = Vector.div(sum, this.detailX);
         
             for (let i = 0; i < this.detailX; i++) {
               this.vertexNormals[i] = sum;
             }
         
             //average the south pole
        -    sum = new p5.Vector(0, 0, 0);
        +    sum = new Vector(0, 0, 0);
             for (
               let i = this.vertices.length - 1;
               i > this.vertices.length - 1 - this.detailX;
        @@ -1935,7 +1316,7 @@ p5.Geometry = class Geometry {
             ) {
               sum.add(this.vertexNormals[i]);
             }
        -    sum = p5.Vector.div(sum, this.detailX);
        +    sum = Vector.div(sum, this.detailX);
         
             for (
               let i = this.vertices.length - 1;
        @@ -1948,10 +1329,10 @@ p5.Geometry = class Geometry {
           }
         
           /**
        - * Create a 2D array for establishing stroke connections
        - * @private
        - * @chainable
        - */
        +   * Create a 2D array for establishing stroke connections
        +   * @private
        +   * @chainable
        +   */
           _makeTriangleEdges() {
             this.edges.length = 0;
         
        @@ -1965,20 +1346,20 @@ p5.Geometry = class Geometry {
           }
         
           /**
        - * Converts each line segment into the vertices and vertex attributes needed
        - * to turn the line into a polygon on screen. This will include:
        - * - Two triangles line segment to create a rectangle
        - * - Two triangles per endpoint to create a stroke cap rectangle. A fragment
        - *   shader is responsible for displaying the appropriate cap style within
        - *   that rectangle.
        - * - Four triangles per join between adjacent line segments, creating a quad on
        - *   either side of the join, perpendicular to the lines. A vertex shader will
        - *   discard the quad in the "elbow" of the join, and a fragment shader will
        - *   display the appropriate join style within the remaining quad.
        - *
        - * @private
        - * @chainable
        - */
        +   * Converts each line segment into the vertices and vertex attributes needed
        +   * to turn the line into a polygon on screen. This will include:
        +   * - Two triangles line segment to create a rectangle
        +   * - Two triangles per endpoint to create a stroke cap rectangle. A fragment
        +   *   shader is responsible for displaying the appropriate cap style within
        +   *   that rectangle.
        +   * - Four triangles per join between adjacent line segments, creating a quad on
        +   *   either side of the join, perpendicular to the lines. A vertex shader will
        +   *   discard the quad in the "elbow" of the join, and a fragment shader will
        +   *   display the appropriate join style within the remaining quad.
        +   *
        +   * @private
        +   * @chainable
        +   */
           _edgesToVertices() {
             this.lineVertices.clear();
             this.lineTangentsIn.clear();
        @@ -2019,88 +1400,89 @@ p5.Geometry = class Geometry {
               if (dirOK) {
                 this._addSegment(begin, end, fromColor, toColor, dir);
               }
        -
        -      if (i > 0 && prevEdge[1] === currEdge[0]) {
        -        if (!connected.has(currEdge[0])) {
        -          connected.add(currEdge[0]);
        -          potentialCaps.delete(currEdge[0]);
        -          // Add a join if this segment shares a vertex with the previous. Skip
        -          // actually adding join vertices if either the previous segment or this
        -          // one has a length of 0.
        -          //
        -          // Don't add a join if the tangents point in the same direction, which
        -          // would mean the edges line up exactly, and there is no need for a join.
        -          if (lastValidDir && dirOK && dir.dot(lastValidDir) < 1 - 1e-8) {
        -            this._addJoin(begin, lastValidDir, dir, fromColor);
        -          }
        -        }
        -      } else {
        -        // Start a new line
        -        if (dirOK && !connected.has(currEdge[0])) {
        -          const existingCap = potentialCaps.get(currEdge[0]);
        -          if (existingCap) {
        -            this._addJoin(
        -              begin,
        -              existingCap.dir,
        -              dir,
        -              fromColor
        -            );
        -            potentialCaps.delete(currEdge[0]);
        +      if (!this.renderer?._simpleLines) {
        +        if (i > 0 && prevEdge[1] === currEdge[0]) {
        +          if (!connected.has(currEdge[0])) {
                     connected.add(currEdge[0]);
        -          } else {
        -            potentialCaps.set(currEdge[0], {
        -              point: begin,
        -              dir: dir.copy().mult(-1),
        -              color: fromColor
        -            });
        +            potentialCaps.delete(currEdge[0]);
        +            // Add a join if this segment shares a vertex with the previous. Skip
        +            // actually adding join vertices if either the previous segment or this
        +            // one has a length of 0.
        +            //
        +            // Don't add a join if the tangents point in the same direction, which
        +            // would mean the edges line up exactly, and there is no need for a join.
        +            if (lastValidDir && dirOK && dir.dot(lastValidDir) < 1 - 1e-8) {
        +              this._addJoin(begin, lastValidDir, dir, fromColor);
        +            }
        +          }
        +        } else {
        +          // Start a new line
        +          if (dirOK && !connected.has(currEdge[0])) {
        +            const existingCap = potentialCaps.get(currEdge[0]);
        +            if (existingCap) {
        +              this._addJoin(
        +                begin,
        +                existingCap.dir,
        +                dir,
        +                fromColor
        +              );
        +              potentialCaps.delete(currEdge[0]);
        +              connected.add(currEdge[0]);
        +            } else {
        +              potentialCaps.set(currEdge[0], {
        +                point: begin,
        +                dir: dir.copy().mult(-1),
        +                color: fromColor
        +              });
        +            }
        +          }
        +          if (lastValidDir && !connected.has(prevEdge[1])) {
        +            const existingCap = potentialCaps.get(prevEdge[1]);
        +            if (existingCap) {
        +              this._addJoin(
        +                this.vertices[prevEdge[1]],
        +                lastValidDir,
        +                existingCap.dir.copy().mult(-1),
        +                prevColor
        +              );
        +              potentialCaps.delete(prevEdge[1]);
        +              connected.add(prevEdge[1]);
        +            } else {
        +              // Close off the last segment with a cap
        +              potentialCaps.set(prevEdge[1], {
        +                point: this.vertices[prevEdge[1]],
        +                dir: lastValidDir,
        +                color: prevColor
        +              });
        +            }
        +            lastValidDir = undefined;
                   }
                 }
        -        if (lastValidDir && !connected.has(prevEdge[1])) {
        -          const existingCap = potentialCaps.get(prevEdge[1]);
        +
        +        if (i === this.edges.length - 1 && !connected.has(currEdge[1])) {
        +          const existingCap = potentialCaps.get(currEdge[1]);
                   if (existingCap) {
                     this._addJoin(
        -              this.vertices[prevEdge[1]],
        -              lastValidDir,
        +              end,
        +              dir,
                       existingCap.dir.copy().mult(-1),
        -              prevColor
        +              toColor
                     );
        -            potentialCaps.delete(prevEdge[1]);
        -            connected.add(prevEdge[1]);
        +            potentialCaps.delete(currEdge[1]);
        +            connected.add(currEdge[1]);
                   } else {
        -            // Close off the last segment with a cap
        -            potentialCaps.set(prevEdge[1], {
        -              point: this.vertices[prevEdge[1]],
        -              dir: lastValidDir,
        -              color: prevColor
        +            potentialCaps.set(currEdge[1], {
        +              point: end,
        +              dir,
        +              color: toColor
                     });
                   }
        -          lastValidDir = undefined;
                 }
        -      }
         
        -      if (i === this.edges.length - 1 && !connected.has(currEdge[1])) {
        -        const existingCap = potentialCaps.get(currEdge[1]);
        -        if (existingCap) {
        -          this._addJoin(
        -            end,
        -            dir,
        -            existingCap.dir.copy().mult(-1),
        -            toColor
        -          );
        -          potentialCaps.delete(currEdge[1]);
        -          connected.add(currEdge[1]);
        -        } else {
        -          potentialCaps.set(currEdge[1], {
        -            point: end,
        -            dir,
        -            color: toColor
        -          });
        +        if (dirOK) {
        +          lastValidDir = dir;
                 }
               }
        -
        -      if (dirOK) {
        -        lastValidDir = dir;
        -      }
             }
             for (const { point, dir, color } of potentialCaps.values()) {
               this._addCap(point, dir, color);
        @@ -2109,21 +1491,21 @@ p5.Geometry = class Geometry {
           }
         
           /**
        - * Adds the vertices and vertex attributes for two triangles making a rectangle
        - * for a straight line segment. A vertex shader is responsible for picking
        - * proper coordinates on the screen given the centerline positions, the tangent,
        - * and the side of the centerline each vertex belongs to. Sides follow the
        - * following scheme:
        - *
        - *  -1            -1
        - *   o-------------o
        - *   |             |
        - *   o-------------o
        - *   1             1
        - *
        - * @private
        - * @chainable
        - */
        +   * Adds the vertices and vertex attributes for two triangles making a rectangle
        +   * for a straight line segment. A vertex shader is responsible for picking
        +   * proper coordinates on the screen given the centerline positions, the tangent,
        +   * and the side of the centerline each vertex belongs to. Sides follow the
        +   * following scheme:
        +   *
        +   *  -1            -1
        +   *   o-------------o
        +   *   |             |
        +   *   o-------------o
        +   *   1             1
        +   *
        +   * @private
        +   * @chainable
        +   */
           _addSegment(
             begin,
             end,
        @@ -2141,33 +1523,35 @@ p5.Geometry = class Geometry {
               }
             }
             this.lineVertices.push(...a, ...b, ...a, ...b, ...b, ...a);
        -    this.lineVertexColors.push(
        -      ...fromColor,
        -      ...toColor,
        -      ...fromColor,
        -      ...toColor,
        -      ...toColor,
        -      ...fromColor
        -    );
        +    if (!this.renderer?._simpleLines) {
        +      this.lineVertexColors.push(
        +        ...fromColor,
        +        ...toColor,
        +        ...fromColor,
        +        ...toColor,
        +        ...toColor,
        +        ...fromColor
        +      );
        +    }
             return this;
           }
         
           /**
        - * Adds the vertices and vertex attributes for two triangles representing the
        - * stroke cap of a line. A fragment shader is responsible for displaying the
        - * appropriate cap style within the rectangle they make.
        - *
        - * The lineSides buffer will include the following values for the points on
        - * the cap rectangle:
        - *
        - *           -1  -2
        - * -----------o---o
        - *            |   |
        - * -----------o---o
        - *            1   2
        - * @private
        - * @chainable
        - */
        +   * Adds the vertices and vertex attributes for two triangles representing the
        +   * stroke cap of a line. A fragment shader is responsible for displaying the
        +   * appropriate cap style within the rectangle they make.
        +   *
        +   * The lineSides buffer will include the following values for the points on
        +   * the cap rectangle:
        +   *
        +   *           -1  -2
        +   * -----------o---o
        +   *            |   |
        +   * -----------o---o
        +   *            1   2
        +   * @private
        +   * @chainable
        +   */
           _addCap(point, tangent, color) {
             const ptArray = point.array();
             const tanInArray = tangent.array();
        @@ -2183,28 +1567,28 @@ p5.Geometry = class Geometry {
           }
         
           /**
        - * Adds the vertices and vertex attributes for four triangles representing a
        - * join between two adjacent line segments. This creates a quad on either side
        - * of the shared vertex of the two line segments, with each quad perpendicular
        - * to the lines. A vertex shader will discard all but the quad in the "elbow" of
        - * the join, and a fragment shader will display the appropriate join style
        - * within the remaining quad.
        - *
        - * The lineSides buffer will include the following values for the points on
        - * the join rectangles:
        - *
        - *            -1     -2
        - * -------------o----o
        - *              |    |
        - *       1 o----o----o -3
        - *         |    | 0  |
        - * --------o----o    |
        - *        2|    3    |
        - *         |         |
        - *         |         |
        - * @private
        - * @chainable
        - */
        +   * Adds the vertices and vertex attributes for four triangles representing a
        +   * join between two adjacent line segments. This creates a quad on either side
        +   * of the shared vertex of the two line segments, with each quad perpendicular
        +   * to the lines. A vertex shader will discard all but the quad in the "elbow" of
        +   * the join, and a fragment shader will display the appropriate join style
        +   * within the remaining quad.
        +   *
        +   * The lineSides buffer will include the following values for the points on
        +   * the join rectangles:
        +   *
        +   *            -1     -2
        +   * -------------o----o
        +   *              |    |
        +   *       1 o----o----o -3
        +   *         |    | 0  |
        +   * --------o----o    |
        +   *        2|    3    |
        +   *         |         |
        +   *         |         |
        +   * @private
        +   * @chainable
        +   */
           _addJoin(
             point,
             fromTangent,
        @@ -2226,77 +1610,76 @@ p5.Geometry = class Geometry {
           }
         
           /**
        - * Transforms the geometry's vertices to fit snugly within a 100×100×100 box
        - * centered at the origin.
        - *
        - * Calling `myGeometry.normalize()` translates the geometry's vertices so that
        - * they're centered at the origin `(0, 0, 0)`. Then it scales the vertices so
        - * that they fill a 100×100×100 box. As a result, small geometries will grow
        - * and large geometries will shrink.
        - *
        - * Note: `myGeometry.normalize()` only works when called in the
        - * <a href="#/p5/setup">setup()</a> function.
        - *
        - * @method normalize
        - * @chainable
        - *
        - * @example
        - * <div>
        - * <code>
        - * let myGeometry;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a very small torus.
        - *   beginGeometry();
        - *   torus(1, 0.25);
        - *   myGeometry = endGeometry();
        - *
        - *   // Normalize the torus so its vertices fill
        - *   // the range [-100, 100].
        - *   myGeometry.normalize();
        - *
        - *   describe('A white torus rotates slowly against a dark gray background.');
        - * }
        - *
        - * function draw() {
        - *   background(50);
        - *
        - *   // Turn on the lights.
        - *   lights();
        - *
        - *   // Rotate around the y-axis.
        - *   rotateY(frameCount * 0.01);
        - *
        - *   // Style the torus.
        - *   noStroke();
        - *
        - *   // Draw the torus.
        - *   model(myGeometry);
        - * }
        - * </code>
        - * </div>
        - */
        -  normalize() {
        -    if (this.vertices.length > 0) {
        -      // Find the corners of our bounding box
        -      const maxPosition = this.vertices[0].copy();
        -      const minPosition = this.vertices[0].copy();
        -
        -      for (let i = 0; i < this.vertices.length; i++) {
        -        maxPosition.x = Math.max(maxPosition.x, this.vertices[i].x);
        -        minPosition.x = Math.min(minPosition.x, this.vertices[i].x);
        -        maxPosition.y = Math.max(maxPosition.y, this.vertices[i].y);
        -        minPosition.y = Math.min(minPosition.y, this.vertices[i].y);
        -        maxPosition.z = Math.max(maxPosition.z, this.vertices[i].z);
        -        minPosition.z = Math.min(minPosition.z, this.vertices[i].z);
        -      }
        -
        -      const center = p5.Vector.lerp(maxPosition, minPosition, 0.5);
        -      const dist = p5.Vector.sub(maxPosition, minPosition);
        -      const longestDist = Math.max(Math.max(dist.x, dist.y), dist.z);
        -      const scale = 200 / longestDist;
        +   * Transforms the geometry's vertices to fit snugly within a 100×100×100 box
        +   * centered at the origin.
        +   *
        +   * Calling `myGeometry.normalize()` translates the geometry's vertices so that
        +   * they're centered at the origin `(0, 0, 0)`. Then it scales the vertices so
        +   * that they fill a 100×100×100 box. As a result, small geometries will grow
        +   * and large geometries will shrink.
        +   *
        +   * Note: `myGeometry.normalize()` only works when called in the
        +   * <a href="#/p5/setup">setup()</a> function.
        +   *
        +   * @chainable
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a very small torus.
        +   *   beginGeometry();
        +   *   torus(1, 0.25);
        +   *   myGeometry = endGeometry();
        +   *
        +   *   // Normalize the torus so its vertices fill
        +   *   // the range [-100, 100].
        +   *   myGeometry.normalize();
        +   *
        +   *   describe('A white torus rotates slowly against a dark gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Rotate around the y-axis.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Style the torus.
        +   *   noStroke();
        +   *
        +   *   // Draw the torus.
        +   *   model(myGeometry);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  normalize() {
        +    if (this.vertices.length > 0) {
        +      // Find the corners of our bounding box
        +      const maxPosition = this.vertices[0].copy();
        +      const minPosition = this.vertices[0].copy();
        +
        +      for (let i = 0; i < this.vertices.length; i++) {
        +        maxPosition.x = Math.max(maxPosition.x, this.vertices[i].x);
        +        minPosition.x = Math.min(minPosition.x, this.vertices[i].x);
        +        maxPosition.y = Math.max(maxPosition.y, this.vertices[i].y);
        +        minPosition.y = Math.min(minPosition.y, this.vertices[i].y);
        +        maxPosition.z = Math.max(maxPosition.z, this.vertices[i].z);
        +        minPosition.z = Math.min(minPosition.z, this.vertices[i].z);
        +      }
        +
        +      const center = Vector.lerp(maxPosition, minPosition, 0.5);
        +      const dist = Vector.sub(maxPosition, minPosition);
        +      const longestDist = Math.max(Math.max(dist.x, dist.y), dist.z);
        +      const scale = 200 / longestDist;
         
               for (let i = 0; i < this.vertices.length; i++) {
                 this.vertices[i].sub(center);
        @@ -2305,6 +1688,820 @@ p5.Geometry = class Geometry {
             }
             return this;
           }
        +
        +  /** Sets the shader's vertex property or attribute variables.
        +   *
        +   * An vertex property or vertex attribute is a variable belonging to a vertex in a shader. p5.js provides some
        +   * default properties, such as `aPosition`, `aNormal`, `aVertexColor`, etc. These are
        +   * set using <a href="#/p5/vertex">vertex()</a>, <a href="#/p5/normal">normal()</a>
        +   * and <a href="#/p5/fill">fill()</a> respectively. Custom properties can also
        +   * be defined within <a href="#/p5/beginShape">beginShape()</a> and
        +   * <a href="#/p5/endShape">endShape()</a>.
        +   *
        +   * The first parameter, `propertyName`, is a string with the property's name.
        +   * This is the same variable name which should be declared in the shader, as in
        +   * `in vec3 aProperty`, similar to .`setUniform()`.
        +   *
        +   * The second parameter, `data`, is the value assigned to the shader variable. This value
        +   * will be pushed directly onto the Geometry object. There should be the same number
        +   * of custom property values as vertices, this method should be invoked once for each
        +   * vertex.
        +   *
        +   * The `data` can be a Number or an array of numbers. Tn the shader program the type
        +   * can be declared according to the WebGL specification. Common types include `float`,
        +   * `vec2`, `vec3`, `vec4` or matrices.
        +   *
        +   * See also the global <a href="#/p5/vertexProperty">vertexProperty()</a> function.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let geo;
        +   *
        +   * function cartesianToSpherical(x, y, z) {
        +   *   let r = sqrt(pow(x, x) + pow(y, y) + pow(z, z));
        +   *   let theta = acos(z / r);
        +   *   let phi = atan2(y, x);
        +   *   return { theta, phi };
        +   * }
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Modify the material shader to display roughness.
        +   *   const myShader = materialShader().modify({
        +   *     vertexDeclarations:`in float aRoughness;
        +   *                         out float vRoughness;`,
        +   *     fragmentDeclarations: 'in float vRoughness;',
        +   *     'void afterVertex': `() {
        +   *         vRoughness = aRoughness;
        +   *     }`,
        +   *     'vec4 combineColors': `(ColorComponents components) {
        +   *             vec4 color = vec4(0.);
        +   *             color.rgb += components.diffuse * components.baseColor * (1.0-vRoughness);
        +   *             color.rgb += components.ambient * components.ambientColor;
        +   *             color.rgb += components.specular * components.specularColor * (1.0-vRoughness);
        +   *             color.a = components.opacity;
        +   *             return color;
        +   *     }`
        +   *   });
        +   *
        +   *   // Create the Geometry object.
        +   *   beginGeometry();
        +   *   fill('hotpink');
        +   *   sphere(45, 50, 50);
        +   *   geo = endGeometry();
        +   *
        +   *   // Set the roughness value for every vertex.
        +   *   for (let v of geo.vertices){
        +   *
        +   *     // convert coordinates to spherical coordinates
        +   *     let spherical = cartesianToSpherical(v.x, v.y, v.z);
        +   *
        +   *     // Set the custom roughness vertex property.
        +   *     let roughness = noise(spherical.theta*5, spherical.phi*5);
        +   *     geo.vertexProperty('aRoughness', roughness);
        +   *   }
        +   *
        +   *   // Use the custom shader.
        +   *   shader(myShader);
        +   *
        +   *   describe('A rough pink sphere rotating on a blue background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Set some styles and lighting
        +   *   background('lightblue');
        +   *   noStroke();
        +   *
        +   *   specularMaterial(255,125,100);
        +   *   shininess(2);
        +   *
        +   *   directionalLight('white', -1, 1, -1);
        +   *   ambientLight(320);
        +   *
        +   *   rotateY(millis()*0.001);
        +   *
        +   *   // Draw the geometry
        +   *   model(geo);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @method vertexProperty
        +   * @param {String} propertyName the name of the vertex property.
        +   * @param {Number|Number[]} data the data tied to the vertex property.
        +   * @param {Number} [size] optional size of each unit of data.
        +   */
        +  vertexProperty(propertyName, data, size){
        +    let prop;
        +    if (!this.userVertexProperties[propertyName]){
        +      prop = this.userVertexProperties[propertyName] =
        +        this._userVertexPropertyHelper(propertyName, data, size);
        +    }
        +    prop = this.userVertexProperties[propertyName];
        +    if (size){
        +      prop.pushDirect(data);
        +    } else{
        +      prop.setCurrentData(data);
        +      prop.pushCurrentData();
        +    }
        +  }
        +
        +  _userVertexPropertyHelper(propertyName, data, size){
        +    const geometryInstance = this;
        +    const prop = this.userVertexProperties[propertyName] = {
        +      name: propertyName,
        +      dataSize: size ? size : data.length ? data.length : 1,
        +      geometry: geometryInstance,
        +      // Getters
        +      getName(){
        +        return this.name;
        +      },
        +      getCurrentData(){
        +        if (this.currentData === undefined) {
        +          this.currentData = new Array(this.getDataSize()).fill(0);
        +        }
        +        return this.currentData;
        +      },
        +      getDataSize() {
        +        return this.dataSize;
        +      },
        +      getSrcName() {
        +        const src = this.name.concat('Src');
        +        return src;
        +      },
        +      getDstName() {
        +        const dst = this.name.concat('Buffer');
        +        return dst;
        +      },
        +      getSrcArray() {
        +        const srcName = this.getSrcName();
        +        return this.geometry[srcName];
        +      },
        +      //Setters
        +      setCurrentData(data) {
        +        const size = data.length ? data.length : 1;
        +        // if (size != this.getDataSize()){
        +        //   p5._friendlyError(`Custom vertex property '${this.name}' has been set with various data sizes. You can change it's name, or if it was an accident, set '${this.name}' to have the same number of inputs each time!`, 'vertexProperty()');
        +        // }
        +        this.currentData = data;
        +      },
        +      // Utilities
        +      pushCurrentData(){
        +        const data = this.getCurrentData();
        +        this.pushDirect(data);
        +      },
        +      pushDirect(data) {
        +        if (data.length){
        +          this.getSrcArray().push(...data);
        +        } else{
        +          this.getSrcArray().push(data);
        +        }
        +      },
        +      resetSrcArray(){
        +        this.geometry[this.getSrcName()] = [];
        +      },
        +      delete() {
        +        const srcName = this.getSrcName();
        +        delete this.geometry[srcName];
        +        delete this;
        +      }
        +    };
        +    this[prop.getSrcName()] = [];
        +    return this.userVertexProperties[propertyName];
        +  }
         };
        -export default p5.Geometry;
         
        +function geometry(p5, fn){
        +  /**
        +   * A class to describe a 3D shape.
        +   *
        +   * Each `p5.Geometry` object represents a 3D shape as a set of connected
        +   * points called *vertices*. All 3D shapes are made by connecting vertices to
        +   * form triangles that are stitched together. Each triangular patch on the
        +   * geometry's surface is called a *face*. The geometry stores information
        +   * about its vertices and faces for use with effects such as lighting and
        +   * texture mapping.
        +   *
        +   * The first parameter, `detailX`, is optional. If a number is passed, as in
        +   * `new p5.Geometry(24)`, it sets the number of triangle subdivisions to use
        +   * along the geometry's x-axis. By default, `detailX` is 1.
        +   *
        +   * The second parameter, `detailY`, is also optional. If a number is passed,
        +   * as in `new p5.Geometry(24, 16)`, it sets the number of triangle
        +   * subdivisions to use along the geometry's y-axis. By default, `detailX` is
        +   * 1.
        +   *
        +   * The third parameter, `callback`, is also optional. If a function is passed,
        +   * as in `new p5.Geometry(24, 16, createShape)`, it will be called once to add
        +   * vertices to the new 3D shape.
        +   *
        +   * @class p5.Geometry
        +   * @param  {Integer} [detailX] number of vertices along the x-axis.
        +   * @param  {Integer} [detailY] number of vertices along the y-axis.
        +   * @param {function} [callback] function to call once the geometry is created.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Geometry object.
        +   *   myGeometry = new p5.Geometry();
        +   *
        +   *   // Create p5.Vector objects to position the vertices.
        +   *   let v0 = createVector(-40, 0, 0);
        +   *   let v1 = createVector(0, -40, 0);
        +   *   let v2 = createVector(40, 0, 0);
        +   *
        +   *   // Add the vertices to the p5.Geometry object's vertices array.
        +   *   myGeometry.vertices.push(v0, v1, v2);
        +   *
        +   *   describe('A white triangle drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the p5.Geometry object.
        +   *   model(myGeometry);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Geometry object using a callback function.
        +   *   myGeometry = new p5.Geometry(1, 1, createShape);
        +   *
        +   *   describe('A white triangle drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the p5.Geometry object.
        +   *   model(myGeometry);
        +   * }
        +   *
        +   * function createShape() {
        +   *   // Create p5.Vector objects to position the vertices.
        +   *   let v0 = createVector(-40, 0, 0);
        +   *   let v1 = createVector(0, -40, 0);
        +   *   let v2 = createVector(40, 0, 0);
        +   *
        +   *   // "this" refers to the p5.Geometry object being created.
        +   *
        +   *   // Add the vertices to the p5.Geometry object's vertices array.
        +   *   this.vertices.push(v0, v1, v2);
        +   *
        +   *   // Add an array to list which vertices belong to the face.
        +   *   // Vertices are listed in clockwise "winding" order from
        +   *   // left to top to right.
        +   *   this.faces.push([0, 1, 2]);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Geometry object using a callback function.
        +   *   myGeometry = new p5.Geometry(1, 1, createShape);
        +   *
        +   *   describe('A white triangle drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the p5.Geometry object.
        +   *   model(myGeometry);
        +   * }
        +   *
        +   * function createShape() {
        +   *   // Create p5.Vector objects to position the vertices.
        +   *   let v0 = createVector(-40, 0, 0);
        +   *   let v1 = createVector(0, -40, 0);
        +   *   let v2 = createVector(40, 0, 0);
        +   *
        +   *   // "this" refers to the p5.Geometry object being created.
        +   *
        +   *   // Add the vertices to the p5.Geometry object's vertices array.
        +   *   this.vertices.push(v0, v1, v2);
        +   *
        +   *   // Add an array to list which vertices belong to the face.
        +   *   // Vertices are listed in clockwise "winding" order from
        +   *   // left to top to right.
        +   *   this.faces.push([0, 1, 2]);
        +   *
        +   *   // Compute the surface normals to help with lighting.
        +   *   this.computeNormals();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * // Adapted from Paul Wheeler's wonderful p5.Geometry tutorial.
        +   * // https://www.paulwheeler.us/articles/custom-3d-geometry-in-p5js/
        +   * // CC-BY-SA 4.0
        +   *
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create the p5.Geometry object.
        +   *   // Set detailX to 48 and detailY to 2.
        +   *   // >>> try changing them.
        +   *   myGeometry = new p5.Geometry(48, 2, createShape);
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the p5.Geometry object.
        +   *   strokeWeight(0.2);
        +   *
        +   *   // Draw the p5.Geometry object.
        +   *   model(myGeometry);
        +   * }
        +   *
        +   * function createShape() {
        +   *   // "this" refers to the p5.Geometry object being created.
        +   *
        +   *   // Define the Möbius strip with a few parameters.
        +   *   let spread = 0.1;
        +   *   let radius = 30;
        +   *   let stripWidth = 15;
        +   *   let xInterval = 4 * PI / this.detailX;
        +   *   let yOffset = -stripWidth / 2;
        +   *   let yInterval = stripWidth / this.detailY;
        +   *
        +   *   for (let j = 0; j <= this.detailY; j += 1) {
        +   *     // Calculate the "vertical" point along the strip.
        +   *     let v = yOffset + yInterval * j;
        +   *
        +   *     for (let i = 0; i <= this.detailX; i += 1) {
        +   *       // Calculate the angle of rotation around the strip.
        +   *       let u = i * xInterval;
        +   *
        +   *       // Calculate the coordinates of the vertex.
        +   *       let x = (radius + v * cos(u / 2)) * cos(u) - sin(u / 2) * 2 * spread;
        +   *       let y = (radius + v * cos(u / 2)) * sin(u);
        +   *       if (u < TWO_PI) {
        +   *         y += sin(u) * spread;
        +   *       } else {
        +   *         y -= sin(u) * spread;
        +   *       }
        +   *       let z = v * sin(u / 2) + sin(u / 4) * 4 * spread;
        +   *
        +   *       // Create a p5.Vector object to position the vertex.
        +   *       let vert = createVector(x, y, z);
        +   *
        +   *       // Add the vertex to the p5.Geometry object's vertices array.
        +   *       this.vertices.push(vert);
        +   *     }
        +   *   }
        +   *
        +   *   // Compute the faces array.
        +   *   this.computeFaces();
        +   *
        +   *   // Compute the surface normals to help with lighting.
        +   *   this.computeNormals();
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  p5.Geometry = Geometry;
        +
        +  /**
        +   * An array with the geometry's vertices.
        +   *
        +   * The geometry's vertices are stored as
        +   * <a href="#/p5.Vector">p5.Vector</a> objects in the `myGeometry.vertices`
        +   * array. The geometry's first vertex is the
        +   * <a href="#/p5.Vector">p5.Vector</a> object at `myGeometry.vertices[0]`,
        +   * its second vertex is `myGeometry.vertices[1]`, its third vertex is
        +   * `myGeometry.vertices[2]`, and so on.
        +   *
        +   * @property vertices
        +   * @for p5.Geometry
        +   * @name vertices
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Geometry object.
        +   *   myGeometry = new p5.Geometry();
        +   *
        +   *   // Create p5.Vector objects to position the vertices.
        +   *   let v0 = createVector(-40, 0, 0);
        +   *   let v1 = createVector(0, -40, 0);
        +   *   let v2 = createVector(40, 0, 0);
        +   *
        +   *   // Add the vertices to the p5.Geometry object's vertices array.
        +   *   myGeometry.vertices.push(v0, v1, v2);
        +   *
        +   *   describe('A white triangle drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Draw the p5.Geometry object.
        +   *   model(myGeometry);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Geometry object.
        +   *   beginGeometry();
        +   *   torus(30, 15, 10, 8);
        +   *   myGeometry = endGeometry();
        +   *
        +   *   describe('A white torus rotates slowly against a dark gray background. Red spheres mark its vertices.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Style the p5.Geometry object.
        +   *   fill(255);
        +   *   stroke(0);
        +   *
        +   *   // Display the p5.Geometry object.
        +   *   model(myGeometry);
        +   *
        +   *   // Style the vertices.
        +   *   fill(255, 0, 0);
        +   *   noStroke();
        +   *
        +   *   // Iterate over the vertices array.
        +   *   for (let v of myGeometry.vertices) {
        +   *     // Draw a sphere to mark the vertex.
        +   *     push();
        +   *     translate(v);
        +   *     sphere(2.5);
        +   *     pop();
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * An array with the vectors that are normal to the geometry's vertices.
        +   *
        +   * A face's orientation is defined by its *normal vector* which points out
        +   * of the face and is normal (perpendicular) to the surface. Calling
        +   * `myGeometry.computeNormals()` first calculates each face's normal
        +   * vector. Then it calculates the normal vector for each vertex by
        +   * averaging the normal vectors of the faces surrounding the vertex. The
        +   * vertex normals are stored as <a href="#/p5.Vector">p5.Vector</a>
        +   * objects in the `myGeometry.vertexNormals` array.
        +   *
        +   * @property vertexNormals
        +   * @name vertexNormals
        +   * @for p5.Geometry
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Geometry object.
        +   *   beginGeometry();
        +   *   torus(30, 15, 10, 8);
        +   *   myGeometry = endGeometry();
        +   *
        +   *   // Compute the vertex normals.
        +   *   myGeometry.computeNormals();
        +   *
        +   *   describe(
        +   *     'A white torus rotates against a dark gray background. Red lines extend outward from its vertices.'
        +   *   );
        +   * }
        +   *
        +   * function draw() {
        +   *   background(50);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Rotate the coordinate system.
        +   *   rotateY(frameCount * 0.01);
        +   *
        +   *   // Style the p5.Geometry object.
        +   *   stroke(0);
        +   *
        +   *   // Display the p5.Geometry object.
        +   *   model(myGeometry);
        +   *
        +   *   // Style the normal vectors.
        +   *   stroke(255, 0, 0);
        +   *
        +   *   // Iterate over the vertices and vertexNormals arrays.
        +   *   for (let i = 0; i < myGeometry.vertices.length; i += 1) {
        +   *
        +   *     // Get the vertex p5.Vector object.
        +   *     let v = myGeometry.vertices[i];
        +   *
        +   *     // Get the vertex normal p5.Vector object.
        +   *     let n = myGeometry.vertexNormals[i];
        +   *
        +   *     // Calculate a point along the vertex normal.
        +   *     let p = p5.Vector.mult(n, 8);
        +   *
        +   *     // Draw the vertex normal as a red line.
        +   *     push();
        +   *     translate(v);
        +   *     line(0, 0, 0, p.x, p.y, p.z);
        +   *     pop();
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Geometry object.
        +   *   myGeometry = new p5.Geometry();
        +   *
        +   *   // Create p5.Vector objects to position the vertices.
        +   *   let v0 = createVector(-40, 0, 0);
        +   *   let v1 = createVector(0, -40, 0);
        +   *   let v2 = createVector(0, 40, 0);
        +   *   let v3 = createVector(40, 0, 0);
        +   *
        +   *   // Add the vertices to the p5.Geometry object's vertices array.
        +   *   myGeometry.vertices.push(v0, v1, v2, v3);
        +   *
        +   *   // Compute the faces array.
        +   *   myGeometry.computeFaces();
        +   *
        +   *   // Compute the surface normals.
        +   *   myGeometry.computeNormals();
        +   *
        +   *   describe('A red square drawn on a gray background.');
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Add a white point light.
        +   *   pointLight(255, 255, 255, 0, 0, 10);
        +   *
        +   *   // Style the p5.Geometry object.
        +   *   noStroke();
        +   *   fill(255, 0, 0);
        +   *
        +   *   // Display the p5.Geometry object.
        +   *   model(myGeometry);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * An array that lists which of the geometry's vertices form each of its
        +   * faces.
        +   *
        +   * All 3D shapes are made by connecting sets of points called *vertices*. A
        +   * geometry's surface is formed by connecting vertices to form triangles
        +   * that are stitched together. Each triangular patch on the geometry's
        +   * surface is called a *face*.
        +   *
        +   * The geometry's vertices are stored as
        +   * <a href="#/p5.Vector">p5.Vector</a> objects in the
        +   * <a href="#/p5.Geometry/vertices">myGeometry.vertices</a> array. The
        +   * geometry's first vertex is the <a href="#/p5.Vector">p5.Vector</a>
        +   * object at `myGeometry.vertices[0]`, its second vertex is
        +   * `myGeometry.vertices[1]`, its third vertex is `myGeometry.vertices[2]`,
        +   * and so on.
        +   *
        +   * For example, a geometry made from a rectangle has two faces because a
        +   * rectangle is made by joining two triangles. `myGeometry.faces` for a
        +   * rectangle would be the two-dimensional array `[[0, 1, 2], [2, 1, 3]]`.
        +   * The first face, `myGeometry.faces[0]`, is the array `[0, 1, 2]` because
        +   * it's formed by connecting `myGeometry.vertices[0]`,
        +   * `myGeometry.vertices[1]`,and `myGeometry.vertices[2]`. The second face,
        +   * `myGeometry.faces[1]`, is the array `[2, 1, 3]` because it's formed by
        +   * connecting `myGeometry.vertices[2]`, `myGeometry.vertices[1]`,and
        +   * `myGeometry.vertices[3]`.
        +   *
        +   * @property faces
        +   * @name faces
        +   * @for p5.Geometry
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Click and drag the mouse to view the scene from different angles.
        +   *
        +   * let myGeometry;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Geometry object.
        +   *   beginGeometry();
        +   *   sphere();
        +   *   myGeometry = endGeometry();
        +   *
        +   *   describe("A sphere drawn on a gray background. The sphere's surface is a grayscale patchwork of triangles.");
        +   * }
        +   *
        +   * function draw() {
        +   *   background(200);
        +   *
        +   *   // Enable orbiting with the mouse.
        +   *   orbitControl();
        +   *
        +   *   // Turn on the lights.
        +   *   lights();
        +   *
        +   *   // Style the p5.Geometry object.
        +   *   noStroke();
        +   *
        +   *   // Set a random seed.
        +   *   randomSeed(1234);
        +   *
        +   *   // Iterate over the faces array.
        +   *   for (let face of myGeometry.faces) {
        +   *
        +   *     // Style the face.
        +   *     let g = random(0, 255);
        +   *     fill(g);
        +   *
        +   *     // Draw the face.
        +   *     beginShape();
        +   *     // Iterate over the vertices that form the face.
        +   *     for (let f of face) {
        +   *       // Get the vertex's p5.Vector object.
        +   *       let v = myGeometry.vertices[f];
        +   *       vertex(v.x, v.y, v.z);
        +   *     }
        +   *     endShape();
        +   *
        +   *   }
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +
        +  /**
        +   * An array that lists the texture coordinates for each of the geometry's
        +   * vertices.
        +   *
        +   * In order for <a href="#/p5/texture">texture()</a> to work, the geometry
        +   * needs a way to map the points on its surface to the pixels in a
        +   * rectangular image that's used as a texture. The geometry's vertex at
        +   * coordinates `(x, y, z)` maps to the texture image's pixel at coordinates
        +   * `(u, v)`.
        +   *
        +   * The `myGeometry.uvs` array stores the `(u, v)` coordinates for each
        +   * vertex in the order it was added to the geometry. For example, the
        +   * first vertex, `myGeometry.vertices[0]`, has its `(u, v)` coordinates
        +   * stored at `myGeometry.uvs[0]` and `myGeometry.uvs[1]`.
        +   *
        +   * @property uvs
        +   * @name uvs
        +   * @for p5.Geometry
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * let img;
        +   *
        +   * async function setup() {
        +   *   img = await loadImage('assets/laDefense.jpg');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   background(200);
        +   *
        +   *   // Create p5.Geometry objects.
        +   *   let geom1 = buildGeometry(createShape);
        +   *   let geom2 = buildGeometry(createShape);
        +   *
        +   *   // Left (original).
        +   *   push();
        +   *   translate(-25, 0, 0);
        +   *   texture(img);
        +   *   noStroke();
        +   *   model(geom1);
        +   *   pop();
        +   *
        +   *   // Set geom2's texture coordinates.
        +   *   geom2.uvs = [0.25, 0.25, 0.75, 0.25, 0.25, 0.75, 0.75, 0.75];
        +   *
        +   *   // Right (zoomed in).
        +   *   push();
        +   *   translate(25, 0, 0);
        +   *   texture(img);
        +   *   noStroke();
        +   *   model(geom2);
        +   *   pop();
        +   *
        +   *   describe(
        +   *     'Two photos of a ceiling on a gray background. The photo on the right zooms in to the center of the photo.'
        +   *   );
        +   * }
        +   *
        +   * function createShape() {
        +   *   plane(40);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +}
        +
        +export default geometry;
        +export { Geometry };
        +
        +if(typeof p5 !== 'undefined'){
        +  geometry(p5, p5.prototype);
        +}
        diff --git a/src/webgl/p5.Matrix.js b/src/webgl/p5.Matrix.js
        deleted file mode 100644
        index da664bd9ec..0000000000
        --- a/src/webgl/p5.Matrix.js
        +++ /dev/null
        @@ -1,1008 +0,0 @@
        -/**
        - * @requires constants
        - * @todo see methods below needing further implementation.
        - * future consideration: implement SIMD optimizations
        - * when browser compatibility becomes available
        - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/
        - *   Reference/Global_Objects/SIMD
        - */
        -
        -import p5 from '../core/main';
        -
        -let GLMAT_ARRAY_TYPE = Array;
        -let isMatrixArray = x => Array.isArray(x);
        -if (typeof Float32Array !== 'undefined') {
        -  GLMAT_ARRAY_TYPE = Float32Array;
        -  isMatrixArray = x => Array.isArray(x) || x instanceof Float32Array;
        -}
        -
        -/**
        - * A class to describe a 4×4 matrix
        - * for model and view matrix manipulation in the p5js webgl renderer.
        - * @class p5.Matrix
        - * @private
        - * @constructor
        - * @param {Array} [mat4] column-major array literal of our 4×4 matrix
        - */
        -p5.Matrix = class {
        -  constructor(...args){
        -
        -    // This is default behavior when object
        -    // instantiated using createMatrix()
        -    // @todo implement createMatrix() in core/math.js
        -    if (args.length && args[args.length - 1] instanceof p5) {
        -      this.p5 = args[args.length - 1];
        -    }
        -
        -    if (args[0] === 'mat3') {
        -      this.mat3 = Array.isArray(args[1])
        -        ? args[1]
        -        : new GLMAT_ARRAY_TYPE([1, 0, 0, 0, 1, 0, 0, 0, 1]);
        -    } else {
        -      this.mat4 = Array.isArray(args[0])
        -        ? args[0]
        -        : new GLMAT_ARRAY_TYPE(
        -          [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
        -    }
        -    return this;
        -  }
        -
        -  reset() {
        -    if (this.mat3) {
        -      this.mat3.set([1, 0, 0, 0, 1, 0, 0, 0, 1]);
        -    } else if (this.mat4) {
        -      this.mat4.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
        -    }
        -    return this;
        -  }
        -
        -  /**
        - * Replace the entire contents of a 4x4 matrix.
        - * If providing an array or a p5.Matrix, the values will be copied without
        - * referencing the source object.
        - * Can also provide 16 numbers as individual arguments.
        - *
        - * @method set
        - * @param {p5.Matrix|Float32Array|Number[]} [inMatrix] the input p5.Matrix or
        - *                                     an Array of length 16
        - * @chainable
        - */
        -  /**
        - * @method set
        - * @param {Number[]} elements 16 numbers passed by value to avoid
        - *                                     array copying.
        - * @chainable
        - */
        -  set(inMatrix) {
        -    let refArray = arguments;
        -    if (inMatrix instanceof p5.Matrix) {
        -      refArray = inMatrix.mat4;
        -    } else if (isMatrixArray(inMatrix)) {
        -      refArray = inMatrix;
        -    }
        -    if (refArray.length !== 16) {
        -      p5._friendlyError(
        -        `Expected 16 values but received ${refArray.length}.`,
        -        'p5.Matrix.set'
        -      );
        -      return this;
        -    }
        -    for (let i = 0; i < 16; i++) {
        -      this.mat4[i] = refArray[i];
        -    }
        -    return this;
        -  }
        -
        -  /**
        - * Gets a copy of the vector, returns a p5.Matrix object.
        - *
        - * @method get
        - * @return {p5.Matrix} the copy of the p5.Matrix object
        - */
        -  get() {
        -    return new p5.Matrix(this.mat4, this.p5);
        -  }
        -
        -  /**
        - * return a copy of this matrix.
        - * If this matrix is 4x4, a 4x4 matrix with exactly the same entries will be
        - * generated. The same is true if this matrix is 3x3.
        - *
        - * @method copy
        - * @return {p5.Matrix}   the result matrix
        - */
        -  copy() {
        -    if (this.mat3 !== undefined) {
        -      const copied3x3 = new p5.Matrix('mat3', this.p5);
        -      copied3x3.mat3[0] = this.mat3[0];
        -      copied3x3.mat3[1] = this.mat3[1];
        -      copied3x3.mat3[2] = this.mat3[2];
        -      copied3x3.mat3[3] = this.mat3[3];
        -      copied3x3.mat3[4] = this.mat3[4];
        -      copied3x3.mat3[5] = this.mat3[5];
        -      copied3x3.mat3[6] = this.mat3[6];
        -      copied3x3.mat3[7] = this.mat3[7];
        -      copied3x3.mat3[8] = this.mat3[8];
        -      return copied3x3;
        -    }
        -    const copied = new p5.Matrix(this.p5);
        -    copied.mat4[0] = this.mat4[0];
        -    copied.mat4[1] = this.mat4[1];
        -    copied.mat4[2] = this.mat4[2];
        -    copied.mat4[3] = this.mat4[3];
        -    copied.mat4[4] = this.mat4[4];
        -    copied.mat4[5] = this.mat4[5];
        -    copied.mat4[6] = this.mat4[6];
        -    copied.mat4[7] = this.mat4[7];
        -    copied.mat4[8] = this.mat4[8];
        -    copied.mat4[9] = this.mat4[9];
        -    copied.mat4[10] = this.mat4[10];
        -    copied.mat4[11] = this.mat4[11];
        -    copied.mat4[12] = this.mat4[12];
        -    copied.mat4[13] = this.mat4[13];
        -    copied.mat4[14] = this.mat4[14];
        -    copied.mat4[15] = this.mat4[15];
        -    return copied;
        -  }
        -
        -  /**
        - * return an identity matrix
        - * @method identity
        - * @return {p5.Matrix}   the result matrix
        - */
        -  static identity(pInst){
        -    return new p5.Matrix(pInst);
        -  }
        -
        -  /**
        - * transpose according to a given matrix
        - * @method transpose
        - * @param  {p5.Matrix|Float32Array|Number[]} a  the matrix to be
        - *                                               based on to transpose
        - * @chainable
        - */
        -  transpose(a) {
        -    let a01, a02, a03, a12, a13, a23;
        -    if (a instanceof p5.Matrix) {
        -      a01 = a.mat4[1];
        -      a02 = a.mat4[2];
        -      a03 = a.mat4[3];
        -      a12 = a.mat4[6];
        -      a13 = a.mat4[7];
        -      a23 = a.mat4[11];
        -
        -      this.mat4[0] = a.mat4[0];
        -      this.mat4[1] = a.mat4[4];
        -      this.mat4[2] = a.mat4[8];
        -      this.mat4[3] = a.mat4[12];
        -      this.mat4[4] = a01;
        -      this.mat4[5] = a.mat4[5];
        -      this.mat4[6] = a.mat4[9];
        -      this.mat4[7] = a.mat4[13];
        -      this.mat4[8] = a02;
        -      this.mat4[9] = a12;
        -      this.mat4[10] = a.mat4[10];
        -      this.mat4[11] = a.mat4[14];
        -      this.mat4[12] = a03;
        -      this.mat4[13] = a13;
        -      this.mat4[14] = a23;
        -      this.mat4[15] = a.mat4[15];
        -    } else if (isMatrixArray(a)) {
        -      a01 = a[1];
        -      a02 = a[2];
        -      a03 = a[3];
        -      a12 = a[6];
        -      a13 = a[7];
        -      a23 = a[11];
        -
        -      this.mat4[0] = a[0];
        -      this.mat4[1] = a[4];
        -      this.mat4[2] = a[8];
        -      this.mat4[3] = a[12];
        -      this.mat4[4] = a01;
        -      this.mat4[5] = a[5];
        -      this.mat4[6] = a[9];
        -      this.mat4[7] = a[13];
        -      this.mat4[8] = a02;
        -      this.mat4[9] = a12;
        -      this.mat4[10] = a[10];
        -      this.mat4[11] = a[14];
        -      this.mat4[12] = a03;
        -      this.mat4[13] = a13;
        -      this.mat4[14] = a23;
        -      this.mat4[15] = a[15];
        -    }
        -    return this;
        -  }
        -
        -  /**
        - * invert  matrix according to a give matrix
        - * @method invert
        - * @param  {p5.Matrix|Float32Array|Number[]} a   the matrix to be
        - *                                                based on to invert
        - * @chainable
        - */
        -  invert(a) {
        -    let a00, a01, a02, a03, a10, a11, a12, a13;
        -    let a20, a21, a22, a23, a30, a31, a32, a33;
        -    if (a instanceof p5.Matrix) {
        -      a00 = a.mat4[0];
        -      a01 = a.mat4[1];
        -      a02 = a.mat4[2];
        -      a03 = a.mat4[3];
        -      a10 = a.mat4[4];
        -      a11 = a.mat4[5];
        -      a12 = a.mat4[6];
        -      a13 = a.mat4[7];
        -      a20 = a.mat4[8];
        -      a21 = a.mat4[9];
        -      a22 = a.mat4[10];
        -      a23 = a.mat4[11];
        -      a30 = a.mat4[12];
        -      a31 = a.mat4[13];
        -      a32 = a.mat4[14];
        -      a33 = a.mat4[15];
        -    } else if (isMatrixArray(a)) {
        -      a00 = a[0];
        -      a01 = a[1];
        -      a02 = a[2];
        -      a03 = a[3];
        -      a10 = a[4];
        -      a11 = a[5];
        -      a12 = a[6];
        -      a13 = a[7];
        -      a20 = a[8];
        -      a21 = a[9];
        -      a22 = a[10];
        -      a23 = a[11];
        -      a30 = a[12];
        -      a31 = a[13];
        -      a32 = a[14];
        -      a33 = a[15];
        -    }
        -    const b00 = a00 * a11 - a01 * a10;
        -    const b01 = a00 * a12 - a02 * a10;
        -    const b02 = a00 * a13 - a03 * a10;
        -    const b03 = a01 * a12 - a02 * a11;
        -    const b04 = a01 * a13 - a03 * a11;
        -    const b05 = a02 * a13 - a03 * a12;
        -    const b06 = a20 * a31 - a21 * a30;
        -    const b07 = a20 * a32 - a22 * a30;
        -    const b08 = a20 * a33 - a23 * a30;
        -    const b09 = a21 * a32 - a22 * a31;
        -    const b10 = a21 * a33 - a23 * a31;
        -    const b11 = a22 * a33 - a23 * a32;
        -
        -    // Calculate the determinant
        -    let det =
        -    b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
        -
        -    if (!det) {
        -      return null;
        -    }
        -    det = 1.0 / det;
        -
        -    this.mat4[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
        -    this.mat4[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
        -    this.mat4[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
        -    this.mat4[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
        -    this.mat4[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
        -    this.mat4[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
        -    this.mat4[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
        -    this.mat4[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
        -    this.mat4[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
        -    this.mat4[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
        -    this.mat4[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
        -    this.mat4[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
        -    this.mat4[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
        -    this.mat4[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
        -    this.mat4[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
        -    this.mat4[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
        -
        -    return this;
        -  }
        -
        -  /**
        - * Inverts a 3×3 matrix
        - * @method invert3x3
        - * @chainable
        - */
        -  invert3x3() {
        -    const a00 = this.mat3[0];
        -    const a01 = this.mat3[1];
        -    const a02 = this.mat3[2];
        -    const a10 = this.mat3[3];
        -    const a11 = this.mat3[4];
        -    const a12 = this.mat3[5];
        -    const a20 = this.mat3[6];
        -    const a21 = this.mat3[7];
        -    const a22 = this.mat3[8];
        -    const b01 = a22 * a11 - a12 * a21;
        -    const b11 = -a22 * a10 + a12 * a20;
        -    const b21 = a21 * a10 - a11 * a20;
        -
        -    // Calculate the determinant
        -    let det = a00 * b01 + a01 * b11 + a02 * b21;
        -    if (!det) {
        -      return null;
        -    }
        -    det = 1.0 / det;
        -    this.mat3[0] = b01 * det;
        -    this.mat3[1] = (-a22 * a01 + a02 * a21) * det;
        -    this.mat3[2] = (a12 * a01 - a02 * a11) * det;
        -    this.mat3[3] = b11 * det;
        -    this.mat3[4] = (a22 * a00 - a02 * a20) * det;
        -    this.mat3[5] = (-a12 * a00 + a02 * a10) * det;
        -    this.mat3[6] = b21 * det;
        -    this.mat3[7] = (-a21 * a00 + a01 * a20) * det;
        -    this.mat3[8] = (a11 * a00 - a01 * a10) * det;
        -    return this;
        -  }
        -
        -  /**
        - * This function is only for 3x3 matrices.
        - * transposes a 3×3 p5.Matrix by a mat3
        - * If there is an array of arguments, the matrix obtained by transposing
        - * the 3x3 matrix generated based on that array is set.
        - * If no arguments, it transposes itself and returns it.
        - *
        - * @method transpose3x3
        - * @param  {Number[]} mat3 1-dimensional array
        - * @chainable
        - */
        -  transpose3x3(mat3) {
        -    if (mat3 === undefined) {
        -      mat3 = this.mat3;
        -    }
        -    const a01 = mat3[1];
        -    const a02 = mat3[2];
        -    const a12 = mat3[5];
        -    this.mat3[0] = mat3[0];
        -    this.mat3[1] = mat3[3];
        -    this.mat3[2] = mat3[6];
        -    this.mat3[3] = a01;
        -    this.mat3[4] = mat3[4];
        -    this.mat3[5] = mat3[7];
        -    this.mat3[6] = a02;
        -    this.mat3[7] = a12;
        -    this.mat3[8] = mat3[8];
        -
        -    return this;
        -  }
        -
        -  /**
        - * converts a 4×4 matrix to its 3×3 inverse transform
        - * commonly used in MVMatrix to NMatrix conversions.
        - * @method invertTranspose
        - * @param  {p5.Matrix} mat4 the matrix to be based on to invert
        - * @chainable
        - * @todo  finish implementation
        - */
        -  inverseTranspose({ mat4 }) {
        -    if (this.mat3 === undefined) {
        -      p5._friendlyError('sorry, this function only works with mat3');
        -    } else {
        -    //convert mat4 -> mat3
        -      this.mat3[0] = mat4[0];
        -      this.mat3[1] = mat4[1];
        -      this.mat3[2] = mat4[2];
        -      this.mat3[3] = mat4[4];
        -      this.mat3[4] = mat4[5];
        -      this.mat3[5] = mat4[6];
        -      this.mat3[6] = mat4[8];
        -      this.mat3[7] = mat4[9];
        -      this.mat3[8] = mat4[10];
        -    }
        -
        -    const inverse = this.invert3x3();
        -    // check inverse succeeded
        -    if (inverse) {
        -      inverse.transpose3x3(this.mat3);
        -    } else {
        -    // in case of singularity, just zero the matrix
        -      for (let i = 0; i < 9; i++) {
        -        this.mat3[i] = 0;
        -      }
        -    }
        -    return this;
        -  }
        -
        -  /**
        - * inspired by Toji's mat4 determinant
        - * @method determinant
        - * @return {Number} Determinant of our 4×4 matrix
        - */
        -  determinant() {
        -    const d00 = this.mat4[0] * this.mat4[5] - this.mat4[1] * this.mat4[4],
        -      d01 = this.mat4[0] * this.mat4[6] - this.mat4[2] * this.mat4[4],
        -      d02 = this.mat4[0] * this.mat4[7] - this.mat4[3] * this.mat4[4],
        -      d03 = this.mat4[1] * this.mat4[6] - this.mat4[2] * this.mat4[5],
        -      d04 = this.mat4[1] * this.mat4[7] - this.mat4[3] * this.mat4[5],
        -      d05 = this.mat4[2] * this.mat4[7] - this.mat4[3] * this.mat4[6],
        -      d06 = this.mat4[8] * this.mat4[13] - this.mat4[9] * this.mat4[12],
        -      d07 = this.mat4[8] * this.mat4[14] - this.mat4[10] * this.mat4[12],
        -      d08 = this.mat4[8] * this.mat4[15] - this.mat4[11] * this.mat4[12],
        -      d09 = this.mat4[9] * this.mat4[14] - this.mat4[10] * this.mat4[13],
        -      d10 = this.mat4[9] * this.mat4[15] - this.mat4[11] * this.mat4[13],
        -      d11 = this.mat4[10] * this.mat4[15] - this.mat4[11] * this.mat4[14];
        -
        -    // Calculate the determinant
        -    return d00 * d11 - d01 * d10 + d02 * d09 +
        -    d03 * d08 - d04 * d07 + d05 * d06;
        -  }
        -
        -  /**
        - * multiply two mat4s
        - * @method mult
        - * @param {p5.Matrix|Float32Array|Number[]} multMatrix The matrix
        - *                                                we want to multiply by
        - * @chainable
        - */
        -  mult(multMatrix) {
        -    let _src;
        -
        -    if (multMatrix === this || multMatrix === this.mat4) {
        -      _src = this.copy().mat4; // only need to allocate in this rare case
        -    } else if (multMatrix instanceof p5.Matrix) {
        -      _src = multMatrix.mat4;
        -    } else if (isMatrixArray(multMatrix)) {
        -      _src = multMatrix;
        -    } else if (arguments.length === 16) {
        -      _src = arguments;
        -    } else {
        -      return; // nothing to do.
        -    }
        -
        -    // each row is used for the multiplier
        -    let b0 = this.mat4[0],
        -      b1 = this.mat4[1],
        -      b2 = this.mat4[2],
        -      b3 = this.mat4[3];
        -    this.mat4[0] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12];
        -    this.mat4[1] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13];
        -    this.mat4[2] = b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14];
        -    this.mat4[3] = b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15];
        -
        -    b0 = this.mat4[4];
        -    b1 = this.mat4[5];
        -    b2 = this.mat4[6];
        -    b3 = this.mat4[7];
        -    this.mat4[4] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12];
        -    this.mat4[5] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13];
        -    this.mat4[6] = b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14];
        -    this.mat4[7] = b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15];
        -
        -    b0 = this.mat4[8];
        -    b1 = this.mat4[9];
        -    b2 = this.mat4[10];
        -    b3 = this.mat4[11];
        -    this.mat4[8] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12];
        -    this.mat4[9] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13];
        -    this.mat4[10] = b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14];
        -    this.mat4[11] = b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15];
        -
        -    b0 = this.mat4[12];
        -    b1 = this.mat4[13];
        -    b2 = this.mat4[14];
        -    b3 = this.mat4[15];
        -    this.mat4[12] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12];
        -    this.mat4[13] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13];
        -    this.mat4[14] = b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14];
        -    this.mat4[15] = b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15];
        -
        -    return this;
        -  }
        -
        -  apply(multMatrix) {
        -    let _src;
        -
        -    if (multMatrix === this || multMatrix === this.mat4) {
        -      _src = this.copy().mat4; // only need to allocate in this rare case
        -    } else if (multMatrix instanceof p5.Matrix) {
        -      _src = multMatrix.mat4;
        -    } else if (isMatrixArray(multMatrix)) {
        -      _src = multMatrix;
        -    } else if (arguments.length === 16) {
        -      _src = arguments;
        -    } else {
        -      return; // nothing to do.
        -    }
        -
        -    const mat4 = this.mat4;
        -
        -    // each row is used for the multiplier
        -    const m0 = mat4[0];
        -    const m4 = mat4[4];
        -    const m8 = mat4[8];
        -    const m12 = mat4[12];
        -    mat4[0] = _src[0] * m0 + _src[1] * m4 + _src[2] * m8 + _src[3] * m12;
        -    mat4[4] = _src[4] * m0 + _src[5] * m4 + _src[6] * m8 + _src[7] * m12;
        -    mat4[8] = _src[8] * m0 + _src[9] * m4 + _src[10] * m8 + _src[11] * m12;
        -    mat4[12] = _src[12] * m0 + _src[13] * m4 + _src[14] * m8 + _src[15] * m12;
        -
        -    const m1 = mat4[1];
        -    const m5 = mat4[5];
        -    const m9 = mat4[9];
        -    const m13 = mat4[13];
        -    mat4[1] = _src[0] * m1 + _src[1] * m5 + _src[2] * m9 + _src[3] * m13;
        -    mat4[5] = _src[4] * m1 + _src[5] * m5 + _src[6] * m9 + _src[7] * m13;
        -    mat4[9] = _src[8] * m1 + _src[9] * m5 + _src[10] * m9 + _src[11] * m13;
        -    mat4[13] = _src[12] * m1 + _src[13] * m5 + _src[14] * m9 + _src[15] * m13;
        -
        -    const m2 = mat4[2];
        -    const m6 = mat4[6];
        -    const m10 = mat4[10];
        -    const m14 = mat4[14];
        -    mat4[2] = _src[0] * m2 + _src[1] * m6 + _src[2] * m10 + _src[3] * m14;
        -    mat4[6] = _src[4] * m2 + _src[5] * m6 + _src[6] * m10 + _src[7] * m14;
        -    mat4[10] = _src[8] * m2 + _src[9] * m6 + _src[10] * m10 + _src[11] * m14;
        -    mat4[14] = _src[12] * m2 + _src[13] * m6 + _src[14] * m10 + _src[15] * m14;
        -
        -    const m3 = mat4[3];
        -    const m7 = mat4[7];
        -    const m11 = mat4[11];
        -    const m15 = mat4[15];
        -    mat4[3] = _src[0] * m3 + _src[1] * m7 + _src[2] * m11 + _src[3] * m15;
        -    mat4[7] = _src[4] * m3 + _src[5] * m7 + _src[6] * m11 + _src[7] * m15;
        -    mat4[11] = _src[8] * m3 + _src[9] * m7 + _src[10] * m11 + _src[11] * m15;
        -    mat4[15] = _src[12] * m3 + _src[13] * m7 + _src[14] * m11 + _src[15] * m15;
        -
        -    return this;
        -  }
        -
        -  /**
        - * scales a p5.Matrix by scalars or a vector
        - * @method scale
        - * @param  {p5.Vector|Float32Array|Number[]} s vector to scale by
        - * @chainable
        - */
        -  scale(x, y, z) {
        -    if (x instanceof p5.Vector) {
        -    // x is a vector, extract the components from it.
        -      y = x.y;
        -      z = x.z;
        -      x = x.x; // must be last
        -    } else if (x instanceof Array) {
        -    // x is an array, extract the components from it.
        -      y = x[1];
        -      z = x[2];
        -      x = x[0]; // must be last
        -    }
        -
        -    this.mat4[0] *= x;
        -    this.mat4[1] *= x;
        -    this.mat4[2] *= x;
        -    this.mat4[3] *= x;
        -    this.mat4[4] *= y;
        -    this.mat4[5] *= y;
        -    this.mat4[6] *= y;
        -    this.mat4[7] *= y;
        -    this.mat4[8] *= z;
        -    this.mat4[9] *= z;
        -    this.mat4[10] *= z;
        -    this.mat4[11] *= z;
        -
        -    return this;
        -  }
        -
        -  /**
        - * rotate our Matrix around an axis by the given angle.
        - * @method rotate
        - * @param  {Number} a The angle of rotation in radians
        - * @param  {p5.Vector|Number[]} axis  the axis(es) to rotate around
        - * @chainable
        - * inspired by Toji's gl-matrix lib, mat4 rotation
        - */
        -  rotate(a, x, y, z) {
        -    if (x instanceof p5.Vector) {
        -    // x is a vector, extract the components from it.
        -      y = x.y;
        -      z = x.z;
        -      x = x.x; //must be last
        -    } else if (x instanceof Array) {
        -    // x is an array, extract the components from it.
        -      y = x[1];
        -      z = x[2];
        -      x = x[0]; //must be last
        -    }
        -
        -    const len = Math.sqrt(x * x + y * y + z * z);
        -    x *= 1 / len;
        -    y *= 1 / len;
        -    z *= 1 / len;
        -
        -    const a00 = this.mat4[0];
        -    const a01 = this.mat4[1];
        -    const a02 = this.mat4[2];
        -    const a03 = this.mat4[3];
        -    const a10 = this.mat4[4];
        -    const a11 = this.mat4[5];
        -    const a12 = this.mat4[6];
        -    const a13 = this.mat4[7];
        -    const a20 = this.mat4[8];
        -    const a21 = this.mat4[9];
        -    const a22 = this.mat4[10];
        -    const a23 = this.mat4[11];
        -
        -    //sin,cos, and tan of respective angle
        -    const sA = Math.sin(a);
        -    const cA = Math.cos(a);
        -    const tA = 1 - cA;
        -    // Construct the elements of the rotation matrix
        -    const b00 = x * x * tA + cA;
        -    const b01 = y * x * tA + z * sA;
        -    const b02 = z * x * tA - y * sA;
        -    const b10 = x * y * tA - z * sA;
        -    const b11 = y * y * tA + cA;
        -    const b12 = z * y * tA + x * sA;
        -    const b20 = x * z * tA + y * sA;
        -    const b21 = y * z * tA - x * sA;
        -    const b22 = z * z * tA + cA;
        -
        -    // rotation-specific matrix multiplication
        -    this.mat4[0] = a00 * b00 + a10 * b01 + a20 * b02;
        -    this.mat4[1] = a01 * b00 + a11 * b01 + a21 * b02;
        -    this.mat4[2] = a02 * b00 + a12 * b01 + a22 * b02;
        -    this.mat4[3] = a03 * b00 + a13 * b01 + a23 * b02;
        -    this.mat4[4] = a00 * b10 + a10 * b11 + a20 * b12;
        -    this.mat4[5] = a01 * b10 + a11 * b11 + a21 * b12;
        -    this.mat4[6] = a02 * b10 + a12 * b11 + a22 * b12;
        -    this.mat4[7] = a03 * b10 + a13 * b11 + a23 * b12;
        -    this.mat4[8] = a00 * b20 + a10 * b21 + a20 * b22;
        -    this.mat4[9] = a01 * b20 + a11 * b21 + a21 * b22;
        -    this.mat4[10] = a02 * b20 + a12 * b21 + a22 * b22;
        -    this.mat4[11] = a03 * b20 + a13 * b21 + a23 * b22;
        -
        -    return this;
        -  }
        -
        -  /**
        - * @todo  finish implementing this method!
        - * translates
        - * @method translate
        - * @param  {Number[]} v vector to translate by
        - * @chainable
        - */
        -  translate(v) {
        -    const x = v[0],
        -      y = v[1],
        -      z = v[2] || 0;
        -    this.mat4[12] += this.mat4[0] * x + this.mat4[4] * y + this.mat4[8] * z;
        -    this.mat4[13] += this.mat4[1] * x + this.mat4[5] * y + this.mat4[9] * z;
        -    this.mat4[14] += this.mat4[2] * x + this.mat4[6] * y + this.mat4[10] * z;
        -    this.mat4[15] += this.mat4[3] * x + this.mat4[7] * y + this.mat4[11] * z;
        -  }
        -
        -  rotateX(a) {
        -    this.rotate(a, 1, 0, 0);
        -  }
        -  rotateY(a) {
        -    this.rotate(a, 0, 1, 0);
        -  }
        -  rotateZ(a) {
        -    this.rotate(a, 0, 0, 1);
        -  }
        -
        -  /**
        - * sets the perspective matrix
        - * @method perspective
        - * @param  {Number} fovy   [description]
        - * @param  {Number} aspect [description]
        - * @param  {Number} near   near clipping plane
        - * @param  {Number} far    far clipping plane
        - * @chainable
        - */
        -  perspective(fovy, aspect, near, far) {
        -    const f = 1.0 / Math.tan(fovy / 2),
        -      nf = 1 / (near - far);
        -
        -    this.mat4[0] = f / aspect;
        -    this.mat4[1] = 0;
        -    this.mat4[2] = 0;
        -    this.mat4[3] = 0;
        -    this.mat4[4] = 0;
        -    this.mat4[5] = f;
        -    this.mat4[6] = 0;
        -    this.mat4[7] = 0;
        -    this.mat4[8] = 0;
        -    this.mat4[9] = 0;
        -    this.mat4[10] = (far + near) * nf;
        -    this.mat4[11] = -1;
        -    this.mat4[12] = 0;
        -    this.mat4[13] = 0;
        -    this.mat4[14] = 2 * far * near * nf;
        -    this.mat4[15] = 0;
        -
        -    return this;
        -  }
        -
        -  /**
        - * sets the ortho matrix
        - * @method ortho
        - * @param  {Number} left   [description]
        - * @param  {Number} right  [description]
        - * @param  {Number} bottom [description]
        - * @param  {Number} top    [description]
        - * @param  {Number} near   near clipping plane
        - * @param  {Number} far    far clipping plane
        - * @chainable
        - */
        -  ortho(left, right, bottom, top, near, far) {
        -    const lr = 1 / (left - right),
        -      bt = 1 / (bottom - top),
        -      nf = 1 / (near - far);
        -    this.mat4[0] = -2 * lr;
        -    this.mat4[1] = 0;
        -    this.mat4[2] = 0;
        -    this.mat4[3] = 0;
        -    this.mat4[4] = 0;
        -    this.mat4[5] = -2 * bt;
        -    this.mat4[6] = 0;
        -    this.mat4[7] = 0;
        -    this.mat4[8] = 0;
        -    this.mat4[9] = 0;
        -    this.mat4[10] = 2 * nf;
        -    this.mat4[11] = 0;
        -    this.mat4[12] = (left + right) * lr;
        -    this.mat4[13] = (top + bottom) * bt;
        -    this.mat4[14] = (far + near) * nf;
        -    this.mat4[15] = 1;
        -
        -    return this;
        -  }
        -
        -  /**
        - * apply a matrix to a vector with x,y,z,w components
        - * get the results in the form of an array
        - * @method multiplyVec4
        - * @param {Number}
        - * @return {Number[]}
        - */
        -  multiplyVec4(x, y, z, w) {
        -    const result = new Array(4);
        -    const m = this.mat4;
        -
        -    result[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
        -    result[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
        -    result[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
        -    result[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
        -
        -    return result;
        -  }
        -
        -  /**
        - * Applies a matrix to a vector.
        - * The fourth component is set to 1.
        - * Returns a vector consisting of the first
        - * through third components of the result.
        - *
        - * @method multiplyPoint
        - * @param {p5.Vector}
        - * @return {p5.Vector}
        - */
        -  multiplyPoint({ x, y, z }) {
        -    const array = this.multiplyVec4(x, y, z, 1);
        -    return new p5.Vector(array[0], array[1], array[2]);
        -  }
        -
        -  /**
        - * Applies a matrix to a vector.
        - * The fourth component is set to 1.
        - * Returns the result of dividing the 1st to 3rd components
        - * of the result by the 4th component as a vector.
        - *
        - * @method multiplyAndNormalizePoint
        - * @param {p5.Vector}
        - * @return {p5.Vector}
        - */
        -  multiplyAndNormalizePoint({ x, y, z }) {
        -    const array = this.multiplyVec4(x, y, z, 1);
        -    array[0] /= array[3];
        -    array[1] /= array[3];
        -    array[2] /= array[3];
        -    return new p5.Vector(array[0], array[1], array[2]);
        -  }
        -
        -  /**
        - * Applies a matrix to a vector.
        - * The fourth component is set to 0.
        - * Returns a vector consisting of the first
        - * through third components of the result.
        - *
        - * @method multiplyDirection
        - * @param {p5.Vector}
        - * @return {p5.Vector}
        - */
        -  multiplyDirection({ x, y, z }) {
        -    const array = this.multiplyVec4(x, y, z, 0);
        -    return new p5.Vector(array[0], array[1], array[2]);
        -  }
        -
        -  /**
        - * This function is only for 3x3 matrices.
        - * multiply two mat3s. It is an operation to multiply the 3x3 matrix of
        - * the argument from the right. Arguments can be a 3x3 p5.Matrix,
        - * a Float32Array of length 9, or a javascript array of length 9.
        - * In addition, it can also be done by enumerating 9 numbers.
        - *
        - * @method mult3x3
        - * @param {p5.Matrix|Float32Array|Number[]} multMatrix The matrix
        - *                                                we want to multiply by
        - * @chainable
        - */
        -  mult3x3(multMatrix) {
        -    let _src;
        -
        -    if (multMatrix === this || multMatrix === this.mat3) {
        -      _src = this.copy().mat3; // only need to allocate in this rare case
        -    } else if (multMatrix instanceof p5.Matrix) {
        -      _src = multMatrix.mat3;
        -    } else if (isMatrixArray(multMatrix)) {
        -      _src = multMatrix;
        -    } else if (arguments.length === 9) {
        -      _src = arguments;
        -    } else {
        -      return; // nothing to do.
        -    }
        -
        -    // each row is used for the multiplier
        -    let b0 = this.mat3[0];
        -    let b1 = this.mat3[1];
        -    let b2 = this.mat3[2];
        -    this.mat3[0] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6];
        -    this.mat3[1] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7];
        -    this.mat3[2] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8];
        -
        -    b0 = this.mat3[3];
        -    b1 = this.mat3[4];
        -    b2 = this.mat3[5];
        -    this.mat3[3] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6];
        -    this.mat3[4] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7];
        -    this.mat3[5] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8];
        -
        -    b0 = this.mat3[6];
        -    b1 = this.mat3[7];
        -    b2 = this.mat3[8];
        -    this.mat3[6] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6];
        -    this.mat3[7] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7];
        -    this.mat3[8] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8];
        -
        -    return this;
        -  }
        -
        -  /**
        - * This function is only for 3x3 matrices.
        - * A function that returns a column vector of a 3x3 matrix.
        - *
        - * @method column
        - * @param {Number} columnIndex matrix column number
        - * @return {p5.Vector}
        - */
        -  column(columnIndex) {
        -    return new p5.Vector(
        -      this.mat3[3 * columnIndex],
        -      this.mat3[3 * columnIndex + 1],
        -      this.mat3[3 * columnIndex + 2]
        -    );
        -  }
        -
        -  /**
        - * This function is only for 3x3 matrices.
        - * A function that returns a row vector of a 3x3 matrix.
        - *
        - * @method row
        - * @param {Number} rowIndex matrix row number
        - * @return {p5.Vector}
        - */
        -  row(rowIndex) {
        -    return new p5.Vector(
        -      this.mat3[rowIndex],
        -      this.mat3[rowIndex + 3],
        -      this.mat3[rowIndex + 6]
        -    );
        -  }
        -
        -  /**
        - * Returns the diagonal elements of the matrix in the form of an array.
        - * A 3x3 matrix will return an array of length 3.
        - * A 4x4 matrix will return an array of length 4.
        - *
        - * @method diagonal
        - * @return {Number[]} An array obtained by arranging the diagonal elements
        - *                    of the matrix in ascending order of index
        - */
        -  diagonal() {
        -    if (this.mat3 !== undefined) {
        -      return [this.mat3[0], this.mat3[4], this.mat3[8]];
        -    }
        -    return [this.mat4[0], this.mat4[5], this.mat4[10], this.mat4[15]];
        -  }
        -
        -  /**
        - * This function is only for 3x3 matrices.
        - * Takes a vector and returns the vector resulting from multiplying to
        - * that vector by this matrix from left.
        - *
        - * @method multiplyVec3
        - * @param {p5.Vector} multVector the vector to which this matrix applies
        - * @param {p5.Vector} [target] The vector to receive the result
        - * @return {p5.Vector}
        - */
        -  multiplyVec3(multVector, target) {
        -    if (target === undefined) {
        -      target = multVector.copy();
        -    }
        -    target.x = this.row(0).dot(multVector);
        -    target.y = this.row(1).dot(multVector);
        -    target.z = this.row(2).dot(multVector);
        -    return target;
        -  }
        -
        -  /**
        - * This function is only for 4x4 matrices.
        - * Creates a 3x3 matrix whose entries are the top left 3x3 part and returns it.
        - *
        - * @method createSubMatrix3x3
        - * @return {p5.Matrix}
        - */
        -  createSubMatrix3x3() {
        -    const result = new p5.Matrix('mat3');
        -    result.mat3[0] = this.mat4[0];
        -    result.mat3[1] = this.mat4[1];
        -    result.mat3[2] = this.mat4[2];
        -    result.mat3[3] = this.mat4[4];
        -    result.mat3[4] = this.mat4[5];
        -    result.mat3[5] = this.mat4[6];
        -    result.mat3[6] = this.mat4[8];
        -    result.mat3[7] = this.mat4[9];
        -    result.mat3[8] = this.mat4[10];
        -    return result;
        -  }
        -
        -  /**
        - * PRIVATE
        - */
        -  // matrix methods adapted from:
        -  // https://developer.mozilla.org/en-US/docs/Web/WebGL/
        -  // gluPerspective
        -  //
        -  // function _makePerspective(fovy, aspect, znear, zfar){
        -  //    const ymax = znear * Math.tan(fovy * Math.PI / 360.0);
        -  //    const ymin = -ymax;
        -  //    const xmin = ymin * aspect;
        -  //    const xmax = ymax * aspect;
        -  //    return _makeFrustum(xmin, xmax, ymin, ymax, znear, zfar);
        -  //  }
        -
        -  ////
        -  //// glFrustum
        -  ////
        -  //function _makeFrustum(left, right, bottom, top, znear, zfar){
        -  //  const X = 2*znear/(right-left);
        -  //  const Y = 2*znear/(top-bottom);
        -  //  const A = (right+left)/(right-left);
        -  //  const B = (top+bottom)/(top-bottom);
        -  //  const C = -(zfar+znear)/(zfar-znear);
        -  //  const D = -2*zfar*znear/(zfar-znear);
        -  //  const frustrumMatrix =[
        -  //  X, 0, A, 0,
        -  //  0, Y, B, 0,
        -  //  0, 0, C, D,
        -  //  0, 0, -1, 0
        -  //];
        -  //return frustrumMatrix;
        -  // }
        -
        -// function _setMVPMatrices(){
        -////an identity matrix
        -////@TODO use the p5.Matrix class to abstract away our MV matrices and
        -///other math
        -//const _mvMatrix =
        -//[
        -//  1.0,0.0,0.0,0.0,
        -//  0.0,1.0,0.0,0.0,
        -//  0.0,0.0,1.0,0.0,
        -//  0.0,0.0,0.0,1.0
        -//];
        -};
        -export default p5.Matrix;
        diff --git a/src/webgl/p5.Quat.js b/src/webgl/p5.Quat.js
        index 23a7d9db7b..7ecb773bff 100644
        --- a/src/webgl/p5.Quat.js
        +++ b/src/webgl/p5.Quat.js
        @@ -3,25 +3,12 @@
          * @submodule Quaternion
          */
         
        -import p5 from '../core/main';
        +import { Vector } from '../math/p5.Vector';
         
        -/**
        - * A class to describe a Quaternion
        - * for vector rotations in the p5js webgl renderer.
        - * Please refer the following link for details on the implementation
        - * https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html
        - * @class p5.Quat
        - * @constructor
        - * @param {Number} [w] Scalar part of the quaternion
        - * @param {Number} [x] x component of imaginary part of quaternion
        - * @param {Number} [y] y component of imaginary part of quaternion
        - * @param {Number} [z] z component of imaginary part of quaternion
        - * @private
        - */
        -p5.Quat = class {
        +class Quat {
           constructor(w, x, y, z) {
             this.w = w;
        -    this.vec = new p5.Vector(x, y, z);
        +    this.vec = new Vector(x, y, z);
           }
         
           /**
        @@ -37,12 +24,12 @@ p5.Quat = class {
             */
           static fromAxisAngle(angle, x, y, z) {
             const w = Math.cos(angle/2);
        -    const vec = new p5.Vector(x, y, z).normalize().mult(Math.sin(angle/2));
        -    return new p5.Quat(w, vec.x, vec.y, vec.z);
        +    const vec = new Vector(x, y, z).normalize().mult(Math.sin(angle/2));
        +    return new Quat(w, vec.x, vec.y, vec.z);
           }
         
           conjugate() {
        -    return new p5.Quat(this.w, -this.vec.x, -this.vec.y, -this.vec.z);
        +    return new Quat(this.w, -this.vec.x, -this.vec.y, -this.vec.z);
           }
         
           /**
        @@ -53,7 +40,7 @@ p5.Quat = class {
              */
           multiply(quat) {
             /* eslint-disable max-len */
        -    return new p5.Quat(
        +    return new Quat(
               this.w * quat.w - this.vec.x * quat.vec.x - this.vec.y * quat.vec.y - this.vec.z - quat.vec.z,
               this.w * quat.vec.x + this.vec.x * quat.w + this.vec.y * quat.vec.z - this.vec.z * quat.vec.y,
               this.w * quat.vec.y - this.vec.x * quat.vec.z + this.vec.y * quat.w + this.vec.z * quat.vec.x,
        @@ -71,9 +58,9 @@ p5.Quat = class {
            * @param {p5.Vector} [p] vector to rotate on the axis quaternion
            */
           rotateVector(p) {
        -    return new p5.Vector.mult( p, this.w*this.w - this.vec.dot(this.vec) )
        -      .add( p5.Vector.mult( this.vec, 2 * p.dot(this.vec) ) )
        -      .add( p5.Vector.mult( this.vec, 2 * this.w ).cross( p ) )
        +    return Vector.mult( p, this.w*this.w - this.vec.dot(this.vec) )
        +      .add( Vector.mult( this.vec, 2 * p.dot(this.vec) ) )
        +      .add( Vector.mult( this.vec, 2 * this.w ).cross( p ) )
               .clampToZero();
           }
         
        @@ -90,6 +77,28 @@ p5.Quat = class {
             return axesQuat.multiply(this).multiply(axesQuat.conjugate()).
               vec.clampToZero();
           }
        -};
        +}
        +
        +function quat(p5, fn){
        +  /**
        +   * A class to describe a Quaternion
        +   * for vector rotations in the p5js webgl renderer.
        +   * Please refer the following link for details on the implementation
        +   * https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html
        +   * @class p5.Quat
        +   * @constructor
        +   * @param {Number} [w] Scalar part of the quaternion
        +   * @param {Number} [x] x component of imaginary part of quaternion
        +   * @param {Number} [y] y component of imaginary part of quaternion
        +   * @param {Number} [z] z component of imaginary part of quaternion
        +   * @private
        +   */
        +  p5.Quat = Quat;
        +}
        +
        +export default quat;
        +export { Quat };
         
        -export default p5.Quat;
        +if(typeof p5 !== 'undefined'){
        +  quat(p5, p5.prototype);
        +}
        diff --git a/src/webgl/p5.RenderBuffer.js b/src/webgl/p5.RenderBuffer.js
        index 79851e485f..87c80ce45b 100644
        --- a/src/webgl/p5.RenderBuffer.js
        +++ b/src/webgl/p5.RenderBuffer.js
        @@ -1,7 +1,5 @@
        -import p5 from '../core/main';
        -
        -p5.RenderBuffer = class {
        -  constructor(size, src, dst, attr, renderer, map){
        +class RenderBuffer {
        +  constructor(size, src, dst, attr, renderer, map) {
             this.size = size; // the number of FLOATs in each vertex
             this.src = src; // the name of the model's source array
             this.dst = dst; // the name of the geometry's buffer
        @@ -11,63 +9,67 @@ p5.RenderBuffer = class {
           }
         
           /**
        - * Enables and binds the buffers used by shader when the appropriate data exists in geometry.
        - * Must always be done prior to drawing geometry in WebGL.
        - * @param {p5.Geometry} geometry Geometry that is going to be drawn
        - * @param {p5.Shader} shader Active shader
        - * @private
        - */
        +   * Enables and binds the buffers used by shader when the appropriate data exists in geometry.
        +   * Must always be done prior to drawing geometry in WebGL.
        +   * @param {p5.Geometry} geometry Geometry that is going to be drawn
        +   * @param {p5.Shader} shader Active shader
        +   * @private
        +   */
           _prepareBuffer(geometry, shader) {
             const attributes = shader.attributes;
             const gl = this._renderer.GL;
        -    let model;
        -    if (geometry.model) {
        -      model = geometry.model;
        -    } else {
        -      model = geometry;
        -    }
        +    const glBuffers = this._renderer._getOrMakeCachedBuffers(geometry);
         
             // loop through each of the buffer definitions
             const attr = attributes[this.attr];
             if (!attr) {
               return;
             }
        -
        -    // check if the model has the appropriate source array
        -    let buffer = geometry[this.dst];
        -    const src = model[this.src];
        -    if (src.length > 0) {
        -    // check if we need to create the GL buffer
        +    // check if the geometry has the appropriate source array
        +    let buffer = glBuffers[this.dst];
        +    const src = geometry[this.src];
        +    if (src && src.length > 0) {
        +      // check if we need to create the GL buffer
               const createBuffer = !buffer;
               if (createBuffer) {
        -      // create and remember the buffer
        -        geometry[this.dst] = buffer = gl.createBuffer();
        +        // create and remember the buffer
        +        glBuffers[this.dst] = buffer = gl.createBuffer();
               }
               // bind the buffer
               gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
         
               // check if we need to fill the buffer with data
        -      if (createBuffer || model.dirtyFlags[this.src] !== false) {
        +      if (createBuffer || geometry.dirtyFlags[this.src] !== false) {
                 const map = this.map;
        -        // get the values from the model, possibly transformed
        +        // get the values from the geometry, possibly transformed
                 const values = map ? map(src) : src;
                 // fill the buffer with the values
                 this._renderer._bindBuffer(buffer, gl.ARRAY_BUFFER, values);
        -
        -        // mark the model's source array as clean
        -        model.dirtyFlags[this.src] = false;
        +        // mark the geometry's source array as clean
        +        geometry.dirtyFlags[this.src] = false;
               }
               // enable the attribute
               shader.enableAttrib(attr, this.size);
             } else {
               const loc = attr.location;
        -      if (loc === -1 || !this._renderer.registerEnabled.has(loc)) { return; }
        +      if (loc === -1 || !this._renderer.registerEnabled.has(loc)) {
        +        return;
        +      }
               // Disable register corresponding to unused attribute
               gl.disableVertexAttribArray(loc);
               // Record register availability
               this._renderer.registerEnabled.delete(loc);
             }
           }
        -};
        +}
        +
        +function renderBuffer(p5, fn) {
        +  p5.RenderBuffer = RenderBuffer;
        +}
        +
        +export default renderBuffer;
        +export { RenderBuffer };
         
        -export default p5.RenderBuffer;
        +if (typeof p5 !== "undefined") {
        +  renderBuffer(p5, p5.prototype);
        +}
        diff --git a/src/webgl/p5.RendererGL.Immediate.js b/src/webgl/p5.RendererGL.Immediate.js
        deleted file mode 100644
        index 31ce48f630..0000000000
        --- a/src/webgl/p5.RendererGL.Immediate.js
        +++ /dev/null
        @@ -1,582 +0,0 @@
        -/**
        - * Welcome to RendererGL Immediate Mode.
        - * Immediate mode is used for drawing custom shapes
        - * from a set of vertices.  Immediate Mode is activated
        - * when you call <a href="#/p5/beginShape">beginShape()</a> & de-activated when you call <a href="#/p5/endShape">endShape()</a>.
        - * Immediate mode is a style of programming borrowed
        - * from OpenGL's (now-deprecated) immediate mode.
        - * It differs from p5.js' default, Retained Mode, which caches
        - * geometries and buffers on the CPU to reduce the number of webgl
        - * draw calls. Retained mode is more efficient & performative,
        - * however, Immediate Mode is useful for sketching quick
        - * geometric ideas.
        - */
        -import p5 from '../core/main';
        -import * as constants from '../core/constants';
        -import './p5.RenderBuffer';
        -
        -/**
        - * Begin shape drawing.  This is a helpful way of generating
        - * custom shapes quickly.  However in WEBGL mode, application
        - * performance will likely drop as a result of too many calls to
        - * <a href="#/p5/beginShape">beginShape()</a> / <a href="#/p5/endShape">endShape()</a>.  As a high performance alternative,
        - * please use p5.js geometry primitives.
        - * @private
        - * @method beginShape
        - * @param  {Number} mode webgl primitives mode.  beginShape supports the
        - *                       following modes:
        - *                       POINTS,LINES,LINE_STRIP,LINE_LOOP,TRIANGLES,
        - *                       TRIANGLE_STRIP, TRIANGLE_FAN, QUADS, QUAD_STRIP,
        - *                       and TESS(WEBGL only)
        - * @chainable
        - */
        -p5.RendererGL.prototype.beginShape = function(mode) {
        -  this.immediateMode.shapeMode =
        -    mode !== undefined ? mode : constants.TESS;
        -  this.immediateMode.geometry.reset();
        -  this.immediateMode.contourIndices = [];
        -  return this;
        -};
        -
        -const immediateBufferStrides = {
        -  vertices: 1,
        -  vertexNormals: 1,
        -  vertexColors: 4,
        -  vertexStrokeColors: 4,
        -  uvs: 2
        -};
        -
        -p5.RendererGL.prototype.beginContour = function() {
        -  if (this.immediateMode.shapeMode !== constants.TESS) {
        -    throw new Error('WebGL mode can only use contours with beginShape(TESS).');
        -  }
        -  this.immediateMode.contourIndices.push(
        -    this.immediateMode.geometry.vertices.length
        -  );
        -};
        -
        -/**
        - * adds a vertex to be drawn in a custom Shape.
        - * @private
        - * @method vertex
        - * @param  {Number} x x-coordinate of vertex
        - * @param  {Number} y y-coordinate of vertex
        - * @param  {Number} z z-coordinate of vertex
        - * @chainable
        - * @TODO implement handling of <a href="#/p5.Vector">p5.Vector</a> args
        - */
        -p5.RendererGL.prototype.vertex = function(x, y) {
        -  // WebGL 1 doesn't support QUADS or QUAD_STRIP, so we duplicate data to turn
        -  // QUADS into TRIANGLES and QUAD_STRIP into TRIANGLE_STRIP. (There is no extra
        -  // work to convert QUAD_STRIP here, since the only difference is in how edges
        -  // are rendered.)
        -  if (this.immediateMode.shapeMode === constants.QUADS) {
        -    // A finished quad turned into triangles should leave 6 vertices in the
        -    // buffer:
        -    // 0--3     0   3--5
        -    // |  | --> | \  \ |
        -    // 1--2     1--2   4
        -    // When vertex index 3 is being added, add the necessary duplicates.
        -    if (this.immediateMode.geometry.vertices.length % 6 === 3) {
        -      for (const key in immediateBufferStrides) {
        -        const stride = immediateBufferStrides[key];
        -        const buffer = this.immediateMode.geometry[key];
        -        buffer.push(
        -          ...buffer.slice(
        -            buffer.length - 3 * stride,
        -            buffer.length - 2 * stride
        -          ),
        -          ...buffer.slice(buffer.length - stride, buffer.length)
        -        );
        -      }
        -    }
        -  }
        -
        -  let z, u, v;
        -
        -  // default to (x, y) mode: all other arguments assumed to be 0.
        -  z = u = v = 0;
        -
        -  if (arguments.length === 3) {
        -    // (x, y, z) mode: (u, v) assumed to be 0.
        -    z = arguments[2];
        -  } else if (arguments.length === 4) {
        -    // (x, y, u, v) mode: z assumed to be 0.
        -    u = arguments[2];
        -    v = arguments[3];
        -  } else if (arguments.length === 5) {
        -    // (x, y, z, u, v) mode
        -    z = arguments[2];
        -    u = arguments[3];
        -    v = arguments[4];
        -  }
        -  const vert = new p5.Vector(x, y, z);
        -  this.immediateMode.geometry.vertices.push(vert);
        -  this.immediateMode.geometry.vertexNormals.push(this._currentNormal);
        -  const vertexColor = this.curFillColor || [0.5, 0.5, 0.5, 1.0];
        -  this.immediateMode.geometry.vertexColors.push(
        -    vertexColor[0],
        -    vertexColor[1],
        -    vertexColor[2],
        -    vertexColor[3]
        -  );
        -  const lineVertexColor = this.curStrokeColor || [0.5, 0.5, 0.5, 1];
        -  this.immediateMode.geometry.vertexStrokeColors.push(
        -    lineVertexColor[0],
        -    lineVertexColor[1],
        -    lineVertexColor[2],
        -    lineVertexColor[3]
        -  );
        -
        -  if (this.textureMode === constants.IMAGE && !this.isProcessingVertices) {
        -    if (this._tex !== null) {
        -      if (this._tex.width > 0 && this._tex.height > 0) {
        -        u /= this._tex.width;
        -        v /= this._tex.height;
        -      }
        -    } else if (
        -      this.userFillShader !== undefined ||
        -      this.userStrokeShader !== undefined ||
        -      this.userPointShader !== undefined
        -    ) {
        -    // Do nothing if user-defined shaders are present
        -    } else if (
        -      this._tex === null &&
        -      arguments.length >= 4
        -    ) {
        -      // Only throw this warning if custom uv's have  been provided
        -      console.warn(
        -        'You must first call texture() before using' +
        -          ' vertex() with image based u and v coordinates'
        -      );
        -    }
        -  }
        -
        -  this.immediateMode.geometry.uvs.push(u, v);
        -
        -  this.immediateMode._bezierVertex[0] = x;
        -  this.immediateMode._bezierVertex[1] = y;
        -  this.immediateMode._bezierVertex[2] = z;
        -
        -  this.immediateMode._quadraticVertex[0] = x;
        -  this.immediateMode._quadraticVertex[1] = y;
        -  this.immediateMode._quadraticVertex[2] = z;
        -
        -  return this;
        -};
        -
        -/**
        - * Sets the normal to use for subsequent vertices.
        - * @private
        - * @method normal
        - * @param  {Number} x
        - * @param  {Number} y
        - * @param  {Number} z
        - * @chainable
        - *
        - * @method normal
        - * @param  {Vector} v
        - * @chainable
        - */
        -p5.RendererGL.prototype.normal = function(xorv, y, z) {
        -  if (xorv instanceof p5.Vector) {
        -    this._currentNormal = xorv;
        -  } else {
        -    this._currentNormal = new p5.Vector(xorv, y, z);
        -  }
        -
        -  return this;
        -};
        -
        -/**
        - * End shape drawing and render vertices to screen.
        - * @chainable
        - */
        -p5.RendererGL.prototype.endShape = function(
        -  mode,
        -  isCurve,
        -  isBezier,
        -  isQuadratic,
        -  isContour,
        -  shapeKind,
        -  count = 1
        -) {
        -  if (this.immediateMode.shapeMode === constants.POINTS) {
        -    this._drawPoints(
        -      this.immediateMode.geometry.vertices,
        -      this.immediateMode.buffers.point
        -    );
        -    return this;
        -  }
        -  // When we are drawing a shape then the shape mode is TESS,
        -  // but in case of triangle we can skip the breaking into small triangle
        -  // this can optimize performance by skipping the step of breaking it into triangles
        -  if (this.immediateMode.geometry.vertices.length === 3 &&
        -      this.immediateMode.shapeMode === constants.TESS
        -  ) {
        -    this.immediateMode.shapeMode = constants.TRIANGLES;
        -  }
        -
        -  this.isProcessingVertices = true;
        -  this._processVertices(...arguments);
        -  this.isProcessingVertices = false;
        -
        -  // LINE_STRIP and LINES are not used for rendering, instead
        -  // they only indicate a way to modify vertices during the _processVertices() step
        -  let is_line = false;
        -  if (
        -    this.immediateMode.shapeMode === constants.LINE_STRIP ||
        -    this.immediateMode.shapeMode === constants.LINES
        -  ) {
        -    this.immediateMode.shapeMode = constants.TRIANGLE_FAN;
        -    is_line = true;
        -  }
        -
        -  // WebGL doesn't support the QUADS and QUAD_STRIP modes, so we
        -  // need to convert them to a supported format. In `vertex()`, we reformat
        -  // the input data into the formats specified below.
        -  if (this.immediateMode.shapeMode === constants.QUADS) {
        -    this.immediateMode.shapeMode = constants.TRIANGLES;
        -  } else if (this.immediateMode.shapeMode === constants.QUAD_STRIP) {
        -    this.immediateMode.shapeMode = constants.TRIANGLE_STRIP;
        -  }
        -
        -  if (this._doFill && !is_line) {
        -    if (
        -      !this.geometryBuilder &&
        -      this.immediateMode.geometry.vertices.length >= 3
        -    ) {
        -      this._drawImmediateFill(count);
        -    }
        -  }
        -  if (this._doStroke) {
        -    if (
        -      !this.geometryBuilder &&
        -      this.immediateMode.geometry.lineVertices.length >= 1
        -    ) {
        -      this._drawImmediateStroke();
        -    }
        -  }
        -
        -  if (this.geometryBuilder) {
        -    this.geometryBuilder.addImmediate();
        -  }
        -
        -  this.isBezier = false;
        -  this.isQuadratic = false;
        -  this.isCurve = false;
        -  this.immediateMode._bezierVertex.length = 0;
        -  this.immediateMode._quadraticVertex.length = 0;
        -  this.immediateMode._curveVertex.length = 0;
        -  return this;
        -};
        -
        -/**
        - * Called from endShape(). This function calculates the stroke vertices for custom shapes and
        - * tesselates shapes when applicable.
        - * @private
        - * @param  {Number} mode webgl primitives mode.  beginShape supports the
        - *                       following modes:
        - *                       POINTS,LINES,LINE_STRIP,LINE_LOOP,TRIANGLES,
        - *                       TRIANGLE_STRIP, TRIANGLE_FAN and TESS(WEBGL only)
        - */
        -p5.RendererGL.prototype._processVertices = function(mode) {
        -  if (this.immediateMode.geometry.vertices.length === 0) return;
        -
        -  const calculateStroke = this._doStroke;
        -  const shouldClose = mode === constants.CLOSE;
        -  if (calculateStroke) {
        -    this.immediateMode.geometry.edges = this._calculateEdges(
        -      this.immediateMode.shapeMode,
        -      this.immediateMode.geometry.vertices,
        -      shouldClose
        -    );
        -    if (!this.geometryBuilder) {
        -      this.immediateMode.geometry._edgesToVertices();
        -    }
        -  }
        -  // For hollow shapes, user must set mode to TESS
        -  const convexShape = this.immediateMode.shapeMode === constants.TESS;
        -  // If the shape has a contour, we have to re-triangulate to cut out the
        -  // contour region
        -  const hasContour = this.immediateMode.contourIndices.length > 0;
        -  // We tesselate when drawing curves or convex shapes
        -  const shouldTess =
        -    this._doFill &&
        -    (
        -      this.isBezier ||
        -      this.isQuadratic ||
        -      this.isCurve ||
        -      convexShape ||
        -      hasContour
        -    ) &&
        -    this.immediateMode.shapeMode !== constants.LINES;
        -
        -  if (shouldTess) {
        -    this._tesselateShape();
        -  }
        -};
        -
        -/**
        - * Called from _processVertices(). This function calculates the stroke vertices for custom shapes and
        - * tesselates shapes when applicable.
        - * @private
        - * @returns  {Number[]} indices for custom shape vertices indicating edges.
        - */
        -p5.RendererGL.prototype._calculateEdges = function(
        -  shapeMode,
        -  verts,
        -  shouldClose
        -) {
        -  const res = [];
        -  let i = 0;
        -  const contourIndices = this.immediateMode.contourIndices.slice();
        -  let contourStart = 0;
        -  switch (shapeMode) {
        -    case constants.TRIANGLE_STRIP:
        -      for (i = 0; i < verts.length - 2; i++) {
        -        res.push([i, i + 1]);
        -        res.push([i, i + 2]);
        -      }
        -      res.push([i, i + 1]);
        -      break;
        -    case constants.TRIANGLE_FAN:
        -      for (i = 1; i < verts.length - 1; i++) {
        -        res.push([0, i]);
        -        res.push([i, i + 1]);
        -      }
        -      res.push([0, verts.length - 1]);
        -      break;
        -    case constants.TRIANGLES:
        -      for (i = 0; i < verts.length - 2; i = i + 3) {
        -        res.push([i, i + 1]);
        -        res.push([i + 1, i + 2]);
        -        res.push([i + 2, i]);
        -      }
        -      break;
        -    case constants.LINES:
        -      for (i = 0; i < verts.length - 1; i = i + 2) {
        -        res.push([i, i + 1]);
        -      }
        -      break;
        -    case constants.QUADS:
        -      // Quads have been broken up into two triangles by `vertex()`:
        -      // 0   3--5
        -      // | \  \ |
        -      // 1--2   4
        -      for (i = 0; i < verts.length - 5; i += 6) {
        -        res.push([i, i + 1]);
        -        res.push([i + 1, i + 2]);
        -        res.push([i + 3, i + 5]);
        -        res.push([i + 4, i + 5]);
        -      }
        -      break;
        -    case constants.QUAD_STRIP:
        -      // 0---2---4
        -      // |   |   |
        -      // 1---3---5
        -      for (i = 0; i < verts.length - 2; i += 2) {
        -        res.push([i, i + 1]);
        -        res.push([i, i + 2]);
        -        res.push([i + 1, i + 3]);
        -      }
        -      res.push([i, i + 1]);
        -      break;
        -    default:
        -      // TODO: handle contours in other modes too
        -      for (i = 0; i < verts.length; i++) {
        -        // Handle breaks between contours
        -        if (i + 1 < verts.length && i + 1 !== contourIndices[0]) {
        -          res.push([i, i + 1]);
        -        } else {
        -          if (shouldClose || contourStart) {
        -            res.push([i, contourStart]);
        -          }
        -          if (contourIndices.length > 0) {
        -            contourStart = contourIndices.shift();
        -          }
        -        }
        -      }
        -      break;
        -  }
        -  if (shapeMode !== constants.TESS && shouldClose) {
        -    res.push([verts.length - 1, 0]);
        -  }
        -  return res;
        -};
        -
        -/**
        - * Called from _processVertices() when applicable. This function tesselates immediateMode.geometry.
        - * @private
        - */
        -p5.RendererGL.prototype._tesselateShape = function() {
        -  // TODO: handle non-TESS shape modes that have contours
        -  this.immediateMode.shapeMode = constants.TRIANGLES;
        -  const contours = [[]];
        -  for (let i = 0; i < this.immediateMode.geometry.vertices.length; i++) {
        -    if (
        -      this.immediateMode.contourIndices.length > 0 &&
        -      this.immediateMode.contourIndices[0] === i
        -    ) {
        -      this.immediateMode.contourIndices.shift();
        -      contours.push([]);
        -    }
        -    contours[contours.length-1].push(
        -      this.immediateMode.geometry.vertices[i].x,
        -      this.immediateMode.geometry.vertices[i].y,
        -      this.immediateMode.geometry.vertices[i].z,
        -      this.immediateMode.geometry.uvs[i * 2],
        -      this.immediateMode.geometry.uvs[i * 2 + 1],
        -      this.immediateMode.geometry.vertexColors[i * 4],
        -      this.immediateMode.geometry.vertexColors[i * 4 + 1],
        -      this.immediateMode.geometry.vertexColors[i * 4 + 2],
        -      this.immediateMode.geometry.vertexColors[i * 4 + 3],
        -      this.immediateMode.geometry.vertexNormals[i].x,
        -      this.immediateMode.geometry.vertexNormals[i].y,
        -      this.immediateMode.geometry.vertexNormals[i].z
        -    );
        -  }
        -  const polyTriangles = this._triangulate(contours);
        -  const originalVertices = this.immediateMode.geometry.vertices;
        -  this.immediateMode.geometry.vertices = [];
        -  this.immediateMode.geometry.vertexNormals = [];
        -  this.immediateMode.geometry.uvs = [];
        -  const colors = [];
        -  for (
        -    let j = 0, polyTriLength = polyTriangles.length;
        -    j < polyTriLength;
        -    j = j + p5.RendererGL.prototype.tessyVertexSize
        -  ) {
        -    colors.push(...polyTriangles.slice(j + 5, j + 9));
        -    this.normal(...polyTriangles.slice(j + 9, j + 12));
        -    this.vertex(...polyTriangles.slice(j, j + 5));
        -  }
        -  if (this.geometryBuilder) {
        -    // Tesselating the face causes the indices of edge vertices to stop being
        -    // correct. When rendering, this is not a problem, since _edgesToVertices
        -    // will have been called before this, and edge vertex indices are no longer
        -    // needed. However, the geometry builder still needs this information, so
        -    // when one is active, we need to update the indices.
        -    //
        -    // We record index mappings in a Map so that once we have found a
        -    // corresponding vertex, we don't need to loop to find it again.
        -    const newIndex = new Map();
        -    this.immediateMode.geometry.edges =
        -      this.immediateMode.geometry.edges.map(edge => edge.map(origIdx => {
        -        if (!newIndex.has(origIdx)) {
        -          const orig = originalVertices[origIdx];
        -          let newVertIndex = this.immediateMode.geometry.vertices.findIndex(
        -            v =>
        -              orig.x === v.x &&
        -              orig.y === v.y &&
        -              orig.z === v.z
        -          );
        -          if (newVertIndex === -1) {
        -            // The tesselation process didn't output a vertex with the exact
        -            // coordinate as before, potentially due to numerical issues. This
        -            // doesn't happen often, but in this case, pick the closest point
        -            let closestDist = Infinity;
        -            let closestIndex = 0;
        -            for (
        -              let i = 0;
        -              i < this.immediateMode.geometry.vertices.length;
        -              i++
        -            ) {
        -              const vert = this.immediateMode.geometry.vertices[i];
        -              const dX = orig.x - vert.x;
        -              const dY = orig.y - vert.y;
        -              const dZ = orig.z - vert.z;
        -              const dist = dX*dX + dY*dY + dZ*dZ;
        -              if (dist < closestDist) {
        -                closestDist = dist;
        -                closestIndex = i;
        -              }
        -            }
        -            newVertIndex = closestIndex;
        -          }
        -          newIndex.set(origIdx, newVertIndex);
        -        }
        -        return newIndex.get(origIdx);
        -      }));
        -  }
        -  this.immediateMode.geometry.vertexColors = colors;
        -};
        -
        -/**
        - * Called from endShape(). Responsible for calculating normals, setting shader uniforms,
        - * enabling all appropriate buffers, applying color blend, and drawing the fill geometry.
        - * @private
        - */
        -p5.RendererGL.prototype._drawImmediateFill = function(count = 1) {
        -  const gl = this.GL;
        -  this._useVertexColor = (this.immediateMode.geometry.vertexColors.length > 0);
        -
        -  let shader;
        -  shader = this._getImmediateFillShader();
        -
        -  this._setFillUniforms(shader);
        -
        -  for (const buff of this.immediateMode.buffers.fill) {
        -    buff._prepareBuffer(this.immediateMode.geometry, shader);
        -  }
        -  shader.disableRemainingAttributes();
        -
        -  this._applyColorBlend(
        -    this.curFillColor,
        -    this.immediateMode.geometry.hasFillTransparency()
        -  );
        -
        -  if (count === 1) {
        -    gl.drawArrays(
        -      this.immediateMode.shapeMode,
        -      0,
        -      this.immediateMode.geometry.vertices.length
        -    );
        -  }
        -  else {
        -    try {
        -      gl.drawArraysInstanced(
        -        this.immediateMode.shapeMode,
        -        0,
        -        this.immediateMode.geometry.vertices.length,
        -        count
        -      );
        -    }
        -    catch (e) {
        -      console.log('🌸 p5.js says: Instancing is only supported in WebGL2 mode');
        -    }
        -  }
        -  shader.unbindShader();
        -};
        -
        -/**
        - * Called from endShape(). Responsible for calculating normals, setting shader uniforms,
        - * enabling all appropriate buffers, applying color blend, and drawing the stroke geometry.
        - * @private
        - */
        -p5.RendererGL.prototype._drawImmediateStroke = function() {
        -  const gl = this.GL;
        -
        -  this._useLineColor =
        -    (this.immediateMode.geometry.vertexStrokeColors.length > 0);
        -
        -  const shader = this._getImmediateStrokeShader();
        -  this._setStrokeUniforms(shader);
        -  for (const buff of this.immediateMode.buffers.stroke) {
        -    buff._prepareBuffer(this.immediateMode.geometry, shader);
        -  }
        -  shader.disableRemainingAttributes();
        -  this._applyColorBlend(
        -    this.curStrokeColor,
        -    this.immediateMode.geometry.hasFillTransparency()
        -  );
        -
        -  gl.drawArrays(
        -    gl.TRIANGLES,
        -    0,
        -    this.immediateMode.geometry.lineVertices.length / 3
        -  );
        -  shader.unbindShader();
        -};
        -
        -export default p5.RendererGL;
        diff --git a/src/webgl/p5.RendererGL.Retained.js b/src/webgl/p5.RendererGL.Retained.js
        deleted file mode 100644
        index 49f2dd772b..0000000000
        --- a/src/webgl/p5.RendererGL.Retained.js
        +++ /dev/null
        @@ -1,269 +0,0 @@
        -//Retained Mode. The default mode for rendering 3D primitives
        -//in WEBGL.
        -import p5 from '../core/main';
        -import './p5.RendererGL';
        -import './p5.RenderBuffer';
        -import * as constants from '../core/constants';
        -
        -/**
        - * @param {p5.Geometry} geometry The model whose resources will be freed
        - */
        -p5.RendererGL.prototype.freeGeometry = function(geometry) {
        -  if (!geometry.gid) {
        -    console.warn('The model you passed to freeGeometry does not have an id!');
        -    return;
        -  }
        -  this._freeBuffers(geometry.gid);
        -};
        -
        -/**
        - * _initBufferDefaults
        - * @private
        - * @description initializes buffer defaults. runs each time a new geometry is
        - * registered
        - * @param  {String} gId  key of the geometry object
        - * @returns {Object} a new buffer object
        - */
        -p5.RendererGL.prototype._initBufferDefaults = function(gId) {
        -  this._freeBuffers(gId);
        -
        -  //@TODO remove this limit on hashes in retainedMode.geometry
        -  if (Object.keys(this.retainedMode.geometry).length > 1000) {
        -    const key = Object.keys(this.retainedMode.geometry)[0];
        -    this._freeBuffers(key);
        -  }
        -
        -  //create a new entry in our retainedMode.geometry
        -  return (this.retainedMode.geometry[gId] = {});
        -};
        -
        -p5.RendererGL.prototype._freeBuffers = function(gId) {
        -  const buffers = this.retainedMode.geometry[gId];
        -  if (!buffers) {
        -    return;
        -  }
        -
        -  delete this.retainedMode.geometry[gId];
        -
        -  const gl = this.GL;
        -  if (buffers.indexBuffer) {
        -    gl.deleteBuffer(buffers.indexBuffer);
        -  }
        -
        -  function freeBuffers(defs) {
        -    for (const def of defs) {
        -      if (buffers[def.dst]) {
        -        gl.deleteBuffer(buffers[def.dst]);
        -        buffers[def.dst] = null;
        -      }
        -    }
        -  }
        -
        -  // free all the buffers
        -  freeBuffers(this.retainedMode.buffers.stroke);
        -  freeBuffers(this.retainedMode.buffers.fill);
        -};
        -
        -/**
        - * creates a buffers object that holds the WebGL render buffers
        - * for a geometry.
        - * @private
        - * @param  {String} gId    key of the geometry object
        - * @param  {p5.Geometry}  model contains geometry data
        - */
        -p5.RendererGL.prototype.createBuffers = function(gId, model) {
        -  const gl = this.GL;
        -  //initialize the gl buffers for our geom groups
        -  const buffers = this._initBufferDefaults(gId);
        -  buffers.model = model;
        -
        -  let indexBuffer = buffers.indexBuffer;
        -
        -  if (model.faces.length) {
        -    // allocate space for faces
        -    if (!indexBuffer) indexBuffer = buffers.indexBuffer = gl.createBuffer();
        -    const vals = p5.RendererGL.prototype._flatten(model.faces);
        -
        -    // If any face references a vertex with an index greater than the maximum
        -    // un-singed 16 bit integer, then we need to use a Uint32Array instead of a
        -    // Uint16Array
        -    const hasVertexIndicesOverMaxUInt16 = vals.some(v => v > 65535);
        -    let type = hasVertexIndicesOverMaxUInt16 ? Uint32Array : Uint16Array;
        -    this._bindBuffer(indexBuffer, gl.ELEMENT_ARRAY_BUFFER, vals, type);
        -
        -    // If we're using a Uint32Array for our indexBuffer we will need to pass a
        -    // different enum value to WebGL draw triangles. This happens in
        -    // the _drawElements function.
        -    buffers.indexBufferType = hasVertexIndicesOverMaxUInt16
        -      ? gl.UNSIGNED_INT
        -      : gl.UNSIGNED_SHORT;
        -
        -    // the vertex count is based on the number of faces
        -    buffers.vertexCount = model.faces.length * 3;
        -  } else {
        -    // the index buffer is unused, remove it
        -    if (indexBuffer) {
        -      gl.deleteBuffer(indexBuffer);
        -      buffers.indexBuffer = null;
        -    }
        -    // the vertex count comes directly from the model
        -    buffers.vertexCount = model.vertices ? model.vertices.length : 0;
        -  }
        -
        -  buffers.lineVertexCount = model.lineVertices
        -    ? model.lineVertices.length / 3
        -    : 0;
        -
        -  return buffers;
        -};
        -
        -/**
        - * Draws buffers given a geometry key ID
        - * @private
        - * @param  {String} gId     ID in our geom hash
        - * @chainable
        - */
        -p5.RendererGL.prototype.drawBuffers = function(gId) {
        -  const gl = this.GL;
        -  const geometry = this.retainedMode.geometry[gId];
        -
        -  if (
        -    !this.geometryBuilder &&
        -    this._doFill &&
        -    this.retainedMode.geometry[gId].vertexCount > 0
        -  ) {
        -    this._useVertexColor = (geometry.model.vertexColors.length > 0);
        -    const fillShader = this._getRetainedFillShader();
        -    this._setFillUniforms(fillShader);
        -    for (const buff of this.retainedMode.buffers.fill) {
        -      buff._prepareBuffer(geometry, fillShader);
        -    }
        -    fillShader.disableRemainingAttributes();
        -    if (geometry.indexBuffer) {
        -      //vertex index buffer
        -      this._bindBuffer(geometry.indexBuffer, gl.ELEMENT_ARRAY_BUFFER);
        -    }
        -    this._applyColorBlend(
        -      this.curFillColor,
        -      geometry.model.hasFillTransparency()
        -    );
        -    this._drawElements(gl.TRIANGLES, gId);
        -    fillShader.unbindShader();
        -  }
        -
        -  if (!this.geometryBuilder && this._doStroke && geometry.lineVertexCount > 0) {
        -    this._useLineColor = (geometry.model.vertexStrokeColors.length > 0);
        -    const strokeShader = this._getRetainedStrokeShader();
        -    this._setStrokeUniforms(strokeShader);
        -    for (const buff of this.retainedMode.buffers.stroke) {
        -      buff._prepareBuffer(geometry, strokeShader);
        -    }
        -    strokeShader.disableRemainingAttributes();
        -    this._applyColorBlend(
        -      this.curStrokeColor,
        -      geometry.model.hasStrokeTransparency()
        -    );
        -    this._drawArrays(gl.TRIANGLES, gId);
        -    strokeShader.unbindShader();
        -  }
        -
        -  if (this.geometryBuilder) {
        -    this.geometryBuilder.addRetained(geometry);
        -  }
        -
        -  return this;
        -};
        -
        -/**
        - * Calls drawBuffers() with a scaled model/view matrix.
        - *
        - * This is used by various 3d primitive methods (in primitives.js, eg. plane,
        - * box, torus, etc...) to allow caching of un-scaled geometries. Those
        - * geometries are generally created with unit-length dimensions, cached as
        - * such, and then scaled appropriately in this method prior to rendering.
        - *
        - * @private
        - * @method drawBuffersScaled
        - * @param {String} gId     ID in our geom hash
        - * @param {Number} scaleX  the amount to scale in the X direction
        - * @param {Number} scaleY  the amount to scale in the Y direction
        - * @param {Number} scaleZ  the amount to scale in the Z direction
        - */
        -p5.RendererGL.prototype.drawBuffersScaled = function(
        -  gId,
        -  scaleX,
        -  scaleY,
        -  scaleZ
        -) {
        -  let originalModelMatrix = this.uModelMatrix.copy();
        -  try {
        -    this.uModelMatrix.scale(scaleX, scaleY, scaleZ);
        -
        -    this.drawBuffers(gId);
        -  } finally {
        -
        -    this.uModelMatrix = originalModelMatrix;
        -  }
        -};
        -p5.RendererGL.prototype._drawArrays = function(drawMode, gId) {
        -  this.GL.drawArrays(
        -    drawMode,
        -    0,
        -    this.retainedMode.geometry[gId].lineVertexCount
        -  );
        -  return this;
        -};
        -
        -p5.RendererGL.prototype._drawElements = function(drawMode, gId) {
        -  const buffers = this.retainedMode.geometry[gId];
        -  const gl = this.GL;
        -  // render the fill
        -  if (buffers.indexBuffer) {
        -    // If this model is using a Uint32Array we need to ensure the
        -    // OES_element_index_uint WebGL extension is enabled.
        -    if (
        -      this._pInst.webglVersion !== constants.WEBGL2 &&
        -      buffers.indexBufferType === gl.UNSIGNED_INT
        -    ) {
        -      if (!gl.getExtension('OES_element_index_uint')) {
        -        throw new Error(
        -          'Unable to render a 3d model with > 65535 triangles. Your web browser does not support the WebGL Extension OES_element_index_uint.'
        -        );
        -      }
        -    }
        -    // we're drawing faces
        -    gl.drawElements(
        -      gl.TRIANGLES,
        -      buffers.vertexCount,
        -      buffers.indexBufferType,
        -      0
        -    );
        -  } else {
        -    // drawing vertices
        -    gl.drawArrays(drawMode || gl.TRIANGLES, 0, buffers.vertexCount);
        -  }
        -};
        -
        -p5.RendererGL.prototype._drawPoints = function(vertices, vertexBuffer) {
        -  const gl = this.GL;
        -  const pointShader = this._getImmediatePointShader();
        -  this._setPointUniforms(pointShader);
        -
        -  this._bindBuffer(
        -    vertexBuffer,
        -    gl.ARRAY_BUFFER,
        -    this._vToNArray(vertices),
        -    Float32Array,
        -    gl.STATIC_DRAW
        -  );
        -
        -  pointShader.enableAttrib(pointShader.attributes.aPosition, 3);
        -
        -  this._applyColorBlend(this.curStrokeColor);
        -
        -  gl.drawArrays(gl.Points, 0, vertices.length);
        -
        -  pointShader.unbindShader();
        -};
        -
        -export default p5.RendererGL;
        diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js
        index d3864a8171..c845a1effb 100644
        --- a/src/webgl/p5.RendererGL.js
        +++ b/src/webgl/p5.RendererGL.js
        @@ -1,19 +1,56 @@
        -import p5 from '../core/main';
        -import * as constants from '../core/constants';
        -import GeometryBuilder from './GeometryBuilder';
        -import libtess from 'libtess';
        -import './p5.Shader';
        -import './p5.Camera';
        -import '../core/p5.Renderer';
        -import './p5.Matrix';
        -import './p5.Framebuffer';
        -import { readFileSync } from 'fs';
        -import { join } from 'path';
        -import { MipmapTexture } from './p5.Texture';
        +import * as constants from "../core/constants";
        +import GeometryBuilder from "./GeometryBuilder";
        +import { Renderer } from "../core/p5.Renderer";
        +import { Matrix } from "../math/p5.Matrix";
        +import { Camera } from "./p5.Camera";
        +import { Vector } from "../math/p5.Vector";
        +import { RenderBuffer } from "./p5.RenderBuffer";
        +import { DataArray } from "./p5.DataArray";
        +import { Shader } from "./p5.Shader";
        +import { Image } from "../image/p5.Image";
        +import { Texture, MipmapTexture } from "./p5.Texture";
        +import { Framebuffer } from "./p5.Framebuffer";
        +import { Graphics } from "../core/p5.Graphics";
        +import { Element } from "../dom/p5.Element";
        +import { ShapeBuilder } from "./ShapeBuilder";
        +import { GeometryBufferCache } from "./GeometryBufferCache";
        +import { filterParamDefaults } from '../image/const';
        +
        +import lightingShader from "./shaders/lighting.glsl";
        +import webgl2CompatibilityShader from "./shaders/webgl2Compatibility.glsl";
        +import normalVert from "./shaders/normal.vert";
        +import normalFrag from "./shaders/normal.frag";
        +import basicFrag from "./shaders/basic.frag";
        +import sphereMappingFrag from "./shaders/sphereMapping.frag";
        +import lightVert from "./shaders/light.vert";
        +import lightTextureFrag from "./shaders/light_texture.frag";
        +import phongVert from "./shaders/phong.vert";
        +import phongFrag from "./shaders/phong.frag";
        +import fontVert from "./shaders/font.vert";
        +import fontFrag from "./shaders/font.frag";
        +import lineVert from "./shaders/line.vert";
        +import lineFrag from "./shaders/line.frag";
        +import pointVert from "./shaders/point.vert";
        +import pointFrag from "./shaders/point.frag";
        +import imageLightVert from "./shaders/imageLight.vert";
        +import imageLightDiffusedFrag from "./shaders/imageLightDiffused.frag";
        +import imageLightSpecularFrag from "./shaders/imageLightSpecular.frag";
        +
        +import filterGrayFrag from "./shaders/filters/gray.frag";
        +import filterErodeFrag from "./shaders/filters/erode.frag";
        +import filterDilateFrag from "./shaders/filters/dilate.frag";
        +import filterBlurFrag from "./shaders/filters/blur.frag";
        +import filterPosterizeFrag from "./shaders/filters/posterize.frag";
        +import filterOpaqueFrag from "./shaders/filters/opaque.frag";
        +import filterInvertFrag from "./shaders/filters/invert.frag";
        +import filterThresholdFrag from "./shaders/filters/threshold.frag";
        +import filterShaderVert from "./shaders/filters/default.vert";
        +import { PrimitiveToVerticesConverter } from "../shape/custom_shapes";
        +import { Color } from "../color/p5.Color";
         
         const STROKE_CAP_ENUM = {};
         const STROKE_JOIN_ENUM = {};
        -let lineDefs = '';
        +let lineDefs = "";
         const defineStrokeCapEnum = function (key, val) {
           lineDefs += `#define STROKE_CAP_${key} ${val}\n`;
           STROKE_CAP_ENUM[constants[key]] = val;
        @@ -23,67 +60,33 @@ const defineStrokeJoinEnum = function (key, val) {
           STROKE_JOIN_ENUM[constants[key]] = val;
         };
         
        -
         // Define constants in line shaders for each type of cap/join, and also record
         // the values in JS objects
        -defineStrokeCapEnum('ROUND', 0);
        -defineStrokeCapEnum('PROJECT', 1);
        -defineStrokeCapEnum('SQUARE', 2);
        -defineStrokeJoinEnum('ROUND', 0);
        -defineStrokeJoinEnum('MITER', 1);
        -defineStrokeJoinEnum('BEVEL', 2);
        -
        -const lightingShader = readFileSync(
        -  join(__dirname, '/shaders/lighting.glsl'),
        -  'utf-8'
        -);
        -const webgl2CompatibilityShader = readFileSync(
        -  join(__dirname, '/shaders/webgl2Compatibility.glsl'),
        -  'utf-8'
        -);
        +defineStrokeCapEnum("ROUND", 0);
        +defineStrokeCapEnum("PROJECT", 1);
        +defineStrokeCapEnum("SQUARE", 2);
        +defineStrokeJoinEnum("ROUND", 0);
        +defineStrokeJoinEnum("MITER", 1);
        +defineStrokeJoinEnum("BEVEL", 2);
         
         const defaultShaders = {
        -  sphereMappingFrag: readFileSync(
        -    join(__dirname, '/shaders/sphereMapping.frag'),
        -    'utf-8'
        -  ),
        -  immediateVert: readFileSync(
        -    join(__dirname, '/shaders/immediate.vert'),
        -    'utf-8'
        -  ),
        -  vertexColorVert: readFileSync(
        -    join(__dirname, '/shaders/vertexColor.vert'),
        -    'utf-8'
        -  ),
        -  vertexColorFrag: readFileSync(
        -    join(__dirname, '/shaders/vertexColor.frag'),
        -    'utf-8'
        -  ),
        -  normalVert: readFileSync(join(__dirname, '/shaders/normal.vert'), 'utf-8'),
        -  normalFrag: readFileSync(join(__dirname, '/shaders/normal.frag'), 'utf-8'),
        -  basicFrag: readFileSync(join(__dirname, '/shaders/basic.frag'), 'utf-8'),
        -  lightVert:
        -    lightingShader +
        -    readFileSync(join(__dirname, '/shaders/light.vert'), 'utf-8'),
        -  lightTextureFrag: readFileSync(
        -    join(__dirname, '/shaders/light_texture.frag'),
        -    'utf-8'
        -  ),
        -  phongVert: readFileSync(join(__dirname, '/shaders/phong.vert'), 'utf-8'),
        -  phongFrag:
        -    lightingShader +
        -    readFileSync(join(__dirname, '/shaders/phong.frag'), 'utf-8'),
        -  fontVert: readFileSync(join(__dirname, '/shaders/font.vert'), 'utf-8'),
        -  fontFrag: readFileSync(join(__dirname, '/shaders/font.frag'), 'utf-8'),
        -  lineVert:
        -    lineDefs + readFileSync(join(__dirname, '/shaders/line.vert'), 'utf-8'),
        -  lineFrag:
        -    lineDefs + readFileSync(join(__dirname, '/shaders/line.frag'), 'utf-8'),
        -  pointVert: readFileSync(join(__dirname, '/shaders/point.vert'), 'utf-8'),
        -  pointFrag: readFileSync(join(__dirname, '/shaders/point.frag'), 'utf-8'),
        -  imageLightVert: readFileSync(join(__dirname, '/shaders/imageLight.vert'), 'utf-8'),
        -  imageLightDiffusedFrag: readFileSync(join(__dirname, '/shaders/imageLightDiffused.frag'), 'utf-8'),
        -  imageLightSpecularFrag: readFileSync(join(__dirname, '/shaders/imageLightSpecular.frag'), 'utf-8')
        +  normalVert,
        +  normalFrag,
        +  basicFrag,
        +  sphereMappingFrag,
        +  lightVert: lightingShader + lightVert,
        +  lightTextureFrag,
        +  phongVert,
        +  phongFrag: lightingShader + phongFrag,
        +  fontVert,
        +  fontFrag,
        +  lineVert: lineDefs + lineVert,
        +  lineFrag: lineDefs + lineFrag,
        +  pointVert,
        +  pointFrag,
        +  imageLightVert,
        +  imageLightDiffusedFrag,
        +  imageLightSpecularFrag,
         };
         let sphereMapping = defaultShaders.sphereMappingFrag;
         for (const key in defaultShaders) {
        @@ -91,468 +94,182 @@ for (const key in defaultShaders) {
         }
         
         const filterShaderFrags = {
        -  [constants.GRAY]:
        -    readFileSync(join(__dirname, '/shaders/filters/gray.frag'), 'utf-8'),
        -  [constants.ERODE]:
        -    readFileSync(join(__dirname, '/shaders/filters/erode.frag'), 'utf-8'),
        -  [constants.DILATE]:
        -    readFileSync(join(__dirname, '/shaders/filters/dilate.frag'), 'utf-8'),
        -  [constants.BLUR]:
        -    readFileSync(join(__dirname, '/shaders/filters/blur.frag'), 'utf-8'),
        -  [constants.POSTERIZE]:
        -    readFileSync(join(__dirname, '/shaders/filters/posterize.frag'), 'utf-8'),
        -  [constants.OPAQUE]:
        -    readFileSync(join(__dirname, '/shaders/filters/opaque.frag'), 'utf-8'),
        -  [constants.INVERT]:
        -    readFileSync(join(__dirname, '/shaders/filters/invert.frag'), 'utf-8'),
        -  [constants.THRESHOLD]:
        -    readFileSync(join(__dirname, '/shaders/filters/threshold.frag'), 'utf-8')
        +  [constants.GRAY]: filterGrayFrag,
        +  [constants.ERODE]: filterErodeFrag,
        +  [constants.DILATE]: filterDilateFrag,
        +  [constants.BLUR]: filterBlurFrag,
        +  [constants.POSTERIZE]: filterPosterizeFrag,
        +  [constants.OPAQUE]: filterOpaqueFrag,
        +  [constants.INVERT]: filterInvertFrag,
        +  [constants.THRESHOLD]: filterThresholdFrag,
         };
        -const filterShaderVert = readFileSync(join(__dirname, '/shaders/filters/default.vert'), 'utf-8');
         
         /**
        - * @module Rendering
        - * @submodule Rendering
        - * @for p5
        - */
        -/**
        - * Set attributes for the WebGL Drawing context.
        - * This is a way of adjusting how the WebGL
        - * renderer works to fine-tune the display and performance.
        - *
        - * Note that this will reinitialize the drawing context
        - * if called after the WebGL canvas is made.
        - *
        - * If an object is passed as the parameter, all attributes
        - * not declared in the object will be set to defaults.
        - *
        - * The available attributes are:
        - * <br>
        - * alpha - indicates if the canvas contains an alpha buffer
        - * default is true
        - *
        - * depth - indicates whether the drawing buffer has a depth buffer
        - * of at least 16 bits - default is true
        - *
        - * stencil - indicates whether the drawing buffer has a stencil buffer
        - * of at least 8 bits
        - *
        - * antialias - indicates whether or not to perform anti-aliasing
        - * default is false (true in Safari)
        - *
        - * premultipliedAlpha - indicates that the page compositor will assume
        - * the drawing buffer contains colors with pre-multiplied alpha
        - * default is true
        - *
        - * preserveDrawingBuffer - if true the buffers will not be cleared and
        - * and will preserve their values until cleared or overwritten by author
        - * (note that p5 clears automatically on draw loop)
        - * default is true
        - *
        - * perPixelLighting - if true, per-pixel lighting will be used in the
        - * lighting shader otherwise per-vertex lighting is used.
        - * default is true.
        - *
        - * version - either 1 or 2, to specify which WebGL version to ask for. By
        - * default, WebGL 2 will be requested. If WebGL2 is not available, it will
        - * fall back to WebGL 1. You can check what version is used with by looking at
        - * the global `webglVersion` property.
        - *
        - * @method setAttributes
        - * @for p5
        - * @param  {String}  key Name of attribute
        - * @param  {Boolean}        value New value of named attribute
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - * }
        - *
        - * function draw() {
        - *   background(255);
        - *   push();
        - *   rotateZ(frameCount * 0.02);
        - *   rotateX(frameCount * 0.02);
        - *   rotateY(frameCount * 0.02);
        - *   fill(0, 0, 0);
        - *   box(50);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - * <br>
        - * Now with the antialias attribute set to true.
        - * <br>
        - * <div>
        - * <code>
        - * function setup() {
        - *   setAttributes('antialias', true);
        - *   createCanvas(100, 100, WEBGL);
        - * }
        - *
        - * function draw() {
        - *   background(255);
        - *   push();
        - *   rotateZ(frameCount * 0.02);
        - *   rotateX(frameCount * 0.02);
        - *   rotateY(frameCount * 0.02);
        - *   fill(0, 0, 0);
        - *   box(50);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // press the mouse button to disable perPixelLighting
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *   noStroke();
        - *   fill(255);
        - * }
        - *
        - * let lights = [
        - *   { c: '#f00', t: 1.12, p: 1.91, r: 0.2 },
        - *   { c: '#0f0', t: 1.21, p: 1.31, r: 0.2 },
        - *   { c: '#00f', t: 1.37, p: 1.57, r: 0.2 },
        - *   { c: '#ff0', t: 1.12, p: 1.91, r: 0.7 },
        - *   { c: '#0ff', t: 1.21, p: 1.31, r: 0.7 },
        - *   { c: '#f0f', t: 1.37, p: 1.57, r: 0.7 }
        - * ];
        - *
        - * function draw() {
        - *   let t = millis() / 1000 + 1000;
        - *   background(0);
        - *   directionalLight(color('#222'), 1, 1, 1);
        - *
        - *   for (let i = 0; i < lights.length; i++) {
        - *     let light = lights[i];
        - *     pointLight(
        - *       color(light.c),
        - *       p5.Vector.fromAngles(t * light.t, t * light.p, width * light.r)
        - *     );
        - *   }
        - *
        - *   specularMaterial(255);
        - *   sphere(width * 0.1);
        - *
        - *   rotateX(t * 0.77);
        - *   rotateY(t * 0.83);
        - *   rotateZ(t * 0.91);
        - *   torus(width * 0.3, width * 0.07, 24, 10);
        - * }
        - *
        - * function mousePressed() {
        - *   setAttributes('perPixelLighting', false);
        - *   noStroke();
        - *   fill(255);
        - * }
        - * function mouseReleased() {
        - *   setAttributes('perPixelLighting', true);
        - *   noStroke();
        - *   fill(255);
        - * }
        - * </code>
        - * </div>
        - *
        - * @alt a rotating cube with smoother edges
        - */
        -/**
        - * @method setAttributes
        - * @for p5
        - * @param  {Object}  obj object with key-value pairs
        - */
        -p5.prototype.setAttributes = function (key, value) {
        -  if (typeof this._glAttributes === 'undefined') {
        -    console.log(
        -      'You are trying to use setAttributes on a p5.Graphics object ' +
        -      'that does not use a WEBGL renderer.'
        -    );
        -    return;
        -  }
        -  let unchanged = true;
        -  if (typeof value !== 'undefined') {
        -    //first time modifying the attributes
        -    if (this._glAttributes === null) {
        -      this._glAttributes = {};
        -    }
        -    if (this._glAttributes[key] !== value) {
        -      //changing value of previously altered attribute
        -      this._glAttributes[key] = value;
        -      unchanged = false;
        -    }
        -    //setting all attributes with some change
        -  } else if (key instanceof Object) {
        -    if (this._glAttributes !== key) {
        -      this._glAttributes = key;
        -      unchanged = false;
        -    }
        -  }
        -  //@todo_FES
        -  if (!this._renderer.isP3D || unchanged) {
        -    return;
        -  }
        -
        -  if (!this._setupDone) {
        -    for (const x in this._renderer.retainedMode.geometry) {
        -      if (this._renderer.retainedMode.geometry.hasOwnProperty(x)) {
        -        p5._friendlyError(
        -          'Sorry, Could not set the attributes, you need to call setAttributes() ' +
        -          'before calling the other drawing methods in setup()'
        -        );
        -        return;
        -      }
        -    }
        -  }
        -
        -  this.push();
        -  this._renderer._resetContext();
        -  this.pop();
        -
        -  if (this._renderer._curCamera) {
        -    this._renderer._curCamera._renderer = this._renderer;
        -  }
        -};
        -/**
        + * 3D graphics class
          * @private
        - * @param {Uint8Array|Float32Array|undefined} pixels An existing pixels array to reuse if the size is the same
        - * @param {WebGLRenderingContext} gl The WebGL context
        - * @param {WebGLFramebuffer|null} framebuffer The Framebuffer to read
        - * @param {Number} x The x coordiante to read, premultiplied by pixel density
        - * @param {Number} y The y coordiante to read, premultiplied by pixel density
        - * @param {Number} width The width in pixels to be read (factoring in pixel density)
        - * @param {Number} height The height in pixels to be read (factoring in pixel density)
        - * @param {GLEnum} format Either RGB or RGBA depending on how many channels to read
        - * @param {GLEnum} type The datatype of each channel, e.g. UNSIGNED_BYTE or FLOAT
        - * @param {Number|undefined} flipY If provided, the total height with which to flip the y axis about
        - * @returns {Uint8Array|Float32Array} pixels A pixels array with the current state of the
        - * WebGL context read into it
        + * @class p5.RendererGL
        + * @extends p5.Renderer
        + * @todo extend class to include public method for offscreen
        + * rendering (FBO).
          */
        -export function readPixelsWebGL(
        -  pixels,
        -  gl,
        -  framebuffer,
        -  x,
        -  y,
        -  width,
        -  height,
        -  format,
        -  type,
        -  flipY
        -) {
        -  // Record the currently bound framebuffer so we can go back to it after, and
        -  // bind the framebuffer we want to read from
        -  const prevFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
        -  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
        -
        -  const channels = format === gl.RGBA ? 4 : 3;
        -
        -  // Make a pixels buffer if it doesn't already exist
        -  const len = width * height * channels;
        -  const TypedArrayClass = type === gl.UNSIGNED_BYTE ? Uint8Array : Float32Array;
        -  if (!(pixels instanceof TypedArrayClass) || pixels.length !== len) {
        -    pixels = new TypedArrayClass(len);
        -  }
        +class RendererGL extends Renderer {
        +  constructor(pInst, w, h, isMainCanvas, elt, attr) {
        +    super(pInst, w, h, isMainCanvas);
         
        -  gl.readPixels(
        -    x,
        -    flipY ? (flipY - y - height) : y,
        -    width,
        -    height,
        -    format,
        -    type,
        -    pixels
        -  );
        -
        -  // Re-bind whatever was previously bound
        -  gl.bindFramebuffer(gl.FRAMEBUFFER, prevFramebuffer);
        +    // Create new canvas
        +    this.canvas = this.elt = elt || document.createElement("canvas");
        +    this._setAttributeDefaults(pInst);
        +    this._initContext();
        +    // This redundant property is useful in reminding you that you are
        +    // interacting with WebGLRenderingContext, still worth considering future removal
        +    this.GL = this.drawingContext;
        +    if (isMainCanvas) {
        +      this._pInst.drawingContext = this.drawingContext;
        +    }
         
        -  if (flipY) {
        -    // WebGL pixels are inverted compared to 2D pixels, so we have to flip
        -    // the resulting rows. Adapted from https://stackoverflow.com/a/41973289
        -    const halfHeight = Math.floor(height / 2);
        -    const tmpRow = new TypedArrayClass(width * channels);
        -    for (let y = 0; y < halfHeight; y++) {
        -      const topOffset = y * width * 4;
        -      const bottomOffset = (height - y - 1) * width * 4;
        -      tmpRow.set(pixels.subarray(topOffset, topOffset + width * 4));
        -      pixels.copyWithin(topOffset, bottomOffset, bottomOffset + width * 4);
        -      pixels.set(tmpRow, bottomOffset);
        +    if (this._isMainCanvas) {
        +      // for pixel method sharing with pimage
        +      this._pInst._curElement = this;
        +      this._pInst.canvas = this.canvas;
        +    } else {
        +      // hide if offscreen buffer by default
        +      this.canvas.style.display = "none";
             }
        -  }
        +    this.elt.id = "defaultCanvas0";
        +    this.elt.classList.add("p5Canvas");
         
        -  return pixels;
        -}
        +    const dimensions = this._adjustDimensions(w, h);
        +    w = dimensions.adjustedWidth;
        +    h = dimensions.adjustedHeight;
         
        -/**
        - * @private
        - * @param {WebGLRenderingContext} gl The WebGL context
        - * @param {WebGLFramebuffer|null} framebuffer The Framebuffer to read
        - * @param {Number} x The x coordinate to read, premultiplied by pixel density
        - * @param {Number} y The y coordinate to read, premultiplied by pixel density
        - * @param {GLEnum} format Either RGB or RGBA depending on how many channels to read
        - * @param {GLEnum} type The datatype of each channel, e.g. UNSIGNED_BYTE or FLOAT
        - * @param {Number|undefined} flipY If provided, the total height with which to flip the y axis about
        - * @returns {Number[]} pixels The channel data for the pixel at that location
        - */
        -export function readPixelWebGL(
        -  gl,
        -  framebuffer,
        -  x,
        -  y,
        -  format,
        -  type,
        -  flipY
        -) {
        -  // Record the currently bound framebuffer so we can go back to it after, and
        -  // bind the framebuffer we want to read from
        -  const prevFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
        -  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
        -
        -  const channels = format === gl.RGBA ? 4 : 3;
        -  const TypedArrayClass = type === gl.UNSIGNED_BYTE ? Uint8Array : Float32Array;
        -  const pixels = new TypedArrayClass(channels);
        +    this.width = w;
        +    this.height = h;
         
        -  gl.readPixels(
        -    x, flipY ? (flipY - y - 1) : y, 1, 1,
        -    format, type,
        -    pixels
        -  );
        +    // Set canvas size
        +    this.elt.width = w * this._pixelDensity;
        +    this.elt.height = h * this._pixelDensity;
        +    this.elt.style.width = `${w}px`;
        +    this.elt.style.height = `${h}px`;
        +    this._origViewport = {
        +      width: this.GL.drawingBufferWidth,
        +      height: this.GL.drawingBufferHeight,
        +    };
        +    this.viewport(this._origViewport.width, this._origViewport.height);
         
        -  // Re-bind whatever was previously bound
        -  gl.bindFramebuffer(gl.FRAMEBUFFER, prevFramebuffer);
        +    // Attach canvas element to DOM
        +    if (this._pInst._userNode) {
        +      // user input node case
        +      this._pInst._userNode.appendChild(this.elt);
        +    } else {
        +      //create main element
        +      if (document.getElementsByTagName("main").length === 0) {
        +        let m = document.createElement("main");
        +        document.body.appendChild(m);
        +      }
        +      //append canvas to main
        +      document.getElementsByTagName("main")[0].appendChild(this.elt);
        +    }
         
        -  return Array.from(pixels);
        -}
        -/**
        - * 3D graphics class
        - * @private
        - * @class p5.RendererGL
        - * @constructor
        - * @extends p5.Renderer
        - * @todo extend class to include public method for offscreen
        - * rendering (FBO).
        - */
        -p5.RendererGL = class RendererGL extends p5.Renderer {
        -  constructor(elt, pInst, isMainCanvas, attr) {
        -    super(elt, pInst, isMainCanvas);
        -    this._setAttributeDefaults(pInst);
        -    this._initContext();
             this.isP3D = true; //lets us know we're in 3d mode
         
        -    // When constructing a new p5.Geometry, this will represent the builder
        +    // When constructing a new Geometry, this will represent the builder
             this.geometryBuilder = undefined;
         
        -    // This redundant property is useful in reminding you that you are
        -    // interacting with WebGLRenderingContext, still worth considering future removal
        -    this.GL = this.drawingContext;
        -    this._pInst._setProperty('drawingContext', this.drawingContext);
        +    // Push/pop state
        +    this.states.uModelMatrix = new Matrix(4);
        +    this.states.uViewMatrix = new Matrix(4);
        +    this.states.uPMatrix = new Matrix(4);
        +
        +    this.states.curCamera = new Camera(this);
        +
        +    this.states.enableLighting = false;
        +    this.states.ambientLightColors = [];
        +    this.states.specularColors = [1, 1, 1];
        +    this.states.directionalLightDirections = [];
        +    this.states.directionalLightDiffuseColors = [];
        +    this.states.directionalLightSpecularColors = [];
        +    this.states.pointLightPositions = [];
        +    this.states.pointLightDiffuseColors = [];
        +    this.states.pointLightSpecularColors = [];
        +    this.states.spotLightPositions = [];
        +    this.states.spotLightDirections = [];
        +    this.states.spotLightDiffuseColors = [];
        +    this.states.spotLightSpecularColors = [];
        +    this.states.spotLightAngle = [];
        +    this.states.spotLightConc = [];
        +    this.states.activeImageLight = null;
        +
        +    this.states.curFillColor = [1, 1, 1, 1];
        +    this.states.curAmbientColor = [1, 1, 1, 1];
        +    this.states.curSpecularColor = [0, 0, 0, 0];
        +    this.states.curEmissiveColor = [0, 0, 0, 0];
        +    this.states.curStrokeColor = [0, 0, 0, 1];
        +
        +    this.states.curBlendMode = constants.BLEND;
        +
        +    this.states._hasSetAmbient = false;
        +    this.states._useSpecularMaterial = false;
        +    this.states._useEmissiveMaterial = false;
        +    this.states._useNormalMaterial = false;
        +    this.states._useShininess = 1;
        +    this.states._useMetalness = 0;
        +
        +    this.states.tint = [255, 255, 255, 255];
        +
        +    this.states.constantAttenuation = 1;
        +    this.states.linearAttenuation = 0;
        +    this.states.quadraticAttenuation = 0;
        +
        +    this.states._currentNormal = new Vector(0, 0, 1);
        +
        +    this.states.drawMode = constants.FILL;
        +
        +    this.states._tex = null;
        +    this.states.textureMode = constants.IMAGE;
        +    this.states.textureWrapX = constants.CLAMP;
        +    this.states.textureWrapY = constants.CLAMP;
         
             // erasing
             this._isErasing = false;
         
        +    // simple lines
        +    this._simpleLines = false;
        +
             // clipping
             this._clipDepths = [];
             this._isClipApplied = false;
             this._stencilTestOn = false;
         
        -    // lights
        -    this._enableLighting = false;
        -
        -    this.ambientLightColors = [];
             this.mixedAmbientLight = [];
             this.mixedSpecularColor = [];
        -    this.specularColors = [1, 1, 1];
        -
        -    this.directionalLightDirections = [];
        -    this.directionalLightDiffuseColors = [];
        -    this.directionalLightSpecularColors = [];
        -
        -    this.pointLightPositions = [];
        -    this.pointLightDiffuseColors = [];
        -    this.pointLightSpecularColors = [];
        -
        -    this.spotLightPositions = [];
        -    this.spotLightDirections = [];
        -    this.spotLightDiffuseColors = [];
        -    this.spotLightSpecularColors = [];
        -    this.spotLightAngle = [];
        -    this.spotLightConc = [];
        -
        -    // This property contains the input image if imageLight function
        -    // is called.
        -    // activeImageLight is checked by _setFillUniforms
        -    // for sending uniforms to the fillshader
        -    this.activeImageLight = null;
        -    // If activeImageLight property is Null, diffusedTextures,
        -    // specularTextures are Empty.
        -    // Else, it maps a p5.Image used by imageLight() to a p5.framebuffer.
        +
             // p5.framebuffer for this are calculated in getDiffusedTexture function
             this.diffusedTextures = new Map();
             // p5.framebuffer for this are calculated in getSpecularTexture function
             this.specularTextures = new Map();
         
        -    this.drawMode = constants.FILL;
        -
        -    this.curFillColor = this._cachedFillStyle = [1, 1, 1, 1];
        -    this.curAmbientColor = this._cachedFillStyle = [1, 1, 1, 1];
        -    this.curSpecularColor = this._cachedFillStyle = [0, 0, 0, 0];
        -    this.curEmissiveColor = this._cachedFillStyle = [0, 0, 0, 0];
        -    this.curStrokeColor = this._cachedStrokeStyle = [0, 0, 0, 1];
        -
        -    this.curBlendMode = constants.BLEND;
             this.preEraseBlend = undefined;
             this._cachedBlendMode = undefined;
        +    this._cachedFillStyle = [1, 1, 1, 1];
        +    this._cachedStrokeStyle = [0, 0, 0, 1];
             if (this.webglVersion === constants.WEBGL2) {
               this.blendExt = this.GL;
             } else {
        -      this.blendExt = this.GL.getExtension('EXT_blend_minmax');
        +      this.blendExt = this.GL.getExtension("EXT_blend_minmax");
             }
             this._isBlending = false;
         
        -
        -    this._hasSetAmbient = false;
        -    this._useSpecularMaterial = false;
        -    this._useEmissiveMaterial = false;
        -    this._useNormalMaterial = false;
        -    this._useShininess = 1;
        -    this._useMetalness = 0;
        -
             this._useLineColor = false;
             this._useVertexColor = false;
         
             this.registerEnabled = new Set();
         
        -    this._tint = [255, 255, 255, 255];
        -
        -    // lightFalloff variables
        -    this.constantAttenuation = 1;
        -    this.linearAttenuation = 0;
        -    this.quadraticAttenuation = 0;
        -
        -    /**
        - * model view, projection, & normal
        - * matrices
        - */
        -    this.uModelMatrix = new p5.Matrix();
        -    this.uViewMatrix = new p5.Matrix();
        -    this.uMVMatrix = new p5.Matrix();
        -    this.uPMatrix = new p5.Matrix();
        -    this.uNMatrix = new p5.Matrix('mat3');
        -    this.curMatrix = new p5.Matrix('mat3');
        -
        -    // Current vertex normal
        -    this._currentNormal = new p5.Vector(0, 0, 1);
        -
             // Camera
        -    this._curCamera = new p5.Camera(this);
        -    this._curCamera._computeCameraDefaultSettings();
        -    this._curCamera._setDefaultCamera();
        +    this.states.curCamera._computeCameraDefaultSettings();
        +    this.states.curCamera._setDefaultCamera();
         
             // FilterCamera
        -    this.filterCamera = new p5.Camera(this);
        +    this.filterCamera = new Camera(this);
             this.filterCamera._computeCameraDefaultSettings();
             this.filterCamera._setDefaultCamera();
             // Information about the previous frame's touch object
        @@ -560,82 +277,120 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             this.prevTouches = [];
             // Velocity variable for use with orbitControl()
             this.zoomVelocity = 0;
        -    this.rotateVelocity = new p5.Vector(0, 0);
        -    this.moveVelocity = new p5.Vector(0, 0);
        +    this.rotateVelocity = new Vector(0, 0);
        +    this.moveVelocity = new Vector(0, 0);
             // Flags for recording the state of zooming, rotation and moving
             this.executeZoom = false;
             this.executeRotateAndMove = false;
         
        -    this.specularShader = undefined;
        +    this._drawingFilter = false;
        +    this._drawingImage = false;
        +
        +    this.states.specularShader = undefined;
             this.sphereMapping = undefined;
        -    this.diffusedShader = undefined;
        +    this.states.diffusedShader = undefined;
             this._defaultLightShader = undefined;
             this._defaultImmediateModeShader = undefined;
             this._defaultNormalShader = undefined;
             this._defaultColorShader = undefined;
             this._defaultPointShader = undefined;
         
        -    this.userFillShader = undefined;
        -    this.userStrokeShader = undefined;
        -    this.userPointShader = undefined;
        -
        -    // Default drawing is done in Retained Mode
        -    // Geometry and Material hashes stored here
        -    this.retainedMode = {
        -      geometry: {},
        -      buffers: {
        -        stroke: [
        -          new p5.RenderBuffer(4, 'lineVertexColors', 'lineColorBuffer', 'aVertexColor', this),
        -          new p5.RenderBuffer(3, 'lineVertices', 'lineVerticesBuffer', 'aPosition', this),
        -          new p5.RenderBuffer(3, 'lineTangentsIn', 'lineTangentsInBuffer', 'aTangentIn', this),
        -          new p5.RenderBuffer(3, 'lineTangentsOut', 'lineTangentsOutBuffer', 'aTangentOut', this),
        -          new p5.RenderBuffer(1, 'lineSides', 'lineSidesBuffer', 'aSide', this)
        -        ],
        -        fill: [
        -          new p5.RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray),
        -          new p5.RenderBuffer(3, 'vertexNormals', 'normalBuffer', 'aNormal', this, this._vToNArray),
        -          new p5.RenderBuffer(4, 'vertexColors', 'colorBuffer', 'aVertexColor', this),
        -          new p5.RenderBuffer(3, 'vertexAmbients', 'ambientBuffer', 'aAmbientColor', this),
        -          //new BufferDef(3, 'vertexSpeculars', 'specularBuffer', 'aSpecularColor'),
        -          new p5.RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
        -        ],
        -        text: [
        -          new p5.RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray),
        -          new p5.RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
        -        ]
        -      }
        -    };
        +    this.states.userFillShader = undefined;
        +    this.states.userStrokeShader = undefined;
        +    this.states.userPointShader = undefined;
        +    this.states.userImageShader = undefined;
         
        -    // Immediate Mode
        -    // Geometry and Material hashes stored here
        -    this.immediateMode = {
        -      geometry: new p5.Geometry(),
        -      shapeMode: constants.TRIANGLE_FAN,
        -      contourIndices: [],
        -      _bezierVertex: [],
        -      _quadraticVertex: [],
        -      _curveVertex: [],
        -      buffers: {
        -        fill: [
        -          new p5.RenderBuffer(3, 'vertices', 'vertexBuffer', 'aPosition', this, this._vToNArray),
        -          new p5.RenderBuffer(3, 'vertexNormals', 'normalBuffer', 'aNormal', this, this._vToNArray),
        -          new p5.RenderBuffer(4, 'vertexColors', 'colorBuffer', 'aVertexColor', this),
        -          new p5.RenderBuffer(3, 'vertexAmbients', 'ambientBuffer', 'aAmbientColor', this),
        -          new p5.RenderBuffer(2, 'uvs', 'uvBuffer', 'aTexCoord', this, this._flatten)
        -        ],
        -        stroke: [
        -          new p5.RenderBuffer(4, 'lineVertexColors', 'lineColorBuffer', 'aVertexColor', this),
        -          new p5.RenderBuffer(3, 'lineVertices', 'lineVerticesBuffer', 'aPosition', this),
        -          new p5.RenderBuffer(3, 'lineTangentsIn', 'lineTangentsInBuffer', 'aTangentIn', this),
        -          new p5.RenderBuffer(3, 'lineTangentsOut', 'lineTangentsOutBuffer', 'aTangentOut', this),
        -          new p5.RenderBuffer(1, 'lineSides', 'lineSidesBuffer', 'aSide', this)
        -        ],
        -        point: this.GL.createBuffer()
        -      }
        +    this.states.curveDetail = 1 / 4;
        +
        +    // Used by beginShape/endShape functions to construct a p5.Geometry
        +    this.shapeBuilder = new ShapeBuilder(this);
        +
        +    this.buffers = {
        +      fill: [
        +        new RenderBuffer(
        +          3,
        +          "vertices",
        +          "vertexBuffer",
        +          "aPosition",
        +          this,
        +          this._vToNArray,
        +        ),
        +        new RenderBuffer(
        +          3,
        +          "vertexNormals",
        +          "normalBuffer",
        +          "aNormal",
        +          this,
        +          this._vToNArray,
        +        ),
        +        new RenderBuffer(
        +          4,
        +          "vertexColors",
        +          "colorBuffer",
        +          "aVertexColor",
        +          this,
        +        ),
        +        new RenderBuffer(
        +          3,
        +          "vertexAmbients",
        +          "ambientBuffer",
        +          "aAmbientColor",
        +          this,
        +        ),
        +        new RenderBuffer(2, "uvs", "uvBuffer", "aTexCoord", this, (arr) =>
        +          arr.flat(),
        +        ),
        +      ],
        +      stroke: [
        +        new RenderBuffer(
        +          4,
        +          "lineVertexColors",
        +          "lineColorBuffer",
        +          "aVertexColor",
        +          this,
        +        ),
        +        new RenderBuffer(
        +          3,
        +          "lineVertices",
        +          "lineVerticesBuffer",
        +          "aPosition",
        +          this,
        +        ),
        +        new RenderBuffer(
        +          3,
        +          "lineTangentsIn",
        +          "lineTangentsInBuffer",
        +          "aTangentIn",
        +          this,
        +        ),
        +        new RenderBuffer(
        +          3,
        +          "lineTangentsOut",
        +          "lineTangentsOutBuffer",
        +          "aTangentOut",
        +          this,
        +        ),
        +        new RenderBuffer(1, "lineSides", "lineSidesBuffer", "aSide", this),
        +      ],
        +      text: [
        +        new RenderBuffer(
        +          3,
        +          "vertices",
        +          "vertexBuffer",
        +          "aPosition",
        +          this,
        +          this._vToNArray,
        +        ),
        +        new RenderBuffer(2, "uvs", "uvBuffer", "aTexCoord", this, (arr) =>
        +          arr.flat(),
        +        ),
        +      ],
        +      point: this.GL.createBuffer(),
        +      user: [],
             };
         
        -    this.pointSize = 5.0; //default point size
        -    this.curStrokeWeight = 1;
        +    this.geometryBufferCache = new GeometryBufferCache(this);
        +
             this.curStrokeCap = constants.ROUND;
             this.curStrokeJoin = constants.ROUND;
         
        @@ -648,55 +403,44 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             this.activeFramebuffers = [];
         
             // for post processing step
        -    this.filterShader = undefined;
        +    this.states.filterShader = undefined;
             this.filterLayer = undefined;
             this.filterLayerTemp = undefined;
             this.defaultFilterShaders = {};
         
        -    this.textureMode = constants.IMAGE;
        -    // default wrap settings
        -    this.textureWrapX = constants.CLAMP;
        -    this.textureWrapY = constants.CLAMP;
        -    this._tex = null;
        -    this._curveTightness = 6;
        -
        -    // lookUpTable for coefficients needed to be calculated for bezierVertex, same are used for curveVertex
        -    this._lookUpTableBezier = [];
        -    // lookUpTable for coefficients needed to be calculated for quadraticVertex
        -    this._lookUpTableQuadratic = [];
        -
        -    // current curveDetail in the Bezier lookUpTable
        -    this._lutBezierDetail = 0;
        -    // current curveDetail in the Quadratic lookUpTable
        -    this._lutQuadraticDetail = 0;
        -
        -    // Used to distinguish between user calls to vertex() and internal calls
        -    this.isProcessingVertices = false;
        -    this._tessy = this._initTessy();
        -
             this.fontInfos = {};
         
             this._curShader = undefined;
        +    this.drawShapeCount = 1;
        +
        +    this.scratchMat3 = new Matrix(3);
           }
         
        +  //////////////////////////////////////////////
        +  // Geometry Building
        +  //////////////////////////////////////////////
        +
           /**
        -    * Starts creating a new p5.Geometry. Subsequent shapes drawn will be added
        -     * to the geometry and then returned when
        -     * <a href="#/p5/endGeometry">endGeometry()</a> is called. One can also use
        -     * <a href="#/p5/buildGeometry">buildGeometry()</a> to pass a function that
        -     * draws shapes.
        -     *
        -     * If you need to draw complex shapes every frame which don't change over time,
        -     * combining them upfront with `beginGeometry()` and `endGeometry()` and then
        -     * drawing that will run faster than repeatedly drawing the individual pieces.
        -     *
        -     * @method beginGeometry
        +   * Starts creating a new p5.Geometry. Subsequent shapes drawn will be added
        +   * to the geometry and then returned when
        +   * <a href="#/p5/endGeometry">endGeometry()</a> is called. One can also use
        +   * <a href="#/p5/buildGeometry">buildGeometry()</a> to pass a function that
        +   * draws shapes.
        +   *
        +   * If you need to draw complex shapes every frame which don't change over time,
        +   * combining them upfront with `beginGeometry()` and `endGeometry()` and then
        +   * drawing that will run faster than repeatedly drawing the individual pieces.
        +   * @private
            */
           beginGeometry() {
             if (this.geometryBuilder) {
        -      throw new Error('It looks like `beginGeometry()` is being called while another p5.Geometry is already being build.');
        +      throw new Error(
        +        "It looks like `beginGeometry()` is being called while another p5.Geometry is already being build.",
        +      );
             }
             this.geometryBuilder = new GeometryBuilder(this);
        +    this.geometryBuilder.prevFillColor = this.states.fillColor;
        +    this.fill(new Color([-1, -1, -1, -1]));
           }
         
           /**
        @@ -704,15 +448,18 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
            * started using <a href="#/p5/beginGeometry">beginGeometry()</a>. One can also
            * use <a href="#/p5/buildGeometry">buildGeometry()</a> to pass a function that
            * draws shapes.
        +   * @private
            *
        -   * @method endGeometry
            * @returns {p5.Geometry} The model that was built.
            */
           endGeometry() {
             if (!this.geometryBuilder) {
        -      throw new Error('Make sure you call beginGeometry() before endGeometry()!');
        +      throw new Error(
        +        "Make sure you call beginGeometry() before endGeometry()!",
        +      );
             }
             const geometry = this.geometryBuilder.finish();
        +    this.fill(this.geometryBuilder.prevFillColor);
             this.geometryBuilder = undefined;
             return geometry;
           }
        @@ -730,8 +477,6 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
            * <a href="#/p5/beginGeometry">beginGeometry()</a> and
            * <a href="#/p5/endGeometry">endGeometry()</a> instead of using a callback
            * function.
        -   *
        -   * @method buildGeometry
            * @param {Function} callback A function that draws shapes.
            * @returns {p5.Geometry} The model that was built from the callback function.
            */
        @@ -741,13 +486,357 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             return this.endGeometry();
           }
         
        +  //////////////////////////////////////////////
        +  // Shape drawing
        +  //////////////////////////////////////////////
        +
        +  beginShape(...args) {
        +    super.beginShape(...args);
        +    // TODO remove when shape refactor is complete
        +    // this.shapeBuilder.beginShape(...args);
        +  }
        +
        +  curveDetail(d) {
        +    if (d === undefined) {
        +      return this.states.curveDetail;
        +    } else {
        +      this.states.curveDetail = d;
        +    }
        +  }
        +
        +  drawShape(shape) {
        +    const visitor = new PrimitiveToVerticesConverter({
        +      curveDetail: this.states.curveDetail,
        +    });
        +    shape.accept(visitor);
        +    this.shapeBuilder.constructFromContours(shape, visitor.contours);
        +
        +    if (this.geometryBuilder) {
        +      this.geometryBuilder.addImmediate(
        +        this.shapeBuilder.geometry,
        +        this.shapeBuilder.shapeMode,
        +      );
        +    } else if (this.states.fillColor || this.states.strokeColor) {
        +      if (this.shapeBuilder.shapeMode === constants.POINTS) {
        +        this._drawPoints(
        +          this.shapeBuilder.geometry.vertices,
        +          this.buffers.point,
        +        );
        +      } else {
        +        this._drawGeometry(this.shapeBuilder.geometry, {
        +          mode: this.shapeBuilder.shapeMode,
        +          count: this.drawShapeCount,
        +        });
        +      }
        +    }
        +    this.drawShapeCount = 1;
        +  }
        +
        +  endShape(mode, count) {
        +    this.drawShapeCount = count;
        +    super.endShape(mode, count);
        +  }
        +
        +  legacyEndShape(
        +    mode,
        +    isCurve,
        +    isBezier,
        +    isQuadratic,
        +    isContour,
        +    shapeKind,
        +    count = 1,
        +  ) {
        +    this.shapeBuilder.endShape(
        +      mode,
        +      isCurve,
        +      isBezier,
        +      isQuadratic,
        +      isContour,
        +      shapeKind,
        +    );
        +
        +    if (this.geometryBuilder) {
        +      this.geometryBuilder.addImmediate(
        +        this.shapeBuilder.geometry,
        +        this.shapeBuilder.shapeMode,
        +      );
        +    } else if (this.states.fillColor || this.states.strokeColor) {
        +      this._drawGeometry(this.shapeBuilder.geometry, {
        +        mode: this.shapeBuilder.shapeMode,
        +        count,
        +      });
        +    }
        +  }
        +
        +  legacyVertex(...args) {
        +    this.shapeBuilder.vertex(...args);
        +  }
        +
        +  vertexProperty(...args) {
        +    this.currentShape.vertexProperty(...args);
        +  }
        +
        +  normal(xorv, y, z) {
        +    if (xorv instanceof Vector) {
        +      this.states._currentNormal = xorv;
        +    } else {
        +      this.states._currentNormal = new Vector(xorv, y, z);
        +    }
        +    this.updateShapeVertexProperties();
        +  }
        +
        +  model(model, count = 1) {
        +    if (model.vertices.length > 0) {
        +      if (this.geometryBuilder) {
        +        this.geometryBuilder.addRetained(model);
        +      } else {
        +        if (!this.geometryInHash(model.gid)) {
        +          model._edgesToVertices();
        +          this._getOrMakeCachedBuffers(model);
        +        }
        +
        +        this._drawGeometry(model, { count });
        +      }
        +    }
        +  }
        +
        +  //////////////////////////////////////////////
        +  // Rendering
        +  //////////////////////////////////////////////
        +
        +  _drawGeometry(geometry, { mode = constants.TRIANGLES, count = 1 } = {}) {
        +    for (const propName in geometry.userVertexProperties) {
        +      const prop = geometry.userVertexProperties[propName];
        +      this.buffers.user.push(
        +        new RenderBuffer(
        +          prop.getDataSize(),
        +          prop.getSrcName(),
        +          prop.getDstName(),
        +          prop.getName(),
        +          this,
        +        ),
        +      );
        +    }
        +
        +    if (
        +      this.states.fillColor &&
        +      geometry.vertices.length >= 3 &&
        +      ![constants.LINES, constants.POINTS].includes(mode)
        +    ) {
        +      this._drawFills(geometry, { mode, count });
        +    }
        +
        +    if (this.states.strokeColor && geometry.lineVertices.length >= 1) {
        +      this._drawStrokes(geometry, { count });
        +    }
        +
        +    this.buffers.user = [];
        +  }
        +
        +  _drawGeometryScaled(model, scaleX, scaleY, scaleZ) {
        +    let originalModelMatrix = this.states.uModelMatrix.copy();
        +    try {
        +      this.states.uModelMatrix.scale(scaleX, scaleY, scaleZ);
        +
        +      if (this.geometryBuilder) {
        +        this.geometryBuilder.addRetained(model);
        +      } else {
        +        this._drawGeometry(model);
        +      }
        +    } finally {
        +      this.states.uModelMatrix = originalModelMatrix;
        +    }
        +  }
        +
        +  _drawFills(geometry, { count, mode } = {}) {
        +    this._useVertexColor = geometry.vertexColors.length > 0;
        +
        +    const shader =
        +      this._drawingFilter && this.states.userFillShader
        +        ? this.states.userFillShader
        +        : this._getFillShader();
        +    shader.bindShader();
        +    this._setGlobalUniforms(shader);
        +    this._setFillUniforms(shader);
        +    shader.bindTextures();
        +
        +    for (const buff of this.buffers.fill) {
        +      buff._prepareBuffer(geometry, shader);
        +    }
        +    this._prepareUserAttributes(geometry, shader);
        +    shader.disableRemainingAttributes();
        +
        +    this._applyColorBlend(
        +      this.states.curFillColor,
        +      geometry.hasFillTransparency(),
        +    );
        +
        +    this._drawBuffers(geometry, { mode, count });
        +
        +    shader.unbindShader();
        +  }
        +
        +  _drawStrokes(geometry, { count } = {}) {
        +    const gl = this.GL;
        +
        +    this._useLineColor = geometry.vertexStrokeColors.length > 0;
        +
        +    const shader = this._getStrokeShader();
        +    shader.bindShader();
        +    this._setGlobalUniforms(shader);
        +    this._setStrokeUniforms(shader);
        +    shader.bindTextures();
        +
        +    for (const buff of this.buffers.stroke) {
        +      buff._prepareBuffer(geometry, shader);
        +    }
        +    this._prepareUserAttributes(geometry, shader);
        +    shader.disableRemainingAttributes();
        +
        +    this._applyColorBlend(
        +      this.states.curStrokeColor,
        +      geometry.hasStrokeTransparency(),
        +    );
        +
        +    if (count === 1) {
        +      gl.drawArrays(gl.TRIANGLES, 0, geometry.lineVertices.length / 3);
        +    } else {
        +      try {
        +        gl.drawArraysInstanced(
        +          gl.TRIANGLES,
        +          0,
        +          geometry.lineVertices.length / 3,
        +          count,
        +        );
        +      } catch (e) {
        +        console.log(
        +          "🌸 p5.js says: Instancing is only supported in WebGL2 mode",
        +        );
        +      }
        +    }
        +
        +    shader.unbindShader();
        +  }
        +
        +  _drawPoints(vertices, vertexBuffer) {
        +    const gl = this.GL;
        +    const pointShader = this._getPointShader();
        +    pointShader.bindShader();
        +    this._setGlobalUniforms(pointShader);
        +    this._setPointUniforms(pointShader);
        +    pointShader.bindTextures();
        +
        +    this._bindBuffer(
        +      vertexBuffer,
        +      gl.ARRAY_BUFFER,
        +      this._vToNArray(vertices),
        +      Float32Array,
        +      gl.STATIC_DRAW,
        +    );
        +
        +    pointShader.enableAttrib(pointShader.attributes.aPosition, 3);
        +
        +    this._applyColorBlend(this.states.curStrokeColor);
        +
        +    gl.drawArrays(gl.Points, 0, vertices.length);
        +
        +    pointShader.unbindShader();
        +  }
        +
        +  _prepareUserAttributes(geometry, shader) {
        +    for (const buff of this.buffers.user) {
        +      if (!this._pInst.constructor.disableFriendleErrors) {
        +        // Check for the right data size
        +        const prop = geometry.userVertexProperties[buff.attr];
        +        if (prop) {
        +          const adjustedLength = prop.getSrcArray().length / prop.getDataSize();
        +          if (adjustedLength > geometry.vertices.length) {
        +            this._pInst.constructor._friendlyError(
        +              `One of the geometries has a custom vertex property '${prop.getName()}' with more values than vertices. This is probably caused by directly using the Geometry.vertexProperty() method.`,
        +              "vertexProperty()",
        +            );
        +          } else if (adjustedLength < geometry.vertices.length) {
        +            this._pInst.constructor._friendlyError(
        +              `One of the geometries has a custom vertex property '${prop.getName()}' with fewer values than vertices. This is probably caused by directly using the Geometry.vertexProperty() method.`,
        +              "vertexProperty()",
        +            );
        +          }
        +        }
        +      }
        +      buff._prepareBuffer(geometry, shader);
        +    }
        +  }
        +
        +  _drawBuffers(geometry, { mode = this.GL.TRIANGLES, count }) {
        +    const gl = this.GL;
        +    const glBuffers = this.geometryBufferCache.getCached(geometry);
        +
        +    if (!glBuffers) return;
        +
        +    if (glBuffers.indexBuffer) {
        +      this._bindBuffer(glBuffers.indexBuffer, gl.ELEMENT_ARRAY_BUFFER);
        +
        +      // If this model is using a Uint32Array we need to ensure the
        +      // OES_element_index_uint WebGL extension is enabled.
        +      if (
        +        this._pInst.webglVersion !== constants.WEBGL2 &&
        +        glBuffers.indexBufferType === gl.UNSIGNED_INT
        +      ) {
        +        if (!gl.getExtension("OES_element_index_uint")) {
        +          throw new Error(
        +            "Unable to render a 3d model with > 65535 triangles. Your web browser does not support the WebGL Extension OES_element_index_uint.",
        +          );
        +        }
        +      }
        +
        +      if (count === 1) {
        +        gl.drawElements(
        +          gl.TRIANGLES,
        +          geometry.faces.length * 3,
        +          glBuffers.indexBufferType,
        +          0,
        +        );
        +      } else {
        +        try {
        +          gl.drawElementsInstanced(
        +            gl.TRIANGLES,
        +            geometry.faces.length * 3,
        +            glBuffers.indexBufferType,
        +            0,
        +            count,
        +          );
        +        } catch (e) {
        +          console.log(
        +            "🌸 p5.js says: Instancing is only supported in WebGL2 mode",
        +          );
        +        }
        +      }
        +    } else {
        +      if (count === 1) {
        +        gl.drawArrays(mode, 0, geometry.vertices.length);
        +      } else {
        +        try {
        +          gl.drawArraysInstanced(mode, 0, geometry.vertices.length, count);
        +        } catch (e) {
        +          console.log(
        +            "🌸 p5.js says: Instancing is only supported in WebGL2 mode",
        +          );
        +        }
        +      }
        +    }
        +  }
        +
        +  _getOrMakeCachedBuffers(geometry) {
        +    return this.geometryBufferCache.ensureCached(geometry);
        +  }
        +
           //////////////////////////////////////////////
           // Setting
           //////////////////////////////////////////////
         
           _setAttributeDefaults(pInst) {
             // See issue #3850, safer to enable AA in Safari
        -    const applyAA = navigator.userAgent.toLowerCase().includes('safari');
        +    const applyAA = navigator.userAgent.toLowerCase().includes("safari");
             const defaults = {
               alpha: true,
               depth: true,
        @@ -756,7 +845,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               premultipliedAlpha: true,
               preserveDrawingBuffer: true,
               perPixelLighting: true,
        -      version: 2
        +      version: 2,
             };
             if (pInst._glAttributes === null) {
               pInst._glAttributes = defaults;
        @@ -767,25 +856,28 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
           }
         
           _initContext() {
        -    if (this._pInst._glAttributes.version !== 1) {
        +    if (this._pInst._glAttributes?.version !== 1) {
               // Unless WebGL1 is explicitly asked for, try to create a WebGL2 context
        -      this.drawingContext =
        -        this.canvas.getContext('webgl2', this._pInst._glAttributes);
        +      this.drawingContext = this.canvas.getContext(
        +        "webgl2",
        +        this._pInst._glAttributes,
        +      );
             }
        -    this.webglVersion =
        -      this.drawingContext ? constants.WEBGL2 : constants.WEBGL;
        +    this.webglVersion = this.drawingContext
        +      ? constants.WEBGL2
        +      : constants.WEBGL;
             // If this is the main canvas, make sure the global `webglVersion` is set
        -    this._pInst._setProperty('webglVersion', this.webglVersion);
        +    this._pInst.webglVersion = this.webglVersion;
             if (!this.drawingContext) {
               // If we were unable to create a WebGL2 context (either because it was
               // disabled via `setAttributes({ version: 1 })` or because the device
               // doesn't support it), fall back to a WebGL1 context
               this.drawingContext =
        -        this.canvas.getContext('webgl', this._pInst._glAttributes) ||
        -        this.canvas.getContext('experimental-webgl', this._pInst._glAttributes);
        +        this.canvas.getContext("webgl", this._pInst._glAttributes) ||
        +        this.canvas.getContext("experimental-webgl", this._pInst._glAttributes);
             }
             if (this.drawingContext === null) {
        -      throw new Error('Error creating webgl context');
        +      throw new Error("Error creating webgl context");
             } else {
               const gl = this.drawingContext;
               gl.enable(gl.DEPTH_TEST);
        @@ -796,37 +888,32 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               // be encoded the same way as textures from everything else.
               gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
               this._viewport = this.drawingContext.getParameter(
        -        this.drawingContext.VIEWPORT
        +        this.drawingContext.VIEWPORT,
               );
             }
           }
         
        -  _getParam() {
        +  _getMaxTextureSize() {
             const gl = this.drawingContext;
             return gl.getParameter(gl.MAX_TEXTURE_SIZE);
           }
         
           _adjustDimensions(width, height) {
             if (!this._maxTextureSize) {
        -      this._maxTextureSize = this._getParam();
        +      this._maxTextureSize = this._getMaxTextureSize();
             }
             let maxTextureSize = this._maxTextureSize;
        -    let maxAllowedPixelDimensions = p5.prototype._maxAllowedPixelDimensions;
         
        -    maxAllowedPixelDimensions = Math.floor(
        -      maxTextureSize / this.pixelDensity()
        -    );
        -    let adjustedWidth = Math.min(
        -      width, maxAllowedPixelDimensions
        -    );
        -    let adjustedHeight = Math.min(
        -      height, maxAllowedPixelDimensions
        +    let maxAllowedPixelDimensions = Math.floor(
        +      maxTextureSize / this._pixelDensity,
             );
        +    let adjustedWidth = Math.min(width, maxAllowedPixelDimensions);
        +    let adjustedHeight = Math.min(height, maxAllowedPixelDimensions);
         
             if (adjustedWidth !== width || adjustedHeight !== height) {
               console.warn(
        -        'Warning: The requested width/height exceeds hardware limits. ' +
        -          `Adjusting dimensions to width: ${adjustedWidth}, height: ${adjustedHeight}.`
        +        "Warning: The requested width/height exceeds hardware limits. " +
        +          `Adjusting dimensions to width: ${adjustedWidth}, height: ${adjustedHeight}.`,
               );
             }
         
        @@ -840,15 +927,15 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             const w = this.width;
             const h = this.height;
             const defaultId = this.canvas.id;
        -    const isPGraphics = this._pInst instanceof p5.Graphics;
        +    const isPGraphics = this._pInst instanceof Graphics;
         
             if (isPGraphics) {
               const pg = this._pInst;
               pg.canvas.parentNode.removeChild(pg.canvas);
        -      pg.canvas = document.createElement('canvas');
        +      pg.canvas = document.createElement("canvas");
               const node = pg._pInst._userNode || document.body;
               node.appendChild(pg.canvas);
        -      p5.Element.call(pg, pg.canvas, pg._pInst);
        +      Element.call(pg, pg.canvas, pg._pInst);
               pg.width = w;
               pg.height = h;
             } else {
        @@ -856,7 +943,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               if (c) {
                 c.parentNode.removeChild(c);
               }
        -      c = document.createElement('canvas');
        +      c = document.createElement("canvas");
               c.id = defaultId;
               if (this._pInst._userNode) {
                 this._pInst._userNode.appendChild(c);
        @@ -867,20 +954,18 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               this.canvas = c;
             }
         
        -    const renderer = new p5.RendererGL(
        -      this._pInst.canvas,
        +    const renderer = new RendererGL(
               this._pInst,
        -      !isPGraphics
        +      w,
        +      h,
        +      !isPGraphics,
        +      this._pInst.canvas,
             );
        -    this._pInst._setProperty('_renderer', renderer);
        -    renderer.resize(w, h);
        -    renderer._applyDefaults();
        +    this._pInst._renderer = renderer;
         
        -    if (!isPGraphics) {
        -      this._pInst._elements.push(renderer);
        -    }
        +    renderer._applyDefaults();
         
        -    if (typeof callback === 'function') {
        +    if (typeof callback === "function") {
               //setTimeout with 0 forces the task to the back of the queue, this ensures that
               //we finish switching out the renderer
               setTimeout(() => {
        @@ -889,40 +974,36 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             }
           }
         
        -
        -  /**
        - * @class p5.RendererGL
        - */
           _update() {
             // reset model view and apply initial camera transform
             // (containing only look at info; no projection).
        -    this.uModelMatrix.reset();
        -    this.uViewMatrix.set(this._curCamera.cameraMatrix);
        +    this.states.uModelMatrix.reset();
        +    this.states.uViewMatrix.set(this.states.curCamera.cameraMatrix);
         
             // reset light data for new frame.
         
        -    this.ambientLightColors.length = 0;
        -    this.specularColors = [1, 1, 1];
        +    this.states.ambientLightColors.length = 0;
        +    this.states.specularColors = [1, 1, 1];
         
        -    this.directionalLightDirections.length = 0;
        -    this.directionalLightDiffuseColors.length = 0;
        -    this.directionalLightSpecularColors.length = 0;
        +    this.states.directionalLightDirections.length = 0;
        +    this.states.directionalLightDiffuseColors.length = 0;
        +    this.states.directionalLightSpecularColors.length = 0;
         
        -    this.pointLightPositions.length = 0;
        -    this.pointLightDiffuseColors.length = 0;
        -    this.pointLightSpecularColors.length = 0;
        +    this.states.pointLightPositions.length = 0;
        +    this.states.pointLightDiffuseColors.length = 0;
        +    this.states.pointLightSpecularColors.length = 0;
         
        -    this.spotLightPositions.length = 0;
        -    this.spotLightDirections.length = 0;
        -    this.spotLightDiffuseColors.length = 0;
        -    this.spotLightSpecularColors.length = 0;
        -    this.spotLightAngle.length = 0;
        -    this.spotLightConc.length = 0;
        +    this.states.spotLightPositions.length = 0;
        +    this.states.spotLightDirections.length = 0;
        +    this.states.spotLightDiffuseColors.length = 0;
        +    this.states.spotLightSpecularColors.length = 0;
        +    this.states.spotLightAngle.length = 0;
        +    this.states.spotLightConc.length = 0;
         
        -    this._enableLighting = false;
        +    this.states.enableLighting = false;
         
             //reset tint value for new frame
        -    this._tint = [255, 255, 255, 255];
        +    this.states.tint = [255, 255, 255, 255];
         
             //Clear depth every frame
             this.GL.clearStencil(0);
        @@ -931,94 +1012,113 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
           }
         
           /**
        - * [background description]
        - */
        +   * [background description]
        +   */
           background(...args) {
             const _col = this._pInst.color(...args);
        -    const _r = _col.levels[0] / 255;
        -    const _g = _col.levels[1] / 255;
        -    const _b = _col.levels[2] / 255;
        -    const _a = _col.levels[3] / 255;
        -    this.clear(_r, _g, _b, _a);
        +    this.clear(..._col._getRGBA());
        +  }
        +
        +  // Combines the model and view matrices to get the uMVMatrix
        +  // This method will be reusable wherever you need to update the combined matrix.
        +  calculateCombinedMatrix() {
        +    const modelMatrix = this.states.uModelMatrix;
        +    const viewMatrix = this.states.uViewMatrix;
        +    return modelMatrix.copy().mult(viewMatrix);
           }
         
           //////////////////////////////////////////////
           // COLOR
           //////////////////////////////////////////////
           /**
        - * Basic fill material for geometry with a given color
        - * @method  fill
        - * @class p5.RendererGL
        - * @param  {Number|Number[]|String|p5.Color} v1  gray value,
        - * red or hue value (depending on the current color mode),
        - * or color Array, or CSS color string
        - * @param  {Number}            [v2] green or saturation value
        - * @param  {Number}            [v3] blue or brightness value
        - * @param  {Number}            [a]  opacity
        - * @chainable
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *   noStroke();
        - *   fill(100, 100, 240);
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   box(75, 75, 75);
        - * }
        - * </code>
        - * </div>
        - *
        - * @alt
        - * black canvas with purple cube spinning
        - */
        -  fill(v1, v2, v3, a) {
        +   * Basic fill material for geometry with a given color
        +   * @param  {Number|Number[]|String|p5.Color} v1  gray value,
        +   * red or hue value (depending on the current color mode),
        +   * or color Array, or CSS color string
        +   * @param  {Number}            [v2] green or saturation value
        +   * @param  {Number}            [v3] blue or brightness value
        +   * @param  {Number}            [a]  opacity
        +   * @chainable
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *   noStroke();
        +   *   fill(100, 100, 240);
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *   box(75, 75, 75);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @alt
        +   * black canvas with purple cube spinning
        +   */
        +  fill(...args) {
        +    super.fill(...args);
             //see material.js for more info on color blending in webgl
        -    const color = p5.prototype.color.apply(this._pInst, arguments);
        -    this.curFillColor = color._array;
        -    this.drawMode = constants.FILL;
        -    this._useNormalMaterial = false;
        -    this._tex = null;
        +    // const color = fn.color.apply(this._pInst, arguments);
        +    const color = this.states.fillColor;
        +    this.states.curFillColor = color._array;
        +    this.states.drawMode = constants.FILL;
        +    this.states._useNormalMaterial = false;
        +    this.states._tex = null;
           }
         
           /**
        - * Basic stroke material for geometry with a given color
        - * @method  stroke
        - * @param  {Number|Number[]|String|p5.Color} v1  gray value,
        - * red or hue value (depending on the current color mode),
        - * or color Array, or CSS color string
        - * @param  {Number}            [v2] green or saturation value
        - * @param  {Number}            [v3] blue or brightness value
        - * @param  {Number}            [a]  opacity
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(200, 200, WEBGL);
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *   stroke(240, 150, 150);
        - *   fill(100, 100, 240);
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   box(75, 75, 75);
        - * }
        - * </code>
        - * </div>
        - *
        - * @alt
        - * black canvas with purple cube with pink outline spinning
        - */
        -  stroke(r, g, b, a) {
        -    const color = p5.prototype.color.apply(this._pInst, arguments);
        -    this.curStrokeColor = color._array;
        +   * Basic stroke material for geometry with a given color
        +   * @param  {Number|Number[]|String|p5.Color} v1  gray value,
        +   * red or hue value (depending on the current color mode),
        +   * or color Array, or CSS color string
        +   * @param  {Number}            [v2] green or saturation value
        +   * @param  {Number}            [v3] blue or brightness value
        +   * @param  {Number}            [a]  opacity
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(200, 200, WEBGL);
        +   * }
        +   *
        +   * function draw() {
        +   *   background(0);
        +   *   stroke(240, 150, 150);
        +   *   fill(100, 100, 240);
        +   *   rotateX(frameCount * 0.01);
        +   *   rotateY(frameCount * 0.01);
        +   *   box(75, 75, 75);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @alt
        +   * black canvas with purple cube with pink outline spinning
        +   */
        +  stroke(...args) {
        +    super.stroke(...args);
        +    // const color = fn.color.apply(this._pInst, arguments);
        +    this.states.curStrokeColor = this.states.strokeColor._array;
        +  }
        +
        +  getCommonVertexProperties() {
        +    return {
        +      ...super.getCommonVertexProperties(),
        +      stroke: this.states.strokeColor,
        +      fill: this.states.fillColor,
        +      normal: this.states._currentNormal,
        +    };
        +  }
        +
        +  getSupportedIndividualVertexProperties() {
        +    return {
        +      textureCoordinates: true,
        +    };
           }
         
           strokeCap(cap) {
        @@ -1030,13 +1130,13 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
           }
           getFilterLayer() {
             if (!this.filterLayer) {
        -      this.filterLayer = this._pInst.createFramebuffer();
        +      this.filterLayer = new Framebuffer(this);
             }
             return this.filterLayer;
           }
           getFilterLayerTemp() {
             if (!this.filterLayerTemp) {
        -      this.filterLayerTemp = this._pInst.createFramebuffer();
        +      this.filterLayerTemp = new Framebuffer(this);
             }
             return this.filterLayerTemp;
           }
        @@ -1053,38 +1153,31 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             }
           }
           filter(...args) {
        -
             let fbo = this.getFilterLayer();
         
             // use internal shader for filter constants BLUR, INVERT, etc
             let filterParameter = undefined;
             let operation = undefined;
        -    if (typeof args[0] === 'string') {
        +    if (typeof args[0] === "string") {
               operation = args[0];
        -      let defaults = {
        -        [constants.BLUR]: 3,
        -        [constants.POSTERIZE]: 4,
        -        [constants.THRESHOLD]: 0.5
        -      };
        -      let useDefaultParam = operation in defaults && args[1] === undefined;
        -      filterParameter = useDefaultParam ? defaults[operation] : args[1];
        +      let useDefaultParam = operation in filterParamDefaults && args[1] === undefined;
        +      filterParameter = useDefaultParam ? filterParamDefaults[operation] : args[1];
         
               // Create and store shader for constants once on initial filter call.
               // Need to store multiple in case user calls different filters,
               // eg. filter(BLUR) then filter(GRAY)
               if (!(operation in this.defaultFilterShaders)) {
        -        this.defaultFilterShaders[operation] = new p5.Shader(
        -          fbo._renderer,
        +        this.defaultFilterShaders[operation] = new Shader(
        +          fbo.renderer,
                   filterShaderVert,
        -          filterShaderFrags[operation]
        +          filterShaderFrags[operation],
                 );
               }
        -      this.filterShader = this.defaultFilterShaders[operation];
        -
        +      this.states.filterShader = this.defaultFilterShaders[operation];
             }
             // use custom user-supplied shader
             else {
        -      this.filterShader = args[0];
        +      this.states.filterShader = args[0];
             }
         
             // Setting the target to the framebuffer when applying a filter to a framebuffer.
        @@ -1094,11 +1187,11 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             // Resize the framebuffer 'fbo' and adjust its pixel density if it doesn't match the target.
             this.matchSize(fbo, target);
         
        -    fbo.draw(() => this._pInst.clear()); // prevent undesirable feedback effects accumulating secretly.
        +    fbo.draw(() => this.clear()); // prevent undesirable feedback effects accumulating secretly.
         
             let texelSize = [
               1 / (target.width * target.pixelDensity()),
        -      1 / (target.height * target.pixelDensity())
        +      1 / (target.height * target.pixelDensity()),
             ];
         
             // apply blur shader with multiple passes.
        @@ -1108,70 +1201,89 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               // Resize the framebuffer 'tmp' and adjust its pixel density if it doesn't match the target.
               this.matchSize(tmp, target);
               // setup
        -      this._pInst.push();
        -      this._pInst.noStroke();
        -      this._pInst.blendMode(constants.BLEND);
        +      this.push();
        +      this.states.strokeColor = null;
        +      this.blendMode(constants.BLEND);
         
               // draw main to temp buffer
        -      this._pInst.shader(this.filterShader);
        -      this.filterShader.setUniform('texelSize', texelSize);
        -      this.filterShader.setUniform('canvasSize', [target.width, target.height]);
        -      this.filterShader.setUniform('radius', Math.max(1, filterParameter));
        +      this.shader(this.states.filterShader);
        +      this.states.filterShader.setUniform("texelSize", texelSize);
        +      this.states.filterShader.setUniform("canvasSize", [
        +        target.width,
        +        target.height,
        +      ]);
        +      this.states.filterShader.setUniform(
        +        "radius",
        +        Math.max(1, filterParameter),
        +      );
         
               // Horiz pass: draw `target` to `tmp`
               tmp.draw(() => {
        -        this.filterShader.setUniform('direction', [1, 0]);
        -        this.filterShader.setUniform('tex0', target);
        -        this._pInst.clear();
        -        this._pInst.shader(this.filterShader);
        -        this._pInst.noLights();
        -        this._pInst.plane(target.width, target.height);
        +        this.states.filterShader.setUniform("direction", [1, 0]);
        +        this.states.filterShader.setUniform("tex0", target);
        +        this.clear();
        +        this.shader(this.states.filterShader);
        +        this.noLights();
        +        this.plane(target.width, target.height);
               });
         
               // Vert pass: draw `tmp` to `fbo`
               fbo.draw(() => {
        -        this.filterShader.setUniform('direction', [0, 1]);
        -        this.filterShader.setUniform('tex0', tmp);
        -        this._pInst.clear();
        -        this._pInst.shader(this.filterShader);
        -        this._pInst.noLights();
        -        this._pInst.plane(target.width, target.height);
        +        this.states.filterShader.setUniform("direction", [0, 1]);
        +        this.states.filterShader.setUniform("tex0", tmp);
        +        this.clear();
        +        this.shader(this.states.filterShader);
        +        this.noLights();
        +        this.plane(target.width, target.height);
               });
         
        -      this._pInst.pop();
        +      this.pop();
             }
             // every other non-blur shader uses single pass
             else {
               fbo.draw(() => {
        -        this._pInst.noStroke();
        -        this._pInst.blendMode(constants.BLEND);
        -        this._pInst.shader(this.filterShader);
        -        this.filterShader.setUniform('tex0', target);
        -        this.filterShader.setUniform('texelSize', texelSize);
        -        this.filterShader.setUniform('canvasSize', [target.width, target.height]);
        +        this.states.strokeColor = null;
        +        this.blendMode(constants.BLEND);
        +        this.shader(this.states.filterShader);
        +        this.states.filterShader.setUniform("tex0", target);
        +        this.states.filterShader.setUniform("texelSize", texelSize);
        +        this.states.filterShader.setUniform("canvasSize", [
        +          target.width,
        +          target.height,
        +        ]);
                 // filterParameter uniform only used for POSTERIZE, and THRESHOLD
                 // but shouldn't hurt to always set
        -        this.filterShader.setUniform('filterParameter', filterParameter);
        -        this._pInst.noLights();
        -        this._pInst.plane(target.width, target.height);
        +        this.states.filterShader.setUniform("filterParameter", filterParameter);
        +        this.noLights();
        +        this.plane(target.width, target.height);
               });
        -
             }
             // draw fbo contents onto main renderer.
        -    this._pInst.push();
        -    this._pInst.noStroke();
        +    this.push();
        +    this.states.strokeColor = null;
             this.clear();
        -    this._pInst.push();
        -    this._pInst.imageMode(constants.CORNER);
        -    this._pInst.blendMode(constants.BLEND);
        +    this.push();
        +    this.states.imageMode = constants.CORNER;
        +    this.blendMode(constants.BLEND);
             target.filterCamera._resize();
        -    this._pInst.setCamera(target.filterCamera);
        -    this._pInst.resetMatrix();
        -    this._pInst.image(fbo, -target.width / 2, -target.height / 2,
        -      target.width, target.height);
        -    this._pInst.clearDepth();
        -    this._pInst.pop();
        -    this._pInst.pop();
        +    this.setCamera(target.filterCamera);
        +    this.resetMatrix();
        +    this._drawingFilter = true;
        +    this.image(
        +      fbo,
        +      0,
        +      0,
        +      this.width,
        +      this.height,
        +      -target.width / 2,
        +      -target.height / 2,
        +      target.width,
        +      target.height,
        +    );
        +    this._drawingFilter = false;
        +    this.clearDepth();
        +    this.pop();
        +    this.pop();
           }
         
           // Pass this off to the host instance so that we can treat a renderer and a
        @@ -1197,7 +1309,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               mode === constants.MULTIPLY ||
               mode === constants.REMOVE
             )
        -      this.curBlendMode = mode;
        +      this.states.curBlendMode = mode;
             else if (
               mode === constants.BURN ||
               mode === constants.OVERLAY ||
        @@ -1206,30 +1318,30 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               mode === constants.DODGE
             ) {
               console.warn(
        -        'BURN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, and DODGE only work for blendMode in 2D mode.'
        +        "BURN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, and DODGE only work for blendMode in 2D mode.",
               );
             }
           }
         
           erase(opacityFill, opacityStroke) {
             if (!this._isErasing) {
        -      this.preEraseBlend = this.curBlendMode;
        +      this.preEraseBlend = this.states.curBlendMode;
               this._isErasing = true;
               this.blendMode(constants.REMOVE);
        -      this._cachedFillStyle = this.curFillColor.slice();
        -      this.curFillColor = [1, 1, 1, opacityFill / 255];
        -      this._cachedStrokeStyle = this.curStrokeColor.slice();
        -      this.curStrokeColor = [1, 1, 1, opacityStroke / 255];
        +      this._cachedFillStyle = this.states.curFillColor.slice();
        +      this.states.curFillColor = [1, 1, 1, opacityFill / 255];
        +      this._cachedStrokeStyle = this.states.curStrokeColor.slice();
        +      this.states.curStrokeColor = [1, 1, 1, opacityStroke / 255];
             }
           }
         
           noErase() {
             if (this._isErasing) {
               // Restore colors
        -      this.curFillColor = this._cachedFillStyle.slice();
        -      this.curStrokeColor = this._cachedStrokeStyle.slice();
        +      this.states.curFillColor = this._cachedFillStyle.slice();
        +      this.states.curStrokeColor = this._cachedStrokeStyle.slice();
               // Restore blend mode
        -      this.curBlendMode = this.preEraseBlend;
        +      this.states.curBlendMode = this.preEraseBlend;
               this.blendMode(this.preEraseBlend);
               // Ensure that _applyBlendMode() sets preEraseBlend back to the original blend mode
               this._isErasing = false;
        @@ -1254,34 +1366,34 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             gl.stencilFunc(
               gl.ALWAYS, // the test
               1, // reference value
        -      0xff // mask
        +      0xff, // mask
             );
             gl.stencilOp(
               gl.KEEP, // what to do if the stencil test fails
               gl.KEEP, // what to do if the depth test fails
        -      gl.REPLACE // what to do if both tests pass
        +      gl.REPLACE, // what to do if both tests pass
             );
             gl.disable(gl.DEPTH_TEST);
         
        -    this._pInst.push();
        -    this._pInst.resetShader();
        -    if (this._doFill) this._pInst.fill(0, 0);
        -    if (this._doStroke) this._pInst.stroke(0, 0);
        +    this.push();
        +    this.resetShader();
        +    if (this.states.fillColor) this.fill(0, 0);
        +    if (this.states.strokeColor) this.stroke(0, 0);
           }
         
           endClip() {
        -    this._pInst.pop();
        +    this.pop();
         
             const gl = this.GL;
             gl.stencilOp(
               gl.KEEP, // what to do if the stencil test fails
               gl.KEEP, // what to do if the depth test fails
        -      gl.KEEP // what to do if both tests pass
        +      gl.KEEP, // what to do if both tests pass
             );
             gl.stencilFunc(
               this._clipInvert ? gl.EQUAL : gl.NOTEQUAL, // the test
               0, // reference value
        -      0xff // mask
        +      0xff, // mask
             );
             gl.enable(gl.DEPTH_TEST);
         
        @@ -1301,52 +1413,6 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             this.drawTarget()._isClipApplied = false;
           }
         
        -  /**
        - * Change weight of stroke
        - * @method  strokeWeight
        - * @param  {Number} stroke weight to be used for drawing
        - * @example
        - * <div>
        - * <code>
        - * function setup() {
        - *   createCanvas(200, 400, WEBGL);
        - *   setAttributes('antialias', true);
        - * }
        - *
        - * function draw() {
        - *   background(0);
        - *   noStroke();
        - *   translate(0, -100, 0);
        - *   stroke(240, 150, 150);
        - *   fill(100, 100, 240);
        - *   push();
        - *   strokeWeight(8);
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   sphere(75);
        - *   pop();
        - *   push();
        - *   translate(0, 200, 0);
        - *   strokeWeight(1);
        - *   rotateX(frameCount * 0.01);
        - *   rotateY(frameCount * 0.01);
        - *   sphere(75);
        - *   pop();
        - * }
        - * </code>
        - * </div>
        - *
        - * @alt
        - * black canvas with two purple rotating spheres with pink
        - * outlines the sphere on top has much heavier outlines,
        - */
        -  strokeWeight(w) {
        -    if (this.curStrokeWeight !== w) {
        -      this.pointSize = w;
        -      this.curStrokeWeight = w;
        -    }
        -  }
        -
           // x,y are canvas-relative (pre-scaled by _pixelDensity)
           _getPixel(x, y) {
             const gl = this.GL;
        @@ -1357,90 +1423,92 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               y,
               gl.RGBA,
               gl.UNSIGNED_BYTE,
        -      this._pInst.height * this._pInst.pixelDensity()
        +      this._pInst.height * this._pInst.pixelDensity(),
             );
           }
         
           /**
        - * Loads the pixels data for this canvas into the pixels[] attribute.
        - * Note that updatePixels() and set() do not work.
        - * Any pixel manipulation must be done directly to the pixels[] array.
        - *
        - * @private
        - * @method loadPixels
        - */
        -
        +   * Loads the pixels data for this canvas into the pixels[] attribute.
        +   * Note that updatePixels() and set() do not work.
        +   * Any pixel manipulation must be done directly to the pixels[] array.
        +   *
        +   * @private
        +   */
           loadPixels() {
        -    const pixelsState = this._pixelsState;
        -
             //@todo_FES
             if (this._pInst._glAttributes.preserveDrawingBuffer !== true) {
               console.log(
        -        'loadPixels only works in WebGL when preserveDrawingBuffer ' + 'is true.'
        +        "loadPixels only works in WebGL when preserveDrawingBuffer " +
        +          "is true.",
               );
               return;
             }
         
        -    const pd = this._pInst._pixelDensity;
        +    const pd = this._pixelDensity;
             const gl = this.GL;
         
        -    pixelsState._setProperty(
        -      'pixels',
        -      readPixelsWebGL(
        -        pixelsState.pixels,
        -        gl,
        -        null,
        -        0,
        -        0,
        -        this.width * pd,
        -        this.height * pd,
        -        gl.RGBA,
        -        gl.UNSIGNED_BYTE,
        -        this.height * pd
        -      )
        +    this.pixels = readPixelsWebGL(
        +      this.pixels,
        +      gl,
        +      null,
        +      0,
        +      0,
        +      this.width * pd,
        +      this.height * pd,
        +      gl.RGBA,
        +      gl.UNSIGNED_BYTE,
        +      this.height * pd,
             );
           }
         
           updatePixels() {
             const fbo = this._getTempFramebuffer();
        -    fbo.pixels = this._pixelsState.pixels;
        +    fbo.pixels = this.pixels;
             fbo.updatePixels();
        -    this._pInst.push();
        -    this._pInst.resetMatrix();
        -    this._pInst.clear();
        -    this._pInst.imageMode(constants.CENTER);
        -    this._pInst.image(fbo, 0, 0);
        -    this._pInst.pop();
        +    this.push();
        +    this.resetMatrix();
        +    this.clear();
        +    this.states.imageMode = constants.CORNER;
        +    this.image(
        +      fbo,
        +      0,
        +      0,
        +      fbo.width,
        +      fbo.height,
        +      -fbo.width / 2,
        +      -fbo.height / 2,
        +      fbo.width,
        +      fbo.height,
        +    );
        +    this.pop();
             this.GL.clearDepth(1);
             this.GL.clear(this.GL.DEPTH_BUFFER_BIT);
           }
         
           /**
        - * @private
        - * @returns {p5.Framebuffer} A p5.Framebuffer set to match the size and settings
        - * of the renderer's canvas. It will be created if it does not yet exist, and
        - * reused if it does.
        - */
        +   * @private
        +   * @returns {p5.Framebuffer} A p5.Framebuffer set to match the size and settings
        +   * of the renderer's canvas. It will be created if it does not yet exist, and
        +   * reused if it does.
        +   */
           _getTempFramebuffer() {
             if (!this._tempFramebuffer) {
        -      this._tempFramebuffer = this._pInst.createFramebuffer({
        +      this._tempFramebuffer = new Framebuffer(this, {
                 format: constants.UNSIGNED_BYTE,
                 useDepth: this._pInst._glAttributes.depth,
                 depthFormat: constants.UNSIGNED_INT,
        -        antialias: this._pInst._glAttributes.antialias
        +        antialias: this._pInst._glAttributes.antialias,
               });
             }
             return this._tempFramebuffer;
           }
         
        -
        -
           //////////////////////////////////////////////
           // HASH | for geometry
           //////////////////////////////////////////////
         
        -  geometryInHash(gId) {
        -    return this.retainedMode.geometry[gId] !== undefined;
        +  geometryInHash(gid) {
        +    return this.geometryBufferCache.isCached(gid);
           }
         
           viewport(w, h) {
        @@ -1449,32 +1517,46 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
           }
         
           /**
        - * [resize description]
        - * @private
        - * @param  {Number} w [description]
        - * @param  {Number} h [description]
        - */
        +   * [resize description]
        +   * @private
        +   * @param  {Number} w [description]
        +   * @param  {Number} h [description]
        +   */
           resize(w, h) {
        -    p5.Renderer.prototype.resize.call(this, w, h);
        +    super.resize(w, h);
        +
        +    // save canvas properties
        +    const props = {};
        +    for (const key in this.drawingContext) {
        +      const val = this.drawingContext[key];
        +      if (typeof val !== "object" && typeof val !== "function") {
        +        props[key] = val;
        +      }
        +    }
        +
        +    const dimensions = this._adjustDimensions(w, h);
        +    w = dimensions.adjustedWidth;
        +    h = dimensions.adjustedHeight;
        +
        +    this.width = w;
        +    this.height = h;
        +
        +    this.canvas.width = w * this._pixelDensity;
        +    this.canvas.height = h * this._pixelDensity;
        +    this.canvas.style.width = `${w}px`;
        +    this.canvas.style.height = `${h}px`;
             this._origViewport = {
               width: this.GL.drawingBufferWidth,
        -      height: this.GL.drawingBufferHeight
        +      height: this.GL.drawingBufferHeight,
             };
        -    this.viewport(
        -      this._origViewport.width,
        -      this._origViewport.height
        -    );
        +    this.viewport(this._origViewport.width, this._origViewport.height);
         
        -    this._curCamera._resize();
        +    this.states.curCamera._resize();
         
             //resize pixels buffer
        -    const pixelsState = this._pixelsState;
        -    if (typeof pixelsState.pixels !== 'undefined') {
        -      pixelsState._setProperty(
        -        'pixels',
        -        new Uint8Array(
        -          this.GL.drawingBufferWidth * this.GL.drawingBufferHeight * 4
        -        )
        +    if (typeof this.pixels !== "undefined") {
        +      this.pixels = new Uint8Array(
        +        this.GL.drawingBufferWidth * this.GL.drawingBufferHeight * 4,
               );
             }
         
        @@ -1483,17 +1565,26 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               // can also update their size
               framebuffer._canvasSizeChanged();
             }
        +
        +    // reset canvas properties
        +    for (const savedKey in props) {
        +      try {
        +        this.drawingContext[savedKey] = props[savedKey];
        +      } catch (err) {
        +        // ignore read-only property errors
        +      }
        +    }
           }
         
           /**
        - * clears color and depth buffers
        - * with r,g,b,a
        - * @private
        - * @param {Number} r normalized red val.
        - * @param {Number} g normalized green val.
        - * @param {Number} b normalized blue val.
        - * @param {Number} a normalized alpha val.
        - */
        +   * clears color and depth buffers
        +   * with r,g,b,a
        +   * @private
        +   * @param {Number} r normalized red val.
        +   * @param {Number} g normalized green val.
        +   * @param {Number} b normalized blue val.
        +   * @param {Number} a normalized alpha val.
        +   */
           clear(...args) {
             const _r = args[0] || 0;
             const _g = args[1] || 0;
        @@ -1531,54 +1622,67 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
         
           applyMatrix(a, b, c, d, e, f) {
             if (arguments.length === 16) {
        -      p5.Matrix.prototype.apply.apply(this.uModelMatrix, arguments);
        +      // this.states.uModelMatrix.apply(arguments);
        +      Matrix.prototype.apply.apply(this.states.uModelMatrix, arguments);
             } else {
        -      this.uModelMatrix.apply([
        -        a, b, 0, 0,
        -        c, d, 0, 0,
        -        0, 0, 1, 0,
        -        e, f, 0, 1
        +      this.states.uModelMatrix.apply([
        +        a,
        +        b,
        +        0,
        +        0,
        +        c,
        +        d,
        +        0,
        +        0,
        +        0,
        +        0,
        +        1,
        +        0,
        +        e,
        +        f,
        +        0,
        +        1,
               ]);
             }
           }
         
           /**
        - * [translate description]
        - * @private
        - * @param  {Number} x [description]
        - * @param  {Number} y [description]
        - * @param  {Number} z [description]
        - * @chainable
        - * @todo implement handle for components or vector as args
        - */
        +   * [translate description]
        +   * @private
        +   * @param  {Number} x [description]
        +   * @param  {Number} y [description]
        +   * @param  {Number} z [description]
        +   * @chainable
        +   * @todo implement handle for components or vector as args
        +   */
           translate(x, y, z) {
        -    if (x instanceof p5.Vector) {
        +    if (x instanceof Vector) {
               z = x.z;
               y = x.y;
               x = x.x;
             }
        -    this.uModelMatrix.translate([x, y, z]);
        +    this.states.uModelMatrix.translate([x, y, z]);
             return this;
           }
         
        -  /**
        - * Scales the Model View Matrix by a vector
        - * @private
        - * @param  {Number | p5.Vector | Array} x [description]
        - * @param  {Number} [y] y-axis scalar
        - * @param  {Number} [z] z-axis scalar
        - * @chainable
        - */
        +  /**
        +   * Scales the Model View Matrix by a vector
        +   * @private
        +   * @param  {Number | p5.Vector | Array} x [description]
        +   * @param  {Number} [y] y-axis scalar
        +   * @param  {Number} [z] z-axis scalar
        +   * @chainable
        +   */
           scale(x, y, z) {
        -    this.uModelMatrix.scale(x, y, z);
        +    this.states.uModelMatrix.scale(x, y, z);
             return this;
           }
         
           rotate(rad, axis) {
        -    if (typeof axis === 'undefined') {
        +    if (typeof axis === "undefined") {
               return this.rotateZ(rad);
             }
        -    p5.Matrix.prototype.rotate.apply(this.uModelMatrix, arguments);
        +    Matrix.prototype.rotate4x4.apply(this.states.uModelMatrix, arguments);
             return this;
           }
         
        @@ -1597,80 +1701,6 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             return this;
           }
         
        -  push() {
        -    // get the base renderer style
        -    const style = p5.Renderer.prototype.push.apply(this);
        -
        -    // add webgl-specific style properties
        -    const properties = style.properties;
        -
        -    properties.uModelMatrix = this.uModelMatrix.copy();
        -    properties.uViewMatrix = this.uViewMatrix.copy();
        -    properties.uPMatrix = this.uPMatrix.copy();
        -    properties._curCamera = this._curCamera;
        -
        -    // make a copy of the current camera for the push state
        -    // this preserves any references stored using 'createCamera'
        -    this._curCamera = this._curCamera.copy();
        -
        -    properties.ambientLightColors = this.ambientLightColors.slice();
        -    properties.specularColors = this.specularColors.slice();
        -
        -    properties.directionalLightDirections =
        -      this.directionalLightDirections.slice();
        -    properties.directionalLightDiffuseColors =
        -      this.directionalLightDiffuseColors.slice();
        -    properties.directionalLightSpecularColors =
        -      this.directionalLightSpecularColors.slice();
        -
        -    properties.pointLightPositions = this.pointLightPositions.slice();
        -    properties.pointLightDiffuseColors = this.pointLightDiffuseColors.slice();
        -    properties.pointLightSpecularColors = this.pointLightSpecularColors.slice();
        -
        -    properties.spotLightPositions = this.spotLightPositions.slice();
        -    properties.spotLightDirections = this.spotLightDirections.slice();
        -    properties.spotLightDiffuseColors = this.spotLightDiffuseColors.slice();
        -    properties.spotLightSpecularColors = this.spotLightSpecularColors.slice();
        -    properties.spotLightAngle = this.spotLightAngle.slice();
        -    properties.spotLightConc = this.spotLightConc.slice();
        -
        -    properties.userFillShader = this.userFillShader;
        -    properties.userStrokeShader = this.userStrokeShader;
        -    properties.userPointShader = this.userPointShader;
        -
        -    properties.pointSize = this.pointSize;
        -    properties.curStrokeWeight = this.curStrokeWeight;
        -    properties.curStrokeColor = this.curStrokeColor;
        -    properties.curFillColor = this.curFillColor;
        -    properties.curAmbientColor = this.curAmbientColor;
        -    properties.curSpecularColor = this.curSpecularColor;
        -    properties.curEmissiveColor = this.curEmissiveColor;
        -
        -    properties._hasSetAmbient = this._hasSetAmbient;
        -    properties._useSpecularMaterial = this._useSpecularMaterial;
        -    properties._useEmissiveMaterial = this._useEmissiveMaterial;
        -    properties._useShininess = this._useShininess;
        -    properties._useMetalness = this._useMetalness;
        -
        -    properties.constantAttenuation = this.constantAttenuation;
        -    properties.linearAttenuation = this.linearAttenuation;
        -    properties.quadraticAttenuation = this.quadraticAttenuation;
        -
        -    properties._enableLighting = this._enableLighting;
        -    properties._useNormalMaterial = this._useNormalMaterial;
        -    properties._tex = this._tex;
        -    properties.drawMode = this.drawMode;
        -
        -    properties._currentNormal = this._currentNormal;
        -    properties.curBlendMode = this.curBlendMode;
        -
        -    // So that the activeImageLight gets reset in push/pop
        -    properties.activeImageLight = this.activeImageLight;
        -
        -    properties.textureMode = this.textureMode;
        -
        -    return style;
        -  }
           pop(...args) {
             if (
               this._clipDepths.length > 0 &&
        @@ -1694,8 +1724,8 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             }
           }
           resetMatrix() {
        -    this.uModelMatrix.reset();
        -    this.uViewMatrix.set(this._curCamera.cameraMatrix);
        +    this.states.uModelMatrix.reset();
        +    this.states.uViewMatrix.set(this.states.curCamera.cameraMatrix);
             return this;
           }
         
        @@ -1704,106 +1734,77 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
           //////////////////////////////////////////////
         
           /*
        - * shaders are created and cached on a per-renderer basis,
        - * on the grounds that each renderer will have its own gl context
        - * and the shader must be valid in that context.
        - */
        +   * shaders are created and cached on a per-renderer basis,
        +   * on the grounds that each renderer will have its own gl context
        +   * and the shader must be valid in that context.
        +   */
         
        -  _getImmediateStrokeShader() {
        +  _getStrokeShader() {
             // select the stroke shader to use
        -    const stroke = this.userStrokeShader;
        -    if (!stroke || !stroke.isStrokeShader()) {
        -      return this._getLineShader();
        +    const stroke = this.states.userStrokeShader;
        +    if (stroke) {
        +      return stroke;
             }
        -    return stroke;
        -  }
        -
        -
        -  _getRetainedStrokeShader() {
        -    return this._getImmediateStrokeShader();
        +    return this._getLineShader();
           }
         
           _getSphereMapping(img) {
             if (!this.sphereMapping) {
        -      this.sphereMapping = this._pInst.createFilterShader(
        -        sphereMapping
        -      );
        -    }
        -    this.uNMatrix.inverseTranspose(this.uViewMatrix);
        -    this.uNMatrix.invert3x3(this.uNMatrix);
        -    this.sphereMapping.setUniform('uFovY', this._curCamera.cameraFOV);
        -    this.sphereMapping.setUniform('uAspect', this._curCamera.aspectRatio);
        -    this.sphereMapping.setUniform('uNewNormalMatrix', this.uNMatrix.mat3);
        -    this.sphereMapping.setUniform('uSampler', img);
        +      this.sphereMapping = this._pInst.createFilterShader(sphereMapping);
        +    }
        +    this.scratchMat3.inverseTranspose4x4(this.states.uViewMatrix);
        +    this.scratchMat3.invert(this.scratchMat3); // uNMMatrix is 3x3
        +    this.sphereMapping.setUniform("uFovY", this.states.curCamera.cameraFOV);
        +    this.sphereMapping.setUniform("uAspect", this.states.curCamera.aspectRatio);
        +    this.sphereMapping.setUniform("uNewNormalMatrix", this.scratchMat3.mat3);
        +    this.sphereMapping.setUniform("uSampler", img);
             return this.sphereMapping;
           }
         
           /*
        -   * selects which fill shader should be used based on renderer state,
        -   * for use with begin/endShape and immediate vertex mode.
        +   * This method will handle both image shaders and
        +   * fill shaders, returning the appropriate shader
        +   * depending on the current context (image or shape).
            */
        -  _getImmediateFillShader() {
        -    const fill = this.userFillShader;
        -    if (this._useNormalMaterial) {
        -      if (!fill || !fill.isNormalShader()) {
        -        return this._getNormalShader();
        +  _getFillShader() {
        +    // If drawing an image, check for user-defined image shader and filters
        +    if (this._drawingImage) {
        +      // Use user-defined image shader if available and no filter is applied
        +      if (this.states.userImageShader && !this._drawingFilter) {
        +        return this.states.userImageShader;
        +      } else {
        +        return this._getLightShader(); // Fallback to light shader
               }
             }
        -    if (this._enableLighting) {
        -      if (!fill || !fill.isLightShader()) {
        -        return this._getLightShader();
        -      }
        -    } else if (this._tex) {
        -      if (!fill || !fill.isTextureShader()) {
        -        return this._getLightShader();
        -      }
        -    } else if (!fill /*|| !fill.isColorShader()*/) {
        -      return this._getImmediateModeShader();
        +    // If user has defined a fill shader, return that
        +    else if (this.states.userFillShader) {
        +      return this.states.userFillShader;
             }
        -    return fill;
        -  }
        -
        -  /*
        -   * selects which fill shader should be used based on renderer state
        -   * for retained mode.
        -   */
        -  _getRetainedFillShader() {
        -    if (this._useNormalMaterial) {
        +    // Use normal shader if normal material is active
        +    else if (this.states._useNormalMaterial) {
               return this._getNormalShader();
             }
        -
        -    const fill = this.userFillShader;
        -    if (this._enableLighting) {
        -      if (!fill || !fill.isLightShader()) {
        -        return this._getLightShader();
        -      }
        -    } else if (this._tex) {
        -      if (!fill || !fill.isTextureShader()) {
        -        return this._getLightShader();
        -      }
        -    } else if (!fill /* || !fill.isColorShader()*/) {
        -      return this._getColorShader();
        +    // Use light shader if lighting or textures are enabled
        +    else if (this.states.enableLighting || this.states._tex) {
        +      return this._getLightShader();
             }
        -    return fill;
        +    // Default to color shader if no other conditions are met
        +    return this._getColorShader();
           }
         
        -  _getImmediatePointShader() {
        +  _getPointShader() {
             // select the point shader to use
        -    const point = this.userPointShader;
        +    const point = this.states.userPointShader;
             if (!point || !point.isPointShader()) {
               return this._getPointShader();
             }
             return point;
           }
         
        -  _getRetainedLineShader() {
        -    return this._getImmediateLineShader();
        -  }
        -
           baseMaterialShader() {
             if (!this._pInst._glAttributes.perPixelLighting) {
               throw new Error(
        -        'The material shader does not support hooks without perPixelLighting. Try turning it back on.'
        +        "The material shader does not support hooks without perPixelLighting. Try turning it back on.",
               );
             }
             return this._getLightShader();
        @@ -1812,27 +1813,24 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
           _getLightShader() {
             if (!this._defaultLightShader) {
               if (this._pInst._glAttributes.perPixelLighting) {
        -        this._defaultLightShader = new p5.Shader(
        +        this._defaultLightShader = new Shader(
                   this,
        -          this._webGL2CompatibilityPrefix('vert', 'highp') +
        -          defaultShaders.phongVert,
        -          this._webGL2CompatibilityPrefix('frag', 'highp') +
        -          defaultShaders.phongFrag,
        +          this._webGL2CompatibilityPrefix("vert", "highp") +
        +            defaultShaders.phongVert,
        +          this._webGL2CompatibilityPrefix("frag", "highp") +
        +            defaultShaders.phongFrag,
                   {
                     vertex: {
        -              'void beforeVertex': '() {}',
        -              'vec3 getLocalPosition': '(vec3 position) { return position; }',
        -              'vec3 getWorldPosition': '(vec3 position) { return position; }',
        -              'vec3 getLocalNormal': '(vec3 normal) { return normal; }',
        -              'vec3 getWorldNormal': '(vec3 normal) { return normal; }',
        -              'vec2 getUV': '(vec2 uv) { return uv; }',
        -              'vec4 getVertexColor': '(vec4 color) { return color; }',
        -              'void afterVertex': '() {}'
        +              "void beforeVertex": "() {}",
        +              "Vertex getObjectInputs": "(Vertex inputs) { return inputs; }",
        +              "Vertex getWorldInputs": "(Vertex inputs) { return inputs; }",
        +              "Vertex getCameraInputs": "(Vertex inputs) { return inputs; }",
        +              "void afterVertex": "() {}",
                     },
                     fragment: {
        -              'void beforeFragment': '() {}',
        -              'Inputs getPixelInputs': '(Inputs inputs) { return inputs; }',
        -              'vec4 combineColors': `(ColorComponents components) {
        +              "void beforeFragment": "() {}",
        +              "Inputs getPixelInputs": "(Inputs inputs) { return inputs; }",
        +              "vec4 combineColors": `(ColorComponents components) {
                         vec4 color = vec4(0.);
                         color.rgb += components.diffuse * components.baseColor;
                         color.rgb += components.ambient * components.ambientColor;
        @@ -1841,18 +1839,18 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
                         color.a = components.opacity;
                         return color;
                       }`,
        -              'vec4 getFinalColor': '(vec4 color) { return color; }',
        -              'void afterFragment': '() {}'
        -            }
        -          }
        +              "vec4 getFinalColor": "(vec4 color) { return color; }",
        +              "void afterFragment": "() {}",
        +            },
        +          },
                 );
               } else {
        -        this._defaultLightShader = new p5.Shader(
        +        this._defaultLightShader = new Shader(
                   this,
        -          this._webGL2CompatibilityPrefix('vert', 'highp') +
        -          defaultShaders.lightVert,
        -          this._webGL2CompatibilityPrefix('frag', 'highp') +
        -          defaultShaders.lightTextureFrag
        +          this._webGL2CompatibilityPrefix("vert", "highp") +
        +            defaultShaders.lightVert,
        +          this._webGL2CompatibilityPrefix("frag", "highp") +
        +            defaultShaders.lightTextureFrag,
                 );
               }
             }
        @@ -1860,49 +1858,32 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             return this._defaultLightShader;
           }
         
        -  _getImmediateModeShader() {
        -    if (!this._defaultImmediateModeShader) {
        -      this._defaultImmediateModeShader = new p5.Shader(
        -        this,
        -        this._webGL2CompatibilityPrefix('vert', 'mediump') +
        -        defaultShaders.immediateVert,
        -        this._webGL2CompatibilityPrefix('frag', 'mediump') +
        -        defaultShaders.vertexColorFrag
        -      );
        -    }
        -
        -    return this._defaultImmediateModeShader;
        -  }
        -
           baseNormalShader() {
             return this._getNormalShader();
           }
         
           _getNormalShader() {
             if (!this._defaultNormalShader) {
        -      this._defaultNormalShader = new p5.Shader(
        +      this._defaultNormalShader = new Shader(
                 this,
        -        this._webGL2CompatibilityPrefix('vert', 'mediump') +
        -        defaultShaders.normalVert,
        -        this._webGL2CompatibilityPrefix('frag', 'mediump') +
        -        defaultShaders.normalFrag,
        +        this._webGL2CompatibilityPrefix("vert", "mediump") +
        +          defaultShaders.normalVert,
        +        this._webGL2CompatibilityPrefix("frag", "mediump") +
        +          defaultShaders.normalFrag,
                 {
                   vertex: {
        -            'void beforeVertex': '() {}',
        -            'vec3 getLocalPosition': '(vec3 position) { return position; }',
        -            'vec3 getWorldPosition': '(vec3 position) { return position; }',
        -            'vec3 getLocalNormal': '(vec3 normal) { return normal; }',
        -            'vec3 getWorldNormal': '(vec3 normal) { return normal; }',
        -            'vec2 getUV': '(vec2 uv) { return uv; }',
        -            'vec4 getVertexColor': '(vec4 color) { return color; }',
        -            'void afterVertex': '() {}'
        +            "void beforeVertex": "() {}",
        +            "Vertex getObjectInputs": "(Vertex inputs) { return inputs; }",
        +            "Vertex getWorldInputs": "(Vertex inputs) { return inputs; }",
        +            "Vertex getCameraInputs": "(Vertex inputs) { return inputs; }",
        +            "void afterVertex": "() {}",
                   },
                   fragment: {
        -            'void beforeFragment': '() {}',
        -            'vec4 getFinalColor': '(vec4 color) { return color; }',
        -            'void afterFragment': '() {}'
        -          }
        -        }
        +            "void beforeFragment": "() {}",
        +            "vec4 getFinalColor": "(vec4 color) { return color; }",
        +            "void afterFragment": "() {}",
        +          },
        +        },
               );
             }
         
        @@ -1915,29 +1896,26 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
         
           _getColorShader() {
             if (!this._defaultColorShader) {
        -      this._defaultColorShader = new p5.Shader(
        +      this._defaultColorShader = new Shader(
                 this,
        -        this._webGL2CompatibilityPrefix('vert', 'mediump') +
        -        defaultShaders.normalVert,
        -        this._webGL2CompatibilityPrefix('frag', 'mediump') +
        -        defaultShaders.basicFrag,
        +        this._webGL2CompatibilityPrefix("vert", "mediump") +
        +          defaultShaders.normalVert,
        +        this._webGL2CompatibilityPrefix("frag", "mediump") +
        +          defaultShaders.basicFrag,
                 {
                   vertex: {
        -            'void beforeVertex': '() {}',
        -            'vec3 getLocalPosition': '(vec3 position) { return position; }',
        -            'vec3 getWorldPosition': '(vec3 position) { return position; }',
        -            'vec3 getLocalNormal': '(vec3 normal) { return normal; }',
        -            'vec3 getWorldNormal': '(vec3 normal) { return normal; }',
        -            'vec2 getUV': '(vec2 uv) { return uv; }',
        -            'vec4 getVertexColor': '(vec4 color) { return color; }',
        -            'void afterVertex': '() {}'
        +            "void beforeVertex": "() {}",
        +            "Vertex getObjectInputs": "(Vertex inputs) { return inputs; }",
        +            "Vertex getWorldInputs": "(Vertex inputs) { return inputs; }",
        +            "Vertex getCameraInputs": "(Vertex inputs) { return inputs; }",
        +            "void afterVertex": "() {}",
                   },
                   fragment: {
        -            'void beforeFragment': '() {}',
        -            'vec4 getFinalColor': '(vec4 color) { return color; }',
        -            'void afterFragment': '() {}'
        -          }
        -        }
        +            "void beforeFragment": "() {}",
        +            "vec4 getFinalColor": "(vec4 color) { return color; }",
        +            "void afterFragment": "() {}",
        +          },
        +        },
               );
             }
         
        @@ -1974,27 +1952,27 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
         
           _getPointShader() {
             if (!this._defaultPointShader) {
        -      this._defaultPointShader = new p5.Shader(
        +      this._defaultPointShader = new Shader(
                 this,
        -        this._webGL2CompatibilityPrefix('vert', 'mediump') +
        -        defaultShaders.pointVert,
        -        this._webGL2CompatibilityPrefix('frag', 'mediump') +
        -        defaultShaders.pointFrag,
        +        this._webGL2CompatibilityPrefix("vert", "mediump") +
        +          defaultShaders.pointVert,
        +        this._webGL2CompatibilityPrefix("frag", "mediump") +
        +          defaultShaders.pointFrag,
                 {
                   vertex: {
        -            'void beforeVertex': '() {}',
        -            'vec3 getLocalPosition': '(vec3 position) { return position; }',
        -            'vec3 getWorldPosition': '(vec3 position) { return position; }',
        -            'float getPointSize': '(float size) { return size; }',
        -            'void afterVertex': '() {}'
        +            "void beforeVertex": "() {}",
        +            "vec3 getLocalPosition": "(vec3 position) { return position; }",
        +            "vec3 getWorldPosition": "(vec3 position) { return position; }",
        +            "float getPointSize": "(float size) { return size; }",
        +            "void afterVertex": "() {}",
                   },
                   fragment: {
        -            'void beforeFragment': '() {}',
        -            'vec4 getFinalColor': '(vec4 color) { return color; }',
        -            'bool shouldDiscard': '(bool outside) { return outside; }',
        -            'void afterFragment': '() {}'
        -          }
        -        }
        +            "void beforeFragment": "() {}",
        +            "vec4 getFinalColor": "(vec4 color) { return color; }",
        +            "bool shouldDiscard": "(bool outside) { return outside; }",
        +            "void afterFragment": "() {}",
        +          },
        +        },
               );
             }
             return this._defaultPointShader;
        @@ -2006,31 +1984,28 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
         
           _getLineShader() {
             if (!this._defaultLineShader) {
        -      this._defaultLineShader = new p5.Shader(
        +      this._defaultLineShader = new Shader(
                 this,
        -        this._webGL2CompatibilityPrefix('vert', 'mediump') +
        -        defaultShaders.lineVert,
        -        this._webGL2CompatibilityPrefix('frag', 'mediump') +
        -        defaultShaders.lineFrag,
        +        this._webGL2CompatibilityPrefix("vert", "mediump") +
        +          defaultShaders.lineVert,
        +        this._webGL2CompatibilityPrefix("frag", "mediump") +
        +          defaultShaders.lineFrag,
                 {
                   vertex: {
        -            'void beforeVertex': '() {}',
        -            'vec3 getLocalPosition': '(vec3 position) { return position; }',
        -            'vec3 getWorldPosition': '(vec3 position) { return position; }',
        -            'float getStrokeWeight': '(float weight) { return weight; }',
        -            'vec2 getLineCenter': '(vec2 center) { return center; }',
        -            'vec2 getLinePosition': '(vec2 position) { return position; }',
        -            'vec4 getVertexColor': '(vec4 color) { return color; }',
        -            'void afterVertex': '() {}'
        +            "void beforeVertex": "() {}",
        +            "StrokeVertex getObjectInputs": "(StrokeVertex inputs) { return inputs; }",
        +            "StrokeVertex getWorldInputs": "(StrokeVertex inputs) { return inputs; }",
        +            "StrokeVertex getCameraInputs": "(StrokeVertex inputs) { return inputs; }",
        +            "void afterVertex": "() {}",
                   },
                   fragment: {
        -            'void beforeFragment': '() {}',
        -            'Inputs getPixelInputs': '(Inputs inputs) { return inputs; }',
        -            'vec4 getFinalColor': '(vec4 color) { return color; }',
        -            'bool shouldDiscard': '(bool outside) { return outside; }',
        -            'void afterFragment': '() {}'
        -          }
        -        }
        +            "void beforeFragment": "() {}",
        +            "Inputs getPixelInputs": "(Inputs inputs) { return inputs; }",
        +            "vec4 getFinalColor": "(vec4 color) { return color; }",
        +            "bool shouldDiscard": "(bool outside) { return outside; }",
        +            "void afterFragment": "() {}",
        +          },
        +        },
               );
             }
         
        @@ -2040,9 +2015,9 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
           _getFontShader() {
             if (!this._defaultFontShader) {
               if (this.webglVersion === constants.WEBGL) {
        -        this.GL.getExtension('OES_standard_derivatives');
        +        this.GL.getExtension("OES_standard_derivatives");
               }
        -      this._defaultFontShader = new p5.Shader(
        +      this._defaultFontShader = new Shader(
                 this,
                 this._webGL2CompatibilityPrefix('vert', 'highp') +
                 defaultShaders.fontVert,
        @@ -2053,18 +2028,15 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             return this._defaultFontShader;
           }
         
        -  _webGL2CompatibilityPrefix(
        -    shaderType,
        -    floatPrecision
        -  ) {
        -    let code = '';
        +  _webGL2CompatibilityPrefix(shaderType, floatPrecision) {
        +    let code = "";
             if (this.webglVersion === constants.WEBGL2) {
        -      code += '#version 300 es\n#define WEBGL2\n';
        +      code += "#version 300 es\n#define WEBGL2\n";
             }
        -    if (shaderType === 'vert') {
        -      code += '#define VERTEX_SHADER\n';
        -    } else if (shaderType === 'frag') {
        -      code += '#define FRAGMENT_SHADER\n';
        +    if (shaderType === "vert") {
        +      code += "#define VERTEX_SHADER\n";
        +    } else if (shaderType === "frag") {
        +      code += "#define FRAGMENT_SHADER\n";
             }
             if (floatPrecision) {
               code += `precision ${floatPrecision} float;\n`;
        @@ -2072,19 +2044,25 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             return code;
           }
         
        +  /**
        +   * @private
        +   * Note: DO NOT CALL THIS while in the middle of binding another texture,
        +   * since it will change the texture binding in order to allocate the empty
        +   * texture! Grab its value beforehand!
        +   */
           _getEmptyTexture() {
             if (!this._emptyTexture) {
               // a plain white texture RGBA, full alpha, single pixel.
        -      const im = new p5.Image(1, 1);
        +      const im = new Image(1, 1);
               im.set(0, 0, 255);
        -      this._emptyTexture = new p5.Texture(this, im);
        +      this._emptyTexture = new Texture(this, im);
             }
             return this._emptyTexture;
           }
         
           getTexture(input) {
             let src = input;
        -    if (src instanceof p5.Framebuffer) {
        +    if (src instanceof Framebuffer) {
               src = src.color;
             }
         
        @@ -2093,16 +2071,16 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               return texture;
             }
         
        -    const tex = new p5.Texture(this, src);
        +    const tex = new Texture(this, src);
             this.textures.set(src, tex);
             return tex;
           }
           /*
        -    *  used in imageLight,
        -    *  To create a blurry image from the input non blurry img, if it doesn't already exist
        -    *  Add it to the diffusedTexture map,
        -    *  Returns the blurry image
        -    *  maps a p5.Image used by imageLight() to a p5.Framebuffer
        +   *  used in imageLight,
        +   *  To create a blurry image from the input non blurry img, if it doesn't already exist
        +   *  Add it to the diffusedTexture map,
        +   *  Returns the blurry image
        +   *  maps a Image used by imageLight() to a p5.Framebuffer
            */
           getDiffusedTexture(input) {
             // if one already exists for a given input image
        @@ -2115,24 +2093,25 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             let smallWidth = 200;
             let width = smallWidth;
             let height = Math.floor(smallWidth * (input.height / input.width));
        -    newFramebuffer = this._pInst.createFramebuffer({
        -      width, height, density: 1
        +    newFramebuffer = new Framebuffer(this, {
        +      width,
        +      height,
        +      density: 1,
             });
             // create framebuffer is like making a new sketch, all functions on main
             // sketch it would be available on framebuffer
        -    if (!this.diffusedShader) {
        -      this.diffusedShader = this._pInst.createShader(
        +    if (!this.states.diffusedShader) {
        +      this.states.diffusedShader = this._pInst.createShader(
                 defaultShaders.imageLightVert,
        -        defaultShaders.imageLightDiffusedFrag
        +        defaultShaders.imageLightDiffusedFrag,
               );
             }
             newFramebuffer.draw(() => {
        -      this._pInst.shader(this.diffusedShader);
        -      this.diffusedShader.setUniform('environmentMap', input);
        -      this._pInst.noStroke();
        -      this._pInst.rectMode(constants.CENTER);
        -      this._pInst.noLights();
        -      this._pInst.rect(0, 0, width, height);
        +      this.shader(this.states.diffusedShader);
        +      this.states.diffusedShader.setUniform("environmentMap", input);
        +      this.states.strokeColor = null;
        +      this.noLights();
        +      this.plane(width, height);
             });
             this.diffusedTextures.set(input, newFramebuffer);
             return newFramebuffer;
        @@ -2145,7 +2124,7 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
            *  sizes and atoring them in `levels` array
            *  Creating a new Mipmap texture with that `levels` array
            *  Storing the texture for input image in map called `specularTextures`
        -   *  maps the input p5.Image to a p5.MipmapTexture
        +   *  maps the input Image to a p5.MipmapTexture
            */
           getSpecularTexture(input) {
             // check if already exits (there are tex of diff resolution so which one to check)
        @@ -2157,14 +2136,16 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
             const size = 512;
             let tex;
             const levels = [];
        -    const framebuffer = this._pInst.createFramebuffer({
        -      width: size, height: size, density: 1
        +    const framebuffer = new Framebuffer(this, {
        +      width: size,
        +      height: size,
        +      density: 1,
             });
             let count = Math.log(size) / Math.log(2);
        -    if (!this.specularShader) {
        -      this.specularShader = this._pInst.createShader(
        +    if (!this.states.specularShader) {
        +      this.states.specularShader = this._pInst.createShader(
                 defaultShaders.imageLightVert,
        -        defaultShaders.imageLightSpecularFrag
        +        defaultShaders.imageLightSpecularFrag,
               );
             }
             // currently only 8 levels
        @@ -2177,13 +2158,13 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               let currCount = Math.log(w) / Math.log(2);
               let roughness = 1 - currCount / count;
               framebuffer.draw(() => {
        -        this._pInst.shader(this.specularShader);
        -        this._pInst.clear();
        -        this.specularShader.setUniform('environmentMap', input);
        -        this.specularShader.setUniform('roughness', roughness);
        -        this._pInst.noStroke();
        -        this._pInst.noLights();
        -        this._pInst.plane(w, w);
        +        this.shader(this.states.specularShader);
        +        this.clear();
        +        this.states.specularShader.setUniform("environmentMap", input);
        +        this.states.specularShader.setUniform("roughness", roughness);
        +        this.states.strokeColor = null;
        +        this.noLights();
        +        this.plane(w, w);
               });
               levels.push(framebuffer.get().drawingContext.getImageData(0, 0, w, w));
             }
        @@ -2195,7 +2176,6 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
           }
         
           /**
        -   * @method activeFramebuffer
            * @private
            * @returns {p5.Framebuffer|null} The currently active framebuffer, or null if
            * the main canvas is the current draw target.
        @@ -2205,156 +2185,206 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
           }
         
           createFramebuffer(options) {
        -    return new p5.Framebuffer(this, options);
        +    return new Framebuffer(this, options);
           }
         
        -  _setStrokeUniforms(baseStrokeShader) {
        -    baseStrokeShader.bindShader();
        +  _setGlobalUniforms(shader) {
        +    const modelMatrix = this.states.uModelMatrix;
        +    const viewMatrix = this.states.uViewMatrix;
        +    const projectionMatrix = this.states.uPMatrix;
        +    const modelViewMatrix = this.calculateCombinedMatrix();
        +
        +    shader.setUniform(
        +      "uPerspective",
        +      this.states.curCamera.useLinePerspective ? 1 : 0,
        +    );
        +    shader.setUniform("uViewMatrix", viewMatrix.mat4);
        +    shader.setUniform("uProjectionMatrix", projectionMatrix.mat4);
        +    shader.setUniform("uModelMatrix", modelMatrix.mat4);
        +    shader.setUniform("uModelViewMatrix", modelViewMatrix.mat4);
        +    if (shader.uniforms.uModelViewProjectionMatrix) {
        +      const modelViewProjectionMatrix = modelViewMatrix.copy();
        +      modelViewProjectionMatrix.mult(projectionMatrix);
        +      shader.setUniform(
        +        "uModelViewProjectionMatrix",
        +        modelViewProjectionMatrix.mat4,
        +      );
        +    }
        +    if (shader.uniforms.uNormalMatrix) {
        +      this.scratchMat3.inverseTranspose4x4(modelViewMatrix);
        +      shader.setUniform("uNormalMatrix", this.scratchMat3.mat3);
        +    }
        +    if (shader.uniforms.uModelNormalMatrix) {
        +      this.scratchMat3.inverseTranspose4x4(this.states.uModelMatrix);
        +      shader.setUniform("uModelNormalMatrix", this.scratchMat3.mat3);
        +    }
        +    if (shader.uniforms.uCameraNormalMatrix) {
        +      this.scratchMat3.inverseTranspose4x4(this.states.uViewMatrix);
        +      shader.setUniform("uCameraNormalMatrix", this.scratchMat3.mat3);
        +    }
        +    if (shader.uniforms.uCameraRotation) {
        +      this.scratchMat3.inverseTranspose4x4(this.states.uViewMatrix);
        +      shader.setUniform("uCameraRotation", this.scratchMat3.mat3);
        +    }
        +    shader.setUniform("uViewport", this._viewport);
        +  }
         
        +  _setStrokeUniforms(strokeShader) {
             // set the uniform values
        -    baseStrokeShader.setUniform('uUseLineColor', this._useLineColor);
        -    baseStrokeShader.setUniform('uMaterialColor', this.curStrokeColor);
        -    baseStrokeShader.setUniform('uStrokeWeight', this.curStrokeWeight);
        -    baseStrokeShader.setUniform('uStrokeCap', STROKE_CAP_ENUM[this.curStrokeCap]);
        -    baseStrokeShader.setUniform('uStrokeJoin', STROKE_JOIN_ENUM[this.curStrokeJoin]);
        +    strokeShader.setUniform("uSimpleLines", this._simpleLines);
        +    strokeShader.setUniform("uUseLineColor", this._useLineColor);
        +    strokeShader.setUniform("uMaterialColor", this.states.curStrokeColor);
        +    strokeShader.setUniform("uStrokeWeight", this.states.strokeWeight);
        +    strokeShader.setUniform("uStrokeCap", STROKE_CAP_ENUM[this.curStrokeCap]);
        +    strokeShader.setUniform(
        +      "uStrokeJoin",
        +      STROKE_JOIN_ENUM[this.curStrokeJoin],
        +    );
           }
         
           _setFillUniforms(fillShader) {
        -    fillShader.bindShader();
        +    this.mixedSpecularColor = [...this.states.curSpecularColor];
         
        -    this.mixedSpecularColor = [...this.curSpecularColor];
        -
        -    if (this._useMetalness > 0) {
        +    if (this.states._useMetalness > 0) {
               this.mixedSpecularColor = this.mixedSpecularColor.map(
                 (mixedSpecularColor, index) =>
        -          this.curFillColor[index] * this._useMetalness +
        -          mixedSpecularColor * (1 - this._useMetalness)
        +          this.states.curFillColor[index] * this.states._useMetalness +
        +          mixedSpecularColor * (1 - this.states._useMetalness),
               );
             }
         
             // TODO: optimize
        -    fillShader.setUniform('uUseVertexColor', this._useVertexColor);
        -    fillShader.setUniform('uMaterialColor', this.curFillColor);
        -    fillShader.setUniform('isTexture', !!this._tex);
        -    if (this._tex) {
        -      fillShader.setUniform('uSampler', this._tex);
        -    }
        -    fillShader.setUniform('uTint', this._tint);
        -
        -    fillShader.setUniform('uHasSetAmbient', this._hasSetAmbient);
        -    fillShader.setUniform('uAmbientMatColor', this.curAmbientColor);
        -    fillShader.setUniform('uSpecularMatColor', this.mixedSpecularColor);
        -    fillShader.setUniform('uEmissiveMatColor', this.curEmissiveColor);
        -    fillShader.setUniform('uSpecular', this._useSpecularMaterial);
        -    fillShader.setUniform('uEmissive', this._useEmissiveMaterial);
        -    fillShader.setUniform('uShininess', this._useShininess);
        -    fillShader.setUniform('uMetallic', this._useMetalness);
        +    fillShader.setUniform("uUseVertexColor", this._useVertexColor);
        +    fillShader.setUniform("uMaterialColor", this.states.curFillColor);
        +    fillShader.setUniform("isTexture", !!this.states._tex);
        +    if (this.states._tex) {
        +      fillShader.setUniform("uSampler", this.states._tex);
        +    }
        +    fillShader.setUniform("uTint", this.states.tint);
        +
        +    fillShader.setUniform("uHasSetAmbient", this.states._hasSetAmbient);
        +    fillShader.setUniform("uAmbientMatColor", this.states.curAmbientColor);
        +    fillShader.setUniform("uSpecularMatColor", this.mixedSpecularColor);
        +    fillShader.setUniform("uEmissiveMatColor", this.states.curEmissiveColor);
        +    fillShader.setUniform("uSpecular", this.states._useSpecularMaterial);
        +    fillShader.setUniform("uEmissive", this.states._useEmissiveMaterial);
        +    fillShader.setUniform("uShininess", this.states._useShininess);
        +    fillShader.setUniform("uMetallic", this.states._useMetalness);
         
             this._setImageLightUniforms(fillShader);
         
        -    fillShader.setUniform('uUseLighting', this._enableLighting);
        +    fillShader.setUniform("uUseLighting", this.states.enableLighting);
         
        -    const pointLightCount = this.pointLightDiffuseColors.length / 3;
        -    fillShader.setUniform('uPointLightCount', pointLightCount);
        -    fillShader.setUniform('uPointLightLocation', this.pointLightPositions);
        +    const pointLightCount = this.states.pointLightDiffuseColors.length / 3;
        +    fillShader.setUniform("uPointLightCount", pointLightCount);
        +    fillShader.setUniform(
        +      "uPointLightLocation",
        +      this.states.pointLightPositions,
        +    );
             fillShader.setUniform(
        -      'uPointLightDiffuseColors',
        -      this.pointLightDiffuseColors
        +      "uPointLightDiffuseColors",
        +      this.states.pointLightDiffuseColors,
             );
             fillShader.setUniform(
        -      'uPointLightSpecularColors',
        -      this.pointLightSpecularColors
        +      "uPointLightSpecularColors",
        +      this.states.pointLightSpecularColors,
             );
         
        -    const directionalLightCount = this.directionalLightDiffuseColors.length / 3;
        -    fillShader.setUniform('uDirectionalLightCount', directionalLightCount);
        -    fillShader.setUniform('uLightingDirection', this.directionalLightDirections);
        +    const directionalLightCount =
        +      this.states.directionalLightDiffuseColors.length / 3;
        +    fillShader.setUniform("uDirectionalLightCount", directionalLightCount);
        +    fillShader.setUniform(
        +      "uLightingDirection",
        +      this.states.directionalLightDirections,
        +    );
             fillShader.setUniform(
        -      'uDirectionalDiffuseColors',
        -      this.directionalLightDiffuseColors
        +      "uDirectionalDiffuseColors",
        +      this.states.directionalLightDiffuseColors,
             );
             fillShader.setUniform(
        -      'uDirectionalSpecularColors',
        -      this.directionalLightSpecularColors
        +      "uDirectionalSpecularColors",
        +      this.states.directionalLightSpecularColors,
             );
         
             // TODO: sum these here...
        -    const ambientLightCount = this.ambientLightColors.length / 3;
        -    this.mixedAmbientLight = [...this.ambientLightColors];
        +    const ambientLightCount = this.states.ambientLightColors.length / 3;
        +    this.mixedAmbientLight = [...this.states.ambientLightColors];
         
        -    if (this._useMetalness > 0) {
        -      this.mixedAmbientLight = this.mixedAmbientLight.map((ambientColors => {
        -        let mixing = ambientColors - this._useMetalness;
        +    if (this.states._useMetalness > 0) {
        +      this.mixedAmbientLight = this.mixedAmbientLight.map((ambientColors) => {
        +        let mixing = ambientColors - this.states._useMetalness;
                 return Math.max(0, mixing);
        -      }));
        +      });
             }
        -    fillShader.setUniform('uAmbientLightCount', ambientLightCount);
        -    fillShader.setUniform('uAmbientColor', this.mixedAmbientLight);
        +    fillShader.setUniform("uAmbientLightCount", ambientLightCount);
        +    fillShader.setUniform("uAmbientColor", this.mixedAmbientLight);
         
        -    const spotLightCount = this.spotLightDiffuseColors.length / 3;
        -    fillShader.setUniform('uSpotLightCount', spotLightCount);
        -    fillShader.setUniform('uSpotLightAngle', this.spotLightAngle);
        -    fillShader.setUniform('uSpotLightConc', this.spotLightConc);
        -    fillShader.setUniform('uSpotLightDiffuseColors', this.spotLightDiffuseColors);
        +    const spotLightCount = this.states.spotLightDiffuseColors.length / 3;
        +    fillShader.setUniform("uSpotLightCount", spotLightCount);
        +    fillShader.setUniform("uSpotLightAngle", this.states.spotLightAngle);
        +    fillShader.setUniform("uSpotLightConc", this.states.spotLightConc);
             fillShader.setUniform(
        -      'uSpotLightSpecularColors',
        -      this.spotLightSpecularColors
        +      "uSpotLightDiffuseColors",
        +      this.states.spotLightDiffuseColors,
        +    );
        +    fillShader.setUniform(
        +      "uSpotLightSpecularColors",
        +      this.states.spotLightSpecularColors,
        +    );
        +    fillShader.setUniform("uSpotLightLocation", this.states.spotLightPositions);
        +    fillShader.setUniform(
        +      "uSpotLightDirection",
        +      this.states.spotLightDirections,
             );
        -    fillShader.setUniform('uSpotLightLocation', this.spotLightPositions);
        -    fillShader.setUniform('uSpotLightDirection', this.spotLightDirections);
        -
        -    fillShader.setUniform('uConstantAttenuation', this.constantAttenuation);
        -    fillShader.setUniform('uLinearAttenuation', this.linearAttenuation);
        -    fillShader.setUniform('uQuadraticAttenuation', this.quadraticAttenuation);
         
        -    fillShader.bindTextures();
        +    fillShader.setUniform(
        +      "uConstantAttenuation",
        +      this.states.constantAttenuation,
        +    );
        +    fillShader.setUniform("uLinearAttenuation", this.states.linearAttenuation);
        +    fillShader.setUniform(
        +      "uQuadraticAttenuation",
        +      this.states.quadraticAttenuation,
        +    );
           }
         
           // getting called from _setFillUniforms
           _setImageLightUniforms(shader) {
             //set uniform values
        -    shader.setUniform('uUseImageLight', this.activeImageLight != null);
        +    shader.setUniform("uUseImageLight", this.states.activeImageLight != null);
             // true
        -    if (this.activeImageLight) {
        -      // this.activeImageLight has image as a key
        +    if (this.states.activeImageLight) {
        +      // this.states.activeImageLight has image as a key
               // look up the texture from the diffusedTexture map
        -      let diffusedLight = this.getDiffusedTexture(this.activeImageLight);
        -      shader.setUniform('environmentMapDiffused', diffusedLight);
        -      let specularLight = this.getSpecularTexture(this.activeImageLight);
        +      let diffusedLight = this.getDiffusedTexture(this.states.activeImageLight);
        +      shader.setUniform("environmentMapDiffused", diffusedLight);
        +      let specularLight = this.getSpecularTexture(this.states.activeImageLight);
         
        -      shader.setUniform('environmentMapSpecular', specularLight);
        +      shader.setUniform("environmentMapSpecular", specularLight);
             }
           }
         
           _setPointUniforms(pointShader) {
        -    pointShader.bindShader();
        -
             // set the uniform values
        -    pointShader.setUniform('uMaterialColor', this.curStrokeColor);
        +    pointShader.setUniform("uMaterialColor", this.states.curStrokeColor);
             // @todo is there an instance where this isn't stroke weight?
             // should be they be same var?
             pointShader.setUniform(
        -      'uPointSize',
        -      this.pointSize * this._pInst._pixelDensity
        +      "uPointSize",
        +      this.states.strokeWeight * this._pixelDensity,
             );
           }
         
           /* Binds a buffer to the drawing context
        -  * when passed more than two arguments it also updates or initializes
        -  * the data associated with the buffer
        -  */
        -  _bindBuffer(
        -    buffer,
        -    target,
        -    values,
        -    type,
        -    usage
        -  ) {
        +   * when passed more than two arguments it also updates or initializes
        +   * the data associated with the buffer
        +   */
        +  _bindBuffer(buffer, target, values, type, usage) {
             if (!target) target = this.GL.ARRAY_BUFFER;
             this.GL.bindBuffer(target, buffer);
             if (values !== undefined) {
               let data = values;
        -      if (values instanceof p5.DataArray) {
        +      if (values instanceof DataArray) {
                 data = values.dataArray();
               } else if (!(data instanceof (type || Float32Array))) {
                 data = new (type || Float32Array)(data);
        @@ -2378,18 +2408,8 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
               Float64Array,
               Int16Array,
               Uint16Array,
        -      Uint32Array
        -    ].some(x => arr instanceof x);
        -  }
        -  /**
        -   * turn a two dimensional array into one dimensional array
        -   * @private
        -   * @param  {Array} arr 2-dimensional array
        -   * @return {Array}     1-dimensional array
        -   * [[1, 2, 3],[4, 5, 6]] -> [1, 2, 3, 4, 5, 6]
        -   */
        -  _flatten(arr) {
        -    return arr.flat();
        +      Uint32Array,
        +    ].some((x) => arr instanceof x);
           }
         
           /**
        @@ -2401,148 +2421,334 @@ p5.RendererGL = class RendererGL extends p5.Renderer {
            * [1, 2, 3, 4, 5, 6]
            */
           _vToNArray(arr) {
        -    return arr.flatMap(item => [item.x, item.y, item.z]);
        -  }
        -
        -  // function to calculate BezierVertex Coefficients
        -  _bezierCoefficients(t) {
        -    const t2 = t * t;
        -    const t3 = t2 * t;
        -    const mt = 1 - t;
        -    const mt2 = mt * mt;
        -    const mt3 = mt2 * mt;
        -    return [mt3, 3 * mt2 * t, 3 * mt * t2, t3];
        -  }
        -
        -  // function to calculate QuadraticVertex Coefficients
        -  _quadraticCoefficients(t) {
        -    const t2 = t * t;
        -    const mt = 1 - t;
        -    const mt2 = mt * mt;
        -    return [mt2, 2 * mt * t, t2];
        -  }
        -
        -  // function to convert Bezier coordinates to Catmull Rom Splines
        -  _bezierToCatmull(w) {
        -    const p1 = w[1];
        -    const p2 = w[1] + (w[2] - w[0]) / this._curveTightness;
        -    const p3 = w[2] - (w[3] - w[1]) / this._curveTightness;
        -    const p4 = w[2];
        -    const p = [p1, p2, p3, p4];
        -    return p;
        -  }
        -  _initTessy() {
        -    // function called for each vertex of tesselator output
        -    function vertexCallback(data, polyVertArray) {
        -      for (const element of data) {
        -        polyVertArray.push(element);
        -      }
        -    }
        +    return arr.flatMap((item) => [item.x, item.y, item.z]);
        +  }
        +}
         
        -    function begincallback(type) {
        -      if (type !== libtess.primitiveType.GL_TRIANGLES) {
        -        console.log(`expected TRIANGLES but got type: ${type}`);
        -      }
        -    }
        +function rendererGL(p5, fn) {
        +  p5.RendererGL = RendererGL;
         
        -    function errorcallback(errno) {
        -      console.log('error callback');
        -      console.log(`error number: ${errno}`);
        +  /**
        +   * @module Rendering
        +   * @submodule Rendering
        +   * @for p5
        +   */
        +  /**
        +   * Set attributes for the WebGL Drawing context.
        +   * This is a way of adjusting how the WebGL
        +   * renderer works to fine-tune the display and performance.
        +   *
        +   * Note that this will reinitialize the drawing context
        +   * if called after the WebGL canvas is made.
        +   *
        +   * If an object is passed as the parameter, all attributes
        +   * not declared in the object will be set to defaults.
        +   *
        +   * The available attributes are:
        +   * <br>
        +   * alpha - indicates if the canvas contains an alpha buffer
        +   * default is true
        +   *
        +   * depth - indicates whether the drawing buffer has a depth buffer
        +   * of at least 16 bits - default is true
        +   *
        +   * stencil - indicates whether the drawing buffer has a stencil buffer
        +   * of at least 8 bits
        +   *
        +   * antialias - indicates whether or not to perform anti-aliasing
        +   * default is false (true in Safari)
        +   *
        +   * premultipliedAlpha - indicates that the page compositor will assume
        +   * the drawing buffer contains colors with pre-multiplied alpha
        +   * default is true
        +   *
        +   * preserveDrawingBuffer - if true the buffers will not be cleared and
        +   * and will preserve their values until cleared or overwritten by author
        +   * (note that p5 clears automatically on draw loop)
        +   * default is true
        +   *
        +   * perPixelLighting - if true, per-pixel lighting will be used in the
        +   * lighting shader otherwise per-vertex lighting is used.
        +   * default is true.
        +   *
        +   * version - either 1 or 2, to specify which WebGL version to ask for. By
        +   * default, WebGL 2 will be requested. If WebGL2 is not available, it will
        +   * fall back to WebGL 1. You can check what version is used with by looking at
        +   * the global `webglVersion` property.
        +   *
        +   * @method setAttributes
        +   * @for p5
        +   * @param  {String}  key Name of attribute
        +   * @param  {Boolean}        value New value of named attribute
        +   * @example
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   push();
        +   *   rotateZ(frameCount * 0.02);
        +   *   rotateX(frameCount * 0.02);
        +   *   rotateY(frameCount * 0.02);
        +   *   fill(0, 0, 0);
        +   *   box(50);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   * <br>
        +   * Now with the antialias attribute set to true.
        +   * <br>
        +   * <div>
        +   * <code>
        +   * function setup() {
        +   *   setAttributes('antialias', true);
        +   *   createCanvas(100, 100, WEBGL);
        +   * }
        +   *
        +   * function draw() {
        +   *   background(255);
        +   *   push();
        +   *   rotateZ(frameCount * 0.02);
        +   *   rotateX(frameCount * 0.02);
        +   *   rotateY(frameCount * 0.02);
        +   *   fill(0, 0, 0);
        +   *   box(50);
        +   *   pop();
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // press the mouse button to disable perPixelLighting
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *   noStroke();
        +   *   fill(255);
        +   * }
        +   *
        +   * let lights = [
        +   *   { c: '#f00', t: 1.12, p: 1.91, r: 0.2 },
        +   *   { c: '#0f0', t: 1.21, p: 1.31, r: 0.2 },
        +   *   { c: '#00f', t: 1.37, p: 1.57, r: 0.2 },
        +   *   { c: '#ff0', t: 1.12, p: 1.91, r: 0.7 },
        +   *   { c: '#0ff', t: 1.21, p: 1.31, r: 0.7 },
        +   *   { c: '#f0f', t: 1.37, p: 1.57, r: 0.7 }
        +   * ];
        +   *
        +   * function draw() {
        +   *   let t = millis() / 1000 + 1000;
        +   *   background(0);
        +   *   directionalLight(color('#222'), 1, 1, 1);
        +   *
        +   *   for (let i = 0; i < lights.length; i++) {
        +   *     let light = lights[i];
        +   *     pointLight(
        +   *       color(light.c),
        +   *       p5.Vector.fromAngles(t * light.t, t * light.p, width * light.r)
        +   *     );
        +   *   }
        +   *
        +   *   specularMaterial(255);
        +   *   sphere(width * 0.1);
        +   *
        +   *   rotateX(t * 0.77);
        +   *   rotateY(t * 0.83);
        +   *   rotateZ(t * 0.91);
        +   *   torus(width * 0.3, width * 0.07, 24, 10);
        +   * }
        +   *
        +   * function mousePressed() {
        +   *   setAttributes('perPixelLighting', false);
        +   *   noStroke();
        +   *   fill(255);
        +   * }
        +   * function mouseReleased() {
        +   *   setAttributes('perPixelLighting', true);
        +   *   noStroke();
        +   *   fill(255);
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * @alt a rotating cube with smoother edges
        +   */
        +  /**
        +   * @method setAttributes
        +   * @for p5
        +   * @param  {Object}  obj object with key-value pairs
        +   */
        +  fn.setAttributes = function (key, value) {
        +    if (typeof this._glAttributes === "undefined") {
        +      console.log(
        +        "You are trying to use setAttributes on a p5.Graphics object " +
        +          "that does not use a WEBGL renderer.",
        +      );
        +      return;
             }
        -    // callback for when segments intersect and must be split
        -    function combinecallback(coords, data, weight) {
        -      const result = new Array(p5.RendererGL.prototype.tessyVertexSize).fill(0);
        -      for (let i = 0; i < weight.length; i++) {
        -        for (let j = 0; j < result.length; j++) {
        -          if (weight[i] === 0 || !data[i]) continue;
        -          result[j] += data[i][j] * weight[i];
        -        }
        +    let unchanged = true;
        +    if (typeof value !== "undefined") {
        +      //first time modifying the attributes
        +      if (this._glAttributes === null) {
        +        this._glAttributes = {};
        +      }
        +      if (this._glAttributes[key] !== value) {
        +        //changing value of previously altered attribute
        +        this._glAttributes[key] = value;
        +        unchanged = false;
        +      }
        +      //setting all attributes with some change
        +    } else if (key instanceof Object) {
        +      if (this._glAttributes !== key) {
        +        this._glAttributes = key;
        +        unchanged = false;
               }
        -      return result;
             }
        -
        -    function edgeCallback(flag) {
        -      // don't really care about the flag, but need no-strip/no-fan behavior
        +    //@todo_FES
        +    if (!this._renderer.isP3D || unchanged) {
        +      return;
             }
         
        -    const tessy = new libtess.GluTesselator();
        -    tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_VERTEX_DATA, vertexCallback);
        -    tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_BEGIN, begincallback);
        -    tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_ERROR, errorcallback);
        -    tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_COMBINE, combinecallback);
        -    tessy.gluTessCallback(libtess.gluEnum.GLU_TESS_EDGE_FLAG, edgeCallback);
        -    tessy.gluTessProperty(
        -      libtess.gluEnum.GLU_TESS_WINDING_RULE,
        -      libtess.windingRule.GLU_TESS_WINDING_NONZERO
        -    );
        -
        -    return tessy;
        -  }
        -
        -  _triangulate(contours) {
        -    // libtess will take 3d verts and flatten to a plane for tesselation.
        -    // libtess is capable of calculating a plane to tesselate on, but
        -    // if all of the vertices have the same z values, we'll just
        -    // assume the face is facing the camera, letting us skip any performance
        -    // issues or bugs in libtess's automatic calculation.
        -    const z = contours[0] ? contours[0][2] : undefined;
        -    let allSameZ = true;
        -    for (const contour of contours) {
        -      for (
        -        let j = 0;
        -        j < contour.length;
        -        j += p5.RendererGL.prototype.tessyVertexSize
        -      ) {
        -        if (contour[j + 2] !== z) {
        -          allSameZ = false;
        -          break;
        -        }
        +    if (!this._setupDone) {
        +      if (this._renderer.geometryBufferCache.numCached() > 0) {
        +        p5._friendlyError(
        +          "Sorry, Could not set the attributes, you need to call setAttributes() " +
        +            "before calling the other drawing methods in setup()",
        +        );
        +        return;
               }
             }
        -    if (allSameZ) {
        -      this._tessy.gluTessNormal(0, 0, 1);
        -    } else {
        -      // Let libtess pick a plane for us
        -      this._tessy.gluTessNormal(0, 0, 0);
        -    }
         
        -    const triangleVerts = [];
        -    this._tessy.gluTessBeginPolygon(triangleVerts);
        +    this.push();
        +    this._renderer._resetContext();
        +    this.pop();
         
        -    for (const contour of contours) {
        -      this._tessy.gluTessBeginContour();
        -      for (
        -        let j = 0;
        -        j < contour.length;
        -        j += p5.RendererGL.prototype.tessyVertexSize
        -      ) {
        -        const coords = contour.slice(
        -          j,
        -          j + p5.RendererGL.prototype.tessyVertexSize
        -        );
        -        this._tessy.gluTessVertex(coords, coords);
        -      }
        -      this._tessy.gluTessEndContour();
        +    if (this._renderer.states.curCamera) {
        +      this._renderer.states.curCamera._renderer = this._renderer;
             }
        +  };
        +
        +  /**
        +   * ensures that p5 is using a 3d renderer. throws an error if not.
        +   */
        +  fn._assert3d = function (name) {
        +    if (!this._renderer.isP3D)
        +      throw new Error(
        +        `${name}() is only supported in WEBGL mode. If you'd like to use 3D graphics and WebGL, see  https://p5js.org/examples/form-3d-primitives.html for more information.`,
        +      );
        +  };
        +
        +  p5.renderers[constants.WEBGL] = p5.RendererGL;
        +  p5.renderers[constants.WEBGL2] = p5.RendererGL;
        +}
        +
        +/**
        + * @private
        + * @param {Uint8Array|Float32Array|undefined} pixels An existing pixels array to reuse if the size is the same
        + * @param {WebGLRenderingContext} gl The WebGL context
        + * @param {WebGLFramebuffer|null} framebuffer The Framebuffer to read
        + * @param {Number} x The x coordiante to read, premultiplied by pixel density
        + * @param {Number} y The y coordiante to read, premultiplied by pixel density
        + * @param {Number} width The width in pixels to be read (factoring in pixel density)
        + * @param {Number} height The height in pixels to be read (factoring in pixel density)
        + * @param {GLEnum} format Either RGB or RGBA depending on how many channels to read
        + * @param {GLEnum} type The datatype of each channel, e.g. UNSIGNED_BYTE or FLOAT
        + * @param {Number|undefined} flipY If provided, the total height with which to flip the y axis about
        + * @returns {Uint8Array|Float32Array} pixels A pixels array with the current state of the
        + * WebGL context read into it
        + */
        +export function readPixelsWebGL(
        +  pixels,
        +  gl,
        +  framebuffer,
        +  x,
        +  y,
        +  width,
        +  height,
        +  format,
        +  type,
        +  flipY,
        +) {
        +  // Record the currently bound framebuffer so we can go back to it after, and
        +  // bind the framebuffer we want to read from
        +  const prevFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
        +  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
         
        -    // finish polygon
        -    this._tessy.gluTessEndPolygon();
        +  const channels = format === gl.RGBA ? 4 : 3;
         
        -    return triangleVerts;
        +  // Make a pixels buffer if it doesn't already exist
        +  const len = width * height * channels;
        +  const TypedArrayClass = type === gl.UNSIGNED_BYTE ? Uint8Array : Float32Array;
        +  if (!(pixels instanceof TypedArrayClass) || pixels.length !== len) {
        +    pixels = new TypedArrayClass(len);
           }
        -};
        +
        +  gl.readPixels(
        +    x,
        +    flipY ? flipY - y - height : y,
        +    width,
        +    height,
        +    format,
        +    type,
        +    pixels,
        +  );
        +
        +  // Re-bind whatever was previously bound
        +  gl.bindFramebuffer(gl.FRAMEBUFFER, prevFramebuffer);
        +
        +  if (flipY) {
        +    // WebGL pixels are inverted compared to 2D pixels, so we have to flip
        +    // the resulting rows. Adapted from https://stackoverflow.com/a/41973289
        +    const halfHeight = Math.floor(height / 2);
        +    const tmpRow = new TypedArrayClass(width * channels);
        +    for (let y = 0; y < halfHeight; y++) {
        +      const topOffset = y * width * 4;
        +      const bottomOffset = (height - y - 1) * width * 4;
        +      tmpRow.set(pixels.subarray(topOffset, topOffset + width * 4));
        +      pixels.copyWithin(topOffset, bottomOffset, bottomOffset + width * 4);
        +      pixels.set(tmpRow, bottomOffset);
        +    }
        +  }
        +
        +  return pixels;
        +}
        +
         /**
        - * ensures that p5 is using a 3d renderer. throws an error if not.
        + * @private
        + * @param {WebGLRenderingContext} gl The WebGL context
        + * @param {WebGLFramebuffer|null} framebuffer The Framebuffer to read
        + * @param {Number} x The x coordinate to read, premultiplied by pixel density
        + * @param {Number} y The y coordinate to read, premultiplied by pixel density
        + * @param {GLEnum} format Either RGB or RGBA depending on how many channels to read
        + * @param {GLEnum} type The datatype of each channel, e.g. UNSIGNED_BYTE or FLOAT
        + * @param {Number|undefined} flipY If provided, the total height with which to flip the y axis about
        + * @returns {Number[]} pixels The channel data for the pixel at that location
          */
        -p5.prototype._assert3d = function (name) {
        -  if (!this._renderer.isP3D)
        -    throw new Error(
        -      `${name}() is only supported in WEBGL mode. If you'd like to use 3D graphics and WebGL, see  https://p5js.org/examples/form-3d-primitives.html for more information.`
        -    );
        -};
        +export function readPixelWebGL(gl, framebuffer, x, y, format, type, flipY) {
        +  // Record the currently bound framebuffer so we can go back to it after, and
        +  // bind the framebuffer we want to read from
        +  const prevFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
        +  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
        +
        +  const channels = format === gl.RGBA ? 4 : 3;
        +  const TypedArrayClass = type === gl.UNSIGNED_BYTE ? Uint8Array : Float32Array;
        +  const pixels = new TypedArrayClass(channels);
        +
        +  gl.readPixels(x, flipY ? flipY - y - 1 : y, 1, 1, format, type, pixels);
        +
        +  // Re-bind whatever was previously bound
        +  gl.bindFramebuffer(gl.FRAMEBUFFER, prevFramebuffer);
         
        -// function to initialize GLU Tesselator
        +  return Array.from(pixels);
        +}
         
        -p5.RendererGL.prototype.tessyVertexSize = 12;
        +export default rendererGL;
        +export { RendererGL };
         
        -export default p5.RendererGL;
        +if (typeof p5 !== "undefined") {
        +  rendererGL(p5, p5.prototype);
        +}
        diff --git a/src/webgl/p5.Shader.js b/src/webgl/p5.Shader.js
        index a82f112361..ff623eff25 100644
        --- a/src/webgl/p5.Shader.js
        +++ b/src/webgl/p5.Shader.js
        @@ -6,153 +6,9 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
        +import { Texture } from './p5.Texture';
         
        -/**
        - * A class to describe a shader program.
        - *
        - * Each `p5.Shader` object contains a shader program that runs on the graphics
        - * processing unit (GPU). Shaders can process many pixels or vertices at the
        - * same time, making them fast for many graphics tasks. They’re written in a
        - * language called
        - * <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders" target="_blank">GLSL</a>
        - * and run along with the rest of the code in a sketch.
        - *
        - * A shader program consists of two files, a vertex shader and a fragment
        - * shader. The vertex shader affects where 3D geometry is drawn on the screen
        - * and the fragment shader affects color. Once the `p5.Shader` object is
        - * created, it can be used with the <a href="#/p5/shader">shader()</a>
        - * function, as in `shader(myShader)`.
        - *
        - * A shader can optionally describe *hooks,* which are functions in GLSL that
        - * users may choose to provide to customize the behavior of the shader. For the
        - * vertex or the fragment shader, users can pass in an object where each key is
        - * the type and name of a hook function, and each value is a string with the
        - * parameter list and default implementation of the hook. For example, to let users
        - * optionally run code at the start of the vertex shader, the options object could
        - * include:
        - *
        - * ```js
        - * {
        - *   vertex: {
        - *     'void beforeVertex': '() {}'
        - *   }
        - * }
        - * ```
        - *
        - * Then, in your vertex shader source, you can run a hook by calling a function
        - * with the same name prefixed by `HOOK_`:
        - *
        - * ```glsl
        - * void main() {
        - *   HOOK_beforeVertex();
        - *   // Add the rest of your shader code here!
        - * }
        - * ```
        - *
        - * Note: <a href="#/p5/createShader">createShader()</a>,
        - * <a href="#/p5/createFilterShader">createFilterShader()</a>, and
        - * <a href="#/p5/loadShader">loadShader()</a> are the recommended ways to
        - * create an instance of this class.
        - *
        - * @class p5.Shader
        - * @constructor
        - * @param {p5.RendererGL} renderer WebGL context for this shader.
        - * @param {String} vertSrc source code for the vertex shader program.
        - * @param {String} fragSrc source code for the fragment shader program.
        - * @param {Object} [options] An optional object describing how this shader can
        - * be augmented with hooks. It can include:
        - *  - `vertex`: An object describing the available vertex shader hooks.
        - *  - `fragment`: An object describing the available frament shader hooks.
        - *
        - * @example
        - * <div>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * // Create a string with the vertex shader program.
        - * // The vertex shader is called for each vertex.
        - * let vertSrc = `
        - * precision highp float;
        - * uniform mat4 uModelViewMatrix;
        - * uniform mat4 uProjectionMatrix;
        - *
        - * attribute vec3 aPosition;
        - * attribute vec2 aTexCoord;
        - * varying vec2 vTexCoord;
        - *
        - * void main() {
        - *   vTexCoord = aTexCoord;
        - *   vec4 positionVec4 = vec4(aPosition, 1.0);
        - *   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        - * }
        - * `;
        - *
        - * // Create a string with the fragment shader program.
        - * // The fragment shader is called for each pixel.
        - * let fragSrc = `
        - * precision highp float;
        - *
        - * void main() {
        - *   // Set each pixel's RGBA value to yellow.
        - *   gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
        - * }
        - * `;
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Create a p5.Shader object.
        - *   let myShader = createShader(vertSrc, fragSrc);
        - *
        - *   // Apply the p5.Shader object.
        - *   shader(myShader);
        - *
        - *   // Style the drawing surface.
        - *   noStroke();
        - *
        - *   // Add a plane as a drawing surface.
        - *   plane(100, 100);
        - *
        - *   describe('A yellow square.');
        - * }
        - * </code>
        - * </div>
        - *
        - * <div>
        - * <code>
        - * // Note: A "uniform" is a global variable within a shader program.
        - *
        - * let mandelbrot;
        - *
        - * // Load the shader and create a p5.Shader object.
        - * function preload() {
        - *   mandelbrot = loadShader('assets/shader.vert', 'assets/shader.frag');
        - * }
        - *
        - * function setup() {
        - *   createCanvas(100, 100, WEBGL);
        - *
        - *   // Use the p5.Shader object.
        - *   shader(mandelbrot);
        - *
        - *   // Set the shader uniform p to an array.
        - *   mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
        - *
        - *   describe('A fractal image zooms in and out of focus.');
        - * }
        - *
        - * function draw() {
        - *   // Set the shader uniform r to a value that oscillates between 0 and 2.
        - *   mandelbrot.setUniform('r', sin(frameCount * 0.01) + 1);
        - *
        - *   // Add a quad as a display surface for the shader.
        - *   quad(-1, -1, 1, -1, 1, 1, -1, 1);
        - * }
        - * </code>
        - * </div>
        - */
        -p5.Shader = class {
        +class Shader {
           constructor(renderer, vertSrc, fragSrc, options = {}) {
             // TODO: adapt this to not take ids, but rather,
             // to take the source for a vertex and fragment shader
        @@ -198,9 +54,10 @@ p5.Shader = class {
         
           shaderSrc(src, shaderType) {
             const main = 'void main';
        -    const [preMain, postMain] = src.split(main);
        +    let [preMain, postMain] = src.split(main);
         
             let hooks = '';
        +    let defines = '';
             for (const key in this.hooks.uniforms) {
               hooks += `uniform ${key};\n`;
             }
        @@ -220,14 +77,22 @@ p5.Shader = class {
               // Add a #define so that if the shader wants to use preprocessor directives to
               // optimize away the extra function calls in main, it can do so
               if (this.hooks.modified[shaderType][hookDef]) {
        -        hooks += '#define AUGMENTED_HOOK_' + hookName + '\n';
        +        defines += '#define AUGMENTED_HOOK_' + hookName + '\n';
               }
         
               hooks +=
                 hookType + ' HOOK_' + hookName + this.hooks[shaderType][hookDef] + '\n';
             }
         
        -    return preMain + hooks + main + postMain;
        +    // Allow shaders to specify the location of hook #define statements. Normally these
        +    // go after function definitions, but one might want to have them defined earlier
        +    // in order to only conditionally make uniforms.
        +    if (preMain.indexOf('#define HOOK_DEFINES') !== -1) {
        +      preMain = preMain.replace('#define HOOK_DEFINES', '\n' + defines + '\n');
        +      defines = '';
        +    }
        +
        +    return preMain + '\n' + defines + hooks + main + postMain;
           }
         
           /**
        @@ -309,7 +174,6 @@ p5.Shader = class {
            * void afterFragment() {}
            * ```
            *
        -   * @method inspectHooks
            * @beta
            */
           inspectHooks() {
        @@ -366,7 +230,6 @@ p5.Shader = class {
            * between hooks. To add declarations just in a vertex or fragment shader, add
            * `vertexDeclarations` and `fragmentDeclarations` keys.
            *
        -   * @method modify
            * @beta
            * @param {Object} [hooks] The hooks in the shader to replace.
            * @returns {p5.Shader}
        @@ -430,7 +293,7 @@ p5.Shader = class {
            * </div>
            */
           modify(hooks) {
        -    p5._validateParameters('p5.Shader.modify', arguments);
        +    // p5._validateParameters('p5.Shader.modify', arguments);
             const newHooks = {
               vertex: {},
               fragment: {},
        @@ -464,7 +327,7 @@ p5.Shader = class {
               modifiedFragment[key] = true;
             }
         
        -    return new p5.Shader(this._renderer, this._vertSrc, this._fragSrc, {
        +    return new Shader(this._renderer, this._vertSrc, this._fragSrc, {
               declarations:
                 (this.hooks.declarations || '') + '\n' + (hooks.declarations || ''),
               uniforms: Object.assign({}, this.hooks.uniforms, hooks.uniforms || {}),
        @@ -483,7 +346,6 @@ p5.Shader = class {
            * sources for the vertex and fragment shaders (provided
            * to the constructor). Populates known attributes and
            * uniforms from the shader.
        -   * @method init
            * @chainable
            * @private
            */
        @@ -509,6 +371,7 @@ p5.Shader = class {
                 if (typeof IS_MINIFIED !== 'undefined') {
                   console.error(glError);
                 } else {
        +          throw glError;
                   p5._friendlyError(
                     `Yikes! An error occurred compiling the vertex shader:${glError}`
                   );
        @@ -526,6 +389,7 @@ p5.Shader = class {
                 if (typeof IS_MINIFIED !== 'undefined') {
                   console.error(glError);
                 } else {
        +          throw glError;
                   p5._friendlyError(
                     `Darn! An error occurred compiling the fragment shader:${glError}`
                   );
        @@ -598,7 +462,6 @@ p5.Shader = class {
            * <a href="#/p5/createFramebuffer">createFramebuffer()</a>. Both objects
            * have the same context as the main canvas.
            *
        -   * @method copyToContext
            * @param {p5|p5.Graphics} context WebGL context for the copied shader.
            * @returns {p5.Shader} new shader compiled for the target context.
            *
        @@ -752,12 +615,12 @@ p5.Shader = class {
            * </div>
            */
           copyToContext(context) {
        -    const shader = new p5.Shader(
        +    const shader = new Shader(
               context._renderer,
               this._vertSrc,
               this._fragSrc
             );
        -    shader.ensureCompiledOnContext(context);
        +    shader.ensureCompiledOnContext(context._renderer);
             return shader;
           }
         
        @@ -765,20 +628,20 @@ p5.Shader = class {
            * @private
            */
           ensureCompiledOnContext(context) {
        -    if (this._glProgram !== 0 && this._renderer !== context._renderer) {
        +    if (this._glProgram !== 0 && this._renderer !== context) {
               throw new Error(
                 'The shader being run is attached to a different context. Do you need to copy it to this context first with .copyToContext()?'
               );
             } else if (this._glProgram === 0) {
        -      this._renderer = context._renderer;
        +      this._renderer = context?._renderer?.filterRenderer?._renderer || context;
               this.init();
             }
           }
         
        +
           /**
            * Queries the active attributes for this shader and loads
            * their names and locations into the attributes array.
        -   * @method _loadAttributes
            * @private
            */
           _loadAttributes() {
        @@ -813,7 +676,6 @@ p5.Shader = class {
           /**
            * Queries the active uniforms for this shader and loads
            * their names and locations into the uniforms array.
        -   * @method _loadUniforms
            * @private
            */
           _loadUniforms() {
        @@ -877,7 +739,6 @@ p5.Shader = class {
         
           /**
            * initializes (if needed) and binds the shader program.
        -   * @method bindShader
            * @private
            */
           bindShader() {
        @@ -885,22 +746,16 @@ p5.Shader = class {
             if (!this._bound) {
               this.useProgram();
               this._bound = true;
        -
        -      this._setMatrixUniforms();
        -
        -      this.setUniform('uViewport', this._renderer._viewport);
             }
           }
         
           /**
        -   * @method unbindShader
            * @chainable
            * @private
            */
           unbindShader() {
             if (this._bound) {
               this.unbindTextures();
        -      //this._renderer.GL.useProgram(0); ??
               this._bound = false;
             }
             return this;
        @@ -909,13 +764,15 @@ p5.Shader = class {
           bindTextures() {
             const gl = this._renderer.GL;
         
        +    const empty = this._renderer._getEmptyTexture();
        +
             for (const uniform of this.samplers) {
               let tex = uniform.texture;
               if (tex === undefined) {
                 // user hasn't yet supplied a texture for this slot.
                 // (or there may not be one--maybe just lighting),
                 // so we supply a default texture instead.
        -        tex = this._renderer._getEmptyTexture();
        +        uniform.texture = tex = empty;
               }
               gl.activeTexture(gl.TEXTURE0 + uniform.samplerIndex);
               tex.bindTexture();
        @@ -934,47 +791,18 @@ p5.Shader = class {
           }
         
           unbindTextures() {
        +    const gl = this._renderer.GL;
        +    const empty = this._renderer._getEmptyTexture();
             for (const uniform of this.samplers) {
        -      this.setUniform(uniform.name, this._renderer._getEmptyTexture());
        -    }
        -  }
        -
        -  _setMatrixUniforms() {
        -    const modelMatrix = this._renderer.uModelMatrix;
        -    const viewMatrix = this._renderer.uViewMatrix;
        -    const projectionMatrix = this._renderer.uPMatrix;
        -    const modelViewMatrix = modelMatrix.copy().mult(viewMatrix);
        -    this._renderer.uMVMatrix = modelViewMatrix;
        -
        -    const modelViewProjectionMatrix = modelViewMatrix.copy();
        -    modelViewProjectionMatrix.mult(projectionMatrix);
        -
        -    if (this.isStrokeShader()) {
        -      this.setUniform(
        -        'uPerspective',
        -        this._renderer._curCamera.useLinePerspective ? 1 : 0
        -      );
        -    }
        -    this.setUniform('uViewMatrix', viewMatrix.mat4);
        -    this.setUniform('uProjectionMatrix', projectionMatrix.mat4);
        -    this.setUniform('uModelMatrix', modelMatrix.mat4);
        -    this.setUniform('uModelViewMatrix', modelViewMatrix.mat4);
        -    this.setUniform(
        -      'uModelViewProjectionMatrix',
        -      modelViewProjectionMatrix.mat4
        -    );
        -    if (this.uniforms.uNormalMatrix) {
        -      this._renderer.uNMatrix.inverseTranspose(this._renderer.uMVMatrix);
        -      this.setUniform('uNormalMatrix', this._renderer.uNMatrix.mat3);
        -    }
        -    if (this.uniforms.uCameraRotation) {
        -      this._renderer.curMatrix.inverseTranspose(this._renderer.uViewMatrix);
        -      this.setUniform('uCameraRotation', this._renderer.curMatrix.mat3);
        +      if (uniform.texture?.isFramebufferTexture) {
        +        gl.activeTexture(gl.TEXTURE0 + uniform.samplerIndex);
        +        empty.bindTexture();
        +        gl.uniform1i(uniform.location, uniform.samplerIndex);
        +      }
             }
           }
         
           /**
        -   * @method useProgram
            * @chainable
            * @private
            */
        @@ -1005,7 +833,6 @@ p5.Shader = class {
            * uniform’s type. Numbers, strings, booleans, arrays, and many types of
            * images can all be passed to a shader with `setUniform()`.
            *
        -   * @method setUniform
            * @chainable
            * @param {String} uniformName name of the uniform. Must match the name
            *                             used in the vertex and fragment shaders.
        @@ -1223,6 +1050,8 @@ p5.Shader = class {
            * </div>
            */
           setUniform(uniformName, data) {
        +    this.init();
        +
             const uniform = this.uniforms[uniformName];
             if (!uniform) {
               return;
        @@ -1341,7 +1170,7 @@ p5.Shader = class {
                 } else {
                   gl.activeTexture(gl.TEXTURE0 + uniform.samplerIndex);
                   uniform.texture =
        -            data instanceof p5.Texture ? data : this._renderer.getTexture(data);
        +            data instanceof Texture ? data : this._renderer.getTexture(data);
                   gl.uniform1i(location, uniform.samplerIndex);
                   if (uniform.texture.src.gifProperties) {
                     uniform.texture.src._animateGif(this._renderer._pInst);
        @@ -1385,59 +1214,7 @@ p5.Shader = class {
             return this;
           }
         
        -  /* NONE OF THIS IS FAST OR EFFICIENT BUT BEAR WITH ME
        -   *
        -   * these shader "type" query methods are used by various
        -   * facilities of the renderer to determine if changing
        -   * the shader type for the required action (for example,
        -   * do we need to load the default lighting shader if the
        -   * current shader cannot handle lighting?)
        -   *
        -   **/
        -
        -  isLightShader() {
        -    return [
        -      this.attributes.aNormal,
        -      this.uniforms.uUseLighting,
        -      this.uniforms.uAmbientLightCount,
        -      this.uniforms.uDirectionalLightCount,
        -      this.uniforms.uPointLightCount,
        -      this.uniforms.uAmbientColor,
        -      this.uniforms.uDirectionalDiffuseColors,
        -      this.uniforms.uDirectionalSpecularColors,
        -      this.uniforms.uPointLightLocation,
        -      this.uniforms.uPointLightDiffuseColors,
        -      this.uniforms.uPointLightSpecularColors,
        -      this.uniforms.uLightingDirection,
        -      this.uniforms.uSpecular
        -    ].some(x => x !== undefined);
        -  }
        -
        -  isNormalShader() {
        -    return this.attributes.aNormal !== undefined;
        -  }
        -
        -  isTextureShader() {
        -    return this.samplers.length > 0;
        -  }
        -
        -  isColorShader() {
        -    return (
        -      this.attributes.aVertexColor !== undefined ||
        -      this.uniforms.uMaterialColor !== undefined
        -    );
        -  }
        -
        -  isTexLightShader() {
        -    return this.isLightShader() && this.isTextureShader();
        -  }
        -
        -  isStrokeShader() {
        -    return this.uniforms.uStrokeWeight !== undefined;
        -  }
        -
           /**
        -   * @method enableAttrib
            * @chainable
            * @private
            */
        @@ -1477,7 +1254,6 @@ p5.Shader = class {
            * Once all buffers have been bound, this checks to see if there are any
            * remaining active attributes, likely left over from previous renders,
            * and disables them so that they don't affect rendering.
        -   * @method disableRemainingAttributes
            * @private
            */
           disableRemainingAttributes() {
        @@ -1494,4 +1270,153 @@ p5.Shader = class {
           }
         };
         
        -export default p5.Shader;
        +function shader(p5, fn){
        +  /**
        +   * A class to describe a shader program.
        +   *
        +   * Each `p5.Shader` object contains a shader program that runs on the graphics
        +   * processing unit (GPU). Shaders can process many pixels or vertices at the
        +   * same time, making them fast for many graphics tasks. They’re written in a
        +   * language called
        +   * <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders" target="_blank">GLSL</a>
        +   * and run along with the rest of the code in a sketch.
        +   *
        +   * A shader program consists of two files, a vertex shader and a fragment
        +   * shader. The vertex shader affects where 3D geometry is drawn on the screen
        +   * and the fragment shader affects color. Once the `p5.Shader` object is
        +   * created, it can be used with the <a href="#/p5/shader">shader()</a>
        +   * function, as in `shader(myShader)`.
        +   *
        +   * A shader can optionally describe *hooks,* which are functions in GLSL that
        +   * users may choose to provide to customize the behavior of the shader. For the
        +   * vertex or the fragment shader, users can pass in an object where each key is
        +   * the type and name of a hook function, and each value is a string with the
        +   * parameter list and default implementation of the hook. For example, to let users
        +   * optionally run code at the start of the vertex shader, the options object could
        +   * include:
        +   *
        +   * ```js
        +   * {
        +   *   vertex: {
        +   *     'void beforeVertex': '() {}'
        +   *   }
        +   * }
        +   * ```
        +   *
        +   * Then, in your vertex shader source, you can run a hook by calling a function
        +   * with the same name prefixed by `HOOK_`:
        +   *
        +   * ```glsl
        +   * void main() {
        +   *   HOOK_beforeVertex();
        +   *   // Add the rest ofy our shader code here!
        +   * }
        +   * ```
        +   *
        +   * Note: <a href="#/p5/createShader">createShader()</a>,
        +   * <a href="#/p5/createFilterShader">createFilterShader()</a>, and
        +   * <a href="#/p5/loadShader">loadShader()</a> are the recommended ways to
        +   * create an instance of this class.
        +   *
        +   * @class p5.Shader
        +   * @constructor
        +   * @param {p5.RendererGL} renderer WebGL context for this shader.
        +   * @param {String} vertSrc source code for the vertex shader program.
        +   * @param {String} fragSrc source code for the fragment shader program.
        +   * @param {Object} [options] An optional object describing how this shader can
        +   * be augmented with hooks. It can include:
        +   *  - `vertex`: An object describing the available vertex shader hooks.
        +   *  - `fragment`: An object describing the available frament shader hooks.
        +   *
        +   * @example
        +   * <div>
        +   * <code>
        +   * // Note: A "uniform" is a global variable within a shader program.
        +   *
        +   * // Create a string with the vertex shader program.
        +   * // The vertex shader is called for each vertex.
        +   * let vertSrc = `
        +   * precision highp float;
        +   * uniform mat4 uModelViewMatrix;
        +   * uniform mat4 uProjectionMatrix;
        +   *
        +   * attribute vec3 aPosition;
        +   * attribute vec2 aTexCoord;
        +   * varying vec2 vTexCoord;
        +   *
        +   * void main() {
        +   *   vTexCoord = aTexCoord;
        +   *   vec4 positionVec4 = vec4(aPosition, 1.0);
        +   *   gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        +   * }
        +   * `;
        +   *
        +   * // Create a string with the fragment shader program.
        +   * // The fragment shader is called for each pixel.
        +   * let fragSrc = `
        +   * precision highp float;
        +   *
        +   * void main() {
        +   *   // Set each pixel's RGBA value to yellow.
        +   *   gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
        +   * }
        +   * `;
        +   *
        +   * function setup() {
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Create a p5.Shader object.
        +   *   let myShader = createShader(vertSrc, fragSrc);
        +   *
        +   *   // Apply the p5.Shader object.
        +   *   shader(myShader);
        +   *
        +   *   // Style the drawing surface.
        +   *   noStroke();
        +   *
        +   *   // Add a plane as a drawing surface.
        +   *   plane(100, 100);
        +   *
        +   *   describe('A yellow square.');
        +   * }
        +   * </code>
        +   * </div>
        +   *
        +   * <div>
        +   * <code>
        +   * // Note: A "uniform" is a global variable within a shader program.
        +   *
        +   * let mandelbrot;
        +   *
        +   * async function setup() {
        +   *   mandelbrot = await loadShader('assets/shader.vert', 'assets/shader.frag');
        +   *   createCanvas(100, 100, WEBGL);
        +   *
        +   *   // Use the p5.Shader object.
        +   *   shader(mandelbrot);
        +   *
        +   *   // Set the shader uniform p to an array.
        +   *   mandelbrot.setUniform('p', [-0.74364388703, 0.13182590421]);
        +   *
        +   *   describe('A fractal image zooms in and out of focus.');
        +   * }
        +   *
        +   * function draw() {
        +   *   // Set the shader uniform r to a value that oscillates between 0 and 2.
        +   *   mandelbrot.setUniform('r', sin(frameCount * 0.01) + 1);
        +   *
        +   *   // Add a quad as a display surface for the shader.
        +   *   quad(-1, -1, 1, -1, 1, 1, -1, 1);
        +   * }
        +   * </code>
        +   * </div>
        +   */
        +  p5.Shader = Shader;
        +}
        +
        +export default shader;
        +export { Shader };
        +
        +if(typeof p5 !== 'undefined'){
        +  shader(p5, p5.prototype);
        +}
        diff --git a/src/webgl/p5.Texture.js b/src/webgl/p5.Texture.js
        index 04b59b487d..b922bb3c45 100644
        --- a/src/webgl/p5.Texture.js
        +++ b/src/webgl/p5.Texture.js
        @@ -6,41 +6,15 @@
          * @requires core
          */
         
        -import p5 from '../core/main';
         import * as constants from '../core/constants';
        -
        -/**
        - * Texture class for WEBGL Mode
        - * @private
        - * @class p5.Texture
        - * @param {p5.RendererGL} renderer an instance of p5.RendererGL that
        - * will provide the GL context for this new p5.Texture
        - * @param {p5.Image|p5.Graphics|p5.Element|p5.MediaElement|ImageData|p5.Framebuffer|p5.FramebufferTexture|ImageData} [obj] the
        - * object containing the image data to store in the texture.
        - * @param {Object} [settings] optional A javascript object containing texture
        - * settings.
        - * @param {Number} [settings.format] optional The internal color component
        - * format for the texture. Possible values for format include gl.RGBA,
        - * gl.RGB, gl.ALPHA, gl.LUMINANCE, gl.LUMINANCE_ALPHA. Defaults to gl.RBGA
        - * @param {Number} [settings.minFilter] optional The texture minification
        - * filter setting. Possible values are gl.NEAREST or gl.LINEAR. Defaults
        - * to gl.LINEAR. Note, Mipmaps are not implemented in p5.
        - * @param {Number} [settings.magFilter] optional The texture magnification
        - * filter setting. Possible values are gl.NEAREST or gl.LINEAR. Defaults
        - * to gl.LINEAR. Note, Mipmaps are not implemented in p5.
        - * @param {Number} [settings.wrapS] optional The texture wrap settings for
        - * the s coordinate, or x axis. Possible values are gl.CLAMP_TO_EDGE,
        - * gl.REPEAT, and gl.MIRRORED_REPEAT. The mirror settings are only available
        - * when using a power of two sized texture. Defaults to gl.CLAMP_TO_EDGE
        - * @param {Number} [settings.wrapT] optional The texture wrap settings for
        - * the t coordinate, or y axis. Possible values are gl.CLAMP_TO_EDGE,
        - * gl.REPEAT, and gl.MIRRORED_REPEAT. The mirror settings are only available
        - * when using a power of two sized texture. Defaults to gl.CLAMP_TO_EDGE
        - * @param {Number} [settings.dataType] optional The data type of the texel
        - * data. Possible values are gl.UNSIGNED_BYTE or gl.FLOAT. There are more
        - * formats that are not implemented in p5. Defaults to gl.UNSIGNED_BYTE.
        - */
        -p5.Texture = class Texture {
        +import { Element } from '../dom/p5.Element';
        +import { Renderer } from '../core/p5.Renderer';
        +import { MediaElement } from '../dom/p5.MediaElement';
        +import { Image } from '../image/p5.Image';
        +import { Graphics } from '../core/p5.Graphics';
        +import { FramebufferTexture } from './p5.Framebuffer';
        +
        +class Texture {
           constructor (renderer, obj, settings) {
             this._renderer = renderer;
         
        @@ -89,20 +63,20 @@ p5.Texture = class Texture {
         
             // used to determine if this texture might need constant updating
             // because it is a video or gif.
        -    this.isSrcMediaElement =
        -      typeof p5.MediaElement !== 'undefined' && obj instanceof p5.MediaElement;
        +    this.isSrcMediaElement = false;
        +      typeof MediaElement !== 'undefined' && obj instanceof MediaElement;
             this._videoPrevUpdateTime = 0;
             this.isSrcHTMLElement =
        -      typeof p5.Element !== 'undefined' &&
        -      obj instanceof p5.Element &&
        -      !(obj instanceof p5.Graphics) &&
        -      !(obj instanceof p5.Renderer);
        -    this.isSrcP5Image = obj instanceof p5.Image;
        -    this.isSrcP5Graphics = obj instanceof p5.Graphics;
        -    this.isSrcP5Renderer = obj instanceof p5.Renderer;
        +      typeof Element !== 'undefined' &&
        +      obj instanceof Element &&
        +      !(obj instanceof Graphics) &&
        +      !(obj instanceof Renderer);
        +    this.isSrcP5Image = obj instanceof Image;
        +    this.isSrcP5Graphics = obj instanceof Graphics;
        +    this.isSrcP5Renderer = obj instanceof Renderer;
             this.isImageData =
               typeof ImageData !== 'undefined' && obj instanceof ImageData;
        -    this.isFramebufferTexture = obj instanceof p5.FramebufferTexture;
        +    this.isFramebufferTexture = obj instanceof FramebufferTexture;
         
             const textureData = this._getTextureDataFromSource();
             this.width = textureData.width;
        @@ -121,15 +95,15 @@ p5.Texture = class Texture {
               textureData = this.src.canvas;
             } else if (
               this.isSrcMediaElement ||
        -    this.isSrcP5Graphics ||
        -    this.isSrcP5Renderer ||
        -    this.isSrcHTMLElement
        +      this.isSrcHTMLElement
             ) {
        -    // if param is a video HTML element
        +      // if param is a video HTML element
               if (this.src._ensureCanvas) {
                 this.src._ensureCanvas();
               }
        -      textureData = this.src.canvas || this.src.elt;
        +      textureData = this.src.elt;
        +    } else if (this.isSrcP5Graphics || this.isSrcP5Renderer) {
        +      textureData = this.src.canvas;
             } else if (this.isImageData) {
               textureData = this.src;
             }
        @@ -149,8 +123,8 @@ p5.Texture = class Texture {
               this.glTex = gl.createTexture();
             }
         
        -    this.glWrapS = this._renderer.textureWrapX;
        -    this.glWrapT = this._renderer.textureWrapY;
        +    this.glWrapS = this._renderer.states.textureWrapX;
        +    this.glWrapT = this._renderer.states.textureWrapY;
         
             this.setWrapMode(this.glWrapS, this.glWrapT);
             this.bindTexture();
        @@ -236,7 +210,7 @@ p5.Texture = class Texture {
                 // flag for update in a future frame.
                 // if we don't do this, a paused video, for example, may not
                 // send the first frame to texture memory.
        -        data.setModified(true);
        +        data.setModified && data.setModified(true);
               }
             } else if (this.isSrcP5Image) {
               // for an image, we only update if the modified field has been set,
        @@ -453,9 +427,9 @@ p5.Texture = class Texture {
             gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, this.glWrapT);
             this.unbindTexture();
           }
        -};
        +}
         
        -export class MipmapTexture extends p5.Texture {
        +class MipmapTexture extends Texture {
           constructor(renderer, levels, settings) {
             super(renderer, levels, settings);
             const gl = this._renderer.GL;
        @@ -500,6 +474,43 @@ export class MipmapTexture extends p5.Texture {
           update() {}
         }
         
        +function texture(p5, fn){
        +  /**
        +   * Texture class for WEBGL Mode
        +   * @private
        +   * @class p5.Texture
        +   * @param {p5.RendererGL} renderer an instance of p5.RendererGL that
        +   * will provide the GL context for this new p5.Texture
        +   * @param {p5.Image|p5.Graphics|p5.Element|p5.MediaElement|ImageData|p5.Framebuffer|p5.FramebufferTexture|ImageData} [obj] the
        +   * object containing the image data to store in the texture.
        +   * @param {Object} [settings] optional A javascript object containing texture
        +   * settings.
        +   * @param {Number} [settings.format] optional The internal color component
        +   * format for the texture. Possible values for format include gl.RGBA,
        +   * gl.RGB, gl.ALPHA, gl.LUMINANCE, gl.LUMINANCE_ALPHA. Defaults to gl.RBGA
        +   * @param {Number} [settings.minFilter] optional The texture minification
        +   * filter setting. Possible values are gl.NEAREST or gl.LINEAR. Defaults
        +   * to gl.LINEAR. Note, Mipmaps are not implemented in p5.
        +   * @param {Number} [settings.magFilter] optional The texture magnification
        +   * filter setting. Possible values are gl.NEAREST or gl.LINEAR. Defaults
        +   * to gl.LINEAR. Note, Mipmaps are not implemented in p5.
        +   * @param {Number} [settings.wrapS] optional The texture wrap settings for
        +   * the s coordinate, or x axis. Possible values are gl.CLAMP_TO_EDGE,
        +   * gl.REPEAT, and gl.MIRRORED_REPEAT. The mirror settings are only available
        +   * when using a power of two sized texture. Defaults to gl.CLAMP_TO_EDGE
        +   * @param {Number} [settings.wrapT] optional The texture wrap settings for
        +   * the t coordinate, or y axis. Possible values are gl.CLAMP_TO_EDGE,
        +   * gl.REPEAT, and gl.MIRRORED_REPEAT. The mirror settings are only available
        +   * when using a power of two sized texture. Defaults to gl.CLAMP_TO_EDGE
        +   * @param {Number} [settings.dataType] optional The data type of the texel
        +   * data. Possible values are gl.UNSIGNED_BYTE or gl.FLOAT. There are more
        +   * formats that are not implemented in p5. Defaults to gl.UNSIGNED_BYTE.
        +   */
        +  p5.Texture = Texture;
        +
        +  p5.MipmapTexture = MipmapTexture;
        +}
        +
         export function checkWebGLCapabilities({ GL, webglVersion }) {
           const gl = GL;
           const supportsFloat = webglVersion === constants.WEBGL2
        @@ -521,4 +532,9 @@ export function checkWebGLCapabilities({ GL, webglVersion }) {
           };
         }
         
        -export default p5.Texture;
        +export default texture;
        +export { Texture, MipmapTexture };
        +
        +if(typeof p5 !== 'undefined'){
        +  texture(p5, p5.prototype);
        +}
        diff --git a/src/webgl/shaders/font.vert b/src/webgl/shaders/font.vert
        index 4655bca0da..ce8b84ab18 100644
        --- a/src/webgl/shaders/font.vert
        +++ b/src/webgl/shaders/font.vert
        @@ -24,7 +24,7 @@ void main() {
             1. / length(newOrigin - newDX),
             1. / length(newOrigin - newDY)
           );
        -  vec2 offset = pixelScale * normalize(aTexCoord - vec2(0.5, 0.5)) * vec2(1., -1.);
        +  vec2 offset = pixelScale * normalize(aTexCoord - vec2(0.5, 0.5));
           vec2 textureOffset = offset * (1. / vec2(
             uGlyphRect.z - uGlyphRect.x,
             uGlyphRect.w - uGlyphRect.y
        diff --git a/src/webgl/shaders/immediate.vert b/src/webgl/shaders/immediate.vert
        deleted file mode 100644
        index d430e60302..0000000000
        --- a/src/webgl/shaders/immediate.vert
        +++ /dev/null
        @@ -1,15 +0,0 @@
        -IN vec3 aPosition;
        -IN vec4 aVertexColor;
        -
        -uniform mat4 uModelViewMatrix;
        -uniform mat4 uProjectionMatrix;
        -uniform float uResolution;
        -uniform float uPointSize;
        -
        -OUT vec4 vColor;
        -void main(void) {
        -  vec4 positionVec4 = vec4(aPosition, 1.0);
        -  gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        -  vColor = aVertexColor;
        -  gl_PointSize = uPointSize;
        -}
        diff --git a/src/webgl/shaders/light.vert b/src/webgl/shaders/light.vert
        index c0d3b25939..0d4f080197 100644
        --- a/src/webgl/shaders/light.vert
        +++ b/src/webgl/shaders/light.vert
        @@ -33,5 +33,5 @@ void main(void) {
             }
           }
           
        -  vColor = (uUseVertexColor ? aVertexColor : uMaterialColor);
        +  vColor = ((uUseVertexColor && aVertexColor.x >= 0.0) ? aVertexColor : uMaterialColor);
         }
        diff --git a/src/webgl/shaders/line.frag b/src/webgl/shaders/line.frag
        index f4b5a5c40b..a0ca059040 100644
        --- a/src/webgl/shaders/line.frag
        +++ b/src/webgl/shaders/line.frag
        @@ -1,4 +1,5 @@
        -precision mediump int;
        +precision highp int;
        +precision highp float;
         
         uniform vec4 uMaterialColor;
         uniform int uStrokeCap;
        diff --git a/src/webgl/shaders/line.vert b/src/webgl/shaders/line.vert
        index bc67818ed9..de422ad6b6 100644
        --- a/src/webgl/shaders/line.vert
        +++ b/src/webgl/shaders/line.vert
        @@ -18,20 +18,30 @@
         
         #define PROCESSING_LINE_SHADER
         
        -precision mediump int;
        +#define HOOK_DEFINES
         
        +precision highp int;
        +precision highp float;
        +
        +#ifdef AUGMENTED_HOOK_getWorldInputs
        +uniform mat4 uModelMatrix;
        +uniform mat4 uViewMatrix;
        +#else
         uniform mat4 uModelViewMatrix;
        +#endif
        +
         uniform mat4 uProjectionMatrix;
         uniform float uStrokeWeight;
         
         uniform bool uUseLineColor;
        +uniform bool uSimpleLines;
         uniform vec4 uMaterialColor;
         
         uniform vec4 uViewport;
         uniform int uPerspective;
         uniform int uStrokeJoin;
         
        -IN vec4 aPosition;
        +IN vec3 aPosition;
         IN vec3 aTangentIn;
         IN vec3 aTangentOut;
         IN float aSide;
        @@ -64,25 +74,66 @@ vec2 lineIntersection(vec2 aPoint, vec2 aDir, vec2 bPoint, vec2 bDir) {
           return aPoint + aDir * intersectionDistance;
         }
         
        +struct StrokeVertex {
        +  vec3 position;
        +  vec3 tangentIn;
        +  vec3 tangentOut;
        +  vec4 color;
        +  float weight;
        +};
        +
         void main() {
           HOOK_beforeVertex();
        -  // Caps have one of either the in or out tangent set to 0
        -  vCap = (aTangentIn == vec3(0.)) != (aTangentOut == (vec3(0.)))
        -    ? 1. : 0.;
        -
        -  // Joins have two unique, defined tangents
        -  vJoin = (
        -    aTangentIn != vec3(0.) &&
        -    aTangentOut != vec3(0.) &&
        -    aTangentIn != aTangentOut
        -  ) ? 1. : 0.;
        -
        -  vec4 localPosition = vec4(HOOK_getLocalPosition(aPosition.xyz), 1.);
        -  vec4 posp = vec4(HOOK_getWorldPosition((uModelViewMatrix * localPosition).xyz), 1.);
        -  vec4 posqIn = posp + uModelViewMatrix * vec4(aTangentIn, 0);
        -  vec4 posqOut = posp + uModelViewMatrix * vec4(aTangentOut, 0);
        -  float strokeWeight = HOOK_getStrokeWeight(uStrokeWeight);
        -  vStrokeWeight = strokeWeight;
        +
        +  if (!uSimpleLines) {
        +      // Caps have one of either the in or out tangent set to 0
        +      vCap = (aTangentIn == vec3(0.)) != (aTangentOut == vec3(0.)) ? 1. : 0.;
        +
        +      // Joins have two unique, defined tangents
        +      vJoin = (
        +          aTangentIn != vec3(0.) &&
        +          aTangentOut != vec3(0.) &&
        +          aTangentIn != aTangentOut
        +      ) ? 1. : 0.;
        +  }
        +
        +  StrokeVertex inputs;
        +  inputs.position = aPosition.xyz;
        +  inputs.color = uUseLineColor ? aVertexColor : uMaterialColor;
        +  inputs.weight = uStrokeWeight;
        +  inputs.tangentIn = aTangentIn;
        +  inputs.tangentOut = aTangentOut;
        +
        +#ifdef AUGMENTED_HOOK_getObjectInputs
        +  inputs = HOOK_getObjectInputs(inputs);
        +#endif
        +
        +#ifdef AUGMENTED_HOOK_getWorldInputs
        +  inputs.position = (uModelMatrix * vec4(inputs.position, 1.)).xyz;
        +  inputs.tangentIn = (uModelMatrix * vec4(aTangentIn, 0.)).xyz;
        +  inputs.tangentOut = (uModelMatrix * vec4(aTangentOut, 0.)).xyz;
        +  inputs = HOOK_getWorldInputs(inputs);
        +#endif
        +
        +#ifdef AUGMENTED_HOOK_getWorldInputs
        +  // Already multiplied by the model matrix, just apply view
        +  inputs.position = (uViewMatrix * vec4(inputs.position, 1.)).xyz;
        +  inputs.tangentIn = (uViewMatrix * vec4(aTangentIn, 0.)).xyz;
        +  inputs.tangentOut = (uViewMatrix * vec4(aTangentOut, 0.)).xyz;
        +#else
        +  // Apply both at once
        +  inputs.position = (uModelViewMatrix * vec4(inputs.position, 1.)).xyz;
        +  inputs.tangentIn = (uModelViewMatrix * vec4(aTangentIn, 0.)).xyz;
        +  inputs.tangentOut = (uModelViewMatrix * vec4(aTangentOut, 0.)).xyz;
        +#endif
        +#ifdef AUGMENTED_HOOK_getCameraInputs
        +  inputs = hook_getCameraInputs(inputs);
        +#endif
        +
        +  vec4 posp = vec4(inputs.position, 1.);
        +  vec4 posqIn = vec4(inputs.position + inputs.tangentIn, 1.);
        +  vec4 posqOut = vec4(inputs.position + inputs.tangentOut, 1.);
        +  vStrokeWeight = inputs.weight;
         
           float facingCamera = pow(
             // The word space tangent's z value is 0 if it's facing the camera
        @@ -116,7 +167,7 @@ void main() {
         
           // Moving vertices slightly toward camera when far away 
           // https://github.com/processing/p5.js/issues/6956 
        -  float zOffset = mix(-0.00045, -1., facingCamera);
        +  float zOffset = mix(0., -1., facingCamera);
           float dynamicZAdjustment = mix(0.0, zOffset, distanceFactor); // Closer = less zAdjustment, farther = more
         
           posp.z -= dynamicZAdjustment;
        @@ -126,7 +177,6 @@ void main() {
           vec4 p = uProjectionMatrix * posp;
           vec4 qIn = uProjectionMatrix * posqIn;
           vec4 qOut = uProjectionMatrix * posqOut;
        -  vCenter = HOOK_getLineCenter(p.xy);
         
           // formula to convert from clip space (range -1..1) to screen space (range 0..[width or height])
           // screen_p = (p.xy/p.w + <1,1>) * 0.5 * uViewport.zw
        @@ -170,7 +220,7 @@ void main() {
           }
         
           vec2 offset;
        -  if (vJoin == 1.) {
        +  if (vJoin == 1. && !uSimpleLines) {
             vTangent = normalize(tangentIn + tangentOut);
             vec2 normalIn = vec2(-tangentIn.y, tangentIn.x);
             vec2 normalOut = vec2(-tangentOut.y, tangentOut.x);
        @@ -191,9 +241,9 @@ void main() {
                 // find where the lines intersect to find the elbow of the join
                 vec2 c = (posp.xy/posp.w + vec2(1.,1.)) * 0.5 * uViewport.zw;
                 vec2 intersection = lineIntersection(
        -          c + (side * normalIn * strokeWeight / 2.),
        +          c + (side * normalIn * inputs.weight / 2.),
                   tangentIn,
        -          c + (side * normalOut * strokeWeight / 2.),
        +          c + (side * normalOut * inputs.weight / 2.),
                   tangentOut
                 );
                 offset = (intersection - c);
        @@ -203,21 +253,21 @@ void main() {
                 // the magnitude to avoid lines going across the whole screen when this
                 // happens.
                 float mag = length(offset);
        -        float maxMag = 3. * strokeWeight;
        +        float maxMag = 3. * inputs.weight;
                 if (mag > maxMag) {
                   offset *= maxMag / mag;
                 }
               } else if (sideEnum == 1.) {
        -        offset = side * normalIn * strokeWeight / 2.;
        +        offset = side * normalIn * inputs.weight / 2.;
               } else if (sideEnum == 3.) {
        -        offset = side * normalOut * strokeWeight / 2.;
        +        offset = side * normalOut * inputs.weight / 2.;
               }
             }
             if (uStrokeJoin == STROKE_JOIN_BEVEL) {
               vec2 avgNormal = vec2(-vTangent.y, vTangent.x);
        -      vMaxDist = abs(dot(avgNormal, normalIn * strokeWeight / 2.));
        +      vMaxDist = abs(dot(avgNormal, normalIn * inputs.weight / 2.));
             } else {
        -      vMaxDist = strokeWeight / 2.;
        +      vMaxDist = inputs.weight / 2.;
             }
           } else {
             vec2 tangent = aTangentIn == vec3(0.) ? tangentOut : tangentIn;
        @@ -229,14 +279,16 @@ void main() {
             // extends out from the line
             float tangentOffset = abs(aSide) - 1.;
             offset = (normal * normalOffset + tangent * tangentOffset) *
        -      strokeWeight * 0.5;
        -    vMaxDist = strokeWeight / 2.;
        +      inputs.weight * 0.5;
        +    vMaxDist = inputs.weight / 2.;
           }
        -  vPosition = HOOK_getLinePosition(vCenter + offset);
        +
        +  vCenter = p.xy;
        +  vPosition = vCenter + offset;
        +  vColor = inputs.color;
         
           gl_Position.xy = p.xy + offset.xy * curPerspScale;
           gl_Position.zw = p.zw;
           
        -  vColor = HOOK_getVertexColor(uUseLineColor ? aVertexColor : uMaterialColor);
           HOOK_afterVertex();
         }
        diff --git a/src/webgl/shaders/normal.vert b/src/webgl/shaders/normal.vert
        index f31f3016e7..9b466b54c6 100644
        --- a/src/webgl/shaders/normal.vert
        +++ b/src/webgl/shaders/normal.vert
        @@ -3,9 +3,18 @@ IN vec3 aNormal;
         IN vec2 aTexCoord;
         IN vec4 aVertexColor;
         
        +#define HOOK_DEFINES
        +
        +#ifdef AUGMENTED_HOOK_getWorldInputs
        +uniform mat4 uModelMatrix;
        +uniform mat4 uViewMatrix;
        +uniform mat3 uModelNormalMatrix;
        +uniform mat3 uCameraNormalMatrix;
        +#else
         uniform mat4 uModelViewMatrix;
        -uniform mat4 uProjectionMatrix;
         uniform mat3 uNormalMatrix;
        +#endif
        +uniform mat4 uProjectionMatrix;
         
         uniform vec4 uMaterialColor;
         uniform bool uUseVertexColor;
        @@ -14,16 +23,50 @@ OUT vec3 vVertexNormal;
         OUT highp vec2 vVertTexCoord;
         OUT vec4 vColor;
         
        +struct Vertex {
        +  vec3 position;
        +  vec3 normal;
        +  vec2 uv;
        +  vec4 color;
        +};
        +
         void main(void) {
           HOOK_beforeVertex();
        -  vec4 positionVec4 = vec4(HOOK_getWorldPosition(
        -    (uModelViewMatrix * vec4(HOOK_getLocalPosition(aPosition), 1.0)).xyz
        -  ), 1.);
         
        -  gl_Position = uProjectionMatrix * positionVec4;
        +  Vertex inputs;
        +  inputs.position = aPosition;
        +  inputs.normal = aNormal;
        +  inputs.uv = aTexCoord;
        +  inputs.color = (uUseVertexColor && aVertexColor.x >= 0.0) ? aVertexColor : uMaterialColor;
        +#ifdef AUGMENTED_HOOK_getObjectInputs
        +  inputs = HOOK_getObjectInputs(inputs);
        +#endif
        +
        +#ifdef AUGMENTED_HOOK_getWorldInputs
        +  inputs.position = (uModelMatrix * vec4(inputs.position, 1.)).xyz;
        +  inputs.normal = uModelNormalMatrix * inputs.normal;
        +  inputs = HOOK_getWorldInputs(inputs);
        +#endif
        +
        +#ifdef AUGMENTED_HOOK_getWorldInputs
        +  // Already multiplied by the model matrix, just apply view
        +  inputs.position = (uViewMatrix * vec4(inputs.position, 1.)).xyz;
        +  inputs.normal = uCameraNormalMatrix * inputs.normal;
        +#else
        +  // Apply both at once
        +  inputs.position = (uModelViewMatrix * vec4(inputs.position, 1.)).xyz;
        +  inputs.normal = uNormalMatrix * inputs.normal;
        +#endif
        +#ifdef AUGMENTED_HOOK_getCameraInputs
        +  inputs = HOOK_getCameraInputs(inputs);
        +#endif
        +
        +  // Pass varyings to fragment shader
        +  vVertTexCoord = inputs.uv;
        +  vVertexNormal = normalize(inputs.normal);
        +  vColor = inputs.color;
        +
        +  gl_Position = uProjectionMatrix * vec4(inputs.position, 1.);
         
        -  vVertexNormal = HOOK_getWorldNormal(normalize(uNormalMatrix * HOOK_getLocalNormal(aNormal)));
        -  vVertTexCoord = HOOK_getUV(aTexCoord);
        -  vColor = HOOK_getVertexColor(uUseVertexColor ? aVertexColor : uMaterialColor);
           HOOK_afterVertex();
         }
        diff --git a/src/webgl/shaders/phong.frag b/src/webgl/shaders/phong.frag
        index e141f62f1f..d4d2205b80 100644
        --- a/src/webgl/shaders/phong.frag
        +++ b/src/webgl/shaders/phong.frag
        @@ -47,13 +47,13 @@ void main(void) {
           inputs.texCoord = vTexCoord;
           inputs.ambientLight = vAmbientColor;
           inputs.color = isTexture
        -      // Textures come in with premultiplied alpha. To apply tint and still have
        -      // premultiplied alpha output, we need to multiply the RGB channels by the
        -      // tint RGB, and all channels by the tint alpha.
        -      ? TEXTURE(uSampler, vTexCoord) * vec4(uTint.rgb/255., 1.) * (uTint.a/255.)
        -      // Colors come in with unmultiplied alpha, so we need to multiply the RGB
        -      // channels by alpha to convert it to premultiplied alpha.
        -      : vec4(vColor.rgb * vColor.a, vColor.a);
        +      ? TEXTURE(uSampler, vTexCoord) * uTint/255.
        +      : vColor;
        +  if (isTexture && inputs.color.a > 0.0) {
        +    // Textures come in with premultiplied alpha. Temporarily unpremultiply it
        +    // so hooks users don't have to think about premultiplied alpha.
        +    inputs.color.rgb /= inputs.color.a;
        +  }
           inputs.shininess = uShininess;
           inputs.metalness = uMetallic;
           inputs.ambientMaterial = uHasSetAmbient ? uAmbientMatColor.rgb : inputs.color.rgb;
        @@ -79,5 +79,6 @@ void main(void) {
           c.specular = specular;
           c.emissive = inputs.emissiveMaterial;
           OUT_COLOR = HOOK_getFinalColor(HOOK_combineColors(c));
        +  OUT_COLOR.rgb *= OUT_COLOR.a; // Premultiply alpha before rendering
           HOOK_afterFragment();
         }
        diff --git a/src/webgl/shaders/phong.vert b/src/webgl/shaders/phong.vert
        index 94f29a25da..f59c09744a 100644
        --- a/src/webgl/shaders/phong.vert
        +++ b/src/webgl/shaders/phong.vert
        @@ -1,5 +1,7 @@
         precision highp int;
         
        +#define HOOK_DEFINES
        +
         IN vec3 aPosition;
         IN vec3 aNormal;
         IN vec2 aTexCoord;
        @@ -7,9 +9,16 @@ IN vec4 aVertexColor;
         
         uniform vec3 uAmbientColor[5];
         
        +#ifdef AUGMENTED_HOOK_getWorldInputs
        +uniform mat4 uModelMatrix;
        +uniform mat4 uViewMatrix;
        +uniform mat3 uModelNormalMatrix;
        +uniform mat3 uCameraNormalMatrix;
        +#else
         uniform mat4 uModelViewMatrix;
        -uniform mat4 uProjectionMatrix;
         uniform mat3 uNormalMatrix;
        +#endif
        +uniform mat4 uProjectionMatrix;
         uniform int uAmbientLightCount;
         
         uniform bool uUseVertexColor;
        @@ -21,18 +30,49 @@ OUT vec3 vViewPosition;
         OUT vec3 vAmbientColor;
         OUT vec4 vColor;
         
        +struct Vertex {
        +  vec3 position;
        +  vec3 normal;
        +  vec2 uv;
        +  vec4 color;
        +};
        +
         void main(void) {
           HOOK_beforeVertex();
        -  vec4 viewModelPosition = vec4(HOOK_getWorldPosition(
        -    (uModelViewMatrix * vec4(HOOK_getLocalPosition(aPosition), 1.0)).xyz
        -  ), 1.);
         
        -  // Pass varyings to fragment shader
        -  vViewPosition = viewModelPosition.xyz;
        -  gl_Position = uProjectionMatrix * viewModelPosition;  
        +  Vertex inputs;
        +  inputs.position = aPosition;
        +  inputs.normal = aNormal;
        +  inputs.uv = aTexCoord;
        +  inputs.color = (uUseVertexColor && aVertexColor.x >= 0.0) ? aVertexColor : uMaterialColor;
        +#ifdef AUGMENTED_HOOK_getObjectInputs
        +  inputs = HOOK_getObjectInputs(inputs);
        +#endif
         
        -  vNormal = HOOK_getWorldNormal(uNormalMatrix * HOOK_getLocalNormal(aNormal));
        -  vTexCoord = HOOK_getUV(aTexCoord);
        +#ifdef AUGMENTED_HOOK_getWorldInputs
        +  inputs.position = (uModelMatrix * vec4(inputs.position, 1.)).xyz;
        +  inputs.normal = uModelNormalMatrix * inputs.normal;
        +  inputs = HOOK_getWorldInputs(inputs);
        +#endif
        +
        +#ifdef AUGMENTED_HOOK_getWorldInputs
        +  // Already multiplied by the model matrix, just apply view
        +  inputs.position = (uViewMatrix * vec4(inputs.position, 1.)).xyz;
        +  inputs.normal = uCameraNormalMatrix * inputs.normal;
        +#else
        +  // Apply both at once
        +  inputs.position = (uModelViewMatrix * vec4(inputs.position, 1.)).xyz;
        +  inputs.normal = uNormalMatrix * inputs.normal;
        +#endif
        +#ifdef AUGMENTED_HOOK_getCameraInputs
        +  inputs = HOOK_getCameraInputs(inputs);
        +#endif
        +
        +  // Pass varyings to fragment shader
        +  vViewPosition = inputs.position;
        +  vTexCoord = inputs.uv;
        +  vNormal = inputs.normal;
        +  vColor = inputs.color;
         
           // TODO: this should be a uniform
           vAmbientColor = vec3(0.0);
        @@ -41,7 +81,7 @@ void main(void) {
               vAmbientColor += uAmbientColor[i];
             }
           }
        -  
        -  vColor = HOOK_getVertexColor((uUseVertexColor ? aVertexColor : uMaterialColor));
        +
        +  gl_Position = uProjectionMatrix * vec4(inputs.position, 1.);
           HOOK_afterVertex();
         }
        diff --git a/src/webgl/shaders/vertexColor.frag b/src/webgl/shaders/vertexColor.frag
        deleted file mode 100644
        index 11b14ea09c..0000000000
        --- a/src/webgl/shaders/vertexColor.frag
        +++ /dev/null
        @@ -1,4 +0,0 @@
        -IN vec4 vColor;
        -void main(void) {
        -  OUT_COLOR = vec4(vColor.rgb, 1.) * vColor.a;
        -}
        diff --git a/src/webgl/shaders/vertexColor.vert b/src/webgl/shaders/vertexColor.vert
        deleted file mode 100644
        index 3dc3df2434..0000000000
        --- a/src/webgl/shaders/vertexColor.vert
        +++ /dev/null
        @@ -1,13 +0,0 @@
        -IN vec3 aPosition;
        -IN vec4 aVertexColor;
        -
        -uniform mat4 uModelViewMatrix;
        -uniform mat4 uProjectionMatrix;
        -
        -OUT vec4 vColor;
        -
        -void main(void) {
        -  vec4 positionVec4 = vec4(aPosition, 1.0);
        -  gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;
        -  vColor = aVertexColor;
        -}
        diff --git a/src/webgl/text.js b/src/webgl/text.js
        index 0e427a57be..1ed420c9c5 100644
        --- a/src/webgl/text.js
        +++ b/src/webgl/text.js
        @@ -1,769 +1,776 @@
        -import p5 from '../core/main';
         import * as constants from '../core/constants';
        -import './p5.Shader';
        -import './p5.RendererGL.Retained';
        -
        -// Text/Typography
        -// @TODO:
        -p5.RendererGL.prototype._applyTextProperties = function() {
        -  //@TODO finish implementation
        -  //console.error('text commands not yet implemented in webgl');
        -  this._setProperty('_textAscent', null);
        -  this._setProperty('_textDescent', null);
        -};
        -
        -p5.RendererGL.prototype.textWidth = function(s) {
        -  if (this._isOpenType()) {
        -    return this._textFont._textWidth(s, this._textSize);
        -  }
        -
        -  return 0; // TODO: error
        -};
        -
        -// rendering constants
        -
        -// the number of rows/columns dividing each glyph
        -const charGridWidth = 9;
        -const charGridHeight = charGridWidth;
        -
        -// size of the image holding the bezier stroke info
        -const strokeImageWidth = 64;
        -const strokeImageHeight = 64;
        -
        -// size of the image holding the stroke indices for each row/col
        -const gridImageWidth = 64;
        -const gridImageHeight = 64;
        -
        -// size of the image holding the offset/length of each row/col stripe
        -const cellImageWidth = 64;
        -const cellImageHeight = 64;
        -
        -/**
        - * @private
        - * @class ImageInfos
        - * @param {Integer} width
        - * @param {Integer} height
        - *
        - * the ImageInfos class holds a list of ImageDatas of a given size.
        - */
        -class ImageInfos {
        -  constructor(width, height) {
        -    this.width = width;
        -    this.height = height;
        -    this.infos = []; // the list of images
        -  }
        -  /**
        -     *
        -     * @method findImage
        -     * @param {Integer} space
        -     * @return {Object} contains the ImageData, and pixel index into that
        -     *                  ImageData where the free space was allocated.
        -     *
        -     * finds free space of a given size in the ImageData list
        -     */
        -  findImage (space) {
        -    const imageSize = this.width * this.height;
        -    if (space > imageSize)
        -      throw new Error('font is too complex to render in 3D');
        -
        -    // search through the list of images, looking for one with
        -    // anough unused space.
        -    let imageInfo, imageData;
        -    for (let ii = this.infos.length - 1; ii >= 0; --ii) {
        -      const imageInfoTest = this.infos[ii];
        -      if (imageInfoTest.index + space < imageSize) {
        -        // found one
        -        imageInfo = imageInfoTest;
        -        imageData = imageInfoTest.imageData;
        -        break;
        -      }
        +import { RendererGL } from './p5.RendererGL';
        +import { Vector } from '../math/p5.Vector';
        +import { Geometry } from './p5.Geometry';
        +import { arrayCommandsToObjects } from '../type/p5.Font';
        +
        +function text(p5, fn){
        +  // Text/Typography
        +  RendererGL.prototype.textWidth = function(s) {
        +    if (this._isOpenType()) {
        +      return this.states.textFont.font._textWidth(s, this.states.textSize);
             }
         
        -    if (!imageInfo) {
        -      try {
        -        // create a new image
        -        imageData = new ImageData(this.width, this.height);
        -      } catch (err) {
        -        // for browsers that don't support ImageData constructors (ie IE11)
        -        // create an ImageData using the old method
        -        let canvas = document.getElementsByTagName('canvas')[0];
        -        const created = !canvas;
        -        if (!canvas) {
        -          // create a temporary canvas
        -          canvas = document.createElement('canvas');
        -          canvas.style.display = 'none';
        -          document.body.appendChild(canvas);
        -        }
        -        const ctx = canvas.getContext('2d');
        -        if (ctx) {
        -          imageData = ctx.createImageData(this.width, this.height);
        -        }
        -        if (created) {
        -          // distroy the temporary canvas, if necessary
        -          document.body.removeChild(canvas);
        -        }
        -      }
        -      // construct & dd the new image info
        -      imageInfo = { index: 0, imageData };
        -      this.infos.push(imageInfo);
        -    }
        +    return 0; // TODO: error
        +  };
         
        -    const index = imageInfo.index;
        -    imageInfo.index += space; // move to the start of the next image
        -    imageData._dirty = true;
        -    return { imageData, index };
        -  }
        -}
        +  // rendering constants
         
        -/**
        - * @function setPixel
        - * @param {Object} imageInfo
        - * @param {Number} r
        - * @param {Number} g
        - * @param {Number} b
        - * @param {Number} a
        - *
        - * writes the next pixel into an indexed ImageData
        - */
        -function setPixel(imageInfo, r, g, b, a) {
        -  const imageData = imageInfo.imageData;
        -  const pixels = imageData.data;
        -  let index = imageInfo.index++ * 4;
        -  pixels[index++] = r;
        -  pixels[index++] = g;
        -  pixels[index++] = b;
        -  pixels[index++] = a;
        -}
        +  // the number of rows/columns dividing each glyph
        +  const charGridWidth = 9;
        +  const charGridHeight = charGridWidth;
         
        -const SQRT3 = Math.sqrt(3);
        -
        -/**
        - * @private
        - * @class FontInfo
        - * @param {Object} font an opentype.js font object
        - *
        - * contains cached images and glyph information for an opentype font
        - */
        -class FontInfo {
        -  constructor(font) {
        -    this.font = font;
        -    // the bezier curve coordinates
        -    this.strokeImageInfos = new ImageInfos(strokeImageWidth, strokeImageHeight);
        -    // lists of curve indices for each row/column slice
        -    this.colDimImageInfos = new ImageInfos(gridImageWidth, gridImageHeight);
        -    this.rowDimImageInfos = new ImageInfos(gridImageWidth, gridImageHeight);
        -    // the offset & length of each row/col slice in the glyph
        -    this.colCellImageInfos = new ImageInfos(cellImageWidth, cellImageHeight);
        -    this.rowCellImageInfos = new ImageInfos(cellImageWidth, cellImageHeight);
        -
        -    // the cached information for each glyph
        -    this.glyphInfos = {};
        -  }
        -  /**
        -     * @method getGlyphInfo
        -     * @param {Glyph} glyph the x positions of points in the curve
        -     * @returns {Object} the glyphInfo for that glyph
        -     *
        -     * calculates rendering info for a glyph, including the curve information,
        -     * row & column stripes compiled into textures.
        -     */
        -  getGlyphInfo (glyph) {
        -    // check the cache
        -    let gi = this.glyphInfos[glyph.index];
        -    if (gi) return gi;
        -
        -    // get the bounding box of the glyph from opentype.js
        -    const bb = glyph.getBoundingBox();
        -    const xMin = bb.x1;
        -    const yMin = bb.y1;
        -    const gWidth = bb.x2 - xMin;
        -    const gHeight = bb.y2 - yMin;
        -    const cmds = glyph.path.commands;
        -    // don't bother rendering invisible glyphs
        -    if (gWidth === 0 || gHeight === 0 || !cmds.length) {
        -      return (this.glyphInfos[glyph.index] = {});
        -    }
        +  // size of the image holding the bezier stroke info
        +  const strokeImageWidth = 64;
        +  const strokeImageHeight = 64;
        +
        +  // size of the image holding the stroke indices for each row/col
        +  const gridImageWidth = 64;
        +  const gridImageHeight = 64;
         
        -    let i;
        -    const strokes = []; // the strokes in this glyph
        -    const rows = []; // the indices of strokes in each row
        -    const cols = []; // the indices of strokes in each column
        -    for (i = charGridWidth - 1; i >= 0; --i) cols.push([]);
        -    for (i = charGridHeight - 1; i >= 0; --i) rows.push([]);
        +  // size of the image holding the offset/length of each row/col stripe
        +  const cellImageWidth = 64;
        +  const cellImageHeight = 64;
         
        +  /**
        +   * @private
        +   * @class ImageInfos
        +   * @param {Integer} width
        +   * @param {Integer} height
        +   *
        +   * the ImageInfos class holds a list of ImageDatas of a given size.
        +   */
        +  class ImageInfos {
        +    constructor(width, height) {
        +      this.width = width;
        +      this.height = height;
        +      this.infos = []; // the list of images
        +    }
             /**
        -       * @function push
        -       * @param {Number[]} xs the x positions of points in the curve
        -       * @param {Number[]} ys the y positions of points in the curve
        -       * @param {Object} v    the curve information
                *
        -       * adds a curve to the rows & columns that it intersects with
        +       * @param {Integer} space
        +       * @return {Object} contains the ImageData, and pixel index into that
        +       *                  ImageData where the free space was allocated.
        +       *
        +       * finds free space of a given size in the ImageData list
                */
        -    function push(xs, ys, v) {
        -      const index = strokes.length; // the index of this stroke
        -      strokes.push(v); // add this stroke to the list
        -
        -      /**
        -         * @function minMax
        -         * @param {Number[]} rg the list of values to compare
        -         * @param {Number} min the initial minimum value
        -         * @param {Number} max the initial maximum value
        -         *
        -         * find the minimum & maximum value in a list of values
        -         */
        -      function minMax(rg, min, max) {
        -        for (let i = rg.length; i-- > 0; ) {
        -          const v = rg[i];
        -          if (min > v) min = v;
        -          if (max < v) max = v;
        +    findImage (space) {
        +      const imageSize = this.width * this.height;
        +      if (space > imageSize)
        +        throw new Error('font is too complex to render in 3D');
        +
        +      // search through the list of images, looking for one with
        +      // anough unused space.
        +      let imageInfo, imageData;
        +      for (let ii = this.infos.length - 1; ii >= 0; --ii) {
        +        const imageInfoTest = this.infos[ii];
        +        if (imageInfoTest.index + space < imageSize) {
        +          // found one
        +          imageInfo = imageInfoTest;
        +          imageData = imageInfoTest.imageData;
        +          break;
                 }
        -        return { min, max };
               }
         
        -      // Expand the bounding box of the glyph by the number of cells below
        -      // before rounding. Curves only partially through a cell won't be added
        -      // to adjacent cells, but ones that are close will be. This helps fix
        -      // small visual glitches that occur when curves are close to grid cell
        -      // boundaries.
        -      const cellOffset = 0.5;
        -
        -      // loop through the rows & columns that the curve intersects
        -      // adding the curve to those slices
        -      const mmX = minMax(xs, 1, 0);
        -      const ixMin = Math.max(
        -        Math.floor(mmX.min * charGridWidth - cellOffset),
        -        0
        -      );
        -      const ixMax = Math.min(
        -        Math.ceil(mmX.max * charGridWidth + cellOffset),
        -        charGridWidth
        -      );
        -      for (let iCol = ixMin; iCol < ixMax; ++iCol) cols[iCol].push(index);
        +      if (!imageInfo) {
        +        try {
        +          // create a new image
        +          imageData = new ImageData(this.width, this.height);
        +        } catch (err) {
        +          // for browsers that don't support ImageData constructors (ie IE11)
        +          // create an ImageData using the old method
        +          let canvas = document.getElementsByTagName('canvas')[0];
        +          const created = !canvas;
        +          if (!canvas) {
        +            // create a temporary canvas
        +            canvas = document.createElement('canvas');
        +            canvas.style.display = 'none';
        +            document.body.appendChild(canvas);
        +          }
        +          const ctx = canvas.getContext('2d');
        +          if (ctx) {
        +            imageData = ctx.createImageData(this.width, this.height);
        +          }
        +          if (created) {
        +            // distroy the temporary canvas, if necessary
        +            document.body.removeChild(canvas);
        +          }
        +        }
        +        // construct & dd the new image info
        +        imageInfo = { index: 0, imageData };
        +        this.infos.push(imageInfo);
        +      }
         
        -      const mmY = minMax(ys, 1, 0);
        -      const iyMin = Math.max(
        -        Math.floor(mmY.min * charGridHeight - cellOffset),
        -        0
        -      );
        -      const iyMax = Math.min(
        -        Math.ceil(mmY.max * charGridHeight + cellOffset),
        -        charGridHeight
        -      );
        -      for (let iRow = iyMin; iRow < iyMax; ++iRow) rows[iRow].push(index);
        +      const index = imageInfo.index;
        +      imageInfo.index += space; // move to the start of the next image
        +      imageData._dirty = true;
        +      return { imageData, index };
             }
        +  }
         
        -    /**
        -       * @function clamp
        -       * @param {Number} v the value to clamp
        -       * @param {Number} min the minimum value
        -       * @param {Number} max the maxmimum value
        -       *
        -       * clamps a value between a minimum & maximum value
        -       */
        -    function clamp(v, min, max) {
        -      if (v < min) return min;
        -      if (v > max) return max;
        -      return v;
        -    }
        +  /**
        +   * @function setPixel
        +   * @param {Object} imageInfo
        +   * @param {Number} r
        +   * @param {Number} g
        +   * @param {Number} b
        +   * @param {Number} a
        +   *
        +   * writes the next pixel into an indexed ImageData
        +   */
        +  function setPixel(imageInfo, r, g, b, a) {
        +    const imageData = imageInfo.imageData;
        +    const pixels = imageData.data;
        +    let index = imageInfo.index++ * 4;
        +    pixels[index++] = r;
        +    pixels[index++] = g;
        +    pixels[index++] = b;
        +    pixels[index++] = a;
        +  }
         
        -    /**
        -       * @function byte
        -       * @param {Number} v the value to scale
        -       *
        -       * converts a floating-point number in the range 0-1 to a byte 0-255
        -       */
        -    function byte(v) {
        -      return clamp(255 * v, 0, 255);
        -    }
        +  const SQRT3 = Math.sqrt(3);
         
        +  /**
        +   * @private
        +   * @class FontInfo
        +   * @param {Object} font an opentype.js font object
        +   *
        +   * contains cached images and glyph information for an opentype font
        +   */
        +  class FontInfo {
        +    constructor(font) {
        +      this.font = font;
        +      // the bezier curve coordinates
        +      this.strokeImageInfos = new ImageInfos(strokeImageWidth, strokeImageHeight);
        +      // lists of curve indices for each row/column slice
        +      this.colDimImageInfos = new ImageInfos(gridImageWidth, gridImageHeight);
        +      this.rowDimImageInfos = new ImageInfos(gridImageWidth, gridImageHeight);
        +      // the offset & length of each row/col slice in the glyph
        +      this.colCellImageInfos = new ImageInfos(cellImageWidth, cellImageHeight);
        +      this.rowCellImageInfos = new ImageInfos(cellImageWidth, cellImageHeight);
        +
        +      // the cached information for each glyph
        +      this.glyphInfos = {};
        +    }
             /**
        -       * @private
        -       * @class Cubic
        -       * @param {Number} p0 the start point of the curve
        -       * @param {Number} c0 the first control point
        -       * @param {Number} c1 the second control point
        -       * @param {Number} p1 the end point
        +       * @param {Glyph} glyph the x positions of points in the curve
        +       * @returns {Object} the glyphInfo for that glyph
                *
        -       * a cubic curve
        +       * calculates rendering info for a glyph, including the curve information,
        +       * row & column stripes compiled into textures.
                */
        -    class Cubic {
        -      constructor(p0, c0, c1, p1) {
        -        this.p0 = p0;
        -        this.c0 = c0;
        -        this.c1 = c1;
        -        this.p1 = p1;
        +    getGlyphInfo(glyph) {
        +      // check the cache
        +      let gi = this.glyphInfos[glyph.index];
        +      if (gi) return gi;
        +
        +      const { glyph: { path: { commands } } } = this.font._singleShapeToPath(glyph.shape);
        +      let xMin = Infinity;
        +      let xMax = -Infinity;
        +      let yMin = Infinity;
        +      let yMax = -Infinity;
        +
        +      for (const cmd of commands) {
        +        for (let i = 1; i < cmd.length; i += 2) {
        +          xMin = Math.min(xMin, cmd[i]);
        +          xMax = Math.max(xMax, cmd[i]);
        +          yMin = Math.min(yMin, cmd[i + 1]);
        +          yMax = Math.max(yMax, cmd[i + 1]);
        +        }
               }
        -      /**
        -           * @method toQuadratic
        -           * @return {Object} the quadratic approximation
        -           *
        -           * converts the cubic to a quadtratic approximation by
        -           * picking an appropriate quadratic control point
        -           */
        -      toQuadratic () {
        -        return {
        -          x: this.p0.x,
        -          y: this.p0.y,
        -          x1: this.p1.x,
        -          y1: this.p1.y,
        -          cx: ((this.c0.x + this.c1.x) * 3 - (this.p0.x + this.p1.x)) / 4,
        -          cy: ((this.c0.y + this.c1.y) * 3 - (this.p0.y + this.p1.y)) / 4
        -        };
        +
        +      // don't bother rendering invisible glyphs
        +      if (xMin >= xMax || yMin >= yMax || !commands.length) {
        +        return (this.glyphInfos[glyph.index] = {});
               }
         
        +      const gWidth = xMax - xMin;
        +      const gHeight = yMax - yMin;
        +
        +      // Convert arrays to named objects
        +      const cmds = arrayCommandsToObjects(commands);
        +
        +      let i;
        +      const strokes = []; // the strokes in this glyph
        +      const rows = []; // the indices of strokes in each row
        +      const cols = []; // the indices of strokes in each column
        +      for (i = charGridWidth - 1; i >= 0; --i) cols.push([]);
        +      for (i = charGridHeight - 1; i >= 0; --i) rows.push([]);
        +
               /**
        -           * @method quadError
        -           * @return {Number} the error
        +         * @function push
        +         * @param {Number[]} xs the x positions of points in the curve
        +         * @param {Number[]} ys the y positions of points in the curve
        +         * @param {Object} v    the curve information
        +         *
        +         * adds a curve to the rows & columns that it intersects with
        +         */
        +      function push(xs, ys, v) {
        +        const index = strokes.length; // the index of this stroke
        +        strokes.push(v); // add this stroke to the list
        +
        +        /**
        +           * @function minMax
        +           * @param {Number[]} rg the list of values to compare
        +           * @param {Number} min the initial minimum value
        +           * @param {Number} max the initial maximum value
                    *
        -           * calculates the magnitude of error of this curve's
        -           * quadratic approximation.
        +           * find the minimum & maximum value in a list of values
                    */
        -      quadError () {
        -        return (
        -          p5.Vector.sub(
        -            p5.Vector.sub(this.p1, this.p0),
        -            p5.Vector.mult(p5.Vector.sub(this.c1, this.c0), 3)
        -          ).mag() / 2
        +        function minMax(rg, min, max) {
        +          for (let i = rg.length; i-- > 0; ) {
        +            const v = rg[i];
        +            if (min > v) min = v;
        +            if (max < v) max = v;
        +          }
        +          return { min, max };
        +        }
        +
        +        // Expand the bounding box of the glyph by the number of cells below
        +        // before rounding. Curves only partially through a cell won't be added
        +        // to adjacent cells, but ones that are close will be. This helps fix
        +        // small visual glitches that occur when curves are close to grid cell
        +        // boundaries.
        +        const cellOffset = 0.5;
        +
        +        // loop through the rows & columns that the curve intersects
        +        // adding the curve to those slices
        +        const mmX = minMax(xs, 1, 0);
        +        const ixMin = Math.max(
        +          Math.floor(mmX.min * charGridWidth - cellOffset),
        +          0
                 );
        +        const ixMax = Math.min(
        +          Math.ceil(mmX.max * charGridWidth + cellOffset),
        +          charGridWidth
        +        );
        +        for (let iCol = ixMin; iCol < ixMax; ++iCol) cols[iCol].push(index);
        +
        +        const mmY = minMax(ys, 1, 0);
        +        const iyMin = Math.max(
        +          Math.floor(mmY.min * charGridHeight - cellOffset),
        +          0
        +        );
        +        const iyMax = Math.min(
        +          Math.ceil(mmY.max * charGridHeight + cellOffset),
        +          charGridHeight
        +        );
        +        for (let iRow = iyMin; iRow < iyMax; ++iRow) rows[iRow].push(index);
               }
         
               /**
        -           * @method split
        -           * @param {Number} t the value (0-1) at which to split
        -           * @return {Cubic} the second part of the curve
        -           *
        -           * splits the cubic into two parts at a point 't' along the curve.
        -           * this cubic keeps its start point and its end point becomes the
        -           * point at 't'. the 'end half is returned.
        -           */
        -      split (t) {
        -        const m1 = p5.Vector.lerp(this.p0, this.c0, t);
        -        const m2 = p5.Vector.lerp(this.c0, this.c1, t);
        -        const mm1 = p5.Vector.lerp(m1, m2, t);
        -
        -        this.c1 = p5.Vector.lerp(this.c1, this.p1, t);
        -        this.c0 = p5.Vector.lerp(m2, this.c1, t);
        -        const pt = p5.Vector.lerp(mm1, this.c0, t);
        -        const part1 = new Cubic(this.p0, m1, mm1, pt);
        -        this.p0 = pt;
        -        return part1;
        +         * @function clamp
        +         * @param {Number} v the value to clamp
        +         * @param {Number} min the minimum value
        +         * @param {Number} max the maxmimum value
        +         *
        +         * clamps a value between a minimum & maximum value
        +         */
        +      function clamp(v, min, max) {
        +        if (v < min) return min;
        +        if (v > max) return max;
        +        return v;
               }
         
               /**
        -           * @method splitInflections
        -           * @return {Cubic[]} the non-inflecting pieces of this cubic
        -           *
        -           * returns an array containing 0, 1 or 2 cubics split resulting
        -           * from splitting this cubic at its inflection points.
        -           * this cubic is (potentially) altered and returned in the list.
        -           */
        -      splitInflections () {
        -        const a = p5.Vector.sub(this.c0, this.p0);
        -        const b = p5.Vector.sub(p5.Vector.sub(this.c1, this.c0), a);
        -        const c = p5.Vector.sub(
        -          p5.Vector.sub(p5.Vector.sub(this.p1, this.c1), a),
        -          p5.Vector.mult(b, 2)
        -        );
        +         * @function byte
        +         * @param {Number} v the value to scale
        +         *
        +         * converts a floating-point number in the range 0-1 to a byte 0-255
        +         */
        +      function byte(v) {
        +        return clamp(255 * v, 0, 255);
        +      }
         
        -        const cubics = [];
        -
        -        // find the derivative coefficients
        -        let A = b.x * c.y - b.y * c.x;
        -        if (A !== 0) {
        -          let B = a.x * c.y - a.y * c.x;
        -          let C = a.x * b.y - a.y * b.x;
        -          const disc = B * B - 4 * A * C;
        -          if (disc >= 0) {
        -            if (A < 0) {
        -              A = -A;
        -              B = -B;
        -              C = -C;
        -            }
        +      /**
        +         * @private
        +         * @class Cubic
        +         * @param {Number} p0 the start point of the curve
        +         * @param {Number} c0 the first control point
        +         * @param {Number} c1 the second control point
        +         * @param {Number} p1 the end point
        +         *
        +         * a cubic curve
        +         */
        +      class Cubic {
        +        constructor(p0, c0, c1, p1) {
        +          this.p0 = p0;
        +          this.c0 = c0;
        +          this.c1 = c1;
        +          this.p1 = p1;
        +        }
        +        /**
        +             * @return {Object} the quadratic approximation
        +             *
        +             * converts the cubic to a quadtratic approximation by
        +             * picking an appropriate quadratic control point
        +             */
        +        toQuadratic () {
        +          return {
        +            x: this.p0.x,
        +            y: this.p0.y,
        +            x1: this.p1.x,
        +            y1: this.p1.y,
        +            cx: ((this.c0.x + this.c1.x) * 3 - (this.p0.x + this.p1.x)) / 4,
        +            cy: ((this.c0.y + this.c1.y) * 3 - (this.p0.y + this.p1.y)) / 4
        +          };
        +        }
         
        -            const Q = Math.sqrt(disc);
        -            const t0 = (-B - Q) / (2 * A); // the first inflection point
        -            let t1 = (-B + Q) / (2 * A); // the second inflection point
        +        /**
        +             * @return {Number} the error
        +             *
        +             * calculates the magnitude of error of this curve's
        +             * quadratic approximation.
        +             */
        +        quadError () {
        +          return (
        +            Vector.sub(
        +              Vector.sub(this.p1, this.p0),
        +              Vector.mult(Vector.sub(this.c1, this.c0), 3)
        +            ).mag() / 2
        +          );
        +        }
         
        -            // test if the first inflection point lies on the curve
        -            if (t0 > 0 && t0 < 1) {
        -              // split at the first inflection point
        -              cubics.push(this.split(t0));
        -              // scale t2 into the second part
        -              t1 = 1 - (1 - t1) / (1 - t0);
        -            }
        +        /**
        +             * @param {Number} t the value (0-1) at which to split
        +             * @return {Cubic} the second part of the curve
        +             *
        +             * splits the cubic into two parts at a point 't' along the curve.
        +             * this cubic keeps its start point and its end point becomes the
        +             * point at 't'. the 'end half is returned.
        +             */
        +        split (t) {
        +          const m1 = Vector.lerp(this.p0, this.c0, t);
        +          const m2 = Vector.lerp(this.c0, this.c1, t);
        +          const mm1 = Vector.lerp(m1, m2, t);
        +
        +          this.c1 = Vector.lerp(this.c1, this.p1, t);
        +          this.c0 = Vector.lerp(m2, this.c1, t);
        +          const pt = Vector.lerp(mm1, this.c0, t);
        +          const part1 = new Cubic(this.p0, m1, mm1, pt);
        +          this.p0 = pt;
        +          return part1;
        +        }
         
        -            // test if the second inflection point lies on the curve
        -            if (t1 > 0 && t1 < 1) {
        -              // split at the second inflection point
        -              cubics.push(this.split(t1));
        +        /**
        +             * @return {Cubic[]} the non-inflecting pieces of this cubic
        +             *
        +             * returns an array containing 0, 1 or 2 cubics split resulting
        +             * from splitting this cubic at its inflection points.
        +             * this cubic is (potentially) altered and returned in the list.
        +             */
        +        splitInflections () {
        +          const a = Vector.sub(this.c0, this.p0);
        +          const b = Vector.sub(Vector.sub(this.c1, this.c0), a);
        +          const c = Vector.sub(
        +            Vector.sub(Vector.sub(this.p1, this.c1), a),
        +            Vector.mult(b, 2)
        +          );
        +
        +          const cubics = [];
        +
        +          // find the derivative coefficients
        +          let A = b.x * c.y - b.y * c.x;
        +          if (A !== 0) {
        +            let B = a.x * c.y - a.y * c.x;
        +            let C = a.x * b.y - a.y * b.x;
        +            const disc = B * B - 4 * A * C;
        +            if (disc >= 0) {
        +              if (A < 0) {
        +                A = -A;
        +                B = -B;
        +                C = -C;
        +              }
        +
        +              const Q = Math.sqrt(disc);
        +              const t0 = (-B - Q) / (2 * A); // the first inflection point
        +              let t1 = (-B + Q) / (2 * A); // the second inflection point
        +
        +              // test if the first inflection point lies on the curve
        +              if (t0 > 0 && t0 < 1) {
        +                // split at the first inflection point
        +                cubics.push(this.split(t0));
        +                // scale t2 into the second part
        +                t1 = 1 - (1 - t1) / (1 - t0);
        +              }
        +
        +              // test if the second inflection point lies on the curve
        +              if (t1 > 0 && t1 < 1) {
        +                // split at the second inflection point
        +                cubics.push(this.split(t1));
        +              }
                     }
                   }
        -        }
         
        -        cubics.push(this);
        -        return cubics;
        +          cubics.push(this);
        +          return cubics;
        +        }
               }
        -    }
         
        -    /**
        -       * @function cubicToQuadratics
        -       * @param {Number} x0
        -       * @param {Number} y0
        -       * @param {Number} cx0
        -       * @param {Number} cy0
        -       * @param {Number} cx1
        -       * @param {Number} cy1
        -       * @param {Number} x1
        -       * @param {Number} y1
        -       * @returns {Cubic[]} an array of cubics whose quadratic approximations
        -       *                    closely match the civen cubic.
        -       *
        -       * converts a cubic curve to a list of quadratics.
        -       */
        -    function cubicToQuadratics(x0, y0, cx0, cy0, cx1, cy1, x1, y1) {
        -      // create the Cubic object and split it at its inflections
        -      const cubics = new Cubic(
        -        new p5.Vector(x0, y0),
        -        new p5.Vector(cx0, cy0),
        -        new p5.Vector(cx1, cy1),
        -        new p5.Vector(x1, y1)
        -      ).splitInflections();
        -
        -      const qs = []; // the final list of quadratics
        -      const precision = 30 / SQRT3;
        -
        -      // for each of the non-inflected pieces of the original cubic
        -      for (let cubic of cubics) {
        -        // the cubic is iteratively split in 3 pieces:
        -        // the first piece is accumulated in 'qs', the result.
        -        // the last piece is accumulated in 'tail', temporarily.
        -        // the middle piece is repeatedly split again, while necessary.
        -        const tail = [];
        -
        -        let t3;
        -        for (;;) {
        -          // calculate this cubic's precision
        -          t3 = precision / cubic.quadError();
        -          if (t3 >= 0.5 * 0.5 * 0.5) {
        -            break; // not too bad, we're done
        -          }
        +      /**
        +         * @function cubicToQuadratics
        +         * @param {Number} x0
        +         * @param {Number} y0
        +         * @param {Number} cx0
        +         * @param {Number} cy0
        +         * @param {Number} cx1
        +         * @param {Number} cy1
        +         * @param {Number} x1
        +         * @param {Number} y1
        +         * @returns {Cubic[]} an array of cubics whose quadratic approximations
        +         *                    closely match the civen cubic.
        +         *
        +         * converts a cubic curve to a list of quadratics.
        +         */
        +      function cubicToQuadratics(x0, y0, cx0, cy0, cx1, cy1, x1, y1) {
        +        // create the Cubic object and split it at its inflections
        +        const cubics = new Cubic(
        +          new Vector(x0, y0),
        +          new Vector(cx0, cy0),
        +          new Vector(cx1, cy1),
        +          new Vector(x1, y1)
        +        ).splitInflections();
        +
        +        const qs = []; // the final list of quadratics
        +        const precision = 30 / SQRT3;
        +
        +        // for each of the non-inflected pieces of the original cubic
        +        for (let cubic of cubics) {
        +          // the cubic is iteratively split in 3 pieces:
        +          // the first piece is accumulated in 'qs', the result.
        +          // the last piece is accumulated in 'tail', temporarily.
        +          // the middle piece is repeatedly split again, while necessary.
        +          const tail = [];
        +
        +          let t3;
        +          for (;;) {
        +            // calculate this cubic's precision
        +            t3 = precision / cubic.quadError();
        +            if (t3 >= 0.5 * 0.5 * 0.5) {
        +              break; // not too bad, we're done
        +            }
         
        -          // find a split point based on the error
        -          const t = Math.pow(t3, 1.0 / 3.0);
        -          // split the cubic in 3
        -          const start = cubic.split(t);
        -          const middle = cubic.split(1 - t / (1 - t));
        +            // find a split point based on the error
        +            const t = Math.pow(t3, 1.0 / 3.0);
        +            // split the cubic in 3
        +            const start = cubic.split(t);
        +            const middle = cubic.split(1 - t / (1 - t));
         
        -          qs.push(start); // the first part
        -          tail.push(cubic); // the last part
        -          cubic = middle; // iterate on the middle piece
        -        }
        +            qs.push(start); // the first part
        +            tail.push(cubic); // the last part
        +            cubic = middle; // iterate on the middle piece
        +          }
        +
        +          if (t3 < 1) {
        +            // a little excess error, split the middle in two
        +            qs.push(cubic.split(0.5));
        +          }
        +          // add the middle piece to the result
        +          qs.push(cubic);
         
        -        if (t3 < 1) {
        -          // a little excess error, split the middle in two
        -          qs.push(cubic.split(0.5));
        +          // finally add the tail, reversed, onto the result
        +          Array.prototype.push.apply(qs, tail.reverse());
                 }
        -        // add the middle piece to the result
        -        qs.push(cubic);
         
        -        // finally add the tail, reversed, onto the result
        -        Array.prototype.push.apply(qs, tail.reverse());
        +        return qs;
               }
         
        -      return qs;
        -    }
        -
        -    /**
        -       * @function pushLine
        -       * @param {Number} x0
        -       * @param {Number} y0
        -       * @param {Number} x1
        -       * @param {Number} y1
        -       *
        -       * add a straight line to the row/col grid of a glyph
        -       */
        -    function pushLine(x0, y0, x1, y1) {
        -      const mx = (x0 + x1) / 2;
        -      const my = (y0 + y1) / 2;
        -      push([x0, x1], [y0, y1], { x: x0, y: y0, cx: mx, cy: my });
        -    }
        +      /**
        +         * @function pushLine
        +         * @param {Number} x0
        +         * @param {Number} y0
        +         * @param {Number} x1
        +         * @param {Number} y1
        +         *
        +         * add a straight line to the row/col grid of a glyph
        +         */
        +      function pushLine(x0, y0, x1, y1) {
        +        const mx = (x0 + x1) / 2;
        +        const my = (y0 + y1) / 2;
        +        push([x0, x1], [y0, y1], { x: x0, y: y0, cx: mx, cy: my });
        +      }
         
        -    /**
        -       * @function samePoint
        -       * @param {Number} x0
        -       * @param {Number} y0
        -       * @param {Number} x1
        -       * @param {Number} y1
        -       * @return {Boolean} true if the two points are sufficiently close
        -       *
        -       * tests if two points are close enough to be considered the same
        -       */
        -    function samePoint(x0, y0, x1, y1) {
        -      return Math.abs(x1 - x0) < 0.00001 && Math.abs(y1 - y0) < 0.00001;
        -    }
        +      /**
        +         * @function samePoint
        +         * @param {Number} x0
        +         * @param {Number} y0
        +         * @param {Number} x1
        +         * @param {Number} y1
        +         * @return {Boolean} true if the two points are sufficiently close
        +         *
        +         * tests if two points are close enough to be considered the same
        +         */
        +      function samePoint(x0, y0, x1, y1) {
        +        return Math.abs(x1 - x0) < 0.00001 && Math.abs(y1 - y0) < 0.00001;
        +      }
         
        -    let x0, y0, xs, ys;
        +      let x0, y0, xs, ys;
         
        -    for (const cmd of cmds) {
        -      // scale the coordinates to the range 0-1
        -      const x1 = (cmd.x - xMin) / gWidth;
        -      const y1 = (cmd.y - yMin) / gHeight;
        +      for (const cmd of cmds) {
        +        // scale the coordinates to the range 0-1
        +        const x1 = (cmd.x - xMin) / gWidth;
        +        const y1 = (cmd.y - yMin) / gHeight;
         
        -      // don't bother if this point is the same as the last
        -      if (samePoint(x0, y0, x1, y1)) continue;
        +        // don't bother if this point is the same as the last
        +        if (samePoint(x0, y0, x1, y1)) continue;
         
        -      switch (cmd.type) {
        -        case 'M': {
        -          // move
        -          xs = x1;
        -          ys = y1;
        -          break;
        -        }
        -        case 'L': {
        -          // line
        -          pushLine(x0, y0, x1, y1);
        -          break;
        -        }
        -        case 'Q': {
        -          // quadratic
        -          const cx = (cmd.x1 - xMin) / gWidth;
        -          const cy = (cmd.y1 - yMin) / gHeight;
        -          push([x0, x1, cx], [y0, y1, cy], { x: x0, y: y0, cx, cy });
        -          break;
        -        }
        -        case 'Z': {
        -          // end
        -          if (!samePoint(x0, y0, xs, ys)) {
        -            // add an extra line closing the loop, if necessary
        -            pushLine(x0, y0, xs, ys);
        -            strokes.push({ x: xs, y: ys });
        -          } else {
        -            strokes.push({ x: x0, y: y0 });
        +        switch (cmd.type) {
        +          case 'M': {
        +            // move
        +            xs = x1;
        +            ys = y1;
        +            break;
                   }
        -          break;
        -        }
        -        case 'C': {
        -          // cubic
        -          const cx1 = (cmd.x1 - xMin) / gWidth;
        -          const cy1 = (cmd.y1 - yMin) / gHeight;
        -          const cx2 = (cmd.x2 - xMin) / gWidth;
        -          const cy2 = (cmd.y2 - yMin) / gHeight;
        -          const qs = cubicToQuadratics(x0, y0, cx1, cy1, cx2, cy2, x1, y1);
        -          for (let iq = 0; iq < qs.length; iq++) {
        -            const q = qs[iq].toQuadratic();
        -            push([q.x, q.x1, q.cx], [q.y, q.y1, q.cy], q);
        +          case 'L': {
        +            // line
        +            pushLine(x0, y0, x1, y1);
        +            break;
                   }
        -          break;
        +          case 'Q': {
        +            // quadratic
        +            const cx = (cmd.x1 - xMin) / gWidth;
        +            const cy = (cmd.y1 - yMin) / gHeight;
        +            push([x0, x1, cx], [y0, y1, cy], { x: x0, y: y0, cx, cy });
        +            break;
        +          }
        +          case 'Z': {
        +            // end
        +            if (!samePoint(x0, y0, xs, ys)) {
        +              // add an extra line closing the loop, if necessary
        +              pushLine(x0, y0, xs, ys);
        +              strokes.push({ x: xs, y: ys });
        +            } else {
        +              strokes.push({ x: x0, y: y0 });
        +            }
        +            break;
        +          }
        +          case 'C': {
        +            // cubic
        +            const cx1 = (cmd.x1 - xMin) / gWidth;
        +            const cy1 = (cmd.y1 - yMin) / gHeight;
        +            const cx2 = (cmd.x2 - xMin) / gWidth;
        +            const cy2 = (cmd.y2 - yMin) / gHeight;
        +            const qs = cubicToQuadratics(x0, y0, cx1, cy1, cx2, cy2, x1, y1);
        +            for (let iq = 0; iq < qs.length; iq++) {
        +              const q = qs[iq].toQuadratic();
        +              push([q.x, q.x1, q.cx], [q.y, q.y1, q.cy], q);
        +            }
        +            break;
        +          }
        +          default:
        +            throw new Error(`unknown command type: ${cmd.type}`);
                 }
        -        default:
        -          throw new Error(`unknown command type: ${cmd.type}`);
        +        x0 = x1;
        +        y0 = y1;
               }
        -      x0 = x1;
        -      y0 = y1;
        -    }
         
        -    // allocate space for the strokes
        -    const strokeCount = strokes.length;
        -    const strokeImageInfo = this.strokeImageInfos.findImage(strokeCount);
        -    const strokeOffset = strokeImageInfo.index;
        +      // allocate space for the strokes
        +      const strokeCount = strokes.length;
        +      const strokeImageInfo = this.strokeImageInfos.findImage(strokeCount);
        +      const strokeOffset = strokeImageInfo.index;
         
        -    // fill the stroke image
        -    for (let il = 0; il < strokeCount; ++il) {
        -      const s = strokes[il];
        -      setPixel(strokeImageInfo, byte(s.x), byte(s.y), byte(s.cx), byte(s.cy));
        -    }
        -
        -    /**
        -       * @function layout
        -       * @param {Number[][]} dim
        -       * @param {ImageInfo[]} dimImageInfos
        -       * @param {ImageInfo[]} cellImageInfos
        -       * @return {Object}
        -       *
        -       * lays out the curves in a dimension (row or col) into two
        -       * images, one for the indices of the curves themselves, and
        -       * one containing the offset and length of those index spans.
        -       */
        -    function layout(dim, dimImageInfos, cellImageInfos) {
        -      const dimLength = dim.length; // the number of slices in this dimension
        -      const dimImageInfo = dimImageInfos.findImage(dimLength);
        -      const dimOffset = dimImageInfo.index;
        -      // calculate the total number of stroke indices in this dimension
        -      let totalStrokes = 0;
        -      for (let id = 0; id < dimLength; ++id) {
        -        totalStrokes += dim[id].length;
        +      // fill the stroke image
        +      for (let il = 0; il < strokeCount; ++il) {
        +        const s = strokes[il];
        +        setPixel(strokeImageInfo, byte(s.x), byte(s.y), byte(s.cx), byte(s.cy));
               }
         
        -      // allocate space for the stroke indices
        -      const cellImageInfo = cellImageInfos.findImage(totalStrokes);
        -
        -      // for each slice in the glyph
        -      for (let i = 0; i < dimLength; ++i) {
        -        const strokeIndices = dim[i];
        -        const strokeCount = strokeIndices.length;
        -        const cellLineIndex = cellImageInfo.index;
        -
        -        // write the offset and count into the glyph slice image
        -        setPixel(
        -          dimImageInfo,
        -          cellLineIndex >> 7,
        -          cellLineIndex & 0x7f,
        -          strokeCount >> 7,
        -          strokeCount & 0x7f
        -        );
        +      /**
        +         * @function layout
        +         * @param {Number[][]} dim
        +         * @param {ImageInfo[]} dimImageInfos
        +         * @param {ImageInfo[]} cellImageInfos
        +         * @return {Object}
        +         *
        +         * lays out the curves in a dimension (row or col) into two
        +         * images, one for the indices of the curves themselves, and
        +         * one containing the offset and length of those index spans.
        +         */
        +      function layout(dim, dimImageInfos, cellImageInfos) {
        +        const dimLength = dim.length; // the number of slices in this dimension
        +        const dimImageInfo = dimImageInfos.findImage(dimLength);
        +        const dimOffset = dimImageInfo.index;
        +        // calculate the total number of stroke indices in this dimension
        +        let totalStrokes = 0;
        +        for (let id = 0; id < dimLength; ++id) {
        +          totalStrokes += dim[id].length;
        +        }
         
        -        // for each stroke index in that slice
        -        for (let iil = 0; iil < strokeCount; ++iil) {
        -          // write the stroke index into the slice's image
        -          const strokeIndex = strokeIndices[iil] + strokeOffset;
        -          setPixel(cellImageInfo, strokeIndex >> 7, strokeIndex & 0x7f, 0, 0);
        +        // allocate space for the stroke indices
        +        const cellImageInfo = cellImageInfos.findImage(totalStrokes);
        +
        +        // for each slice in the glyph
        +        for (let i = 0; i < dimLength; ++i) {
        +          const strokeIndices = dim[i];
        +          const strokeCount = strokeIndices.length;
        +          const cellLineIndex = cellImageInfo.index;
        +
        +          // write the offset and count into the glyph slice image
        +          setPixel(
        +            dimImageInfo,
        +            cellLineIndex >> 7,
        +            cellLineIndex & 0x7f,
        +            strokeCount >> 7,
        +            strokeCount & 0x7f
        +          );
        +
        +          // for each stroke index in that slice
        +          for (let iil = 0; iil < strokeCount; ++iil) {
        +            // write the stroke index into the slice's image
        +            const strokeIndex = strokeIndices[iil] + strokeOffset;
        +            setPixel(cellImageInfo, strokeIndex >> 7, strokeIndex & 0x7f, 0, 0);
        +          }
                 }
        +
        +        return {
        +          cellImageInfo,
        +          dimOffset,
        +          dimImageInfo
        +        };
               }
         
        -      return {
        -        cellImageInfo,
        -        dimOffset,
        -        dimImageInfo
        +      // initialize the info for this glyph
        +      gi = this.glyphInfos[glyph.index] = {
        +        glyph,
        +        uGlyphRect: [xMin, yMin, xMax, yMax],
        +        strokeImageInfo,
        +        strokes,
        +        colInfo: layout(cols, this.colDimImageInfos, this.colCellImageInfos),
        +        rowInfo: layout(rows, this.rowDimImageInfos, this.rowCellImageInfos)
               };
        +      gi.uGridOffset = [gi.colInfo.dimOffset, gi.rowInfo.dimOffset];
        +      return gi;
             }
        -
        -    // initialize the info for this glyph
        -    gi = this.glyphInfos[glyph.index] = {
        -      glyph,
        -      uGlyphRect: [bb.x1, -bb.y1, bb.x2, -bb.y2],
        -      strokeImageInfo,
        -      strokes,
        -      colInfo: layout(cols, this.colDimImageInfos, this.colCellImageInfos),
        -      rowInfo: layout(rows, this.rowDimImageInfos, this.rowCellImageInfos)
        -    };
        -    gi.uGridOffset = [gi.colInfo.dimOffset, gi.rowInfo.dimOffset];
        -    return gi;
           }
        -}
         
        -p5.RendererGL.prototype._renderText = function(p, line, x, y, maxY) {
        -  if (!this._textFont || typeof this._textFont === 'string') {
        -    console.log(
        -      'WEBGL: you must load and set a font before drawing text. See `loadFont` and `textFont` for more details.'
        -    );
        -    return;
        -  }
        -  if (y >= maxY || !this._doFill) {
        -    return; // don't render lines beyond our maxY position
        -  }
        +  RendererGL.prototype._renderText = function(line, x, y, maxY, minY) {
        +    if (!this.states.textFont || typeof this.states.textFont === 'string') {
        +      console.log(
        +        'WEBGL: you must load and set a font before drawing text. See `loadFont` and `textFont` for more details.'
        +      );
        +      return;
        +    }
        +    if (y >= maxY || !this.states.fillColor) {
        +      return; // don't render lines beyond our maxY position
        +    }
         
        -  if (!this._isOpenType()) {
        -    console.log(
        -      'WEBGL: only Opentype (.otf) and Truetype (.ttf) fonts are supported'
        -    );
        -    return p;
        -  }
        +    if (!this._isOpenType()) {
        +      console.log(
        +        'WEBGL: only Opentype (.otf) and Truetype (.ttf) fonts are supported'
        +      );
        +      return;
        +    }
         
        -  p.push(); // fix to #803
        +    this.push(); // fix to #803
         
        -  // remember this state, so it can be restored later
        -  const doStroke = this._doStroke;
        -  const drawMode = this.drawMode;
        +    // remember this state, so it can be restored later
        +    const doStroke = this.states.strokeColor;
        +    const drawMode = this.states.drawMode;
         
        -  this._doStroke = false;
        -  this.drawMode = constants.TEXTURE;
        +    this.states.strokeColor = null;
        +    this.states.drawMode = constants.TEXTURE;
         
        -  // get the cached FontInfo object
        -  const font = this._textFont.font;
        -  let fontInfo = this._textFont._fontInfo;
        -  if (!fontInfo) {
        -    fontInfo = this._textFont._fontInfo = new FontInfo(font);
        -  }
        +    // get the cached FontInfo object
        +    const { font } = this.states.textFont;
        +    if (!font) {
        +      throw new Error(
        +        'In WebGL mode, textFont() needs to be given the result of loadFont() instead of a font family name.'
        +      );
        +    }
        +    let fontInfo = this.states.textFont._fontInfo;
        +    if (!fontInfo) {
        +      fontInfo = this.states.textFont._fontInfo = new FontInfo(font);
        +    }
         
        -  // calculate the alignment and move/scale the view accordingly
        -  const pos = this._textFont._handleAlignment(this, line, x, y);
        -  const fontSize = this._textSize;
        -  const scale = fontSize / font.unitsPerEm;
        -  this.translate(pos.x, pos.y, 0);
        -  this.scale(scale, scale, 1);
        -
        -  // initialize the font shader
        -  const gl = this.GL;
        -  const initializeShader = !this._defaultFontShader;
        -  const sh = this._getFontShader();
        -  sh.init();
        -  sh.bindShader(); // first time around, bind the shader fully
        -
        -  if (initializeShader) {
        -    // these are constants, really. just initialize them one-time.
        -    sh.setUniform('uGridImageSize', [gridImageWidth, gridImageHeight]);
        -    sh.setUniform('uCellsImageSize', [cellImageWidth, cellImageHeight]);
        -    sh.setUniform('uStrokeImageSize', [strokeImageWidth, strokeImageHeight]);
        -    sh.setUniform('uGridSize', [charGridWidth, charGridHeight]);
        -  }
        -  this._applyColorBlend(this.curFillColor);
        -
        -  let g = this.retainedMode.geometry['glyph'];
        -  if (!g) {
        -    // create the geometry for rendering a quad
        -    const geom = (this._textGeom = new p5.Geometry(1, 1, function() {
        -      for (let i = 0; i <= 1; i++) {
        -        for (let j = 0; j <= 1; j++) {
        -          this.vertices.push(new p5.Vector(j, i, 0));
        -          this.uvs.push(j, i);
        +    // calculate the alignment and move/scale the view accordingly
        +    // TODO: check this
        +    const pos = { x, y } // this.states.textFont._handleAlignment(this, line, x, y);
        +    const fontSize = this.states.textSize;
        +    const scale = fontSize / (font.data?.head?.unitsPerEm || 1000);
        +    this.translate(pos.x, pos.y, 0);
        +    this.scale(scale, scale, 1);
        +
        +    // initialize the font shader
        +    const gl = this.GL;
        +    const initializeShader = !this._defaultFontShader;
        +    const sh = this._getFontShader();
        +    sh.init();
        +    sh.bindShader(); // first time around, bind the shader fully
        +
        +    if (initializeShader) {
        +      // these are constants, really. just initialize them one-time.
        +      sh.setUniform('uGridImageSize', [gridImageWidth, gridImageHeight]);
        +      sh.setUniform('uCellsImageSize', [cellImageWidth, cellImageHeight]);
        +      sh.setUniform('uStrokeImageSize', [strokeImageWidth, strokeImageHeight]);
        +      sh.setUniform('uGridSize', [charGridWidth, charGridHeight]);
        +    }
        +
        +    const curFillColor = this.states.fillSet ? this.states.curFillColor : [0, 0, 0, 255];
        +
        +    this._setGlobalUniforms(sh);
        +    this._applyColorBlend(curFillColor);
        +
        +    let g = this.geometryBufferCache.getGeometryByID('glyph');
        +    if (!g) {
        +      // create the geometry for rendering a quad
        +      g = (this._textGeom = new Geometry(1, 1, function() {
        +        for (let i = 0; i <= 1; i++) {
        +          for (let j = 0; j <= 1; j++) {
        +            this.vertices.push(new Vector(j, i, 0));
        +            this.uvs.push(j, i);
        +          }
                 }
        -      }
        -    }));
        -    geom.computeFaces().computeNormals();
        -    g = this.createBuffers('glyph', geom);
        -  }
        +      }, this) );
        +      g.gid = 'glyph';
        +      g.computeFaces().computeNormals();
        +      this.geometryBufferCache.ensureCached(g);
        +    }
         
        -  // bind the shader buffers
        -  for (const buff of this.retainedMode.buffers.text) {
        -    buff._prepareBuffer(g, sh);
        -  }
        -  this._bindBuffer(g.indexBuffer, gl.ELEMENT_ARRAY_BUFFER);
        -
        -  // this will have to do for now...
        -  sh.setUniform('uMaterialColor', this.curFillColor);
        -  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
        -
        -  try {
        -    let dx = 0; // the x position in the line
        -    let glyphPrev = null; // the previous glyph, used for kerning
        -    // fetch the glyphs in the line of text
        -    const glyphs = font.stringToGlyphs(line);
        -
        -    for (const glyph of glyphs) {
        -      // kern
        -      if (glyphPrev) dx += font.getKerningValue(glyphPrev, glyph);
        -
        -      const gi = fontInfo.getGlyphInfo(glyph);
        -      if (gi.uGlyphRect) {
        -        const rowInfo = gi.rowInfo;
        -        const colInfo = gi.colInfo;
        -        sh.setUniform('uSamplerStrokes', gi.strokeImageInfo.imageData);
        -        sh.setUniform('uSamplerRowStrokes', rowInfo.cellImageInfo.imageData);
        -        sh.setUniform('uSamplerRows', rowInfo.dimImageInfo.imageData);
        -        sh.setUniform('uSamplerColStrokes', colInfo.cellImageInfo.imageData);
        -        sh.setUniform('uSamplerCols', colInfo.dimImageInfo.imageData);
        -        sh.setUniform('uGridOffset', gi.uGridOffset);
        -        sh.setUniform('uGlyphRect', gi.uGlyphRect);
        -        sh.setUniform('uGlyphOffset', dx);
        -
        -        sh.bindTextures(); // afterwards, only textures need updating
        -
        -        // draw it
        -        gl.drawElements(gl.TRIANGLES, 6, this.GL.UNSIGNED_SHORT, 0);
        -      }
        -      dx += glyph.advanceWidth;
        -      glyphPrev = glyph;
        +    // bind the shader buffers
        +    for (const buff of this.buffers.text) {
        +      buff._prepareBuffer(g, sh);
             }
        -  } finally {
        -    // clean up
        -    sh.unbindShader();
        +    this._bindBuffer(this.geometryBufferCache.cache.glyph.indexBuffer, gl.ELEMENT_ARRAY_BUFFER);
        +
        +    // this will have to do for now...
        +    sh.setUniform('uMaterialColor', curFillColor);
        +    gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
        +
        +    try {
        +      // fetch the glyphs in the line of text
        +      const glyphs = font._positionGlyphs(line);
        +
        +      for (const glyph of glyphs) {
        +        const gi = fontInfo.getGlyphInfo(glyph);
        +        if (gi.uGlyphRect) {
        +          const rowInfo = gi.rowInfo;
        +          const colInfo = gi.colInfo;
        +          sh.setUniform('uSamplerStrokes', gi.strokeImageInfo.imageData);
        +          sh.setUniform('uSamplerRowStrokes', rowInfo.cellImageInfo.imageData);
        +          sh.setUniform('uSamplerRows', rowInfo.dimImageInfo.imageData);
        +          sh.setUniform('uSamplerColStrokes', colInfo.cellImageInfo.imageData);
        +          sh.setUniform('uSamplerCols', colInfo.dimImageInfo.imageData);
        +          sh.setUniform('uGridOffset', gi.uGridOffset);
        +          sh.setUniform('uGlyphRect', gi.uGlyphRect);
        +          sh.setUniform('uGlyphOffset', glyph.x);
        +
        +          sh.bindTextures(); // afterwards, only textures need updating
        +
        +          // draw it
        +          gl.drawElements(gl.TRIANGLES, 6, this.GL.UNSIGNED_SHORT, 0);
        +        }
        +      }
        +    } finally {
        +      // clean up
        +      sh.unbindShader();
         
        -    this._doStroke = doStroke;
        -    this.drawMode = drawMode;
        -    gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
        +      this.states.strokeColor = doStroke;
        +      this.states.drawMode = drawMode;
        +      gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
         
        -    p.pop();
        -  }
        +      this.pop();
        +    }
        +  };
        +}
         
        -  return p;
        -};
        +export default text;
        diff --git a/tasks/build/browserify.js b/tasks/build/browserify.js
        deleted file mode 100644
        index 2d3fa59245..0000000000
        --- a/tasks/build/browserify.js
        +++ /dev/null
        @@ -1,119 +0,0 @@
        -// This file holds the "browersify" task which compiles the individual src/ code into p5.js and p5.min.js.
        -
        -'use strict';
        -
        -import { resolve } from 'path';
        -import browserify from 'browserify';
        -import derequire from 'derequire';
        -
        -const bannerTemplate =
        -  '/*! p5.js v<%= pkg.version %> <%= grunt.template.today("mmmm dd, yyyy") %> */';
        -
        -module.exports = function(grunt) {
        -  const srcFilePath = require.resolve('../../src/app.js');
        -
        -  grunt.registerTask(
        -    'browserify',
        -    'Compile the p5.js source with Browserify',
        -    function(param) {
        -      const isMin = param === 'min';
        -      const isTest = param === 'test';
        -      const isDev = param === 'dev';
        -
        -      const filename = isMin
        -        ? 'p5.pre-min.js'
        -        : isTest ? 'p5-test.js' : 'p5.js';
        -
        -      // This file will not exist until it has been built
        -      const libFilePath = resolve('lib/' + filename);
        -
        -      // Reading and writing files is asynchronous
        -      const done = this.async();
        -
        -      // Render the banner for the top of the file
        -      const banner = grunt.template.process(bannerTemplate);
        -
        -      let globalVars = {};
        -      if (isDev) {
        -        globalVars['P5_DEV_BUILD'] = () => true;
        -      }
        -      // Invoke Browserify programatically to bundle the code
        -      let browserified = browserify(srcFilePath, {
        -        standalone: 'p5',
        -        insertGlobalVars: globalVars
        -      });
        -
        -      if (isMin) {
        -        // These paths should be the exact same as what are used in the import
        -        // statements in the source. They are not relative to this file. It's
        -        // just how browserify works apparently.
        -        browserified = browserified
        -          .exclude('../../docs/reference/data.json')
        -          .exclude('../../../docs/parameterData.json')
        -          .exclude('../../translations')
        -          .exclude('./browser_errors')
        -          .ignore('i18next')
        -          .ignore('i18next-browser-languagedetector');
        -      }
        -
        -      if (!isDev) {
        -        browserified = browserified.exclude('../../translations/dev');
        -      }
        -
        -      const babelifyOpts = {
        -        global: true
        -      };
        -
        -      if (isTest) {
        -        babelifyOpts.envName = 'test';
        -      }
        -
        -      const bundle = browserified
        -        .transform('brfs-babel')
        -        .transform('babelify', babelifyOpts)
        -        .bundle();
        -
        -      // Start the generated output with the banner comment,
        -      let code = banner + '\n';
        -
        -      // Then read the bundle into memory so we can run it through derequire
        -      bundle
        -        .on('data', function(data) {
        -          code += data;
        -        })
        -        .on('end', function() {
        -          code = code.replace(
        -            "'VERSION_CONST_WILL_BE_REPLACED_BY_BROWSERIFY_BUILD_PROCESS'",
        -            grunt.template.process("'<%= pkg.version %>'")
        -          );
        -
        -          // "code" is complete: create the distributable UMD build by running
        -          // the bundle through derequire
        -          // (Derequire changes the bundle's internal "require" function to
        -          // something that will not interfere with this module being used
        -          // within a separate browserify bundle.)
        -          code = derequire(code);
        -
        -          // and prettify the code
        -          if (!isMin) {
        -            const prettyFast = require('pretty-fast');
        -            code = prettyFast(code, {
        -              url: '(anonymous)',
        -              indent: '  '
        -            }).code;
        -          }
        -
        -          // finally, write it to disk
        -          grunt.file.write(libFilePath, code);
        -
        -          // Print a success message
        -          grunt.log.writeln(
        -            '>>'.green + ' Bundle ' + ('lib/' + filename).cyan + ' created.'
        -          );
        -
        -          // Complete the task
        -          done();
        -        });
        -    }
        -  );
        -};
        diff --git a/tasks/build/combineModules.js b/tasks/build/combineModules.js
        deleted file mode 100644
        index 17db3f898b..0000000000
        --- a/tasks/build/combineModules.js
        +++ /dev/null
        @@ -1,109 +0,0 @@
        -// This file contains the "combineModules" task called during the build process.
        -
        -'use strict';
        -
        -const fs = require('fs');
        -const path = require('path');
        -const browserify = require('browserify');
        -const derequire = require('derequire');
        -
        -module.exports = function(grunt) {
        -  const tempFilePath = path.resolve('./src/customApp.js');
        -  grunt.registerTask(
        -    'combineModules',
        -    'Compile and combine certain modules with Browserify',
        -    function(args) {
        -      // Reading and writing files is asynchronous
        -      const done = this.async();
        -
        -      // Modules is an array of p5 modules to be bundled.
        -      const modules = ['core'];
        -      for (const arg of arguments) {
        -        if (arg !== 'min') {
        -          modules.push(arg);
        -        }
        -      }
        -      const modulesList = modules.join(', ');
        -
        -      // Render the banner for the top of the file. Includes the Module name.
        -      const bannerTemplate = `/*! Custom p5.js v<%= pkg.version %> <%= grunt.template.today("mmmm dd, yyyy") %>
        -        Contains the following modules : ${modulesList}*/`;
        -      const banner = grunt.template.process(bannerTemplate);
        -
        -      // Make a list of sources from app.js which match our criteria
        -      const dump = fs.readFileSync('./src/app.js', 'utf8').split('\n');
        -      const sources = [];
        -      const regexp = new RegExp('./(' + modules.join('/|') + ')', 'g');
        -      dump.forEach(source => {
        -        let match;
        -        while ((match = regexp.exec(source)) !== null) {
        -          sources.push(match.input);
        -        }
        -      });
        -      sources.push('module.exports = p5;');
        -
        -      // Create a temp file with this data and feed it to browserify
        -      fs.writeFileSync(tempFilePath, sources.join('\n'), 'utf8');
        -
        -      // Check whether combineModules is minified
        -      const isMin = args === 'min';
        -      const filename = isMin ? 'p5Custom.pre-min.js' : 'p5Custom.js';
        -
        -      // Target file path
        -      const libFilePath = path.resolve('lib/modules/' + filename);
        -
        -      // Invoke Browserify programatically to bundle the code
        -      let browseified = browserify(tempFilePath, {
        -        standalone: 'p5'
        -      });
        -
        -      if (isMin) {
        -        browseified = browseified
        -          .exclude('../../docs/reference/data.json')
        -          .exclude('../../docs/parameterData.json');
        -      }
        -
        -      const babelifyOpts = { plugins: ['static-fs'] };
        -
        -      const bundle = browseified.transform('babelify', babelifyOpts).bundle();
        -
        -      // Start the generated output with the banner comment,
        -      let code = banner + '\n';
        -
        -      // Then read the bundle into memory so we can run it through derequire
        -      bundle
        -        .on('data', function(data) {
        -          code += data;
        -        })
        -        .on('end', function() {
        -          // "code" is complete: create the distributable UMD build by running
        -          // the bundle through derequire
        -          // (Derequire changes the bundle's internal "require" function to
        -          // something that will not interfere with this module being used
        -          // within a separate browserify bundle.)
        -          code = derequire(code);
        -
        -          // and prettify the code
        -          if (!isMin) {
        -            const prettyFast = require('pretty-fast');
        -            code = prettyFast(code, {
        -              url: '(anonymous)',
        -              indent: '  '
        -            }).code;
        -          }
        -
        -          // finally, write it to disk and remove the temp file
        -          grunt.file.write(libFilePath, code);
        -          grunt.file.delete(tempFilePath);
        -
        -          // Print a success message
        -          grunt.log.writeln(
        -            '>>'.green + ' Module ' + libFilePath.blue + ' created.'
        -          );
        -
        -          // Complete the task
        -          done();
        -        });
        -    }
        -  );
        -};
        diff --git a/tasks/build/eslint-samples.js b/tasks/build/eslint-samples.js
        deleted file mode 100644
        index 37d11c7722..0000000000
        --- a/tasks/build/eslint-samples.js
        +++ /dev/null
        @@ -1,52 +0,0 @@
        -// This file contains the "eslint-samples" task.
        -
        -'use strict';
        -import { magenta } from 'chalk';
        -
        -module.exports = grunt => {
        -  grunt.registerMultiTask(
        -    'eslint-samples',
        -    'Validate samples with ESLint',
        -    async function() {
        -      const done = this.async();
        -      const opts = this.options({
        -        outputFile: false,
        -        quiet: false,
        -        maxWarnings: -1,
        -        envs: ['eslint-samples/p5'],
        -        verbose: true,
        -        debug: true
        -      });
        -
        -      if (this.filesSrc.length === 0) {
        -        grunt.log.writeln(magenta('Could not find any files to validate'));
        -        return true;
        -      }
        -
        -      // need to use require here because we want this to only
        -      // get loaded after the data file has been created by a
        -      // prior grunt task
        -      const sampleLinter = require('../../utils/sample-linter.js');
        -      const result = await sampleLinter.eslintFiles(opts, this.filesSrc);
        -      const report = result.report;
        -      const output = result.output;
        -
        -      if (opts.outputFile) {
        -        grunt.file.write(opts.outputFile, output);
        -      } else if (output) {
        -        console.log(output);
        -      }
        -
        -      const tooManyWarnings =
        -        opts.maxWarnings >= 0 && report.warningCount > opts.maxWarnings;
        -
        -      if (report.errorCount === 0 && tooManyWarnings) {
        -        grunt.warn(
        -          `ESLint found too many warnings (maximum: ${opts.maxWarnings})`
        -        );
        -      }
        -
        -      done(report.errorCount === 0);
        -    }
        -  );
        -};
        diff --git a/tasks/test/mocha-chrome.js b/tasks/test/mocha-chrome.js
        deleted file mode 100644
        index dc247ab87c..0000000000
        --- a/tasks/test/mocha-chrome.js
        +++ /dev/null
        @@ -1,130 +0,0 @@
        -/* Grunt Task to test mocha in a local Chrome instance */
        -
        -const puppeteer = require('puppeteer');
        -const util = require('util');
        -const mapSeries = require('promise-map-series');
        -const fs = require('fs');
        -const path = require('path');
        -const EventEmitter = require('events');
        -
        -const mkdir = util.promisify(fs.mkdir);
        -const writeFile = util.promisify(fs.writeFile);
        -
        -module.exports = function(grunt) {
        -  grunt.registerMultiTask('mochaChrome', async function() {
        -    const done = this.async();
        -
        -    // Launch Chrome in headless mode
        -    const browser = await puppeteer.launch({
        -      headless: true,
        -      args: ['--no-sandbox', '--disable-setuid-sandbox']
        -    });
        -
        -    try {
        -      // options here come from `Gruntfile.js` > `mochaConfig.test`
        -      const options = this.data.options;
        -
        -      for (const testURL of options.urls) {
        -        const event = new EventEmitter();
        -        const page = await browser.newPage();
        -
        -        try {
        -          // Set up visual tests
        -          await page.evaluateOnNewDocument(function(shouldGenerateScreenshots) {
        -            window.shouldGenerateScreenshots = shouldGenerateScreenshots;
        -          }, !process.env.CI);
        -
        -          await page.exposeFunction('writeImageFile', function(filename, base64Data) {
        -            fs.mkdirSync('test/' + path.dirname(filename), { recursive: true });
        -            const prefix = /^data:image\/\w+;base64,/;
        -            fs.writeFileSync(
        -              'test/' + filename,
        -              base64Data.replace(prefix, ''),
        -              'base64'
        -            );
        -          });
        -          await page.exposeFunction('writeTextFile', function(filename, data) {
        -            fs.mkdirSync('test/' + path.dirname(filename), { recursive: true });
        -            fs.writeFileSync(
        -              'test/' + filename,
        -              data
        -            );
        -          });
        -
        -          // Using eval to start the test in the browser
        -          // A 'mocha:end' event will be triggered with test runner end
        -          await page.evaluateOnNewDocument(`
        -            addEventListener('DOMContentLoaded', () => {
        -              if (typeof mocha !== 'undefined') {
        -                const _mochaRun = mocha.run.bind(mocha);
        -                mocha.reporter('spec');
        -                mocha.color(true);
        -                mocha.run = function(fn) {
        -                  debugger;
        -                  var runner = _mochaRun(function(stats) {
        -                    if (typeof fn === 'function')
        -                      return fn(stats);
        -                  });
        -
        -                  runner.on('end', () => {
        -                    const results = { stats: runner.stats, coverage: window.__coverage__ }
        -                    fireMochaEvent('mocha:end', results);
        -                  });
        -
        -                  return runner;
        -                };
        -              }
        -            });
        -          `);
        -
        -          // Pipe console messages from the browser to the terminal
        -          page.on('console', async msg => {
        -            const args = await mapSeries(msg.args(), v => v.jsonValue());
        -            console.log(util.format.apply(util, args));
        -          });
        -
        -          // Wait for test end function to be called and emit on the event
        -          await page.exposeFunction('fireMochaEvent', event.emit.bind(event));
        -
        -          await new Promise(async (resolve, reject) => {
        -            // When test end, check if there are any failures and record coverage
        -            event.on('mocha:end', async results => {
        -              const { stats, coverage } = results;
        -              if (stats.failures) {
        -                reject(stats);
        -              }
        -              await saveCoverage(coverage);
        -              resolve(stats);
        -            });
        -
        -            // Nagivate to URL and start test
        -            await page.goto(testURL);
        -          });
        -        } finally {
        -          await page.close();
        -        }
        -      }
        -
        -      done();
        -    } catch (e) {
        -      if (e instanceof Error) {
        -        done(e);
        -      } else {
        -        done(new Error(e));
        -      }
        -    } finally {
        -      await browser.close();
        -    }
        -  });
        -};
        -
        -async function saveCoverage(cov) {
        -  if (cov) {
        -    try {
        -      await mkdir('./.nyc_output/', { recursive: true });
        -      await writeFile('./.nyc_output/out.json', JSON.stringify(cov));
        -    } catch (e) {
        -      console.error(e);
        -    }
        -  }
        -}
        diff --git a/test/.eslintrc b/test/.eslintrc.json
        similarity index 93%
        rename from test/.eslintrc
        rename to test/.eslintrc.json
        index 2f3892a895..138e38a865 100644
        --- a/test/.eslintrc
        +++ b/test/.eslintrc.json
        @@ -4,7 +4,6 @@
           },
           "globals": {
             "assert": false,
        -    "sinon": false,
             "expect": false,
             "promisedSketch": true,
             "testSketchWithPromise": true,
        diff --git a/test/bench/REPORTS/REPORT_1_TEST CPU.md b/test/bench/REPORTS/REPORT_1_TEST CPU.md
        new file mode 100644
        index 0000000000..f645c2f235
        --- /dev/null
        +++ b/test/bench/REPORTS/REPORT_1_TEST CPU.md	
        @@ -0,0 +1,134 @@
        +# Benchmark Report
        +
        +## Sample Test Results
        +
        +This benchmark report summarizes the performance of tests on the `test/bench/cpu_transforms.ench.js` script, comparing CPU transforms set to true and false. The results show that TEST CPU TRANSFORMS false consistently outperforms true, with higher operations per second (ops/sec) and lower statistical variations. For example, in one test, false achieved 0.9418 ops/sec compared to true at 0.7533 ops/sec, making false 1.25x faster. The report includes detailed metrics and a summary of the relative performance differences.
        +
        +https://editor.p5js.org/davepagurek/sketches/FAqbP5k8i
        +
        +## Script that we are benchmarking
        +
        +Since in the test environment we are not running the draw function we are benchmarking the first run of the draw.
        +
        +```
        +const TEST_CPU_TRANSFORMS = false
        +
        +// Not testing strokes rn because they're slower in general and we don't
        +// need them for this test to make sense
        +p5.Geometry.prototype._edgesToVertices = () => {}
        +
        +let fps
        +const state = []
        +
        +function setup() {
        +  createCanvas(400, 400, WEBGL);
        +  for (let i = 0; i < 100; i++) {
        +    state.push({
        +      pos: createVector(random(-200, 200), random(-200, 200)),
        +      vel: createVector(random(-2, 2), random(-2, 2)),
        +    })
        +  }
        +  fps = createP()
        +}
        +
        +function draw() {
        +  background(220);
        +  
        +  for (const s of state) {
        +    s.pos.add(s.vel)
        +    for (const axis of ['x', 'y']) {
        +      if (s.pos[axis] < -200) {
        +        s.pos[axis] = -200
        +        s.vel[axis] *= -1
        +      }
        +      if (s.pos[axis] > 200) {
        +        s.pos[axis] = 200
        +        s.vel[axis] *= -1
        +      }
        +    }
        +  }
        +  
        +  const drawCircles = () => {
        +    for (const s of state) {
        +      push()
        +      translate(s.pos.x, s.pos.y)
        +      const pts = 500
        +      beginShape(TRIANGLE_FAN)
        +      vertex(0,0)
        +      for (let i = 0; i <= pts; i++) {
        +        const a = (i/pts) * TWO_PI
        +        vertex(5*cos(a), 5*sin(a))
        +      }
        +      endShape()
        +      pop()
        +    }
        +  }
        +  if (TEST_CPU_TRANSFORMS) {
        +    // Flattens into a single buffer
        +    const shape = buildGeometry(drawCircles)
        +    model(shape)
        +    freeGeometry(model)
        +  } else {
        +    drawCircles()
        +  }
        +  
        +  fps.html(round(frameRate()))
        +}
        +```
        +
        +
        +### test/bench/dave.bench.js (2) 35272ms
        +- **Dave bench test (2) 35268ms**
        +  - **TEST CPU TRANSFORMS true**: 0.7533 ops/sec ±3.80% (10 samples)
        +  - **TEST CPU TRANSFORMS false**: 0.9418 ops/sec ±0.20% (10 samples) _fastest_
        +
        +#### Summary
        +- **TEST CPU TRANSFORMS false** is 1.25x faster than **TEST CPU TRANSFORMS true**
        +
        +### Separated
        +#### test/bench/dave.bench.js (2) 27912ms
        +- **Dave bench test (2) 27909ms**
        +  - **TEST CPU TRANSFORMS false**: 0.5350 ops/sec ±4.39% (10 samples)
        +  - **TEST CPU TRANSFORMS true**: _skipped_
        +
        +#### test/bench/dave.bench.js (2) 19331ms
        +- **Dave bench test (2) 19327ms**
        +  - **TEST CPU TRANSFORMS false**: _skipped_
        +  - **TEST CPU TRANSFORMS true**: 0.7519 ops/sec ±3.80% (10 samples)
        +
        +### Comparing
        +#### DEV v2.1.5 /Users/cristianbanuelos/repos/p5.js.git/bench
        +- **test/bench/dave.bench.js (2) 15671ms**
        +  - **Dave bench test (2) 15667ms**
        +    - **TEST CPU TRANSFORMS false**: 0.9719 ops/sec ±1.36% (2 samples) _fastest_
        +    - **TEST CPU TRANSFORMS true**: 0.8170 ops/sec ±15.16% (2 samples)
        +
        +#### Summary
        +- **TEST CPU TRANSFORMS false** is 1.19x faster than **TEST CPU TRANSFORMS true**
        +
        +#### DEV v2.1.5 /Users/cristianbanuelos/repos/p5.js.git/bench
        +- **test/bench/dave.bench.js (2) 15915ms**
        +  - **Dave bench test (2) 15905ms**
        +    - **TEST CPU TRANSFORMS true**: 0.7961 ops/sec ±4.86% (2 samples)
        +    - **TEST CPU TRANSFORMS false**: 0.9286 ops/sec ±36.88% (2 samples) _fastest_
        +
        +#### Summary
        +- **TEST CPU TRANSFORMS false** is 1.17x faster than **TEST CPU TRANSFORMS true**
        +
        +## With 1000 Elements
        +### test/bench/dave.bench.js (2) 151509ms
        +- **Dave bench test (2) 151505ms**
        +  - **TEST CPU TRANSFORMS true**: 0.0582 ops/sec ±0.00% (1 sample)
        +  - **TEST CPU TRANSFORMS false**: 0.0912 ops/sec ±0.00% (1 sample) _fastest_
        +
        +#### Summary
        +- **TEST CPU TRANSFORMS false** is 1.57x faster than **TEST CPU TRANSFORMS true**
        +
        +### 1000 Elements Second Run
        +#### test/bench/dave.bench.js (2) 150601ms
        +- **Dave bench test (2) 150597ms**
        +  - **TEST CPU TRANSFORMS true**: 0.0589 ops/sec ±0.00% (1 sample)
        +  - **TEST CPU TRANSFORMS false**: 0.0914 ops/sec ±0.00% (1 sample) _fastest_
        +
        +#### Summary
        +- **TEST CPU TRANSFORMS false** is 1.55x faster than **TEST CPU TRANSFORMS true**
        diff --git a/test/bench/cpu_transforms.bench.js b/test/bench/cpu_transforms.bench.js
        new file mode 100644
        index 0000000000..f94b322842
        --- /dev/null
        +++ b/test/bench/cpu_transforms.bench.js
        @@ -0,0 +1,168 @@
        +// import p5 from "../../../src/app.js";
        +import { bench, describe } from "vitest";
        +import p5 from "../../src/app";
        +
        +const options = { iterations: 1, time: 1500 };
        +const ITERATIONS = 100
        +describe.sequential("Dave bench test", () => {
        +  bench(
        +    "TEST CPU TRANSFORMS true",
        +    async () => {
        +      try {
        +        const TEST_CPU_TRANSFORMS = true;
        +        var myp5;
        +        new p5(function (p) {
        +          p.setup = function () {
        +            myp5 = p;
        +          };
        +        });
        +        await vi.waitFor(() => {
        +          if (myp5 === undefined) {
        +            throw new Error("not ready");
        +          }
        +        });
        +        let fps;
        +        const state = [];
        +
        +        myp5.createCanvas(400, 400, myp5.WEBGL);
        +        for (let i = 0; i < ITERATIONS; i++) {
        +          state.push({
        +            pos: myp5.createVector(
        +              myp5.random(-200, 200),
        +              myp5.random(-200, 200)
        +            ),
        +            vel: myp5.createVector(myp5.random(-2, 2), myp5.random(-2, 2)),
        +          });
        +        }
        +        fps = myp5.createP();
        +
        +        assert.equal(myp5.webglVersion, myp5.webglVersion);
        +        myp5.remove();
        +
        +        // Now what's in draw
        +        myp5.background(220);
        +
        +        for (const s of state) {
        +          s.pos.add(s.vel);
        +          for (const axis of ["x", "y"]) {
        +            if (s.pos[axis] < -200) {
        +              s.pos[axis] = -200;
        +              s.vel[axis] *= -1;
        +            }
        +            if (s.pos[axis] > 200) {
        +              s.pos[axis] = 200;
        +              s.vel[axis] *= -1;
        +            }
        +          }
        +        }
        +
        +        const drawCircles = () => {
        +          for (const s of state) {
        +            myp5.push();
        +            myp5.translate(s.pos.x, s.pos.y);
        +            const pts = 500;
        +            myp5.beginShape(myp5.TRIANGLE_FAN);
        +            myp5.vertex(0, 0);
        +            for (let i = 0; i <= pts; i++) {
        +              const a = (i / pts) * myp5.TWO_PI;
        +              myp5.vertex(5 * myp5.cos(a), 5 * myp5.sin(a));
        +            }
        +            myp5.endShape();
        +            myp5.pop();
        +          }
        +        };
        +
        +        if (TEST_CPU_TRANSFORMS) {
        +          // Flattens into a single buffer
        +          const shape = myp5.buildGeometry(drawCircles);
        +          myp5.model(shape);
        +          myp5.freeGeometry(myp5.model);
        +        } else {
        +          drawCircles();
        +        }
        +      } catch (error) {
        +        console.log(error);
        +      }
        +    },
        +    options
        +  ),
        +    options;
        +  bench(
        +    "TEST CPU TRANSFORMS false",
        +    async () => {
        +      const TEST_CPU_TRANSFORMS = false;
        +      var myp5;
        +      new p5(function (p) {
        +        p.setup = function () {
        +          myp5 = p;
        +        };
        +      });
        +      await vi.waitFor(() => {
        +        if (myp5 === undefined) {
        +          throw new Error("not ready");
        +        }
        +      });
        +      let fps;
        +      const state = [];
        +
        +      myp5.createCanvas(400, 400, myp5.WEBGL);
        +      for (let i = 0; i < ITERATIONS; i++) {
        +        state.push({
        +          pos: myp5.createVector(
        +            myp5.random(-200, 200),
        +            myp5.random(-200, 200)
        +          ),
        +          vel: myp5.createVector(myp5.random(-2, 2), myp5.random(-2, 2)),
        +        });
        +      }
        +      fps = myp5.createP();
        +
        +      assert.equal(myp5.webglVersion, myp5.webglVersion);
        +      myp5.remove();
        +
        +      // Now what's in draw
        +      myp5.background(220);
        +
        +      for (const s of state) {
        +        s.pos.add(s.vel);
        +        for (const axis of ["x", "y"]) {
        +          if (s.pos[axis] < -200) {
        +            s.pos[axis] = -200;
        +            s.vel[axis] *= -1;
        +          }
        +          if (s.pos[axis] > 200) {
        +            s.pos[axis] = 200;
        +            s.vel[axis] *= -1;
        +          }
        +        }
        +      }
        +
        +      const drawCircles = () => {
        +        for (const s of state) {
        +          myp5.push();
        +          myp5.translate(s.pos.x, s.pos.y);
        +          const pts = 500;
        +          myp5.beginShape(myp5.TRIANGLE_FAN);
        +          myp5.vertex(0, 0);
        +          for (let i = 0; i <= pts; i++) {
        +            const a = (i / pts) * myp5.TWO_PI;
        +            myp5.vertex(5 * myp5.cos(a), 5 * myp5.sin(a));
        +          }
        +          myp5.endShape();
        +          myp5.pop();
        +        }
        +      };
        +
        +      if (TEST_CPU_TRANSFORMS) {
        +        // Flattens into a single buffer
        +        const shape = myp5.buildGeometry(drawCircles);
        +        myp5.model(shape);
        +        myp5.freeGeometry(myp5.model);
        +      } else {
        +        drawCircles();
        +      }
        +    },
        +    options
        +  ),
        +    options;
        +});
        diff --git a/test/bench/poc.bench.js b/test/bench/poc.bench.js
        new file mode 100644
        index 0000000000..100cd2f197
        --- /dev/null
        +++ b/test/bench/poc.bench.js
        @@ -0,0 +1,29 @@
        +import { bench, describe } from "vitest";
        +
        +describe.only("sort", () => {
        +  bench(
        +    "normal",
        +    () => {
        +      const x = [1, 5, 4, 2, 3];
        +      x.sort((a, b) => {
        +        return a - b;
        +      });
        +      throw new Error("error");
        +    },
        +    { iterations: 100 }
        +  );
        +
        +  bench(
        +    "reverse",
        +    () => {
        +      const x = [1, 5, 4, 2, 3];
        +      x.reverse()
        +        .reverse()
        +        .reverse()
        +        .sort((a, b) => {
        +          return a - b;
        +        });
        +    },
        +    { iterations: 10 }
        +  );
        +});
        diff --git a/test/bench/rendering.bench.js b/test/bench/rendering.bench.js
        new file mode 100644
        index 0000000000..7d4813473f
        --- /dev/null
        +++ b/test/bench/rendering.bench.js
        @@ -0,0 +1,200 @@
        +// import p5 from "../../../src/app.js";
        +import { bench, describe } from "vitest";
        +import p5 from "../../src/app";
        +
        +const options = { iterations: 20, time: 500 };
        +describe("Rendering bench test", () => {
        +  bench(
        +    "normal",
        +    async () => {
        +      try {
        +        var myp5;
        +        new p5(function (p) {
        +          p.setup = function () {
        +            myp5 = p;
        +            p.rect(10, 10, 10, 10);
        +          };
        +        });
        +        await vi.waitFor(() => {
        +          if (myp5 === undefined) {
        +            throw new Error("not ready");
        +          }
        +        });
        +
        +        myp5.createCanvas(13, 15);
        +        myp5.fill(0, 100, 0);
        +        myp5.rect(20, 20, 20, 20);
        +
        +        assert.equal(myp5.webglVersion, myp5.P2D);
        +        myp5.remove();
        +      } catch (error) {
        +        console.log(error);
        +      }
        +    },
        +    options
        +  );
        +
        +  bench(
        +    "thousand",
        +    async () => {
        +      try {
        +        var myp5;
        +        new p5(function (p) {
        +          p.setup = function () {
        +            myp5 = p;
        +            p.rect(10, 10, 10, 10);
        +          };
        +        });
        +        await vi.waitFor(() => {
        +          if (myp5 === undefined) {
        +            throw new Error("not ready");
        +          }
        +        });
        +
        +        myp5.createCanvas(13, 15);
        +        myp5.fill(0, 100, 0);
        +        myp5.rect(20, 20, 20, 20);
        +        for (let i = 0; i < 10000; i++) {
        +          myp5.rect(20, 20, 20, 20);
        +        }
        +
        +        assert.equal(myp5.webglVersion, myp5.P2D);
        +        myp5.remove();
        +      } catch (error) {
        +        console.log(error);
        +      }
        +    },
        +    options
        +  );
        +
        +  bench(
        +    "10k",
        +    async () => {
        +      try {
        +        var myp5;
        +        new p5(function (p) {
        +          p.setup = function () {
        +            myp5 = p;
        +            p.rect(10, 10, 10, 10);
        +          };
        +        });
        +        await vi.waitFor(() => {
        +          if (myp5 === undefined) {
        +            throw new Error("not ready");
        +          }
        +        });
        +
        +        myp5.createCanvas(13, 15);
        +        myp5.fill(0, 100, 0);
        +        myp5.rect(20, 20, 20, 20);
        +        for (let i = 0; i < 10000; i++) {
        +          myp5.rect(20, 20, 20, 20);
        +        }
        +
        +        assert.equal(myp5.webglVersion, myp5.P2D);
        +        myp5.remove();
        +      } catch (error) {
        +        console.log(error);
        +      }
        +    },
        +    options
        +  );
        +});
        +
        +describe("Another suite", () => {
        +  bench(
        +    "normal v2",
        +    async () => {
        +      try {
        +        var myp5;
        +        new p5(function (p) {
        +          p.setup = function () {
        +            myp5 = p;
        +            p.rect(10, 10, 10, 10);
        +          };
        +        });
        +        await vi.waitFor(() => {
        +          if (myp5 === undefined) {
        +            throw new Error("not ready");
        +          }
        +        });
        +
        +        myp5.createCanvas(13, 15);
        +        myp5.fill(0, 100, 0);
        +        myp5.rect(20, 20, 20, 20);
        +
        +        assert.equal(myp5.webglVersion, myp5.P2D);
        +        myp5.remove();
        +      } catch (error) {
        +        console.log(error);
        +      }
        +    },
        +    options
        +  );
        +
        +  bench(
        +    "thousand v2",
        +    async () => {
        +      try {
        +        var myp5;
        +        new p5(function (p) {
        +          p.setup = function () {
        +            myp5 = p;
        +            p.rect(10, 10, 10, 10);
        +          };
        +        });
        +        await vi.waitFor(() => {
        +          if (myp5 === undefined) {
        +            throw new Error("not ready");
        +          }
        +        });
        +
        +        myp5.createCanvas(13, 15);
        +        myp5.fill(0, 100, 0);
        +        myp5.rect(20, 20, 20, 20);
        +        for (let i = 0; i < 1000; i++) {
        +          myp5.rect(20, 20, 20, 20);
        +        }
        +
        +        assert.equal(myp5.webglVersion, myp5.P2D);
        +        myp5.remove();
        +      } catch (error) {
        +        console.log(error);
        +      }
        +    },
        +    options
        +  );
        +
        +  bench(
        +    "10k v2",
        +    async () => {
        +      try {
        +        var myp5;
        +        new p5(function (p) {
        +          p.setup = function () {
        +            myp5 = p;
        +            p.rect(10, 10, 10, 10);
        +          };
        +        });
        +        await vi.waitFor(() => {
        +          if (myp5 === undefined) {
        +            throw new Error("not ready");
        +          }
        +        });
        +
        +        myp5.createCanvas(13, 15);
        +        myp5.fill(0, 100, 0);
        +        myp5.rect(20, 20, 20, 20);
        +        for (let i = 0; i < 10000; i++) {
        +          myp5.rect(20, 20, 20, 20);
        +        }
        +
        +        assert.equal(myp5.webglVersion, myp5.P2D);
        +        myp5.remove();
        +      } catch (error) {
        +        console.log(error);
        +      }
        +    },
        +    options
        +  );
        +});
        diff --git a/test/js/chai_helpers.js b/test/js/chai_helpers.js
        index 72217e08ec..7018aa9c26 100644
        --- a/test/js/chai_helpers.js
        +++ b/test/js/chai_helpers.js
        @@ -1,21 +1,29 @@
        +import p5 from '../../src/app.js';
        +
         // Setup chai
         var expect = chai.expect;
         var assert = chai.assert;
         
        -assert.arrayApproximately = function(arr1, arr2, delta) {
        +assert.arrayApproximately = function (arr1, arr2, delta, desc) {
           assert.equal(arr1.length, arr2.length);
        -  for(var i = 0; i < arr1.length; i++) {
        -    assert.approximately(arr1[i], arr2[i], delta);
        +  for (var i = 0; i < arr1.length; i++) {
        +    assert.approximately(arr1[i], arr2[i], delta, desc);
           }
         }
         
        +assert.deepCloseTo = function (actual, expected, digits = 4) {
        +  expect(actual.length).toBe(expected.length);
        +  for (let i = 0; i < actual.length; i++) {
        +    expect(actual[i]).withContext(`[${i}]`).toBeCloseTo(expected[i], digits);
        +  }
        +}
         
         // a custom assertion for validation errors that correctly handles
         // minified p5 libraries.
        -assert.validationError = function(fn) {
        +assert.validationError = function (fn) {
           if (p5.ValidationError) {
             assert.throws(fn, p5.ValidationError);
           } else {
             assert.doesNotThrow(fn, Error, 'got unwanted exception');
           }
        -};
        +};
        \ No newline at end of file
        diff --git a/test/js/mocks.js b/test/js/mocks.js
        new file mode 100644
        index 0000000000..80a4cd68f8
        --- /dev/null
        +++ b/test/js/mocks.js
        @@ -0,0 +1,49 @@
        +import { vi } from 'vitest';
        +import { http, HttpResponse, passthrough } from 'msw';
        +import { setupWorker } from 'msw/browser';
        +
        +// HTTP requests mocks
        +const httpMocks = [
        +  http.get('404file', () => {
        +    return new HttpResponse('Not Found', {
        +      status: 404,
        +      statusText: 'Not Found',
        +    });
        +  }),
        +  http.all('*', ({request}) => {
        +    return passthrough();
        +  })
        +];
        +
        +export const httpMock = setupWorker(...httpMocks);
        +
        +// p5.js module mocks
        +const rendererStates = {};
        +export const mockP5 = vi.fn();
        +Object.assign(mockP5, {
        +  _validateParameters: vi.fn(),
        +  _friendlyFileLoadError: vi.fn(),
        +  _friendlyError: vi.fn(),
        +  Renderer: {
        +    states: rendererStates
        +  }
        +});
        +
        +const mockCanvas = document.createElement('canvas');
        +mockCanvas.id = 'myCanvasID';
        +document.getElementsByTagName("body")[0].appendChild(mockCanvas);
        +
        +export const mockP5Prototype = {
        +  saveCanvas: vi.fn(),
        +  elt: mockCanvas,
        +  _curElement: {
        +    elt: mockCanvas
        +  },
        +  canvas: {
        +    id: 'myCanvasID'
        +  },
        +  _elements: [],
        +  _renderer: {
        +    states: rendererStates
        +  }
        +};
        diff --git a/test/js/p5_helpers.js b/test/js/p5_helpers.js
        index 09603e0c9c..363c0d462e 100644
        --- a/test/js/p5_helpers.js
        +++ b/test/js/p5_helpers.js
        @@ -1,6 +1,7 @@
         /* eslint no-unused-vars: 0 */
        +import p5 from '../../src/app.js';
         
        -function promisedSketch(sketch_fn) {
        +export function promisedSketch(sketch_fn) {
           var myInstance;
           var promise = new Promise(function(resolve, reject) {
             myInstance = new p5(function(sketch) {
        @@ -14,7 +15,7 @@ function promisedSketch(sketch_fn) {
           return promise;
         }
         
        -function testSketchWithPromise(name, sketch_fn) {
        +export function testSketchWithPromise(name, sketch_fn) {
           var test_fn = function() {
             return promisedSketch(sketch_fn);
           };
        @@ -24,59 +25,12 @@ function testSketchWithPromise(name, sketch_fn) {
           return test(name, test_fn);
         }
         
        -function testWithDownload(name, fn, asyncFn = false) {
        -  var test_fn = function(done) {
        -    // description of this is also on
        -    // https://github.com/processing/p5.js/pull/4418/
        -
        -    let blobContainer = {};
        -
        -    // create a backup of createObjectURL
        -    let couBackup = window.URL.createObjectURL;
        -
        -    // file-saver uses createObjectURL as an intermediate step. If we
        -    // modify the definition a just a little bit we can capture whenever
        -    // it is called and also peek in the data that was passed to it
        -    window.URL.createObjectURL = blob => {
        -      blobContainer.blob = blob;
        -      return couBackup(blob);
        -    };
        -
        -    let error;
        -    if (asyncFn) {
        -      fn(blobContainer)
        -        .then(() => {
        -          window.URL.createObjectURL = couBackup;
        -        })
        -        .catch(err => {
        -          error = err;
        -        })
        -        .finally(() => {
        -          // restore createObjectURL to the original one
        -          window.URL.createObjectURL = couBackup;
        -          error ? done(error) : done();
        -        });
        -    } else {
        -      try {
        -        fn(blobContainer);
        -      } catch (err) {
        -        error = err;
        -      }
        -      // restore createObjectURL to the original one
        -      window.URL.createObjectURL = couBackup;
        -      error ? done(error) : done();
        -    }
        -  };
        -
        -  return test(name, test_fn);
        -}
        -
         // Tests should run only for the unminified script
        -function testUnMinified(name, test_fn) {
        +export function testUnMinified(name, test_fn) {
           return !window.IS_TESTING_MINIFIED_VERSION ? test(name, test_fn) : null;
         }
         
        -function parallelSketches(sketch_fns) {
        +export function parallelSketches(sketch_fns) {
           var setupPromises = [];
           var resultPromises = [];
           var endCallbacks = [];
        @@ -111,10 +65,10 @@ function parallelSketches(sketch_fns) {
           };
         }
         
        -var P5_SCRIPT_URL = '../../lib/p5.js';
        -var P5_SCRIPT_TAG = '<script src="' + P5_SCRIPT_URL + '"></script>';
        +export const P5_SCRIPT_URL = '../../lib/p5.js';
        +export const P5_SCRIPT_TAG = '<script src="' + P5_SCRIPT_URL + '"></script>';
         
        -function createP5Iframe(html) {
        +export function createP5Iframe(html) {
           html = html || P5_SCRIPT_TAG;
         
           var elt = document.createElement('iframe');
        diff --git a/test/js/sinon.js b/test/js/sinon.js
        deleted file mode 100644
        index f0a9e842e8..0000000000
        --- a/test/js/sinon.js
        +++ /dev/null
        @@ -1,5949 +0,0 @@
        -/**
        - * Sinon.JS 1.15.4, 2015/06/27
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS
        - *
        - * (The BSD License)
        - *
        - * Copyright (c) 2010-2014, Christian Johansen, christian@cjohansen.no
        - * 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 Christian Johansen nor the names of his 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 HOLDER 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.
        - */
        -
        -(function (root, factory) {
        -  'use strict';
        -  if (typeof define === 'function' && define.amd) {
        -    define('sinon', [], function () {
        -      return (root.sinon = factory());
        -    });
        -  } else if (typeof exports === 'object') {
        -    module.exports = factory();
        -  } else {
        -    root.sinon = factory();
        -  }
        -}(this, function () {
        -  'use strict';
        -  var samsam, formatio, lolex;
        -  (function () {
        -                function define(mod, deps, fn) {
        -                  if (mod == "samsam") {
        -                    samsam = deps();
        -                  } else if (typeof deps === "function" && mod.length === 0) {
        -                    lolex = deps();
        -                  } else if (typeof fn === "function") {
        -                    formatio = fn(samsam);
        -                  }
        -                }
        -    define.amd = {};
        -((typeof define === "function" && define.amd && function (m) { define("samsam", m); }) ||
        - (typeof module === "object" &&
        -      function (m) { module.exports = m(); }) || // Node
        - function (m) { this.samsam = m(); } // Browser globals
        -)(function () {
        -    var o = Object.prototype;
        -    var div = typeof document !== "undefined" && document.createElement("div");
        -
        -    function isNaN(value) {
        -        // Unlike global isNaN, this avoids type coercion
        -        // typeof check avoids IE host object issues, hat tip to
        -        // lodash
        -        var val = value; // JsLint thinks value !== value is "weird"
        -        return typeof value === "number" && value !== val;
        -    }
        -
        -    function getClass(value) {
        -        // Returns the internal [[Class]] by calling Object.prototype.toString
        -        // with the provided value as this. Return value is a string, naming the
        -        // internal class, e.g. "Array"
        -        return o.toString.call(value).split(/[ \]]/)[1];
        -    }
        -
        -    /**
        -     * @name samsam.isArguments
        -     * @param Object object
        -     *
        -     * Returns ``true`` if ``object`` is an ``arguments`` object,
        -     * ``false`` otherwise.
        -     */
        -    function isArguments(object) {
        -        if (getClass(object) === 'Arguments') { return true; }
        -        if (typeof object !== "object" || typeof object.length !== "number" ||
        -                getClass(object) === "Array") {
        -            return false;
        -        }
        -        if (typeof object.callee == "function") { return true; }
        -        try {
        -            object[object.length] = 6;
        -            delete object[object.length];
        -        } catch (e) {
        -            return true;
        -        }
        -        return false;
        -    }
        -
        -    /**
        -     * @name samsam.isElement
        -     * @param Object object
        -     *
        -     * Returns ``true`` if ``object`` is a DOM element node. Unlike
        -     * Underscore.js/lodash, this function will return ``false`` if ``object``
        -     * is an *element-like* object, i.e. a regular object with a ``nodeType``
        -     * property that holds the value ``1``.
        -     */
        -    function isElement(object) {
        -        if (!object || object.nodeType !== 1 || !div) { return false; }
        -        try {
        -            object.appendChild(div);
        -            object.removeChild(div);
        -        } catch (e) {
        -            return false;
        -        }
        -        return true;
        -    }
        -
        -    /**
        -     * @name samsam.keys
        -     * @param Object object
        -     *
        -     * Return an array of own property names.
        -     */
        -    function keys(object) {
        -        var ks = [], prop;
        -        for (prop in object) {
        -            if (o.hasOwnProperty.call(object, prop)) { ks.push(prop); }
        -        }
        -        return ks;
        -    }
        -
        -    /**
        -     * @name samsam.isDate
        -     * @param Object value
        -     *
        -     * Returns true if the object is a ``Date``, or *date-like*. Duck typing
        -     * of date objects work by checking that the object has a ``getTime``
        -     * function whose return value equals the return value from the object's
        -     * ``valueOf``.
        -     */
        -    function isDate(value) {
        -        return typeof value.getTime == "function" &&
        -            value.getTime() == value.valueOf();
        -    }
        -
        -    /**
        -     * @name samsam.isNegZero
        -     * @param Object value
        -     *
        -     * Returns ``true`` if ``value`` is ``-0``.
        -     */
        -    function isNegZero(value) {
        -        return value === 0 && 1 / value === -Infinity;
        -    }
        -
        -    /**
        -     * @name samsam.equal
        -     * @param Object obj1
        -     * @param Object obj2
        -     *
        -     * Returns ``true`` if two objects are strictly equal. Compared to
        -     * ``===`` there are two exceptions:
        -     *
        -     *   - NaN is considered equal to NaN
        -     *   - -0 and +0 are not considered equal
        -     */
        -    function identical(obj1, obj2) {
        -        if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) {
        -            return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2);
        -        }
        -    }
        -
        -
        -    /**
        -     * @name samsam.deepEqual
        -     * @param Object obj1
        -     * @param Object obj2
        -     *
        -     * Deep equal comparison. Two values are "deep equal" if:
        -     *
        -     *   - They are equal, according to samsam.identical
        -     *   - They are both date objects representing the same time
        -     *   - They are both arrays containing elements that are all deepEqual
        -     *   - They are objects with the same set of properties, and each property
        -     *     in ``obj1`` is deepEqual to the corresponding property in ``obj2``
        -     *
        -     * Supports cyclic objects.
        -     */
        -    function deepEqualCyclic(obj1, obj2) {
        -
        -        // used for cyclic comparison
        -        // contain already visited objects
        -        var objects1 = [],
        -            objects2 = [],
        -        // contain pathes (position in the object structure)
        -        // of the already visited objects
        -        // indexes same as in objects arrays
        -            paths1 = [],
        -            paths2 = [],
        -        // contains combinations of already compared objects
        -        // in the manner: { "$1['ref']$2['ref']": true }
        -            compared = {};
        -
        -        /**
        -         * used to check, if the value of a property is an object
        -         * (cyclic logic is only needed for objects)
        -         * only needed for cyclic logic
        -         */
        -        function isObject(value) {
        -
        -            if (typeof value === 'object' && value !== null &&
        -                    !(value instanceof Boolean) &&
        -                    !(value instanceof Date)    &&
        -                    !(value instanceof Number)  &&
        -                    !(value instanceof RegExp)  &&
        -                    !(value instanceof String)) {
        -
        -                return true;
        -            }
        -
        -            return false;
        -        }
        -
        -        /**
        -         * returns the index of the given object in the
        -         * given objects array, -1 if not contained
        -         * only needed for cyclic logic
        -         */
        -        function getIndex(objects, obj) {
        -
        -            var i;
        -            for (i = 0; i < objects.length; i++) {
        -                if (objects[i] === obj) {
        -                    return i;
        -                }
        -            }
        -
        -            return -1;
        -        }
        -
        -        // does the recursion for the deep equal check
        -        return (function deepEqual(obj1, obj2, path1, path2) {
        -            var type1 = typeof obj1;
        -            var type2 = typeof obj2;
        -
        -            // == null also matches undefined
        -            if (obj1 === obj2 ||
        -                    isNaN(obj1) || isNaN(obj2) ||
        -                    obj1 == null || obj2 == null ||
        -                    type1 !== "object" || type2 !== "object") {
        -
        -                return identical(obj1, obj2);
        -            }
        -
        -            // Elements are only equal if identical(expected, actual)
        -            if (isElement(obj1) || isElement(obj2)) { return false; }
        -
        -            var isDate1 = isDate(obj1), isDate2 = isDate(obj2);
        -            if (isDate1 || isDate2) {
        -                if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) {
        -                    return false;
        -                }
        -            }
        -
        -            if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
        -                if (obj1.toString() !== obj2.toString()) { return false; }
        -            }
        -
        -            var class1 = getClass(obj1);
        -            var class2 = getClass(obj2);
        -            var keys1 = keys(obj1);
        -            var keys2 = keys(obj2);
        -
        -            if (isArguments(obj1) || isArguments(obj2)) {
        -                if (obj1.length !== obj2.length) { return false; }
        -            } else {
        -                if (type1 !== type2 || class1 !== class2 ||
        -                        keys1.length !== keys2.length) {
        -                    return false;
        -                }
        -            }
        -
        -            var key, i, l,
        -                // following vars are used for the cyclic logic
        -                value1, value2,
        -                isObject1, isObject2,
        -                index1, index2,
        -                newPath1, newPath2;
        -
        -            for (i = 0, l = keys1.length; i < l; i++) {
        -                key = keys1[i];
        -                if (!o.hasOwnProperty.call(obj2, key)) {
        -                    return false;
        -                }
        -
        -                // Start of the cyclic logic
        -
        -                value1 = obj1[key];
        -                value2 = obj2[key];
        -
        -                isObject1 = isObject(value1);
        -                isObject2 = isObject(value2);
        -
        -                // determine, if the objects were already visited
        -                // (it's faster to check for isObject first, than to
        -                // get -1 from getIndex for non objects)
        -                index1 = isObject1 ? getIndex(objects1, value1) : -1;
        -                index2 = isObject2 ? getIndex(objects2, value2) : -1;
        -
        -                // determine the new pathes of the objects
        -                // - for non cyclic objects the current path will be extended
        -                //   by current property name
        -                // - for cyclic objects the stored path is taken
        -                newPath1 = index1 !== -1
        -                    ? paths1[index1]
        -                    : path1 + '[' + JSON.stringify(key) + ']';
        -                newPath2 = index2 !== -1
        -                    ? paths2[index2]
        -                    : path2 + '[' + JSON.stringify(key) + ']';
        -
        -                // stop recursion if current objects are already compared
        -                if (compared[newPath1 + newPath2]) {
        -                    return true;
        -                }
        -
        -                // remember the current objects and their pathes
        -                if (index1 === -1 && isObject1) {
        -                    objects1.push(value1);
        -                    paths1.push(newPath1);
        -                }
        -                if (index2 === -1 && isObject2) {
        -                    objects2.push(value2);
        -                    paths2.push(newPath2);
        -                }
        -
        -                // remember that the current objects are already compared
        -                if (isObject1 && isObject2) {
        -                    compared[newPath1 + newPath2] = true;
        -                }
        -
        -                // End of cyclic logic
        -
        -                // neither value1 nor value2 is a cycle
        -                // continue with next level
        -                if (!deepEqual(value1, value2, newPath1, newPath2)) {
        -                    return false;
        -                }
        -            }
        -
        -            return true;
        -
        -        }(obj1, obj2, '$1', '$2'));
        -    }
        -
        -    var match;
        -
        -    function arrayContains(array, subset) {
        -        if (subset.length === 0) { return true; }
        -        var i, l, j, k;
        -        for (i = 0, l = array.length; i < l; ++i) {
        -            if (match(array[i], subset[0])) {
        -                for (j = 0, k = subset.length; j < k; ++j) {
        -                    if (!match(array[i + j], subset[j])) { return false; }
        -                }
        -                return true;
        -            }
        -        }
        -        return false;
        -    }
        -
        -    /**
        -     * @name samsam.match
        -     * @param Object object
        -     * @param Object matcher
        -     *
        -     * Compare arbitrary value ``object`` with matcher.
        -     */
        -    match = function match(object, matcher) {
        -        if (matcher && typeof matcher.test === "function") {
        -            return matcher.test(object);
        -        }
        -
        -        if (typeof matcher === "function") {
        -            return matcher(object) === true;
        -        }
        -
        -        if (typeof matcher === "string") {
        -            matcher = matcher.toLowerCase();
        -            var notNull = typeof object === "string" || !!object;
        -            return notNull &&
        -                (String(object)).toLowerCase().indexOf(matcher) >= 0;
        -        }
        -
        -        if (typeof matcher === "number") {
        -            return matcher === object;
        -        }
        -
        -        if (typeof matcher === "boolean") {
        -            return matcher === object;
        -        }
        -
        -        if (typeof(matcher) === "undefined") {
        -            return typeof(object) === "undefined";
        -        }
        -
        -        if (matcher === null) {
        -            return object === null;
        -        }
        -
        -        if (getClass(object) === "Array" && getClass(matcher) === "Array") {
        -            return arrayContains(object, matcher);
        -        }
        -
        -        if (matcher && typeof matcher === "object") {
        -            if (matcher === object) {
        -                return true;
        -            }
        -            var prop;
        -            for (prop in matcher) {
        -                var value = object[prop];
        -                if (typeof value === "undefined" &&
        -                        typeof object.getAttribute === "function") {
        -                    value = object.getAttribute(prop);
        -                }
        -                if (matcher[prop] === null || typeof matcher[prop] === 'undefined') {
        -                    if (value !== matcher[prop]) {
        -                        return false;
        -                    }
        -                } else if (typeof  value === "undefined" || !match(value, matcher[prop])) {
        -                    return false;
        -                }
        -            }
        -            return true;
        -        }
        -
        -        throw new Error("Matcher was not a string, a number, a " +
        -                        "function, a boolean or an object");
        -    };
        -
        -    return {
        -        isArguments: isArguments,
        -        isElement: isElement,
        -        isDate: isDate,
        -        isNegZero: isNegZero,
        -        identical: identical,
        -        deepEqual: deepEqualCyclic,
        -        match: match,
        -        keys: keys
        -    };
        -});
        -((typeof define === "function" && define.amd && function (m) {
        -    define("formatio", ["samsam"], m);
        -}) || (typeof module === "object" && function (m) {
        -    module.exports = m(require("samsam"));
        -}) || function (m) { this.formatio = m(this.samsam); }
        -)(function (samsam) {
        -
        -    var formatio = {
        -        excludeConstructors: ["Object", /^.$/],
        -        quoteStrings: true,
        -        limitChildrenCount: 0
        -    };
        -
        -    var hasOwn = Object.prototype.hasOwnProperty;
        -
        -    var specialObjects = [];
        -    if (typeof global !== "undefined") {
        -        specialObjects.push({ object: global, value: "[object global]" });
        -    }
        -    if (typeof document !== "undefined") {
        -        specialObjects.push({
        -            object: document,
        -            value: "[object HTMLDocument]"
        -        });
        -    }
        -    if (typeof window !== "undefined") {
        -        specialObjects.push({ object: window, value: "[object Window]" });
        -    }
        -
        -    function functionName(func) {
        -        if (!func) { return ""; }
        -        if (func.displayName) { return func.displayName; }
        -        if (func.name) { return func.name; }
        -        var matches = func.toString().match(/function\s+([^\(]+)/m);
        -        return (matches && matches[1]) || "";
        -    }
        -
        -    function constructorName(f, object) {
        -        var name = functionName(object && object.constructor);
        -        var excludes = f.excludeConstructors ||
        -                formatio.excludeConstructors || [];
        -
        -        var i, l;
        -        for (i = 0, l = excludes.length; i < l; ++i) {
        -            if (typeof excludes[i] === "string" && excludes[i] === name) {
        -                return "";
        -            } else if (excludes[i].test && excludes[i].test(name)) {
        -                return "";
        -            }
        -        }
        -
        -        return name;
        -    }
        -
        -    function isCircular(object, objects) {
        -        if (typeof object !== "object") { return false; }
        -        var i, l;
        -        for (i = 0, l = objects.length; i < l; ++i) {
        -            if (objects[i] === object) { return true; }
        -        }
        -        return false;
        -    }
        -
        -    function ascii(f, object, processed, indent) {
        -        if (typeof object === "string") {
        -            var qs = f.quoteStrings;
        -            var quote = typeof qs !== "boolean" || qs;
        -            return processed || quote ? '"' + object + '"' : object;
        -        }
        -
        -        if (typeof object === "function" && !(object instanceof RegExp)) {
        -            return ascii.func(object);
        -        }
        -
        -        processed = processed || [];
        -
        -        if (isCircular(object, processed)) { return "[Circular]"; }
        -
        -        if (Object.prototype.toString.call(object) === "[object Array]") {
        -            return ascii.array.call(f, object, processed);
        -        }
        -
        -        if (!object) { return String((1/object) === -Infinity ? "-0" : object); }
        -        if (samsam.isElement(object)) { return ascii.element(object); }
        -
        -        if (typeof object.toString === "function" &&
        -                object.toString !== Object.prototype.toString) {
        -            return object.toString();
        -        }
        -
        -        var i, l;
        -        for (i = 0, l = specialObjects.length; i < l; i++) {
        -            if (object === specialObjects[i].object) {
        -                return specialObjects[i].value;
        -            }
        -        }
        -
        -        return ascii.object.call(f, object, processed, indent);
        -    }
        -
        -    ascii.func = function (func) {
        -        return "function " + functionName(func) + "() {}";
        -    };
        -
        -    ascii.array = function (array, processed) {
        -        processed = processed || [];
        -        processed.push(array);
        -        var pieces = [];
        -        var i, l;
        -        l = (this.limitChildrenCount > 0) ?
        -            Math.min(this.limitChildrenCount, array.length) : array.length;
        -
        -        for (i = 0; i < l; ++i) {
        -            pieces.push(ascii(this, array[i], processed));
        -        }
        -
        -        if(l < array.length)
        -            pieces.push("[... " + (array.length - l) + " more elements]");
        -
        -        return "[" + pieces.join(", ") + "]";
        -    };
        -
        -    ascii.object = function (object, processed, indent) {
        -        processed = processed || [];
        -        processed.push(object);
        -        indent = indent || 0;
        -        var pieces = [], properties = samsam.keys(object).sort();
        -        var length = 3;
        -        var prop, str, obj, i, k, l;
        -        l = (this.limitChildrenCount > 0) ?
        -            Math.min(this.limitChildrenCount, properties.length) : properties.length;
        -
        -        for (i = 0; i < l; ++i) {
        -            prop = properties[i];
        -            obj = object[prop];
        -
        -            if (isCircular(obj, processed)) {
        -                str = "[Circular]";
        -            } else {
        -                str = ascii(this, obj, processed, indent + 2);
        -            }
        -
        -            str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str;
        -            length += str.length;
        -            pieces.push(str);
        -        }
        -
        -        var cons = constructorName(this, object);
        -        var prefix = cons ? "[" + cons + "] " : "";
        -        var is = "";
        -        for (i = 0, k = indent; i < k; ++i) { is += " "; }
        -
        -        if(l < properties.length)
        -            pieces.push("[... " + (properties.length - l) + " more elements]");
        -
        -        if (length + indent > 80) {
        -            return prefix + "{\n  " + is + pieces.join(",\n  " + is) + "\n" +
        -                is + "}";
        -        }
        -        return prefix + "{ " + pieces.join(", ") + " }";
        -    };
        -
        -    ascii.element = function (element) {
        -        var tagName = element.tagName.toLowerCase();
        -        var attrs = element.attributes, attr, pairs = [], attrName, i, l, val;
        -
        -        for (i = 0, l = attrs.length; i < l; ++i) {
        -            attr = attrs.item(i);
        -            attrName = attr.nodeName.toLowerCase().replace("html:", "");
        -            val = attr.nodeValue;
        -            if (attrName !== "contenteditable" || val !== "inherit") {
        -                if (!!val) { pairs.push(attrName + "=\"" + val + "\""); }
        -            }
        -        }
        -
        -        var formatted = "<" + tagName + (pairs.length > 0 ? " " : "");
        -        var content = element.innerHTML;
        -
        -        if (content.length > 20) {
        -            content = content.slice(0, 20) + "[...]";
        -        }
        -
        -        var res = formatted + pairs.join(" ") + ">" + content +
        -                "</" + tagName + ">";
        -
        -        return res.replace(/ contentEditable="inherit"/, "");
        -    };
        -
        -    function Formatio(options) {
        -        for (var opt in options) {
        -            this[opt] = options[opt];
        -        }
        -    }
        -
        -    Formatio.prototype = {
        -        functionName: functionName,
        -
        -        configure: function (options) {
        -            return new Formatio(options);
        -        },
        -
        -        constructorName: function (object) {
        -            return constructorName(this, object);
        -        },
        -
        -        ascii: function (object, processed, indent) {
        -            return ascii(this, object, processed, indent);
        -        }
        -    };
        -
        -    return Formatio.prototype;
        -});
        -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.lolex=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
        -(function (global){
        -/*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/
        -/*global global*/
        -/**
        - * @author Christian Johansen (christian@cjohansen.no) and contributors
        - * @license BSD
        - *
        - * Copyright (c) 2010-2014 Christian Johansen
        - */
        -
        -// node expects setTimeout/setInterval to return a fn object w/ .ref()/.unref()
        -// browsers, a number.
        -// see https://github.com/cjohansen/Sinon.JS/pull/436
        -var timeoutResult = setTimeout(function() {}, 0);
        -var addTimerReturnsObject = typeof timeoutResult === "object";
        -clearTimeout(timeoutResult);
        -
        -var NativeDate = Date;
        -var id = 1;
        -
        -/**
        - * Parse strings like "01:10:00" (meaning 1 hour, 10 minutes, 0 seconds) into
        - * number of milliseconds. This is used to support human-readable strings passed
        - * to clock.tick()
        - */
        -function parseTime(str) {
        -    if (!str) {
        -        return 0;
        -    }
        -
        -    var strings = str.split(":");
        -    var l = strings.length, i = l;
        -    var ms = 0, parsed;
        -
        -    if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
        -        throw new Error("tick only understands numbers and 'h:m:s'");
        -    }
        -
        -    while (i--) {
        -        parsed = parseInt(strings[i], 10);
        -
        -        if (parsed >= 60) {
        -            throw new Error("Invalid time " + str);
        -        }
        -
        -        ms += parsed * Math.pow(60, (l - i - 1));
        -    }
        -
        -    return ms * 1000;
        -}
        -
        -/**
        - * Used to grok the `now` parameter to createClock.
        - */
        -function getEpoch(epoch) {
        -    if (!epoch) { return 0; }
        -    if (typeof epoch.getTime === "function") { return epoch.getTime(); }
        -    if (typeof epoch === "number") { return epoch; }
        -    throw new TypeError("now should be milliseconds since UNIX epoch");
        -}
        -
        -function inRange(from, to, timer) {
        -    return timer && timer.callAt >= from && timer.callAt <= to;
        -}
        -
        -function mirrorDateProperties(target, source) {
        -    if (source.now) {
        -        target.now = function now() {
        -            return target.clock.now;
        -        };
        -    } else {
        -        delete target.now;
        -    }
        -
        -    if (source.toSource) {
        -        target.toSource = function toSource() {
        -            return source.toSource();
        -        };
        -    } else {
        -        delete target.toSource;
        -    }
        -
        -    target.toString = function toString() {
        -        return source.toString();
        -    };
        -
        -    target.prototype = source.prototype;
        -    target.parse = source.parse;
        -    target.UTC = source.UTC;
        -    target.prototype.toUTCString = source.prototype.toUTCString;
        -
        -    for (var prop in source) {
        -        if (source.hasOwnProperty(prop)) {
        -            target[prop] = source[prop];
        -        }
        -    }
        -
        -    return target;
        -}
        -
        -function createDate() {
        -    function ClockDate(year, month, date, hour, minute, second, ms) {
        -        // Defensive and verbose to avoid potential harm in passing
        -        // explicit undefined when user does not pass argument
        -        switch (arguments.length) {
        -        case 0:
        -            return new NativeDate(ClockDate.clock.now);
        -        case 1:
        -            return new NativeDate(year);
        -        case 2:
        -            return new NativeDate(year, month);
        -        case 3:
        -            return new NativeDate(year, month, date);
        -        case 4:
        -            return new NativeDate(year, month, date, hour);
        -        case 5:
        -            return new NativeDate(year, month, date, hour, minute);
        -        case 6:
        -            return new NativeDate(year, month, date, hour, minute, second);
        -        default:
        -            return new NativeDate(year, month, date, hour, minute, second, ms);
        -        }
        -    }
        -
        -    return mirrorDateProperties(ClockDate, NativeDate);
        -}
        -
        -function addTimer(clock, timer) {
        -    if (typeof timer.func === "undefined") {
        -        throw new Error("Callback must be provided to timer calls");
        -    }
        -
        -    if (!clock.timers) {
        -        clock.timers = {};
        -    }
        -
        -    timer.id = id++;
        -    timer.createdAt = clock.now;
        -    timer.callAt = clock.now + (timer.delay || 0);
        -
        -    clock.timers[timer.id] = timer;
        -
        -    if (addTimerReturnsObject) {
        -        return {
        -            id: timer.id,
        -            ref: function() {},
        -            unref: function() {}
        -        };
        -    }
        -    else {
        -        return timer.id;
        -    }
        -}
        -
        -function firstTimerInRange(clock, from, to) {
        -    var timers = clock.timers, timer = null;
        -
        -    for (var id in timers) {
        -        if (!inRange(from, to, timers[id])) {
        -            continue;
        -        }
        -
        -        if (!timer || ~compareTimers(timer, timers[id])) {
        -            timer = timers[id];
        -        }
        -    }
        -
        -    return timer;
        -}
        -
        -function compareTimers(a, b) {
        -    // Sort first by absolute timing
        -    if (a.callAt < b.callAt) {
        -        return -1;
        -    }
        -    if (a.callAt > b.callAt) {
        -        return 1;
        -    }
        -
        -    // Sort next by immediate, immediate timers take precedence
        -    if (a.immediate && !b.immediate) {
        -        return -1;
        -    }
        -    if (!a.immediate && b.immediate) {
        -        return 1;
        -    }
        -
        -    // Sort next by creation time, earlier-created timers take precedence
        -    if (a.createdAt < b.createdAt) {
        -        return -1;
        -    }
        -    if (a.createdAt > b.createdAt) {
        -        return 1;
        -    }
        -
        -    // Sort next by id, lower-id timers take precedence
        -    if (a.id < b.id) {
        -        return -1;
        -    }
        -    if (a.id > b.id) {
        -        return 1;
        -    }
        -
        -    // As timer ids are unique, no fallback `0` is necessary
        -}
        -
        -function callTimer(clock, timer) {
        -    if (typeof timer.interval == "number") {
        -        clock.timers[timer.id].callAt += timer.interval;
        -    } else {
        -        delete clock.timers[timer.id];
        -    }
        -
        -    try {
        -        if (typeof timer.func == "function") {
        -            timer.func.apply(null, timer.args);
        -        } else {
        -            eval(timer.func);
        -        }
        -    } catch (e) {
        -        var exception = e;
        -    }
        -
        -    if (!clock.timers[timer.id]) {
        -        if (exception) {
        -            throw exception;
        -        }
        -        return;
        -    }
        -
        -    if (exception) {
        -        throw exception;
        -    }
        -}
        -
        -function uninstall(clock, target) {
        -    var method;
        -
        -    for (var i = 0, l = clock.methods.length; i < l; i++) {
        -        method = clock.methods[i];
        -
        -        if (target[method].hadOwnProperty) {
        -            target[method] = clock["_" + method];
        -        } else {
        -            try {
        -                delete target[method];
        -            } catch (e) {}
        -        }
        -    }
        -
        -    // Prevent multiple executions which will completely remove these props
        -    clock.methods = [];
        -}
        -
        -function hijackMethod(target, method, clock) {
        -    clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(target, method);
        -    clock["_" + method] = target[method];
        -
        -    if (method == "Date") {
        -        var date = mirrorDateProperties(clock[method], target[method]);
        -        target[method] = date;
        -    } else {
        -        target[method] = function () {
        -            return clock[method].apply(clock, arguments);
        -        };
        -
        -        for (var prop in clock[method]) {
        -            if (clock[method].hasOwnProperty(prop)) {
        -                target[method][prop] = clock[method][prop];
        -            }
        -        }
        -    }
        -
        -    target[method].clock = clock;
        -}
        -
        -var timers = {
        -    setTimeout: setTimeout,
        -    clearTimeout: clearTimeout,
        -    setImmediate: (typeof setImmediate !== "undefined" ? setImmediate : undefined),
        -    clearImmediate: (typeof clearImmediate !== "undefined" ? clearImmediate: undefined),
        -    setInterval: setInterval,
        -    clearInterval: clearInterval,
        -    Date: Date
        -};
        -
        -var keys = Object.keys || function (obj) {
        -    var ks = [];
        -    for (var key in obj) {
        -        ks.push(key);
        -    }
        -    return ks;
        -};
        -
        -exports.timers = timers;
        -
        -var createClock = exports.createClock = function (now) {
        -    var clock = {
        -        now: getEpoch(now),
        -        timeouts: {},
        -        Date: createDate()
        -    };
        -
        -    clock.Date.clock = clock;
        -
        -    clock.setTimeout = function setTimeout(func, timeout) {
        -        return addTimer(clock, {
        -            func: func,
        -            args: Array.prototype.slice.call(arguments, 2),
        -            delay: timeout
        -        });
        -    };
        -
        -    clock.clearTimeout = function clearTimeout(timerId) {
        -        if (!timerId) {
        -            // null appears to be allowed in most browsers, and appears to be
        -            // relied upon by some libraries, like Bootstrap carousel
        -            return;
        -        }
        -        if (!clock.timers) {
        -            clock.timers = [];
        -        }
        -        // in Node, timerId is an object with .ref()/.unref(), and
        -        // its .id field is the actual timer id.
        -        if (typeof timerId === "object") {
        -            timerId = timerId.id
        -        }
        -        if (timerId in clock.timers) {
        -            delete clock.timers[timerId];
        -        }
        -    };
        -
        -    clock.setInterval = function setInterval(func, timeout) {
        -        return addTimer(clock, {
        -            func: func,
        -            args: Array.prototype.slice.call(arguments, 2),
        -            delay: timeout,
        -            interval: timeout
        -        });
        -    };
        -
        -    clock.clearInterval = function clearInterval(timerId) {
        -        clock.clearTimeout(timerId);
        -    };
        -
        -    clock.setImmediate = function setImmediate(func) {
        -        return addTimer(clock, {
        -            func: func,
        -            args: Array.prototype.slice.call(arguments, 1),
        -            immediate: true
        -        });
        -    };
        -
        -    clock.clearImmediate = function clearImmediate(timerId) {
        -        clock.clearTimeout(timerId);
        -    };
        -
        -    clock.tick = function tick(ms) {
        -        ms = typeof ms == "number" ? ms : parseTime(ms);
        -        var tickFrom = clock.now, tickTo = clock.now + ms, previous = clock.now;
        -        var timer = firstTimerInRange(clock, tickFrom, tickTo);
        -
        -        var firstException;
        -        while (timer && tickFrom <= tickTo) {
        -            if (clock.timers[timer.id]) {
        -                tickFrom = clock.now = timer.callAt;
        -                try {
        -                    callTimer(clock, timer);
        -                } catch (e) {
        -                    firstException = firstException || e;
        -                }
        -            }
        -
        -            timer = firstTimerInRange(clock, previous, tickTo);
        -            previous = tickFrom;
        -        }
        -
        -        clock.now = tickTo;
        -
        -        if (firstException) {
        -            throw firstException;
        -        }
        -
        -        return clock.now;
        -    };
        -
        -    clock.reset = function reset() {
        -        clock.timers = {};
        -    };
        -
        -    return clock;
        -};
        -
        -exports.install = function install(target, now, toFake) {
        -    if (typeof target === "number") {
        -        toFake = now;
        -        now = target;
        -        target = null;
        -    }
        -
        -    if (!target) {
        -        target = global;
        -    }
        -
        -    var clock = createClock(now);
        -
        -    clock.uninstall = function () {
        -        uninstall(clock, target);
        -    };
        -
        -    clock.methods = toFake || [];
        -
        -    if (clock.methods.length === 0) {
        -        clock.methods = keys(timers);
        -    }
        -
        -    for (var i = 0, l = clock.methods.length; i < l; i++) {
        -        hijackMethod(target, clock.methods[i], clock);
        -    }
        -
        -    return clock;
        -};
        -
        -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
        -},{}]},{},[1])(1)
        -});
        -  })();
        -  var define;
        -/**
        - * Sinon core utilities. For internal use only.
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -var sinon = (function () {
        -"use strict";
        -
        -    var sinon;
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require === "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        sinon = module.exports = require("./sinon/util/core");
        -        require("./sinon/extend");
        -        require("./sinon/typeOf");
        -        require("./sinon/times_in_words");
        -        require("./sinon/spy");
        -        require("./sinon/call");
        -        require("./sinon/behavior");
        -        require("./sinon/stub");
        -        require("./sinon/mock");
        -        require("./sinon/collection");
        -        require("./sinon/assert");
        -        require("./sinon/sandbox");
        -        require("./sinon/test");
        -        require("./sinon/test_case");
        -        require("./sinon/match");
        -        require("./sinon/format");
        -        require("./sinon/log_error");
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -        sinon = module.exports;
        -    } else {
        -        sinon = {};
        -    }
        -
        -    return sinon;
        -}());
        -
        -/**
        - * @depend ../../sinon.js
        - */
        -/**
        - * Sinon core utilities. For internal use only.
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -(function (sinon) {
        -    var div = typeof document != "undefined" && document.createElement("div");
        -    var hasOwn = Object.prototype.hasOwnProperty;
        -
        -    function isDOMNode(obj) {
        -        var success = false;
        -
        -        try {
        -            obj.appendChild(div);
        -            success = div.parentNode == obj;
        -        } catch (e) {
        -            return false;
        -        } finally {
        -            try {
        -                obj.removeChild(div);
        -            } catch (e) {
        -                // Remove failed, not much we can do about that
        -            }
        -        }
        -
        -        return success;
        -    }
        -
        -    function isElement(obj) {
        -        return div && obj && obj.nodeType === 1 && isDOMNode(obj);
        -    }
        -
        -    function isFunction(obj) {
        -        return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply);
        -    }
        -
        -    function isReallyNaN(val) {
        -        return typeof val === "number" && isNaN(val);
        -    }
        -
        -    function mirrorProperties(target, source) {
        -        for (var prop in source) {
        -            if (!hasOwn.call(target, prop)) {
        -                target[prop] = source[prop];
        -            }
        -        }
        -    }
        -
        -    function isRestorable(obj) {
        -        return typeof obj === "function" && typeof obj.restore === "function" && obj.restore.sinon;
        -    }
        -
        -    // Cheap way to detect if we have ES5 support.
        -    var hasES5Support = "keys" in Object;
        -
        -    function makeApi(sinon) {
        -        sinon.wrapMethod = function wrapMethod(object, property, method) {
        -            if (!object) {
        -                throw new TypeError("Should wrap property of object");
        -            }
        -
        -            if (typeof method != "function" && typeof method != "object") {
        -                throw new TypeError("Method wrapper should be a function or a property descriptor");
        -            }
        -
        -            function checkWrappedMethod(wrappedMethod) {
        -                if (!isFunction(wrappedMethod)) {
        -                    error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " +
        -                                        property + " as function");
        -                } else if (wrappedMethod.restore && wrappedMethod.restore.sinon) {
        -                    error = new TypeError("Attempted to wrap " + property + " which is already wrapped");
        -                } else if (wrappedMethod.calledBefore) {
        -                    var verb = !!wrappedMethod.returns ? "stubbed" : "spied on";
        -                    error = new TypeError("Attempted to wrap " + property + " which is already " + verb);
        -                }
        -
        -                if (error) {
        -                    if (wrappedMethod && wrappedMethod.stackTrace) {
        -                        error.stack += "\n--------------\n" + wrappedMethod.stackTrace;
        -                    }
        -                    throw error;
        -                }
        -            }
        -
        -            var error, wrappedMethod;
        -
        -            // IE 8 does not support hasOwnProperty on the window object and Firefox has a problem
        -            // when using hasOwn.call on objects from other frames.
        -            var owned = object.hasOwnProperty ? object.hasOwnProperty(property) : hasOwn.call(object, property);
        -
        -            if (hasES5Support) {
        -                var methodDesc = (typeof method == "function") ? {value: method} : method,
        -                    wrappedMethodDesc = sinon.getPropertyDescriptor(object, property),
        -                    i;
        -
        -                if (!wrappedMethodDesc) {
        -                    error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " +
        -                                        property + " as function");
        -                } else if (wrappedMethodDesc.restore && wrappedMethodDesc.restore.sinon) {
        -                    error = new TypeError("Attempted to wrap " + property + " which is already wrapped");
        -                }
        -                if (error) {
        -                    if (wrappedMethodDesc && wrappedMethodDesc.stackTrace) {
        -                        error.stack += "\n--------------\n" + wrappedMethodDesc.stackTrace;
        -                    }
        -                    throw error;
        -                }
        -
        -                var types = sinon.objectKeys(methodDesc);
        -                for (i = 0; i < types.length; i++) {
        -                    wrappedMethod = wrappedMethodDesc[types[i]];
        -                    checkWrappedMethod(wrappedMethod);
        -                }
        -
        -                mirrorProperties(methodDesc, wrappedMethodDesc);
        -                for (i = 0; i < types.length; i++) {
        -                    mirrorProperties(methodDesc[types[i]], wrappedMethodDesc[types[i]]);
        -                }
        -                Object.defineProperty(object, property, methodDesc);
        -            } else {
        -                wrappedMethod = object[property];
        -                checkWrappedMethod(wrappedMethod);
        -                object[property] = method;
        -                method.displayName = property;
        -            }
        -
        -            method.displayName = property;
        -
        -            // Set up a stack trace which can be used later to find what line of
        -            // code the original method was created on.
        -            method.stackTrace = (new Error("Stack Trace for original")).stack;
        -
        -            method.restore = function () {
        -                // For prototype properties try to reset by delete first.
        -                // If this fails (ex: localStorage on mobile safari) then force a reset
        -                // via direct assignment.
        -                if (!owned) {
        -                    // In some cases `delete` may throw an error
        -                    try {
        -                        delete object[property];
        -                    } catch (e) {}
        -                    // For native code functions `delete` fails without throwing an error
        -                    // on Chrome < 43, etc.
        -                } else if (hasES5Support) {
        -                    Object.defineProperty(object, property, wrappedMethodDesc);
        -                }
        -
        -                // Use strict equality comparison to check failures then force a reset
        -                // via direct assignment.
        -                if (object[property] === method) {
        -                    object[property] = wrappedMethod;
        -                }
        -            };
        -
        -            method.restore.sinon = true;
        -
        -            if (!hasES5Support) {
        -                mirrorProperties(method, wrappedMethod);
        -            }
        -
        -            return method;
        -        };
        -
        -        sinon.create = function create(proto) {
        -            var F = function () {};
        -            F.prototype = proto;
        -            return new F();
        -        };
        -
        -        sinon.deepEqual = function deepEqual(a, b) {
        -            if (sinon.match && sinon.match.isMatcher(a)) {
        -                return a.test(b);
        -            }
        -
        -            if (typeof a != "object" || typeof b != "object") {
        -                if (isReallyNaN(a) && isReallyNaN(b)) {
        -                    return true;
        -                } else {
        -                    return a === b;
        -                }
        -            }
        -
        -            if (isElement(a) || isElement(b)) {
        -                return a === b;
        -            }
        -
        -            if (a === b) {
        -                return true;
        -            }
        -
        -            if ((a === null && b !== null) || (a !== null && b === null)) {
        -                return false;
        -            }
        -
        -            if (a instanceof RegExp && b instanceof RegExp) {
        -                return (a.source === b.source) && (a.global === b.global) &&
        -                    (a.ignoreCase === b.ignoreCase) && (a.multiline === b.multiline);
        -            }
        -
        -            var aString = Object.prototype.toString.call(a);
        -            if (aString != Object.prototype.toString.call(b)) {
        -                return false;
        -            }
        -
        -            if (aString == "[object Date]") {
        -                return a.valueOf() === b.valueOf();
        -            }
        -
        -            var prop, aLength = 0, bLength = 0;
        -
        -            if (aString == "[object Array]" && a.length !== b.length) {
        -                return false;
        -            }
        -
        -            for (prop in a) {
        -                aLength += 1;
        -
        -                if (!(prop in b)) {
        -                    return false;
        -                }
        -
        -                if (!deepEqual(a[prop], b[prop])) {
        -                    return false;
        -                }
        -            }
        -
        -            for (prop in b) {
        -                bLength += 1;
        -            }
        -
        -            return aLength == bLength;
        -        };
        -
        -        sinon.functionName = function functionName(func) {
        -            var name = func.displayName || func.name;
        -
        -            // Use function decomposition as a last resort to get function
        -            // name. Does not rely on function decomposition to work - if it
        -            // doesn't debugging will be slightly less informative
        -            // (i.e. toString will say 'spy' rather than 'myFunc').
        -            if (!name) {
        -                var matches = func.toString().match(/function ([^\s\(]+)/);
        -                name = matches && matches[1];
        -            }
        -
        -            return name;
        -        };
        -
        -        sinon.functionToString = function toString() {
        -            if (this.getCall && this.callCount) {
        -                var thisValue, prop, i = this.callCount;
        -
        -                while (i--) {
        -                    thisValue = this.getCall(i).thisValue;
        -
        -                    for (prop in thisValue) {
        -                        if (thisValue[prop] === this) {
        -                            return prop;
        -                        }
        -                    }
        -                }
        -            }
        -
        -            return this.displayName || "sinon fake";
        -        };
        -
        -        sinon.objectKeys = function objectKeys(obj) {
        -            if (obj !== Object(obj)) {
        -                throw new TypeError("sinon.objectKeys called on a non-object");
        -            }
        -
        -            var keys = [];
        -            var key;
        -            for (key in obj) {
        -                if (hasOwn.call(obj, key)) {
        -                    keys.push(key);
        -                }
        -            }
        -
        -            return keys;
        -        };
        -
        -        sinon.getPropertyDescriptor = function getPropertyDescriptor(object, property) {
        -            var proto = object, descriptor;
        -            while (proto && !(descriptor = Object.getOwnPropertyDescriptor(proto, property))) {
        -                proto = Object.getPrototypeOf(proto);
        -            }
        -            return descriptor;
        -        }
        -
        -        sinon.getConfig = function (custom) {
        -            var config = {};
        -            custom = custom || {};
        -            var defaults = sinon.defaultConfig;
        -
        -            for (var prop in defaults) {
        -                if (defaults.hasOwnProperty(prop)) {
        -                    config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop];
        -                }
        -            }
        -
        -            return config;
        -        };
        -
        -        sinon.defaultConfig = {
        -            injectIntoThis: true,
        -            injectInto: null,
        -            properties: ["spy", "stub", "mock", "clock", "server", "requests"],
        -            useFakeTimers: true,
        -            useFakeServer: true
        -        };
        -
        -        sinon.timesInWords = function timesInWords(count) {
        -            return count == 1 && "once" ||
        -                count == 2 && "twice" ||
        -                count == 3 && "thrice" ||
        -                (count || 0) + " times";
        -        };
        -
        -        sinon.calledInOrder = function (spies) {
        -            for (var i = 1, l = spies.length; i < l; i++) {
        -                if (!spies[i - 1].calledBefore(spies[i]) || !spies[i].called) {
        -                    return false;
        -                }
        -            }
        -
        -            return true;
        -        };
        -
        -        sinon.orderByFirstCall = function (spies) {
        -            return spies.sort(function (a, b) {
        -                // uuid, won't ever be equal
        -                var aCall = a.getCall(0);
        -                var bCall = b.getCall(0);
        -                var aId = aCall && aCall.callId || -1;
        -                var bId = bCall && bCall.callId || -1;
        -
        -                return aId < bId ? -1 : 1;
        -            });
        -        };
        -
        -        sinon.createStubInstance = function (constructor) {
        -            if (typeof constructor !== "function") {
        -                throw new TypeError("The constructor should be a function.");
        -            }
        -            return sinon.stub(sinon.create(constructor.prototype));
        -        };
        -
        -        sinon.restore = function (object) {
        -            if (object !== null && typeof object === "object") {
        -                for (var prop in object) {
        -                    if (isRestorable(object[prop])) {
        -                        object[prop].restore();
        -                    }
        -                }
        -            } else if (isRestorable(object)) {
        -                object.restore();
        -            }
        -        };
        -
        -        return sinon;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports) {
        -        makeApi(exports);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        - * @depend util/core.js
        - */
        -
        -(function (sinon) {
        -    function makeApi(sinon) {
        -
        -        // Adapted from https://developer.mozilla.org/en/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
        -        var hasDontEnumBug = (function () {
        -            var obj = {
        -                constructor: function () {
        -                    return "0";
        -                },
        -                toString: function () {
        -                    return "1";
        -                },
        -                valueOf: function () {
        -                    return "2";
        -                },
        -                toLocaleString: function () {
        -                    return "3";
        -                },
        -                prototype: function () {
        -                    return "4";
        -                },
        -                isPrototypeOf: function () {
        -                    return "5";
        -                },
        -                propertyIsEnumerable: function () {
        -                    return "6";
        -                },
        -                hasOwnProperty: function () {
        -                    return "7";
        -                },
        -                length: function () {
        -                    return "8";
        -                },
        -                unique: function () {
        -                    return "9"
        -                }
        -            };
        -
        -            var result = [];
        -            for (var prop in obj) {
        -                result.push(obj[prop]());
        -            }
        -            return result.join("") !== "0123456789";
        -        })();
        -
        -        /* Public: Extend target in place with all (own) properties from sources in-order. Thus, last source will
        -         *         override properties in previous sources.
        -         *
        -         * target - The Object to extend
        -         * sources - Objects to copy properties from.
        -         *
        -         * Returns the extended target
        -         */
        -        function extend(target /*, sources */) {
        -            var sources = Array.prototype.slice.call(arguments, 1),
        -                source, i, prop;
        -
        -            for (i = 0; i < sources.length; i++) {
        -                source = sources[i];
        -
        -                for (prop in source) {
        -                    if (source.hasOwnProperty(prop)) {
        -                        target[prop] = source[prop];
        -                    }
        -                }
        -
        -                // Make sure we copy (own) toString method even when in JScript with DontEnum bug
        -                // See https://developer.mozilla.org/en/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
        -                if (hasDontEnumBug && source.hasOwnProperty("toString") && source.toString !== target.toString) {
        -                    target.toString = source.toString;
        -                }
        -            }
        -
        -            return target;
        -        };
        -
        -        sinon.extend = extend;
        -        return sinon.extend;
        -    }
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        - * @depend util/core.js
        - */
        -
        -(function (sinon) {
        -    function makeApi(sinon) {
        -
        -        function timesInWords(count) {
        -            switch (count) {
        -                case 1:
        -                    return "once";
        -                case 2:
        -                    return "twice";
        -                case 3:
        -                    return "thrice";
        -                default:
        -                    return (count || 0) + " times";
        -            }
        -        }
        -
        -        sinon.timesInWords = timesInWords;
        -        return sinon.timesInWords;
        -    }
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        - * @depend util/core.js
        - */
        -/**
        - * Format functions
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2014 Christian Johansen
        - */
        -
        -(function (sinon, formatio) {
        -    function makeApi(sinon) {
        -        function typeOf(value) {
        -            if (value === null) {
        -                return "null";
        -            } else if (value === undefined) {
        -                return "undefined";
        -            }
        -            var string = Object.prototype.toString.call(value);
        -            return string.substring(8, string.length - 1).toLowerCase();
        -        };
        -
        -        sinon.typeOf = typeOf;
        -        return sinon.typeOf;
        -    }
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(
        -    (typeof sinon == "object" && sinon || null),
        -    (typeof formatio == "object" && formatio)
        -));
        -
        -/**
        - * @depend util/core.js
        - * @depend typeOf.js
        - */
        -/*jslint eqeqeq: false, onevar: false, plusplus: false*/
        -/*global module, require, sinon*/
        -/**
        - * Match functions
        - *
        - * @author Maximilian Antoni (mail@maxantoni.de)
        - * @license BSD
        - *
        - * Copyright (c) 2012 Maximilian Antoni
        - */
        -
        -(function (sinon) {
        -    function makeApi(sinon) {
        -        function assertType(value, type, name) {
        -            var actual = sinon.typeOf(value);
        -            if (actual !== type) {
        -                throw new TypeError("Expected type of " + name + " to be " +
        -                    type + ", but was " + actual);
        -            }
        -        }
        -
        -        var matcher = {
        -            toString: function () {
        -                return this.message;
        -            }
        -        };
        -
        -        function isMatcher(object) {
        -            return matcher.isPrototypeOf(object);
        -        }
        -
        -        function matchObject(expectation, actual) {
        -            if (actual === null || actual === undefined) {
        -                return false;
        -            }
        -            for (var key in expectation) {
        -                if (expectation.hasOwnProperty(key)) {
        -                    var exp = expectation[key];
        -                    var act = actual[key];
        -                    if (match.isMatcher(exp)) {
        -                        if (!exp.test(act)) {
        -                            return false;
        -                        }
        -                    } else if (sinon.typeOf(exp) === "object") {
        -                        if (!matchObject(exp, act)) {
        -                            return false;
        -                        }
        -                    } else if (!sinon.deepEqual(exp, act)) {
        -                        return false;
        -                    }
        -                }
        -            }
        -            return true;
        -        }
        -
        -        matcher.or = function (m2) {
        -            if (!arguments.length) {
        -                throw new TypeError("Matcher expected");
        -            } else if (!isMatcher(m2)) {
        -                m2 = match(m2);
        -            }
        -            var m1 = this;
        -            var or = sinon.create(matcher);
        -            or.test = function (actual) {
        -                return m1.test(actual) || m2.test(actual);
        -            };
        -            or.message = m1.message + ".or(" + m2.message + ")";
        -            return or;
        -        };
        -
        -        matcher.and = function (m2) {
        -            if (!arguments.length) {
        -                throw new TypeError("Matcher expected");
        -            } else if (!isMatcher(m2)) {
        -                m2 = match(m2);
        -            }
        -            var m1 = this;
        -            var and = sinon.create(matcher);
        -            and.test = function (actual) {
        -                return m1.test(actual) && m2.test(actual);
        -            };
        -            and.message = m1.message + ".and(" + m2.message + ")";
        -            return and;
        -        };
        -
        -        var match = function (expectation, message) {
        -            var m = sinon.create(matcher);
        -            var type = sinon.typeOf(expectation);
        -            switch (type) {
        -            case "object":
        -                if (typeof expectation.test === "function") {
        -                    m.test = function (actual) {
        -                        return expectation.test(actual) === true;
        -                    };
        -                    m.message = "match(" + sinon.functionName(expectation.test) + ")";
        -                    return m;
        -                }
        -                var str = [];
        -                for (var key in expectation) {
        -                    if (expectation.hasOwnProperty(key)) {
        -                        str.push(key + ": " + expectation[key]);
        -                    }
        -                }
        -                m.test = function (actual) {
        -                    return matchObject(expectation, actual);
        -                };
        -                m.message = "match(" + str.join(", ") + ")";
        -                break;
        -            case "number":
        -                m.test = function (actual) {
        -                    return expectation == actual;
        -                };
        -                break;
        -            case "string":
        -                m.test = function (actual) {
        -                    if (typeof actual !== "string") {
        -                        return false;
        -                    }
        -                    return actual.indexOf(expectation) !== -1;
        -                };
        -                m.message = "match(\"" + expectation + "\")";
        -                break;
        -            case "regexp":
        -                m.test = function (actual) {
        -                    if (typeof actual !== "string") {
        -                        return false;
        -                    }
        -                    return expectation.test(actual);
        -                };
        -                break;
        -            case "function":
        -                m.test = expectation;
        -                if (message) {
        -                    m.message = message;
        -                } else {
        -                    m.message = "match(" + sinon.functionName(expectation) + ")";
        -                }
        -                break;
        -            default:
        -                m.test = function (actual) {
        -                    return sinon.deepEqual(expectation, actual);
        -                };
        -            }
        -            if (!m.message) {
        -                m.message = "match(" + expectation + ")";
        -            }
        -            return m;
        -        };
        -
        -        match.isMatcher = isMatcher;
        -
        -        match.any = match(function () {
        -            return true;
        -        }, "any");
        -
        -        match.defined = match(function (actual) {
        -            return actual !== null && actual !== undefined;
        -        }, "defined");
        -
        -        match.truthy = match(function (actual) {
        -            return !!actual;
        -        }, "truthy");
        -
        -        match.falsy = match(function (actual) {
        -            return !actual;
        -        }, "falsy");
        -
        -        match.same = function (expectation) {
        -            return match(function (actual) {
        -                return expectation === actual;
        -            }, "same(" + expectation + ")");
        -        };
        -
        -        match.typeOf = function (type) {
        -            assertType(type, "string", "type");
        -            return match(function (actual) {
        -                return sinon.typeOf(actual) === type;
        -            }, "typeOf(\"" + type + "\")");
        -        };
        -
        -        match.instanceOf = function (type) {
        -            assertType(type, "function", "type");
        -            return match(function (actual) {
        -                return actual instanceof type;
        -            }, "instanceOf(" + sinon.functionName(type) + ")");
        -        };
        -
        -        function createPropertyMatcher(propertyTest, messagePrefix) {
        -            return function (property, value) {
        -                assertType(property, "string", "property");
        -                var onlyProperty = arguments.length === 1;
        -                var message = messagePrefix + "(\"" + property + "\"";
        -                if (!onlyProperty) {
        -                    message += ", " + value;
        -                }
        -                message += ")";
        -                return match(function (actual) {
        -                    if (actual === undefined || actual === null ||
        -                            !propertyTest(actual, property)) {
        -                        return false;
        -                    }
        -                    return onlyProperty || sinon.deepEqual(value, actual[property]);
        -                }, message);
        -            };
        -        }
        -
        -        match.has = createPropertyMatcher(function (actual, property) {
        -            if (typeof actual === "object") {
        -                return property in actual;
        -            }
        -            return actual[property] !== undefined;
        -        }, "has");
        -
        -        match.hasOwn = createPropertyMatcher(function (actual, property) {
        -            return actual.hasOwnProperty(property);
        -        }, "hasOwn");
        -
        -        match.bool = match.typeOf("boolean");
        -        match.number = match.typeOf("number");
        -        match.string = match.typeOf("string");
        -        match.object = match.typeOf("object");
        -        match.func = match.typeOf("function");
        -        match.array = match.typeOf("array");
        -        match.regexp = match.typeOf("regexp");
        -        match.date = match.typeOf("date");
        -
        -        sinon.match = match;
        -        return match;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        require("./typeOf");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        - * @depend util/core.js
        - */
        -/**
        - * Format functions
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2014 Christian Johansen
        - */
        -
        -(function (sinon, formatio) {
        -    function makeApi(sinon) {
        -        function valueFormatter(value) {
        -            return "" + value;
        -        }
        -
        -        function getFormatioFormatter() {
        -            var formatter = formatio.configure({
        -                    quoteStrings: false,
        -                    limitChildrenCount: 250
        -                });
        -
        -            function format() {
        -                return formatter.ascii.apply(formatter, arguments);
        -            };
        -
        -            return format;
        -        }
        -
        -        function getNodeFormatter(value) {
        -            function format(value) {
        -                return typeof value == "object" && value.toString === Object.prototype.toString ? util.inspect(value) : value;
        -            };
        -
        -            try {
        -                var util = require("util");
        -            } catch (e) {
        -                /* Node, but no util module - would be very old, but better safe than sorry */
        -            }
        -
        -            return util ? format : valueFormatter;
        -        }
        -
        -        var isNode = typeof module !== "undefined" && module.exports && typeof require == "function",
        -            formatter;
        -
        -        if (isNode) {
        -            try {
        -                formatio = require("formatio");
        -            } catch (e) {}
        -        }
        -
        -        if (formatio) {
        -            formatter = getFormatioFormatter()
        -        } else if (isNode) {
        -            formatter = getNodeFormatter();
        -        } else {
        -            formatter = valueFormatter;
        -        }
        -
        -        sinon.format = formatter;
        -        return sinon.format;
        -    }
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(
        -    (typeof sinon == "object" && sinon || null),
        -    (typeof formatio == "object" && formatio)
        -));
        -
        -/**
        -  * @depend util/core.js
        -  * @depend match.js
        -  * @depend format.js
        -  */
        -/**
        -  * Spy calls
        -  *
        -  * @author Christian Johansen (christian@cjohansen.no)
        -  * @author Maximilian Antoni (mail@maxantoni.de)
        -  * @license BSD
        -  *
        -  * Copyright (c) 2010-2013 Christian Johansen
        -  * Copyright (c) 2013 Maximilian Antoni
        -  */
        -
        -(function (sinon) {
        -    function makeApi(sinon) {
        -        function throwYieldError(proxy, text, args) {
        -            var msg = sinon.functionName(proxy) + text;
        -            if (args.length) {
        -                msg += " Received [" + slice.call(args).join(", ") + "]";
        -            }
        -            throw new Error(msg);
        -        }
        -
        -        var slice = Array.prototype.slice;
        -
        -        var callProto = {
        -            calledOn: function calledOn(thisValue) {
        -                if (sinon.match && sinon.match.isMatcher(thisValue)) {
        -                    return thisValue.test(this.thisValue);
        -                }
        -                return this.thisValue === thisValue;
        -            },
        -
        -            calledWith: function calledWith() {
        -                var l = arguments.length;
        -                if (l > this.args.length) {
        -                    return false;
        -                }
        -                for (var i = 0; i < l; i += 1) {
        -                    if (!sinon.deepEqual(arguments[i], this.args[i])) {
        -                        return false;
        -                    }
        -                }
        -
        -                return true;
        -            },
        -
        -            calledWithMatch: function calledWithMatch() {
        -                var l = arguments.length;
        -                if (l > this.args.length) {
        -                    return false;
        -                }
        -                for (var i = 0; i < l; i += 1) {
        -                    var actual = this.args[i];
        -                    var expectation = arguments[i];
        -                    if (!sinon.match || !sinon.match(expectation).test(actual)) {
        -                        return false;
        -                    }
        -                }
        -                return true;
        -            },
        -
        -            calledWithExactly: function calledWithExactly() {
        -                return arguments.length == this.args.length &&
        -                    this.calledWith.apply(this, arguments);
        -            },
        -
        -            notCalledWith: function notCalledWith() {
        -                return !this.calledWith.apply(this, arguments);
        -            },
        -
        -            notCalledWithMatch: function notCalledWithMatch() {
        -                return !this.calledWithMatch.apply(this, arguments);
        -            },
        -
        -            returned: function returned(value) {
        -                return sinon.deepEqual(value, this.returnValue);
        -            },
        -
        -            threw: function threw(error) {
        -                if (typeof error === "undefined" || !this.exception) {
        -                    return !!this.exception;
        -                }
        -
        -                return this.exception === error || this.exception.name === error;
        -            },
        -
        -            calledWithNew: function calledWithNew() {
        -                return this.proxy.prototype && this.thisValue instanceof this.proxy;
        -            },
        -
        -            calledBefore: function (other) {
        -                return this.callId < other.callId;
        -            },
        -
        -            calledAfter: function (other) {
        -                return this.callId > other.callId;
        -            },
        -
        -            callArg: function (pos) {
        -                this.args[pos]();
        -            },
        -
        -            callArgOn: function (pos, thisValue) {
        -                this.args[pos].apply(thisValue);
        -            },
        -
        -            callArgWith: function (pos) {
        -                this.callArgOnWith.apply(this, [pos, null].concat(slice.call(arguments, 1)));
        -            },
        -
        -            callArgOnWith: function (pos, thisValue) {
        -                var args = slice.call(arguments, 2);
        -                this.args[pos].apply(thisValue, args);
        -            },
        -
        -            yield: function () {
        -                this.yieldOn.apply(this, [null].concat(slice.call(arguments, 0)));
        -            },
        -
        -            yieldOn: function (thisValue) {
        -                var args = this.args;
        -                for (var i = 0, l = args.length; i < l; ++i) {
        -                    if (typeof args[i] === "function") {
        -                        args[i].apply(thisValue, slice.call(arguments, 1));
        -                        return;
        -                    }
        -                }
        -                throwYieldError(this.proxy, " cannot yield since no callback was passed.", args);
        -            },
        -
        -            yieldTo: function (prop) {
        -                this.yieldToOn.apply(this, [prop, null].concat(slice.call(arguments, 1)));
        -            },
        -
        -            yieldToOn: function (prop, thisValue) {
        -                var args = this.args;
        -                for (var i = 0, l = args.length; i < l; ++i) {
        -                    if (args[i] && typeof args[i][prop] === "function") {
        -                        args[i][prop].apply(thisValue, slice.call(arguments, 2));
        -                        return;
        -                    }
        -                }
        -                throwYieldError(this.proxy, " cannot yield to '" + prop +
        -                    "' since no callback was passed.", args);
        -            },
        -
        -            toString: function () {
        -                var callStr = this.proxy.toString() + "(";
        -                var args = [];
        -
        -                for (var i = 0, l = this.args.length; i < l; ++i) {
        -                    args.push(sinon.format(this.args[i]));
        -                }
        -
        -                callStr = callStr + args.join(", ") + ")";
        -
        -                if (typeof this.returnValue != "undefined") {
        -                    callStr += " => " + sinon.format(this.returnValue);
        -                }
        -
        -                if (this.exception) {
        -                    callStr += " !" + this.exception.name;
        -
        -                    if (this.exception.message) {
        -                        callStr += "(" + this.exception.message + ")";
        -                    }
        -                }
        -
        -                return callStr;
        -            }
        -        };
        -
        -        callProto.invokeCallback = callProto.yield;
        -
        -        function createSpyCall(spy, thisValue, args, returnValue, exception, id) {
        -            if (typeof id !== "number") {
        -                throw new TypeError("Call id is not a number");
        -            }
        -            var proxyCall = sinon.create(callProto);
        -            proxyCall.proxy = spy;
        -            proxyCall.thisValue = thisValue;
        -            proxyCall.args = args;
        -            proxyCall.returnValue = returnValue;
        -            proxyCall.exception = exception;
        -            proxyCall.callId = id;
        -
        -            return proxyCall;
        -        }
        -        createSpyCall.toString = callProto.toString; // used by mocks
        -
        -        sinon.spyCall = createSpyCall;
        -        return createSpyCall;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        require("./match");
        -        require("./format");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        -  * @depend times_in_words.js
        -  * @depend util/core.js
        -  * @depend extend.js
        -  * @depend call.js
        -  * @depend format.js
        -  */
        -/**
        -  * Spy functions
        -  *
        -  * @author Christian Johansen (christian@cjohansen.no)
        -  * @license BSD
        -  *
        -  * Copyright (c) 2010-2013 Christian Johansen
        -  */
        -
        -(function (sinon) {
        -
        -    function makeApi(sinon) {
        -        var push = Array.prototype.push;
        -        var slice = Array.prototype.slice;
        -        var callId = 0;
        -
        -        function spy(object, property, types) {
        -            if (!property && typeof object == "function") {
        -                return spy.create(object);
        -            }
        -
        -            if (!object && !property) {
        -                return spy.create(function () { });
        -            }
        -
        -            if (types) {
        -                var methodDesc = sinon.getPropertyDescriptor(object, property);
        -                for (var i = 0; i < types.length; i++) {
        -                    methodDesc[types[i]] = spy.create(methodDesc[types[i]]);
        -                }
        -                return sinon.wrapMethod(object, property, methodDesc);
        -            } else {
        -                var method = object[property];
        -                return sinon.wrapMethod(object, property, spy.create(method));
        -            }
        -        }
        -
        -        function matchingFake(fakes, args, strict) {
        -            if (!fakes) {
        -                return;
        -            }
        -
        -            for (var i = 0, l = fakes.length; i < l; i++) {
        -                if (fakes[i].matches(args, strict)) {
        -                    return fakes[i];
        -                }
        -            }
        -        }
        -
        -        function incrementCallCount() {
        -            this.called = true;
        -            this.callCount += 1;
        -            this.notCalled = false;
        -            this.calledOnce = this.callCount == 1;
        -            this.calledTwice = this.callCount == 2;
        -            this.calledThrice = this.callCount == 3;
        -        }
        -
        -        function createCallProperties() {
        -            this.firstCall = this.getCall(0);
        -            this.secondCall = this.getCall(1);
        -            this.thirdCall = this.getCall(2);
        -            this.lastCall = this.getCall(this.callCount - 1);
        -        }
        -
        -        var vars = "a,b,c,d,e,f,g,h,i,j,k,l";
        -        function createProxy(func, proxyLength) {
        -            // Retain the function length:
        -            var p;
        -            if (proxyLength) {
        -                eval("p = (function proxy(" + vars.substring(0, proxyLength * 2 - 1) +
        -                    ") { return p.invoke(func, this, slice.call(arguments)); });");
        -            } else {
        -                p = function proxy() {
        -                    return p.invoke(func, this, slice.call(arguments));
        -                };
        -            }
        -            p.isSinonProxy = true;
        -            return p;
        -        }
        -
        -        var uuid = 0;
        -
        -        // Public API
        -        var spyApi = {
        -            reset: function () {
        -                if (this.invoking) {
        -                    var err = new Error("Cannot reset Sinon function while invoking it. " +
        -                                        "Move the call to .reset outside of the callback.");
        -                    err.name = "InvalidResetException";
        -                    throw err;
        -                }
        -
        -                this.called = false;
        -                this.notCalled = true;
        -                this.calledOnce = false;
        -                this.calledTwice = false;
        -                this.calledThrice = false;
        -                this.callCount = 0;
        -                this.firstCall = null;
        -                this.secondCall = null;
        -                this.thirdCall = null;
        -                this.lastCall = null;
        -                this.args = [];
        -                this.returnValues = [];
        -                this.thisValues = [];
        -                this.exceptions = [];
        -                this.callIds = [];
        -                if (this.fakes) {
        -                    for (var i = 0; i < this.fakes.length; i++) {
        -                        this.fakes[i].reset();
        -                    }
        -                }
        -
        -                return this;
        -            },
        -
        -            create: function create(func, spyLength) {
        -                var name;
        -
        -                if (typeof func != "function") {
        -                    func = function () { };
        -                } else {
        -                    name = sinon.functionName(func);
        -                }
        -
        -                if (!spyLength) {
        -                    spyLength = func.length;
        -                }
        -
        -                var proxy = createProxy(func, spyLength);
        -
        -                sinon.extend(proxy, spy);
        -                delete proxy.create;
        -                sinon.extend(proxy, func);
        -
        -                proxy.reset();
        -                proxy.prototype = func.prototype;
        -                proxy.displayName = name || "spy";
        -                proxy.toString = sinon.functionToString;
        -                proxy.instantiateFake = sinon.spy.create;
        -                proxy.id = "spy#" + uuid++;
        -
        -                return proxy;
        -            },
        -
        -            invoke: function invoke(func, thisValue, args) {
        -                var matching = matchingFake(this.fakes, args);
        -                var exception, returnValue;
        -
        -                incrementCallCount.call(this);
        -                push.call(this.thisValues, thisValue);
        -                push.call(this.args, args);
        -                push.call(this.callIds, callId++);
        -
        -                // Make call properties available from within the spied function:
        -                createCallProperties.call(this);
        -
        -                try {
        -                    this.invoking = true;
        -
        -                    if (matching) {
        -                        returnValue = matching.invoke(func, thisValue, args);
        -                    } else {
        -                        returnValue = (this.func || func).apply(thisValue, args);
        -                    }
        -
        -                    var thisCall = this.getCall(this.callCount - 1);
        -                    if (thisCall.calledWithNew() && typeof returnValue !== "object") {
        -                        returnValue = thisValue;
        -                    }
        -                } catch (e) {
        -                    exception = e;
        -                } finally {
        -                    delete this.invoking;
        -                }
        -
        -                push.call(this.exceptions, exception);
        -                push.call(this.returnValues, returnValue);
        -
        -                // Make return value and exception available in the calls:
        -                createCallProperties.call(this);
        -
        -                if (exception !== undefined) {
        -                    throw exception;
        -                }
        -
        -                return returnValue;
        -            },
        -
        -            named: function named(name) {
        -                this.displayName = name;
        -                return this;
        -            },
        -
        -            getCall: function getCall(i) {
        -                if (i < 0 || i >= this.callCount) {
        -                    return null;
        -                }
        -
        -                return sinon.spyCall(this, this.thisValues[i], this.args[i],
        -                                        this.returnValues[i], this.exceptions[i],
        -                                        this.callIds[i]);
        -            },
        -
        -            getCalls: function () {
        -                var calls = [];
        -                var i;
        -
        -                for (i = 0; i < this.callCount; i++) {
        -                    calls.push(this.getCall(i));
        -                }
        -
        -                return calls;
        -            },
        -
        -            calledBefore: function calledBefore(spyFn) {
        -                if (!this.called) {
        -                    return false;
        -                }
        -
        -                if (!spyFn.called) {
        -                    return true;
        -                }
        -
        -                return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1];
        -            },
        -
        -            calledAfter: function calledAfter(spyFn) {
        -                if (!this.called || !spyFn.called) {
        -                    return false;
        -                }
        -
        -                return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1];
        -            },
        -
        -            withArgs: function () {
        -                var args = slice.call(arguments);
        -
        -                if (this.fakes) {
        -                    var match = matchingFake(this.fakes, args, true);
        -
        -                    if (match) {
        -                        return match;
        -                    }
        -                } else {
        -                    this.fakes = [];
        -                }
        -
        -                var original = this;
        -                var fake = this.instantiateFake();
        -                fake.matchingAguments = args;
        -                fake.parent = this;
        -                push.call(this.fakes, fake);
        -
        -                fake.withArgs = function () {
        -                    return original.withArgs.apply(original, arguments);
        -                };
        -
        -                for (var i = 0; i < this.args.length; i++) {
        -                    if (fake.matches(this.args[i])) {
        -                        incrementCallCount.call(fake);
        -                        push.call(fake.thisValues, this.thisValues[i]);
        -                        push.call(fake.args, this.args[i]);
        -                        push.call(fake.returnValues, this.returnValues[i]);
        -                        push.call(fake.exceptions, this.exceptions[i]);
        -                        push.call(fake.callIds, this.callIds[i]);
        -                    }
        -                }
        -                createCallProperties.call(fake);
        -
        -                return fake;
        -            },
        -
        -            matches: function (args, strict) {
        -                var margs = this.matchingAguments;
        -
        -                if (margs.length <= args.length &&
        -                    sinon.deepEqual(margs, args.slice(0, margs.length))) {
        -                    return !strict || margs.length == args.length;
        -                }
        -            },
        -
        -            printf: function (format) {
        -                var spy = this;
        -                var args = slice.call(arguments, 1);
        -                var formatter;
        -
        -                return (format || "").replace(/%(.)/g, function (match, specifyer) {
        -                    formatter = spyApi.formatters[specifyer];
        -
        -                    if (typeof formatter == "function") {
        -                        return formatter.call(null, spy, args);
        -                    } else if (!isNaN(parseInt(specifyer, 10))) {
        -                        return sinon.format(args[specifyer - 1]);
        -                    }
        -
        -                    return "%" + specifyer;
        -                });
        -            }
        -        };
        -
        -        function delegateToCalls(method, matchAny, actual, notCalled) {
        -            spyApi[method] = function () {
        -                if (!this.called) {
        -                    if (notCalled) {
        -                        return notCalled.apply(this, arguments);
        -                    }
        -                    return false;
        -                }
        -
        -                var currentCall;
        -                var matches = 0;
        -
        -                for (var i = 0, l = this.callCount; i < l; i += 1) {
        -                    currentCall = this.getCall(i);
        -
        -                    if (currentCall[actual || method].apply(currentCall, arguments)) {
        -                        matches += 1;
        -
        -                        if (matchAny) {
        -                            return true;
        -                        }
        -                    }
        -                }
        -
        -                return matches === this.callCount;
        -            };
        -        }
        -
        -        delegateToCalls("calledOn", true);
        -        delegateToCalls("alwaysCalledOn", false, "calledOn");
        -        delegateToCalls("calledWith", true);
        -        delegateToCalls("calledWithMatch", true);
        -        delegateToCalls("alwaysCalledWith", false, "calledWith");
        -        delegateToCalls("alwaysCalledWithMatch", false, "calledWithMatch");
        -        delegateToCalls("calledWithExactly", true);
        -        delegateToCalls("alwaysCalledWithExactly", false, "calledWithExactly");
        -        delegateToCalls("neverCalledWith", false, "notCalledWith", function () {
        -            return true;
        -        });
        -        delegateToCalls("neverCalledWithMatch", false, "notCalledWithMatch", function () {
        -            return true;
        -        });
        -        delegateToCalls("threw", true);
        -        delegateToCalls("alwaysThrew", false, "threw");
        -        delegateToCalls("returned", true);
        -        delegateToCalls("alwaysReturned", false, "returned");
        -        delegateToCalls("calledWithNew", true);
        -        delegateToCalls("alwaysCalledWithNew", false, "calledWithNew");
        -        delegateToCalls("callArg", false, "callArgWith", function () {
        -            throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");
        -        });
        -        spyApi.callArgWith = spyApi.callArg;
        -        delegateToCalls("callArgOn", false, "callArgOnWith", function () {
        -            throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");
        -        });
        -        spyApi.callArgOnWith = spyApi.callArgOn;
        -        delegateToCalls("yield", false, "yield", function () {
        -            throw new Error(this.toString() + " cannot yield since it was not yet invoked.");
        -        });
        -        // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode.
        -        spyApi.invokeCallback = spyApi.yield;
        -        delegateToCalls("yieldOn", false, "yieldOn", function () {
        -            throw new Error(this.toString() + " cannot yield since it was not yet invoked.");
        -        });
        -        delegateToCalls("yieldTo", false, "yieldTo", function (property) {
        -            throw new Error(this.toString() + " cannot yield to '" + property +
        -                "' since it was not yet invoked.");
        -        });
        -        delegateToCalls("yieldToOn", false, "yieldToOn", function (property) {
        -            throw new Error(this.toString() + " cannot yield to '" + property +
        -                "' since it was not yet invoked.");
        -        });
        -
        -        spyApi.formatters = {
        -            c: function (spy) {
        -                return sinon.timesInWords(spy.callCount);
        -            },
        -
        -            n: function (spy) {
        -                return spy.toString();
        -            },
        -
        -            C: function (spy) {
        -                var calls = [];
        -
        -                for (var i = 0, l = spy.callCount; i < l; ++i) {
        -                    var stringifiedCall = "    " + spy.getCall(i).toString();
        -                    if (/\n/.test(calls[i - 1])) {
        -                        stringifiedCall = "\n" + stringifiedCall;
        -                    }
        -                    push.call(calls, stringifiedCall);
        -                }
        -
        -                return calls.length > 0 ? "\n" + calls.join("\n") : "";
        -            },
        -
        -            t: function (spy) {
        -                var objects = [];
        -
        -                for (var i = 0, l = spy.callCount; i < l; ++i) {
        -                    push.call(objects, sinon.format(spy.thisValues[i]));
        -                }
        -
        -                return objects.join(", ");
        -            },
        -
        -            "*": function (spy, args) {
        -                var formatted = [];
        -
        -                for (var i = 0, l = args.length; i < l; ++i) {
        -                    push.call(formatted, sinon.format(args[i]));
        -                }
        -
        -                return formatted.join(", ");
        -            }
        -        };
        -
        -        sinon.extend(spy, spyApi);
        -
        -        spy.spyCall = sinon.spyCall;
        -        sinon.spy = spy;
        -
        -        return spy;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        require("./call");
        -        require("./extend");
        -        require("./times_in_words");
        -        require("./format");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        - * @depend util/core.js
        - * @depend extend.js
        - */
        -/**
        - * Stub behavior
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @author Tim Fischbach (mail@timfischbach.de)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -(function (sinon) {
        -    var slice = Array.prototype.slice;
        -    var join = Array.prototype.join;
        -    var useLeftMostCallback = -1;
        -    var useRightMostCallback = -2;
        -
        -    var nextTick = (function () {
        -        if (typeof process === "object" && typeof process.nextTick === "function") {
        -            return process.nextTick;
        -        } else if (typeof setImmediate === "function") {
        -            return setImmediate;
        -        } else {
        -            return function (callback) {
        -                setTimeout(callback, 0);
        -            };
        -        }
        -    })();
        -
        -    function throwsException(error, message) {
        -        if (typeof error == "string") {
        -            this.exception = new Error(message || "");
        -            this.exception.name = error;
        -        } else if (!error) {
        -            this.exception = new Error("Error");
        -        } else {
        -            this.exception = error;
        -        }
        -
        -        return this;
        -    }
        -
        -    function getCallback(behavior, args) {
        -        var callArgAt = behavior.callArgAt;
        -
        -        if (callArgAt >= 0) {
        -            return args[callArgAt];
        -        }
        -
        -        var argumentList;
        -
        -        if (callArgAt === useLeftMostCallback) {
        -            argumentList = args;
        -        }
        -
        -        if (callArgAt === useRightMostCallback) {
        -            argumentList = slice.call(args).reverse();
        -        }
        -
        -        var callArgProp = behavior.callArgProp;
        -
        -        for (var i = 0, l = argumentList.length; i < l; ++i) {
        -            if (!callArgProp && typeof argumentList[i] == "function") {
        -                return argumentList[i];
        -            }
        -
        -            if (callArgProp && argumentList[i] &&
        -                typeof argumentList[i][callArgProp] == "function") {
        -                return argumentList[i][callArgProp];
        -            }
        -        }
        -
        -        return null;
        -    }
        -
        -    function makeApi(sinon) {
        -        function getCallbackError(behavior, func, args) {
        -            if (behavior.callArgAt < 0) {
        -                var msg;
        -
        -                if (behavior.callArgProp) {
        -                    msg = sinon.functionName(behavior.stub) +
        -                        " expected to yield to '" + behavior.callArgProp +
        -                        "', but no object with such a property was passed.";
        -                } else {
        -                    msg = sinon.functionName(behavior.stub) +
        -                        " expected to yield, but no callback was passed.";
        -                }
        -
        -                if (args.length > 0) {
        -                    msg += " Received [" + join.call(args, ", ") + "]";
        -                }
        -
        -                return msg;
        -            }
        -
        -            return "argument at index " + behavior.callArgAt + " is not a function: " + func;
        -        }
        -
        -        function callCallback(behavior, args) {
        -            if (typeof behavior.callArgAt == "number") {
        -                var func = getCallback(behavior, args);
        -
        -                if (typeof func != "function") {
        -                    throw new TypeError(getCallbackError(behavior, func, args));
        -                }
        -
        -                if (behavior.callbackAsync) {
        -                    nextTick(function () {
        -                        func.apply(behavior.callbackContext, behavior.callbackArguments);
        -                    });
        -                } else {
        -                    func.apply(behavior.callbackContext, behavior.callbackArguments);
        -                }
        -            }
        -        }
        -
        -        var proto = {
        -            create: function create(stub) {
        -                var behavior = sinon.extend({}, sinon.behavior);
        -                delete behavior.create;
        -                behavior.stub = stub;
        -
        -                return behavior;
        -            },
        -
        -            isPresent: function isPresent() {
        -                return (typeof this.callArgAt == "number" ||
        -                        this.exception ||
        -                        typeof this.returnArgAt == "number" ||
        -                        this.returnThis ||
        -                        this.returnValueDefined);
        -            },
        -
        -            invoke: function invoke(context, args) {
        -                callCallback(this, args);
        -
        -                if (this.exception) {
        -                    throw this.exception;
        -                } else if (typeof this.returnArgAt == "number") {
        -                    return args[this.returnArgAt];
        -                } else if (this.returnThis) {
        -                    return context;
        -                }
        -
        -                return this.returnValue;
        -            },
        -
        -            onCall: function onCall(index) {
        -                return this.stub.onCall(index);
        -            },
        -
        -            onFirstCall: function onFirstCall() {
        -                return this.stub.onFirstCall();
        -            },
        -
        -            onSecondCall: function onSecondCall() {
        -                return this.stub.onSecondCall();
        -            },
        -
        -            onThirdCall: function onThirdCall() {
        -                return this.stub.onThirdCall();
        -            },
        -
        -            withArgs: function withArgs(/* arguments */) {
        -                throw new Error("Defining a stub by invoking \"stub.onCall(...).withArgs(...)\" is not supported. " +
        -                                "Use \"stub.withArgs(...).onCall(...)\" to define sequential behavior for calls with certain arguments.");
        -            },
        -
        -            callsArg: function callsArg(pos) {
        -                if (typeof pos != "number") {
        -                    throw new TypeError("argument index is not number");
        -                }
        -
        -                this.callArgAt = pos;
        -                this.callbackArguments = [];
        -                this.callbackContext = undefined;
        -                this.callArgProp = undefined;
        -                this.callbackAsync = false;
        -
        -                return this;
        -            },
        -
        -            callsArgOn: function callsArgOn(pos, context) {
        -                if (typeof pos != "number") {
        -                    throw new TypeError("argument index is not number");
        -                }
        -                if (typeof context != "object") {
        -                    throw new TypeError("argument context is not an object");
        -                }
        -
        -                this.callArgAt = pos;
        -                this.callbackArguments = [];
        -                this.callbackContext = context;
        -                this.callArgProp = undefined;
        -                this.callbackAsync = false;
        -
        -                return this;
        -            },
        -
        -            callsArgWith: function callsArgWith(pos) {
        -                if (typeof pos != "number") {
        -                    throw new TypeError("argument index is not number");
        -                }
        -
        -                this.callArgAt = pos;
        -                this.callbackArguments = slice.call(arguments, 1);
        -                this.callbackContext = undefined;
        -                this.callArgProp = undefined;
        -                this.callbackAsync = false;
        -
        -                return this;
        -            },
        -
        -            callsArgOnWith: function callsArgWith(pos, context) {
        -                if (typeof pos != "number") {
        -                    throw new TypeError("argument index is not number");
        -                }
        -                if (typeof context != "object") {
        -                    throw new TypeError("argument context is not an object");
        -                }
        -
        -                this.callArgAt = pos;
        -                this.callbackArguments = slice.call(arguments, 2);
        -                this.callbackContext = context;
        -                this.callArgProp = undefined;
        -                this.callbackAsync = false;
        -
        -                return this;
        -            },
        -
        -            yields: function () {
        -                this.callArgAt = useLeftMostCallback;
        -                this.callbackArguments = slice.call(arguments, 0);
        -                this.callbackContext = undefined;
        -                this.callArgProp = undefined;
        -                this.callbackAsync = false;
        -
        -                return this;
        -            },
        -
        -            yieldsRight: function () {
        -                this.callArgAt = useRightMostCallback;
        -                this.callbackArguments = slice.call(arguments, 0);
        -                this.callbackContext = undefined;
        -                this.callArgProp = undefined;
        -                this.callbackAsync = false;
        -
        -                return this;
        -            },
        -
        -            yieldsOn: function (context) {
        -                if (typeof context != "object") {
        -                    throw new TypeError("argument context is not an object");
        -                }
        -
        -                this.callArgAt = useLeftMostCallback;
        -                this.callbackArguments = slice.call(arguments, 1);
        -                this.callbackContext = context;
        -                this.callArgProp = undefined;
        -                this.callbackAsync = false;
        -
        -                return this;
        -            },
        -
        -            yieldsTo: function (prop) {
        -                this.callArgAt = useLeftMostCallback;
        -                this.callbackArguments = slice.call(arguments, 1);
        -                this.callbackContext = undefined;
        -                this.callArgProp = prop;
        -                this.callbackAsync = false;
        -
        -                return this;
        -            },
        -
        -            yieldsToOn: function (prop, context) {
        -                if (typeof context != "object") {
        -                    throw new TypeError("argument context is not an object");
        -                }
        -
        -                this.callArgAt = useLeftMostCallback;
        -                this.callbackArguments = slice.call(arguments, 2);
        -                this.callbackContext = context;
        -                this.callArgProp = prop;
        -                this.callbackAsync = false;
        -
        -                return this;
        -            },
        -
        -            throws: throwsException,
        -            throwsException: throwsException,
        -
        -            returns: function returns(value) {
        -                this.returnValue = value;
        -                this.returnValueDefined = true;
        -
        -                return this;
        -            },
        -
        -            returnsArg: function returnsArg(pos) {
        -                if (typeof pos != "number") {
        -                    throw new TypeError("argument index is not number");
        -                }
        -
        -                this.returnArgAt = pos;
        -
        -                return this;
        -            },
        -
        -            returnsThis: function returnsThis() {
        -                this.returnThis = true;
        -
        -                return this;
        -            }
        -        };
        -
        -        // create asynchronous versions of callsArg* and yields* methods
        -        for (var method in proto) {
        -            // need to avoid creating anotherasync versions of the newly added async methods
        -            if (proto.hasOwnProperty(method) &&
        -                method.match(/^(callsArg|yields)/) &&
        -                !method.match(/Async/)) {
        -                proto[method + "Async"] = (function (syncFnName) {
        -                    return function () {
        -                        var result = this[syncFnName].apply(this, arguments);
        -                        this.callbackAsync = true;
        -                        return result;
        -                    };
        -                })(method);
        -            }
        -        }
        -
        -        sinon.behavior = proto;
        -        return proto;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        require("./extend");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        - * @depend util/core.js
        - * @depend extend.js
        - * @depend spy.js
        - * @depend behavior.js
        - */
        -/**
        - * Stub functions
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -(function (sinon) {
        -    function makeApi(sinon) {
        -        function stub(object, property, func) {
        -            if (!!func && typeof func != "function" && typeof func != "object") {
        -                throw new TypeError("Custom stub should be a function or a property descriptor");
        -            }
        -
        -            var wrapper;
        -
        -            if (func) {
        -                if (typeof func == "function") {
        -                    wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func;
        -                } else {
        -                    wrapper = func;
        -                    if (sinon.spy && sinon.spy.create) {
        -                        var types = sinon.objectKeys(wrapper);
        -                        for (var i = 0; i < types.length; i++) {
        -                            wrapper[types[i]] = sinon.spy.create(wrapper[types[i]]);
        -                        }
        -                    }
        -                }
        -            } else {
        -                var stubLength = 0;
        -                if (typeof object == "object" && typeof object[property] == "function") {
        -                    stubLength = object[property].length;
        -                }
        -                wrapper = stub.create(stubLength);
        -            }
        -
        -            if (!object && typeof property === "undefined") {
        -                return sinon.stub.create();
        -            }
        -
        -            if (typeof property === "undefined" && typeof object == "object") {
        -                for (var prop in object) {
        -                    if (typeof sinon.getPropertyDescriptor(object, prop).value === "function") {
        -                        stub(object, prop);
        -                    }
        -                }
        -
        -                return object;
        -            }
        -
        -            return sinon.wrapMethod(object, property, wrapper);
        -        }
        -
        -        function getDefaultBehavior(stub) {
        -            return stub.defaultBehavior || getParentBehaviour(stub) || sinon.behavior.create(stub);
        -        }
        -
        -        function getParentBehaviour(stub) {
        -            return (stub.parent && getCurrentBehavior(stub.parent));
        -        }
        -
        -        function getCurrentBehavior(stub) {
        -            var behavior = stub.behaviors[stub.callCount - 1];
        -            return behavior && behavior.isPresent() ? behavior : getDefaultBehavior(stub);
        -        }
        -
        -        var uuid = 0;
        -
        -        var proto = {
        -            create: function create(stubLength) {
        -                var functionStub = function () {
        -                    return getCurrentBehavior(functionStub).invoke(this, arguments);
        -                };
        -
        -                functionStub.id = "stub#" + uuid++;
        -                var orig = functionStub;
        -                functionStub = sinon.spy.create(functionStub, stubLength);
        -                functionStub.func = orig;
        -
        -                sinon.extend(functionStub, stub);
        -                functionStub.instantiateFake = sinon.stub.create;
        -                functionStub.displayName = "stub";
        -                functionStub.toString = sinon.functionToString;
        -
        -                functionStub.defaultBehavior = null;
        -                functionStub.behaviors = [];
        -
        -                return functionStub;
        -            },
        -
        -            resetBehavior: function () {
        -                var i;
        -
        -                this.defaultBehavior = null;
        -                this.behaviors = [];
        -
        -                delete this.returnValue;
        -                delete this.returnArgAt;
        -                this.returnThis = false;
        -
        -                if (this.fakes) {
        -                    for (i = 0; i < this.fakes.length; i++) {
        -                        this.fakes[i].resetBehavior();
        -                    }
        -                }
        -            },
        -
        -            onCall: function onCall(index) {
        -                if (!this.behaviors[index]) {
        -                    this.behaviors[index] = sinon.behavior.create(this);
        -                }
        -
        -                return this.behaviors[index];
        -            },
        -
        -            onFirstCall: function onFirstCall() {
        -                return this.onCall(0);
        -            },
        -
        -            onSecondCall: function onSecondCall() {
        -                return this.onCall(1);
        -            },
        -
        -            onThirdCall: function onThirdCall() {
        -                return this.onCall(2);
        -            }
        -        };
        -
        -        for (var method in sinon.behavior) {
        -            if (sinon.behavior.hasOwnProperty(method) &&
        -                !proto.hasOwnProperty(method) &&
        -                method != "create" &&
        -                method != "withArgs" &&
        -                method != "invoke") {
        -                proto[method] = (function (behaviorMethod) {
        -                    return function () {
        -                        this.defaultBehavior = this.defaultBehavior || sinon.behavior.create(this);
        -                        this.defaultBehavior[behaviorMethod].apply(this.defaultBehavior, arguments);
        -                        return this;
        -                    };
        -                }(method));
        -            }
        -        }
        -
        -        sinon.extend(stub, proto);
        -        sinon.stub = stub;
        -
        -        return stub;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        require("./behavior");
        -        require("./spy");
        -        require("./extend");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        - * @depend times_in_words.js
        - * @depend util/core.js
        - * @depend call.js
        - * @depend extend.js
        - * @depend match.js
        - * @depend spy.js
        - * @depend stub.js
        - * @depend format.js
        - */
        -/**
        - * Mock functions.
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -(function (sinon) {
        -    function makeApi(sinon) {
        -        var push = [].push;
        -        var match = sinon.match;
        -
        -        function mock(object) {
        -            // if (typeof console !== undefined && console.warn) {
        -            //     console.warn("mock will be removed from Sinon.JS v2.0");
        -            // }
        -
        -            if (!object) {
        -                return sinon.expectation.create("Anonymous mock");
        -            }
        -
        -            return mock.create(object);
        -        }
        -
        -        function each(collection, callback) {
        -            if (!collection) {
        -                return;
        -            }
        -
        -            for (var i = 0, l = collection.length; i < l; i += 1) {
        -                callback(collection[i]);
        -            }
        -        }
        -
        -        sinon.extend(mock, {
        -            create: function create(object) {
        -                if (!object) {
        -                    throw new TypeError("object is null");
        -                }
        -
        -                var mockObject = sinon.extend({}, mock);
        -                mockObject.object = object;
        -                delete mockObject.create;
        -
        -                return mockObject;
        -            },
        -
        -            expects: function expects(method) {
        -                if (!method) {
        -                    throw new TypeError("method is falsy");
        -                }
        -
        -                if (!this.expectations) {
        -                    this.expectations = {};
        -                    this.proxies = [];
        -                }
        -
        -                if (!this.expectations[method]) {
        -                    this.expectations[method] = [];
        -                    var mockObject = this;
        -
        -                    sinon.wrapMethod(this.object, method, function () {
        -                        return mockObject.invokeMethod(method, this, arguments);
        -                    });
        -
        -                    push.call(this.proxies, method);
        -                }
        -
        -                var expectation = sinon.expectation.create(method);
        -                push.call(this.expectations[method], expectation);
        -
        -                return expectation;
        -            },
        -
        -            restore: function restore() {
        -                var object = this.object;
        -
        -                each(this.proxies, function (proxy) {
        -                    if (typeof object[proxy].restore == "function") {
        -                        object[proxy].restore();
        -                    }
        -                });
        -            },
        -
        -            verify: function verify() {
        -                var expectations = this.expectations || {};
        -                var messages = [], met = [];
        -
        -                each(this.proxies, function (proxy) {
        -                    each(expectations[proxy], function (expectation) {
        -                        if (!expectation.met()) {
        -                            push.call(messages, expectation.toString());
        -                        } else {
        -                            push.call(met, expectation.toString());
        -                        }
        -                    });
        -                });
        -
        -                this.restore();
        -
        -                if (messages.length > 0) {
        -                    sinon.expectation.fail(messages.concat(met).join("\n"));
        -                } else if (met.length > 0) {
        -                    sinon.expectation.pass(messages.concat(met).join("\n"));
        -                }
        -
        -                return true;
        -            },
        -
        -            invokeMethod: function invokeMethod(method, thisValue, args) {
        -                var expectations = this.expectations && this.expectations[method];
        -                var length = expectations && expectations.length || 0, i;
        -
        -                for (i = 0; i < length; i += 1) {
        -                    if (!expectations[i].met() &&
        -                        expectations[i].allowsCall(thisValue, args)) {
        -                        return expectations[i].apply(thisValue, args);
        -                    }
        -                }
        -
        -                var messages = [], available, exhausted = 0;
        -
        -                for (i = 0; i < length; i += 1) {
        -                    if (expectations[i].allowsCall(thisValue, args)) {
        -                        available = available || expectations[i];
        -                    } else {
        -                        exhausted += 1;
        -                    }
        -                    push.call(messages, "    " + expectations[i].toString());
        -                }
        -
        -                if (exhausted === 0) {
        -                    return available.apply(thisValue, args);
        -                }
        -
        -                messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({
        -                    proxy: method,
        -                    args: args
        -                }));
        -
        -                sinon.expectation.fail(messages.join("\n"));
        -            }
        -        });
        -
        -        var times = sinon.timesInWords;
        -        var slice = Array.prototype.slice;
        -
        -        function callCountInWords(callCount) {
        -            if (callCount == 0) {
        -                return "never called";
        -            } else {
        -                return "called " + times(callCount);
        -            }
        -        }
        -
        -        function expectedCallCountInWords(expectation) {
        -            var min = expectation.minCalls;
        -            var max = expectation.maxCalls;
        -
        -            if (typeof min == "number" && typeof max == "number") {
        -                var str = times(min);
        -
        -                if (min != max) {
        -                    str = "at least " + str + " and at most " + times(max);
        -                }
        -
        -                return str;
        -            }
        -
        -            if (typeof min == "number") {
        -                return "at least " + times(min);
        -            }
        -
        -            return "at most " + times(max);
        -        }
        -
        -        function receivedMinCalls(expectation) {
        -            var hasMinLimit = typeof expectation.minCalls == "number";
        -            return !hasMinLimit || expectation.callCount >= expectation.minCalls;
        -        }
        -
        -        function receivedMaxCalls(expectation) {
        -            if (typeof expectation.maxCalls != "number") {
        -                return false;
        -            }
        -
        -            return expectation.callCount == expectation.maxCalls;
        -        }
        -
        -        function verifyMatcher(possibleMatcher, arg) {
        -            if (match && match.isMatcher(possibleMatcher)) {
        -                return possibleMatcher.test(arg);
        -            } else {
        -                return true;
        -            }
        -        }
        -
        -        sinon.expectation = {
        -            minCalls: 1,
        -            maxCalls: 1,
        -
        -            create: function create(methodName) {
        -                var expectation = sinon.extend(sinon.stub.create(), sinon.expectation);
        -                delete expectation.create;
        -                expectation.method = methodName;
        -
        -                return expectation;
        -            },
        -
        -            invoke: function invoke(func, thisValue, args) {
        -                this.verifyCallAllowed(thisValue, args);
        -
        -                return sinon.spy.invoke.apply(this, arguments);
        -            },
        -
        -            atLeast: function atLeast(num) {
        -                if (typeof num != "number") {
        -                    throw new TypeError("'" + num + "' is not number");
        -                }
        -
        -                if (!this.limitsSet) {
        -                    this.maxCalls = null;
        -                    this.limitsSet = true;
        -                }
        -
        -                this.minCalls = num;
        -
        -                return this;
        -            },
        -
        -            atMost: function atMost(num) {
        -                if (typeof num != "number") {
        -                    throw new TypeError("'" + num + "' is not number");
        -                }
        -
        -                if (!this.limitsSet) {
        -                    this.minCalls = null;
        -                    this.limitsSet = true;
        -                }
        -
        -                this.maxCalls = num;
        -
        -                return this;
        -            },
        -
        -            never: function never() {
        -                return this.exactly(0);
        -            },
        -
        -            once: function once() {
        -                return this.exactly(1);
        -            },
        -
        -            twice: function twice() {
        -                return this.exactly(2);
        -            },
        -
        -            thrice: function thrice() {
        -                return this.exactly(3);
        -            },
        -
        -            exactly: function exactly(num) {
        -                if (typeof num != "number") {
        -                    throw new TypeError("'" + num + "' is not a number");
        -                }
        -
        -                this.atLeast(num);
        -                return this.atMost(num);
        -            },
        -
        -            met: function met() {
        -                return !this.failed && receivedMinCalls(this);
        -            },
        -
        -            verifyCallAllowed: function verifyCallAllowed(thisValue, args) {
        -                if (receivedMaxCalls(this)) {
        -                    this.failed = true;
        -                    sinon.expectation.fail(this.method + " already called " + times(this.maxCalls));
        -                }
        -
        -                if ("expectedThis" in this && this.expectedThis !== thisValue) {
        -                    sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " +
        -                        this.expectedThis);
        -                }
        -
        -                if (!("expectedArguments" in this)) {
        -                    return;
        -                }
        -
        -                if (!args) {
        -                    sinon.expectation.fail(this.method + " received no arguments, expected " +
        -                        sinon.format(this.expectedArguments));
        -                }
        -
        -                if (args.length < this.expectedArguments.length) {
        -                    sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) +
        -                        "), expected " + sinon.format(this.expectedArguments));
        -                }
        -
        -                if (this.expectsExactArgCount &&
        -                    args.length != this.expectedArguments.length) {
        -                    sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) +
        -                        "), expected " + sinon.format(this.expectedArguments));
        -                }
        -
        -                for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {
        -
        -                    if (!verifyMatcher(this.expectedArguments[i], args[i])) {
        -                        sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) +
        -                            ", didn't match " + this.expectedArguments.toString());
        -                    }
        -
        -                    if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
        -                        sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) +
        -                            ", expected " + sinon.format(this.expectedArguments));
        -                    }
        -                }
        -            },
        -
        -            allowsCall: function allowsCall(thisValue, args) {
        -                if (this.met() && receivedMaxCalls(this)) {
        -                    return false;
        -                }
        -
        -                if ("expectedThis" in this && this.expectedThis !== thisValue) {
        -                    return false;
        -                }
        -
        -                if (!("expectedArguments" in this)) {
        -                    return true;
        -                }
        -
        -                args = args || [];
        -
        -                if (args.length < this.expectedArguments.length) {
        -                    return false;
        -                }
        -
        -                if (this.expectsExactArgCount &&
        -                    args.length != this.expectedArguments.length) {
        -                    return false;
        -                }
        -
        -                for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {
        -                    if (!verifyMatcher(this.expectedArguments[i], args[i])) {
        -                        return false;
        -                    }
        -
        -                    if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
        -                        return false;
        -                    }
        -                }
        -
        -                return true;
        -            },
        -
        -            withArgs: function withArgs() {
        -                this.expectedArguments = slice.call(arguments);
        -                return this;
        -            },
        -
        -            withExactArgs: function withExactArgs() {
        -                this.withArgs.apply(this, arguments);
        -                this.expectsExactArgCount = true;
        -                return this;
        -            },
        -
        -            on: function on(thisValue) {
        -                this.expectedThis = thisValue;
        -                return this;
        -            },
        -
        -            toString: function () {
        -                var args = (this.expectedArguments || []).slice();
        -
        -                if (!this.expectsExactArgCount) {
        -                    push.call(args, "[...]");
        -                }
        -
        -                var callStr = sinon.spyCall.toString.call({
        -                    proxy: this.method || "anonymous mock expectation",
        -                    args: args
        -                });
        -
        -                var message = callStr.replace(", [...", "[, ...") + " " +
        -                    expectedCallCountInWords(this);
        -
        -                if (this.met()) {
        -                    return "Expectation met: " + message;
        -                }
        -
        -                return "Expected " + message + " (" +
        -                    callCountInWords(this.callCount) + ")";
        -            },
        -
        -            verify: function verify() {
        -                if (!this.met()) {
        -                    sinon.expectation.fail(this.toString());
        -                } else {
        -                    sinon.expectation.pass(this.toString());
        -                }
        -
        -                return true;
        -            },
        -
        -            pass: function pass(message) {
        -                sinon.assert.pass(message);
        -            },
        -
        -            fail: function fail(message) {
        -                var exception = new Error(message);
        -                exception.name = "ExpectationError";
        -
        -                throw exception;
        -            }
        -        };
        -
        -        sinon.mock = mock;
        -        return mock;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        require("./times_in_words");
        -        require("./call");
        -        require("./extend");
        -        require("./match");
        -        require("./spy");
        -        require("./stub");
        -        require("./format");
        -
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        - * @depend util/core.js
        - * @depend spy.js
        - * @depend stub.js
        - * @depend mock.js
        - */
        -/**
        - * Collections of stubs, spies and mocks.
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -(function (sinon) {
        -    var push = [].push;
        -    var hasOwnProperty = Object.prototype.hasOwnProperty;
        -
        -    function getFakes(fakeCollection) {
        -        if (!fakeCollection.fakes) {
        -            fakeCollection.fakes = [];
        -        }
        -
        -        return fakeCollection.fakes;
        -    }
        -
        -    function each(fakeCollection, method) {
        -        var fakes = getFakes(fakeCollection);
        -
        -        for (var i = 0, l = fakes.length; i < l; i += 1) {
        -            if (typeof fakes[i][method] == "function") {
        -                fakes[i][method]();
        -            }
        -        }
        -    }
        -
        -    function compact(fakeCollection) {
        -        var fakes = getFakes(fakeCollection);
        -        var i = 0;
        -        while (i < fakes.length) {
        -            fakes.splice(i, 1);
        -        }
        -    }
        -
        -    function makeApi(sinon) {
        -        var collection = {
        -            verify: function resolve() {
        -                each(this, "verify");
        -            },
        -
        -            restore: function restore() {
        -                each(this, "restore");
        -                compact(this);
        -            },
        -
        -            reset: function restore() {
        -                each(this, "reset");
        -            },
        -
        -            verifyAndRestore: function verifyAndRestore() {
        -                var exception;
        -
        -                try {
        -                    this.verify();
        -                } catch (e) {
        -                    exception = e;
        -                }
        -
        -                this.restore();
        -
        -                if (exception) {
        -                    throw exception;
        -                }
        -            },
        -
        -            add: function add(fake) {
        -                push.call(getFakes(this), fake);
        -                return fake;
        -            },
        -
        -            spy: function spy() {
        -                return this.add(sinon.spy.apply(sinon, arguments));
        -            },
        -
        -            stub: function stub(object, property, value) {
        -                if (property) {
        -                    var original = object[property];
        -
        -                    if (typeof original != "function") {
        -                        if (!hasOwnProperty.call(object, property)) {
        -                            throw new TypeError("Cannot stub non-existent own property " + property);
        -                        }
        -
        -                        object[property] = value;
        -
        -                        return this.add({
        -                            restore: function () {
        -                                object[property] = original;
        -                            }
        -                        });
        -                    }
        -                }
        -                if (!property && !!object && typeof object == "object") {
        -                    var stubbedObj = sinon.stub.apply(sinon, arguments);
        -
        -                    for (var prop in stubbedObj) {
        -                        if (typeof stubbedObj[prop] === "function") {
        -                            this.add(stubbedObj[prop]);
        -                        }
        -                    }
        -
        -                    return stubbedObj;
        -                }
        -
        -                return this.add(sinon.stub.apply(sinon, arguments));
        -            },
        -
        -            mock: function mock() {
        -                return this.add(sinon.mock.apply(sinon, arguments));
        -            },
        -
        -            inject: function inject(obj) {
        -                var col = this;
        -
        -                obj.spy = function () {
        -                    return col.spy.apply(col, arguments);
        -                };
        -
        -                obj.stub = function () {
        -                    return col.stub.apply(col, arguments);
        -                };
        -
        -                obj.mock = function () {
        -                    return col.mock.apply(col, arguments);
        -                };
        -
        -                return obj;
        -            }
        -        };
        -
        -        sinon.collection = collection;
        -        return collection;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        require("./mock");
        -        require("./spy");
        -        require("./stub");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/*global lolex */
        -
        -/**
        - * Fake timer API
        - * setTimeout
        - * setInterval
        - * clearTimeout
        - * clearInterval
        - * tick
        - * reset
        - * Date
        - *
        - * Inspired by jsUnitMockTimeOut from JsUnit
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -if (typeof sinon == "undefined") {
        -    var sinon = {};
        -}
        -
        -(function (global) {
        -    function makeApi(sinon, lol) {
        -        var llx = typeof lolex !== "undefined" ? lolex : lol;
        -
        -        sinon.useFakeTimers = function () {
        -            var now, methods = Array.prototype.slice.call(arguments);
        -
        -            if (typeof methods[0] === "string") {
        -                now = 0;
        -            } else {
        -                now = methods.shift();
        -            }
        -
        -            var clock = llx.install(now || 0, methods);
        -            clock.restore = clock.uninstall;
        -            return clock;
        -        };
        -
        -        sinon.clock = {
        -            create: function (now) {
        -                return llx.createClock(now);
        -            }
        -        };
        -
        -        sinon.timers = {
        -            setTimeout: setTimeout,
        -            clearTimeout: clearTimeout,
        -            setImmediate: (typeof setImmediate !== "undefined" ? setImmediate : undefined),
        -            clearImmediate: (typeof clearImmediate !== "undefined" ? clearImmediate : undefined),
        -            setInterval: setInterval,
        -            clearInterval: clearInterval,
        -            Date: Date
        -        };
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, epxorts, module, lolex) {
        -        var sinon = require("./core");
        -        makeApi(sinon, lolex);
        -        module.exports = sinon;
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module, require("lolex"));
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof global != "undefined" && typeof global !== "function" ? global : this));
        -
        -/**
        - * Minimal Event interface implementation
        - *
        - * Original implementation by Sven Fuchs: https://gist.github.com/995028
        - * Modifications and tests by Christian Johansen.
        - *
        - * @author Sven Fuchs (svenfuchs@artweb-design.de)
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2011 Sven Fuchs, Christian Johansen
        - */
        -
        -if (typeof sinon == "undefined") {
        -    this.sinon = {};
        -}
        -
        -(function () {
        -    var push = [].push;
        -
        -    function makeApi(sinon) {
        -        sinon.Event = function Event(type, bubbles, cancelable, target) {
        -            this.initEvent(type, bubbles, cancelable, target);
        -        };
        -
        -        sinon.Event.prototype = {
        -            initEvent: function (type, bubbles, cancelable, target) {
        -                this.type = type;
        -                this.bubbles = bubbles;
        -                this.cancelable = cancelable;
        -                this.target = target;
        -            },
        -
        -            stopPropagation: function () {},
        -
        -            preventDefault: function () {
        -                this.defaultPrevented = true;
        -            }
        -        };
        -
        -        sinon.ProgressEvent = function ProgressEvent(type, progressEventRaw, target) {
        -            this.initEvent(type, false, false, target);
        -            this.loaded = progressEventRaw.loaded || null;
        -            this.total = progressEventRaw.total || null;
        -            this.lengthComputable = !!progressEventRaw.total;
        -        };
        -
        -        sinon.ProgressEvent.prototype = new sinon.Event();
        -
        -        sinon.ProgressEvent.prototype.constructor =  sinon.ProgressEvent;
        -
        -        sinon.CustomEvent = function CustomEvent(type, customData, target) {
        -            this.initEvent(type, false, false, target);
        -            this.detail = customData.detail || null;
        -        };
        -
        -        sinon.CustomEvent.prototype = new sinon.Event();
        -
        -        sinon.CustomEvent.prototype.constructor =  sinon.CustomEvent;
        -
        -        sinon.EventTarget = {
        -            addEventListener: function addEventListener(event, listener) {
        -                this.eventListeners = this.eventListeners || {};
        -                this.eventListeners[event] = this.eventListeners[event] || [];
        -                push.call(this.eventListeners[event], listener);
        -            },
        -
        -            removeEventListener: function removeEventListener(event, listener) {
        -                var listeners = this.eventListeners && this.eventListeners[event] || [];
        -
        -                for (var i = 0, l = listeners.length; i < l; ++i) {
        -                    if (listeners[i] == listener) {
        -                        return listeners.splice(i, 1);
        -                    }
        -                }
        -            },
        -
        -            dispatchEvent: function dispatchEvent(event) {
        -                var type = event.type;
        -                var listeners = this.eventListeners && this.eventListeners[type] || [];
        -
        -                for (var i = 0; i < listeners.length; i++) {
        -                    if (typeof listeners[i] == "function") {
        -                        listeners[i].call(this, event);
        -                    } else {
        -                        listeners[i].handleEvent(event);
        -                    }
        -                }
        -
        -                return !!event.defaultPrevented;
        -            }
        -        };
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require) {
        -        var sinon = require("./core");
        -        makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require);
        -    } else {
        -        makeApi(sinon);
        -    }
        -}());
        -
        -/**
        - * @depend util/core.js
        - */
        -/**
        - * Logs errors
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2014 Christian Johansen
        - */
        -
        -(function (sinon) {
        -    // cache a reference to setTimeout, so that our reference won't be stubbed out
        -    // when using fake timers and errors will still get logged
        -    // https://github.com/cjohansen/Sinon.JS/issues/381
        -    var realSetTimeout = setTimeout;
        -
        -    function makeApi(sinon) {
        -
        -        function log() {}
        -
        -        function logError(label, err) {
        -            var msg = label + " threw exception: ";
        -
        -            sinon.log(msg + "[" + err.name + "] " + err.message);
        -
        -            if (err.stack) {
        -                sinon.log(err.stack);
        -            }
        -
        -            logError.setTimeout(function () {
        -                err.message = msg + err.message;
        -                throw err;
        -            }, 0);
        -        };
        -
        -        // wrap realSetTimeout with something we can stub in tests
        -        logError.setTimeout = function (func, timeout) {
        -            realSetTimeout(func, timeout);
        -        }
        -
        -        var exports = {};
        -        exports.log = sinon.log = log;
        -        exports.logError = sinon.logError = logError;
        -
        -        return exports;
        -    }
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        - * @depend core.js
        - * @depend ../extend.js
        - * @depend event.js
        - * @depend ../log_error.js
        - */
        -/**
        - * Fake XDomainRequest object
        - */
        -
        -if (typeof sinon == "undefined") {
        -    this.sinon = {};
        -}
        -
        -// wrapper for global
        -(function (global) {
        -    var xdr = { XDomainRequest: global.XDomainRequest };
        -    xdr.GlobalXDomainRequest = global.XDomainRequest;
        -    xdr.supportsXDR = typeof xdr.GlobalXDomainRequest != "undefined";
        -    xdr.workingXDR = xdr.supportsXDR ? xdr.GlobalXDomainRequest :  false;
        -
        -    function makeApi(sinon) {
        -        sinon.xdr = xdr;
        -
        -        function FakeXDomainRequest() {
        -            this.readyState = FakeXDomainRequest.UNSENT;
        -            this.requestBody = null;
        -            this.requestHeaders = {};
        -            this.status = 0;
        -            this.timeout = null;
        -
        -            if (typeof FakeXDomainRequest.onCreate == "function") {
        -                FakeXDomainRequest.onCreate(this);
        -            }
        -        }
        -
        -        function verifyState(xdr) {
        -            if (xdr.readyState !== FakeXDomainRequest.OPENED) {
        -                throw new Error("INVALID_STATE_ERR");
        -            }
        -
        -            if (xdr.sendFlag) {
        -                throw new Error("INVALID_STATE_ERR");
        -            }
        -        }
        -
        -        function verifyRequestSent(xdr) {
        -            if (xdr.readyState == FakeXDomainRequest.UNSENT) {
        -                throw new Error("Request not sent");
        -            }
        -            if (xdr.readyState == FakeXDomainRequest.DONE) {
        -                throw new Error("Request done");
        -            }
        -        }
        -
        -        function verifyResponseBodyType(body) {
        -            if (typeof body != "string") {
        -                var error = new Error("Attempted to respond to fake XDomainRequest with " +
        -                                    body + ", which is not a string.");
        -                error.name = "InvalidBodyException";
        -                throw error;
        -            }
        -        }
        -
        -        sinon.extend(FakeXDomainRequest.prototype, sinon.EventTarget, {
        -            open: function open(method, url) {
        -                this.method = method;
        -                this.url = url;
        -
        -                this.responseText = null;
        -                this.sendFlag = false;
        -
        -                this.readyStateChange(FakeXDomainRequest.OPENED);
        -            },
        -
        -            readyStateChange: function readyStateChange(state) {
        -                this.readyState = state;
        -                var eventName = "";
        -                switch (this.readyState) {
        -                case FakeXDomainRequest.UNSENT:
        -                    break;
        -                case FakeXDomainRequest.OPENED:
        -                    break;
        -                case FakeXDomainRequest.LOADING:
        -                    if (this.sendFlag) {
        -                        //raise the progress event
        -                        eventName = "onprogress";
        -                    }
        -                    break;
        -                case FakeXDomainRequest.DONE:
        -                    if (this.isTimeout) {
        -                        eventName = "ontimeout"
        -                    } else if (this.errorFlag || (this.status < 200 || this.status > 299)) {
        -                        eventName = "onerror";
        -                    } else {
        -                        eventName = "onload"
        -                    }
        -                    break;
        -                }
        -
        -                // raising event (if defined)
        -                if (eventName) {
        -                    if (typeof this[eventName] == "function") {
        -                        try {
        -                            this[eventName]();
        -                        } catch (e) {
        -                            sinon.logError("Fake XHR " + eventName + " handler", e);
        -                        }
        -                    }
        -                }
        -            },
        -
        -            send: function send(data) {
        -                verifyState(this);
        -
        -                if (!/^(get|head)$/i.test(this.method)) {
        -                    this.requestBody = data;
        -                }
        -                this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
        -
        -                this.errorFlag = false;
        -                this.sendFlag = true;
        -                this.readyStateChange(FakeXDomainRequest.OPENED);
        -
        -                if (typeof this.onSend == "function") {
        -                    this.onSend(this);
        -                }
        -            },
        -
        -            abort: function abort() {
        -                this.aborted = true;
        -                this.responseText = null;
        -                this.errorFlag = true;
        -
        -                if (this.readyState > sinon.FakeXDomainRequest.UNSENT && this.sendFlag) {
        -                    this.readyStateChange(sinon.FakeXDomainRequest.DONE);
        -                    this.sendFlag = false;
        -                }
        -            },
        -
        -            setResponseBody: function setResponseBody(body) {
        -                verifyRequestSent(this);
        -                verifyResponseBodyType(body);
        -
        -                var chunkSize = this.chunkSize || 10;
        -                var index = 0;
        -                this.responseText = "";
        -
        -                do {
        -                    this.readyStateChange(FakeXDomainRequest.LOADING);
        -                    this.responseText += body.substring(index, index + chunkSize);
        -                    index += chunkSize;
        -                } while (index < body.length);
        -
        -                this.readyStateChange(FakeXDomainRequest.DONE);
        -            },
        -
        -            respond: function respond(status, contentType, body) {
        -                // content-type ignored, since XDomainRequest does not carry this
        -                // we keep the same syntax for respond(...) as for FakeXMLHttpRequest to ease
        -                // test integration across browsers
        -                this.status = typeof status == "number" ? status : 200;
        -                this.setResponseBody(body || "");
        -            },
        -
        -            simulatetimeout: function simulatetimeout() {
        -                this.status = 0;
        -                this.isTimeout = true;
        -                // Access to this should actually throw an error
        -                this.responseText = undefined;
        -                this.readyStateChange(FakeXDomainRequest.DONE);
        -            }
        -        });
        -
        -        sinon.extend(FakeXDomainRequest, {
        -            UNSENT: 0,
        -            OPENED: 1,
        -            LOADING: 3,
        -            DONE: 4
        -        });
        -
        -        sinon.useFakeXDomainRequest = function useFakeXDomainRequest() {
        -            sinon.FakeXDomainRequest.restore = function restore(keepOnCreate) {
        -                if (xdr.supportsXDR) {
        -                    global.XDomainRequest = xdr.GlobalXDomainRequest;
        -                }
        -
        -                delete sinon.FakeXDomainRequest.restore;
        -
        -                if (keepOnCreate !== true) {
        -                    delete sinon.FakeXDomainRequest.onCreate;
        -                }
        -            };
        -            if (xdr.supportsXDR) {
        -                global.XDomainRequest = sinon.FakeXDomainRequest;
        -            }
        -            return sinon.FakeXDomainRequest;
        -        };
        -
        -        sinon.FakeXDomainRequest = FakeXDomainRequest;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./core");
        -        require("../extend");
        -        require("./event");
        -        require("../log_error");
        -        makeApi(sinon);
        -        module.exports = sinon;
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else {
        -        makeApi(sinon);
        -    }
        -})(typeof global !== "undefined" ? global : self);
        -
        -/**
        - * @depend core.js
        - * @depend ../extend.js
        - * @depend event.js
        - * @depend ../log_error.js
        - */
        -/**
        - * Fake XMLHttpRequest object
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -(function (global) {
        -
        -    var supportsProgress = typeof ProgressEvent !== "undefined";
        -    var supportsCustomEvent = typeof CustomEvent !== "undefined";
        -    var supportsFormData = typeof FormData !== "undefined";
        -    var sinonXhr = { XMLHttpRequest: global.XMLHttpRequest };
        -    sinonXhr.GlobalXMLHttpRequest = global.XMLHttpRequest;
        -    sinonXhr.GlobalActiveXObject = global.ActiveXObject;
        -    sinonXhr.supportsActiveX = typeof sinonXhr.GlobalActiveXObject != "undefined";
        -    sinonXhr.supportsXHR = typeof sinonXhr.GlobalXMLHttpRequest != "undefined";
        -    sinonXhr.workingXHR = sinonXhr.supportsXHR ? sinonXhr.GlobalXMLHttpRequest : sinonXhr.supportsActiveX
        -                                     ? function () {
        -                                        return new sinonXhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0")
        -                                    } : false;
        -    sinonXhr.supportsCORS = sinonXhr.supportsXHR && "withCredentials" in (new sinonXhr.GlobalXMLHttpRequest());
        -
        -    /*jsl:ignore*/
        -    var unsafeHeaders = {
        -        "Accept-Charset": true,
        -        "Accept-Encoding": true,
        -        Connection: true,
        -        "Content-Length": true,
        -        Cookie: true,
        -        Cookie2: true,
        -        "Content-Transfer-Encoding": true,
        -        Date: true,
        -        Expect: true,
        -        Host: true,
        -        "Keep-Alive": true,
        -        Referer: true,
        -        TE: true,
        -        Trailer: true,
        -        "Transfer-Encoding": true,
        -        Upgrade: true,
        -        "User-Agent": true,
        -        Via: true
        -    };
        -    /*jsl:end*/
        -
        -    // Note that for FakeXMLHttpRequest to work pre ES5
        -    // we lose some of the alignment with the spec.
        -    // To ensure as close a match as possible,
        -    // set responseType before calling open, send or respond;
        -    function FakeXMLHttpRequest() {
        -        this.readyState = FakeXMLHttpRequest.UNSENT;
        -        this.requestHeaders = {};
        -        this.requestBody = null;
        -        this.status = 0;
        -        this.statusText = "";
        -        this.upload = new UploadProgress();
        -        this.responseType = "";
        -        this.response = "";
        -        if (sinonXhr.supportsCORS) {
        -            this.withCredentials = false;
        -        }
        -
        -        var xhr = this;
        -        var events = ["loadstart", "load", "abort", "loadend"];
        -
        -        function addEventListener(eventName) {
        -            xhr.addEventListener(eventName, function (event) {
        -                var listener = xhr["on" + eventName];
        -
        -                if (listener && typeof listener == "function") {
        -                    listener.call(this, event);
        -                }
        -            });
        -        }
        -
        -        for (var i = events.length - 1; i >= 0; i--) {
        -            addEventListener(events[i]);
        -        }
        -
        -        if (typeof FakeXMLHttpRequest.onCreate == "function") {
        -            FakeXMLHttpRequest.onCreate(this);
        -        }
        -    }
        -
        -    // An upload object is created for each
        -    // FakeXMLHttpRequest and allows upload
        -    // events to be simulated using uploadProgress
        -    // and uploadError.
        -    function UploadProgress() {
        -        this.eventListeners = {
        -            progress: [],
        -            load: [],
        -            abort: [],
        -            error: []
        -        }
        -    }
        -
        -    UploadProgress.prototype.addEventListener = function addEventListener(event, listener) {
        -        this.eventListeners[event].push(listener);
        -    };
        -
        -    UploadProgress.prototype.removeEventListener = function removeEventListener(event, listener) {
        -        var listeners = this.eventListeners[event] || [];
        -
        -        for (var i = 0, l = listeners.length; i < l; ++i) {
        -            if (listeners[i] == listener) {
        -                return listeners.splice(i, 1);
        -            }
        -        }
        -    };
        -
        -    UploadProgress.prototype.dispatchEvent = function dispatchEvent(event) {
        -        var listeners = this.eventListeners[event.type] || [];
        -
        -        for (var i = 0, listener; (listener = listeners[i]) != null; i++) {
        -            listener(event);
        -        }
        -    };
        -
        -    function verifyState(xhr) {
        -        if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
        -            throw new Error("INVALID_STATE_ERR");
        -        }
        -
        -        if (xhr.sendFlag) {
        -            throw new Error("INVALID_STATE_ERR");
        -        }
        -    }
        -
        -    function getHeader(headers, header) {
        -        header = header.toLowerCase();
        -
        -        for (var h in headers) {
        -            if (h.toLowerCase() == header) {
        -                return h;
        -            }
        -        }
        -
        -        return null;
        -    }
        -
        -    // filtering to enable a white-list version of Sinon FakeXhr,
        -    // where whitelisted requests are passed through to real XHR
        -    function each(collection, callback) {
        -        if (!collection) {
        -            return;
        -        }
        -
        -        for (var i = 0, l = collection.length; i < l; i += 1) {
        -            callback(collection[i]);
        -        }
        -    }
        -    function some(collection, callback) {
        -        for (var index = 0; index < collection.length; index++) {
        -            if (callback(collection[index]) === true) {
        -                return true;
        -            }
        -        }
        -        return false;
        -    }
        -    // largest arity in XHR is 5 - XHR#open
        -    var apply = function (obj, method, args) {
        -        switch (args.length) {
        -        case 0: return obj[method]();
        -        case 1: return obj[method](args[0]);
        -        case 2: return obj[method](args[0], args[1]);
        -        case 3: return obj[method](args[0], args[1], args[2]);
        -        case 4: return obj[method](args[0], args[1], args[2], args[3]);
        -        case 5: return obj[method](args[0], args[1], args[2], args[3], args[4]);
        -        }
        -    };
        -
        -    FakeXMLHttpRequest.filters = [];
        -    FakeXMLHttpRequest.addFilter = function addFilter(fn) {
        -        this.filters.push(fn)
        -    };
        -    var IE6Re = /MSIE 6/;
        -    FakeXMLHttpRequest.defake = function defake(fakeXhr, xhrArgs) {
        -        var xhr = new sinonXhr.workingXHR();
        -        each([
        -            "open",
        -            "setRequestHeader",
        -            "send",
        -            "abort",
        -            "getResponseHeader",
        -            "getAllResponseHeaders",
        -            "addEventListener",
        -            "overrideMimeType",
        -            "removeEventListener"
        -        ], function (method) {
        -            fakeXhr[method] = function () {
        -                return apply(xhr, method, arguments);
        -            };
        -        });
        -
        -        var copyAttrs = function (args) {
        -            each(args, function (attr) {
        -                try {
        -                    fakeXhr[attr] = xhr[attr]
        -                } catch (e) {
        -                    if (!IE6Re.test(navigator.userAgent)) {
        -                        throw e;
        -                    }
        -                }
        -            });
        -        };
        -
        -        var stateChange = function stateChange() {
        -            fakeXhr.readyState = xhr.readyState;
        -            if (xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) {
        -                copyAttrs(["status", "statusText"]);
        -            }
        -            if (xhr.readyState >= FakeXMLHttpRequest.LOADING) {
        -                copyAttrs(["responseText", "response"]);
        -            }
        -            if (xhr.readyState === FakeXMLHttpRequest.DONE) {
        -                copyAttrs(["responseXML"]);
        -            }
        -            if (fakeXhr.onreadystatechange) {
        -                fakeXhr.onreadystatechange.call(fakeXhr, { target: fakeXhr });
        -            }
        -        };
        -
        -        if (xhr.addEventListener) {
        -            for (var event in fakeXhr.eventListeners) {
        -                if (fakeXhr.eventListeners.hasOwnProperty(event)) {
        -                    each(fakeXhr.eventListeners[event], function (handler) {
        -                        xhr.addEventListener(event, handler);
        -                    });
        -                }
        -            }
        -            xhr.addEventListener("readystatechange", stateChange);
        -        } else {
        -            xhr.onreadystatechange = stateChange;
        -        }
        -        apply(xhr, "open", xhrArgs);
        -    };
        -    FakeXMLHttpRequest.useFilters = false;
        -
        -    function verifyRequestOpened(xhr) {
        -        if (xhr.readyState != FakeXMLHttpRequest.OPENED) {
        -            throw new Error("INVALID_STATE_ERR - " + xhr.readyState);
        -        }
        -    }
        -
        -    function verifyRequestSent(xhr) {
        -        if (xhr.readyState == FakeXMLHttpRequest.DONE) {
        -            throw new Error("Request done");
        -        }
        -    }
        -
        -    function verifyHeadersReceived(xhr) {
        -        if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) {
        -            throw new Error("No headers received");
        -        }
        -    }
        -
        -    function verifyResponseBodyType(body) {
        -        if (typeof body != "string") {
        -            var error = new Error("Attempted to respond to fake XMLHttpRequest with " +
        -                                 body + ", which is not a string.");
        -            error.name = "InvalidBodyException";
        -            throw error;
        -        }
        -    }
        -
        -    FakeXMLHttpRequest.parseXML = function parseXML(text) {
        -        var xmlDoc;
        -
        -        if (typeof DOMParser != "undefined") {
        -            var parser = new DOMParser();
        -            xmlDoc = parser.parseFromString(text, "text/xml");
        -        } else {
        -            xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
        -            xmlDoc.async = "false";
        -            xmlDoc.loadXML(text);
        -        }
        -
        -        return xmlDoc;
        -    };
        -
        -    FakeXMLHttpRequest.statusCodes = {
        -        100: "Continue",
        -        101: "Switching Protocols",
        -        200: "OK",
        -        201: "Created",
        -        202: "Accepted",
        -        203: "Non-Authoritative Information",
        -        204: "No Content",
        -        205: "Reset Content",
        -        206: "Partial Content",
        -        207: "Multi-Status",
        -        300: "Multiple Choice",
        -        301: "Moved Permanently",
        -        302: "Found",
        -        303: "See Other",
        -        304: "Not Modified",
        -        305: "Use Proxy",
        -        307: "Temporary Redirect",
        -        400: "Bad Request",
        -        401: "Unauthorized",
        -        402: "Payment Required",
        -        403: "Forbidden",
        -        404: "Not Found",
        -        405: "Method Not Allowed",
        -        406: "Not Acceptable",
        -        407: "Proxy Authentication Required",
        -        408: "Request Timeout",
        -        409: "Conflict",
        -        410: "Gone",
        -        411: "Length Required",
        -        412: "Precondition Failed",
        -        413: "Request Entity Too Large",
        -        414: "Request-URI Too Long",
        -        415: "Unsupported Media Type",
        -        416: "Requested Range Not Satisfiable",
        -        417: "Expectation Failed",
        -        422: "Unprocessable Entity",
        -        500: "Internal Server Error",
        -        501: "Not Implemented",
        -        502: "Bad Gateway",
        -        503: "Service Unavailable",
        -        504: "Gateway Timeout",
        -        505: "HTTP Version Not Supported"
        -    };
        -
        -    function makeApi(sinon) {
        -        sinon.xhr = sinonXhr;
        -
        -        sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, {
        -            async: true,
        -
        -            open: function open(method, url, async, username, password) {
        -                this.method = method;
        -                this.url = url;
        -                this.async = typeof async == "boolean" ? async : true;
        -                this.username = username;
        -                this.password = password;
        -                this.responseText = null;
        -                this.response = this.responseType === "json" ? null : "";
        -                this.responseXML = null;
        -                this.requestHeaders = {};
        -                this.sendFlag = false;
        -
        -                if (FakeXMLHttpRequest.useFilters === true) {
        -                    var xhrArgs = arguments;
        -                    var defake = some(FakeXMLHttpRequest.filters, function (filter) {
        -                        return filter.apply(this, xhrArgs)
        -                    });
        -                    if (defake) {
        -                        return FakeXMLHttpRequest.defake(this, arguments);
        -                    }
        -                }
        -                this.readyStateChange(FakeXMLHttpRequest.OPENED);
        -            },
        -
        -            readyStateChange: function readyStateChange(state) {
        -                this.readyState = state;
        -
        -                if (typeof this.onreadystatechange == "function") {
        -                    try {
        -                        this.onreadystatechange();
        -                    } catch (e) {
        -                        sinon.logError("Fake XHR onreadystatechange handler", e);
        -                    }
        -                }
        -
        -                switch (this.readyState) {
        -                    case FakeXMLHttpRequest.DONE:
        -                        if (supportsProgress) {
        -                            this.upload.dispatchEvent(new sinon.ProgressEvent("progress", {loaded: 100, total: 100}));
        -                            this.dispatchEvent(new sinon.ProgressEvent("progress", {loaded: 100, total: 100}));
        -                        }
        -                        this.upload.dispatchEvent(new sinon.Event("load", false, false, this));
        -                        this.dispatchEvent(new sinon.Event("load", false, false, this));
        -                        this.dispatchEvent(new sinon.Event("loadend", false, false, this));
        -                        break;
        -                }
        -
        -                this.dispatchEvent(new sinon.Event("readystatechange"));
        -            },
        -
        -            setRequestHeader: function setRequestHeader(header, value) {
        -                verifyState(this);
        -
        -                if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) {
        -                    throw new Error("Refused to set unsafe header \"" + header + "\"");
        -                }
        -
        -                if (this.requestHeaders[header]) {
        -                    this.requestHeaders[header] += "," + value;
        -                } else {
        -                    this.requestHeaders[header] = value;
        -                }
        -            },
        -
        -            // Helps testing
        -            setResponseHeaders: function setResponseHeaders(headers) {
        -                verifyRequestOpened(this);
        -                this.responseHeaders = {};
        -
        -                for (var header in headers) {
        -                    if (headers.hasOwnProperty(header)) {
        -                        this.responseHeaders[header] = headers[header];
        -                    }
        -                }
        -
        -                if (this.async) {
        -                    this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);
        -                } else {
        -                    this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED;
        -                }
        -            },
        -
        -            // Currently treats ALL data as a DOMString (i.e. no Document)
        -            send: function send(data) {
        -                verifyState(this);
        -
        -                if (!/^(get|head)$/i.test(this.method)) {
        -                    var contentType = getHeader(this.requestHeaders, "Content-Type");
        -                    if (this.requestHeaders[contentType]) {
        -                        var value = this.requestHeaders[contentType].split(";");
        -                        this.requestHeaders[contentType] = value[0] + ";charset=utf-8";
        -                    } else if (supportsFormData && !(data instanceof FormData)) {
        -                        this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
        -                    }
        -
        -                    this.requestBody = data;
        -                }
        -
        -                this.errorFlag = false;
        -                this.sendFlag = this.async;
        -                this.response = this.responseType === "json" ? null : "";
        -                this.readyStateChange(FakeXMLHttpRequest.OPENED);
        -
        -                if (typeof this.onSend == "function") {
        -                    this.onSend(this);
        -                }
        -
        -                this.dispatchEvent(new sinon.Event("loadstart", false, false, this));
        -            },
        -
        -            abort: function abort() {
        -                this.aborted = true;
        -                this.responseText = null;
        -                this.response = this.responseType === "json" ? null : "";
        -                this.errorFlag = true;
        -                this.requestHeaders = {};
        -                this.responseHeaders = {};
        -
        -                if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) {
        -                    this.readyStateChange(FakeXMLHttpRequest.DONE);
        -                    this.sendFlag = false;
        -                }
        -
        -                this.readyState = FakeXMLHttpRequest.UNSENT;
        -
        -                this.dispatchEvent(new sinon.Event("abort", false, false, this));
        -
        -                this.upload.dispatchEvent(new sinon.Event("abort", false, false, this));
        -
        -                if (typeof this.onerror === "function") {
        -                    this.onerror();
        -                }
        -            },
        -
        -            getResponseHeader: function getResponseHeader(header) {
        -                if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
        -                    return null;
        -                }
        -
        -                if (/^Set-Cookie2?$/i.test(header)) {
        -                    return null;
        -                }
        -
        -                header = getHeader(this.responseHeaders, header);
        -
        -                return this.responseHeaders[header] || null;
        -            },
        -
        -            getAllResponseHeaders: function getAllResponseHeaders() {
        -                if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
        -                    return "";
        -                }
        -
        -                var headers = "";
        -
        -                for (var header in this.responseHeaders) {
        -                    if (this.responseHeaders.hasOwnProperty(header) &&
        -                        !/^Set-Cookie2?$/i.test(header)) {
        -                        headers += header + ": " + this.responseHeaders[header] + "\r\n";
        -                    }
        -                }
        -
        -                return headers;
        -            },
        -
        -            setResponseBody: function setResponseBody(body) {
        -                verifyRequestSent(this);
        -                verifyHeadersReceived(this);
        -                verifyResponseBodyType(body);
        -
        -                var chunkSize = this.chunkSize || 10;
        -                var index = 0;
        -                this.responseText = "";
        -
        -                do {
        -                    if (this.async) {
        -                        this.readyStateChange(FakeXMLHttpRequest.LOADING);
        -                    }
        -
        -                    this.responseText += body.substring(index, index + chunkSize);
        -                    index += chunkSize;
        -                } while (index < body.length);
        -
        -                var type = this.getResponseHeader("Content-Type");
        -
        -                if (this.responseText &&
        -                    (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) {
        -                    try {
        -                        this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText);
        -                    } catch (e) {
        -                        // Unable to parse XML - no biggie
        -                    }
        -                }
        -
        -                this.response = this.responseType === "json" ? JSON.parse(this.responseText) : this.responseText;
        -                this.readyStateChange(FakeXMLHttpRequest.DONE);
        -            },
        -
        -            respond: function respond(status, headers, body) {
        -                this.status = typeof status == "number" ? status : 200;
        -                this.statusText = FakeXMLHttpRequest.statusCodes[this.status];
        -                this.setResponseHeaders(headers || {});
        -                this.setResponseBody(body || "");
        -            },
        -
        -            uploadProgress: function uploadProgress(progressEventRaw) {
        -                if (supportsProgress) {
        -                    this.upload.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw));
        -                }
        -            },
        -
        -            downloadProgress: function downloadProgress(progressEventRaw) {
        -                if (supportsProgress) {
        -                    this.dispatchEvent(new sinon.ProgressEvent("progress", progressEventRaw));
        -                }
        -            },
        -
        -            uploadError: function uploadError(error) {
        -                if (supportsCustomEvent) {
        -                    this.upload.dispatchEvent(new sinon.CustomEvent("error", {detail: error}));
        -                }
        -            }
        -        });
        -
        -        sinon.extend(FakeXMLHttpRequest, {
        -            UNSENT: 0,
        -            OPENED: 1,
        -            HEADERS_RECEIVED: 2,
        -            LOADING: 3,
        -            DONE: 4
        -        });
        -
        -        sinon.useFakeXMLHttpRequest = function () {
        -            FakeXMLHttpRequest.restore = function restore(keepOnCreate) {
        -                if (sinonXhr.supportsXHR) {
        -                    global.XMLHttpRequest = sinonXhr.GlobalXMLHttpRequest;
        -                }
        -
        -                if (sinonXhr.supportsActiveX) {
        -                    global.ActiveXObject = sinonXhr.GlobalActiveXObject;
        -                }
        -
        -                delete FakeXMLHttpRequest.restore;
        -
        -                if (keepOnCreate !== true) {
        -                    delete FakeXMLHttpRequest.onCreate;
        -                }
        -            };
        -            if (sinonXhr.supportsXHR) {
        -                global.XMLHttpRequest = FakeXMLHttpRequest;
        -            }
        -
        -            if (sinonXhr.supportsActiveX) {
        -                global.ActiveXObject = function ActiveXObject(objId) {
        -                    if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) {
        -
        -                        return new FakeXMLHttpRequest();
        -                    }
        -
        -                    return new sinonXhr.GlobalActiveXObject(objId);
        -                };
        -            }
        -
        -            return FakeXMLHttpRequest;
        -        };
        -
        -        sinon.FakeXMLHttpRequest = FakeXMLHttpRequest;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./core");
        -        require("../extend");
        -        require("./event");
        -        require("../log_error");
        -        makeApi(sinon);
        -        module.exports = sinon;
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (typeof sinon === "undefined") {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -
        -})(typeof global !== "undefined" ? global : self);
        -
        -/**
        - * @depend fake_xdomain_request.js
        - * @depend fake_xml_http_request.js
        - * @depend ../format.js
        - * @depend ../log_error.js
        - */
        -/**
        - * The Sinon "server" mimics a web server that receives requests from
        - * sinon.FakeXMLHttpRequest and provides an API to respond to those requests,
        - * both synchronously and asynchronously. To respond synchronuously, canned
        - * answers have to be provided upfront.
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -if (typeof sinon == "undefined") {
        -    var sinon = {};
        -}
        -
        -(function () {
        -    var push = [].push;
        -    function F() {}
        -
        -    function create(proto) {
        -        F.prototype = proto;
        -        return new F();
        -    }
        -
        -    function responseArray(handler) {
        -        var response = handler;
        -
        -        if (Object.prototype.toString.call(handler) != "[object Array]") {
        -            response = [200, {}, handler];
        -        }
        -
        -        if (typeof response[2] != "string") {
        -            throw new TypeError("Fake server response body should be string, but was " +
        -                                typeof response[2]);
        -        }
        -
        -        return response;
        -    }
        -
        -    var wloc = typeof window !== "undefined" ? window.location : {};
        -    var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host);
        -
        -    function matchOne(response, reqMethod, reqUrl) {
        -        var rmeth = response.method;
        -        var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase();
        -        var url = response.url;
        -        var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl));
        -
        -        return matchMethod && matchUrl;
        -    }
        -
        -    function match(response, request) {
        -        var requestUrl = request.url;
        -
        -        if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) {
        -            requestUrl = requestUrl.replace(rCurrLoc, "");
        -        }
        -
        -        if (matchOne(response, this.getHTTPMethod(request), requestUrl)) {
        -            if (typeof response.response == "function") {
        -                var ru = response.url;
        -                var args = [request].concat(ru && typeof ru.exec == "function" ? ru.exec(requestUrl).slice(1) : []);
        -                return response.response.apply(response, args);
        -            }
        -
        -            return true;
        -        }
        -
        -        return false;
        -    }
        -
        -    function makeApi(sinon) {
        -        sinon.fakeServer = {
        -            create: function () {
        -                var server = create(this);
        -                if (!sinon.xhr.supportsCORS) {
        -                    this.xhr = sinon.useFakeXDomainRequest();
        -                } else {
        -                    this.xhr = sinon.useFakeXMLHttpRequest();
        -                }
        -                server.requests = [];
        -
        -                this.xhr.onCreate = function (xhrObj) {
        -                    server.addRequest(xhrObj);
        -                };
        -
        -                return server;
        -            },
        -
        -            addRequest: function addRequest(xhrObj) {
        -                var server = this;
        -                push.call(this.requests, xhrObj);
        -
        -                xhrObj.onSend = function () {
        -                    server.handleRequest(this);
        -
        -                    if (server.respondImmediately) {
        -                        server.respond();
        -                    } else if (server.autoRespond && !server.responding) {
        -                        setTimeout(function () {
        -                            server.responding = false;
        -                            server.respond();
        -                        }, server.autoRespondAfter || 10);
        -
        -                        server.responding = true;
        -                    }
        -                };
        -            },
        -
        -            getHTTPMethod: function getHTTPMethod(request) {
        -                if (this.fakeHTTPMethods && /post/i.test(request.method)) {
        -                    var matches = (request.requestBody || "").match(/_method=([^\b;]+)/);
        -                    return !!matches ? matches[1] : request.method;
        -                }
        -
        -                return request.method;
        -            },
        -
        -            handleRequest: function handleRequest(xhr) {
        -                if (xhr.async) {
        -                    if (!this.queue) {
        -                        this.queue = [];
        -                    }
        -
        -                    push.call(this.queue, xhr);
        -                } else {
        -                    this.processRequest(xhr);
        -                }
        -            },
        -
        -            log: function log(response, request) {
        -                var str;
        -
        -                str =  "Request:\n"  + sinon.format(request)  + "\n\n";
        -                str += "Response:\n" + sinon.format(response) + "\n\n";
        -
        -                sinon.log(str);
        -            },
        -
        -            respondWith: function respondWith(method, url, body) {
        -                if (arguments.length == 1 && typeof method != "function") {
        -                    this.response = responseArray(method);
        -                    return;
        -                }
        -
        -                if (!this.responses) {
        -                    this.responses = [];
        -                }
        -
        -                if (arguments.length == 1) {
        -                    body = method;
        -                    url = method = null;
        -                }
        -
        -                if (arguments.length == 2) {
        -                    body = url;
        -                    url = method;
        -                    method = null;
        -                }
        -
        -                push.call(this.responses, {
        -                    method: method,
        -                    url: url,
        -                    response: typeof body == "function" ? body : responseArray(body)
        -                });
        -            },
        -
        -            respond: function respond() {
        -                if (arguments.length > 0) {
        -                    this.respondWith.apply(this, arguments);
        -                }
        -
        -                var queue = this.queue || [];
        -                var requests = queue.splice(0, queue.length);
        -                var request;
        -
        -                while (request = requests.shift()) {
        -                    this.processRequest(request);
        -                }
        -            },
        -
        -            processRequest: function processRequest(request) {
        -                try {
        -                    if (request.aborted) {
        -                        return;
        -                    }
        -
        -                    var response = this.response || [404, {}, ""];
        -
        -                    if (this.responses) {
        -                        for (var l = this.responses.length, i = l - 1; i >= 0; i--) {
        -                            if (match.call(this, this.responses[i], request)) {
        -                                response = this.responses[i].response;
        -                                break;
        -                            }
        -                        }
        -                    }
        -
        -                    if (request.readyState != 4) {
        -                        this.log(response, request);
        -
        -                        request.respond(response[0], response[1], response[2]);
        -                    }
        -                } catch (e) {
        -                    sinon.logError("Fake server request processing", e);
        -                }
        -            },
        -
        -            restore: function restore() {
        -                return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments);
        -            }
        -        };
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./core");
        -        require("./fake_xdomain_request");
        -        require("./fake_xml_http_request");
        -        require("../format");
        -        makeApi(sinon);
        -        module.exports = sinon;
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else {
        -        makeApi(sinon);
        -    }
        -}());
        -
        -/**
        - * @depend fake_server.js
        - * @depend fake_timers.js
        - */
        -/**
        - * Add-on for sinon.fakeServer that automatically handles a fake timer along with
        - * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery
        - * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead,
        - * it polls the object for completion with setInterval. Dispite the direct
        - * motivation, there is nothing jQuery-specific in this file, so it can be used
        - * in any environment where the ajax implementation depends on setInterval or
        - * setTimeout.
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -(function () {
        -    function makeApi(sinon) {
        -        function Server() {}
        -        Server.prototype = sinon.fakeServer;
        -
        -        sinon.fakeServerWithClock = new Server();
        -
        -        sinon.fakeServerWithClock.addRequest = function addRequest(xhr) {
        -            if (xhr.async) {
        -                if (typeof setTimeout.clock == "object") {
        -                    this.clock = setTimeout.clock;
        -                } else {
        -                    this.clock = sinon.useFakeTimers();
        -                    this.resetClock = true;
        -                }
        -
        -                if (!this.longestTimeout) {
        -                    var clockSetTimeout = this.clock.setTimeout;
        -                    var clockSetInterval = this.clock.setInterval;
        -                    var server = this;
        -
        -                    this.clock.setTimeout = function (fn, timeout) {
        -                        server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);
        -
        -                        return clockSetTimeout.apply(this, arguments);
        -                    };
        -
        -                    this.clock.setInterval = function (fn, timeout) {
        -                        server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);
        -
        -                        return clockSetInterval.apply(this, arguments);
        -                    };
        -                }
        -            }
        -
        -            return sinon.fakeServer.addRequest.call(this, xhr);
        -        };
        -
        -        sinon.fakeServerWithClock.respond = function respond() {
        -            var returnVal = sinon.fakeServer.respond.apply(this, arguments);
        -
        -            if (this.clock) {
        -                this.clock.tick(this.longestTimeout || 0);
        -                this.longestTimeout = 0;
        -
        -                if (this.resetClock) {
        -                    this.clock.restore();
        -                    this.resetClock = false;
        -                }
        -            }
        -
        -            return returnVal;
        -        };
        -
        -        sinon.fakeServerWithClock.restore = function restore() {
        -            if (this.clock) {
        -                this.clock.restore();
        -            }
        -
        -            return sinon.fakeServer.restore.apply(this, arguments);
        -        };
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require) {
        -        var sinon = require("./core");
        -        require("./fake_server");
        -        require("./fake_timers");
        -        makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require);
        -    } else {
        -        makeApi(sinon);
        -    }
        -}());
        -
        -/**
        - * @depend util/core.js
        - * @depend extend.js
        - * @depend collection.js
        - * @depend util/fake_timers.js
        - * @depend util/fake_server_with_clock.js
        - */
        -/**
        - * Manages fake collections as well as fake utilities such as Sinon's
        - * timers and fake XHR implementation in one convenient object.
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -(function () {
        -    function makeApi(sinon) {
        -        var push = [].push;
        -
        -        function exposeValue(sandbox, config, key, value) {
        -            if (!value) {
        -                return;
        -            }
        -
        -            if (config.injectInto && !(key in config.injectInto)) {
        -                config.injectInto[key] = value;
        -                sandbox.injectedKeys.push(key);
        -            } else {
        -                push.call(sandbox.args, value);
        -            }
        -        }
        -
        -        function prepareSandboxFromConfig(config) {
        -            var sandbox = sinon.create(sinon.sandbox);
        -
        -            if (config.useFakeServer) {
        -                if (typeof config.useFakeServer == "object") {
        -                    sandbox.serverPrototype = config.useFakeServer;
        -                }
        -
        -                sandbox.useFakeServer();
        -            }
        -
        -            if (config.useFakeTimers) {
        -                if (typeof config.useFakeTimers == "object") {
        -                    sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers);
        -                } else {
        -                    sandbox.useFakeTimers();
        -                }
        -            }
        -
        -            return sandbox;
        -        }
        -
        -        sinon.sandbox = sinon.extend(sinon.create(sinon.collection), {
        -            useFakeTimers: function useFakeTimers() {
        -                this.clock = sinon.useFakeTimers.apply(sinon, arguments);
        -
        -                return this.add(this.clock);
        -            },
        -
        -            serverPrototype: sinon.fakeServer,
        -
        -            useFakeServer: function useFakeServer() {
        -                var proto = this.serverPrototype || sinon.fakeServer;
        -
        -                if (!proto || !proto.create) {
        -                    return null;
        -                }
        -
        -                this.server = proto.create();
        -                return this.add(this.server);
        -            },
        -
        -            inject: function (obj) {
        -                sinon.collection.inject.call(this, obj);
        -
        -                if (this.clock) {
        -                    obj.clock = this.clock;
        -                }
        -
        -                if (this.server) {
        -                    obj.server = this.server;
        -                    obj.requests = this.server.requests;
        -                }
        -
        -                obj.match = sinon.match;
        -
        -                return obj;
        -            },
        -
        -            restore: function () {
        -                sinon.collection.restore.apply(this, arguments);
        -                this.restoreContext();
        -            },
        -
        -            restoreContext: function () {
        -                if (this.injectedKeys) {
        -                    for (var i = 0, j = this.injectedKeys.length; i < j; i++) {
        -                        delete this.injectInto[this.injectedKeys[i]];
        -                    }
        -                    this.injectedKeys = [];
        -                }
        -            },
        -
        -            create: function (config) {
        -                if (!config) {
        -                    return sinon.create(sinon.sandbox);
        -                }
        -
        -                var sandbox = prepareSandboxFromConfig(config);
        -                sandbox.args = sandbox.args || [];
        -                sandbox.injectedKeys = [];
        -                sandbox.injectInto = config.injectInto;
        -                var prop, value, exposed = sandbox.inject({});
        -
        -                if (config.properties) {
        -                    for (var i = 0, l = config.properties.length; i < l; i++) {
        -                        prop = config.properties[i];
        -                        value = exposed[prop] || prop == "sandbox" && sandbox;
        -                        exposeValue(sandbox, config, prop, value);
        -                    }
        -                } else {
        -                    exposeValue(sandbox, config, "sandbox", value);
        -                }
        -
        -                return sandbox;
        -            },
        -
        -            match: sinon.match
        -        });
        -
        -        sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer;
        -
        -        return sinon.sandbox;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        require("./extend");
        -        require("./util/fake_server_with_clock");
        -        require("./util/fake_timers");
        -        require("./collection");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}());
        -
        -/**
        - * @depend util/core.js
        - * @depend sandbox.js
        - */
        -/**
        - * Test function, sandboxes fakes
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -(function (sinon) {
        -    function makeApi(sinon) {
        -        var slice = Array.prototype.slice;
        -
        -        function test(callback) {
        -            var type = typeof callback;
        -
        -            if (type != "function") {
        -                throw new TypeError("sinon.test needs to wrap a test function, got " + type);
        -            }
        -
        -            function sinonSandboxedTest() {
        -                var config = sinon.getConfig(sinon.config);
        -                config.injectInto = config.injectIntoThis && this || config.injectInto;
        -                var sandbox = sinon.sandbox.create(config);
        -                var args = slice.call(arguments);
        -                var oldDone = args.length && args[args.length - 1];
        -                var exception, result;
        -
        -                if (typeof oldDone == "function") {
        -                    args[args.length - 1] = function sinonDone(result) {
        -                        if (result) {
        -                            sandbox.restore();
        -                            throw exception;
        -                        } else {
        -                            sandbox.verifyAndRestore();
        -                        }
        -                        oldDone(result);
        -                    };
        -                }
        -
        -                try {
        -                    result = callback.apply(this, args.concat(sandbox.args));
        -                } catch (e) {
        -                    exception = e;
        -                }
        -
        -                if (typeof oldDone != "function") {
        -                    if (typeof exception !== "undefined") {
        -                        sandbox.restore();
        -                        throw exception;
        -                    } else {
        -                        sandbox.verifyAndRestore();
        -                    }
        -                }
        -
        -                return result;
        -            }
        -
        -            if (callback.length) {
        -                return function sinonAsyncSandboxedTest(callback) {
        -                    return sinonSandboxedTest.apply(this, arguments);
        -                };
        -            }
        -
        -            return sinonSandboxedTest;
        -        }
        -
        -        test.config = {
        -            injectIntoThis: true,
        -            injectInto: null,
        -            properties: ["spy", "stub", "mock", "clock", "server", "requests"],
        -            useFakeTimers: true,
        -            useFakeServer: true
        -        };
        -
        -        sinon.test = test;
        -        return test;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        require("./sandbox");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (sinon) {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        - * @depend util/core.js
        - * @depend test.js
        - */
        -/**
        - * Test case, sandboxes all test functions
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -(function (sinon) {
        -    function createTest(property, setUp, tearDown) {
        -        return function () {
        -            if (setUp) {
        -                setUp.apply(this, arguments);
        -            }
        -
        -            var exception, result;
        -
        -            try {
        -                result = property.apply(this, arguments);
        -            } catch (e) {
        -                exception = e;
        -            }
        -
        -            if (tearDown) {
        -                tearDown.apply(this, arguments);
        -            }
        -
        -            if (exception) {
        -                throw exception;
        -            }
        -
        -            return result;
        -        };
        -    }
        -
        -    function makeApi(sinon) {
        -        function testCase(tests, prefix) {
        -            if (!tests || typeof tests != "object") {
        -                throw new TypeError("sinon.testCase needs an object with test functions");
        -            }
        -
        -            prefix = prefix || "test";
        -            var rPrefix = new RegExp("^" + prefix);
        -            var methods = {}, testName, property, method;
        -            var setUp = tests.setUp;
        -            var tearDown = tests.tearDown;
        -
        -            for (testName in tests) {
        -                if (tests.hasOwnProperty(testName) && !/^(setUp|tearDown)$/.test(testName)) {
        -                    property = tests[testName];
        -
        -                    if (typeof property == "function" && rPrefix.test(testName)) {
        -                        method = property;
        -
        -                        if (setUp || tearDown) {
        -                            method = createTest(property, setUp, tearDown);
        -                        }
        -
        -                        methods[testName] = sinon.test(method);
        -                    } else {
        -                        methods[testName] = tests[testName];
        -                    }
        -                }
        -            }
        -
        -            return methods;
        -        }
        -
        -        sinon.testCase = testCase;
        -        return testCase;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        require("./test");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -}(typeof sinon == "object" && sinon || null));
        -
        -/**
        - * @depend times_in_words.js
        - * @depend util/core.js
        - * @depend match.js
        - * @depend format.js
        - */
        -/**
        - * Assertions matching the test spy retrieval interface.
        - *
        - * @author Christian Johansen (christian@cjohansen.no)
        - * @license BSD
        - *
        - * Copyright (c) 2010-2013 Christian Johansen
        - */
        -
        -(function (sinon, global) {
        -    var slice = Array.prototype.slice;
        -
        -    function makeApi(sinon) {
        -        var assert;
        -
        -        function verifyIsStub() {
        -            var method;
        -
        -            for (var i = 0, l = arguments.length; i < l; ++i) {
        -                method = arguments[i];
        -
        -                if (!method) {
        -                    assert.fail("fake is not a spy");
        -                }
        -
        -                if (method.proxy && method.proxy.isSinonProxy) {
        -                    verifyIsStub(method.proxy);
        -                } else {
        -                    if (typeof method != "function") {
        -                        assert.fail(method + " is not a function");
        -                    }
        -
        -                    if (typeof method.getCall != "function") {
        -                        assert.fail(method + " is not stubbed");
        -                    }
        -                }
        -
        -            }
        -        }
        -
        -        function failAssertion(object, msg) {
        -            object = object || global;
        -            var failMethod = object.fail || assert.fail;
        -            failMethod.call(object, msg);
        -        }
        -
        -        function mirrorPropAsAssertion(name, method, message) {
        -            if (arguments.length == 2) {
        -                message = method;
        -                method = name;
        -            }
        -
        -            assert[name] = function (fake) {
        -                verifyIsStub(fake);
        -
        -                var args = slice.call(arguments, 1);
        -                var failed = false;
        -
        -                if (typeof method == "function") {
        -                    failed = !method(fake);
        -                } else {
        -                    failed = typeof fake[method] == "function" ?
        -                        !fake[method].apply(fake, args) : !fake[method];
        -                }
        -
        -                if (failed) {
        -                    failAssertion(this, (fake.printf || fake.proxy.printf).apply(fake, [message].concat(args)));
        -                } else {
        -                    assert.pass(name);
        -                }
        -            };
        -        }
        -
        -        function exposedName(prefix, prop) {
        -            return !prefix || /^fail/.test(prop) ? prop :
        -                prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1);
        -        }
        -
        -        assert = {
        -            failException: "AssertError",
        -
        -            fail: function fail(message) {
        -                var error = new Error(message);
        -                error.name = this.failException || assert.failException;
        -
        -                throw error;
        -            },
        -
        -            pass: function pass(assertion) {},
        -
        -            callOrder: function assertCallOrder() {
        -                verifyIsStub.apply(null, arguments);
        -                var expected = "", actual = "";
        -
        -                if (!sinon.calledInOrder(arguments)) {
        -                    try {
        -                        expected = [].join.call(arguments, ", ");
        -                        var calls = slice.call(arguments);
        -                        var i = calls.length;
        -                        while (i) {
        -                            if (!calls[--i].called) {
        -                                calls.splice(i, 1);
        -                            }
        -                        }
        -                        actual = sinon.orderByFirstCall(calls).join(", ");
        -                    } catch (e) {
        -                        // If this fails, we'll just fall back to the blank string
        -                    }
        -
        -                    failAssertion(this, "expected " + expected + " to be " +
        -                                "called in order but were called as " + actual);
        -                } else {
        -                    assert.pass("callOrder");
        -                }
        -            },
        -
        -            callCount: function assertCallCount(method, count) {
        -                verifyIsStub(method);
        -
        -                if (method.callCount != count) {
        -                    var msg = "expected %n to be called " + sinon.timesInWords(count) +
        -                        " but was called %c%C";
        -                    failAssertion(this, method.printf(msg));
        -                } else {
        -                    assert.pass("callCount");
        -                }
        -            },
        -
        -            expose: function expose(target, options) {
        -                if (!target) {
        -                    throw new TypeError("target is null or undefined");
        -                }
        -
        -                var o = options || {};
        -                var prefix = typeof o.prefix == "undefined" && "assert" || o.prefix;
        -                var includeFail = typeof o.includeFail == "undefined" || !!o.includeFail;
        -
        -                for (var method in this) {
        -                    if (method != "expose" && (includeFail || !/^(fail)/.test(method))) {
        -                        target[exposedName(prefix, method)] = this[method];
        -                    }
        -                }
        -
        -                return target;
        -            },
        -
        -            match: function match(actual, expectation) {
        -                var matcher = sinon.match(expectation);
        -                if (matcher.test(actual)) {
        -                    assert.pass("match");
        -                } else {
        -                    var formatted = [
        -                        "expected value to match",
        -                        "    expected = " + sinon.format(expectation),
        -                        "    actual = " + sinon.format(actual)
        -                    ]
        -                    failAssertion(this, formatted.join("\n"));
        -                }
        -            }
        -        };
        -
        -        mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called");
        -        mirrorPropAsAssertion("notCalled", function (spy) {
        -            return !spy.called;
        -        }, "expected %n to not have been called but was called %c%C");
        -        mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C");
        -        mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C");
        -        mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C");
        -        mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t");
        -        mirrorPropAsAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t");
        -        mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new");
        -        mirrorPropAsAssertion("alwaysCalledWithNew", "expected %n to always be called with new");
        -        mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C");
        -        mirrorPropAsAssertion("calledWithMatch", "expected %n to be called with match %*%C");
        -        mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C");
        -        mirrorPropAsAssertion("alwaysCalledWithMatch", "expected %n to always be called with match %*%C");
        -        mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C");
        -        mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C");
        -        mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C");
        -        mirrorPropAsAssertion("neverCalledWithMatch", "expected %n to never be called with match %*%C");
        -        mirrorPropAsAssertion("threw", "%n did not throw exception%C");
        -        mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C");
        -
        -        sinon.assert = assert;
        -        return assert;
        -    }
        -
        -    var isNode = typeof module !== "undefined" && module.exports && typeof require == "function";
        -    var isAMD = typeof define === "function" && typeof define.amd === "object" && define.amd;
        -
        -    function loadDependencies(require, exports, module) {
        -        var sinon = require("./util/core");
        -        require("./match");
        -        require("./format");
        -        module.exports = makeApi(sinon);
        -    }
        -
        -    if (isAMD) {
        -        define(loadDependencies);
        -    } else if (isNode) {
        -        loadDependencies(require, module.exports, module);
        -    } else if (!sinon) {
        -        return;
        -    } else {
        -        makeApi(sinon);
        -    }
        -
        -}(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : (typeof self != "undefined") ? self : global));
        -
        -  return sinon;
        -}));
        diff --git a/test/manual-test-examples/addons/p5.sound/autoCorrelation/sketch.js b/test/manual-test-examples/addons/p5.sound/autoCorrelation/sketch.js
        index 733963440b..f86b6e94a1 100644
        --- a/test/manual-test-examples/addons/p5.sound/autoCorrelation/sketch.js
        +++ b/test/manual-test-examples/addons/p5.sound/autoCorrelation/sketch.js
        @@ -36,7 +36,7 @@ function draw() {
           for (var i = 0; i < corrBuff.length; i++) {
             var w = map(i, 0, corrBuff.length, 0, width);
             var h = map(corrBuff[i], -1, 1, height, 0);
        -    curveVertex(w, h);
        +    splineVertex(w, h);
           }
           endShape();
         }
        diff --git a/test/manual-test-examples/p5.Font/Helvetica.ttf b/test/manual-test-examples/p5.Font/Helvetica.ttf
        deleted file mode 100644
        index 3019c69fb3..0000000000
        Binary files a/test/manual-test-examples/p5.Font/Helvetica.ttf and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/Merriweather-LightItalic.ttf b/test/manual-test-examples/p5.Font/Merriweather-LightItalic.ttf
        deleted file mode 100644
        index dfa087caa4..0000000000
        Binary files a/test/manual-test-examples/p5.Font/Merriweather-LightItalic.ttf and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/Montserrat-Regular.ttf b/test/manual-test-examples/p5.Font/Montserrat-Regular.ttf
        deleted file mode 100644
        index b4368631ef..0000000000
        Binary files a/test/manual-test-examples/p5.Font/Montserrat-Regular.ttf and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/OpenSans-Regular.ttf b/test/manual-test-examples/p5.Font/OpenSans-Regular.ttf
        deleted file mode 100644
        index db433349b7..0000000000
        Binary files a/test/manual-test-examples/p5.Font/OpenSans-Regular.ttf and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/SourceSansPro-Bold.ttf b/test/manual-test-examples/p5.Font/SourceSansPro-Bold.ttf
        deleted file mode 100755
        index 50d81bdad5..0000000000
        Binary files a/test/manual-test-examples/p5.Font/SourceSansPro-Bold.ttf and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/SourceSansPro-Italic.ttf b/test/manual-test-examples/p5.Font/SourceSansPro-Italic.ttf
        deleted file mode 100755
        index e5a1a86e63..0000000000
        Binary files a/test/manual-test-examples/p5.Font/SourceSansPro-Italic.ttf and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/SourceSansPro-Regular.otf b/test/manual-test-examples/p5.Font/SourceSansPro-Regular.otf
        deleted file mode 100644
        index 38941ae72f..0000000000
        Binary files a/test/manual-test-examples/p5.Font/SourceSansPro-Regular.otf and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/callback/index.html b/test/manual-test-examples/p5.Font/callback/index.html
        deleted file mode 100755
        index f917cd3877..0000000000
        --- a/test/manual-test-examples/p5.Font/callback/index.html
        +++ /dev/null
        @@ -1,13 +0,0 @@
        -<html>
        -<head>
        -  <script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
        -  <!-- uncomment lines below to include extra p5 libraries -->
        -  <!--<script language="javascript" src="../addons/p5.sound.js"></script>-->
        -  <script language="javascript" type="text/javascript" src="sketch.js"></script>
        -  <!-- this line removes any default padding and style. you might only need one of these values set. -->
        -  <style> body {padding: 0; margin: 0;} canvas{border: 1px solid #f0f0f0;}</style>
        -</head>
        -
        -<body>
        -</body>
        -</html>
        diff --git a/test/manual-test-examples/p5.Font/callback/sketch.js b/test/manual-test-examples/p5.Font/callback/sketch.js
        deleted file mode 100755
        index 7294d1d7bc..0000000000
        --- a/test/manual-test-examples/p5.Font/callback/sketch.js
        +++ /dev/null
        @@ -1,20 +0,0 @@
        -function setup() {
        -  createCanvas(240, 160);
        -  textSize(18);
        -  text('Default Text', 10, 30);
        -  noStroke();
        -  fill(0, 102, 153);
        -  text('Black No Stroke Text', 10, 60);
        -  textSize(12);
        -  fill(120);
        -  loadFont('../SourceSansPro-Regular.otf', function(f) {
        -    textFont(f);
        -    text(
        -      'Simple long Text: Lorem Ipsum is simply dummy text of the printing and typesetting industry. ',
        -      10,
        -      90,
        -      220,
        -      60
        -    );
        -  });
        -}
        diff --git a/test/manual-test-examples/p5.Font/custom/LEFT.BL.lead.png b/test/manual-test-examples/p5.Font/custom/LEFT.BL.lead.png
        deleted file mode 100644
        index c7a64cee04..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/LEFT.BL.lead.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/LEFT.BOTTOM.lead.png b/test/manual-test-examples/p5.Font/custom/LEFT.BOTTOM.lead.png
        deleted file mode 100644
        index 2be46d776d..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/LEFT.BOTTOM.lead.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/LEFT.CENTER.lead.png b/test/manual-test-examples/p5.Font/custom/LEFT.CENTER.lead.png
        deleted file mode 100644
        index 50aacbea38..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/LEFT.CENTER.lead.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/LEFT.TOP.lead.png b/test/manual-test-examples/p5.Font/custom/LEFT.TOP.lead.png
        deleted file mode 100644
        index 580cb1252f..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/LEFT.TOP.lead.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/index.html b/test/manual-test-examples/p5.Font/custom/index.html
        deleted file mode 100755
        index 1f5dc86328..0000000000
        --- a/test/manual-test-examples/p5.Font/custom/index.html
        +++ /dev/null
        @@ -1,75 +0,0 @@
        -<html>
        -<head>
        -  <meta charset="UTF-8">
        -  <script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
        -  <!-- uncomment lines below to include extra p5 libraries -->
        -  <!--<script language="javascript" src="../addons/p5.sound.js"></script>-->
        -  <script language="javascript" type="text/javascript" src="sketch.js"></script>
        -  <!-- this line removes any default padding and style. you might only need one of these values set. -->
        -  <style> body {padding: 0; margin: 0;} canvas{border: 1px solid #f0f0f0; display: block;} img{ border: 1px solid #f0f;} div{ margin: 100px 0px;}</style>
        -</head>
        -
        -<body>
        -  <div id='textSketch'>
        -  <img src="textSketch.png" width="240" height="160"></img>
        -  </div>
        -  <div id='textLineSketch'>
        -  <img src="textLineSketch.png" width="960" height="160"></img>
        -  </div>
        -  <div id='textWrapSketch'>
        -  <img src="textWrapSketch.png" width="960" height="160"></img>
        -  </div>
        -  <div id='textFontSketch'>
        -  </div>
        -  <div id='textAlignSketch'>
        -  <img src="textAlignSketch.png" width="240" height="160"></img>
        -  </div>
        -  <div id='textLeadingSketch'>
        -  <img src="LEFT.TOP.lead.png" width="400" height="200"></img>
        -  </div>
        -  <div id='textLeadingSketch2'>
        -  <img src="LEFT.CENTER.lead.png" width="400" height="200"></img>
        -  </div>
        -  <div id='textLeadingSketch3'>
        -  <img src="LEFT.BL.lead.png" width="400" height="200"></img>
        -  </div>
        -  <div id='textLeadingSketch4'>
        -  <img src="LEFT.BOTTOM.lead.png" width="400" height="200"></img>
        -  </div>
        -  <div id='textAlignmentSketch' style="position:relative">
        -  <img src="textAlignmentSketch.png" width="400" height="800" style="display:inline-block"></img>
        -  </div>
        -  <div id='textVertAlignmentSketch' style="position:relative">
        -  <img src="textVertAlignmentSketch.png" width="1000" height="200"></img>
        -  </div> 
        -  <div id='textSizeSketch'>
        -  <img src="textSizeSketch.png" width="240" height="160"></img>
        -  </div>
        -  <div id='textBoundsSketch'>
        -  </div>
        -  <div id='textStyleSketch'>
        -  </div>
        -  <div id='textWidthSketch'>
        -  <img src="textWidthSketch.png" width="240" height="160"></img>
        -  </div>
        -  <div id='textOverlapSketch'>
        -  <img src="textOverlapSketch.png" width="240" height="160"></img>
        -  </div>
        -  <div id='textFlySketch'>
        -  </div>
        -  <div id='textFlickerSketch'>
        -  </div>
        -  <div id='textFadeSketch'>
        -  </div>
        -  <div id='textRotateSketch'>
        -  </div>
        -  <div id='textGrowSketch'>
        -  </div>
        -  <div id='textAvoidSketch'>
        -  </div>
        -  <div id='textBendSketch'>
        -  </div>
        -  <div id='typographyLetterSketch'>
        -  </div>
        -</body>
        -</html>
        diff --git a/test/manual-test-examples/p5.Font/custom/sketch.js b/test/manual-test-examples/p5.Font/custom/sketch.js
        deleted file mode 100755
        index b7243e0d64..0000000000
        --- a/test/manual-test-examples/p5.Font/custom/sketch.js
        +++ /dev/null
        @@ -1,897 +0,0 @@
        -var textSketch = function(p) {
        -  var font, font2;
        -  p.preload = function() {
        -    //font = p.loadFont('../acmesa.ttf');
        -    font2 = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    //p.ellipse(20,20,50,70);
        -    //p.textFont(font);
        -    //p.text('Default Text', 10, 30);
        -    p.textSize(18);
        -    p.textFont(font2);
        -    p.noStroke();
        -    p.fill(0, 102, 153);
        -    p.text('Blue No Stroke Text', 10, 60);
        -    //p.stroke(0, 200, 0);
        -    //p.strokeWeight(0.5);
        -    //p.text('Blue with Green Stroked Text', 10, 90);
        -    p.noStroke();
        -    p.textSize(12);
        -    p.fill(120);
        -    p.text(
        -      'Simple long Text: Lorem Ipsum is simply dummy text of the printing and typesetting industry. ',
        -      10,
        -      90,
        -      220,
        -      60
        -    );
        -  };
        -};
        -
        -var textLineSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(240 * 4, 160);
        -    p.textFont(font);
        -    p.textSize(10);
        -    p.stroke(0);
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(10, 10, 220, 10);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.TOP);
        -    p.text('LEFT TOP is simply dummy text.', 10, 10);
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(10, 60, 220, 60);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.TOP);
        -    p.text('CENTER TOP is simply dummy text.', 10, 60);
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(10, 110, 220, 110);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.TOP);
        -    p.text('RIGHT TOP is simply dummy text.', 10, 110);
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(250, 10, 470, 10);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.CENTER);
        -    p.text('LEFT CENTER is simply dummy text.', 250, 10);
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(250, 60, 470, 60);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.CENTER);
        -    p.text('CENTER CENTER is simply dummy text.', 250, 60);
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(250, 110, 470, 110);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.CENTER);
        -    p.text('RIGHT CENTER is simply dummy text.', 250, 110);
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(490, 10, 710, 10);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.BOTTOM);
        -    p.text('LEFT BOTTOM is simply dummy text.', 490, 10);
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(490, 60, 710, 60);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.BOTTOM);
        -    p.text('CENTER BOTTOM is simply dummy text.', 490, 60);
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(490, 110, 710, 110);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.BOTTOM);
        -    p.text('RIGHT BOTTOM is simply dummy text.', 490, 110);
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(730, 10, 950, 10);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.BASELINE);
        -    p.text('LEFT BASELINE is simply dummy text.', 730, 10);
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(730, 60, 950, 60);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.BASELINE);
        -    p.text('CENTER BASELINE is simply dummy text.', 730, 60);
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(730, 110, 950, 110);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.BASELINE);
        -    p.text('RIGHT BASELINE is simply dummy text.', 730, 110);
        -  };
        -};
        -
        -var textWrapSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(240 * 4, 160);
        -    p.textFont(font);
        -    p.textSize(10);
        -    p.stroke(0);
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(10, 10, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.TOP);
        -    p.text(
        -      'LEFT TOP is simply dummy text of the printing and typesetting industry. ',
        -      10,
        -      10,
        -      220,
        -      40
        -    );
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(10, 60, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.TOP);
        -    p.text(
        -      'CENTER TOP is simply dummy text of the printing and typesetting industry. ',
        -      10,
        -      60,
        -      220,
        -      40
        -    );
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(10, 110, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.TOP);
        -    p.text(
        -      'RIGHT TOP is simply dummy text of the printing and typesetting industry. ',
        -      10,
        -      110,
        -      220,
        -      40
        -    );
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(250, 10, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.CENTER);
        -    p.text(
        -      'LEFT CENTER is simply dummy text of the printing and typesetting industry. ',
        -      250,
        -      10,
        -      220,
        -      40
        -    );
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(250, 60, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.CENTER);
        -    p.text(
        -      'CENTER CENTER is simply dummy text of the printing and typesetting industry. ',
        -      250,
        -      60,
        -      220,
        -      40
        -    );
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(250, 110, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.CENTER);
        -    p.text(
        -      'RIGHT CENTER is simply dummy text of the printing and typesetting industry. ',
        -      250,
        -      110,
        -      220,
        -      40
        -    );
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(490, 10, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.BOTTOM);
        -    p.text(
        -      'LEFT BOTTOM is simply dummy text of the printing and typesetting industry. ',
        -      490,
        -      10,
        -      220,
        -      40
        -    );
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(490, 60, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.BOTTOM);
        -    p.text(
        -      'CENTER BOTTOM is simply dummy text of the printing and typesetting industry. ',
        -      490,
        -      60,
        -      220,
        -      40
        -    );
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(490, 110, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.BOTTOM);
        -    p.text(
        -      'RIGHT BOTTOM is simply dummy text of the printing and typesetting industry. ',
        -      490,
        -      110,
        -      220,
        -      40
        -    );
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(730, 10, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.BASELINE);
        -    p.text(
        -      'LEFT BASELINE is simply dummy text of the printing and typesetting industry. ',
        -      730,
        -      10,
        -      220,
        -      40
        -    );
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(730, 60, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.BASELINE);
        -    p.text(
        -      'CENTER BASELINE is simply dummy text of the printing and typesetting industry. ',
        -      730,
        -      60,
        -      220,
        -      40
        -    );
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(730, 110, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.BASELINE);
        -    p.text(
        -      'RIGHT BASELINE is simply dummy text of the printing and typesetting industry. ',
        -      730,
        -      110,
        -      220,
        -      40
        -    );
        -  };
        -};
        -
        -var textAlignSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.fill(0);
        -    p.strokeWeight(0);
        -    p.textSize(12);
        -    p.textAlign(p.RIGHT, p.TOP);
        -    p.text('Top Right', 120, 30);
        -    p.textAlign(p.CENTER, p.CENTER);
        -    p.text('Center Center', 120, 60);
        -    p.textAlign(p.LEFT, p.BOTTOM);
        -    p.text('Left Bottom', 120, 90);
        -    p.textAlign(p.RIGHT, p.BASELINE);
        -    p.text('Right Baseline', 120, 90);
        -    p.strokeWeight(1);
        -    p.line(120, 0, 120, 160);
        -  };
        -};
        -
        -var textLeadingSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(400, 200);
        -    p.textFont(font);
        -    p.fill(0);
        -    p.textSize(12);
        -
        -    p.line(0, 100, p.width, 100);
        -    p.textAlign(p.LEFT, p.TOP);
        -    p.strokeWeight(0);
        -
        -    var s10 = 'LEFT/TOP@10px',
        -      s20 = s10.replace('1', '2'),
        -      s30 = s10.replace('1', '3');
        -
        -    p.textLeading(10); // Set leading to 10
        -    p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        -    p.textLeading(20); // Set leading to 20
        -    p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        -    p.textLeading(30); // Set leading to 30
        -    p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        -  };
        -};
        -
        -var textLeadingSketch2 = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(400, 200);
        -    p.textFont(font);
        -    p.fill(0);
        -    p.textSize(12);
        -
        -    p.line(0, 100, p.width, 100);
        -    p.textAlign(p.LEFT, p.CENTER);
        -    p.strokeWeight(0);
        -
        -    var s10 = 'LEFT/CENTER@10px',
        -      s20 = s10.replace('1', '2'),
        -      s30 = s10.replace('1', '3');
        -
        -    p.textLeading(10); // Set leading to 10
        -    p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        -    p.textLeading(20); // Set leading to 20
        -    p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        -    p.textLeading(30); // Set leading to 30
        -    p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        -  };
        -};
        -
        -var textLeadingSketch3 = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(400, 200);
        -    p.textFont(font);
        -    p.fill(0);
        -    p.textSize(12);
        -
        -    p.line(0, 100, p.width, 100);
        -    p.textAlign(p.LEFT, p.BASELINE);
        -    p.strokeWeight(0);
        -
        -    var s10 = 'LEFT/BASELINE@10px',
        -      s20 = s10.replace('1', '2'),
        -      s30 = s10.replace('1', '3');
        -
        -    p.textLeading(10); // Set leading to 10
        -    p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        -    p.textLeading(20); // Set leading to 20
        -    p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        -    p.textLeading(30); // Set leading to 30
        -    p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        -  };
        -};
        -
        -var textLeadingSketch4 = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(400, 200);
        -    p.textFont(font);
        -    p.fill(0);
        -    p.textSize(12);
        -
        -    p.line(0, 100, p.width, 100);
        -    p.textAlign(p.LEFT, p.BOTTOM);
        -    p.strokeWeight(0);
        -
        -    var s10 = 'LEFT/BOTTOM@10px',
        -      s20 = s10.replace('1', '2'),
        -      s30 = s10.replace('1', '3');
        -
        -    p.textLeading(10); // Set leading to 10
        -    p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        -    p.textLeading(20); // Set leading to 20
        -    p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        -    p.textLeading(30); // Set leading to 30
        -    p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        -  };
        -};
        -
        -var textAlignmentSketch = function(p) {
        -  var font1, font2, font3, font4;
        -  var hAligns = [p.LEFT, p.CENTER, p.RIGHT];
        -  var vAligns = [p.TOP, p.CENTER, p.BASELINE, p.BOTTOM];
        -  var textString = 'Hello p5';
        -  var padding = 10;
        -  p.preload = function() {
        -    font1 = p.loadFont('../SourceSansPro-Regular.otf'); // different
        -    font2 = p.loadFont('../FiraSans-Book.otf');
        -    font3 = p.loadFont('../Inconsolata-Bold.ttf');
        -    font4 = p.loadFont('../PlayfairDisplay-Regular.ttf');
        -  };
        -  var drawFontAlignments = function(font, xOff, yOff) {
        -    p.textFont(font);
        -    p.textSize(20);
        -    for (var h = 0; h < hAligns.length; h += 1) {
        -      for (var v = 0; v < vAligns.length; v += 1) {
        -        // Distribute words across the screen
        -        var x = xOff + p.map(h, 0, hAligns.length - 1, padding, 400 - padding);
        -        var y = yOff + p.map(v, 0, vAligns.length - 1, padding, 200 - padding);
        -
        -        p.stroke(200);
        -        p.line(0, y, p.width, y);
        -        p.line(x, 0, x, p.height);
        -
        -        // Align the text & calculate the bounds
        -        p.textAlign(hAligns[h], vAligns[v]);
        -
        -        // Draw the text
        -        p.fill(255, 0, 0);
        -        p.noStroke();
        -        p.text(textString, x, y);
        -
        -        // Draw the (x, y) coordinates
        -        p.stroke(0);
        -        p.fill('#FF8132');
        -        p.ellipse(x, y, 3, 3);
        -      }
        -    }
        -  };
        -  p.setup = function() {
        -    var renderer = p.createCanvas(400, 800);
        -    renderer.elt.style.position = 'absolute';
        -    renderer.elt.style.top = '0';
        -    renderer.elt.style.left = '0';
        -    drawFontAlignments(font1, 0, 0);
        -    drawFontAlignments(font2, 0, 200);
        -    drawFontAlignments(font3, 0, 400);
        -    drawFontAlignments(font4, 0, 600);
        -  };
        -};
        -
        -var textVertAlignmentSketch = function(p) {
        -  var fontNames = [
        -    'acmesa.ttf',
        -    'FiraSans-Book.otf',
        -    'Lato-Black.ttf',
        -    'Inconsolata-Bold.ttf',
        -    'Merriweather-LightItalic.ttf',
        -    'Montserrat-Regular.ttf',
        -    'OpenSans-Regular.ttf',
        -    'SourceSansPro-Regular.otf' // different
        -  ];
        -  var fonts = [];
        -  var vAligns = [p.TOP, p.CENTER, p.BASELINE, p.BOTTOM];
        -  p.preload = function() {
        -    for (var i = 0; i < fontNames.length; i += 1) {
        -      fonts.push(p.loadFont('../' + fontNames[i]));
        -    }
        -  };
        -  var drawFontAlignments = function(font, xOff, yOff) {
        -    p.textFont(font);
        -    p.textSize(20);
        -    for (var v = 0; v < vAligns.length; v += 1) {
        -      // Distribute words across the screen
        -      var x = xOff;
        -      var y = yOff + p.map(v, 0, vAligns.length - 1, 10, p.height - 10);
        -
        -      p.stroke(200);
        -      p.line(0, y, p.width, y);
        -
        -      // Align the text & calculate the bounds
        -      p.textAlign(p.CENTER, vAligns[v]);
        -
        -      // Draw the text
        -      p.fill(255, 0, 0);
        -      p.noStroke();
        -      p.text('Hello p5', x, y);
        -
        -      // Draw the (x, y) coordinates
        -      p.stroke(0);
        -      p.fill('#FF8132');
        -      p.ellipse(x, y, 3, 3);
        -    }
        -  };
        -  p.setup = function() {
        -    var renderer = p.createCanvas(1000, 200);
        -    renderer.elt.style.position = 'absolute';
        -    renderer.elt.style.top = '0';
        -    renderer.elt.style.left = '0';
        -    for (var i = 0; i < fonts.length; i += 1) {
        -      var x = p.map(i, 0, fonts.length - 1, 100, p.width - 100);
        -      drawFontAlignments(fonts[i], x, 0);
        -    }
        -  };
        -};
        -
        -var textSizeSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.fill(0);
        -    p.strokeWeight(0);
        -    p.textSize(12);
        -    p.text('Font Size 12', 10, 30);
        -    p.textSize(14);
        -    p.text('Font Size 14', 10, 60);
        -    p.textSize(16);
        -    p.text('Font Size 16', 10, 90);
        -  };
        -};
        -
        -var textBoundsSketch = function(p) {
        -  var font;
        -  var text = 'Lorem ipsum dolor sit amet.';
        -  p.preload = function() {
        -    font = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.strokeWeight(1);
        -    p.textSize(16);
        -    var bbox = font.textBounds(text, 30, 60, 16);
        -    p.fill(255);
        -    p.stroke(0);
        -    p.rect(bbox.x, bbox.y, bbox.w, bbox.h);
        -    p.fill(0);
        -    p.strokeWeight(0);
        -    p.text(text, 30, 60);
        -  };
        -};
        -
        -var textStyleSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    fontRegular = p.loadFont('../SourceSansPro-Regular.otf');
        -    fontItalic = p.loadFont('../SourceSansPro-Italic.ttf');
        -    fontBold = p.loadFont('../SourceSansPro-Bold.ttf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p
        -      .fill(0)
        -      .strokeWeight(0)
        -      .textSize(24);
        -    p.textFont(fontRegular);
        -    p.text('Font Style Normal', 30, 50);
        -    p.textFont(fontItalic);
        -    p.text('Font Style Italic', 30, 80);
        -    p.textFont(fontBold);
        -    p.text('Font Style Bold', 30, 110);
        -  };
        -};
        -
        -var textWidthSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.fill(0);
        -    p.strokeWeight(0);
        -    p.textSize(12);
        -    var s = "What's the width of this line?";
        -    var textWidth = p.textWidth(s);
        -    p.text(s, 10, 30);
        -    p.rect(10, 30, textWidth, 2);
        -    p.text('width: ' + textWidth, 10, 60);
        -  };
        -};
        -
        -var textOverlapSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../SourceSansPro-Regular.otf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.fill(0);
        -    p.strokeWeight(0);
        -    p.textSize(72);
        -    p.fill(0, 160); // Black with low opacity
        -    p.text('O', 0, 100);
        -    p.text('V', 30, 100);
        -    p.text('E', 60, 100);
        -    p.text('R', 90, 100);
        -    p.text('L', 120, 100);
        -    p.text('A', 150, 100);
        -    p.text('P', 180, 100);
        -  };
        -};
        -
        -var textFlySketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../acmesa.ttf');
        -  };
        -  var x1 = 100;
        -  var x2 = 0;
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.fill(0);
        -    p.textSize(48);
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    p.text('Left', x1, 50);
        -    p.text('Right', x2, 150);
        -    x2 += 2.0;
        -    if (x2 > 240) {
        -      x2 = -100;
        -    }
        -    x1 -= 1.0;
        -    if (x1 < -100) {
        -      x1 = 240;
        -    }
        -  };
        -};
        -
        -var textFlickerSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../acmesa.ttf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.textSize(48);
        -    p.noStroke();
        -  };
        -  p.draw = function() {
        -    p.fill(204, 24);
        -    p.rect(0, 0, p.width, p.height);
        -    p.fill(0);
        -    p.text('flicker Text', p.random(-100, 240), p.random(-20, 160));
        -  };
        -};
        -
        -var textFadeSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../acmesa.ttf');
        -  };
        -  var opacity = 0;
        -  var direction = 1;
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.textSize(72);
        -    p.noStroke();
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    opacity += 4 * direction;
        -    if (opacity < 0 || opacity > 255) {
        -      direction = -direction;
        -    }
        -    p.fill(0, opacity);
        -    p.text('fade', 50, 100);
        -  };
        -};
        -
        -var textRotateSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../acmesa.ttf');
        -  };
        -  var angle = 0.0;
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.textSize(24);
        -    p.noStroke();
        -    p.fill(0);
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    angle += 0.05;
        -    p.push();
        -    p.translate(120, 80);
        -    p.scale((p.cos(angle / 4.0) + 1.2) * 2.0);
        -    p.rotate(angle);
        -    p.text('Rotating', 0, 0);
        -    p.pop();
        -  };
        -};
        -
        -var textGrowSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../acmesa.ttf');
        -  };
        -  var angle = 0.0;
        -  var str = 'GROW';
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.textSize(24);
        -    p.noStroke();
        -    p.fill(0, 0, 0, 120);
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    angle += 0.1;
        -    for (var i = 0; i < str.length; i++) {
        -      var c = p.sin(angle + i / p.PI);
        -      p.textSize((c + 1.0) * 40 + 10);
        -      p.text(str.charAt(i), i * 40 + 20, 100);
        -    }
        -  };
        -};
        -
        -var textAvoidSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../acmesa.ttf');
        -  };
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.textSize(24);
        -    p.noStroke();
        -    p.fill(0);
        -    p.textAlign(p.CENTER);
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    p.text('AVOID', p.width - p.mouseX, p.height - p.mouseY);
        -  };
        -};
        -
        -var textBendSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../acmesa.ttf');
        -  };
        -  var str = 'Flexibility';
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textFont(font);
        -    p.textSize(30);
        -    p.noStroke();
        -    p.fill(0);
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    p.push();
        -    p.translate(0, 33);
        -    for (var i = 0; i < str.length; i++) {
        -      var angle = p.map(p.mouseX, 0, p.width, 0, p.PI / 8);
        -      p.rotate(angle);
        -      p.text(str[i], 20, 0);
        -      p.translate(p.textWidth(str[i]) * 1.5, 0);
        -    }
        -    p.pop();
        -  };
        -};
        -
        -var typographyLetterSketch = function(p) {
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont('../acmesa.ttf');
        -  };
        -  var margin = 10;
        -  var gap = 46;
        -  var counter = 35;
        -  p.setup = function() {
        -    p.createCanvas(720, 320);
        -    p.textFont(font);
        -    p.background(0);
        -    p.textSize(24);
        -    p.textStyle(p.BOLD);
        -    p.textAlign(p.CENTER, p.CENTER);
        -    p.translate(margin * 4, margin * 4);
        -    for (var y = 0; y < p.height - gap; y += gap) {
        -      for (var x = 0; x < p.width - gap; x += gap) {
        -        var letter = p.char(counter);
        -        if (letter === 'P' || letter === '5') {
        -          p.fill(255, 204, 0);
        -        } else if (letter === 'J' || letter === 'S') {
        -          p.fill(204, 0, 255);
        -        } else {
        -          p.fill(255);
        -        }
        -        p.text(letter, x, y);
        -        counter++;
        -      }
        -    }
        -  };
        -};
        -
        -new p5(textSketch, 'textSketch');
        -new p5(textLineSketch, 'textLineSketch');
        -new p5(textWrapSketch, 'textWrapSketch');
        -new p5(textAlignSketch, 'textAlignSketch');
        -new p5(textLeadingSketch, 'textLeadingSketch');
        -new p5(textLeadingSketch2, 'textLeadingSketch2');
        -new p5(textLeadingSketch3, 'textLeadingSketch3');
        -new p5(textLeadingSketch4, 'textLeadingSketch4');
        -new p5(textAlignmentSketch, 'textAlignmentSketch');
        -new p5(textVertAlignmentSketch, 'textVertAlignmentSketch');
        -new p5(textSizeSketch, 'textSizeSketch');
        -new p5(textBoundsSketch, 'textBoundsSketch');
        -new p5(textStyleSketch, 'textStyleSketch');
        -new p5(textWidthSketch, 'textWidthSketch');
        -new p5(textOverlapSketch, 'textOverlapSketch');
        -new p5(textFlySketch, 'textFlySketch');
        -new p5(textFlickerSketch, 'textFlickerSketch');
        -new p5(textFadeSketch, 'textFadeSketch');
        -new p5(textRotateSketch, 'textRotateSketch');
        -new p5(textGrowSketch, 'textGrowSketch');
        -new p5(textAvoidSketch, 'textAvoidSketch');
        -new p5(textBendSketch, 'textBendSketch');
        -new p5(typographyLetterSketch, 'typographyLetterSketch');
        diff --git a/test/manual-test-examples/p5.Font/custom/textAlignSketch.png b/test/manual-test-examples/p5.Font/custom/textAlignSketch.png
        deleted file mode 100644
        index 571973b350..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/textAlignSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/textAlignmentSketch.png b/test/manual-test-examples/p5.Font/custom/textAlignmentSketch.png
        deleted file mode 100644
        index 6d6628f03c..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/textAlignmentSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/textLineSketch.png b/test/manual-test-examples/p5.Font/custom/textLineSketch.png
        deleted file mode 100644
        index 24d3b7fe00..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/textLineSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/textOverlapSketch.png b/test/manual-test-examples/p5.Font/custom/textOverlapSketch.png
        deleted file mode 100644
        index 17343cce1b..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/textOverlapSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/textSizeSketch.png b/test/manual-test-examples/p5.Font/custom/textSizeSketch.png
        deleted file mode 100644
        index 1813714359..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/textSizeSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/textSketch.png b/test/manual-test-examples/p5.Font/custom/textSketch.png
        deleted file mode 100644
        index 965c569a96..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/textSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/textVertAlignmentSketch.png b/test/manual-test-examples/p5.Font/custom/textVertAlignmentSketch.png
        deleted file mode 100644
        index 5ecce582b8..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/textVertAlignmentSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/textWidthSketch.png b/test/manual-test-examples/p5.Font/custom/textWidthSketch.png
        deleted file mode 100644
        index 5b640ac39a..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/textWidthSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/custom/textWrapSketch.png b/test/manual-test-examples/p5.Font/custom/textWrapSketch.png
        deleted file mode 100644
        index 622f09746f..0000000000
        Binary files a/test/manual-test-examples/p5.Font/custom/textWrapSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/opentype/AvenirNextLTPro-Demi.otf b/test/manual-test-examples/p5.Font/opentype/AvenirNextLTPro-Demi.otf
        deleted file mode 100644
        index 750b0c5994..0000000000
        Binary files a/test/manual-test-examples/p5.Font/opentype/AvenirNextLTPro-Demi.otf and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/opentype/index.html b/test/manual-test-examples/p5.Font/opentype/index.html
        deleted file mode 100644
        index 2a50b6a37e..0000000000
        --- a/test/manual-test-examples/p5.Font/opentype/index.html
        +++ /dev/null
        @@ -1,12 +0,0 @@
        -<html>
        -
        -<head>
        -  <link rel="stylesheet" href="../../styles.css">
        -  <script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
        -  <script language="javascript" type="text/javascript" src="sketch.js"></script>
        -</head>
        -
        -<body>
        -</body>
        -
        -</html>
        \ No newline at end of file
        diff --git a/test/manual-test-examples/p5.Font/opentype/sketch.js b/test/manual-test-examples/p5.Font/opentype/sketch.js
        deleted file mode 100644
        index f0cec39b59..0000000000
        --- a/test/manual-test-examples/p5.Font/opentype/sketch.js
        +++ /dev/null
        @@ -1,51 +0,0 @@
        -var font;
        -var snapDistance = 71;
        -
        -function preload() {
        -  font = loadFont('AvenirNextLTPro-Demi.otf');
        -}
        -
        -function setup() {
        -  createCanvas(720, 480);
        -  frameRate(24);
        -  fill(255);
        -  textSize(150);
        -}
        -
        -function draw() {
        -  background(237, 34, 93);
        -
        -  var path = font._getPath('p5*js', 170, 275);
        -  doSnap(path, snapDistance);
        -  font._renderPath(path);
        -
        -  if (--snapDistance === -26) {
        -    snapDistance = 67;
        -  }
        -}
        -
        -function doSnap(path, dist) {
        -  var i,
        -    value = dist <= 0 ? 1 : dist;
        -
        -  for (i = 0; i < path.commands.length; i++) {
        -    var cmd = path.commands[i];
        -    if (cmd.type !== 'Z') {
        -      cmd.x = snap(cmd.x, value);
        -      cmd.y = snap(cmd.y, value);
        -    }
        -    if (cmd.type === 'Q' || cmd.type === 'C') {
        -      cmd.x1 = snap(cmd.x1, value);
        -      cmd.y1 = snap(cmd.y1, value);
        -    }
        -    if (cmd.type === 'C') {
        -      cmd.x2 = snap(cmd.x2, value);
        -      cmd.y2 = snap(cmd.y2, value);
        -    }
        -  }
        -}
        -
        -// Round a value to the nearest "step".
        -function snap(v, distance) {
        -  return Math.round(v / distance) * distance;
        -}
        diff --git a/test/manual-test-examples/p5.Font/pathpoints/boids.js b/test/manual-test-examples/p5.Font/pathpoints/boids.js
        deleted file mode 100644
        index c868aa9c33..0000000000
        --- a/test/manual-test-examples/p5.Font/pathpoints/boids.js
        +++ /dev/null
        @@ -1,244 +0,0 @@
        -// adapted from Shiffman's The Nature of Code
        -// http://natureofcode.com
        -
        -class Boid {
        -  constructor(target) {
        -    this.acceleration = createVector(0, 0);
        -    this.velocity = createVector(random(-1, 1), random(-1, 1));
        -    this.position = createVector(width / 2, height / 2);
        -
        -    this.r = 3.0;
        -    this.maxspeed = 3; // Maximum speed
        -    this.maxforce = 0.05; // Maximum steering force
        -
        -    this.theta =
        -      p5.Vector.fromAngle(radians(target.alpha)).heading() + radians(90);
        -    this.target = createVector(target.x, target.y);
        -    this.arrived = false;
        -    this.hidden = true;
        -  }
        -  place (x, y) {
        -    this.position = createVector(mouseX, mouseY);
        -    this.velocity = p5.Vector.sub(
        -      createVector(mouseX, mouseY),
        -      createVector(pmouseX, pmouseY)
        -    );
        -    this.hidden = false;
        -  }
        -
        -  run (boids) {
        -    if (this.hidden)
        -      return;
        -
        -    if (flock.assemble) {
        -      this.arrive(this.target);
        -    } else {
        -      this.flock(boids);
        -    }
        -    this.update();
        -    this.borders();
        -    this.render();
        -  }
        -
        -  applyForce (force) {
        -    // We could add mass here if we want A = F / M
        -    this.acceleration.add(force);
        -  }
        -
        -  // We accumulate a new acceleration each time based on three rules
        -  flock (boids) {
        -    var sep = this.separate(boids); // Separation
        -    var ali = this.align(boids); // Alignment
        -    var coh = this.cohesion(boids); // Cohesion
        -
        -    // Arbitrarily weight these forces
        -    sep.mult(1.5);
        -    ali.mult(1.0);
        -    coh.mult(1.0);
        -    // Add the force vectors to acceleration
        -    this.applyForce(sep);
        -    this.applyForce(ali);
        -    this.applyForce(coh);
        -  }
        -
        -  // Method to update location
        -  update () {
        -    if (flock.assemble &&
        -        !this.arrived &&
        -        this.target.dist(this.position) < 1) {
        -      this.arrived = true;
        -      this.velocity = p5.Vector.fromAngle(this.theta + radians(90));
        -    } else {
        -      this.velocity.add(this.acceleration);
        -      this.velocity.limit(this.maxspeed);
        -      this.position.add(this.velocity);
        -      this.acceleration.mult(0);
        -    }
        -  }
        -
        -  seek (target) {
        -    var desired = p5.Vector.sub(target, this.position);
        -    // Normalize desired and scale to maximum speed
        -    desired.normalize();
        -    desired.mult(this.maxspeed);
        -    // Steering = Desired minus Velocity
        -    var steer = p5.Vector.sub(desired, this.velocity);
        -    steer.limit(this.maxforce); // Limit to maximum steering force
        -    return steer;
        -  }
        -
        -  render () {
        -    // Draw a triangle rotated in the direction of velocity
        -    var theta = this.velocity.heading() + radians(90);
        -    fill(255);
        -    noStroke();
        -    push();
        -    translate(this.position.x, this.position.y);
        -    rotate(theta);
        -    beginShape();
        -    vertex(0, -this.r * 2);
        -    vertex(-this.r, this.r * 2);
        -    vertex(this.r, this.r * 2);
        -    endShape(CLOSE);
        -    pop();
        -  }
        -
        -  // Wraparound
        -  borders () {
        -    if (this.position.x < -this.r)
        -      this.position.x = width + this.r;
        -    if (this.position.y < -this.r)
        -      this.position.y = height + this.r;
        -    if (this.position.x > width + this.r)
        -      this.position.x = -this.r;
        -    if (this.position.y > height + this.r)
        -      this.position.y = -this.r;
        -  }
        -
        -  // Separation
        -  // Method checks for nearby boids and steers away
        -  separate (boids) {
        -    var desiredseparation = 25.0;
        -    var steer = createVector(0, 0);
        -    var count = 0;
        -    // For every boid in the system, check if it's too close
        -    for (var i = 0; i < boids.length; i++) {
        -      var d = p5.Vector.dist(this.position, boids[i].position);
        -      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
        -      if (d > 0 && d < desiredseparation) {
        -        // Calculate vector pointing away from neighbor
        -        var diff = p5.Vector.sub(this.position, boids[i].position);
        -        diff.normalize();
        -        diff.div(d); // Weight by distance
        -        steer.add(diff);
        -        count++; // Keep track of how many
        -      }
        -    }
        -    // Average -- divide by how many
        -    if (count > 0) {
        -      steer.div(count);
        -    }
        -
        -    // As long as the vector is greater than 0
        -    if (steer.mag() > 0) {
        -      // Implement Reynolds: Steering = Desired - Velocity
        -      steer.normalize();
        -      steer.mult(this.maxspeed);
        -      steer.sub(this.velocity);
        -      steer.limit(this.maxforce);
        -    }
        -    return steer;
        -  }
        -
        -  // Alignment
        -  // For every nearby boid in the system, calculate the average velocity
        -  align (boids) {
        -    var neighbordist = 50;
        -    var sum = createVector(0, 0);
        -    var count = 0;
        -    for (var i = 0; i < boids.length; i++) {
        -      var d = p5.Vector.dist(this.position, boids[i].position);
        -      if (d > 0 && d < neighbordist) {
        -        sum.add(boids[i].velocity);
        -        count++;
        -      }
        -    }
        -    if (count > 0) {
        -      sum.div(count);
        -      sum.normalize();
        -      sum.mult(this.maxspeed);
        -      var steer = p5.Vector.sub(sum, this.velocity);
        -      steer.limit(this.maxforce);
        -      return steer;
        -    } else {
        -      return createVector(0, 0);
        -    }
        -  }
        -
        -  // Cohesion
        -  // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
        -  cohesion (boids) {
        -    var neighbordist = 50;
        -    var sum = createVector(0, 0); // Start with empty vector to accumulate all locations
        -    var num = 0;
        -    for (var i = 0; i < boids.length; i++) {
        -      var d = p5.Vector.dist(this.position, boids[i].position);
        -      if (d > 0 && d < neighbordist) {
        -        sum.add(boids[i].position); // Add location
        -        num++;
        -      }
        -    }
        -    if (num > 0) {
        -      return this.seek(sum.div(num)); // Steer towards the location
        -    } else {
        -      return createVector(0, 0);
        -    }
        -  }
        -
        -  arrive (target) {
        -    // A vector pointing from the location to the target
        -    var desired = p5.Vector.sub(target, this.position), d = desired.mag();
        -
        -    // Scale with arbitrary damping within 100 pixels
        -    desired.setMag(d < 100 ? map(d, 0, 100, 0, this.maxspeed) : this.maxspeed);
        -
        -    // Steering = Desired minus Velocity
        -    var steer = p5.Vector.sub(desired, this.velocity);
        -    steer.limit(this.maxforce); // Limit to maximum steering force
        -    this.applyForce(steer);
        -  }
        -
        -}
        -
        -function mouseOnScreen() {
        -  return mouseX && mouseX <= width && mouseY && mouseY <= height;
        -}
        -
        -class Flock {
        -  constructor() {
        -    this.count = 0;
        -    this.boids = [];
        -    this.assemble = false;
        -  }
        -  arrived() {
        -    var i;
        -    if (arguments.length) {
        -      for (i = 0; i < this.boids.length; i++)
        -        this.boids[i].arrived = arguments[0];
        -      if (!arguments[0]) this.count = 0;
        -    } else {
        -      for (i = 0; i < this.boids.length; i++)
        -        if (!this.boids[i].arrived) return false;
        -      return true;
        -    }
        -  }
        -
        -  run() {
        -    this.assemble = this.count === flock.boids.length;
        -
        -    if (!this.assemble && mouseOnScreen())
        -      this.boids[this.count++].place(mouseX, mouseY);
        -
        -    for (var i = 0; i < this.boids.length; i++) this.boids[i].run(this.boids);
        -  }
        -}
        diff --git a/test/manual-test-examples/p5.Font/pathpoints/index.html b/test/manual-test-examples/p5.Font/pathpoints/index.html
        deleted file mode 100644
        index 8eb7c4bc0c..0000000000
        --- a/test/manual-test-examples/p5.Font/pathpoints/index.html
        +++ /dev/null
        @@ -1,11 +0,0 @@
        -<html>
        -<head>
        -  <script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
        -  <script language="javascript" type="text/javascript" src="boids.js"></script>
        -  <script language="javascript" type="text/javascript" src="sketch.js"></script>
        -  <style> body {padding: 0; margin: 0;} canvas{border: 1px solid #f0f0f0;}</style>
        -</head>
        -
        -<body>
        -</body>
        -</html>
        diff --git a/test/manual-test-examples/p5.Font/pathpoints/sketch.js b/test/manual-test-examples/p5.Font/pathpoints/sketch.js
        deleted file mode 100644
        index da989e3b79..0000000000
        --- a/test/manual-test-examples/p5.Font/pathpoints/sketch.js
        +++ /dev/null
        @@ -1,27 +0,0 @@
        -// Adapted from Dan Shiffman's 'The Nature
        -// of Code': http://natureofcode.com
        -
        -var flock;
        -
        -function setup() {
        -  createCanvas(500, 300);
        -
        -  flock = new Flock();
        -
        -  loadFont('../opentype/AvenirNextLTPro-Demi.otf', function(f) {
        -    var points = f.textToPoints('p5.js', 80, 185, 150);
        -    for (var k = 0; k < points.length; k++) {
        -      flock.boids.push(new Boid(points[k]));
        -    }
        -  });
        -}
        -
        -function draw() {
        -  var c = flock.count / flock.boids.length;
        -  background(c * 237, 34, 93);
        -  flock.run();
        -}
        -
        -function mouseReleased() {
        -  if (flock.arrived()) flock.arrived(false);
        -}
        diff --git a/test/manual-test-examples/p5.Font/renderpath/index.html b/test/manual-test-examples/p5.Font/renderpath/index.html
        deleted file mode 100644
        index 37545f805b..0000000000
        --- a/test/manual-test-examples/p5.Font/renderpath/index.html
        +++ /dev/null
        @@ -1,10 +0,0 @@
        -<html>
        -<head>
        -  <script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
        -  <script language="javascript" type="text/javascript" src="sketch.js"></script>
        -  <style> body {padding: 0; margin: 0;} canvas{border: 1px solid #f0f0f0;}</style>
        -</head>
        -
        -<body>
        -</body>
        -</html>
        diff --git a/test/manual-test-examples/p5.Font/renderpath/sketch.js b/test/manual-test-examples/p5.Font/renderpath/sketch.js
        deleted file mode 100644
        index 334c6c8da2..0000000000
        --- a/test/manual-test-examples/p5.Font/renderpath/sketch.js
        +++ /dev/null
        @@ -1,35 +0,0 @@
        -function setup() {
        -  var txt = 'Default Text',
        -    x = 190;
        -
        -  createCanvas(200, 150);
        -
        -  line(190, 0, 190, height);
        -
        -  textAlign(RIGHT);
        -  textSize(32);
        -  text(txt, x, 30);
        -
        -  loadFont('../SourceSansPro-Regular.otf', function(font) {
        -    text(txt, x, 60);
        -
        -    textSize(35); // not aligning correctly (ignore alignment or fix)
        -    var path = font._getPath(txt, x, 90);
        -    font._renderPath(path);
        -
        -    textFont(font);
        -    text(txt, x, 120);
        -
        -    x = 20;
        -    textSize(20);
        -    textAlign(LEFT);
        -
        -    var td = x + font._textWidth('space');
        -    var tw = font._textWidth(' ');
        -
        -    text('space width: ' + tw.toFixed(2) + 'px', x, 140);
        -
        -    line(td, 145, td, 145 - 22);
        -    line(td + tw, 145, td + tw, 145 - 22);
        -  });
        -}
        diff --git a/test/manual-test-examples/p5.Font/simple/index.html b/test/manual-test-examples/p5.Font/simple/index.html
        deleted file mode 100644
        index 184edd329e..0000000000
        --- a/test/manual-test-examples/p5.Font/simple/index.html
        +++ /dev/null
        @@ -1,19 +0,0 @@
        -<html>
        -<head>
        -  <meta charset="UTF-8">
        -  <script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
        -  <script language="javascript" type="text/javascript" src="sketch.js"></script>
        -  <style> body {padding: 0; margin: 0; font-size: 10px } canvas{border: 1px solid #f0f0f0; display: block;} img{ border: 1px solid #f0f;} div{ margin: 10px 0px;}</style>
        -</head>
        -
        -<body>
        -  <div id='textSketch'></div>
        -  Note: (tight) bounds for a text string <i>may</i> start to the right of the its x-position (blue line)<br />
        -  <div id='textSketchMono'></div><br />&nbsp;<br />
        -  Issue #1958:
        -  <div id='textSketch1958'></div><br />&nbsp;<br />
        -  <!--div id='textSketch1957'></div-->
        -  Issue #5181:
        -  <div id='textSketch5181'></div><br />&nbsp;<br />
        -</body>
        -</html>
        diff --git a/test/manual-test-examples/p5.Font/simple/sketch.js b/test/manual-test-examples/p5.Font/simple/sketch.js
        deleted file mode 100644
        index 7adf603693..0000000000
        --- a/test/manual-test-examples/p5.Font/simple/sketch.js
        +++ /dev/null
        @@ -1,198 +0,0 @@
        -var _setup = function(p, font) {
        -  var txt,
        -    tb,
        -    tw,
        -    x = 20,
        -    y = 50;
        -
        -  p.createCanvas(240, 160);
        -  p.textFont(font);
        -  p.textSize(20);
        -
        -  p.stroke('blue');
        -  p.line(x, 0, x, p.height);
        -
        -  txt = ' leading space';
        -  tb = font.textBounds(txt, x, y);
        -  tw = p.textWidth(txt);
        -  p.stroke('black');
        -  p.rect(tb.x, tb.y, tb.w, tb.h);
        -  p.noStroke();
        -  p.text(txt, x, y);
        -  p.stroke('red');
        -  p.line(x, y + 6, x + tw, y + 6);
        -
        -  y = 80;
        -  txt = 'traction waste';
        -  tb = font.textBounds(txt, x, y);
        -  tw = p.textWidth(txt);
        -  p.stroke('black');
        -  p.rect(tb.x, tb.y, tb.w, tb.h);
        -  p.noStroke();
        -  p.text(txt, x, y);
        -  p.stroke('red');
        -  p.line(x, y + 6, x + tw, y + 6);
        -
        -  y = 110;
        -  txt = 'trailing space ';
        -  tb = font.textBounds(txt, x, y);
        -  tw = p.textWidth(txt);
        -  p.stroke('black');
        -  p.rect(tb.x, tb.y, tb.w, tb.h);
        -  p.noStroke();
        -  p.text(txt, x, y);
        -  p.stroke('red');
        -  p.line(x, y + 6, x + tw, y + 6);
        -
        -  y = 140;
        -  txt = ' ';
        -  tb = font.textBounds(txt, x, y);
        -  tw = p.textWidth(txt);
        -  p.stroke('black');
        -  p.rect(tb.x, tb.y, tb.w, p.max(tb.h, 3));
        -  p.noStroke();
        -  p.text(txt, x, y);
        -  p.stroke('red');
        -  p.line(x, y + 6, x + tw, y + 6);
        -};
        -
        -var textSketch = function(p) {
        -  p.setup = function() {
        -    p.loadFont('../acmesa.ttf', function(f) {
        -      _setup(p, f);
        -    });
        -  };
        -};
        -
        -var textSketchMono = function(p) {
        -  p.setup = function() {
        -    p.loadFont('../AndaleMono.ttf', function(f) {
        -      _setup(p, f);
        -    });
        -  };
        -};
        -
        -var textSketch1958 = function(p) {
        -  // issue #1958
        -  var font,
        -    lineW,
        -    words = 'swimming back to the rock';
        -
        -  p.preload = function() {
        -    font = p.loadFont('../OpenSans-Regular.ttf');
        -  };
        -
        -  p.setup = function() {
        -    function textAsWords(words, x, y) {
        -      var tw,
        -        spaceW = p.textWidth(' ');
        -      //console.log(spaceW);
        -      for (var i = 0; i < words.length; i++) {
        -        if (i !== 0) {
        -          tw = p.textWidth(words[i - 1]);
        -          x += tw + spaceW;
        -          p.stroke(0);
        -          p.noFill();
        -          p.rect(x - spaceW, y + 5, spaceW, -25);
        -        }
        -        p.fill(0);
        -        p.noStroke();
        -        p.text(words[i], x, y);
        -      }
        -    }
        -
        -    p.createCanvas(300, 200);
        -    p.background(255);
        -
        -    p.textSize(20); // Case 1: Default font
        -    p.noStroke();
        -    p.text(words, 20, 50);
        -    textAsWords(words.split(' '), 20, 80);
        -
        -    p.stroke(255, 0, 0);
        -    p.line(20, 0, 20, p.height);
        -
        -    lineW = p.textWidth(words);
        -    p.line(20 + lineW, 0, 20 + lineW, 90);
        -
        -    p.textFont(font, 20); // Case 2: OpenSans
        -    p.noStroke();
        -    p.text(words, 20, 120);
        -    textAsWords(words.split(' '), 20, 150);
        -
        -    p.stroke(255, 0, 0);
        -    lineW = p.textWidth(words);
        -    p.line(20 + lineW, 100, 20 + lineW, p.height - 20);
        -
        -    p.stroke(0);
        -    p.line(20, 160, 20 + p.textWidth(' '), 160);
        -  };
        -};
        -
        -/*var textSketch1957 = function(p) { // issue #1957
        -  var font;
        -  p.preload = function() {
        -    font = p.loadFont("../AndaleMono.ttf");
        -  };
        -  p.setup = function() {
        -
        -    p.createCanvas(300, 400);
        -    p.textFont(font, 80);
        -
        -    p.text("a", 0, 100);
        -    p.text("b", 0, 200);
        -    p.text("c", 0, 300);
        -
        -    p.stroke(255,0,0);
        -    p.line(p.textWidth("a"), 0, p.textWidth("a"), p.height);
        -    p.line(p.textWidth("b"), 0, p.textWidth("b"), p.height);
        -    p.line(p.textWidth("c"), 0, p.textWidth("c"), p.height);
        -    p.noStroke();
        -    p.textSize(10);
        -    p.text("a="+p.textWidth("a")+" b="+p.textWidth("b")+" c="+p.textWidth("c"), 10, 350);
        -    console.log(font);
        -  }
        -}
        -new p5(textSketch1957, "textSketch1957");*/
        -
        -var textSketch5181 = function(p) {
        -  // issue #5181
        -  var font,
        -    txt =
        -      "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.";
        -
        -  p.preload = function() {
        -    font = p.loadFont('../OpenSans-Regular.ttf');
        -  };
        -
        -  p.setup = function() {
        -    p.createCanvas(300, 700);
        -    p.background(255);
        -
        -    let bounds = [20, 10, 250, 130];
        -    p.textFont(font, 12);
        -    p.rect(...bounds);
        -    p.text('Default Size/Lead (12/15): ' + txt, ...bounds);
        -
        -    bounds = [20, 150, 250, 230];
        -    p.textFont(font, 16);
        -    p.rect(...bounds);
        -    p.text('Default Size/Lead (16/20): ' + txt, ...bounds);
        -
        -    bounds = [20, 390, 250, 105];
        -    p.textLeading(12);
        -    p.textFont(font, 12);
        -    p.rect(...bounds);
        -    p.text('User-set Size/Lead (12/12): ' + txt, ...bounds);
        -
        -    bounds = [20, 505, 250, 185];
        -    p.textFont(font, 20);
        -    p.rect(...bounds);
        -    p.text('Maintain Custom Leading (20/12): ' + txt, ...bounds);
        -  };
        -};
        -
        -new p5(textSketch, 'textSketch');
        -new p5(textSketchMono, 'textSketchMono');
        -new p5(textSketch1958, 'textSketch1958');
        -new p5(textSketch5181, 'textSketch5181');
        diff --git a/test/manual-test-examples/p5.Font/style/index.html b/test/manual-test-examples/p5.Font/style/index.html
        deleted file mode 100644
        index 35938afb91..0000000000
        --- a/test/manual-test-examples/p5.Font/style/index.html
        +++ /dev/null
        @@ -1,9 +0,0 @@
        -<html>
        -<head>
        -  <meta charset="UTF-8">
        -  <script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
        -
        -  <script language="javascript" type="text/javascript" src="sketch.js"></script>
        -
        -</head>
        -</html>
        diff --git a/test/manual-test-examples/p5.Font/style/sketch.js b/test/manual-test-examples/p5.Font/style/sketch.js
        deleted file mode 100644
        index adba674b10..0000000000
        --- a/test/manual-test-examples/p5.Font/style/sketch.js
        +++ /dev/null
        @@ -1,10 +0,0 @@
        -var font;
        -
        -function preload() {
        -  font = loadFont('../acmesa.ttf');
        -}
        -
        -function setup() {
        -  var myDiv = createDiv('hello there');
        -  myDiv.style('font-family', 'acmesa');
        -}
        diff --git a/test/manual-test-examples/p5.Font/svgpath/index.html b/test/manual-test-examples/p5.Font/svgpath/index.html
        deleted file mode 100644
        index 02623e5b84..0000000000
        --- a/test/manual-test-examples/p5.Font/svgpath/index.html
        +++ /dev/null
        @@ -1,9 +0,0 @@
        -<html>
        -<head>
        -  <script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
        -  <script language="javascript" type="text/javascript" src="sketch.js"></script>
        -  <style> body {padding: 0; margin: 0;} canvas{border: 1px solid #f0f0f0;}</style>
        -</head>
        -<body>
        -</body>
        -</html>
        diff --git a/test/manual-test-examples/p5.Font/svgpath/sketch.js b/test/manual-test-examples/p5.Font/svgpath/sketch.js
        deleted file mode 100644
        index c6e669ed7e..0000000000
        --- a/test/manual-test-examples/p5.Font/svgpath/sketch.js
        +++ /dev/null
        @@ -1,40 +0,0 @@
        -function setup() {
        -  var canvas = createCanvas(200, 150).canvas;
        -
        -  loadFont('../SourceSansPro-Regular.otf', function(font) {
        -    // render text with opentype font
        -    textAlign(RIGHT);
        -    textFont(font, 32);
        -    text('Text Path', 190, 60);
        -
        -    // converted path to canvas Path2D then render
        -    var pathStr = font._getPathData('Path Data', 190, 90);
        -    var cPath = new Path2D(pathStr);
        -    canvas.getContext('2d').fill(cPath);
        -
        -    // not displayed, just print <path> tag in console
        -    var pathTag = font._getSVG('SVG', 190, 120, {
        -      decimals: 4,
        -      fill: 'red',
        -      strokeWidth: 2,
        -      stroke: 'green'
        -    });
        -    //console.log(pathTag);
        -
        -    // hit detection for canvas Path2D (cursor changes)
        -    canvas.onmousemove = function(e) {
        -      var context = e.target.getContext('2d');
        -      var coordX = e.offsetX;
        -      var coordY = e.offsetY;
        -
        -      // Test the square for clicks
        -      if (context.isPointInPath(cPath, coordX, coordY)) {
        -        e.target.style.cursor = 'pointer';
        -        return;
        -      }
        -
        -      // Reset the pointer to the default
        -      e.target.style.cursor = 'default';
        -    };
        -  });
        -}
        diff --git a/test/manual-test-examples/p5.Font/system/LEFT.BL.lead.png b/test/manual-test-examples/p5.Font/system/LEFT.BL.lead.png
        deleted file mode 100644
        index 80ec65164d..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/LEFT.BL.lead.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/system/LEFT.BOTTOM.lead.png b/test/manual-test-examples/p5.Font/system/LEFT.BOTTOM.lead.png
        deleted file mode 100644
        index e953cdbcc8..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/LEFT.BOTTOM.lead.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/system/LEFT.CENTER.lead.png b/test/manual-test-examples/p5.Font/system/LEFT.CENTER.lead.png
        deleted file mode 100644
        index c970803170..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/LEFT.CENTER.lead.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/system/LEFT.TOP.lead.png b/test/manual-test-examples/p5.Font/system/LEFT.TOP.lead.png
        deleted file mode 100644
        index 2479e78dca..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/LEFT.TOP.lead.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/system/index.html b/test/manual-test-examples/p5.Font/system/index.html
        deleted file mode 100755
        index d6e618d600..0000000000
        --- a/test/manual-test-examples/p5.Font/system/index.html
        +++ /dev/null
        @@ -1,67 +0,0 @@
        -<html>
        -<head>
        -  <meta charset="UTF-8">
        -  <script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
        -  <script language="javascript" type="text/javascript" src="sketch.js"></script>
        -  <style> body {padding: 0; margin: 0;} canvas{border: 1px solid #f0f0f0; display: block;} img{ border: 1px solid #f0f;} div{ margin: 100px 0px;}</style>
        -</head>
        -
        -<body>
        -  <div id='textSketch'>
        -  <img src="textSketch.png" width="240" height="160"></img>
        -  </div>
        -  <div id='textLineSketch'>
        -  <img src="textLineSketch.png" width="960" height="160"></img>
        -  </div>
        -  <div id='textWrapSketch'>
        -  <img src="textWrapSketch.png" width="960" height="160"></img>
        -  </div>
        -  <div id='textFontSketch'>
        -  </div>
        -  <div id='textAlignSketch'>
        -  <img src="textAlignSketch.png" width="240" height="160"></img>
        -  </div>
        -  <div id="textAllAlignmentsSketch" style="position:relative;">
        -  <img src="textAllAlignmentsSketch.png" width="400" height="600" style="display:inline-block;"></img>
        -  </div>
        -  <div id='textLeadingSketch'>
        -  <img src="LEFT.TOP.lead.png" width="400" height="200"></img>
        -  </div>
        -  <div id='textLeadingSketch2'>
        -  <img src="LEFT.CENTER.lead.png" width="400" height="200"></img>
        -  </div>
        -  <div id='textLeadingSketch3'>
        -  <img src="LEFT.BL.lead.png" width="400" height="200"></img>
        -  </div>
        -  <div id='textLeadingSketch4'>
        -  <img src="LEFT.BOTTOM.lead.png" width="400" height="200"></img>
        -  </div>
        -  <div id='textSizeSketch'>
        -  <img src="textSizeSketch.png" width="240" height="160"></img>
        -  </div>
        -  <div id='textStyleSketch'>
        -  </div>
        -  <div id='textWidthSketch'>
        -  <img src="textWidthSketch.png" width="240" height="160"></img>
        -  </div>
        -  <div id='textOverlapSketch'>
        -  <img src="textOverlapSketch.png" width="240" height="160"></img>
        -  </div>
        -  <div id='textFlySketch'>
        -  </div>
        -  <div id='textFlickerSketch'>
        -  </div>
        -  <div id='textFadeSketch'>
        -  </div>
        -  <div id='textRotateSketch'>
        -  </div>
        -  <div id='textGrowSketch'>
        -  </div>
        -  <div id='textAvoidSketch'>
        -  </div>
        -  <div id='textBendSketch'>
        -  </div>
        -  <div id='typographyLetterSketch'>
        -  </div>
        -</body>
        -</html>
        diff --git a/test/manual-test-examples/p5.Font/system/sketch.js b/test/manual-test-examples/p5.Font/system/sketch.js
        deleted file mode 100755
        index 726abfc2bf..0000000000
        --- a/test/manual-test-examples/p5.Font/system/sketch.js
        +++ /dev/null
        @@ -1,736 +0,0 @@
        -var textSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.background(204);
        -    p.textSize(19.5);
        -    p.fill(255);
        -    p.text('Default Text', 10, 30);
        -    p.noStroke();
        -    p.fill(57, 112, 155);
        -    p.text('Black No Stroke Text', 10, 60);
        -    //p.textStyle(p.ITALIC);
        -    //p.text('Blue No Stroke Text Italic', 10, 80);
        -    //p.textStyle(p.BOLD);
        -    //p.text('Blue No Stroke Text Bold', 10, 100);
        -    p
        -      .fill(120)
        -      .textStyle(p.NORMAL)
        -      .textSize(13.5)
        -      .text(
        -        'Simple long Text: Lorem Ipsum is simply dummy text of the printing and typesetting industry. ',
        -        10,
        -        92,
        -        220,
        -        60
        -      );
        -  };
        -};
        -
        -var textLineSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(240 * 4, 160);
        -    p.textSize(10);
        -    p.stroke(0);
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(10, 10, 220, 10);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.TOP);
        -    p.text('LEFT TOP is simply dummy text.', 10, 10);
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(10, 60, 220, 60);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.TOP);
        -    p.text('CENTER TOP is simply dummy text.', 10, 60);
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(10, 110, 220, 110);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.TOP);
        -    p.text('RIGHT TOP is simply dummy text.', 10, 110);
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(250, 10, 470, 10);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.CENTER);
        -    p.text('LEFT CENTER is simply dummy text.', 250, 10);
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(250, 60, 470, 60);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.CENTER);
        -    p.text('CENTER CENTER is simply dummy text.', 250, 60);
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(250, 110, 470, 110);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.CENTER);
        -    p.text('RIGHT CENTER is simply dummy text.', 250, 110);
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(490, 10, 710, 10);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.BOTTOM);
        -    p.text('LEFT BOTTOM is simply dummy text.', 490, 10);
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(490, 60, 710, 60);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.BOTTOM);
        -    p.text('CENTER BOTTOM is simply dummy text.', 490, 60);
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(490, 110, 710, 110);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.BOTTOM);
        -    p.text('RIGHT BOTTOM is simply dummy text.', 490, 110);
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(730, 10, 950, 10);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.BASELINE);
        -    p.text('LEFT BASELINE is simply dummy text.', 730, 10);
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(730, 60, 950, 60);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.BASELINE);
        -    p.text('CENTER BASELINE is simply dummy text.', 730, 60);
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.line(730, 110, 950, 110);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.BASELINE);
        -    p.text('RIGHT BASELINE is simply dummy text.', 730, 110);
        -  };
        -};
        -
        -var textWrapSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(240 * 4, 160);
        -    p.textSize(10);
        -    p.stroke(0);
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(10, 10, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.TOP);
        -    p.text(
        -      'LEFT TOP is simply dummy text of the printing and typesetting industry. ',
        -      10,
        -      10,
        -      220,
        -      40
        -    );
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(10, 60, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.TOP);
        -    p.text(
        -      'CENTER TOP is simply dummy text of the printing and typesetting industry. ',
        -      10,
        -      60,
        -      220,
        -      40
        -    );
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(10, 110, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.TOP);
        -    p.text(
        -      'RIGHT TOP is simply dummy text of the printing and typesetting industry. ',
        -      10,
        -      110,
        -      220,
        -      40
        -    );
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(250, 10, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.CENTER);
        -    p.text(
        -      'LEFT CENTER is simply dummy text of the printing and typesetting industry. ',
        -      250,
        -      10,
        -      220,
        -      40
        -    );
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(250, 60, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.CENTER);
        -    p.text(
        -      'CENTER CENTER is simply dummy text of the printing and typesetting industry. ',
        -      250,
        -      60,
        -      220,
        -      40
        -    );
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(250, 110, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.CENTER);
        -    p.text(
        -      'RIGHT CENTER is simply dummy text of the printing and typesetting industry. ',
        -      250,
        -      110,
        -      220,
        -      40
        -    );
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(490, 10, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.BOTTOM);
        -    p.text(
        -      'LEFT BOTTOM is simply dummy text of the printing and typesetting industry. ',
        -      490,
        -      10,
        -      220,
        -      40
        -    );
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(490, 60, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.BOTTOM);
        -    p.text(
        -      'CENTER BOTTOM is simply dummy text of the printing and typesetting industry. ',
        -      490,
        -      60,
        -      220,
        -      40
        -    );
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(490, 110, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.BOTTOM);
        -    p.text(
        -      'RIGHT BOTTOM is simply dummy text of the printing and typesetting industry. ',
        -      490,
        -      110,
        -      220,
        -      40
        -    );
        -
        -    //1
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(730, 10, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.LEFT, p.BASELINE);
        -    p.text(
        -      'LEFT BASELINE is simply dummy text of the printing and typesetting industry. ',
        -      730,
        -      10,
        -      220,
        -      40
        -    );
        -    //2
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(730, 60, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.CENTER, p.BASELINE);
        -    p.text(
        -      'CENTER BASELINE is simply dummy text of the printing and typesetting industry. ',
        -      730,
        -      60,
        -      220,
        -      40
        -    );
        -    //3
        -    p.fill(255);
        -    p.strokeWeight(1);
        -    p.rect(730, 110, 220, 40);
        -    p.strokeWeight(0);
        -    p.fill(0);
        -    p.textAlign(p.RIGHT, p.BASELINE);
        -    p.text(
        -      'RIGHT BASELINE is simply dummy text of the printing and typesetting industry. ',
        -      730,
        -      110,
        -      220,
        -      40
        -    );
        -  };
        -};
        -
        -var textFontSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textSize(20);
        -    p.fill(0);
        -    p.strokeWeight(0);
        -    p.textFont('times');
        -    p.text('Times Font', 10, 30);
        -    p.textFont('arial');
        -    p.text('Arial Font', 10, 60);
        -    p.textFont('Courier');
        -    p.text('Courier Font', 10, 90);
        -  };
        -};
        -
        -var textAlignSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.fill(0);
        -    p.strokeWeight(0);
        -    p.textSize(12);
        -    p.textAlign(p.RIGHT, p.TOP);
        -    p.text('Top Right', 120, 30);
        -    p.textAlign(p.CENTER, p.CENTER);
        -    p.text('Center Center', 120, 60);
        -    p.textAlign(p.LEFT, p.BOTTOM);
        -    p.text('Left Bottom', 120, 90);
        -    p.textAlign(p.RIGHT, p.BASELINE);
        -    p.text('Right Baseline', 120, 90);
        -    p.strokeWeight(1);
        -    p.line(120, 0, 120, 160);
        -  };
        -};
        -
        -var textAllAlignmentsSketch = function(p) {
        -  var systemFonts = ['Arial', 'Times New Roman', 'Consolas'];
        -  var font1, font2, font3;
        -  var hAligns = [p.LEFT, p.CENTER, p.RIGHT];
        -  var vAligns = [p.TOP, p.CENTER, p.BASELINE, p.BOTTOM];
        -  var padding = 10;
        -  var drawFontAlignments = function(font, textString, xOff, yOff) {
        -    p.textFont(font);
        -    p.textSize(20);
        -    for (var h = 0; h < hAligns.length; h += 1) {
        -      for (var v = 0; v < vAligns.length; v += 1) {
        -        // Distribute words across the screen
        -        var x = xOff + p.map(h, 0, hAligns.length - 1, padding, 400 - padding);
        -        var y = yOff + p.map(v, 0, vAligns.length - 1, padding, 200 - padding);
        -
        -        p.stroke(200);
        -        p.line(0, y, p.width, y);
        -        p.line(x, 0, x, p.height);
        -
        -        // Align the text & calculate the bounds
        -        p.textAlign(hAligns[h], vAligns[v]);
        -
        -        // Draw the text
        -        p.fill(255, 0, 0);
        -        p.noStroke();
        -        p.text(textString, x, y);
        -
        -        // Draw the (x, y) coordinates
        -        p.stroke(0);
        -        p.fill('#FF8132');
        -        p.ellipse(x, y, 3, 3);
        -      }
        -    }
        -  };
        -  p.setup = function() {
        -    var renderer = p.createCanvas(400, 600);
        -    renderer.elt.style.position = 'absolute';
        -    renderer.elt.style.top = '0';
        -    renderer.elt.style.left = '0';
        -    drawFontAlignments(systemFonts[0], 'Arial', 0, 0);
        -    drawFontAlignments(systemFonts[1], 'Times', 0, 200);
        -    drawFontAlignments(systemFonts[2], 'Consolas', 0, 400);
        -    // These proprietary fonts aren't included in the repo!
        -    // p.loadFont("../arial.ttf", function(font) {drawFontAlignments(font, "Arial", 0, 0)});
        -    // p.loadFont("../times.ttf", function(font) {drawFontAlignments(font, "Times", 0, 200)});
        -    // p.loadFont("../consola.ttf", function(font) {drawFontAlignments(font, "Consolas", 0, 400)});
        -  };
        -};
        -
        -var textLeadingSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(400, 200);
        -    p.textFont('Arial');
        -    p.fill(0);
        -    p.textSize(12);
        -
        -    p.line(0, 100, p.width, 100);
        -    p.textAlign(p.LEFT, p.TOP);
        -    p.strokeWeight(0);
        -
        -    var s10 = 'LEFT/TOP@10px',
        -      s20 = s10.replace('1', '2'),
        -      s30 = s10.replace('1', '3');
        -
        -    p.textLeading(10); // Set leading to 10
        -    p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        -    p.textLeading(20); // Set leading to 20
        -    p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        -    p.textLeading(30); // Set leading to 30
        -    p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        -  };
        -};
        -
        -var textLeadingSketch2 = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(400, 200);
        -    p.textFont('Arial');
        -    p.fill(0);
        -    p.textSize(12);
        -
        -    p.line(0, 100, p.width, 100);
        -    p.textAlign(p.LEFT, p.CENTER);
        -    p.strokeWeight(0);
        -
        -    var s10 = 'LEFT/CENTER@10px',
        -      s20 = s10.replace('1', '2'),
        -      s30 = s10.replace('1', '3');
        -
        -    p.textLeading(10); // Set leading to 10
        -    p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        -    p.textLeading(20); // Set leading to 20
        -    p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        -    p.textLeading(30); // Set leading to 30
        -    p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        -  };
        -};
        -
        -var textLeadingSketch3 = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(400, 200);
        -    p.textFont('Arial');
        -    p.fill(0);
        -    p.textSize(12);
        -
        -    p.line(0, 100, p.width, 100);
        -    p.textAlign(p.LEFT, p.BASELINE);
        -    p.strokeWeight(0);
        -
        -    var s10 = 'LEFT/BASELINE@10px',
        -      s20 = s10.replace('1', '2'),
        -      s30 = s10.replace('1', '3');
        -
        -    p.textLeading(10); // Set leading to 10
        -    p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        -    p.textLeading(20); // Set leading to 20
        -    p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        -    p.textLeading(30); // Set leading to 30
        -    p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        -  };
        -};
        -
        -var textLeadingSketch4 = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(400, 200);
        -    p.textFont('Arial');
        -    p.fill(0);
        -    p.textSize(12);
        -
        -    p.line(0, 100, p.width, 100);
        -    p.textAlign(p.LEFT, p.BOTTOM);
        -    p.strokeWeight(0);
        -
        -    var s10 = 'LEFT/BOTTOM@10px',
        -      s20 = s10.replace('1', '2'),
        -      s30 = s10.replace('1', '3');
        -
        -    p.textLeading(10); // Set leading to 10
        -    p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        -    p.textLeading(20); // Set leading to 20
        -    p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        -    p.textLeading(30); // Set leading to 30
        -    p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        -  };
        -};
        -
        -var textSizeSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.fill(0);
        -    p.strokeWeight(0);
        -    p.textSize(12);
        -    p.text('Font Size 12', 10, 30);
        -    p.textSize(14);
        -    p.text('Font Size 14', 10, 60);
        -    p.textSize(16);
        -    p.text('Font Size 16', 10, 90);
        -  };
        -};
        -
        -var textStyleSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.fill(0);
        -    p.strokeWeight(0);
        -    p.textSize(12);
        -    p.textStyle(p.NORMAL);
        -    p.text('Font Style Normal', 10, 30);
        -    p.textStyle(p.ITALIC);
        -    p.text('Font Style Italic', 10, 60);
        -    p.textStyle(p.BOLD);
        -    p.text('Font Style Bold', 10, 90);
        -  };
        -};
        -
        -var textWidthSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.fill(0);
        -    p.strokeWeight(0);
        -    p.textSize(12);
        -    var s = "What's the width of this line?";
        -    var textWidth = p.textWidth(s);
        -    p.text(s, 10, 30);
        -    p.rect(10, 30, textWidth, 2);
        -    p.text('width: ' + textWidth, 10, 60);
        -  };
        -};
        -
        -var textOverlapSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.fill(0);
        -    p.strokeWeight(0);
        -    p.textSize(72);
        -    p.fill(0, 160); // Black with low opacity
        -    p.text('O', 0, 100);
        -    p.text('V', 30, 100);
        -    p.text('E', 60, 100);
        -    p.text('R', 90, 100);
        -    p.text('L', 120, 100);
        -    p.text('A', 150, 100);
        -    p.text('P', 180, 100);
        -  };
        -};
        -
        -var textFlySketch = function(p) {
        -  var x1 = 100;
        -  var x2 = 0;
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.fill(0);
        -    p.textSize(48);
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    p.text('Left', x1, 50);
        -    p.text('Right', x2, 150);
        -    x2 += 2.0;
        -    if (x2 > 240) {
        -      x2 = -100;
        -    }
        -    x1 -= 1.0;
        -    if (x1 < -100) {
        -      x1 = 240;
        -    }
        -  };
        -};
        -
        -var textFlickerSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textSize(48);
        -    p.noStroke();
        -  };
        -  p.draw = function() {
        -    p.fill(204, 24);
        -    p.rect(0, 0, p.width, p.height);
        -    p.fill(0);
        -    p.text('flicker Text', p.random(-100, 240), p.random(-20, 160));
        -  };
        -};
        -
        -var textFadeSketch = function(p) {
        -  var opacity = 0;
        -  var direction = 1;
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textSize(72);
        -    p.noStroke();
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    opacity += 4 * direction;
        -    if (opacity < 0 || opacity > 255) {
        -      direction = -direction;
        -    }
        -    p.fill(0, opacity);
        -    p.text('fade', 50, 100);
        -  };
        -};
        -
        -var textRotateSketch = function(p) {
        -  var angle = 0.0;
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textSize(24);
        -    p.noStroke();
        -    p.fill(0);
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    angle += 0.05;
        -    p.push();
        -    p.translate(120, 80);
        -    p.scale((p.cos(angle / 4.0) + 1.2) * 2.0);
        -    p.rotate(angle);
        -    p.text('Rotating', 0, 0);
        -    p.pop();
        -  };
        -};
        -
        -var textGrowSketch = function(p) {
        -  var angle = 0.0;
        -  var str = 'GROW';
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textSize(24);
        -    p.noStroke();
        -    p.fill(0, 0, 0, 120);
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    angle += 0.1;
        -    for (var i = 0; i < str.length; i++) {
        -      var c = p.sin(angle + i / p.PI);
        -      p.textSize((c + 1.0) * 40 + 10);
        -      p.text(str.charAt(i), i * 40 + 20, 100);
        -    }
        -  };
        -};
        -
        -var textAvoidSketch = function(p) {
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textSize(24);
        -    p.noStroke();
        -    p.fill(0);
        -    p.textAlign(p.CENTER);
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    p.text('AVOID', p.width - p.mouseX, p.height - p.mouseY);
        -  };
        -};
        -
        -var textBendSketch = function(p) {
        -  var str = 'Flexibility';
        -  p.setup = function() {
        -    p.createCanvas(240, 160);
        -    p.textSize(30);
        -    p.noStroke();
        -    p.fill(0);
        -  };
        -  p.draw = function() {
        -    p.background(204);
        -    p.push();
        -    p.translate(0, 33);
        -    for (var i = 0; i < str.length; i++) {
        -      var angle = p.map(p.mouseX, 0, p.width, 0, p.PI / 8);
        -      p.rotate(angle);
        -      p.text(str[i], 20, 0);
        -      p.translate(p.textWidth(str[i]) * 1.5, 0);
        -    }
        -    p.pop();
        -  };
        -};
        -
        -var typographyLetterSketch = function(p) {
        -  var margin = 10;
        -  var gap = 46;
        -  var counter = 35;
        -  p.setup = function() {
        -    p.createCanvas(720, 320);
        -    p.background(0);
        -    p.textFont('Georgia');
        -    p.textSize(24);
        -    p.textStyle(p.BOLD);
        -    p.textAlign(p.CENTER, p.CENTER);
        -    p.translate(margin * 4, margin * 4);
        -    for (var y = 0; y < p.height - gap; y += gap) {
        -      for (var x = 0; x < p.width - gap; x += gap) {
        -        var letter = p.char(counter);
        -        if (letter === 'P' || letter === '5') {
        -          p.fill(255, 204, 0);
        -        } else if (letter === 'J' || letter === 'S') {
        -          p.fill(204, 0, 255);
        -        } else {
        -          p.fill(255);
        -        }
        -        p.text(letter, x, y);
        -        counter++;
        -      }
        -    }
        -  };
        -};
        -
        -new p5(textSketch, 'textSketch');
        -new p5(textLineSketch, 'textLineSketch');
        -new p5(textWrapSketch, 'textWrapSketch');
        -new p5(textFontSketch, 'textFontSketch');
        -new p5(textAlignSketch, 'textAlignSketch');
        -new p5(textAllAlignmentsSketch, 'textAllAlignmentsSketch');
        -new p5(textLeadingSketch, 'textLeadingSketch');
        -new p5(textLeadingSketch2, 'textLeadingSketch2');
        -new p5(textLeadingSketch3, 'textLeadingSketch3');
        -new p5(textLeadingSketch4, 'textLeadingSketch4');
        -new p5(textSizeSketch, 'textSizeSketch');
        -new p5(textStyleSketch, 'textStyleSketch');
        -new p5(textWidthSketch, 'textWidthSketch');
        -new p5(textOverlapSketch, 'textOverlapSketch');
        -new p5(textFlySketch, 'textFlySketch');
        -new p5(textFlickerSketch, 'textFlickerSketch');
        -new p5(textFadeSketch, 'textFadeSketch');
        -new p5(textRotateSketch, 'textRotateSketch');
        -new p5(textGrowSketch, 'textGrowSketch');
        -new p5(textAvoidSketch, 'textAvoidSketch');
        -new p5(textBendSketch, 'textBendSketch');
        -new p5(typographyLetterSketch, 'typographyLetterSketch');
        diff --git a/test/manual-test-examples/p5.Font/system/textAlignSketch.png b/test/manual-test-examples/p5.Font/system/textAlignSketch.png
        deleted file mode 100644
        index 53f27cdaed..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/textAlignSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/system/textAllAlignmentsSketch.png b/test/manual-test-examples/p5.Font/system/textAllAlignmentsSketch.png
        deleted file mode 100644
        index 5a3aa8558f..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/textAllAlignmentsSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/system/textLineSketch.png b/test/manual-test-examples/p5.Font/system/textLineSketch.png
        deleted file mode 100644
        index 9189587cbf..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/textLineSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/system/textOverlapSketch.png b/test/manual-test-examples/p5.Font/system/textOverlapSketch.png
        deleted file mode 100644
        index ca6a1ded2b..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/textOverlapSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/system/textSizeSketch.png b/test/manual-test-examples/p5.Font/system/textSizeSketch.png
        deleted file mode 100644
        index 40a041e471..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/textSizeSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/system/textSketch.png b/test/manual-test-examples/p5.Font/system/textSketch.png
        deleted file mode 100644
        index db3a943985..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/textSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/system/textWidthSketch.png b/test/manual-test-examples/p5.Font/system/textWidthSketch.png
        deleted file mode 100644
        index fc1d0694c3..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/textWidthSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/system/textWrapSketch.png b/test/manual-test-examples/p5.Font/system/textWrapSketch.png
        deleted file mode 100644
        index aaac7eb836..0000000000
        Binary files a/test/manual-test-examples/p5.Font/system/textWrapSketch.png and /dev/null differ
        diff --git a/test/manual-test-examples/p5.Font/textAsWords/index.html b/test/manual-test-examples/p5.Font/textAsWords/index.html
        deleted file mode 100644
        index 0ead77ae00..0000000000
        --- a/test/manual-test-examples/p5.Font/textAsWords/index.html
        +++ /dev/null
        @@ -1,17 +0,0 @@
        -<html>
        -<head>
        -  <meta charset="UTF-8">
        -  <script language="javascript" type="text/javascript" src="../../../../node_modules/opentype.js/dist/opentype.js"></script>
        -  <script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
        -  <script language="javascript" type="text/javascript" src="sketch.js"></script>
        -  <style> body {padding: 5px; margin: 0; font-size: 10px } canvas{border: 1px solid #f0f0f0; display: block;} img{ border: 1px solid #f0f;} div{ margin: 10px 0px;}</style>
        -</head>
        -
        -<body>
        -  1. default<br />
        -  2. p5/loaded<br />
        -  3. opentype/loaded<br />
        -  <div id='textSketch1958'></div>
        -</body>
        -
        -</html>
        diff --git a/test/manual-test-examples/p5.Font/textAsWords/sketch.js b/test/manual-test-examples/p5.Font/textAsWords/sketch.js
        deleted file mode 100644
        index 79875b85a0..0000000000
        --- a/test/manual-test-examples/p5.Font/textAsWords/sketch.js
        +++ /dev/null
        @@ -1,85 +0,0 @@
        -var font,
        -  lineW,
        -  words = 'swimming back to the rock';
        -
        -function preload() {
        -  font = loadFont('../Helvetica.ttf');
        -}
        -
        -function setup() {
        -  function textAsWords(words, x, y) {
        -    var tw,
        -      spaceW = textWidth(' ');
        -    //console.log('space=' + spaceW);
        -    for (var i = 0; i < words.length; i++) {
        -      fill(0);
        -      noStroke();
        -      text(words[i], x, y);
        -      x += textWidth(words[i]);
        -      //console.log(words[i] + '=' + x);
        -
        -      if (i < words.length - 1) {
        -        stroke(0);
        -        noFill();
        -        rect(x, y + 5, spaceW, -25);
        -        x += spaceW;
        -      }
        -    }
        -    stroke(0, 0, 255);
        -    line(x, y - 45, x, y + 5);
        -    fill(0);
        -    noStroke();
        -  }
        -
        -  createCanvas(300, 280);
        -  background(255);
        -
        -  textSize(20); // Case 1: Default font
        -  noStroke();
        -  //console.log('default');
        -  text(words, 20, 50);
        -  textAsWords(words.split(' '), 20, 80);
        -
        -  stroke(255, 0, 0);
        -  line(20, 0, 20, height);
        -
        -  textFont(font, 20); // Case 2: OpenSans
        -  noStroke();
        -  //console.log('\np5/loaded');
        -  text(words, 20, 120);
        -
        -  textAsWords(words.split(' '), 20, 150);
        -  stroke(0);
        -}
        -
        -setTimeout(function() {
        -  function _textAsWords(ctx, font, text, x, y, fontSize) {
        -    var tw,
        -      spaceW = font.getAdvanceWidth(' ', fontSize);
        -    //console.log('space=' + spaceW);
        -
        -    for (var i = 0; i < text.length; i++) {
        -      var pth = font.getPath(text[i], x, y, fontSize);
        -      pth.draw(ctx);
        -      x += font.getAdvanceWidth(text[i], fontSize);
        -      //console.log(text[i] + '=' + x);
        -      if (i < text.length - 1) {
        -        ctx.strokeRect(x, y + 5, spaceW, -25);
        -        x += spaceW;
        -      }
        -    }
        -    ctx.strokeStyle = '#00f';
        -    ctx.beginPath();
        -    ctx.moveTo(x, y - 45);
        -    ctx.lineTo(x, y + 5);
        -    ctx.stroke();
        -  }
        -
        -  opentype.load('../Helvetica.ttf', function(err, font) {
        -    if (err) throw 'Font could not be loaded: ' + err;
        -    var ctx = document.getElementById('defaultCanvas0').getContext('2d');
        -    font.getPath(words, 20, 190, 20).draw(ctx);
        -    //console.log('\nopentype/loaded');
        -    _textAsWords(ctx, font, words.split(' '), 20, 220, 20);
        -  });
        -}, 100);
        diff --git a/test/manual-test-examples/p5.Font/textInRect/index.html b/test/manual-test-examples/p5.Font/textInRect/index.html
        deleted file mode 100644
        index 35938afb91..0000000000
        --- a/test/manual-test-examples/p5.Font/textInRect/index.html
        +++ /dev/null
        @@ -1,9 +0,0 @@
        -<html>
        -<head>
        -  <meta charset="UTF-8">
        -  <script language="javascript" type="text/javascript" src="../../../../lib/p5.js"></script>
        -
        -  <script language="javascript" type="text/javascript" src="sketch.js"></script>
        -
        -</head>
        -</html>
        diff --git a/test/manual-test-examples/p5.Font/textInRect/sketch.js b/test/manual-test-examples/p5.Font/textInRect/sketch.js
        deleted file mode 100644
        index d7d5fa0f49..0000000000
        --- a/test/manual-test-examples/p5.Font/textInRect/sketch.js
        +++ /dev/null
        @@ -1,81 +0,0 @@
        -let xpos = 50;
        -let ypos = 100;
        -let str =
        -  'One Two Three Four Five Six Seven Eight Nine Ten Eleven Twelve Thirteen Fourteen Fifteen Sixteen Seventeen Eighteen Nineteen Twenty Twenty-one Twenty-two Twenty-three Twenty-four Twenty-five Twenty-six Twenty-seven Twenty-eight Twenty-nine Thirty Thirty-one Thirty-two Thirty-three Thirty-four Thirty-five Thirty-six Thirty-seven Thirty-eight Thirty-nine Forty Forty-one Forty-two Forty-three Forty-four Forty-five Forty-six Forty-seven Forty-eight Forty-nine Fifty Fifty-one Fifty-two Fifty-three';
        -
        -function setup() {
        -  createCanvas(1050, 800);
        -  background(245);
        -
        -  let ta = textAscent();
        -
        -  textAlign(CENTER, TOP);
        -  rect(xpos, ypos, 200, 200);
        -  text(str, xpos, ypos, 200, 200);
        -  xpos += 250;
        -
        -  textAlign(CENTER, CENTER);
        -  rect(xpos, ypos, 200, 200);
        -  text(str, xpos, ypos, 200, 200);
        -  xpos += 250;
        -
        -  textAlign(CENTER, BOTTOM);
        -  rect(xpos, ypos, 200, 200);
        -  text(str, xpos, ypos, 200, 200);
        -  xpos += 250;
        -
        -  textAlign(CENTER, BASELINE);
        -  rect(xpos, ypos, 200, 200);
        -  text(str, xpos, ypos, 200, 200);
        -
        -  textSize(18);
        -  textAlign(CENTER, TOP);
        -  text('TOP', 150, height / 2 - 40);
        -  text('CENTER', 400, height / 2 - 40);
        -  text('BOTTOM', 650, height / 2 - 40);
        -  text('BASELINE', 900, height / 2 - 40);
        -  textSize(12);
        -
        -  xpos = 50;
        -  ypos += 400;
        -
        -  textAlign(CENTER, TOP);
        -  rect(xpos, ypos, 200, 200);
        -  text(str, xpos, ypos, 200);
        -  xpos += 250;
        -
        -  textAlign(CENTER, CENTER);
        -  rect(xpos, ypos, 200, 200);
        -  text(str, xpos, ypos, 200);
        -  xpos += 250;
        -
        -  textAlign(CENTER, BOTTOM);
        -  rect(xpos, ypos, 200, 200);
        -  text(str, xpos, ypos, 200);
        -  xpos += 250;
        -
        -  textAlign(CENTER, BASELINE);
        -  rect(xpos, ypos, 200, 200);
        -  text(str, xpos, ypos, 200);
        -
        -  textSize(18);
        -  textAlign(CENTER, TOP);
        -  text('TOP', 150, height / 2 - 40);
        -  text('CENTER', 400, height / 2 - 40);
        -  text('BOTTOM', 650, height / 2 - 40);
        -  text('BASELINE', 900, height / 2 - 40);
        -  text('TOP', 150, ypos + 270);
        -  text('CENTER', 400, ypos + 270);
        -  text('BOTTOM', 650, ypos + 270);
        -  text('BASELINE', 900, ypos + 270);
        -
        -  fill(255);
        -  noStroke();
        -  textSize(24);
        -
        -  rect(0, height / 2, width, 15);
        -  fill(0);
        -  textAlign(LEFT, TOP);
        -  text('text(s, x, y, w, h)', 20, 40);
        -  text('text(s, x, y, w) [no height]', 20, height / 2 + 40);
        -}
        diff --git a/test/manual-test-examples/pixel/set-pixels.html b/test/manual-test-examples/pixel/set-pixels.html
        deleted file mode 100644
        index ce9d5eb1f1..0000000000
        --- a/test/manual-test-examples/pixel/set-pixels.html
        +++ /dev/null
        @@ -1,7 +0,0 @@
        -<head>
        -  <script language="javascript" type="text/javascript" src="../../../lib/p5.js"></script>
        -	<script language="javascript" type="text/javascript" src="set-pixels.js"></script>
        -</head>
        -
        -<body>
        -</body>
        \ No newline at end of file
        diff --git a/test/manual-test-examples/pixel/set-pixels.js b/test/manual-test-examples/pixel/set-pixels.js
        deleted file mode 100644
        index 5bb25845e4..0000000000
        --- a/test/manual-test-examples/pixel/set-pixels.js
        +++ /dev/null
        @@ -1,23 +0,0 @@
        -var img;
        -var radius = 60;
        -var smoothAmount;
        -var canvasImg;
        -
        -function preload() {
        -  img = loadImage('unicorn.jpg'); // Load an image into the program
        -}
        -
        -function setup() {
        -  createCanvas(256, 256);
        -  loadPixels();
        -  set(width / 2, height / 2, img);
        -}
        -
        -function draw() {
        -  for (var i = -5; i < 5; i++) {
        -    set(mouseX + i, mouseY, [0, 0, 255, 100]);
        -  }
        -  set(mouseX, mouseY, [255, 0, 255, 255]);
        -  set(mouseX + 10, mouseY + 10, 0);
        -  updatePixels();
        -}
        diff --git a/test/manual-test-examples/pixel/unicorn.jpg b/test/manual-test-examples/pixel/unicorn.jpg
        deleted file mode 100644
        index 6c56624aca..0000000000
        Binary files a/test/manual-test-examples/pixel/unicorn.jpg and /dev/null differ
        diff --git a/test/manual-test-examples/pixel/update-pixels.html b/test/manual-test-examples/pixel/update-pixels.html
        deleted file mode 100644
        index 53736040b9..0000000000
        --- a/test/manual-test-examples/pixel/update-pixels.html
        +++ /dev/null
        @@ -1,7 +0,0 @@
        -<head>
        -  <script language="javascript" type="text/javascript" src="../../../lib/p5.js"></script>
        -	<script language="javascript" type="text/javascript" src="update-pixels.js"></script>
        -</head>
        -
        -<body>
        -</body>
        \ No newline at end of file
        diff --git a/test/manual-test-examples/pixel/update-pixels.js b/test/manual-test-examples/pixel/update-pixels.js
        deleted file mode 100644
        index 8046250562..0000000000
        --- a/test/manual-test-examples/pixel/update-pixels.js
        +++ /dev/null
        @@ -1,29 +0,0 @@
        -var img;
        -var radius = 60;
        -var smoothAmount;
        -var canvasImg;
        -
        -function preload() {
        -  img = loadImage('unicorn.jpg'); // Load an image into the program
        -}
        -
        -function setup() {
        -  createCanvas(256, 256);
        -  loadPixels();
        -}
        -
        -function draw() {
        -  for (var y = 0; y < height; y++) {
        -    for (var x = 0; x < width; x++) {
        -      if (pow(x - mouseX, 2) + pow(y - mouseY, 2) < pow(radius, 2)) {
        -        var c = img.get(x, y);
        -        set(x, y, c);
        -        //pixels[4*(y*width+x)] = c[0];
        -        //pixels[4*(y*width+x)+1] = c[1];
        -        //pixels[4*(y*width+x)+2] = c[2];
        -        //pixels[4*(y*width+x)+3] = c[3];
        -      }
        -    }
        -  }
        -  updatePixels();
        -}
        diff --git a/test/manual-test-examples/p5.Font/acmesa.ttf b/test/manual-test-examples/type/font/Acmesa.ttf
        similarity index 100%
        rename from test/manual-test-examples/p5.Font/acmesa.ttf
        rename to test/manual-test-examples/type/font/Acmesa.ttf
        diff --git a/test/manual-test-examples/p5.Font/AndaleMono.ttf b/test/manual-test-examples/type/font/AndaleMono.ttf
        similarity index 100%
        rename from test/manual-test-examples/p5.Font/AndaleMono.ttf
        rename to test/manual-test-examples/type/font/AndaleMono.ttf
        diff --git a/test/manual-test-examples/type/font/BricolageGrotesque-Variable.ttf b/test/manual-test-examples/type/font/BricolageGrotesque-Variable.ttf
        new file mode 100644
        index 0000000000..1c7c35e602
        Binary files /dev/null and b/test/manual-test-examples/type/font/BricolageGrotesque-Variable.ttf differ
        diff --git a/test/manual-test-examples/p5.Font/FiraSans-Book.otf b/test/manual-test-examples/type/font/FiraSans-Book.otf
        similarity index 100%
        rename from test/manual-test-examples/p5.Font/FiraSans-Book.otf
        rename to test/manual-test-examples/type/font/FiraSans-Book.otf
        diff --git a/test/manual-test-examples/p5.Font/Lato-Black.ttf b/test/manual-test-examples/type/font/Lato-Black.ttf
        similarity index 100%
        rename from test/manual-test-examples/p5.Font/Lato-Black.ttf
        rename to test/manual-test-examples/type/font/Lato-Black.ttf
        diff --git a/test/manual-test-examples/type/font/Lato-Regular.woff b/test/manual-test-examples/type/font/Lato-Regular.woff
        new file mode 100644
        index 0000000000..c3321de244
        Binary files /dev/null and b/test/manual-test-examples/type/font/Lato-Regular.woff differ
        diff --git a/test/manual-test-examples/type/font/LiberationSans-Bold.ttf b/test/manual-test-examples/type/font/LiberationSans-Bold.ttf
        new file mode 100644
        index 0000000000..4581ebf3ee
        Binary files /dev/null and b/test/manual-test-examples/type/font/LiberationSans-Bold.ttf differ
        diff --git a/test/manual-test-examples/type/font/NotoNaskhArabic.woff2 b/test/manual-test-examples/type/font/NotoNaskhArabic.woff2
        new file mode 100644
        index 0000000000..5b28703a17
        Binary files /dev/null and b/test/manual-test-examples/type/font/NotoNaskhArabic.woff2 differ
        diff --git a/test/manual-test-examples/p5.Font/PlayfairDisplay-Regular.ttf b/test/manual-test-examples/type/font/PlayfairDisplay.ttf
        similarity index 100%
        rename from test/manual-test-examples/p5.Font/PlayfairDisplay-Regular.ttf
        rename to test/manual-test-examples/type/font/PlayfairDisplay.ttf
        diff --git a/test/manual-test-examples/type/fontfaceobserver.standalone.js b/test/manual-test-examples/type/fontfaceobserver.standalone.js
        new file mode 100644
        index 0000000000..3177ec6d94
        --- /dev/null
        +++ b/test/manual-test-examples/type/fontfaceobserver.standalone.js
        @@ -0,0 +1,8 @@
        +/* Font Face Observer v2.3.0 - © Bram Stein. License: BSD-3-Clause */(function(){function p(a,c){document.addEventListener?a.addEventListener("scroll",c,!1):a.attachEvent("scroll",c)}function u(a){document.body?a():document.addEventListener?document.addEventListener("DOMContentLoaded",function b(){document.removeEventListener("DOMContentLoaded",b);a()}):document.attachEvent("onreadystatechange",function g(){if("interactive"==document.readyState||"complete"==document.readyState)document.detachEvent("onreadystatechange",g),a()})};function w(a){this.g=document.createElement("div");this.g.setAttribute("aria-hidden","true");this.g.appendChild(document.createTextNode(a));this.h=document.createElement("span");this.i=document.createElement("span");this.m=document.createElement("span");this.j=document.createElement("span");this.l=-1;this.h.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.i.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";
        +this.j.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.m.style.cssText="display:inline-block;width:200%;height:200%;font-size:16px;max-width:none;";this.h.appendChild(this.m);this.i.appendChild(this.j);this.g.appendChild(this.h);this.g.appendChild(this.i)}
        +function x(a,c){a.g.style.cssText="max-width:none;min-width:20px;min-height:20px;display:inline-block;overflow:hidden;position:absolute;width:auto;margin:0;padding:0;top:-999px;white-space:nowrap;font-synthesis:none;font:"+c+";"}function B(a){var c=a.g.offsetWidth,b=c+100;a.j.style.width=b+"px";a.i.scrollLeft=b;a.h.scrollLeft=a.h.scrollWidth+100;return a.l!==c?(a.l=c,!0):!1}function C(a,c){function b(){var e=g;B(e)&&null!==e.g.parentNode&&c(e.l)}var g=a;p(a.h,b);p(a.i,b);B(a)};function D(a,c,b){c=c||{};b=b||window;this.family=a;this.style=c.style||"normal";this.weight=c.weight||"normal";this.stretch=c.stretch||"normal";this.context=b}var E=null,F=null,G=null,H=null;function I(a){null===F&&(M(a)&&/Apple/.test(window.navigator.vendor)?(a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))(?:\.([0-9]+))/.exec(window.navigator.userAgent),F=!!a&&603>parseInt(a[1],10)):F=!1);return F}function M(a){null===H&&(H=!!a.document.fonts);return H}
        +function N(a,c){var b=a.style,g=a.weight;if(null===G){var e=document.createElement("div");try{e.style.font="condensed 100px sans-serif"}catch(q){}G=""!==e.style.font}return[b,g,G?a.stretch:"","100px",c].join(" ")}
        +D.prototype.load=function(a,c){var b=this,g=a||"BESbswy",e=0,q=c||3E3,J=(new Date).getTime();return new Promise(function(K,L){if(M(b.context)&&!I(b.context)){var O=new Promise(function(r,t){function h(){(new Date).getTime()-J>=q?t(Error(""+q+"ms timeout exceeded")):b.context.document.fonts.load(N(b,'"'+b.family+'"'),g).then(function(n){1<=n.length?r():setTimeout(h,25)},t)}h()}),P=new Promise(function(r,t){e=setTimeout(function(){t(Error(""+q+"ms timeout exceeded"))},q)});Promise.race([P,O]).then(function(){clearTimeout(e);
        +K(b)},L)}else u(function(){function r(){var d;if(d=-1!=k&&-1!=l||-1!=k&&-1!=m||-1!=l&&-1!=m)(d=k!=l&&k!=m&&l!=m)||(null===E&&(d=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent),E=!!d&&(536>parseInt(d[1],10)||536===parseInt(d[1],10)&&11>=parseInt(d[2],10))),d=E&&(k==y&&l==y&&m==y||k==z&&l==z&&m==z||k==A&&l==A&&m==A)),d=!d;d&&(null!==f.parentNode&&f.parentNode.removeChild(f),clearTimeout(e),K(b))}function t(){if((new Date).getTime()-J>=q)null!==f.parentNode&&f.parentNode.removeChild(f),
        +L(Error(""+q+"ms timeout exceeded"));else{var d=b.context.document.hidden;if(!0===d||void 0===d)k=h.g.offsetWidth,l=n.g.offsetWidth,m=v.g.offsetWidth,r();e=setTimeout(t,50)}}var h=new w(g),n=new w(g),v=new w(g),k=-1,l=-1,m=-1,y=-1,z=-1,A=-1,f=document.createElement("div");f.dir="ltr";x(h,N(b,"sans-serif"));x(n,N(b,"serif"));x(v,N(b,"monospace"));f.appendChild(h.g);f.appendChild(n.g);f.appendChild(v.g);b.context.document.body.appendChild(f);y=h.g.offsetWidth;z=n.g.offsetWidth;A=v.g.offsetWidth;t();
        +C(h,function(d){k=d;r()});x(h,N(b,'"'+b.family+'",sans-serif'));C(n,function(d){l=d;r()});x(n,N(b,'"'+b.family+'",serif'));C(v,function(d){m=d;r()});x(v,N(b,'"'+b.family+'",monospace'))})})};"object"===typeof module?module.exports=D:(window.FontFaceObserver=D,window.FontFaceObserver.prototype.load=D.prototype.load);}());
        diff --git a/test/manual-test-examples/type/img/LEFT.BL.lead.png b/test/manual-test-examples/type/img/LEFT.BL.lead.png
        new file mode 100644
        index 0000000000..5169f1aa06
        Binary files /dev/null and b/test/manual-test-examples/type/img/LEFT.BL.lead.png differ
        diff --git a/test/manual-test-examples/type/img/LEFT.BOTTOM.lead.png b/test/manual-test-examples/type/img/LEFT.BOTTOM.lead.png
        new file mode 100644
        index 0000000000..f2f979214a
        Binary files /dev/null and b/test/manual-test-examples/type/img/LEFT.BOTTOM.lead.png differ
        diff --git a/test/manual-test-examples/type/img/LEFT.CENTER.lead.png b/test/manual-test-examples/type/img/LEFT.CENTER.lead.png
        new file mode 100644
        index 0000000000..6e508183e5
        Binary files /dev/null and b/test/manual-test-examples/type/img/LEFT.CENTER.lead.png differ
        diff --git a/test/manual-test-examples/type/img/LEFT.TOP.lead.png b/test/manual-test-examples/type/img/LEFT.TOP.lead.png
        new file mode 100644
        index 0000000000..eb55c09604
        Binary files /dev/null and b/test/manual-test-examples/type/img/LEFT.TOP.lead.png differ
        diff --git a/test/manual-test-examples/type/img/boundingBoxBreaks.p5.jpg b/test/manual-test-examples/type/img/boundingBoxBreaks.p5.jpg
        new file mode 100644
        index 0000000000..27a1f3404f
        Binary files /dev/null and b/test/manual-test-examples/type/img/boundingBoxBreaks.p5.jpg differ
        diff --git a/test/manual-test-examples/type/img/boundingBoxBreaksLeft.p5.jpg b/test/manual-test-examples/type/img/boundingBoxBreaksLeft.p5.jpg
        new file mode 100644
        index 0000000000..75a6ffd460
        Binary files /dev/null and b/test/manual-test-examples/type/img/boundingBoxBreaksLeft.p5.jpg differ
        diff --git a/test/manual-test-examples/type/img/boundingBoxMulti.p5.jpg b/test/manual-test-examples/type/img/boundingBoxMulti.p5.jpg
        new file mode 100644
        index 0000000000..81eb429477
        Binary files /dev/null and b/test/manual-test-examples/type/img/boundingBoxMulti.p5.jpg differ
        diff --git a/test/manual-test-examples/type/img/loadedFontSketch.p5.png b/test/manual-test-examples/type/img/loadedFontSketch.p5.png
        new file mode 100644
        index 0000000000..5beadcb1cf
        Binary files /dev/null and b/test/manual-test-examples/type/img/loadedFontSketch.p5.png differ
        diff --git a/test/manual-test-examples/type/img/multilineAlignChar.p5.png b/test/manual-test-examples/type/img/multilineAlignChar.p5.png
        new file mode 100644
        index 0000000000..253ca8b5d7
        Binary files /dev/null and b/test/manual-test-examples/type/img/multilineAlignChar.p5.png differ
        diff --git a/test/manual-test-examples/type/img/multilineAlignSketch.p5.png b/test/manual-test-examples/type/img/multilineAlignSketch.p5.png
        new file mode 100644
        index 0000000000..1562e1cadf
        Binary files /dev/null and b/test/manual-test-examples/type/img/multilineAlignSketch.p5.png differ
        diff --git a/test/manual-test-examples/type/img/multilineAlignSketch.png b/test/manual-test-examples/type/img/multilineAlignSketch.png
        new file mode 100644
        index 0000000000..589ec53b9e
        Binary files /dev/null and b/test/manual-test-examples/type/img/multilineAlignSketch.png differ
        diff --git a/test/manual-test-examples/type/img/p5.jpg b/test/manual-test-examples/type/img/p5.jpg
        new file mode 100644
        index 0000000000..627ed49b3e
        Binary files /dev/null and b/test/manual-test-examples/type/img/p5.jpg differ
        diff --git a/test/manual-test-examples/type/img/p5v2.jpg b/test/manual-test-examples/type/img/p5v2.jpg
        new file mode 100644
        index 0000000000..deb7b8c7d9
        Binary files /dev/null and b/test/manual-test-examples/type/img/p5v2.jpg differ
        diff --git a/test/manual-test-examples/type/img/proc.jpg b/test/manual-test-examples/type/img/proc.jpg
        new file mode 100644
        index 0000000000..ffcaca9ecd
        Binary files /dev/null and b/test/manual-test-examples/type/img/proc.jpg differ
        diff --git a/test/manual-test-examples/type/img/text-width-issue.png b/test/manual-test-examples/type/img/text-width-issue.png
        new file mode 100644
        index 0000000000..fbf900718f
        Binary files /dev/null and b/test/manual-test-examples/type/img/text-width-issue.png differ
        diff --git a/test/manual-test-examples/type/img/textAlignSketch.p5.png b/test/manual-test-examples/type/img/textAlignSketch.p5.png
        new file mode 100644
        index 0000000000..f852182e91
        Binary files /dev/null and b/test/manual-test-examples/type/img/textAlignSketch.p5.png differ
        diff --git a/test/manual-test-examples/type/img/textAlignSketch.png b/test/manual-test-examples/type/img/textAlignSketch.png
        new file mode 100644
        index 0000000000..909954aa14
        Binary files /dev/null and b/test/manual-test-examples/type/img/textAlignSketch.png differ
        diff --git a/test/manual-test-examples/type/img/textAllAlignmentsSketch.p5.jpg b/test/manual-test-examples/type/img/textAllAlignmentsSketch.p5.jpg
        new file mode 100644
        index 0000000000..e091e58a59
        Binary files /dev/null and b/test/manual-test-examples/type/img/textAllAlignmentsSketch.p5.jpg differ
        diff --git a/test/manual-test-examples/type/img/textAllAlignmentsSketch.png b/test/manual-test-examples/type/img/textAllAlignmentsSketch.png
        new file mode 100644
        index 0000000000..7d12fa834a
        Binary files /dev/null and b/test/manual-test-examples/type/img/textAllAlignmentsSketch.png differ
        diff --git a/test/manual-test-examples/type/img/textFontSketch.jpg b/test/manual-test-examples/type/img/textFontSketch.jpg
        new file mode 100644
        index 0000000000..53b7f08456
        Binary files /dev/null and b/test/manual-test-examples/type/img/textFontSketch.jpg differ
        diff --git a/test/manual-test-examples/type/img/textFontSketch.p5.jpg b/test/manual-test-examples/type/img/textFontSketch.p5.jpg
        new file mode 100644
        index 0000000000..fb07179ccb
        Binary files /dev/null and b/test/manual-test-examples/type/img/textFontSketch.p5.jpg differ
        diff --git a/test/manual-test-examples/type/img/textLeadingBaseline.p5.jpg b/test/manual-test-examples/type/img/textLeadingBaseline.p5.jpg
        new file mode 100644
        index 0000000000..b2fe9c75da
        Binary files /dev/null and b/test/manual-test-examples/type/img/textLeadingBaseline.p5.jpg differ
        diff --git a/test/manual-test-examples/type/img/textLeadingBottom.p5.jpg b/test/manual-test-examples/type/img/textLeadingBottom.p5.jpg
        new file mode 100644
        index 0000000000..f9712d1e91
        Binary files /dev/null and b/test/manual-test-examples/type/img/textLeadingBottom.p5.jpg differ
        diff --git a/test/manual-test-examples/type/img/textLeadingCenter.p5.jpg b/test/manual-test-examples/type/img/textLeadingCenter.p5.jpg
        new file mode 100644
        index 0000000000..b52434b93c
        Binary files /dev/null and b/test/manual-test-examples/type/img/textLeadingCenter.p5.jpg differ
        diff --git a/test/manual-test-examples/type/img/textLeadingTop.p5.jpg b/test/manual-test-examples/type/img/textLeadingTop.p5.jpg
        new file mode 100644
        index 0000000000..81eb429477
        Binary files /dev/null and b/test/manual-test-examples/type/img/textLeadingTop.p5.jpg differ
        diff --git a/test/manual-test-examples/type/img/textOverlapSketch.p5.png b/test/manual-test-examples/type/img/textOverlapSketch.p5.png
        new file mode 100644
        index 0000000000..c058ee6f8e
        Binary files /dev/null and b/test/manual-test-examples/type/img/textOverlapSketch.p5.png differ
        diff --git a/test/manual-test-examples/type/img/textOverlapSketch.png b/test/manual-test-examples/type/img/textOverlapSketch.png
        new file mode 100644
        index 0000000000..397fe26d46
        Binary files /dev/null and b/test/manual-test-examples/type/img/textOverlapSketch.png differ
        diff --git a/test/manual-test-examples/type/img/textSizeSketch.p5.png b/test/manual-test-examples/type/img/textSizeSketch.p5.png
        new file mode 100644
        index 0000000000..0e4d2505f7
        Binary files /dev/null and b/test/manual-test-examples/type/img/textSizeSketch.p5.png differ
        diff --git a/test/manual-test-examples/type/img/textSizeSketch.png b/test/manual-test-examples/type/img/textSizeSketch.png
        new file mode 100644
        index 0000000000..c25f2673ae
        Binary files /dev/null and b/test/manual-test-examples/type/img/textSizeSketch.png differ
        diff --git a/test/manual-test-examples/type/img/textSketch.p5.png b/test/manual-test-examples/type/img/textSketch.p5.png
        new file mode 100644
        index 0000000000..b7d50e717d
        Binary files /dev/null and b/test/manual-test-examples/type/img/textSketch.p5.png differ
        diff --git a/test/manual-test-examples/type/img/textSketch.png b/test/manual-test-examples/type/img/textSketch.png
        new file mode 100644
        index 0000000000..3bbb8cbe62
        Binary files /dev/null and b/test/manual-test-examples/type/img/textSketch.png differ
        diff --git a/test/manual-test-examples/type/img/textStyleSketch.p5.jpg b/test/manual-test-examples/type/img/textStyleSketch.p5.jpg
        new file mode 100644
        index 0000000000..a8bfe6bf58
        Binary files /dev/null and b/test/manual-test-examples/type/img/textStyleSketch.p5.jpg differ
        diff --git a/test/manual-test-examples/type/img/textVerticalAlign.p5.png b/test/manual-test-examples/type/img/textVerticalAlign.p5.png
        new file mode 100644
        index 0000000000..ad6782e882
        Binary files /dev/null and b/test/manual-test-examples/type/img/textVerticalAlign.p5.png differ
        diff --git a/test/manual-test-examples/type/img/textVerticalAlign.png b/test/manual-test-examples/type/img/textVerticalAlign.png
        new file mode 100644
        index 0000000000..4e1e7e0ecf
        Binary files /dev/null and b/test/manual-test-examples/type/img/textVerticalAlign.png differ
        diff --git a/test/manual-test-examples/type/img/textWidthSketch.p5.png b/test/manual-test-examples/type/img/textWidthSketch.p5.png
        new file mode 100644
        index 0000000000..3942aad917
        Binary files /dev/null and b/test/manual-test-examples/type/img/textWidthSketch.p5.png differ
        diff --git a/test/manual-test-examples/type/img/textWidthSketch.png b/test/manual-test-examples/type/img/textWidthSketch.png
        new file mode 100644
        index 0000000000..ee10f578c2
        Binary files /dev/null and b/test/manual-test-examples/type/img/textWidthSketch.png differ
        diff --git a/test/manual-test-examples/type/img/typographyLetterSketch.p5.png b/test/manual-test-examples/type/img/typographyLetterSketch.p5.png
        new file mode 100644
        index 0000000000..03826a12fc
        Binary files /dev/null and b/test/manual-test-examples/type/img/typographyLetterSketch.p5.png differ
        diff --git a/test/manual-test-examples/type/img/typographyLetterSketch.png b/test/manual-test-examples/type/img/typographyLetterSketch.png
        new file mode 100644
        index 0000000000..08465e118c
        Binary files /dev/null and b/test/manual-test-examples/type/img/typographyLetterSketch.png differ
        diff --git a/test/manual-test-examples/type/index.html b/test/manual-test-examples/type/index.html
        new file mode 100755
        index 0000000000..3beba27d44
        --- /dev/null
        +++ b/test/manual-test-examples/type/index.html
        @@ -0,0 +1,2679 @@
        +<html>
        +
        +<head>
        +  <meta charset="UTF-8">
        +  <!-- <script language="javascript" type="text/javascript" src="img/../../../../lib/p5.js"></script-->
        +  <style>
        +    body {
        +      padding: 0;
        +      margin: 0;
        +    }
        +
        +    canvas {
        +      border: 1px solid #f0f0f0;
        +      display: block;
        +    }
        +
        +    img {
        +      border: 1px solid #fff;
        +    }
        +
        +    img.gray {
        +      border: 1px solid #eee;
        +    }
        +
        +    img.red {
        +      border: 1px solid #f00;
        +    }
        +
        +    div {
        +      margin: 40px 0px;
        +    }
        +
        +    p.caption {
        +      margin: 2px;
        +      font-weight: bold;
        +    }
        +  </style>
        +</head>
        +
        +
        +
        +<body>
        +  <script type="module">
        +
        +    import p5 from '../../../src/app.js';
        +    //import 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.11.0/p5.js';
        +
        +    let textSketch = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(240, 160);
        +        //console.log(p.textProperties());
        +        p.background(204);
        +        p.textSize(19.5).fill(255).text('Default Text', 10, 30)
        +        p.noStroke().fill(57, 112, 155).text('Black No Stroke Text', 10, 60)
        +        p.fill(120).textStyle(p.NORMAL).textSize(13.5).text('Simple long Text: Lorem Ipsum is simply dummy text of the printing and typesetting industry.', 10, 92, 220, 60);
        +        p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      };
        +    };
        +
        +
        +    let directFontSet = function (p) {
        +      let font = "italic bold 32px Georgia";
        +      p.setup = async function () {
        +        p.createCanvas(550, 280);
        +        p.textSize(32);
        +        p.text("Normal font using the defaults", 20, 40);
        +
        +        p.textFont(font);
        +        p.text('direct: ' + font, 20, 80);
        +
        +        p.textFont(font = font.replace('italic bold', 'oblique'));
        +        p.text('direct: ' + font, 20, 120);
        +
        +        p.textFont(font = font.replace(32, 36).replace('oblique', 800));
        +        p.text('direct: ' + font, 20, 160);
        +
        +        p.textFont(font = font.replace('800', 'small-caps'));
        +        p.text('direct: ' + font, 20, 200);
        +
        +        p.textFont(font = '2em BADFONT, Times');
        +        p.text('direct: ' + font, 20, 240);
        +
        +        //console.log(p.drawingContext.font, p._renderer.states);
        +      }
        +    };
        +
        +    const fontStretchSketch = function (p) {
        +      let url = 'url(https://fonts.gstatic.com/s/inconsolata/v31/QlddNThLqRwH-OJ1UHjlKENVzlm-WkL3GZQmAwPyya15.woff2) format("woff2")';
        +      let opts = ['ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded'];
        +      p.setup = async function () {
        +        p.createCanvas(900, 330);
        +        let f = await p.loadFont(url, 'Inconsolata', { stretch: "50% 200%" });
        +        p.textFont(f, 32);
        +        p.text(`Hello world (default: normal)`, 5, 35);
        +        opts.forEach((opt, i) => {
        +          p.textFont(f, 32, { fontStretch: opt });
        +          p.text(`Hello world (${opt})`, 5, 35 * (i + 2));
        +        });
        +      };
        +    };
        +
        +    const singleWideLine = function (p) {
        +      let x = 20, y = 10, w = 350, h = 24;
        +      let s, bb, suf = 'the-text-is-gonna-extend-beyond-the-max-width';
        +      p.setup = function () {
        +        p.createCanvas(450, 100);
        +        p.noFill() && p.rect(x, y, w, h);
        +        p.textSize(20).textLeading(22).fill(0).text(suf, x, y, w, h);
        +
        +        y += 40;
        +        h += 20;
        +        suf = 'the ' + suf;
        +        p.noFill() && p.rect(x, y, w, h);
        +        p.textSize(20).textLeading(22).fill(0).text(suf, x, y, w, h);
        +      };
        +    };
        +
        +    const fontStretchViaProp = function (p) {
        +      let url = 'url(https://fonts.gstatic.com/s/inconsolata/v31/QlddNThLqRwH-OJ1UHjlKENVzlm-WkL3GZQmAwPyya15.woff2) format("woff2")';
        +      let opts = ['ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded'];
        +      p.setup = async function () {
        +        p.createCanvas(900, 330);
        +        let f = await p.loadFont(url, 'Inconsolata', { stretch: "50% 200%" });
        +        p.textFont(f, 32);
        +        p.text(`Hello world (default: normal)`, 5, 35);
        +        opts.forEach((opt, i) => {
        +          p.textFont(f, 32);
        +          p.textProperty('fontStretch', opt);
        +          p.text(`Hello world (${opt})`, 5, 35 * (i + 2));
        +        });
        +      };
        +    };
        +
        +
        +    const textStretchViaProp = function (p) { // not used
        +      let url = 'url(https://fonts.gstatic.com/s/inconsolata/v31/QlddNThLqRwH-OJ1UHjlKENVzlm-WkL3GZQmAwPyya15.woff2) format("woff2")';
        +      let opts = ['ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded'];
        +      p.setup = async function () {
        +        p.createCanvas(900, 330);
        +        let f = await p.loadFont(url, 'Inconsolata', { stretch: "50% 200%" });
        +        p.textFont(f, 32);
        +        p.text(`Hello world (default: normal)`, 5, 35);
        +        opts.forEach((opt, i) => {
        +          p.textFont(f, 32);
        +          p.textProperty('textStretch', opt);
        +          p.text(`Hello world (${opt})`, 5, 35 * (i + 2));
        +        });
        +      };
        +    };
        +
        +
        +    const letterSpacingSketch = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(650, 375);
        +        p.textSize('2em');
        +        for (let i = 0; i < 10; i++) {
        +          let ls = p.map(i, 0, 10, -3, 7) + 'px';
        +          p.textProperty('letterSpacing', ls);
        +          let key = (ls === 0) ? 'default' : 'letterSpacing';
        +          p.text(`Hello world (${key}: ${ls})`, 5, 35 * (i + 1));
        +        };
        +      };
        +    };
        +
        +    const wordSpacingSketch = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(650, 375);
        +        p.textSize('2em');
        +        for (let i = 0; i < 10; i++) {
        +          let ls = p.map(i, 0, 10, 0, 60) + 'px';
        +          p.textProperty('wordSpacing', ls);
        +          p.text(`Hello world (wordSpacing: ${ls})`, 5, 35 * (i + 1));
        +        };
        +      };
        +    };
        +
        +    const fontKerningSketch = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(500, 125);
        +        p.textFont('serif', '2em');
        +        let opts = ['auto', 'normal', 'none'];
        +        for (let i = 0; i < opts.length; i++) {
        +          let k = opts[i];
        +          p.textProperty('fontKerning', k);
        +          p.text(`AVA Ta We  (fontKerning: ${k})`, 5, 35 * (i + 1));
        +        };
        +        //console.log(p.textProperties());
        +      };
        +    };
        +
        +    const fontVariantCapsSketch = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(500, 270);
        +        p.textFont('serif', '2em');
        +        let opts = ['small-caps', "all-small-caps", "petite-caps", "all-petite-caps", "unicase", "titling-caps"];
        +        p.text(`Hello World  (${this.drawingContext.fontVariantCaps})`, 5, 35);
        +        for (let i = 0; i < opts.length; i++) {
        +          p.textProperty('fontVariantCaps', opts[i]);
        +          p.text(`Hello World  (${opts[i]})`, 5, 35 * (i + 2));
        +        };
        +        //console.log(p.textProperties());
        +      };
        +    };
        +
        +    const textRenderingProp = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(500, 150);
        +        p.textFont('serif', '2em');
        +        let opts = ['auto', 'optimizeSpeed', 'optimizeLegibility', 'geometricPrecision'];
        +        for (let i = 0; i < opts.length; i++) {
        +          p.textProperty('textRendering', opts[i]);
        +          p.text(`Hello world (${opts[i] === 'auto' ? 'default: auto' : opts[i]})`, 10, 30 + 35 * i);
        +        };
        +      };
        +    };
        +
        +    const textToPointsSketch = function (p) {
        +      let pts, f;
        +      p.setup = async function () {
        +        p.createCanvas(750, 350);
        +        p.background(255);
        +        p.textFont(f = await p.loadFont('./font/LiberationSans-Bold.ttf'), 300);
        +        p.fill(220);
        +        p.stroke(0);
        +        p.text('p5*js', 5, 250);
        +        p.stroke('#E92D55');
        +        pts = f.textToPoints('p5*js', 5, 250);
        +        pts.forEach(pt => p.circle(pt.x, pt.y, 5));
        +      };
        +    };
        +
        +    let textToPathsSketch = function (p) {
        +      let paths, f;
        +      p.setup = async function () {
        +        p.createCanvas(750, 350);
        +        p.background(255);
        +        p.textFont(f = await p.loadFont('./font/LiberationSans-Bold.ttf'), 300);
        +        p.fill('#E92D55');
        +        paths = f.textToPaths('p5*js', 5, 250);
        +        //drawPaths(paths, p);
        +        f.drawPaths(p.drawingContext, paths, { fill: '#E92D55' });
        +      }
        +    }
        +
        +    let singleWordBounds = function (p) {
        +      let font;
        +
        +      function dt(s, x, y, halign, valign) {
        +        drawP5(s, x, y, halign, valign);
        +        drawDirect(s, x, y, halign, valign);
        +        drawPoints(s, x, y, halign, valign);
        +      }
        +      // call font.textToPoints() and draw the points
        +      function drawPoints(s, x, y, halign, valign) {
        +        p.textAlign(halign, valign);
        +        let pts = font.textToPoints(s, x, y);
        +        p.stroke('green').fill('green');
        +        pts.forEach(pt => p.circle(pt.x, pt.y, 2));
        +      }
        +
        +      function drawP5(s, x, y, halign, valign) {
        +        p.textAlign(halign, valign);
        +        let m = p.drawingContext.measureText(s);
        +        p.stroke(0) && p.line(0, y, p.width, y) && p.line(x, 0, x, p.height);
        +        p.fill(0) && p.noStroke() && p.text(s, x, y);
        +        let bb = p.textBounds(s, x, y);
        +        p.noFill() && p.stroke('red') && p.rect(bb.x, bb.y, bb.w, bb.h);
        +      }
        +
        +      function drawDirect(s, x, y, halign, valign) {
        +        p.drawingContext.textAlign = halign;
        +        p.drawingContext.textBaseline = valign;
        +        p.drawingContext.fillStyle = 'black';
        +        p.drawingContext.fillText(s, x, y);
        +        let m = p.drawingContext.measureText(s);
        +        let asc = m.actualBoundingBoxAscent;
        +        let desc = m.actualBoundingBoxDescent;
        +        let abl = m.actualBoundingBoxLeft;
        +        let abr = m.actualBoundingBoxRight;
        +        let bb = {
        +          x: x - abl,
        +          y: y - asc,
        +          w: abr + abl,
        +          h: asc + desc,
        +        };
        +        p.drawingContext.strokeStyle = 'red';
        +        p.drawingContext.strokeRect(bb.x, bb.y, bb.w, bb.h);
        +      }
        +
        +      p.setup = async function () {
        +        p.createCanvas(1200, 1050);
        +        font = await p.loadFont("font/Lato-Black.ttf");
        +        p.textFont(font, 64);
        +        let x = 150, y = 0;
        +        dt('.ooo', x, y += 80, p.LEFT, p.TOP);
        +        dt(',doi', x, y += 80, p.LEFT, p.TOP);
        +        dt('.dog', x, y += 80, p.LEFT, p.TOP);
        +        dt('-oog', x, y += 80, p.LEFT, p.TOP);
        +        dt('.ooo', x, y += 80, p.CENTER, p.TOP);
        +        dt(',doi', x, y += 80, p.CENTER, p.TOP);
        +        dt('.dog', x, y += 80, p.CENTER, p.TOP);
        +        dt('-oog', x, y += 80, p.CENTER, p.TOP);
        +        dt('.ooo', x, y += 80, p.RIGHT, p.TOP);
        +        dt(',doi', x, y += 80, p.RIGHT, p.TOP);
        +        dt('.dog', x, y += 80, p.RIGHT, p.TOP);
        +        dt('-oog', x, y += 80, p.RIGHT, p.TOP);
        +
        +        y = 0, x = 450;
        +        dt('.ooo', x, y += 80, p.LEFT, p.BASELINE);
        +        dt(',doi', x, y += 80, p.LEFT, p.BASELINE);
        +        dt('.dog', x, y += 80, p.LEFT, p.BASELINE);
        +        dt('-oog', x, y += 80, p.LEFT, p.BASELINE);
        +        dt('.ooo', x, y += 80, p.CENTER, p.BASELINE);
        +        dt(',doi', x, y += 80, p.CENTER, p.BASELINE);
        +        dt('.dog', x, y += 80, p.CENTER, p.BASELINE);
        +        dt('-oog', x, y += 80, p.CENTER, p.BASELINE);
        +        dt('.ooo', x, y += 80, p.RIGHT, p.BASELINE);
        +        dt(',doi', x, y += 80, p.RIGHT, p.BASELINE);
        +        dt('.dog', x, y += 80, p.RIGHT, p.BASELINE);
        +        dt('-oog', x, y += 80, p.RIGHT, p.BASELINE);
        +
        +        y = 0, x = 750;
        +        dt('.ooo', x, y += 80, p.LEFT, p._CTX_MIDDLE);
        +        dt(',doi', x, y += 80, p.LEFT, p._CTX_MIDDLE);
        +        dt('.dog', x, y += 80, p.LEFT, p._CTX_MIDDLE);
        +        dt('-oog', x, y += 80, p.LEFT, p._CTX_MIDDLE);
        +        dt('.ooo', x, y += 80, p.CENTER, p._CTX_MIDDLE);
        +        dt(',doi', x, y += 80, p.CENTER, p._CTX_MIDDLE);
        +        dt('.dog', x, y += 80, p.CENTER, p._CTX_MIDDLE);
        +        dt('-oog', x, y += 80, p.CENTER, p._CTX_MIDDLE);
        +        dt('.ooo', x, y += 80, p.RIGHT, p._CTX_MIDDLE);
        +        dt(',doi', x, y += 80, p.RIGHT, p._CTX_MIDDLE);
        +        dt('.dog', x, y += 80, p.RIGHT, p._CTX_MIDDLE);
        +        dt('-oog', x, y += 80, p.RIGHT, p._CTX_MIDDLE);
        +
        +        y = 0, x = 1050;
        +        dt('.ooo', x, y += 80, p.LEFT, p.BOTTOM);
        +        dt(',doi', x, y += 80, p.LEFT, p.BOTTOM);
        +        dt('.dog', x, y += 80, p.LEFT, p.BOTTOM);
        +        dt('-oog', x, y += 80, p.LEFT, p.BOTTOM);
        +        dt('.ooo', x, y += 80, p.CENTER, p.BOTTOM);
        +        dt(',doi', x, y += 80, p.CENTER, p.BOTTOM);
        +        dt('.dog', x, y += 80, p.CENTER, p.BOTTOM);
        +        dt('-oog', x, y += 80, p.CENTER, p.BOTTOM);
        +        dt('.ooo', x, y += 80, p.RIGHT, p.BOTTOM);
        +        dt(',doi', x, y += 80, p.RIGHT, p.BOTTOM);
        +        dt('.dog', x, y += 80, p.RIGHT, p.BOTTOM);
        +        dt('-oog', x, y += 80, p.RIGHT, p.BOTTOM);
        +
        +        p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      };
        +
        +    };
        +
        +    let singleLineBounds = function (p) {
        +      let x, y, s, showBB = 1;
        +
        +      p.setup = function () {
        +        p.createCanvas(240 * 5, 200);
        +        p.textSize(30);
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        y = 50;
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        //1
        +        x = 10, s = 'LEFT BASELINE is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y);
        +        //p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +        let bb = Object.values(p.textBounds(s, x, y));
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...bb);
        +        p.stroke(0) && p.strokeWeight(1) && p.line(x, 0, x, p.height); // v-line
        +
        +        //2
        +        x += 580, s = 'CENTER BASELINE is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +        p.stroke(0) && p.strokeWeight(1) && p.line(x, 0, x, p.height); // v-line
        +
        +        //3
        +        x += 600, s = 'RIGHT BASELINE is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +        p.stroke(0) && p.strokeWeight(1) && p.line(x, 0, x, p.height); // v-line
        +
        +
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        y += 50;
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        // 4
        +        x = 10, s = 'LEFT BOTTOM is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        // 5
        +        x += 580, s = 'CENTER BOTTOM is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        // 6
        +        x += 600, s = 'RIGHT BOTTOM is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        y += 30;
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        x = 10, s = 'LEFT CENTER is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 580, s = 'CENTER CENTER is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 600, s = 'RIGHT CENTER is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        y += 30;
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        x = 10, s = 'LEFT TOP is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 580, s = 'CENTER TOP is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 600, s = 'RIGHT TOP is easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      };
        +    };
        +
        +
        +    let loadedBoundsSingle = function (p) {
        +      let x, y, s, showBB = 1;
        +
        +      p.setup = async function () {
        +        p.createCanvas(240 * 5, 200);
        +        p.textFont(await p.loadFont("font/AndaleMono.ttf"));
        +        p.textSize(30);
        +
        +        let measureFunc = p._renderer._textBoundsSingle.bind(p._renderer);
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        y = 50;
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        //1
        +        x = 10, s = 'LEFT BASELINE easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y);
        +        //p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +        p.stroke(0) && p.strokeWeight(1) && p.line(x, 0, x, p.height); // v-line
        +
        +        //2
        +        x += 580, s = 'CENTER BASELINE easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +        p.stroke(0) && p.strokeWeight(1) && p.line(x, 0, x, p.height); // v-line
        +
        +
        +        //3
        +        x += 600, s = 'RIGHT BASELINE easy?';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +        p.stroke(0) && p.strokeWeight(1) && p.line(x, 0, x, p.height); // v-line
        +
        +
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        y += 50;
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        // 4
        +        x = 10, s = 'LEFT BOTTOM ease.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +
        +        // 5
        +        x += 580, s = 'CENTER BOTTOM ease.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +
        +        // 6
        +        x += 600, s = 'RIGHT BOTTOM easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        y += 30;
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        x = 10, s = 'LEFT CENTER easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 580, s = 'CENTER CENTER easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 600, s = 'RIGHT CENTER easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        y += 30;
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        x = 10, s = 'LEFT TOP easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 580, s = 'CENTER TOP is WoW';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 600, s = 'RIGHT TOP easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y);
        +        p.noFill() && p.stroke(0) && p.rect(...Object.values(p.fontBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      };
        +    };
        +
        +
        +    let manualLineBreaks = function (p) {
        +      let x, y, s, showBB = 0;
        +
        +      p.setup = function () {
        +        p.createCanvas(240 * 5, 400);
        +        p.textSize(30);
        +        p.textLeading(26);
        +
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        y = 50;
        +        p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        //1
        +        x = 10, s = 'LEFT BASELINE\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y);
        +        //p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +        p.stroke(0) && p.strokeWeight(1) && p.line(x, 0, x, p.height); // v-line
        +
        +        //2
        +        x += 580, s = 'CENTER BASELINE\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +        p.stroke(0) && p.strokeWeight(1) && p.line(x, 0, x, p.height); // v-line
        +
        +        //3
        +        x += 600, s = 'RIGHT BASELINE\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +        p.stroke(0) && p.strokeWeight(1) && p.line(x, 0, x, p.height); // v-line
        +
        +
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        y += 120;
        +        p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        // 4
        +        x = 10, s = 'LEFT BOTTOM\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        // 5
        +        x += 580, s = 'CENTER BOTTOM\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        // 6
        +        x += 600, s = 'RIGHT BOTTOM\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        y += 70;
        +        p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        x = 10, s = 'LEFT CENTER\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 580, s = 'CENTER CENTER\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 600, s = 'RIGHT CENTER\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        y += 70;
        +        p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y); //h-line
        +
        +        x = 10, s = 'LEFT TOP\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 580, s = 'CENTER TOP\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y);
        +        if (showBB) sp.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        x += 600, s = 'RIGHT TOP\nis easy.';
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y);
        +        if (showBB) p.noFill() && p.stroke(0) && p.rect(...Object.values(p.textBounds(s, x, y)));
        +
        +        p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +
        +      };
        +    };
        +
        +    let loadedBoundsWithPoints = function (p) {
        +      let x, y, w = 300, h = 70, showBB = 1, font;
        +      let s, suf = 'text gonna wrap when it gets too long and is then breaking.';
        +
        +      p.setup = async function () {
        +        p.createCanvas(1000, 420);
        +        let font = await p.loadFont("font/Lato-Black.ttf");
        +        p.textFont(font);
        +        p.textSize(20);
        +        p.textLeading(22);
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        y = 30;
        +
        +        //1
        +        x = 20, s = 'LEFT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +        // draw horizontal line at y=64
        +        p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        //2
        +        x += 330, s = 'CENTER BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +
        +
        +        //3
        +        x += 330, s = 'RIGHT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +
        +
        +
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        y += 100;
        +
        +        // 4
        +        x = 20, s = 'LEFT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +
        +        // 5
        +        x += 330, s = 'CENTER BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +
        +        // 6
        +        x += 330, s = 'RIGHT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        y += 100;
        +
        +        x = 20, s = 'LEFT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +
        +        x += 330, s = 'CENTER CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +
        +        x += 330, s = 'RIGHT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        y += 100;
        +
        +        x = 20, s = 'LEFT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +
        +        x += 330, s = 'CENTER TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +
        +        x += 330, s = 'RIGHT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(font.textBounds(s, x, y, w, h)));
        +        font.textToPoints(s, x, y, w, h).forEach(pt => p.stroke('green').point(pt.x, pt.y));
        +
        +        p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      }
        +    };
        +
        +
        +
        +    let systemBoundsMultiOverflow = function (p) {
        +      let x, y, w = 300, h = 70, showBB = 1;
        +      let s, suf = 'text gonna wrap when it gets too long and is then breaking. This is actually just overflow.';
        +
        +      p.setup = function () {
        +        p.createCanvas(1010, 430);
        +        p.textSize(20);
        +        p.textLeading(22);
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        y = 30;
        +        //1
        +        x = 20, s = 'LEFT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        // draw horizontal line at y=64
        +        p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        //2
        +        x += 330, s = 'CENTER BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        //3
        +        x += 330, s = 'RIGHT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        y += 100;
        +
        +        // 4
        +        x = 20, s = 'LEFT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        // 5
        +        x += 330, s = 'CENTER BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        // 6
        +        x += 330, s = 'RIGHT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        y += 100;
        +
        +        x = 20, s = 'LEFT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        x += 330, s = 'CENTER CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        x += 330, s = 'RIGHT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        y += 100;
        +
        +        x = 20, s = 'LEFT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        p.noFill() && p.stroke(0) && p.strokeWeight(.1) && p.line(0, y, p.width, y);
        +
        +        x += 330, s = 'CENTER TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        x += 330, s = 'RIGHT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      }
        +    };
        +
        +    let systemBoundsMulti = function (p) { // RectMode=CENTER(default)
        +      let x, y, w = 300, h = 70, showBB = 1;
        +      let s, suf = 'text gonna wrap when it gets too long and is then breaking.';
        +
        +      p.setup = function () {
        +        p.createCanvas(1010, 430);
        +        p.textSize(20);
        +        p.textLeading(22);
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        y = 30;
        +        //1
        +        x = 20, s = 'LEFT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        // draw horizontal line at y=64
        +        p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        //2
        +        x += 330, s = 'CENTER BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        //3
        +        x += 330, s = 'RIGHT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        y += 100;
        +
        +        // 4
        +        x = 20, s = 'LEFT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        // 5
        +        x += 330, s = 'CENTER BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        // 6
        +        x += 330, s = 'RIGHT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        y += 100;
        +
        +        x = 20, s = 'LEFT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        x += 330, s = 'CENTER CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        x += 330, s = 'RIGHT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        y += 100;
        +
        +        x = 20, s = 'LEFT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        p.noFill() && p.stroke(0) && p.strokeWeight(.1) && p.line(0, y, p.width, y);
        +
        +        x += 330, s = 'CENTER TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        x += 330, s = 'RIGHT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      }
        +    };
        +
        +    let systemBoundsMulti_RectModeCenter = function (p) {
        +      //let x, y, w = 300, h = 70, 
        +      let x = 170, ox = x, y = 65, w = 300, h = 70, showBB = 1, showPoints = 1;
        +      let s, suf = 'text gonna wrap when it gets too long and is then breaking.';
        +
        +      p.setup = function () {
        +        p.createCanvas(1010, 430);
        +        p.textSize(20);
        +        p.textLeading(22);
        +        p.rectMode(p.CENTER);
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        //1
        +        s = 'LEFT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        // draw horizontal line at y=64
        +
        +
        +        //2
        +        x += 330, s = 'CENTER BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +
        +        //3
        +        x += 330, s = 'RIGHT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        x = ox, y += 100;
        +
        +        // 4
        +        s = 'LEFT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        // 5
        +        x += 330, s = 'CENTER BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +
        +        // 6
        +        x += 330, s = 'RIGHT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        x = ox, y += 100;
        +
        +        s = 'LEFT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +
        +        x += 330, s = 'CENTER CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +
        +        x += 330, s = 'RIGHT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        x = ox, y += 100;
        +
        +        s = 'LEFT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +        //p.noFill() && p.stroke(0) && p.strokeWeight(.1) && p.line(0, y, p.width, y);
        +
        +        x += 330, s = 'CENTER TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +
        +        x += 330, s = 'RIGHT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +
        +
        +        //p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      }
        +    };
        +
        +    let systemBoundsMulti_RectModeCorners = function (p) {
        +      let x = 20, ox = x, y = 30, x2 = 320, ox2 = x2, y2 = 100, showBB = 1;
        +      let s, suf = 'text gonna wrap when it gets too long and is then breaking.';
        +
        +      p.setup = function () {
        +        p.createCanvas(1010, 430);
        +        p.textSize(20);
        +        p.textLeading(22);
        +        p.rectMode(p.CORNERS);
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        //1
        +        s = 'LEFT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        // draw horizontal line at y=64
        +        p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        //2
        +        x += 330, x2 += 330, s = 'CENTER BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +
        +
        +        //3
        +        x += 330, x2 += 330, s = 'RIGHT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        x = ox, x2 = ox2, y += 100, y2 += 100;
        +
        +        // 4
        +        s = 'LEFT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        // 5
        +        x += 330, x2 += 330, s = 'CENTER BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +
        +        // 6
        +        x += 330, x2 += 330, s = 'RIGHT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        x = ox, x2 = ox2, y += 100, y2 += 100;
        +
        +        s = 'LEFT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +
        +        x += 330, x2 += 330, s = 'CENTER CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +
        +        x += 330, x2 += 330, s = 'RIGHT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        x = ox, x2 = ox2, y += 100, y2 += 100;
        +
        +        s = 'LEFT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        //p.noFill() && p.stroke(0) && p.strokeWeight(.1) && p.line(0, y, p.width, y);
        +
        +        x += 330, x2 += 330, s = 'CENTER TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +
        +        x += 330, x2 += 330, s = 'RIGHT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +
        +        //p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      }
        +    };
        +
        +    let systemBoundsMulti_RectModeRadius = function (p) {
        +      let x = 170, ox = x, y = 65, rw = 150, ox2 = rw, rh = 35, showBB = 1;
        +      let s, suf = 'text gonna wrap when it gets too long and is then breaking.';
        +
        +      p.setup = function () {
        +        p.createCanvas(1010, 430);
        +        p.textSize(20);
        +        p.textLeading(22);
        +        p.rectMode(p.RADIUS);
        +        //p.rect(x,y,rw,rh);
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        //1
        +        s = 'LEFT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +
        +        // draw horizontal line at y=64
        +        p.stroke(0) && p.strokeWeight(.1) && p.line(0, y, p.width, y);
        +
        +        //2
        +        x += 330, s = 'CENTER BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +
        +
        +        //3
        +        x += 330, s = 'RIGHT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        x = ox, y += 100;
        +
        +        // 4
        +        s = 'LEFT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        // 5
        +        x += 330, s = 'CENTER BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +
        +        // 6
        +        x += 330, s = 'RIGHT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        x = ox, y += 100;
        +
        +        s = 'LEFT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +
        +        x += 330, s = 'CENTER CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +
        +        x += 330, s = 'RIGHT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        x = ox, y += 100;
        +
        +        s = 'LEFT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        //p.noFill() && p.stroke(0) && p.strokeWeight(.1) && p.line(0, y, p.width, y);
        +
        +        x += 330, s = 'CENTER TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +
        +        x += 330, s = 'RIGHT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +
        +        //p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      }
        +    };
        +
        +
        +    let customBoundsMulti_RectModeCenter = function (p) {
        +      //let x, y, w = 300, h = 70, 
        +      let x = 170, ox = x, y = 65, w = 300, h = 70, showBB = 1, showPoints = 1;
        +      let s, suf = 'text gonna wrap when it gets too long and is then breaking.';
        +
        +      p.setup = async function () {
        +        p.createCanvas(1010, 430);
        +        let font = await p.loadFont("font/Lato-Black.ttf");
        +        p.textFont(font, 20);
        +        p.textLeading(22);
        +        p.rectMode(p.CENTER);
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        //1
        +        s = 'LEFT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +
        +        // draw horizontal line at y=64
        +
        +
        +        //2
        +        x += 330, s = 'CENTER BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +
        +        //3
        +        x += 330, s = 'RIGHT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        x = ox, y += 100;
        +
        +        // 4
        +        s = 'LEFT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        // 5
        +        x += 330, s = 'CENTER BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +
        +        // 6
        +        x += 330, s = 'RIGHT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        x = ox, y += 100;
        +
        +        s = 'LEFT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +
        +        x += 330, s = 'CENTER CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +
        +        x += 330, s = 'RIGHT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        x = ox, y += 100;
        +
        +        s = 'LEFT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +        //p.noFill() && p.stroke(0) && p.strokeWeight(.1) && p.line(0, y, p.width, y);
        +
        +        x += 330, s = 'CENTER TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +
        +        x += 330, s = 'RIGHT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, w, h);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y, w, h);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, w, h)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, w, h).forEach(pt => p.point(pt.x, pt.y));
        +
        +        //p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      }
        +    };
        +
        +    let customBoundsMulti_RectModeCorners = function (p) {
        +      let x = 20, ox = x, y = 30, x2 = 320, ox2 = x2, y2 = 100, showBB = 1, showPoints = 1;
        +      let s, bb, suf = 'text gonna wrap when it gets too long and is then breaking.';
        +
        +      p.setup = async function () {
        +        p.createCanvas(1010, 430);
        +        let font = await p.loadFont("font/Lato-Black.ttf");
        +        p.textFont(font, 20);
        +        p.textLeading(22);
        +        p.rectMode(p.CORNERS);
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        //1
        +        p.circle(x, y, 5);
        +        p.circle(x2, y2, 5);
        +        s = 'LEFT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +        // draw horizontal line at y=64
        +        p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        //2
        +        x += 330, x2 += 330, s = 'CENTER BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +
        +        //3
        +        x += 330, x2 += 330, s = 'RIGHT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        x = ox, x2 = ox2, y += 100, y2 += 100;
        +
        +        // 4
        +        s = 'LEFT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +        // 5
        +        x += 330, x2 += 330, s = 'CENTER BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +        // 6
        +        x += 330, x2 += 330, s = 'RIGHT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        x = ox, x2 = ox2, y += 100, y2 += 100;
        +
        +        s = 'LEFT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +        x += 330, x2 += 330, s = 'CENTER CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +        x += 330, x2 += 330, s = 'RIGHT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        x = ox, x2 = ox2, y += 100, y2 += 100;
        +
        +        s = 'LEFT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        //p.noFill() && p.stroke(0) && p.strokeWeight(.1) && p.line(0, y, p.width, y);
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +        x += 330, x2 += 330, s = 'CENTER TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +        x += 330, x2 += 330, s = 'RIGHT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, x2, y2);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y, x2, y2);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, x2, y2)));
        +        if (showPoints) p.stroke('green') && font.textToPoints(s, x, y, x2, y2).forEach(pt => p.point(pt.x, pt.y));
        +
        +        //p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      }
        +    };
        +
        +    let customBoundsMulti_RectModeRadius = function (p) {
        +      let x = 170, ox = x, y = 65, rw = 150, ox2 = rw, rh = 35, showBB = 1;
        +      let s, pts, suf = 'text gonna wrap when it gets too long and is then breaking.';
        +
        +      p.setup = async function () {
        +        p.createCanvas(1010, 430);
        +        let font = await p.loadFont("font/Lato-Black.ttf");
        +        p.textFont(font, 20);
        +        p.textLeading(22);
        +        p.rectMode(p.RADIUS);
        +        //p.rect(x,y,rw,rh);
        +
        +        ///////////////////////////// BASELINE (L,C,R) ///////////////////////////////
        +        //1
        +        s = 'LEFT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BASELINE) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +
        +        // draw horizontal line at y=64
        +        p.stroke(0) && p.strokeWeight(.1) && p.line(0, y, p.width, y);
        +
        +        //2
        +        x += 330, s = 'CENTER BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BASELINE) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +
        +        //3
        +        x += 330, s = 'RIGHT BASELINE ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BASELINE) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +
        +        /////////////////////////// BOTTOM (L,C,R) /////////////////////////////////
        +        x = ox, y += 100;
        +
        +        // 4
        +        s = 'LEFT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.BOTTOM) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +        //p.stroke(0) && p.strokeWeight(1) && p.line(0, y, p.width, y);
        +
        +        // 5
        +        x += 330, s = 'CENTER BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.BOTTOM) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +
        +        // 6
        +        x += 330, s = 'RIGHT BOTTOM ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.BOTTOM) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +
        +        //////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +        x = ox, y += 100;
        +
        +        s = 'LEFT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.CENTER) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +
        +        x += 330, s = 'CENTER CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.CENTER) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +
        +        x += 330, s = 'RIGHT CENTER ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.CENTER) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +        x = ox, y += 100;
        +
        +        s = 'LEFT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.LEFT, p.TOP) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        //p.noFill() && p.stroke(0) && p.strokeWeight(.1) && p.line(0, y, p.width, y);
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +
        +        x += 330, s = 'CENTER TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.CENTER, p.TOP) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +
        +        x += 330, s = 'RIGHT TOP ' + suf;
        +        p.noFill() && p.stroke(0) && p.strokeWeight(1) && p.rect(x, y, rw, rh);
        +        p.fill(0) && p.noStroke() && p.textAlign(p.RIGHT, p.TOP) && p.text(s, x, y, rw, rh);
        +        if (showBB) p.noFill() && p.stroke('red') && p.rect(...Object.values(p.textBounds(s, x, y, rw, rh)));
        +        p.stroke('green') && font.textToPoints(s, x, y, rw, rh).forEach(pt => p.point(pt.x, pt.y));
        +
        +        //p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 20, 0));
        +      }
        +    };
        +
        +    let textVerticalAlign = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(240 * 4, 160);
        +        p.textSize(10);
        +        p.stroke(0);
        +
        +        ///////////////////////////// TOP (L,C,R) ///////////////////////////////
        +
        +        //1
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(10, 10, 220, 10);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.TOP);
        +        p.text('LEFT TOP is simply dummy text.', 10, 10);
        +
        +        //2
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(10, 60, 220, 60);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.TOP); // off-canvas
        +        p.text('CENTER TOP is simply dummy text.', 10, 60);
        +
        +        //3
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(10, 110, 220, 110);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.TOP); // off-canvas
        +        p.text('RIGHT TOP is simply dummy text.', 10, 110);
        +
        +        /////////////////////////// CENTER (L,C,R) /////////////////////////////////
        +
        +        //1
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(250, 10, 470, 10);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.CENTER);
        +        p.text('LEFT CENTER is simply dummy text.', 250, 10);
        +
        +        //2
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(250, 60, 470, 60);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.CENTER);
        +        p.text('CENTER CENTER is simply dummy text.', 250, 60);
        +
        +        //3
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(250, 110, 470, 110);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.CENTER);
        +        p.text('RIGHT CENTER is simply dummy text.', 250, 110);
        +
        +        //////////////////////////// BOTTOM (L,C,R) ////////////////////////////////
        +
        +        //1
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(490, 10, 710, 10);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.BOTTOM);
        +        p.text('LEFT BOTTOM is simply dummy text.', 490, 10);
        +
        +        //2
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(490, 60, 710, 60);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.BOTTOM);
        +        p.text('CENTER BOTTOM is simply dummy text.', 490, 60);
        +
        +        //3
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(490, 110, 710, 110);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.BOTTOM);
        +        p.text('RIGHT BOTTOM is simply dummy text.', 490, 110);
        +
        +        ////////////////////////////////// BASELINE /////////////////////////////////
        +
        +        //1
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(730, 10, 950, 10);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.BASELINE);
        +        p.text('LEFT BASELINE is simply dummy text.', 730, 10);
        +
        +        //2
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(730, 60, 950, 60);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.BASELINE);
        +        p.text('CENTER BASELINE is simply dummy text.', 730, 60);
        +
        +        //3
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.line(730, 110, 950, 110);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.BASELINE);
        +        p.text('RIGHT BASELINE is simply dummy text.', 730, 110);
        +
        +        ////////////////////////////////////////////////////////////////////////
        +
        +        p.loadImage('img/p5v2.jpg').then((img) => p.image(img, p.width - 30, 0));
        +
        +      };
        +    };
        +
        +    let multilineAlignWord = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(240 * 4, 160);
        +        p.textWrap(p.WORD);
        +        p.textSize(10);
        +        p.stroke(0);
        +        //p.noFill();
        +        //p.line(10, 10, p.width - 10, 10);
        +
        +        //1
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(10, 10, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.TOP);
        +        p.text(
        +          'LEFT TOP is simply dummy text of the printing and typesetting industry.',
        +          10, 10, 220, 40);
        +
        +        //2
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(10, 60, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.TOP);
        +        p.text(
        +          'CENTER TOP is simply dummy text of the printing and typesetting industry.',
        +          10, 60, 220, 40);
        +
        +        //3
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(10, 110, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.TOP);
        +        p.text(
        +          'RIGHT TOP is simply dummy text of the printing and typesetting industry.',
        +          10, 110, 220, 40
        +        );
        +
        +        //1
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(250, 10, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.CENTER);
        +        p.text(
        +          'LEFT CENTER is simply dummy text of the printing and typesetting industry.',
        +          250,
        +          10,
        +          220,
        +          40
        +        );
        +
        +        //2
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(250, 60, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.CENTER);
        +        p.text(
        +          'CENTER CENTER is simply dummy text of the printing and typesetting industry.',
        +          250,
        +          60,
        +          220,
        +          40
        +        );
        +
        +        //3
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(250, 110, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.CENTER);
        +        p.text(
        +          'RIGHT CENTER is simply dummy text of the printing and typesetting industry.',
        +          250,
        +          110,
        +          220,
        +          40
        +        );
        +
        +        //1
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(490, 10, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.BOTTOM);
        +        p.text(
        +          'LEFT BOTTOM is simply dummy text of the printing and typesetting industry.',
        +          490,
        +          10,
        +          220,
        +          40
        +        );
        +
        +        //2
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(490, 60, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.BOTTOM);
        +        p.text(
        +          'CENTER BOTTOM is simply dummy text of the printing and typesetting industry.',
        +          490,
        +          60,
        +          220,
        +          40
        +        );
        +
        +        //3
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(490, 110, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.BOTTOM);
        +        p.text(
        +          'RIGHT BOTTOM is simply dummy text of the printing and typesetting industry.',
        +          490,
        +          110,
        +          220,
        +          40
        +        );
        +
        +        //1
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(730, 10, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.BASELINE);
        +        p.text(
        +          'LEFT BASELINE is simply dummy text of the printing and typesetting industry.',
        +          730,
        +          10,
        +          220,
        +          40
        +        );
        +        //2
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(730, 60, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.BASELINE);
        +        p.text(
        +          'CENTER BASELINE is simply dummy text of the printing and typesetting industry.',
        +          730,
        +          60,
        +          220,
        +          40
        +        );
        +        //3
        +        p.noFill();
        +        p.strokeWeight(1);
        +        p.rect(730, 110, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.BASELINE);
        +        p.text(
        +          'RIGHT BASELINE is simply dummy text of the printing and typesetting industry.',
        +          730,
        +          110,
        +          220,
        +          40
        +        );
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +
        +      };
        +    };
        +
        +    let multilineAlignChar = function (p) {
        +
        +      p.setup = function () {
        +        p.createCanvas(240 * 4, 160);
        +        p.textWrap(p.CHAR);
        +        p.textSize(10);
        +        p.stroke(0);
        +        //1
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(10, 10, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.TOP);
        +
        +        p.text(
        +          'LEFT TOP is simply dummy text of the printing and typesetting industry.',
        +          10, 10, 220, 40);
        +        //2
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(10, 60, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.TOP);
        +        p.text(
        +          'CENTER TOP is simply dummy text of the printing and typesetting industry.',
        +          10, 60, 220, 40);
        +        //3
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(10, 110, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.TOP);
        +        p.text(
        +          'RIGHT TOP is simply dummy text of the printing and typesetting industry.',
        +          10, 110, 220, 40
        +        );
        +
        +        //1
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(250, 10, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.CENTER);
        +        p.text(
        +          'LEFT CENTER is simply dummy text of the printing and typesetting industry.',
        +          250,
        +          10,
        +          220,
        +          40
        +        );
        +        //2
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(250, 60, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.CENTER);
        +        p.text(
        +          'CENTER CENTER is simply dummy text of the printing and typesetting industry.',
        +          250,
        +          60,
        +          220,
        +          40
        +        );
        +        //3
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(250, 110, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.CENTER);
        +        p.text(
        +          'RIGHT CENTER is simply dummy text of the printing and typesetting industry.',
        +          250,
        +          110,
        +          220,
        +          40
        +        );
        +
        +        //1
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(490, 10, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.BOTTOM);
        +        p.text(
        +          'LEFT BOTTOM is simply dummy text of the printing and typesetting industry.',
        +          490,
        +          10,
        +          220,
        +          40
        +        );
        +        //2
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(490, 60, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.BOTTOM);
        +        p.text(
        +          'CENTER BOTTOM is simply dummy text of the printing and typesetting industry.',
        +          490,
        +          60,
        +          220,
        +          40
        +        );
        +        //3
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(490, 110, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.BOTTOM);
        +        p.text(
        +          'RIGHT BOTTOM is simply dummy text of the printing and typesetting industry.',
        +          490,
        +          110,
        +          220,
        +          40
        +        );
        +
        +        //1
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(730, 10, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.LEFT, p.BASELINE);
        +        p.text(
        +          'LEFT BASELINE is simply dummy text of the printing and typesetting industry.',
        +          730,
        +          10,
        +          220,
        +          40
        +        );
        +        //2
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(730, 60, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.CENTER, p.BASELINE);
        +        p.text(
        +          'CENTER BASELINE is simply dummy text of the printing and typesetting industry.',
        +          730,
        +          60,
        +          220,
        +          40
        +        );
        +        //3
        +        p.fill(255);
        +        p.strokeWeight(1);
        +        p.rect(730, 110, 220, 40);
        +        p.strokeWeight(0);
        +        p.fill(0);
        +        p.textAlign(p.RIGHT, p.BASELINE);
        +        p.text(
        +          'RIGHT BASELINE is simply dummy text of the printing and typesetting industry.',
        +          730,
        +          110,
        +          220,
        +          40
        +        );
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +      };
        +    };
        +
        +    let textFontSketch = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(240, 160);
        +        p.background(255);
        +        p.textSize(20);
        +        p.fill(0);
        +        p.strokeWeight(0);
        +        p.textFont('times');
        +        p.text('Times Font', 10, 30);
        +        p.textFont('arial');
        +        p.text('Arial Font', 10, 60);
        +        p.textFont('Courier');
        +        p.text('Courier Font', 10, 90);
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +      };
        +    };
        +
        +    let rightToLeftBounds = function (p) {
        +      p.setup = async function () {
        +        p.createCanvas(1000, 600);
        +        let bb, str = "السلام عليكم";
        +        let x = p.width / 2, y = p.height / 4 + 20;
        +
        +        p.textDirection(p.RIGHT_TO_LEFT);
        +
        +        p.textSize(200).textAlign(p.CENTER).noStroke().fill(0).text(str, x, y);
        +
        +        bb = p.textBounds(str, x, y);
        +        p.noFill().stroke(0, 255, 0).rect(bb.x, bb.y, bb.w, bb.h);
        +
        +        y += p.height / 2;
        +        p.textFont(await p.loadFont('./font/NotoNaskhArabic.woff2')).noStroke().fill(0).text(str, x, y);
        +        //console.log(p.textProperties());
        +
        +        bb = p.textBounds(str, x, y);
        +        p.noFill().stroke(0, 255, 0).strokeWeight(1).rect(bb.x, bb.y, bb.w, bb.h);
        +      };
        +    };
        +
        +    let loadedFontSketch = function (p) {
        +
        +      p.setup = async function () {
        +        p.createCanvas(240, 260);
        +        p.background(255);
        +        p.textSize(27);
        +        p.fill(0);
        +        p.strokeWeight(0);
        +
        +        // await plus (name, path) -> font-string
        +        await p.loadFont("font/acmesa.ttf", "acmesa");
        +        p.textFont("acmesa");
        +        p.text('Acmesa.ttf', 10, 40);
        +
        +        // await plus (name, path) -> font-object
        +        let f = await p.loadFont("font/AndaleMono.ttf", "andale");
        +        p.textFont(f);
        +        p.text('AndaleMono.ttf', 10, 80);
        +
        +        // then plus (name, path) -> font-object
        +        p.loadFont("font/FiraSans-Book.otf", "FiraSans").then(f => {
        +          p.textFont(f);
        +          p.text('FiraSans-Book.otf', 10, 120);
        +        });
        +
        +        // await plus (path) -> font-object
        +        f = await p.loadFont("font/Lato-Black.ttf");
        +        p.textFont(f);
        +        p.text('Lato-Black.ttf', 10, 160);
        +
        +
        +        // await plus (path) -> font-string
        +        await p.loadFont("font/PlayfairDisplay.ttf");
        +        p.textFont('PlayfairDisplay');
        +        p.text('PlayfairDisplay.ttf', 10, 200);
        +
        +        // test arabic writing
        +        f = await p.loadFont("font/NotoNaskhArabic.woff2");
        +        p.textFont(f, 40);
        +        //p.textProperty('direction', 'rtl');
        +        p.text("السلام عليكم", 10, 240);
        +
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +
        +      };
        +    };
        +
        +    let textAlignSketch = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(240, 160);
        +        p.fill(0);
        +        p.strokeWeight(0);
        +        p.textSize(12);
        +        p.textAlign(p.RIGHT, p.TOP);
        +        p.text('Top Right', 120, 30);
        +        p.textAlign(p.CENTER, p.CENTER);
        +        p.text('Center Center', 120, 60);
        +        p.textAlign(p.LEFT, p.BOTTOM);
        +        p.text('Left Bottom', 120, 90);
        +        p.textAlign(p.RIGHT, p.BASELINE);
        +        p.text('Right Baseline', 120, 90);
        +        p.strokeWeight(1);
        +        p.line(120, 0, 120, 160);
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +
        +      };
        +    };
        +
        +    let textAllAlignmentsSketch = function (p) {
        +      let systemFonts = ['Arial', 'Times New Roman', 'Consolas'];
        +      let font1, font2, font3;
        +      let hAligns = [p.LEFT, p.CENTER, p.RIGHT];
        +      let vAligns = [p.TOP, p.CENTER, p.BASELINE, p.BOTTOM];
        +      let padding = 10;
        +
        +      let drawFontAlignments = function (font, textString, xOff, yOff) {
        +        p.textFont(font);
        +        p.textSize(20);
        +        for (let h = 0; h < hAligns.length; h += 1) {
        +          for (let v = 0; v < vAligns.length; v += 1) {
        +
        +            // Distribute words across the screen
        +            let x = xOff + p.map(h, 0, hAligns.length - 1, padding, 400 - padding);
        +            let y = yOff + p.map(v, 0, vAligns.length - 1, padding, 200 - padding);
        +
        +            p.stroke(200);
        +            p.line(0, y, p.width, y);
        +            p.line(x, 0, x, p.height);
        +
        +            // Align the text & calculate the bounds
        +            p.textAlign(hAligns[h], vAligns[v]);
        +
        +            // Draw the text
        +            p.fill(255, 0, 0);
        +            p.noStroke();
        +            p.text(textString, x, y);
        +
        +            // Draw the (x, y) coordinates
        +            p.stroke(0);
        +            p.fill('#FF8132');
        +            p.ellipse(x, y, 3, 3);
        +          }
        +        }
        +      };
        +
        +
        +      p.setup = function () {
        +        let renderer = p.createCanvas(400, 600);
        +        renderer.elt.style.position = 'absolute';
        +        renderer.elt.style.top = '0';
        +        renderer.elt.style.left = '0';
        +        drawFontAlignments(systemFonts[0], 'Arial', 0, 0);
        +        drawFontAlignments(systemFonts[1], 'Times', 0, 200);
        +        drawFontAlignments(systemFonts[2], 'Consolas', 0, 400);
        +
        +        // These proprietary fonts aren't included in the repo!
        +        // p.loadFont("../arial.ttf", function(font) {drawFontAlignments(font, "Arial", 0, 0)});
        +        // p.loadFont("../times.ttf", function(font) {drawFontAlignments(font, "Times", 0, 200)});
        +        // p.loadFont("../consola.ttf", function(font) {drawFontAlignments(font, "Consolas", 0, 400)});
        +        //p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 40, 0));
        +      };
        +    };
        +
        +    let textLeadingTop = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(400, 200);
        +        p.textFont('Arial');
        +        p.fill(0);
        +        p.textSize(12);
        +
        +        p.line(0, 100, p.width, 100);
        +        p.textAlign(p.LEFT, p.TOP);
        +        p.strokeWeight(0);
        +
        +        let s10 = 'LEFT/TOP@10px',
        +          s20 = s10.replace('1', '2'),
        +          s30 = s10.replace('1', '3');
        +
        +        p.textLeading(10); // Set leading to 10
        +        p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        +        p.textLeading(20); // Set leading to 20
        +        p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        +        p.textLeading(30); // Set leading to 30
        +        p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +
        +      };
        +    };
        +
        +    let textLeadingCenter = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(400, 200);
        +        p.textFont('Arial');
        +        p.fill(0);
        +        p.textSize(12);
        +
        +        p.line(0, 100, p.width, 100);
        +        p.textAlign(p.LEFT, p.CENTER);
        +        p.strokeWeight(0);
        +
        +        let s10 = 'LEFT/CENTER@10px',
        +          s20 = s10.replace('1', '2'),
        +          s30 = s10.replace('1', '3');
        +
        +        p.textLeading(10); // Set leading to 10
        +        p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        +        p.textLeading(20); // Set leading to 20
        +        p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        +        p.textLeading(30); // Set leading to 30
        +        p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +
        +      };
        +    };
        +
        +    let textLeadingBaseline = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(400, 200);
        +        p.textFont('Arial');
        +        p.fill(0);
        +        p.textSize(12);
        +
        +        p.line(0, 100, p.width, 100);
        +        p.textAlign(p.LEFT, p.BASELINE);
        +        p.strokeWeight(0);
        +
        +        let s10 = 'LEFT/BASELINE@10px',
        +          s20 = s10.replace('1', '2'),
        +          s30 = s10.replace('1', '3');
        +
        +        p.textLeading(10); // Set leading to 10
        +        p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        +        p.textLeading(20); // Set leading to 20
        +        p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        +        p.textLeading(30); // Set leading to 30
        +        p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +
        +      };
        +    };
        +
        +    let textLeadingBottom = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(400, 200);
        +        p.textFont('Arial');
        +        p.fill(0);
        +        p.textSize(12);
        +
        +        p.line(0, 100, p.width, 100);
        +        p.textAlign(p.LEFT, p.BOTTOM);
        +        p.strokeWeight(0);
        +
        +        let s10 = 'LEFT/BOTTOM@10px',
        +          s20 = s10.replace('1', '2'),
        +          s30 = s10.replace('1', '3');
        +
        +        p.textLeading(10); // Set leading to 10
        +        p.text(s10 + '\n' + s10 + '\n' + s10, 10, 100);
        +        p.textLeading(20); // Set leading to 20
        +        p.text(s20 + '\n' + s20 + '\n' + s20, 140, 100);
        +        p.textLeading(30); // Set leading to 30
        +        p.text(s30 + '\n' + s30 + '\n' + s30, 270, 100);
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +
        +      };
        +    };
        +
        +    let textSizeSketch = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(240, 160);
        +        p.fill(0);
        +        p.strokeWeight(0);
        +        p.textSize(12);
        +        p.text('Font Size 12', 10, 30);
        +        p.textSize(14);
        +        p.text('Font Size 14', 10, 60);
        +        p.textSize(16);
        +        p.text('Font Size 16', 10, 90);
        +        //console.log(p.textProperties());
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +      };
        +    };
        +
        +    let textStyleSketch = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(240, 160);
        +        p.fill(0);
        +        p.strokeWeight(0);
        +        p.textSize(12);
        +        p.textStyle(p.NORMAL);
        +        p.text('Font Style Normal', 10, 30);
        +        p.textStyle(p.ITALIC);
        +        p.text('Font Style Italic', 10, 60);
        +        p.textStyle(p.BOLD);
        +        p.text('Font Style Bold', 10, 90);
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +      };
        +    };
        +
        +    let textWidthSketch = function (p) {
        +      p.setup = function () {
        +
        +        p.createCanvas(240, 160);
        +        p.fill(0);
        +        p.strokeWeight(0);
        +        p.textSize(12);
        +        let s = "What's the width of this line?";
        +        let tw = p.fontWidth(s), x = 10, y = 30;
        +        p.text(s, x, y);
        +        p.rect(x, y, tw, 2);
        +        p.text('width: ' + tw, x, y + 30);
        +
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +        return;
        +
        +        // TODO: compare textWidth with fontWidth
        +
        +        p.noFill();
        +        p.stroke(0);
        +        p.rect(x, y, tw, 2);
        +        p.line(x + tw, 0, x + tw, p.height);
        +
        +        y += 90;
        +        tw = p.textWidth(s);
        +        p.text(s, x, y);
        +        p.rect(x, y, tw, 2);
        +        p.text('width: ' + tw, x, y + 30);
        +
        +
        +      };
        +    };
        +
        +    let typographyLetterSketch = function (p) {
        +      p.setup = function () {
        +        let margin = 10;
        +        let gap = 46;
        +        let counter = 35;
        +        p.createCanvas(720, 320);
        +        p.background(0);
        +        p.textFont('Georgia');
        +        p.textSize(24);
        +        p.textStyle(p.BOLD);
        +        p.textAlign(p.CENTER, p.CENTER);
        +        p.translate(margin * 4, margin * 4);
        +        for (let y = 0; y < p.height - gap; y += gap) {
        +          for (let x = 0; x < p.width - gap; x += gap) {
        +            let letter = p.char(counter);
        +            if (letter === 'P' || letter === '5') {
        +              p.fill(255, 204, 0);
        +            } else if (letter === 'J' || letter === 'S') {
        +              p.fill(204, 0, 255);
        +            } else {
        +              p.fill(255);
        +            }
        +            p.text(letter, x, y);
        +            counter++;
        +          }
        +        }
        +        p.translate(margin * -4, margin * -4);
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 21, 1));
        +      };
        +    };
        +
        +    let textOverlapSketch = function (p) {
        +      p.setup = function () {
        +        p.createCanvas(240, 160);
        +        p.fill(0);
        +        p.strokeWeight(0);
        +        p.textSize(72);
        +        p.fill(0, 160); // Black with low opacity
        +        p.text('O', 0, 100);
        +        p.text('V', 30, 100);
        +        p.text('E', 60, 100);
        +        p.text('R', 90, 100);
        +        p.text('L', 120, 100);
        +        p.text('A', 150, 100);
        +        p.text('P', 180, 100);
        +        p.loadImage('img/p5v2.jpg').then(img => p.image(img, p.width - 20, 0));
        +      };
        +    };
        +
        +    new p5(textSketch, 'textSketch');
        +    new p5(textToPointsSketch, 'textToPointsSketch');
        +    new p5(textToPathsSketch, 'textToPathsSketch');
        +    new p5(fontStretchSketch, 'fontStretchSketch');
        +    new p5(fontStretchViaProp, 'fontStretchViaProp');
        +    new p5(directFontSet, 'directFontSet');
        +    new p5(letterSpacingSketch, 'letterSpacingSketch');
        +    new p5(wordSpacingSketch, 'wordSpacingSketch');
        +    new p5(fontKerningSketch, 'fontKerningSketch');
        +    new p5(fontVariantCapsSketch, 'fontVariantCapsSketch'); // warning
        +    new p5(textRenderingProp, 'textRenderingProp'); // warning
        +    new p5(singleLineBounds, 'singleLineBounds');
        +    new p5(loadedBoundsSingle, 'loadedBoundsSingle');
        +    new p5(systemBoundsMulti, 'systemBoundsMulti');
        +    new p5(systemBoundsMultiOverflow, 'systemBoundsMultiOverflow');
        +
        +    new p5(systemBoundsMulti_RectModeCenter, 'systemBoundsMulti_RectModeCenter');
        +    new p5(systemBoundsMulti_RectModeCorners, 'systemBoundsMulti_RectModeCorners');
        +    new p5(systemBoundsMulti_RectModeRadius, 'systemBoundsMulti_RectModeRadius');
        +
        +    new p5(customBoundsMulti_RectModeCenter, 'customBoundsMulti_RectModeCenter');
        +    new p5(customBoundsMulti_RectModeCorners, 'customBoundsMulti_RectModeCorners');
        +    new p5(customBoundsMulti_RectModeRadius, 'customBoundsMulti_RectModeRadius');
        +
        +    new p5(loadedBoundsWithPoints, 'loadedBoundsWithPoints');
        +    new p5(manualLineBreaks, 'manualLineBreaks');
        +    new p5(singleWordBounds, 'singleWordBounds');
        +    new p5(textVerticalAlign, 'textVerticalAlign');
        +    new p5(multilineAlignWord, 'multilineAlignWord');
        +    new p5(multilineAlignChar, 'multilineAlignChar');
        +    new p5(textFontSketch, 'textFontSketch');
        +    new p5(loadedFontSketch, 'loadedFontSketch');
        +    new p5(rightToLeftBounds, 'rightToLeftBounds');
        +    new p5(textAlignSketch, 'textAlignSketch');
        +    new p5(textAllAlignmentsSketch, 'textAllAlignmentsSketch');
        +    new p5(textAllAlignmentsSketch, 'textAllAlignmentsSketch2');
        +    new p5(textLeadingTop, 'textLeadingTop');
        +    new p5(textLeadingCenter, 'textLeadingCenter');
        +    new p5(textLeadingBaseline, 'textLeadingBaseline');
        +    new p5(textLeadingBottom, 'textLeadingBottom');
        +    new p5(textSizeSketch, 'textSizeSketch');
        +    new p5(textStyleSketch, 'textStyleSketch');
        +    new p5(textWidthSketch, 'textWidthSketch');
        +    new p5(typographyLetterSketch, 'typographyLetterSketch');
        +    new p5(singleWideLine, 'singleWideLine');
        +    new p5(textOverlapSketch, 'textOverlapSketch');
        +  </script>
        +
        +  <div id='textFontSketch'>
        +    <p class="caption">textFont</p>
        +    <img src="img/textFontSketch.p5.jpg" width="240" height="160" class="gray"></img>
        +    <img src="img/textFontSketch.jpg" width="240" height="160" class="gray"></img>
        +  </div>
        +
        +  <div id='loadedFontSketch'>
        +    <p class="caption">loadFont</p>
        +    <img src="img/loadedFontSketch.p5.png" width="240" height="240" class="gray"></img>
        +  </div>
        +
        +  <div id='rightToLeftBounds'>
        +    <p class="caption">right-to-leftBounds</p>
        +  </div>
        +
        +  <div id='textSketch'>
        +    <p class="caption">textAttributes</p>
        +    <img src="img/textSketch.p5.png" width="240" height="160"></img>
        +    <img src="img/textSketch.png" width="240" height="160"></img>
        +  </div>
        +
        +  <div id='textToPointsSketch'>
        +    <p class="caption">textToPoints</p>
        +  </div>
        +
        +  <div id='textToPathsSketch'>
        +    <p class="caption">textToPaths</p>
        +  </div>
        +
        +  <div id='fontStretchSketch'>
        +    <p class="caption">fontStretch (via textFont)</p>
        +  </div>
        +
        +  <div id='fontStretchViaProp'>
        +    <p class="caption">fontStretch (via textProperty)</p>
        +  </div>
        +
        +  <div id='directFontSet'>
        +    <p class="caption">directFontSet</p>
        +  </div>
        +
        +  <!-- <div id='textStretchViaProp'>
        +    <p class="caption">textStretch (via textProperty)</p>
        +  </div> -->
        +
        +  <div id='letterSpacingSketch'>
        +    <p class="caption">letterSpacing</p>
        +  </div>
        +
        +  <div id='wordSpacingSketch'>
        +    <p class="caption">wordSpacing</p>
        +  </div>
        +
        +  <div id='fontKerningSketch'>
        +    <p class="caption">fontKerning</p>
        +  </div>
        +
        +  <div id='fontVariantCapsSketch'>
        +    <p class="caption">fontVariantCaps (differs between FF/Chrome)</p>
        +  </div>
        +
        +  <div id='textRenderingProp'>
        +    <p class="caption">textRendering property</p>
        +  </div>
        +
        +  <div id='singleLineBounds'>
        +    <p class="caption">singleLineBounds</p>
        +  </div>
        +
        +  <div id='loadedBoundsSingle'>
        +    <p class="caption">loadedBoundsSingle</p>
        +  </div>
        +
        +  <div id='systemBoundsMulti'>
        +    <p class="caption">systemBoundsMulti (RectMode.DEFAULT)</p>
        +  </div>
        +  <div id='systemBoundsMulti_RectModeCenter'>
        +    <p class="caption">systemBoundsMulti (RectMode.CENTER)</p>
        +  </div>
        +  <div id='systemBoundsMulti_RectModeCorners'>
        +    <p class="caption">systemBoundsMulti (RectMode.CORNERS)</p>
        +  </div>
        +  <div id='systemBoundsMulti_RectModeRadius'>
        +    <p class="caption">systemBoundsMulti (RectMode.RADIUS)</p>
        +  </div>
        +
        +  <div id='loadedBoundsWithPoints'>
        +    <p class="caption">textToPointsBounds (RectMode.DEFAULT)</p>
        +  </div>
        +  <div id='customBoundsMulti_RectModeCenter'>
        +    <p class="caption">textToPointsBounds (RectMode.CENTER)</p>
        +  </div>
        +  <div id='customBoundsMulti_RectModeCorners'>
        +    <p class="caption">textToPointsBounds (RectMode.CORNERS)</p>
        +  </div>
        +  <div id='customBoundsMulti_RectModeRadius'>
        +    <p class="caption">textToPointsBounds (RectMode.RADIUS)</p>
        +  </div>
        +
        +
        +  <div id='systemBoundsMultiOverflow'>
        +    <p class="caption">systemBoundsMulti.Overflow</p>
        +  </div>
        +
        +  <div id='singleWordBounds'>
        +    <p class="caption">singleWordBounds</p>
        +  </div>
        +
        +  <div id='manualLineBreaks'>
        +    <p class="caption">manualLineBreaks</p>
        +    <img src="img/boundingBoxBreaks.p5.jpg" width="1200" height="400">
        +  </div>
        +
        +  <div id='textVerticalAlign'>
        +    <p class="caption">textVerticalAlign</p>
        +    <img src="img/textVerticalAlign.png" width="960" height="160" class="gray"></img>
        +    <img src="img/textVerticalAlign.p5.png" width="960" height="160" class="gray"></img>
        +  </div>
        +
        +  <div id='multilineAlignWord'>
        +    <p class="caption">multilineAlign.Word</p>
        +    <img src="img/multilineAlignSketch.png" width="960" height="160" class="gray"></img>
        +    <img src="img/multilineAlignSketch.p5.png" width="960" height="160" class="gray"></img>
        +  </div>
        +
        +  <div id='multilineAlignChar'>
        +    <p class="caption">multilineAlign.Char</p>
        +    <img src="img/multilineAlignChar.p5.png" width="960" height="160" class="gray"></img>
        +  </div>
        +
        +  <div id='textAlignSketch'>
        +    <p class="caption">textAlign</p>
        +    <img src="img/textAlignSketch.p5.png" width="240" height="160" class="gray"></img>
        +    <img src="img/textAlignSketch.png" width="240" height="160" class="gray"></img>
        +  </div>
        +
        +  <div id="textAllAlignmentsSketch" style="position:relative;">
        +    <img src="img/textAllAlignmentsSketch.png" width="400" height="600" style="display:inline-block;"></img>
        +    <p class="caption">Overlay (Processing)</p>
        +
        +  </div>
        +  <div id="textAllAlignmentsSketch2" style="position:relative;">
        +    <img src="img/textAllAlignmentsSketch.p5.jpg" width="400" height="600" style="display:inline-block;"></img>
        +    <p class="caption">Overlay (p5.js v1)</p>
        +  </div>
        +
        +  <div id='textLeadingTop'>
        +    <p class="caption">textLeading.Top</p>
        +    <img src="img/textLeadingTop.p5.jpg" width="400" height="200" class="gray">
        +    <img src="img/LEFT.TOP.lead.png" width="400" height="200" class="gray"></img>
        +  </div>
        +
        +  <div id='textLeadingCenter'>
        +    <p class="caption">textLeading.Center</p>
        +    <img src="img/textLeadingCenter.p5.jpg" width="400" height="200" class="gray"></img>
        +    <img src="img/LEFT.CENTER.lead.png" width="400" height="200" class="gray"></img>
        +  </div>
        +
        +  <div id='textLeadingBaseline'>
        +    <p class="caption">textLeading.Baseline</p>
        +    <img src="img/textLeadingBaseline.p5.jpg" width="400" height="200" class="gray"></img>
        +    <img src="img/LEFT.BL.lead.png" width="400" height="200" class="gray"></img>
        +  </div>
        +
        +  <div id='textLeadingBottom'>
        +    <p class="caption">textLeading.Bottom</p>
        +    <img src="img/textLeadingBottom.p5.jpg" width="400" height="200" class="gray">
        +    <img src="img/LEFT.BOTTOM.lead.png" width="400" height="200" class="gray"></img>
        +  </div>
        +
        +  <div id='textSizeSketch'>
        +    <p class="caption">textSizeSketch</p>
        +    <img src="img/textSizeSketch.p5.png" width="240" height="160" class="gray"></img>
        +    <img src="img/textSizeSketch.png" width="240" height="160" class="gray"></img>
        +  </div>
        +
        +  <div id='textStyleSketch'>
        +    <p class="caption">textStyleSketch</p>
        +    <img src="img/textStyleSketch.p5.jpg" width="240" height="160" class="gray"></img>
        +  </div>
        +
        +  <div id='textWidthSketch'>
        +    <p class="caption">fontWidth/textWidth</p>
        +    <img src="img/textWidthSketch.p5.png" width="240" height="160" class="gray"></img>
        +    <img src="img/textWidthSketch.png" width="240" height="160" class="gray"></img>
        +  </div>
        +
        +  <div id='typographyLetterSketch'>
        +    <p class="caption">typographyGrid</p>
        +    <img src="img/typographyLetterSketch.png" width="720" height="320"></img>
        +    <img src="img/typographyLetterSketch.p5.png" width="720" height="320"></img>
        +  </div>
        +
        +  <div id='singleWideLine'>
        +    <p class="caption">singleWideLine </p>
        +  </div>
        +
        +  <div id='textOverlapSketch'>
        +    <p class="caption">textAlphaSketch</p>
        +    <img src="img/textOverlapSketch.p5.png" width="240" height="160" class="gray"></img>
        +    <img src="img/textOverlapSketch.png" width="240" height="160" class="gray"></img>
        +  </div>
        +
        +</body>
        +
        +</html>
        \ No newline at end of file
        diff --git a/test/manual-test-examples/type/issues/fontSizeAdjustIssue.html b/test/manual-test-examples/type/issues/fontSizeAdjustIssue.html
        new file mode 100644
        index 0000000000..8373e11ad1
        --- /dev/null
        +++ b/test/manual-test-examples/type/issues/fontSizeAdjustIssue.html
        @@ -0,0 +1,85 @@
        +<html>
        +
        +<head>
        +  <meta charset="UTF-8">
        +  <!-- <script language="javascript" type="text/javascript" src="img/../../../../lib/p5.js"></script>
        +  <script language="javascript" type="text/javascript" src="img/sketch.js"></script> -->
        +  <style>
        +    body {
        +      padding: 0;
        +      margin: 0;
        +    }
        +
        +    canvas {
        +      border: 1px solid #f0f0f0;
        +      display: block;
        +    }
        +
        +    img {
        +      border: 1px solid #fff;
        +    }
        +
        +    div {
        +      margin: 100px 0px;
        +    }
        +
        +    .times {
        +      font-family: Times, serif;
        +      font-size: 14px;
        +    }
        +
        +    .verdana {
        +      font-family: Verdana, sans-serif;
        +      font-size: 14px;
        +    }
        +
        +    .adj-times-ex-height {
        +      font-size-adjust: 0.545;
        +    }
        +
        +    .adj-times-cap-height {
        +      font-size-adjust: cap-height 0.73;
        +    }
        +  </style>
        +</head>
        +
        +
        +
        +<body>
        +  <script type="module">
        +    import p5 from '../../../../src/app.js';
        +
        +    const sketch = function (p) {
        +
        +      // font-size-adjust property is not set, regardless of whether it is set directly or via p5
        +      // see example at: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust
        +
        +      p.setup = async function () {
        +        p.createCanvas(650, 375);
        +
        +        p.textFont('Verdana, sans-serif', '14px');
        +        p.text('A: This text uses the Verdana font (14px), which has relatively large lowercase letters.', 5, 20);
        +
        +        p.textFont('Times, Serif', '14px');
        +        p.text('B: This text uses the Times font (14px), which is hard to read in small sizes.', 5, 45);
        +
        +        // p.drawingContext.canvas.style['font-size-adjust'] = '0.1';
        +        // console.log(p.drawingContext.canvas.style['font-size-adjust']);
        +        
        +        p.textProperty('fontSizeAdjust', '0.2');
        +        //console.log(p.drawingContext.canvas.style['font-size-adjust']);
        +        p.text('C: This text in 14px Times font is adjusted to the same aspect value as the Verdana font, so lowercase letters are normalized across the two fonts.', 5, 60, 640, 100);
        +
        +        p.textProperty('fontSizeAdjust', 'cap-height 0.73');
        +        p.text('D: This text in 14px Times font is adjusted to the same cap-height to font size ratio as the Verdana font, so uppercase letters are normalized across the two fonts.', 5, 100, 640, 100);
        +      };
        +    };
        +
        +    new p5(sketch);
        +
        +  </script>
        +
        +</body>
        +
        +
        +</html>
        \ No newline at end of file
        diff --git a/test/manual-test-examples/type/issues/fontStretchIssue.html b/test/manual-test-examples/type/issues/fontStretchIssue.html
        new file mode 100644
        index 0000000000..ba7ab56db3
        --- /dev/null
        +++ b/test/manual-test-examples/type/issues/fontStretchIssue.html
        @@ -0,0 +1,45 @@
        +<html>
        +
        +<head>
        +  <meta charset="UTF-8">
        +  <!-- <script language="javascript" type="text/javascript" src="img/../../../../lib/p5.js"></script>
        +  <script language="javascript" type="text/javascript" src="img/sketch.js"></script> -->
        +  <style>
        +    body {
        +      padding: 0;
        +      margin: 0;
        +    }
        +
        +    canvas {
        +      border: 1px solid #f0f0f0;
        +      display: block;
        +    }
        +
        +  </style>
        +</head>
        +
        +<body>
        +  <script type="module">
        +    import p5 from '../../../../src/app.js';
        +
        +    // Font-stretch works but is not accepted as the font-string (see warning in the console)
        +    const sketch = function (p) {
        +
        +      let url = 'url(https://fonts.gstatic.com/s/inconsolata/v31/QlddNThLqRwH-OJ1UHjlKENVzlm-WkL3GZQmAwPyya15.woff2) format("woff2")';
        +      p.setup = async function () {
        +        p.createCanvas(900, 50);
        +        let f = await p.loadFont(url, 'Inconsolata');
        +        p.textFont(f, "2em", { fontStretch: 'ultra-expanded' });
        +        p.text(`Hello world (default: normal)`, 5, 35);
        +      };
        +
        +    };
        +
        +    new p5(sketch);
        +
        +  </script>
        +
        +</body>
        +
        +
        +</html>
        \ No newline at end of file
        diff --git a/test/manual-test-examples/type/text-to-paths.html b/test/manual-test-examples/type/text-to-paths.html
        new file mode 100644
        index 0000000000..97b464b8e7
        --- /dev/null
        +++ b/test/manual-test-examples/type/text-to-paths.html
        @@ -0,0 +1,91 @@
        +<html>
        +
        +<head>
        +  <meta charset='UTF-8'>
        +  <style>
        +    body {
        +      padding: 0;
        +      margin: 0;
        +    }
        +
        +    canvas {
        +      border: 1px solid #f0f0f0;
        +      display: block;
        +    }
        +
        +    img {
        +      border: 1px solid #fff;
        +    }
        +
        +    div {
        +      margin: 100px 0px;
        +    }
        +  </style>
        +</head>
        +
        +<body>
        +  <script type='module'>
        +    import p5 from '../../../src/app.js';
        +
        +    let sketch = function (p) {
        +      
        +      p.setup = async function () {
        +        p.createCanvas(750, 350);
        +        p.background(255);
        +        let f = await p.loadFont('./font/LiberationSans-Bold.ttf')
        +        p.textFont(f, 300);
        +        p.fill('#E92D55');
        +
        +        // draw the paths
        +        let paths = f.textToPaths('p5*js', 5, 250);
        +
        +        if (0) {
        +          f.drawPaths(p.drawingContext, paths, { fill: '#E92D55' }); // DIRECT
        +        }
        +        else {
        +          drawPaths(paths, p); // USING P5
        +        }
        +
        +        // draw the points
        +        f.textToPoints('p5*js', 5, 250).forEach((pt, i) => {
        +          p.fill(0);
        +          p.circle(pt.x, pt.y, 5);
        +        });
        +      }
        +    }
        +
        +    function drawPaths(commands, p) {
        +      
        +      p.beginShape();
        +
        +      // TODO: remove (dummy shape for now)
        +      p.vertex(0,0);
        +      p.vertex(0,0);
        +      p.vertex(0,0);
        +
        +      for (let k = 0; k < commands.length; k++) {
        +        let data = commands[k];
        +        let type = data.shift();
        +        if (type === 'M') {
        +          p.beginContour();
        +          p.vertex(...data);
        +        } else if (type === 'L') {
        +          p.vertex(...data);
        +        } else if (type === 'C') {
        +          p.bezierVertex(...data);
        +        } else if (type === 'Q') {
        +          p.quadraticVertex(...data);
        +        } else if (type === 'Z') {
        +          p.endContour();
        +        }
        +      }
        +      p.endShape();
        +    }
        +
        +    new p5(sketch);
        +  </script>
        +
        +</body>
        +
        +
        +</html>
        \ No newline at end of file
        diff --git a/test/manual-test-examples/type/text-to-points-buffer.html b/test/manual-test-examples/type/text-to-points-buffer.html
        new file mode 100644
        index 0000000000..d833ecd1fc
        --- /dev/null
        +++ b/test/manual-test-examples/type/text-to-points-buffer.html
        @@ -0,0 +1,76 @@
        +<html>
        +
        +<!-- 
        +CSS-style font properties (preferred, but doesn't support custom axes):
        +  font-weight: 650;
        +  font-style: oblique 80deg;  (must add deg)
        +  font-stretch: 75%;          (only accepts percentage with %)  
        +  font-optical-sizing: auto;  (only allows for 'auto' or 'none')
        +Font-variation style properties:
        +  font-variations-settings: 'wght' 650, 'slnt' 80, 'wdth' 75, 'opsz' 100;
        +-->
        +
        +<head>
        +  <meta charset='UTF-8'>
        +  <!-- <script language="javascript" type="text/javascript" src="./lib/Typr.js"></script>
        +  <script language="javascript" type="text/javascript" src="./lib/Typr.U.js"></script> -->
        +
        +  <style>
        +    body {
        +      padding: 0;
        +      margin: 0;
        +    }
        +
        +    canvas {
        +      border: 1px solid #f0f0f0;
        +      display: block;
        +    }
        +
        +    img {
        +      border: 1px solid #fff;
        +    }
        +
        +    div {
        +      margin: 100px 0px;
        +    }
        +  </style>
        +</head>
        +
        +
        +
        +<body>
        +  <script type='module'>
        +    import p5 from '../../../src/app.js';
        +    let sketch = async function (p) {
        +      let f, g, pts;
        +      p.setup = async function () {
        +        p.createCanvas(400, 300);
        +        f = await p.loadFont("https://fonts.gstatic.com/s/opensans/v40/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1y4nY1M2xLER.ttf");
        +        g = p.createGraphics(p.width, p.height)
        +        g.background(220);
        +        g.textAlign(p.CENTER, p.CENTER)
        +        g.textFont(f, 50);
        +        
        +        g.text('Serendipity', g.width / 2, g.height / 2);
        +        //let bb = g.textBounds('Serendipity', g.width / 2, g.height / 2);
        +        let bb = f.textBounds('Serendipity', g.width / 2, g.height / 2, { graphics: g });
        +        g.noFill();
        +        g.stroke(0);
        +        g.rect(bb.x, bb.y, bb.w, bb.h);
        +        
        +        pts = f.textToPoints('Serendipity', g.width / 2, g.height / 2, { graphics: g });
        +        pts.forEach((pt, i) => {
        +          g.fill(0);
        +          g.circle(pt.x, pt.y, 2);
        +        });
        +        p.image(g, 0, 0);
        +      }
        +    }
        +
        +    new p5(sketch);
        +  </script>
        +
        +</body>
        +
        +
        +</html>
        \ No newline at end of file
        diff --git a/test/manual-test-examples/type/text-to-points.html b/test/manual-test-examples/type/text-to-points.html
        new file mode 100644
        index 0000000000..233d966d24
        --- /dev/null
        +++ b/test/manual-test-examples/type/text-to-points.html
        @@ -0,0 +1,67 @@
        +<html>
        +
        +<!-- 
        +CSS-style font properties (preferred, but doesn't support custom axes):
        +  font-weight: 650;
        +  font-style: oblique 80deg;  (must add deg)
        +  font-stretch: 75%;          (only accepts percentage with %)  
        +  font-optical-sizing: auto;  (only allows for 'auto' or 'none')
        +Font-variation style properties:
        +  font-variations-settings: 'wght' 650, 'slnt' 80, 'wdth' 75, 'opsz' 100;
        +-->
        +
        +<head>
        +  <meta charset='UTF-8'>
        +  <!-- <script language="javascript" type="text/javascript" src="./lib/Typr.js"></script>
        +  <script language="javascript" type="text/javascript" src="./lib/Typr.U.js"></script> -->
        +
        +  <style>
        +    body {
        +      padding: 0;
        +      margin: 0;
        +    }
        +
        +    canvas {
        +      border: 1px solid #f0f0f0;
        +      display: block;
        +    }
        +
        +    img {
        +      border: 1px solid #fff;
        +    }
        +
        +    div {
        +      margin: 100px 0px;
        +    }
        +  </style>
        +</head>
        +
        +
        +
        +<body>
        +  <script type='module'>
        +    import p5 from '../../../src/app.js';
        +    let sketch = async function (p) {
        +      let f;
        +      p.setup = async function () {
        +        p.createCanvas(750, 350);
        +        p.background(255);
        +        p.textFont(f = await p.loadFont('./font/LiberationSans-Bold.ttf'), 300);
        +        p.fill('#E92D55');
        +
        +        let paths = f.textToPaths('p5*js', 5, 250);
        +        f.drawPaths(p.drawingContext, paths, { fill: '#E92D55' });
        +        f.textToPoints('p5*js', 5, 250, { sampleFactor: .1 }).forEach((pt, i) => {
        +          p.fill(0);
        +          p.circle(pt.x, pt.y, 5);
        +        });
        +      }
        +    }
        +
        +    new p5(sketch);
        +  </script>
        +
        +</body>
        +
        +
        +</html>
        \ No newline at end of file
        diff --git a/test/manual-test-examples/type/variable-font-local.html b/test/manual-test-examples/type/variable-font-local.html
        new file mode 100644
        index 0000000000..f652fe2e8f
        --- /dev/null
        +++ b/test/manual-test-examples/type/variable-font-local.html
        @@ -0,0 +1,69 @@
        +<html>
        +
        +<!-- NEXT: get axes/values from font file -->
        +
        +<head>
        +  <meta charset="UTF-8">
        +
        +  <style>
        +    body {
        +      padding: 0;
        +      margin: 0;
        +    }
        +
        +    canvas {
        +      border: 1px solid #f0f0f0;
        +      display: block;
        +    }
        +
        +    img {
        +      border: 1px solid #fff;
        +    }
        +
        +    div {
        +      margin: 100px 0px;
        +    }
        +  </style>
        +</head>
        +
        +<body>
        +  <script type="module">
        +    import p5 from '../../../src/app.js';
        +
        +    let sketch = function (p) {
        +      let font, path = './font/BricolageGrotesque-Variable.ttf';
        +      
        +      p.setup = async function () {
        +        p.createCanvas(800, 200);
        +        font = await p.loadFont(path);
        +        console.log(font.data);
        +        
        +        p.textAlign(p.CENTER, p.CENTER);
        +        p.textSize(60);
        +      }
        +
        +      p.draw = function () {
        +        p.background(255);
        +        //if (p.frameCount > 5) p.noLoop();
        +
        +        let m = p.map(p.cos(p.frameCount / 20), -1, 1, 200, 800);// m = 100;
        +        let n = p.map(p.sin(p.frameCount / 30), -1, 1, 75, 100); // n = 400;
        +        let o = p.map(p.sin(p.frameCount / 20), -1, 1, 12, 48); // o = 48; 
        +
        +        p.textFont(font, 72, {
        +          fontVariationSettings: `"wght" ${m}, "wdth" ${n}, "opsz" ${o}`
        +        });
        +        p.text(`This is a variable font.`, 400, 100);
        +
        +        let bb = p.textBounds(`This is a variable font.`, 400, 100);
        +        p.noFill().stroke(0).rect(bb.x, bb.y, bb.w, bb.h);
        +      };
        +    };
        +
        +    new p5(sketch);
        +  </script>
        +
        +</body>
        +
        +
        +</html>
        \ No newline at end of file
        diff --git a/test/manual-test-examples/type/variable-font.html b/test/manual-test-examples/type/variable-font.html
        new file mode 100644
        index 0000000000..3465f07584
        --- /dev/null
        +++ b/test/manual-test-examples/type/variable-font.html
        @@ -0,0 +1,102 @@
        +<html>
        +
        +<!-- 
        +CSS-style font properties (preferred, but doesn't support custom axes):
        +  font-weight: 650;
        +  font-style: oblique 80deg;  (must add deg)
        +  font-stretch: 75%;          (only accepts percentage with %)  
        +  font-optical-sizing: auto;  (only allows for 'auto' or 'none')
        +
        +Font-variation style properties:
        +  font-variations-settings: 'wght' 650, 'slnt' 80, 'wdth' 75, 'opsz' 100;
        +-->
        +
        +<head>
        +  <meta charset='UTF-8'>
        +  <style>
        +    body {
        +      padding: 0;
        +      margin: 0;
        +    }
        +
        +    canvas {
        +      border: 1px solid #f0f0f0;
        +      display: block;
        +      font-stretch: 50%;
        +    }
        +
        +    img {
        +      border: 1px solid #fff;
        +    }
        +
        +    div {
        +      margin: 100px 0px;
        +    }
        +  </style>
        +  <!-- link fontfaceobserver.js in the head -->
        +  <script src='./fontfaceobserver.standalone.js'></script>
        +</head>
        +
        +
        +
        +<body>
        +  <script type='module'>
        +    import p5 from '../../../src/app.js';
        +
        +    let sketch = function (p) {
        +      let fonts, fontUrl = 'https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wdth,wght@75..100,200..800&display=swap';
        +      p.setup = async function () {
        +        p.createCanvas(800, 200);
        +        fonts = await loadGoogleFontSet(p, 'Bricolage Grotesque', fontUrl);
        +        p.textAlign(p.CENTER, p.CENTER);
        +        p.textSize(60);
        +      }
        +
        +      p.draw = function () {
        +        p.background(255);
        +
        +        //if (p.frameCount > 5) p.noLoop();
        +
        +        let m = p.map(p.cos(p.frameCount / 20), -1, 1, 200, 800);// m = 100;
        +        let n = p.map(p.sin(p.frameCount / 30), -1, 1, 75, 100);// n = 400;
        +        let o = p.map(p.sin(p.frameCount / 20), -1, 1, 12, 96);// o = 48; 
        +
        +        p.textFont('Bricolage Grotesque', 72, {
        +          fontVariationSettings: `'wght' ${m}, 'wdth' ${n}, 'opsz' ${o}`
        +        });
        +        p.text(`This is a variable font.`, 400, 100);
        +        
        +        let bb = p.textBounds(`This is a variable font.`, 400, 100);
        +        p.noFill().stroke(0).rect(bb.x, bb.y, bb.w, bb.h);
        +      };
        +
        +      p5.Font.variationsRaw = function () {
        +        return fonts[0].fontFace.tables.fvar.instances.map(i => i.coordinates);
        +      }
        +
        +      async function loadGoogleFontSet(p, name, url) {
        +        injectFontLink(fontUrl);
        +        const obs = new FontFaceObserver(name);
        +        await obs.load();
        +        return Array.from(document.fonts).map(f => {
        +          if (name !== f.family) throw Error('Font-name mismatch');
        +          return new p5.Font(p, f, name, fontUrl)
        +        });
        +      }
        +
        +      function injectFontLink(href) {
        +        const link = document.createElement('link');
        +        link.id = 'font';
        +        link.href = href;
        +        link.rel = 'stylesheet';
        +        document.head.appendChild(link);
        +      }
        +    }
        +
        +    new p5(sketch);
        +  </script>
        +
        +</body>
        +
        +
        +</html>
        \ No newline at end of file
        diff --git a/test/manual-test-examples/type/vite.config.mjs b/test/manual-test-examples/type/vite.config.mjs
        new file mode 100644
        index 0000000000..301192ed7f
        --- /dev/null
        +++ b/test/manual-test-examples/type/vite.config.mjs
        @@ -0,0 +1,13 @@
        +import { defineConfig } from 'vitest/config';
        +import vitePluginString from 'vite-plugin-string';
        +
        +export default defineConfig({
        +  root: './',
        +  plugins: [
        +    vitePluginString({
        +      include: [
        +        'src/webgl/shaders/**/*'
        +      ]
        +    })
        +  ]
        +});
        diff --git a/test/manual-test-examples/webgl/curves/sketch.js b/test/manual-test-examples/webgl/curves/sketch.js
        index 435ad59dd3..bdde1699e0 100644
        --- a/test/manual-test-examples/webgl/curves/sketch.js
        +++ b/test/manual-test-examples/webgl/curves/sketch.js
        @@ -39,16 +39,16 @@ function draw() {
           fill(0, 77, 64);
         
           beginShape();
        -  curveVertex(10, 150, -4);
        -  curveVertex(10, 150, -4);
        -  curveVertex(60, 80, -4);
        -  curveVertex(140, 100, -4);
        -  curveVertex(200, 100, -4);
        -  curveVertex(200, 110, -4);
        -  curveVertex(160, 140, -4);
        -  curveVertex(80, 160, -4);
        -  curveVertex(10, 150, -4);
        -  curveVertex(10, 150, -4);
        +  splineVertex(10, 150, -4);
        +  splineVertex(10, 150, -4);
        +  splineVertex(60, 80, -4);
        +  splineVertex(140, 100, -4);
        +  splineVertex(200, 100, -4);
        +  splineVertex(200, 110, -4);
        +  splineVertex(160, 140, -4);
        +  splineVertex(80, 160, -4);
        +  splineVertex(10, 150, -4);
        +  splineVertex(10, 150, -4);
           endShape();
         
           angle += 0.01;
        diff --git a/test/manual-test-examples/webgl/geometryImmediate/sketch.js b/test/manual-test-examples/webgl/geometryImmediate/sketch.js
        index 27ce74a562..8ec44bc3fd 100644
        --- a/test/manual-test-examples/webgl/geometryImmediate/sketch.js
        +++ b/test/manual-test-examples/webgl/geometryImmediate/sketch.js
        @@ -82,7 +82,7 @@ function drawStrip(mode) {
         }
         
         function ngon(n, x, y, d) {
        -  beginShape(TESS);
        +  beginShape(PATH);
           for (let i = 0; i < n + 1; i++) {
             angle = TWO_PI / n * i;
             px = x + sin(angle) * d / 2;
        diff --git a/test/mocha.css b/test/mocha.css
        deleted file mode 100644
        index 42b9798fa4..0000000000
        --- a/test/mocha.css
        +++ /dev/null
        @@ -1,270 +0,0 @@
        -@charset "utf-8";
        -
        -body {
        -  margin:0;
        -}
        -
        -#mocha {
        -  font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
        -  margin: 60px 50px;
        -}
        -
        -#mocha ul,
        -#mocha li {
        -  margin: 0;
        -  padding: 0;
        -}
        -
        -#mocha ul {
        -  list-style: none;
        -}
        -
        -#mocha h1,
        -#mocha h2 {
        -  margin: 0;
        -}
        -
        -#mocha h1 {
        -  margin-top: 15px;
        -  font-size: 1em;
        -  font-weight: 200;
        -}
        -
        -#mocha h1 a {
        -  text-decoration: none;
        -  color: inherit;
        -}
        -
        -#mocha h1 a:hover {
        -  text-decoration: underline;
        -}
        -
        -#mocha .suite .suite h1 {
        -  margin-top: 0;
        -  font-size: .8em;
        -}
        -
        -#mocha .hidden {
        -  display: none;
        -}
        -
        -#mocha h2 {
        -  font-size: 12px;
        -  font-weight: normal;
        -  cursor: pointer;
        -}
        -
        -#mocha .suite {
        -  margin-left: 15px;
        -}
        -
        -#mocha .test {
        -  margin-left: 15px;
        -  overflow: hidden;
        -}
        -
        -#mocha .test.pending:hover h2::after {
        -  content: '(pending)';
        -  font-family: arial, sans-serif;
        -}
        -
        -#mocha .test.pass.medium .duration {
        -  background: #c09853;
        -}
        -
        -#mocha .test.pass.slow .duration {
        -  background: #b94a48;
        -}
        -
        -#mocha .test.pass::before {
        -  content: '✓';
        -  font-size: 12px;
        -  display: block;
        -  float: left;
        -  margin-right: 5px;
        -  color: #00d6b2;
        -}
        -
        -#mocha .test.pass .duration {
        -  font-size: 9px;
        -  margin-left: 5px;
        -  padding: 2px 5px;
        -  color: #fff;
        -  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
        -  -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
        -  box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
        -  -webkit-border-radius: 5px;
        -  -moz-border-radius: 5px;
        -  -ms-border-radius: 5px;
        -  -o-border-radius: 5px;
        -  border-radius: 5px;
        -}
        -
        -#mocha .test.pass.fast .duration {
        -  display: none;
        -}
        -
        -#mocha .test.pending {
        -  color: #0b97c4;
        -}
        -
        -#mocha .test.pending::before {
        -  content: '◦';
        -  color: #0b97c4;
        -}
        -
        -#mocha .test.fail {
        -  color: #c00;
        -}
        -
        -#mocha .test.fail pre {
        -  color: black;
        -}
        -
        -#mocha .test.fail::before {
        -  content: '✖';
        -  font-size: 12px;
        -  display: block;
        -  float: left;
        -  margin-right: 5px;
        -  color: #c00;
        -}
        -
        -#mocha .test pre.error {
        -  color: #c00;
        -  max-height: 300px;
        -  overflow: auto;
        -}
        -
        -/**
        - * (1): approximate for browsers not supporting calc
        - * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
        - *      ^^ seriously
        - */
        -#mocha .test pre {
        -  display: block;
        -  float: left;
        -  clear: left;
        -  font: 12px/1.5 monaco, monospace;
        -  margin: 5px;
        -  padding: 15px;
        -  border: 1px solid #eee;
        -  max-width: 85%; /*(1)*/
        -  max-width: calc(100% - 42px); /*(2)*/
        -  word-wrap: break-word;
        -  border-bottom-color: #ddd;
        -  -webkit-border-radius: 3px;
        -  -webkit-box-shadow: 0 1px 3px #eee;
        -  -moz-border-radius: 3px;
        -  -moz-box-shadow: 0 1px 3px #eee;
        -  border-radius: 3px;
        -}
        -
        -#mocha .test h2 {
        -  position: relative;
        -}
        -
        -#mocha .test a.replay {
        -  position: absolute;
        -  top: 3px;
        -  right: 0;
        -  text-decoration: none;
        -  vertical-align: middle;
        -  display: block;
        -  width: 15px;
        -  height: 15px;
        -  line-height: 15px;
        -  text-align: center;
        -  background: #eee;
        -  font-size: 15px;
        -  -moz-border-radius: 15px;
        -  border-radius: 15px;
        -  -webkit-transition: opacity 200ms;
        -  -moz-transition: opacity 200ms;
        -  transition: opacity 200ms;
        -  opacity: 0.3;
        -  color: #888;
        -}
        -
        -#mocha .test:hover a.replay {
        -  opacity: 1;
        -}
        -
        -#mocha-report.pass .test.fail {
        -  display: none;
        -}
        -
        -#mocha-report.fail .test.pass {
        -  display: none;
        -}
        -
        -#mocha-report.pending .test.pass,
        -#mocha-report.pending .test.fail {
        -  display: none;
        -}
        -#mocha-report.pending .test.pass.pending {
        -  display: block;
        -}
        -
        -#mocha-error {
        -  color: #c00;
        -  font-size: 1.5em;
        -  font-weight: 100;
        -  letter-spacing: 1px;
        -}
        -
        -#mocha-stats {
        -  position: fixed;
        -  top: 15px;
        -  right: 10px;
        -  font-size: 12px;
        -  margin: 0;
        -  color: #888;
        -  z-index: 1;
        -}
        -
        -#mocha-stats .progress {
        -  float: right;
        -  padding-top: 0;
        -}
        -
        -#mocha-stats em {
        -  color: black;
        -}
        -
        -#mocha-stats a {
        -  text-decoration: none;
        -  color: inherit;
        -}
        -
        -#mocha-stats a:hover {
        -  border-bottom: 1px solid #eee;
        -}
        -
        -#mocha-stats li {
        -  display: inline-block;
        -  margin: 0 5px;
        -  list-style: none;
        -  padding-top: 11px;
        -}
        -
        -#mocha-stats canvas {
        -  width: 40px;
        -  height: 40px;
        -}
        -
        -#mocha code .comment { color: #ddd; }
        -#mocha code .init { color: #2f6fad; }
        -#mocha code .string { color: #5890ad; }
        -#mocha code .keyword { color: #8a6343; }
        -#mocha code .number { color: #2f6fad; }
        -
        -@media screen and (max-device-width: 480px) {
        -  #mocha {
        -    margin: 60px 0px;
        -  }
        -
        -  #mocha #stats {
        -    position: absolute;
        -  }
        -}
        diff --git a/test/mockServiceWorker.js b/test/mockServiceWorker.js
        new file mode 100644
        index 0000000000..ec47a9a50a
        --- /dev/null
        +++ b/test/mockServiceWorker.js
        @@ -0,0 +1,307 @@
        +/* eslint-disable */
        +/* tslint:disable */
        +
        +/**
        + * Mock Service Worker.
        + * @see https://github.com/mswjs/msw
        + * - Please do NOT modify this file.
        + * - Please do NOT serve this file on production.
        + */
        +
        +const PACKAGE_VERSION = '2.7.0'
        +const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
        +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
        +const activeClientIds = new Set()
        +
        +self.addEventListener('install', function () {
        +  self.skipWaiting()
        +})
        +
        +self.addEventListener('activate', function (event) {
        +  event.waitUntil(self.clients.claim())
        +})
        +
        +self.addEventListener('message', async function (event) {
        +  const clientId = event.source.id
        +
        +  if (!clientId || !self.clients) {
        +    return
        +  }
        +
        +  const client = await self.clients.get(clientId)
        +
        +  if (!client) {
        +    return
        +  }
        +
        +  const allClients = await self.clients.matchAll({
        +    type: 'window',
        +  })
        +
        +  switch (event.data) {
        +    case 'KEEPALIVE_REQUEST': {
        +      sendToClient(client, {
        +        type: 'KEEPALIVE_RESPONSE',
        +      })
        +      break
        +    }
        +
        +    case 'INTEGRITY_CHECK_REQUEST': {
        +      sendToClient(client, {
        +        type: 'INTEGRITY_CHECK_RESPONSE',
        +        payload: {
        +          packageVersion: PACKAGE_VERSION,
        +          checksum: INTEGRITY_CHECKSUM,
        +        },
        +      })
        +      break
        +    }
        +
        +    case 'MOCK_ACTIVATE': {
        +      activeClientIds.add(clientId)
        +
        +      sendToClient(client, {
        +        type: 'MOCKING_ENABLED',
        +        payload: {
        +          client: {
        +            id: client.id,
        +            frameType: client.frameType,
        +          },
        +        },
        +      })
        +      break
        +    }
        +
        +    case 'MOCK_DEACTIVATE': {
        +      activeClientIds.delete(clientId)
        +      break
        +    }
        +
        +    case 'CLIENT_CLOSED': {
        +      activeClientIds.delete(clientId)
        +
        +      const remainingClients = allClients.filter((client) => {
        +        return client.id !== clientId
        +      })
        +
        +      // Unregister itself when there are no more clients
        +      if (remainingClients.length === 0) {
        +        self.registration.unregister()
        +      }
        +
        +      break
        +    }
        +  }
        +})
        +
        +self.addEventListener('fetch', function (event) {
        +  const { request } = event
        +
        +  // Bypass navigation requests.
        +  if (request.mode === 'navigate') {
        +    return
        +  }
        +
        +  // Opening the DevTools triggers the "only-if-cached" request
        +  // that cannot be handled by the worker. Bypass such requests.
        +  if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
        +    return
        +  }
        +
        +  // Bypass all requests when there are no active clients.
        +  // Prevents the self-unregistered worked from handling requests
        +  // after it's been deleted (still remains active until the next reload).
        +  if (activeClientIds.size === 0) {
        +    return
        +  }
        +
        +  // Generate unique request ID.
        +  const requestId = crypto.randomUUID()
        +  event.respondWith(handleRequest(event, requestId))
        +})
        +
        +async function handleRequest(event, requestId) {
        +  const client = await resolveMainClient(event)
        +  const response = await getResponse(event, client, requestId)
        +
        +  // Send back the response clone for the "response:*" life-cycle events.
        +  // Ensure MSW is active and ready to handle the message, otherwise
        +  // this message will pend indefinitely.
        +  if (client && activeClientIds.has(client.id)) {
        +    ;(async function () {
        +      const responseClone = response.clone()
        +
        +      sendToClient(
        +        client,
        +        {
        +          type: 'RESPONSE',
        +          payload: {
        +            requestId,
        +            isMockedResponse: IS_MOCKED_RESPONSE in response,
        +            type: responseClone.type,
        +            status: responseClone.status,
        +            statusText: responseClone.statusText,
        +            body: responseClone.body,
        +            headers: Object.fromEntries(responseClone.headers.entries()),
        +          },
        +        },
        +        [responseClone.body],
        +      )
        +    })()
        +  }
        +
        +  return response
        +}
        +
        +// Resolve the main client for the given event.
        +// Client that issues a request doesn't necessarily equal the client
        +// that registered the worker. It's with the latter the worker should
        +// communicate with during the response resolving phase.
        +async function resolveMainClient(event) {
        +  const client = await self.clients.get(event.clientId)
        +
        +  if (activeClientIds.has(event.clientId)) {
        +    return client
        +  }
        +
        +  if (client?.frameType === 'top-level') {
        +    return client
        +  }
        +
        +  const allClients = await self.clients.matchAll({
        +    type: 'window',
        +  })
        +
        +  return allClients
        +    .filter((client) => {
        +      // Get only those clients that are currently visible.
        +      return client.visibilityState === 'visible'
        +    })
        +    .find((client) => {
        +      // Find the client ID that's recorded in the
        +      // set of clients that have registered the worker.
        +      return activeClientIds.has(client.id)
        +    })
        +}
        +
        +async function getResponse(event, client, requestId) {
        +  const { request } = event
        +
        +  // Clone the request because it might've been already used
        +  // (i.e. its body has been read and sent to the client).
        +  const requestClone = request.clone()
        +
        +  function passthrough() {
        +    // Cast the request headers to a new Headers instance
        +    // so the headers can be manipulated with.
        +    const headers = new Headers(requestClone.headers)
        +
        +    // Remove the "accept" header value that marked this request as passthrough.
        +    // This prevents request alteration and also keeps it compliant with the
        +    // user-defined CORS policies.
        +    const acceptHeader = headers.get('accept')
        +    if (acceptHeader) {
        +      const values = acceptHeader.split(',').map((value) => value.trim())
        +      const filteredValues = values.filter(
        +        (value) => value !== 'msw/passthrough',
        +      )
        +
        +      if (filteredValues.length > 0) {
        +        headers.set('accept', filteredValues.join(', '))
        +      } else {
        +        headers.delete('accept')
        +      }
        +    }
        +
        +    return fetch(requestClone, { headers })
        +  }
        +
        +  // Bypass mocking when the client is not active.
        +  if (!client) {
        +    return passthrough()
        +  }
        +
        +  // Bypass initial page load requests (i.e. static assets).
        +  // The absence of the immediate/parent client in the map of the active clients
        +  // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
        +  // and is not ready to handle requests.
        +  if (!activeClientIds.has(client.id)) {
        +    return passthrough()
        +  }
        +
        +  // Notify the client that a request has been intercepted.
        +  const requestBuffer = await request.arrayBuffer()
        +  const clientMessage = await sendToClient(
        +    client,
        +    {
        +      type: 'REQUEST',
        +      payload: {
        +        id: requestId,
        +        url: request.url,
        +        mode: request.mode,
        +        method: request.method,
        +        headers: Object.fromEntries(request.headers.entries()),
        +        cache: request.cache,
        +        credentials: request.credentials,
        +        destination: request.destination,
        +        integrity: request.integrity,
        +        redirect: request.redirect,
        +        referrer: request.referrer,
        +        referrerPolicy: request.referrerPolicy,
        +        body: requestBuffer,
        +        keepalive: request.keepalive,
        +      },
        +    },
        +    [requestBuffer],
        +  )
        +
        +  switch (clientMessage.type) {
        +    case 'MOCK_RESPONSE': {
        +      return respondWithMock(clientMessage.data)
        +    }
        +
        +    case 'PASSTHROUGH': {
        +      return passthrough()
        +    }
        +  }
        +
        +  return passthrough()
        +}
        +
        +function sendToClient(client, message, transferrables = []) {
        +  return new Promise((resolve, reject) => {
        +    const channel = new MessageChannel()
        +
        +    channel.port1.onmessage = (event) => {
        +      if (event.data && event.data.error) {
        +        return reject(event.data.error)
        +      }
        +
        +      resolve(event.data)
        +    }
        +
        +    client.postMessage(
        +      message,
        +      [channel.port2].concat(transferrables.filter(Boolean)),
        +    )
        +  })
        +}
        +
        +async function respondWithMock(response) {
        +  // Setting response status code to 0 is a no-op.
        +  // However, when responding with a "Response.error()", the produced Response
        +  // instance will have status code set to 0. Since it's not possible to create
        +  // a Response instance with status code 0, handle that use-case separately.
        +  if (response.status === 0) {
        +    return Response.error()
        +  }
        +
        +  const mockedResponse = new Response(response.body, response)
        +
        +  Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
        +    value: true,
        +    enumerable: true,
        +  })
        +
        +  return mockedResponse
        +}
        diff --git a/test/reporter/simple.js b/test/reporter/simple.js
        deleted file mode 100644
        index 19ddebf642..0000000000
        --- a/test/reporter/simple.js
        +++ /dev/null
        @@ -1,52 +0,0 @@
        -module.exports = function(runner) {
        -  var failures = (runner.failures = []);
        -
        -  var stats = (runner.stats = {
        -    suites: 0,
        -    tests: 0,
        -    passes: 0,
        -    pending: 0,
        -    failures: 0,
        -    duration: 0,
        -    start: 0,
        -    end: 0
        -  });
        -
        -  runner.on('start', function() {
        -    console.log('-> start');
        -    stats.start = new Date().getTime();
        -  });
        -
        -  runner.on('test', function(test) {
        -    stats.tests++;
        -  });
        -
        -  runner.on('suite', function(suite) {
        -    stats.suites++;
        -  });
        -
        -  runner.on('suite end', function(suite) {});
        -
        -  runner.on('pending', function(test) {
        -    stats.pending++;
        -    console.log('?  pending: %s', test.fullTitle());
        -  });
        -
        -  runner.on('pass', function(test) {
        -    stats.passes++;
        -    console.log('-> pass: %s', test.fullTitle());
        -  });
        -
        -  runner.on('fail', function(test, err) {
        -    stats.failures++;
        -    test.err = err;
        -    failures.push(test);
        -    console.log('!! fail: %s -- error: %s', test.fullTitle(), err.message);
        -  });
        -
        -  runner.on('end', function() {
        -    stats.end = new Date().getTime();
        -    stats.duration = stats.end - stats.start;
        -    console.log('-> end: %d/%d', stats.passes, stats.passes + stats.failures);
        -  });
        -};
        diff --git a/test/test-minified.html b/test/test-minified.html
        deleted file mode 100644
        index c60829b8d3..0000000000
        --- a/test/test-minified.html
        +++ /dev/null
        @@ -1,41 +0,0 @@
        -<!DOCTYPE html>
        -<html>
        -<head>
        -  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
        -  <link rel="stylesheet" href="mocha.css"/>
        -  <title>Example Mocha Test</title>
        -</head>
        -<body>
        -  <!-- Required for browser reporter -->
        -  <div id="mocha"></div>
        -
        -  <!-- mocha -->
        -  <script src="../node_modules/mocha/mocha.js" type="text/javascript" charset="utf-8"></script>
        -<script src="js/mocha_setup.js" type="text/javascript"></script>
        -
        -  <!-- Include your assertion lib of choice -->
        -  <script src="../node_modules/chai/chai.js" type="text/javascript" ></script>
        -  <script src="js/sinon.js" type="text/javascript" ></script>
        -  <script src="js/modernizr.js" type="text/javascript" ></script>
        -  <script src="js/chai_helpers.js" type="text/javascript" ></script>
        -  <script src="js/p5_helpers.js" type="text/javascript" ></script>
        -
        -  <!-- Include anything you want to test -->
        -  <script src="../lib/p5.min.js" type="text/javascript" ></script>
        -
        -  <script type="text/javascript">
        -    // Let unit tests know we're testing the minified version.
        -    window.IS_TESTING_MINIFIED_VERSION = true;
        -  </script>
        -
        -  <!-- Spec files (Now centralised in unit/spec.js) -->
        -  <script src="unit/spec.js" type="text/javascript" ></script>
        -
        -  <!-- run mocha -->
        -  <script type="text/javascript" >
        -    window.addEventListener('load', function() {
        -      mocha.run();
        -    }, false);
        -  </script>
        -</body>
        -</html>
        diff --git a/test/test-reference.html b/test/test-reference.html
        deleted file mode 100644
        index 66d0ae9a90..0000000000
        --- a/test/test-reference.html
        +++ /dev/null
        @@ -1,232 +0,0 @@
        -<!DOCTYPE html>
        -<html>
        -<head>
        -  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
        -  <link rel="stylesheet" href="mocha.css"/>
        -  <style> iframe { visibility: hidden; } </style>
        -  <title>p5 Reference Documentation Test</title>
        -</head>
        -<body>
        -  <!-- Required for browser reporter -->
        -  <div id="mocha"></div>
        -
        -  <!-- mocha -->
        -  <script src="../node_modules/mocha/mocha.js" type="text/javascript" charset="utf-8"></script>
        -  <script src="js/mocha_setup.js" type="text/javascript"></script>
        -
        -  <!-- Include your assertion lib of choice -->
        -  <script src="../node_modules/chai/chai.js" type="text/javascript" ></script>
        -  <script src="js/sinon.js" type="text/javascript" ></script>
        -  <script src="js/modernizr.js"></script>
        -  <script src="js/chai_helpers.js" type="text/javascript" ></script>
        -
        -  <!-- Include anything you want to test -->
        -  <script src="../lib/p5.js" type="text/javascript" ></script>
        -  <script>
        -    // These tests fail. TODO: Work out why.
        -    if (Modernizr.webaudio)
        -      document.write(
        -        '<script src="../lib/addons/p5.sound.js"><\/script>'
        -      );
        -  </script>
        -
        -  <script>
        -  p5._throwValidationErrors = true;
        -
        -  var TEST_FILENAME_FILTERS = [
        -    {regex: /webgl/, condition: Modernizr.webgl},
        -    {regex: /acceleration/, condition: Modernizr.webgl},
        -    {regex: /sound/, condition: Modernizr.webaudio}
        -  ];
        -  var MS_TIMEOUT = 4000;
        -  var fxns = [
        -    'setup', 'draw', 'preload', 'mousePressed', 'mouseReleased',
        -    'mouseMoved', 'mouseDragged', 'mouseClicked', 'mouseWheel',
        -    'touchStarted', 'touchMoved', 'touchEnded',
        -    'keyPressed', 'keyReleased', 'keyTyped'
        -  ];
        -
        -  // TODO: This code has basically been harvested from
        -  // https://github.com/processing/p5.js-website/blob/main/js/render.js.
        -  // Ideally we should factor out this common code into a shared place
        -  // where we're not duplicating code.
        -  function createExampleSketch(exampleCode, cb) {
        -    var runnable = exampleCode.replace(/^\s+|\s+$/g, '');
        -
        -    return function( p ) {
        -      if (runnable.indexOf('setup()') === -1 &&
        -          runnable.indexOf('draw()') === -1){
        -        p.setup = function() {
        -          p.createCanvas(100, 100);
        -          p.background(200);
        -          with (p) {
        -            eval(runnable);
        -          }
        -        }
        -      } else {
        -        with (p) {
        -          eval(runnable);
        -        }
        -
        -        fxns.forEach(function(f) {
        -          var ind = runnable.indexOf(f+'(');
        -          // this is a gross hack within a hacky script that
        -          // ensures the function names found are not substrings
        -          // proper use of regex would be preferable...
        -          if (ind !== -1 && runnable[ind+f.length] === '(' &&
        -              eval('typeof ' + f) !== 'undefined') {
        -            with (p) {
        -              p[f] = eval(f);
        -            }
        -          }
        -        });
        -        if (typeof p.setup === 'undefined') {
        -          p.setup = function() {
        -            p.createCanvas(100, 100);
        -            p.background(200);
        -          }
        -        }
        -      }
        -
        -      if (cb) {
        -        cb(p);
        -      }
        -    };
        -  }
        -
        -  function defineTest(info) {
        -    suite(info.name + " documentation", function() {
        -      var myp5, myp5Div;
        -      var div = document.createElement('div');
        -      var examples;
        -
        -      setup(function() {
        -        myp5 = null;
        -        myp5Div = null;
        -      });
        -
        -      teardown(function() {
        -        if (myp5) {
        -          myp5.remove();
        -          myp5 = null;
        -        }
        -        if (myp5Div && myp5Div.parentNode) {
        -          myp5Div.parentNode.removeChild(myp5Div);
        -          myp5Div = null;
        -        }
        -      });
        -
        -      div.style.display = 'none';
        -      div.innerHTML = info.example.join('');
        -      document.body.appendChild(div);
        -      examples = [].slice.call(div.querySelectorAll('div:not(.notest)'));
        -      document.body.removeChild(div);
        -
        -      examples.forEach(function(el, i) {
        -
        -        var ms = el.attributes['modernizr'];
        -        if (ms && ms.value.split(',').some(
        -          function (m) { return !Modernizr[m]; })) {
        -          return;
        -        }
        -
        -        var exampleCode = el.textContent
        -          .replace(/assets\//g, '../docs/reference/assets/');
        -        var startTime = null;
        -
        -        var testFunc = function() {
        -          var test_context = this;
        -          return new Promise(function(resolve, reject) {
        -            startTime = Date.now();
        -
        -            test_context.timeout(MS_TIMEOUT);
        -            myp5Div = document.createElement('div');
        -            document.body.appendChild(myp5Div);
        -
        -            myp5 = new p5(createExampleSketch(exampleCode, function(p) {
        -              var drawCalled = false;
        -              fxns.forEach(function(f) {
        -                var old = p[f];
        -                if(old) {
        -                  p[f] = function() {
        -                    try {
        -                      old.apply(this, arguments);
        -                    } catch(err) {
        -                      reject(err);
        -                    }
        -                  }
        -                }
        -              });
        -              var oldDraw = p.draw || function() {};
        -
        -              p.draw = function() {
        -                try {
        -                  if (drawCalled) return;
        -                  oldDraw.call(p);
        -                  drawCalled = true;
        -
        -                  if (Date.now() - startTime > MS_TIMEOUT) {
        -                    // This can happen if the page contained processor-intensive
        -                    // code that blocked the UI for too long.
        -                    reject(new Error("code took too long to execute"));
        -                    return;
        -                  }
        -
        -                  resolve();
        -                } catch(err) {
        -                  reject(err);
        -                }
        -              };
        -            }, myp5Div));
        -          });
        -        };
        -
        -
        -        testFunc.toString = function() {
        -          // When a user clicks on a specific test, mocha shows its
        -          // source code; in our case, we want the source of the example
        -          // we're running to be shown, *not* the example runner code.
        -          return exampleCode;
        -        };
        -
        -        test("example #" + (i + 1) + " works", testFunc);
        -      });
        -    });
        -  }
        -
        -  onload = function() {
        -    var req = new XMLHttpRequest();
        -    req.open('GET', '../docs/reference/data.json');
        -    req.onload = function() {
        -      var data = JSON.parse(req.responseText);
        -      var files = {};
        -
        -      data.classitems
        -        .concat(Object.keys(data.classes).map(function(name) {
        -          return data.classes[name];
        -        })).filter(function(info) {
        -          return info.name && info.example && info.example.length;
        -        }).forEach(function(info) {
        -          if (!files[info.file])
        -            files[info.file] = [];
        -          files[info.file].push(info);
        -        });
        -
        -      Object.keys(files)
        -        .filter(function excludeIfNoBrowserSupportFor(filename) {
        -          return !TEST_FILENAME_FILTERS.some(function(filter) {
        -            return filter.regex.test(filename) && !filter.condition;
        -          });
        -        }).forEach(function(filename) {
        -          suite(filename, function() {
        -            files[filename].forEach(defineTest);
        -          });
        -        });
        -
        -      mocha.run();
        -    };
        -    req.send(null);
        -  };
        -  </script>
        -</body>
        -</html>
        diff --git a/test/test.html b/test/test.html
        deleted file mode 100644
        index 6ba4df33a1..0000000000
        --- a/test/test.html
        +++ /dev/null
        @@ -1,68 +0,0 @@
        -<!DOCTYPE html>
        -<html>
        -<head>
        -  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
        -  <link rel="stylesheet" href="mocha.css"/>
        -</head>
        -<body>
        -  <!-- Required for browser reporter -->
        -  <div id="mocha"></div>
        -
        -  <!-- mocha -->
        -  <script src="../node_modules/mocha/mocha.js" type="text/javascript" charset="utf-8"></script>
        -<script src="js/mocha_setup.js" type="text/javascript"></script>
        -
        -  <!-- Include your assertion lib of choice -->
        -  <script src="../node_modules/chai/chai.js" type="text/javascript" ></script>
        -  <script src="js/sinon.js" type="text/javascript" ></script>
        -  <script src="js/modernizr.js" type="text/javascript" ></script>
        -  <script src="js/chai_helpers.js" type="text/javascript" ></script>
        -  <script src="js/p5_helpers.js" type="text/javascript" ></script>
        -
        -    <!-- Include anything you want to test -->
        -    <script src="../lib/p5-test.js" type="text/javascript"></script>
        -
        -  <!-- Spec files (Now centralised in unit/spec.js) -->
        -  <script src="unit/spec.js" type="text/javascript" ></script>
        -
        -  <!-- run mocha -->
        -  <script type="text/javascript" >
        -    p5._throwValidationErrors = true;
        -
        -    window.addEventListener('load', function() {
        -      var runner = mocha.run();
        -
        -      // NOTE: Sauce Labs removed, keeping these for now
        -      // This exposes our test results to Sauce Labs. For more
        -      // details, see: https://github.com/axemclion/grunt-saucelabs.
        -
        -      var failedTests = [];
        -      runner.on('end', function(){
        -        window.mochaResults = runner.stats;
        -        window.mochaResults.reports = failedTests;
        -      });
        -
        -      runner.on('fail', logFailure);
        -
        -      function logFailure(test, err) {
        -        var flattenTitles = function(test){
        -          var titles = [];
        -          while (test.parent.title){
        -            titles.push(test.parent.title);
        -            test = test.parent;
        -          }
        -          return titles.reverse();
        -        };
        -
        -        failedTests.push({
        -          name: test.title,
        -          result: false,
        -          message: err.message,
        -          stack: err.stack,
        -          titles: flattenTitles(test)
        -        });
        -      }
        -    }, false);
        -  </script>
        -</body>
        -</html>
        diff --git a/test/unit/accessibility/describe.js b/test/unit/accessibility/describe.js
        index 41292277f5..31676179ff 100644
        --- a/test/unit/accessibility/describe.js
        +++ b/test/unit/accessibility/describe.js
        @@ -1,81 +1,72 @@
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import describe from '../../../src/accessibility/describe';
        +
         suite('describe', function() {
        -  let myp5;
        -  let myID = 'myCanvasID';
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        let cnv = p.createCanvas(100, 100);
        -        cnv.id(myID);
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +  const myID = 'myCanvasID';
         
        -  teardown(function() {
        -    myp5.remove();
        +  beforeAll(function() {
        +    describe(mockP5, mockP5Prototype);
        +
        +    mockP5Prototype.LABEL = 'label';
        +    mockP5Prototype.FALLBACK = 'fallback';
           });
         
           suite('p5.prototype.describe', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.describe);
        -      assert.typeOf(myp5.describe, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.describe(1, myp5.LABEL);
        -      });
        -    });
        -    test('no params', function() {
        -      assert.validationError(function() {
        -        myp5.describe();
        -      });
        +      assert.ok(mockP5Prototype.describe);
        +      assert.typeOf(mockP5Prototype.describe, 'function');
             });
        +
             test('err when LABEL at param #0', function() {
               assert.throws(
                 function() {
        -          myp5.describe(myp5.LABEL);
        +          mockP5Prototype.describe(mockP5Prototype.LABEL);
                 },
                 Error,
                 'description should not be LABEL or FALLBACK'
               );
             });
        +
             test('should create description as fallback', function() {
        -      myp5.describe('a');
        +      mockP5Prototype.describe('a');
               let actual = document.getElementById(myID + '_fallbackDesc');
               assert.deepEqual(actual.innerHTML, 'a.');
             });
        +
             test('should not add extra period if string ends in "."', function() {
        -      myp5.describe('A.');
        +      mockP5Prototype.describe('A.');
               let actual = document.getElementById(myID + '_fallbackDesc');
               assert.deepEqual(actual.innerHTML, 'A.');
             });
        +
             test('should not add period if string ends in "!" or "?', function() {
        -      myp5.describe('A!');
        +      mockP5Prototype.describe('A!');
               let actual = document.getElementById(myID + '_fallbackDesc');
               if (actual.innerHTML === 'A!') {
        -        myp5.describe('A?');
        +        mockP5Prototype.describe('A?');
         
                 actual = document.getElementById(myID + '_fallbackDesc');
                 assert.deepEqual(actual.innerHTML, 'A?');
               }
             });
        +
             test('should create description when called after describeElement()', function() {
        -      myp5.describeElement('b', 'c');
        -      myp5.describe('a');
        +      mockP5Prototype.describeElement('b', 'c');
        +      mockP5Prototype.describe('a');
               let actual = document.getElementById(myID + '_fallbackDesc');
               assert.deepEqual(actual.innerHTML, 'a.');
             });
        +
             test('should create Label adjacent to canvas', function() {
        -      myp5.describe('a', myp5.LABEL);
        +      mockP5Prototype.describe('a', mockP5Prototype.LABEL);
         
               let actual = document.getElementById(myID + '_labelDesc');
               assert.deepEqual(actual.innerHTML, 'a.');
             });
        +
             test('should create Label adjacent to canvas when label of element already exists', function() {
        -      myp5.describeElement('ba', 'c', myp5.LABEL);
        -      myp5.describe('a', myp5.LABEL);
        +      mockP5Prototype.describeElement('ba', 'c', mockP5Prototype.LABEL);
        +      mockP5Prototype.describe('a', mockP5Prototype.LABEL);
               let actual = document.getElementById(myID + '_labelDesc');
               assert.deepEqual(actual.innerHTML, 'a.');
             });
        @@ -83,79 +74,77 @@ suite('describe', function() {
         
           suite('p5.prototype.describeElement', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.describeElement);
        -      assert.typeOf(myp5.describeElement, 'function');
        -    });
        -    test('wrong param type at #0 and #1', function() {
        -      assert.validationError(function() {
        -        myp5.describeElement(1, 2);
        -      });
        -    });
        -    test('no params', function() {
        -      assert.validationError(function() {
        -        myp5.describeElement();
        -      });
        +      assert.ok(mockP5Prototype.describeElement);
        +      assert.typeOf(mockP5Prototype.describeElement, 'function');
             });
        +
             test('err when LABEL at param #0', function() {
               assert.throws(
                 function() {
        -          myp5.describeElement(myp5.LABEL, 'b');
        +          mockP5Prototype.describeElement(mockP5Prototype.LABEL, 'b');
                 },
                 Error,
                 'element name should not be LABEL or FALLBACK'
               );
             });
        +
             test('err when LABEL at param #1', function() {
               assert.throws(
                 function() {
        -          myp5.describeElement('a', myp5.LABEL);
        +          mockP5Prototype.describeElement('a', mockP5Prototype.LABEL);
                 },
                 Error,
                 'description should not be LABEL or FALLBACK'
               );
             });
        +
             test('should create element description as fallback', function() {
        -      myp5.describeElement('az', 'b');
        +      mockP5Prototype.describeElement('az', 'b');
               let actual = document.getElementById(myID + '_fte_az').innerHTML;
               assert.deepEqual(actual, '<th scope="row">az:</th><td>b.</td>');
             });
        +
             test('should not add extra ":" if element name ends in colon', function() {
        -      myp5.describeElement('ab:', 'b.');
        +      mockP5Prototype.describeElement('ab:', 'b.');
               let actual = document.getElementById(myID + '_fte_ab').innerHTML;
               assert.deepEqual(actual, '<th scope="row">ab:</th><td>b.</td>');
             });
        +
             test('should replace ";", ",", "." for ":" in element name', function() {
               let actual;
        -      myp5.describeElement('ac;', 'b.');
        +      mockP5Prototype.describeElement('ac;', 'b.');
               if (
                 document.getElementById(myID + '_fte_ac').innerHTML ===
                 '<th scope="row">ac:</th><td>b.</td>'
               ) {
        -        myp5.describeElement('ad,', 'b.');
        +        mockP5Prototype.describeElement('ad,', 'b.');
                 if (
                   document.getElementById(myID + '_fte_ad').innerHTML ===
                   '<th scope="row">ad:</th><td>b.</td>'
                 ) {
        -          myp5.describeElement('ae.', 'b.');
        +          mockP5Prototype.describeElement('ae.', 'b.');
                   actual = document.getElementById(myID + '_fte_ae').innerHTML;
                   assert.deepEqual(actual, '<th scope="row">ae:</th><td>b.</td>');
                 }
               }
             });
        +
             test('should create element description when called after describe()', function() {
        -      myp5.describe('c');
        -      myp5.describeElement('af', 'b');
        +      mockP5Prototype.describe('c');
        +      mockP5Prototype.describeElement('af', 'b');
               let actual = document.getElementById(myID + '_fte_af').innerHTML;
               assert.deepEqual(actual, '<th scope="row">af:</th><td>b.</td>');
             });
        +
             test('should create element label adjacent to canvas', function() {
        -      myp5.describeElement('ag', 'b', myp5.LABEL);
        +      mockP5Prototype.describeElement('ag', 'b', mockP5Prototype.LABEL);
               const actual = document.getElementById(myID + '_lte_ag').innerHTML;
               assert.deepEqual(actual, '<th scope="row">ag:</th><td>b.</td>');
             });
        +
             test('should create element label adjacent to canvas when called after describe()', function() {
        -      myp5.describe('c', myp5.LABEL);
        -      myp5.describeElement('ah:', 'b', myp5.LABEL);
        +      mockP5Prototype.describe('c', mockP5Prototype.LABEL);
        +      mockP5Prototype.describeElement('ah:', 'b', mockP5Prototype.LABEL);
               const actual = document.getElementById(myID + '_lte_ah').innerHTML;
               assert.deepEqual(actual, '<th scope="row">ah:</th><td>b.</td>');
             });
        diff --git a/test/unit/accessibility/outputs.js b/test/unit/accessibility/outputs.js
        index 6998d8e718..56d0c8cae0 100644
        --- a/test/unit/accessibility/outputs.js
        +++ b/test/unit/accessibility/outputs.js
        @@ -1,35 +1,26 @@
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import outputs from '../../../src/accessibility/outputs';
        +import textOutput from '../../../src/accessibility/textOutput';
        +
        +// TODO: Is it possible to test this without a runtime?
         suite('outputs', function() {
        -  let myp5;
           let myID = 'myCanvasID';
         
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        let cnv = p.createCanvas(100, 100);
        -        cnv.id(myID);
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        -
        -  teardown(function() {
        -    myp5.remove();
        +  beforeAll(function() {
        +    outputs(mockP5, mockP5Prototype);
        +    textOutput(mockP5, mockP5Prototype);
           });
         
           suite('p5.prototype.textOutput', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.textOutput);
        -      assert.typeOf(myp5.textOutput, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.textOutput(1);
        -      });
        +      assert.ok(mockP5Prototype.textOutput);
        +      assert.typeOf(mockP5Prototype.textOutput, 'function');
             });
        +
             let expected =
               'Your output is a, 100 by 100 pixels, white canvas containing the following shape:';
        -    test('should create output as fallback', function() {
        +
        +    test.todo('should create output as fallback', function() {
               return new Promise(function(resolve, reject) {
                 let actual = '';
                 new p5(function(p) {
        @@ -54,7 +45,8 @@ suite('outputs', function() {
                 });
               });
             });
        -    test('should create output as label', function() {
        +
        +    test.todo('should create output as label', function() {
               return new Promise(function(resolve, reject) {
                 let label = '';
                 let fallback = '';
        @@ -83,7 +75,8 @@ suite('outputs', function() {
                 });
               });
             });
        -    test('should create text output for arc()', function() {
        +
        +    test.todo('should create text output for arc()', function() {
               return new Promise(function(resolve, reject) {
                 expected =
                   '<li><a href="#myCanvasIDtextOutputshape0">red arc</a>, at middle, covering 31% of the canvas.</li>';
        @@ -97,7 +90,7 @@ suite('outputs', function() {
                     p.fill(255, 0, 0);
                     p.arc(50, 50, 80, 80, 0, p.PI + p.QUARTER_PI);
                     if (p.frameCount === 2) {
        -              actual = document.getElementById('myCanvasIDtextOutput_list')
        +              let actual = document.getElementById('myCanvasIDtextOutput_list')
                         .innerHTML;
                       if (actual === expected) {
                         resolve();
        @@ -110,7 +103,8 @@ suite('outputs', function() {
                 });
               });
             });
        -    test('should create text output for ellipse()', function() {
        +
        +    test.todo('should create text output for ellipse()', function() {
               return new Promise(function(resolve, reject) {
                 expected =
                   '<li><a href="#myCanvasIDtextOutputshape0">green circle</a>, at middle, covering 24% of the canvas.</li>';
        @@ -124,7 +118,7 @@ suite('outputs', function() {
                   };
                   p.draw = function() {
                     if (p.frameCount === 1) {
        -              actual = document.getElementById('myCanvasIDtextOutput_list')
        +              let actual = document.getElementById('myCanvasIDtextOutput_list')
                         .innerHTML;
                       if (actual === expected) {
                         resolve();
        @@ -137,7 +131,8 @@ suite('outputs', function() {
                 });
               });
             });
        -    test('should create text output for triangle()', function() {
        +
        +    test.todo('should create text output for triangle()', function() {
               return new Promise(function(resolve, reject) {
                 expected =
                   '<li><a href="#myCanvasIDtextOutputshape0">green triangle</a>, at top left, covering 13% of the canvas.</li>';
        @@ -151,7 +146,7 @@ suite('outputs', function() {
                   };
                   p.draw = function() {
                     if (p.frameCount === 1) {
        -              actual = document.getElementById('myCanvasIDtextOutput_list')
        +              let actual = document.getElementById('myCanvasIDtextOutput_list')
                         .innerHTML;
                       if (actual === expected) {
                         resolve();
        @@ -168,17 +163,14 @@ suite('outputs', function() {
         
           suite('p5.prototype.gridOutput', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.gridOutput);
        -      assert.typeOf(myp5.gridOutput, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.gridOutput(1);
        -      });
        +      assert.ok(mockP5Prototype.gridOutput);
        +      assert.typeOf(mockP5Prototype.gridOutput, 'function');
             });
        +
             let expected =
               'white canvas, 100 by 100 pixels, contains 1 shape:  1 square';
        -    test('should create output as fallback', function() {
        +
        +    test.todo('should create output as fallback', function() {
               return new Promise(function(resolve, reject) {
                 let actual = '';
                 new p5(function(p) {
        @@ -203,7 +195,8 @@ suite('outputs', function() {
                 });
               });
             });
        -    test('should create output as label', function() {
        +
        +    test.todo('should create output as label', function() {
               return new Promise(function(resolve, reject) {
                 let label = '';
                 let fallback = '';
        @@ -232,7 +225,8 @@ suite('outputs', function() {
                 });
               });
             });
        -    test('should create text output for quad()', function() {
        +
        +    test.todo('should create text output for quad()', function() {
               return new Promise(function(resolve, reject) {
                 expected = 'red quadrilateral, location = top left, area = 45 %';
                 new p5(function(p) {
        @@ -245,7 +239,7 @@ suite('outputs', function() {
                     p.fill(255, 0, 0);
                     p.quad(0, 0, 80, 0, 50, 50, 0, 100);
                     if (p.frameCount === 2) {
        -              actual = document.getElementById('myCanvasIDgridOutputshape0')
        +              let actual = document.getElementById('myCanvasIDgridOutputshape0')
                         .innerHTML;
                       if (actual === expected) {
                         resolve();
        @@ -258,7 +252,8 @@ suite('outputs', function() {
                 });
               });
             });
        -    test('should create text output for point()', function() {
        +
        +    test.todo('should create text output for point()', function() {
               return new Promise(function(resolve, reject) {
                 expected = 'dark fuchsia point, location = bottom right';
                 new p5(function(p) {
        @@ -271,7 +266,7 @@ suite('outputs', function() {
                   };
                   p.draw = function() {
                     if (p.frameCount === 1) {
        -              actual = document.getElementById('myCanvasIDgridOutputshape0')
        +              let actual = document.getElementById('myCanvasIDgridOutputshape0')
                         .innerHTML;
                       if (actual === expected) {
                         resolve();
        @@ -284,7 +279,8 @@ suite('outputs', function() {
                 });
               });
             });
        -    test('should create text output for triangle()', function() {
        +
        +    test.todo('should create text output for triangle()', function() {
               return new Promise(function(resolve, reject) {
                 expected = 'green triangle, location = top left, area = 13 %';
                 new p5(function(p) {
        @@ -297,7 +293,7 @@ suite('outputs', function() {
                   };
                   p.draw = function() {
                     if (p.frameCount === 1) {
        -              actual = document.getElementById('myCanvasIDgridOutputshape0')
        +              let actual = document.getElementById('myCanvasIDgridOutputshape0')
                         .innerHTML;
                       if (actual === expected) {
                         resolve();
        diff --git a/test/unit/assets/BricolageGrotesque-Variable.ttf b/test/unit/assets/BricolageGrotesque-Variable.ttf
        new file mode 100644
        index 0000000000..1c7c35e602
        Binary files /dev/null and b/test/unit/assets/BricolageGrotesque-Variable.ttf differ
        diff --git a/test/manual-test-examples/p5.Font/Inconsolata-Bold.ttf b/test/unit/assets/Inconsolata-Bold.ttf
        similarity index 100%
        rename from test/manual-test-examples/p5.Font/Inconsolata-Bold.ttf
        rename to test/unit/assets/Inconsolata-Bold.ttf
        diff --git a/test/unit/assets/Lato-Regular.woff b/test/unit/assets/Lato-Regular.woff
        new file mode 100644
        index 0000000000..c3321de244
        Binary files /dev/null and b/test/unit/assets/Lato-Regular.woff differ
        diff --git a/test/unit/color/color_conversion.js b/test/unit/color/color_conversion.js
        index bd0ba27681..777ddee526 100644
        --- a/test/unit/color/color_conversion.js
        +++ b/test/unit/color/color_conversion.js
        @@ -1,4 +1,13 @@
        -suite('color/p5.ColorConversion', function() {
        +import p5 from '../../../src/app.js';
        +
        +assert.arrayApproximately = function(arr1, arr2, delta) {
        +  assert.equal(arr1.length, arr2.length);
        +  for(var i = 0; i < arr1.length; i++) {
        +    assert.approximately(arr1[i], arr2[i], delta);
        +  }
        +};
        +
        +suite.todo('color/p5.ColorConversion', function() {
           var rgba = [1, 0, 0.4, 0.8];
           var rgbaWithMaxHue = [1, 0, 0, 0.6];
           var rgbaWithHighLightness = [0.969, 0.753, 0.122, 0.8];
        diff --git a/test/unit/color/creating_reading.js b/test/unit/color/creating_reading.js
        index d313cfbbce..f4b134c204 100644
        --- a/test/unit/color/creating_reading.js
        +++ b/test/unit/color/creating_reading.js
        @@ -1,215 +1,153 @@
        -suite('color/CreatingReading', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import creatingReading from '../../../src/color/creating_reading';
        +import setting from '../../../src/color/setting';
        +import p5Color from '../../../src/color/p5.Color';
         
        -  teardown(function() {
        -    myp5.remove();
        +suite('color/CreatingReading', function() {
        +  beforeAll(async function() {
        +    creatingReading(mockP5, mockP5Prototype);
        +    setting(mockP5, mockP5Prototype);
        +    p5Color(mockP5, mockP5Prototype, {});
           });
         
           var fromColor;
           var toColor;
        -  var c;
        -  var val;
         
        -  suite('p5.prototype.alpha', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.RGB);
        -    });
        -    test('no friendly-err-msg I', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          var string = 'magenta';
        -          c = myp5.color(string);
        -          val = myp5.alpha(c);
        -          assert.approximately(val, 255, 0.01);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg II', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          c = myp5.color('hsba(160, 100%, 50%, 0.5)');
        -          val = myp5.alpha(c);
        -          assert.approximately(val, 127.5, 0.01);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        c = 20;
        -        val = myp5.alpha(c);
        -      });
        +  suite.todo('p5.prototype.alpha', function() {
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.RGB);
             });
           });
         
        -  suite('p5.prototype.red, green, blue', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.RGB);
        -    });
        -    test('red(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          c = myp5.color('hsl(126, 100%, 60%)');
        -          val = myp5.red(c);
        -          assert.approximately(val, 51, 0.5);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('green(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          c = myp5.color('hsl(126, 100%, 60%)');
        -          val = myp5.green(c);
        -          assert.approximately(val, 255, 0.5);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('blue(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          c = myp5.color('hsl(126, 100%, 60%)');
        -          val = myp5.blue(c);
        -          assert.approximately(val, 71, 0.5);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        +  suite.todo('p5.prototype.red, green, blue', function() {
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.RGB);
             });
           });
         
        -  suite('p5.prototype.hue, brightness, lightness, saturation', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSL);
        -    });
        -    test('hue(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          c = myp5.color('#7fffd4');
        -          val = myp5.hue(c);
        -          assert.approximately(val, 160, 0.5);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('brightness(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          c = myp5.color('#7fffd4');
        -          val = myp5.brightness(c);
        -          assert.approximately(val, 100, 0.5);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('lightness(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          c = myp5.color('#7fffd4');
        -          val = myp5.lightness(c);
        -          assert.approximately(val, 75, 0.5);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('saturation(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          c = myp5.color('#7fffd4');
        -          val = myp5.saturation(c);
        -          assert.approximately(val, 100, 0.5);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        +  suite.todo('p5.prototype.hue, brightness, lightness, saturation', function() {
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSL);
             });
           });
         
           suite('p5.prototype.lerpColor', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.RGB);
        -      fromColor = myp5.color(218, 165, 32);
        -      toColor = myp5.color(72, 61, 139);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.RGB);
        +      fromColor = mockP5Prototype.color(218, 165, 32);
        +      toColor = mockP5Prototype.color(72, 61, 139);
             });
        +
             test('should correctly get lerp colors in RGB', function() {
        -      var interA = myp5.lerpColor(fromColor, toColor, 0.33);
        -      var interB = myp5.lerpColor(fromColor, toColor, 0.66);
        -      assert.deepEqual(interA.levels, [170, 131, 67, 255]);
        -      assert.deepEqual(interB.levels, [122, 96, 103, 255]);
        +      var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33);
        +      var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66);
        +
        +      assert.closeTo(interA._color.coords[0] * 255, 170, 1);
        +      assert.closeTo(interA._color.coords[1] * 255, 131, 1);
        +      assert.closeTo(interA._color.coords[2] * 255, 67, 1);
        +
        +      assert.closeTo(interB._color.coords[0] * 255, 122, 1);
        +      assert.closeTo(interB._color.coords[1] * 255, 96, 1);
        +      assert.closeTo(interB._color.coords[2] * 255, 103, 1);
             });
        +
             test('should correctly get lerp colors in HSL', function() {
        -      myp5.colorMode(myp5.HSL);
        -      var interA = myp5.lerpColor(fromColor, toColor, 0.33);
        -      var interB = myp5.lerpColor(fromColor, toColor, 0.66);
        -      assert.deepEqual(interA.levels, [190, 44, 63, 255]);
        -      assert.deepEqual(interB.levels, [164, 53, 162, 255]);
        +      // NOTE: This is equivalent to RGB case so is testing nothing new
        +      mockP5Prototype.colorMode(mockP5Prototype.HSL);
        +      var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33);
        +      var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66);
        +
        +      assert.closeTo(interA._color.coords[0], 37, 1);
        +      assert.closeTo(interA._color.coords[1], 43, 1);
        +      assert.closeTo(interA._color.coords[2], 46, 1);
        +
        +      assert.closeTo(interB._color.coords[0], 345, 1);
        +      assert.closeTo(interB._color.coords[1], 12, 1);
        +      assert.closeTo(interB._color.coords[2], 43, 1);
             });
        +
             test('should correctly get lerp colors in HSB', function() {
        -      myp5.colorMode(myp5.HSB);
        -      var interA = myp5.lerpColor(fromColor, toColor, 0.33);
        -      var interB = myp5.lerpColor(fromColor, toColor, 0.66);
        -      assert.deepEqual(interA.levels, [192, 47, 66, 255]);
        -      assert.deepEqual(interB.levels, [166, 56, 164, 255]);
        +      // NOTE: This is equivalent to RGB case so is testing nothing new
        +      mockP5Prototype.colorMode(mockP5Prototype.HSB);
        +      var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33);
        +      var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66);
        +
        +      assert.closeTo(interA._color.coords[0], 37, 1);
        +      assert.closeTo(interA._color.coords[1], 60, 1);
        +      assert.closeTo(interA._color.coords[2], 66, 1);
        +
        +      assert.closeTo(interB._color.coords[0], 345, 1);
        +      assert.closeTo(interB._color.coords[1], 20, 1);
        +      assert.closeTo(interB._color.coords[2], 47, 1);
             });
        -    test('should not extrapolate', function() {
        -      var interA = myp5.lerpColor(fromColor, toColor, -0.5);
        -      var interB = myp5.lerpColor(fromColor, toColor, 1.5);
        +
        +    test.todo('should not extrapolate', function() {
        +      // NOTE: maybe it should extrapolate
        +      var interA = mockP5Prototype.lerpColor(fromColor, toColor, -0.5);
        +      var interB = mockP5Prototype.lerpColor(fromColor, toColor, 1.5);
               assert.deepEqual(interA.levels, [218, 165, 32, 255]);
               assert.deepEqual(interB.levels, [72, 61, 139, 255]);
             });
        -    test('missing param #2', function() {
        -      assert.validationError(function() {
        -        myp5.lerpColor(fromColor, toColor);
        -      });
        -    });
           });
        +
           suite('p5.prototype.lerpColor with alpha', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.RGB);
        -      fromColor = myp5.color(218, 165, 32, 49);
        -      toColor = myp5.color(72, 61, 139, 200);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.RGB);
        +      fromColor = mockP5Prototype.color(218, 165, 32, 49);
        +      toColor = mockP5Prototype.color(72, 61, 139, 200);
             });
        +
             test('should correctly get lerp colors in RGB with alpha', function() {
        -      var interA = myp5.lerpColor(fromColor, toColor, 0.33);
        -      var interB = myp5.lerpColor(fromColor, toColor, 0.66);
        -      assert.deepEqual(interA.levels, [170, 131, 67, 99]);
        -      assert.deepEqual(interB.levels, [122, 96, 103, 149]);
        +      var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33);
        +      var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66);
        +
        +      assert.closeTo(interA._color.coords[0], 0.66, 0.01);
        +      assert.closeTo(interA._color.coords[1], 0.51, 0.01);
        +      assert.closeTo(interA._color.coords[2], 0.26, 0.01);
        +      assert.closeTo(interA._color.alpha, 0.38, 0.01);
        +
        +      assert.closeTo(interB._color.coords[0], 0.47, 0.01);
        +      assert.closeTo(interB._color.coords[1], 0.37, 0.01);
        +      assert.closeTo(interB._color.coords[2], 0.40, 0.01);
        +      assert.closeTo(interB._color.alpha, 0.58, 0.01);
             });
        +
             test('should correctly get lerp colors in HSL with alpha', function() {
        -      myp5.colorMode(myp5.HSL);
        -      var interA = myp5.lerpColor(fromColor, toColor, 0.33);
        -      var interB = myp5.lerpColor(fromColor, toColor, 0.66);
        -      assert.deepEqual(interA.levels, [190, 44, 63, 99]);
        -      assert.deepEqual(interB.levels, [164, 53, 162, 149]);
        +      mockP5Prototype.colorMode(mockP5Prototype.HSL);
        +      var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33);
        +      var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66);
        +
        +      assert.closeTo(interA._color.coords[0], 37, 1);
        +      assert.closeTo(interA._color.coords[1], 43, 1);
        +      assert.closeTo(interA._color.coords[2], 46, 1);
        +      assert.closeTo(interA._color.alpha, 0.38, 0.01);
        +
        +      assert.closeTo(interB._color.coords[0], 345, 1);
        +      assert.closeTo(interB._color.coords[1], 11, 1);
        +      assert.closeTo(interB._color.coords[2], 42, 1);
        +      assert.closeTo(interB._color.alpha, 0.58, 0.01);
             });
        +
             test('should correctly get lerp colors in HSB with alpha', function() {
        -      myp5.colorMode(myp5.HSB);
        -      var interA = myp5.lerpColor(fromColor, toColor, 0.33);
        -      var interB = myp5.lerpColor(fromColor, toColor, 0.66);
        -      assert.deepEqual(interA.levels, [192, 47, 66, 99]);
        -      assert.deepEqual(interB.levels, [166, 56, 164, 149]);
        +      mockP5Prototype.colorMode(mockP5Prototype.HSB);
        +      var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33);
        +      var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66);
        +
        +      assert.closeTo(interA._color.coords[0], 37, 1);
        +      assert.closeTo(interA._color.coords[1], 60, 1);
        +      assert.closeTo(interA._color.coords[2], 66, 1);
        +      assert.closeTo(interA._color.alpha, 0.38, 0.01);
        +
        +      assert.closeTo(interB._color.coords[0], 345, 1);
        +      assert.closeTo(interB._color.coords[1], 20, 1);
        +      assert.closeTo(interB._color.coords[2], 47, 1);
        +      assert.closeTo(interB._color.alpha, 0.58, 0.01);
             });
        -    test('should not extrapolate', function() {
        -      var interA = myp5.lerpColor(fromColor, toColor, -0.5);
        -      var interB = myp5.lerpColor(fromColor, toColor, 1.5);
        +
        +    test.todo('should not extrapolate', function() {
        +      // NOTE: maybe it should extrapolate
        +      var interA = mockP5Prototype.lerpColor(fromColor, toColor, -0.5);
        +      var interB = mockP5Prototype.lerpColor(fromColor, toColor, 1.5);
               assert.deepEqual(interA.levels, [218, 165, 32, 49]);
               assert.deepEqual(interB.levels, [72, 61, 139, 200]);
             });
        diff --git a/test/unit/color/p5.Color.js b/test/unit/color/p5.Color.js
        index 43ffaa4f02..12fc3e6648 100644
        --- a/test/unit/color/p5.Color.js
        +++ b/test/unit/color/p5.Color.js
        @@ -1,579 +1,303 @@
        -suite('p5.Color', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import creatingReading from '../../../src/color/creating_reading';
        +import setting from '../../../src/color/setting';
        +import { Color } from '../../../src/color/p5.Color';
        +import p5Color from '../../../src/color/p5.Color';
         
        -  teardown(function() {
        -    myp5.remove();
        +suite('p5.Color', function() {
        +  beforeAll(() => {
        +    creatingReading(mockP5, mockP5Prototype);
        +    setting(mockP5, mockP5Prototype);
        +    p5Color(mockP5, mockP5Prototype, {});
           });
         
           var c;
         
           suite('p5.prototype.color(r,g,b)', function() {
        -    setup(function() {
        -      c = myp5.color(255, 0, 102);
        +    beforeEach(function() {
        +      c = mockP5Prototype.color(255, 0, 102);
             });
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
        -
             test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 255]);
        -    });
        -
        -    test("shouldn't set HSBA property before hsb access func is called", function() {
        -      assert.equal(c.hsba, undefined);
        -    });
        -
        -    test("shouldn't set HSLA property before hsb access func is called", function() {
        -      assert.equal(c.hsla, undefined);
        -    });
        -
        -    test('color(): missing param #0 + throws error', function() {
        -      expect(function() {
        -        c = myp5.color();
        -      }).to.throw();
        +      assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +      assert.equal(c._color.alpha, 1);
             });
           });
         
        -  suite('p5.prototype.color("#rgb")', function() {
        -    setup(function() {
        -      c = myp5.color('#f06');
        +  suite('p5.prototype.color(r,g,b,a)', function() {
        +    beforeEach(function() {
        +      c = mockP5Prototype.color(255, 0, 102, 204);
             });
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
         
             test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 255]);
        +      assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +      assert.equal(c._color.alpha, 0.8);
             });
        +  });
         
        -    suite('spot check', function() {
        -      test('numeric hex values', function() {
        -        c = myp5.color('#000');
        -        assert.deepEqual(c.levels, [0, 0, 0, 255]);
        -      });
        +  // NOTE: suite for all signatures
         
        -      test('alphabetic hex values', function() {
        -        c = myp5.color('#fff');
        -        assert.deepEqual(c.levels, [255, 255, 255, 255]);
        +  suite('p5.prototype.color(string)', function(){
        +    suite('#rgb', function(){
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('#f06');
               });
        -
        -      test('alphanumeric hex values', function() {
        -        c = myp5.color('#f00');
        -        assert.deepEqual(c.levels, [255, 0, 0, 255]);
        -        c = myp5.color('#f0e');
        -        assert.deepEqual(c.levels, [255, 0, 238, 255]);
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
               });
        -    });
        -
        -    test('invalid hex values resolve to white', function() {
        -      c = myp5.color('#cat');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255]);
        -    });
        -
        -    test('should not be able to pass css & alpha', function() {
        -      // TODO: change this to expect p5.ValidationError when
        -      // color() docs are fixed.
        -      assert.throws(function() {
        -        c = myp5.color('#fff', 100);
        -      }, Error);
        -    });
        -  });
        -
        -  suite('p5.prototype.color("#rgba")', function() {
        -    setup(function() {
        -      c = myp5.color('#f016');
        -    });
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
         
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 17, 102]);
        +      test('should correctly set RGBA property', function() {
        +        assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +        assert.equal(c._color.alpha, 1);
        +      });
             });
         
        -    suite('spot check', function() {
        -      test('numeric hex values', function() {
        -        c = myp5.color('#0000');
        -        assert.deepEqual(c.levels, [0, 0, 0, 0]);
        +    suite('#rgba', function(){
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('#f066');
               });
        -
        -      test('alphabetic hex values', function() {
        -        c = myp5.color('#ffff');
        -        assert.deepEqual(c.levels, [255, 255, 255, 255]);
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
               });
         
        -      test('alphanumeric hex values', function() {
        -        c = myp5.color('#f007');
        -        assert.deepEqual(c.levels, [255, 0, 0, 119]);
        -        c = myp5.color('#f0e5');
        -        assert.deepEqual(c.levels, [255, 0, 238, 85]);
        +      test('should correctly set RGBA property', function() {
        +        assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +        assert.equal(c._color.alpha, 0.4);
               });
             });
         
        -    test('invalid hex values resolve to white', function() {
        -      c = myp5.color('#fire');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255]);
        -    });
        -  });
        -
        -  suite('p5.prototype.color("#rrggbb")', function() {
        -    setup(function() {
        -      c = myp5.color('#ff0066');
        -    });
        -
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        -
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 255]);
        -    });
        -
        -    suite('spot check', function() {
        -      test('numeric hex values', function() {
        -        c = myp5.color('#123456');
        -        assert.deepEqual(c.levels, [18, 52, 86, 255]);
        +    suite('#rrggbb', function(){
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('#ff0066');
               });
         
        -      test('alphabetic hex values', function() {
        -        c = myp5.color('#abcdef');
        -        assert.deepEqual(c.levels, [171, 205, 239, 255]);
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
               });
         
        -      test('alphanumeric hex values', function() {
        -        c = myp5.color('#a1a1a1');
        -        assert.deepEqual(c.levels, [161, 161, 161, 255]);
        -        c = myp5.color('#14ffa8');
        -        assert.deepEqual(c.levels, [20, 255, 168, 255]);
        +      test('should correctly set RGBA property', function() {
        +        assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +        assert.equal(c._color.alpha, 1);
               });
             });
         
        -    test('invalid hex values resolve to white', function() {
        -      c = myp5.color('#zzztop');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255]);
        -    });
        -  });
        -
        -  suite('p5.prototype.color("#rrggbbaa")', function() {
        -    setup(function() {
        -      c = myp5.color('#f01dab1e');
        -    });
        -
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        -
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [240, 29, 171, 30]);
        -    });
        -
        -    suite('spot check', function() {
        -      test('numeric hex values', function() {
        -        c = myp5.color('#12345678');
        -        assert.deepEqual(c.levels, [18, 52, 86, 120]);
        +    suite('#rrggbbaa', function(){
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('#f01dab1e');
               });
         
        -      test('alphabetic hex values', function() {
        -        c = myp5.color('#abcdeffe');
        -        assert.deepEqual(c.levels, [171, 205, 239, 254]);
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
               });
         
        -      test('alphanumeric hex values', function() {
        -        c = myp5.color('#a1a1a1a1');
        -        assert.deepEqual(c.levels, [161, 161, 161, 161]);
        -        c = myp5.color('#14ffaca6');
        -        assert.deepEqual(c.levels, [20, 255, 172, 166]);
        +      test('should correctly set RGBA property', function() {
        +        assert.deepEqual(c._color.coords, [240, 29, 171].map(c => c/255));
        +        assert.equal(c._color.alpha, 30/255);
               });
             });
         
        -    test('invalid hex values resolve to white', function() {
        -      c = myp5.color('#c0vfefed');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255]);
        -    });
        -  });
        -
        -  suite('p5.prototype.color("rgb(r,g,b)")', function() {
        -    setup(function() {
        -      c = myp5.color('rgb(255,0,102)');
        -    });
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        +    suite('rgb(r,g,b)', function(){
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('rgb(255,0,102)');
        +      });
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
        +      });
         
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 255]);
        -    });
        -
        -    test('spot check variant spacing', function() {
        -      // Exhaustive testing of spacing variations within RGB format is
        -      // prohibitive: spot check a set of representative values
        -      c = myp5.color('rgb(0,0,0)');
        -      assert.deepEqual(c.levels, [0, 0, 0, 255]);
        -      c = myp5.color('rgb(0,100 ,0)');
        -      assert.deepEqual(c.levels, [0, 100, 0, 255]);
        -      c = myp5.color('rgb( 100,255,137)');
        -      assert.deepEqual(c.levels, [100, 255, 137, 255]);
        -      c = myp5.color('rgb(0, 50,0)');
        -      assert.deepEqual(c.levels, [0, 50, 0, 255]);
        -      c = myp5.color('rgb(0,100, 0)');
        -      assert.deepEqual(c.levels, [0, 100, 0, 255]);
        -      c = myp5.color('rgb( 111, 255, 57)');
        -      assert.deepEqual(c.levels, [111, 255, 57, 255]);
        -      c = myp5.color('rgb(40, 0, 0)');
        -      assert.deepEqual(c.levels, [40, 0, 0, 255]);
        -      c = myp5.color('rgb(0,255, 10 )');
        -      assert.deepEqual(c.levels, [0, 255, 10, 255]);
        -    });
        -
        -    test('invalid RGB values resolve to white', function() {
        -      c = myp5.color('rgb(100.5, 40, 3)');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255], 'decimal R value');
        -      c = myp5.color('rgb(100, 40.00009, 3)');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255], 'decimal G value');
        -      c = myp5.color('rgb(100, 40, 3.14159265)');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255], 'decimal B value');
        -      c = myp5.color('rgb(.9, 40, 3, 1.0)');
        -      assert.deepEqual(
        -        c.levels,
        -        [255, 255, 255, 255],
        -        'decimal without leading 0'
        -      );
        -      c = myp5.color('skip a beat');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255], 'non-color strings');
        +      test('should correctly set RGBA property', function() {
        +        assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +        assert.equal(c._color.alpha, 1);
        +      });
             });
        -  });
         
        -  suite('p5.prototype.color("rgb(r%,g%,b%)")', function() {
        -    setup(function() {
        -      c = myp5.color('rgb(100%, 0%, 40%)');
        -    });
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        +    suite('rgb(r%,g%,b%)', function(){
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('rgb(100%, 0%, 40%)');
        +      });
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
        +      });
         
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 255]);
        -    });
        -
        -    test('spot check variant spacing', function() {
        -      // Exhaustive testing of spacing variations within RGB format is
        -      // prohibitive: spot check a set of representative values
        -      c = myp5.color('rgb(100%,70%,100%)');
        -      assert.deepEqual(c.levels, [255, 179, 255, 255]);
        -      c = myp5.color('rgb(0%,0%,0% )');
        -      assert.deepEqual(c.levels, [0, 0, 0, 255]);
        -      c = myp5.color('rgb(0%,50%  ,  0%)');
        -      assert.deepEqual(c.levels, [0, 128, 0, 255]);
        -      c = myp5.color('rgb(10%, 50%,0%)');
        -      assert.deepEqual(c.levels, [26, 128, 0, 255]);
        -      c = myp5.color('rgb(0%,48%, 0%)');
        -      assert.deepEqual(c.levels, [0, 122, 0, 255]);
        -      c = myp5.color('rgb(0%,    0%, 40%)');
        -      assert.deepEqual(c.levels, [0, 0, 102, 255]);
        -      c = myp5.color('rgb(0%,87%, 10%)');
        -      assert.deepEqual(c.levels, [0, 222, 26, 255]);
        -    });
        -
        -    test('spot check decimal percentage values', function() {
        -      // Percentage values in CSS <color> identifiers are floats 0.0%-100.0%
        -      c = myp5.color('rgb( 50%,100% ,.9%)');
        -      assert.deepEqual(c.levels, [128, 255, 2, 255], 'B% without leading 0');
        -      c = myp5.color('rgb( 9.90%, 12%, 50%)');
        -      assert.deepEqual(c.levels, [25, 31, 128, 255], 'decimal R%');
        -    });
        -
        -    test('invalid percentage values default to white', function() {
        -      c = myp5.color('rgb(50, 100%, 100%');
        -      assert.deepEqual(
        -        c.levels,
        -        [255, 255, 255, 255],
        -        'mixed percentage and non-percentage input'
        -      );
        -      c = myp5.color('rgb(,0%,0%)');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255], 'missing values');
        -      c = myp5.color('rgb(A%,B%,C%)');
        -      assert.deepEqual(
        -        c.levels,
        -        [255, 255, 255, 255],
        -        'non-numeric percentages'
        -      );
        +      test('should correctly set RGBA property', function() {
        +        assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +        assert.equal(c._color.alpha, 1);
        +      });
             });
        -  });
         
        -  suite('p5.prototype.color("rgba(r,g,b,a)")', function() {
        -    setup(function() {
        -      c = myp5.color('rgba(255,0,102,0.8)');
        -    });
        +    suite('rgba(r,g,b,a)', function(){
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('rgba(255,0,102,0.8)');
        +      });
         
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
        +      });
         
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        -    });
        -
        -    test('spot check variant spacing', function() {
        -      // Exhaustive testing of spacing variations within RGBA format is
        -      // prohibitive: spot check a set of representative values
        -      c = myp5.color('rgba(255,255,255,1)');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255]);
        -      c = myp5.color('rgba(0,0,0,1)');
        -      assert.deepEqual(c.levels, [0, 0, 0, 255]);
        -      c = myp5.color('rgba(0,100,0,   0.5)');
        -      assert.deepEqual(c.levels, [0, 100, 0, 128]);
        -      c = myp5.color('rgba(  100,255 ,255, 0)');
        -      assert.deepEqual(c.levels, [100, 255, 255, 0]);
        -      c = myp5.color('rgba(0, 0,255, 0.1515236)');
        -      assert.deepEqual(c.levels, [0, 0, 255, 39]);
        -      c = myp5.color('rgba(100,101, 0, 0.75)');
        -      assert.deepEqual(c.levels, [100, 101, 0, 191]);
        -      c = myp5.color('rgba( 255, 255, 255, .9)');
        -      assert.deepEqual(c.levels, [255, 255, 255, 230]);
        -      c = myp5.color('rgba(0, 0, 0, 1)');
        -      assert.deepEqual(c.levels, [0, 0, 0, 255]);
        -      c = myp5.color('rgba(255,0, 10 , 0.33)');
        -      assert.deepEqual(c.levels, [255, 0, 10, 84]);
        -    });
        -
        -    test('invalid RGBA values resolve to white', function() {
        -      c = myp5.color('rgba(100.5, 40, 3, 1.0)');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255], 'decimal R% value');
        -      c = myp5.color('rgba(100, 40.00009, 3, 1.0)');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255], 'decimal G% value');
        -      c = myp5.color('rgba(100, 40, 3.14159265, 1.0)');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255], 'decimal B% value');
        -      c = myp5.color('rgba(.9, 40, 3, 1.0)');
        -      assert.deepEqual(
        -        c.levels,
        -        [255, 255, 255, 255],
        -        'decimal R% without leading 0'
        -      );
        +      test('should correctly set RGBA property', function() {
        +        assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +        assert.equal(c._color.alpha, 0.8);
        +      });
             });
        -  });
         
        -  suite('p5.prototype.color("rgba(r%,g%,b%,a)")', function() {
        -    setup(function() {
        -      c = myp5.color('rgba(100.0%,0.0%,40%,0.8)');
        -    });
        +    suite('rgba(r%,g%,b%,a)', function(){
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('rgba(100.0%,0.0%,40%,0.8)');
        +      });
         
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
        +      });
         
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        -    });
        -
        -    test('spot check variant spacing', function() {
        -      // Exhaustive testing of spacing variations within RGBA format is
        -      // prohibitive: spot check a set of representative values
        -      c = myp5.color('rgba(100%,10.9%,100%,1)');
        -      assert.deepEqual(c.levels, [255, 28, 255, 255]);
        -      c = myp5.color('rgba(37%,     0%,0%,1)');
        -      assert.deepEqual(c.levels, [94, 0, 0, 255]);
        -      c = myp5.color('rgba(0%,50%,0%, 0.5)');
        -      assert.deepEqual(c.levels, [0, 128, 0, 128]);
        -      c = myp5.color('rgba( 50%,.9% ,100%, 0)');
        -      assert.deepEqual(c.levels, [128, 2, 255, 0]);
        -      c = myp5.color('rgba(10%, 50%,0%, 0.2515236)');
        -      assert.deepEqual(c.levels, [26, 128, 0, 64]);
        -      c = myp5.color('rgba(0%,50%, 0%, 0.75)');
        -      assert.deepEqual(c.levels, [0, 128, 0, 191]);
        -      c = myp5.color('rgba( 100%, 12%, 100%, .9)');
        -      assert.deepEqual(c.levels, [255, 31, 255, 230]);
        -      c = myp5.color('rgba(0%, 0%, 0%, 1)');
        -      assert.deepEqual(c.levels, [0, 0, 0, 255]);
        -      c = myp5.color('rgba(0%,87%, 10% , 0.3)');
        -      assert.deepEqual(c.levels, [0, 222, 26, 77]);
        -    });
        -
        -    test('spot check decimal percentage values', function() {
        -      // Percentage values in CSS <color> identifiers are floats 0.0%-100.0%
        -      c = myp5.color('rgba(90.5%, 40%, 3%, 0.45)');
        -      assert.deepEqual(c.levels, [231, 102, 8, 115], 'Decimal A% value');
        -      c = myp5.color('rgba(90%, 40.00009%, 3%, 0.45)');
        -      assert.deepEqual(c.levels, [230, 102, 8, 115], 'Decimal G% value');
        -      c = myp5.color('rgba(90%, 40%, 3.14159265%, 0.45)');
        -      assert.deepEqual(c.levels, [230, 102, 8, 115], 'Decimal B% value');
        -      c = myp5.color('rgba(90%, 40%, .9%, 0.45)');
        -      assert.deepEqual(
        -        c.levels,
        -        [230, 102, 2, 115],
        -        'Decimal B% without leading 0'
        -      );
        -    });
        -
        -    test('invalid RGBA percentage values resolve to white', function() {
        -      c = myp5.color('rgb(50,100%,100%,1');
        -      assert.deepEqual(
        -        c.levels,
        -        [255, 255, 255, 255],
        -        'mixed percentage and non-percentage input'
        -      );
        -      c = myp5.color('rgb(A%,B%,C%,0.5)');
        -      assert.deepEqual(
        -        c.levels,
        -        [255, 255, 255, 255],
        -        'non-numeric percentages'
        -      );
        -      c = myp5.color('rgba(,50%,20%,1)');
        -      assert.deepEqual(c.levels, [255, 255, 255, 255], 'missing values');
        +      test('should correctly set RGBA property', function() {
        +        assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +        assert.equal(c._color.alpha, 0.8);
        +      });
             });
        -  });
         
        -  suite('p5.prototype.color("hsl(h, s%, l%)")', function() {
        -    setup(function() {
        -      c = myp5.color('hsl(336, 100%, 50%)');
        -    });
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 255]);
        +    suite('hsl(h, s%, l%)', function(){
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('hsl(336, 100%, 50%)');
        +      });
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
        +      });
        +      test('should correctly set RGBA property', function() {
        +        // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version
        +        // assert.deepEqual(c.color.coords, [336, 100, 50]);
        +        assert.equal(+c._color.coords[0], 336);
        +        assert.equal(c._color.coords[1], 100);
        +        assert.equal(c._color.coords[2], 50);
        +        assert.equal(c._color.alpha, 1);
        +      });
             });
        -  });
         
        -  suite('p5.prototype.color("hsla(h, s%, l%, a)")', function() {
        -    setup(function() {
        -      c = myp5.color('hsla(336, 100%, 50%, 0.8)');
        -    });
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +    suite('hsla(h, s%, l%, a)', function() {
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('hsla(336, 100%, 50%, 0.8)');
        +      });
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
        +      });
        +      test('should correctly set RGBA property', function() {
        +        // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version
        +        // assert.deepEqual(c.color.coords, [336, 100, 50]);
        +        assert.equal(+c._color.coords[0], 336);
        +        assert.equal(c._color.coords[1], 100);
        +        assert.equal(c._color.coords[2], 50);
        +        assert.equal(c._color.alpha, 0.8);
        +      });
             });
        -  });
         
        -  suite('p5.prototype.color("hsb(h, s%, b%)")', function() {
        -    setup(function() {
        -      c = myp5.color('hsb(336, 100%, 100%)');
        -    });
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 255]);
        +    suite('hsb(h, s%, b%)', function() {
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('hsb(336, 100%, 100%)');
        +      });
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
        +      });
        +      test('should correctly set RGBA property', function() {
        +        // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version
        +        // assert.deepEqual(c.color.coords, [336, 100, 100]);
        +        assert.equal(+c._color.coords[0], 336);
        +        assert.equal(c._color.coords[1], 100);
        +        assert.equal(c._color.coords[2], 100);
        +        assert.equal(c._color.alpha, 1);
        +      });
             });
        -  });
         
        -  suite('p5.prototype.color("hsba(h, s%, b%, a)")', function() {
        -    setup(function() {
        -      c = myp5.color('hsba(336, 100%, 100%, 0.8)');
        -    });
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +    suite('hsba(h, s%, b%, a)', function() {
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('hsba(336, 100%, 100%, 0.8)');
        +      });
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
        +      });
        +      test('should correctly set RGBA property', function() {
        +        // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version
        +        // assert.deepEqual(c.color.coords, [336, 100, 50]);
        +        assert.equal(+c._color.coords[0], 336);
        +        assert.equal(c._color.coords[1], 100);
        +        assert.equal(c._color.coords[2], 100);
        +        assert.equal(c._color.alpha, 0.8);
        +      });
             });
        -  });
         
        -  suite('p5.prototype.color("svgnamedcolor")', function() {
        -    setup(function() {
        -      c = myp5.color('papayawhip');
        -    });
        +    suite('named colors', function() {
        +      beforeEach(function() {
        +        c = mockP5Prototype.color('papayawhip');
        +      });
         
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        +      test('should create instance of p5.Color', function() {
        +        assert.instanceOf(c, Color);
        +      });
         
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 239, 213, 255]);
        +      test('should correctly set RGBA property', function() {
        +        assert.deepEqual(c._color.coords, [255, 239, 213].map(c => c/255));
        +        assert.equal(c._color.alpha, 1);
        +      });
             });
         
        -    test('spot check color keywords', function() {
        -      c = myp5.color('red');
        -      assert.deepEqual(c.levels, [255, 0, 0, 255]);
        -      c = myp5.color('magenta');
        -      assert.deepEqual(c.levels, [255, 0, 255, 255]);
        -      c = myp5.color('limegreen');
        -      assert.deepEqual(c.levels, [50, 205, 50, 255]);
        -    });
        +    // NOTE: previously returns a white color
        +    suite.todo('invalid string');
           });
         
           suite('p5.prototype.color([])', function() {
        -    setup(function() {
        -      c = myp5.color([255, 0, 102]);
        +    beforeEach(function() {
        +      c = mockP5Prototype.color([255, 0, 102]);
             });
        -    test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        -    });
        -    test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 255]);
        -    });
        -  });
         
        -  suite('p5.prototype.color(r,g,b,a)', function() {
        -    setup(function() {
        -      c = myp5.color(255, 0, 102, 204);
        -    });
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
         
             test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        -    });
        -
        -    test('should correctly get hue/saturation/brightness/lightness', function() {
        -      assert.approximately(c._getHue(), 336, 0.5);
        -      assert.approximately(c._getSaturation(), 100, 0.5);
        -      assert.approximately(c._getBrightness(), 100, 0.5);
        -      assert.approximately(c._getLightness(), 50, 0.5);
        -    });
        -
        -    test('should correctly get RGBA values', function() {
        -      assert.approximately(c._getRed(), 255, 0.5);
        -      assert.approximately(c._getGreen(), 0, 0.5);
        -      assert.approximately(c._getBlue(), 102, 0.5);
        -      assert.approximately(c._getAlpha(), 204, 0.5);
        -    });
        -
        -    test('should correctly render color string', function() {
        -      assert.equal(c.toString(), 'rgba(255,0,102,0.8)');
        +      assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +      assert.equal(c._color.alpha, 1);
             });
           });
         
           // color level setters
           suite('in default mode', function() {
             test('can be modified with alpha setter', function() {
        -      var cc = myp5.color(255, 0, 102, 204);
        -      assert.deepEqual(cc.levels, [255, 0, 102, 204]);
        -      cc.setAlpha(98);
        -      assert.deepEqual(cc.levels, [255, 0, 102, 98]);
        +      let cc = mockP5Prototype.color(255, 0, 102, 204);
        +      assert.deepEqual(cc._color.coords, [1, 0, 0.4]);
        +      assert.equal(cc._color.alpha, 0.8);
        +      cc.setAlpha(98/255);
        +      assert.deepEqual(cc._color.coords, [1, 0, 0.4]);
        +      assert.equal(cc._color.alpha, 98/255);
             });
        +
             test('can be modified with rgb setters', function() {
        -      var cc = myp5.color(255, 0, 102, 204);
        -      assert.deepEqual(cc.levels, [255, 0, 102, 204]);
        -      cc.setRed(98);
        -      assert.deepEqual(cc.levels, [98, 0, 102, 204]);
        -      cc.setGreen(44);
        -      assert.deepEqual(cc.levels, [98, 44, 102, 204]);
        -      cc.setBlue(244);
        -      assert.deepEqual(cc.levels, [98, 44, 244, 204]);
        +      var cc = mockP5Prototype.color(255, 0, 102, 204);
        +      assert.deepEqual(cc._color.coords, [1, 0, 0.4]);
        +      assert.equal(cc._color.alpha, 0.8);
        +      cc.setRed(98/255);
        +      assert.deepEqual(cc._color.coords, [98/255, 0, 0.4]);
        +      assert.equal(cc._color.alpha, 0.8);
        +      cc.setGreen(44/255);
        +      assert.deepEqual(cc._color.coords, [98/255, 44/255, 0.4]);
        +      assert.equal(cc._color.alpha, 0.8);
        +      cc.setBlue(244/255);
        +      assert.deepEqual(cc._color.coords, [98/255, 44/255, 244/255]);
        +      assert.equal(cc._color.alpha, 0.8);
             });
           });
         
           // Color Mode
           suite('p5.Color in RGB mode with custom range', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.RGB, 1);
        -      c = myp5.color(1, 0, 0.4, 0.8);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.RGB, 1);
        +      c = mockP5Prototype.color(1, 0, 0.4, 0.8);
             });
         
             test('should correctly convert to RGBA', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +      assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +      assert.equal(c._color.alpha, 0.8);
             });
         
             test('should correctly get RGBA property', function() {
        @@ -584,56 +308,79 @@ suite('p5.Color', function() {
             });
         
             test('should correctly render color string', function() {
        -      assert.equal(c.toString(), 'rgba(255,0,102,0.8)');
        +      assert.equal(c.toString(), 'rgb(100% 0% 40% / 0.8)');
             });
         
             test('should correctly get RGBA property after overwrite', function() {
        -      myp5.colorMode(myp5.RGB, 255, 255, 255, 255);
        -      assert.equal(c._getRed(), 255);
        -      assert.equal(c._getGreen(), 0);
        -      assert.equal(c._getBlue(), 102);
        -      assert.equal(c._getAlpha(), 204);
        +      mockP5Prototype.colorMode(mockP5Prototype.RGB, 255, 255, 255, 255);
        +      assert.equal(c._getRed(), 255/255);
        +      assert.equal(c._getGreen(), 0/255);
        +      assert.equal(c._getBlue(), 102/255);
        +      assert.equal(c._getAlpha(), 204/255);
             });
           });
         
           suite('p5.Color in HSL mode', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSL);
        -      c = myp5.color(336, 100, 50);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSL);
        +      c = mockP5Prototype.color(336, 100, 50);
             });
        +
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
        +
             test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 255]);
        +      assert.deepEqual(c._color.coords, [336, 100, 50]);
        +      assert.equal(c._color.alpha, 1);
             });
        +
             test('can be modified with alpha setter', function() {
        -      var cc = myp5.color(336, 100, 50);
        +      let cc = mockP5Prototype.color(336, 100, 50);
               cc.setAlpha(0.73);
        -      assert.deepEqual(cc.levels, [255, 0, 102, 186]);
        +      assert.deepEqual(cc._color.coords, [336, 100, 50]);
        +      assert.equal(cc._color.alpha, 0.73);
             });
        +
             test('can be modified with rgb setters', function() {
        -      var cc = myp5.color(336, 100, 50);
        -      assert.deepEqual(cc.levels, [255, 0, 102, 255]);
        -      cc.setRed(98);
        -      assert.deepEqual(cc.levels, [98, 0, 102, 255]);
        -      cc.setGreen(44);
        -      assert.deepEqual(cc.levels, [98, 44, 102, 255]);
        -      cc.setBlue(244);
        -      assert.deepEqual(cc.levels, [98, 44, 244, 255]);
        +      let cc = mockP5Prototype.color(336, 100, 50);
        +      assert.deepEqual(cc._color.coords, [336, 100, 50]);
        +      assert.equal(cc._color.alpha, 1);
        +
        +      // TODO: separately check these values are correct (not in test here)
        +      cc.setRed(98/255);
        +      assert.closeTo(cc._color.coords[0], 297, 1);
        +      assert.closeTo(cc._color.coords[1], 100, 1);
        +      assert.closeTo(cc._color.coords[2], 20, 1);
        +      assert.equal(cc._color.alpha, 1);
        +
        +      cc.setGreen(44/255);
        +      assert.closeTo(cc._color.coords[0], 295, 1);
        +      assert.closeTo(cc._color.coords[1], 39, 1);
        +      assert.closeTo(cc._color.coords[2], 28, 1);
        +      assert.equal(cc._color.alpha, 1);
        +
        +      cc.setBlue(244/255);
        +      assert.closeTo(cc._color.coords[0], 256, 1);
        +      assert.closeTo(cc._color.coords[1], 90, 1);
        +      assert.closeTo(cc._color.coords[2], 56, 1);
        +      assert.equal(cc._color.alpha, 1);
             });
           });
         
           suite('p5.Color in HSL mode with Alpha', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSL);
        -      c = myp5.color(336, 100, 50, 0.8);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSL);
        +      c = mockP5Prototype.color(336, 100, 50, 0.8);
             });
        +
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
        +
             test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +      assert.deepEqual(c._color.coords, [336, 100, 50]);
        +      assert.equal(c._color.alpha, 0.8);
             });
         
             test('should correctly get hue/saturation/lightness/alpha', function() {
        @@ -645,50 +392,73 @@ suite('p5.Color', function() {
           });
         
           suite('p5.Color in HSL mode with custom range', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSL, 100, 200, 300, 10);
        -      c = myp5.color(93.33, 200, 150, 8);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSL, 100, 200, 300, 10);
        +      c = mockP5Prototype.color(93.33, 200, 150, 8);
             });
         
             test('should correctly get HSLA property', function() {
        -      assert.approximately(c._getHue(), 93, 0.5);
        -      assert.approximately(c._getSaturation(), 200, 0.5);
        -      assert.approximately(c._getLightness(), 150, 0.5);
        -      assert.approximately(c._getAlpha(), 8, 0.5);
        +      assert.approximately(c._getHue(), 336, 0.5);
        +      assert.approximately(c._getSaturation(), 100, 0.5);
        +      assert.approximately(c._getLightness(), 50, 0.5);
        +      assert.approximately(c._getAlpha(), 0.8, 0.5);
             });
         
             test('should correctly convert to RGBA', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +      assert.closeTo(c._color.coords[0], 336, 1);
        +      assert.equal(c._color.coords[1], 100);
        +      assert.equal(c._color.coords[2], 50);
        +      assert.equal(c._color.alpha, 0.8);
             });
         
             test('should correctly render color string', function() {
        -      assert.equal(c.toString(), 'rgba(255,0,102,0.8)');
        +      assert.equal(c.toString(), 'hsl(335.99 100% 50% / 0.8)');
             });
         
             test('can be modified with alpha setter', function() {
        -      var cc = myp5.color(93.33, 200, 150, 8);
        -      cc.setAlpha(7.3);
        -      assert.deepEqual(cc.levels, [255, 0, 102, 186]);
        +      let cc = mockP5Prototype.color(93.33, 200, 150, 8);
        +      cc.setAlpha(0.73);
        +      assert.closeTo(cc._color.coords[0], 336, 1);
        +      assert.equal(cc._color.coords[1], 100);
        +      assert.equal(cc._color.coords[2], 50);
        +      assert.equal(cc._color.alpha, 0.73);
             });
        +
             test('can be modified with rgb setters', function() {
        -      var cc = myp5.color(93.33, 200, 150, 8);
        -      assert.deepEqual(cc.levels, [255, 0, 102, 204]);
        -      cc.setRed(98);
        -      assert.deepEqual(cc.levels, [98, 0, 102, 204]);
        -      cc.setGreen(44);
        -      assert.deepEqual(cc.levels, [98, 44, 102, 204]);
        -      cc.setBlue(244);
        -      assert.deepEqual(cc.levels, [98, 44, 244, 204]);
        +      let cc = mockP5Prototype.color(93.33, 200, 150, 8);
        +      assert.closeTo(c._color.coords[0], 336, 1);
        +      assert.equal(c._color.coords[1], 100);
        +      assert.equal(c._color.coords[2], 50);
        +      assert.equal(c._color.alpha, 0.8);
        +
        +      cc.setRed(98/255);
        +      assert.closeTo(cc._color.coords[0], 297, 1);
        +      assert.closeTo(cc._color.coords[1], 100, 1);
        +      assert.closeTo(cc._color.coords[2], 20, 1);
        +      assert.equal(cc._color.alpha, 0.8);
        +
        +      cc.setGreen(44/255);
        +      assert.closeTo(cc._color.coords[0], 295, 1);
        +      assert.closeTo(cc._color.coords[1], 39, 1);
        +      assert.closeTo(cc._color.coords[2], 28, 1);
        +      assert.equal(cc._color.alpha, 0.8);
        +
        +      cc.setBlue(244/255);
        +      assert.closeTo(cc._color.coords[0], 256, 1);
        +      assert.closeTo(cc._color.coords[1], 90, 1);
        +      assert.closeTo(cc._color.coords[2], 56, 1);
        +      assert.equal(cc._color.alpha, 0.8);
             });
           });
         
           suite('p5.Color in HSL mode with RGB string', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSL, 360, 100, 100, 1);
        -      c = myp5.color('rgba(255, 0, 102, 0.8)');
        +    // NOTE: will still create a sRGB color in this case
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSL, 360, 100, 100, 1);
        +      c = mockP5Prototype.color('rgba(255, 0, 102, 0.8)');
             });
         
        -    test('should correctly get HSLA property', function() {
        +    test.todo('should correctly get HSLA property', function() {
               assert.approximately(c._getHue(), 336, 0.5);
               assert.approximately(c._getSaturation(), 100, 0.5);
               assert.approximately(c._getLightness(), 50, 0.5);
        @@ -696,21 +466,22 @@ suite('p5.Color', function() {
             });
         
             test('should correctly convert to RGBA', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +      assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +      assert.equal(c._color.alpha, 0.8);
             });
         
             test('should correctly render color string', function() {
        -      assert.equal(c.toString(), 'rgba(255,0,102,0.8)');
        +      assert.equal(c.toString(), 'rgb(100% 0% 40% / 0.8)');
             });
           });
         
           suite('p5.Color in HSL mode with HSL string', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSL, 360, 100, 100, 1);
        -      c = myp5.color('hsla(336, 100%, 50%, 0.8)');
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSL, 360, 100, 100, 1);
        +      c = mockP5Prototype.color('hsla(336, 100%, 50%, 0.8)');
             });
         
        -    test('should correctly get HSLA property', function() {
        +    test.todo('should correctly get HSLA property', function() {
               assert.approximately(c._getHue(), 336, 0.5);
               assert.approximately(c._getSaturation(), 100, 0.5);
               assert.approximately(c._getLightness(), 50, 0.5);
        @@ -718,21 +489,26 @@ suite('p5.Color', function() {
             });
         
             test('should correctly convert to RGBA', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +      // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version
        +      // assert.deepEqual(c.color.coords, [336, 100, 50]);
        +      assert.equal(+c._color.coords[0], 336);
        +      assert.equal(c._color.coords[1], 100);
        +      assert.equal(c._color.coords[2], 50);
        +      assert.equal(c._color.alpha, 0.8);
             });
         
             test('should correctly render color string', function() {
        -      assert.equal(c.toString(), 'rgba(255,0,102,0.8)');
        +      assert.equal(c.toString(), 'hsl(336 100% 50% / 0.8)');
             });
           });
         
           suite('p5.Color in HSL mode with HSB string', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSL, 360, 100, 100, 1);
        -      c = myp5.color('hsba(336, 100%, 100%, 0.8)');
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSL, 360, 100, 100, 1);
        +      c = mockP5Prototype.color('hsba(336, 100%, 100%, 0.8)');
             });
         
        -    test('should correctly get HSLA property', function() {
        +    test.todo('should correctly get HSLA property', function() {
               assert.approximately(c._getHue(), 336, 0.5);
               assert.approximately(c._getSaturation(), 100, 0.5);
               assert.approximately(c._getLightness(), 50, 0.5);
        @@ -740,52 +516,77 @@ suite('p5.Color', function() {
             });
         
             test('should correctly convert to RGBA', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +      // NOTE: 0.5.2 of color.js uses `new Number` which is corrected in future version
        +      // assert.deepEqual(c.color.coords, [336, 100, 50]);
        +      assert.equal(+c._color.coords[0], 336);
        +      assert.equal(c._color.coords[1], 100);
        +      assert.equal(c._color.coords[2], 100);
        +      assert.equal(c._color.alpha, 0.8);
             });
         
             test('should correctly render color string', function() {
        -      assert.equal(c.toString(), 'rgba(255,0,102,0.8)');
        +      assert.equal(c.toString(), 'rgb(100% 0% 40% / 0.8)');
             });
           });
         
           suite('p5.Color in HSB mode', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSB);
        -      c = myp5.color(336, 100, 100);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSB);
        +      c = mockP5Prototype.color(336, 100, 100);
             });
        +
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
        +
             test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 255]);
        +      assert.deepEqual(c._color.coords, [336, 100, 100]);
        +      assert.equal(c._color.alpha, 1);
             });
        +
             test('can be modified with alpha setter', function() {
        -      var cc = myp5.color(336, 100, 100);
        +      var cc = mockP5Prototype.color(336, 100, 100);
               cc.setAlpha(0.73);
        -      assert.deepEqual(cc.levels, [255, 0, 102, 186]);
        +      assert.deepEqual(cc._color.coords, [336, 100, 100]);
        +      assert.equal(cc._color.alpha, 0.73);
             });
        +
             test('can be modified with rgb setters', function() {
        -      var cc = myp5.color(336, 100, 100);
        -      assert.deepEqual(cc.levels, [255, 0, 102, 255]);
        -      cc.setRed(98);
        -      assert.deepEqual(cc.levels, [98, 0, 102, 255]);
        -      cc.setGreen(44);
        -      assert.deepEqual(cc.levels, [98, 44, 102, 255]);
        -      cc.setBlue(244);
        -      assert.deepEqual(cc.levels, [98, 44, 244, 255]);
        +      var cc = mockP5Prototype.color(336, 100, 100);
        +      assert.deepEqual(cc._color.coords, [336, 100, 100]);
        +      assert.equal(cc._color.alpha, 1);
        +
        +      cc.setRed(98/255);
        +      assert.closeTo(cc._color.coords[0], 297, 1);
        +      assert.closeTo(cc._color.coords[1], 100, 1);
        +      assert.closeTo(cc._color.coords[2], 40, 1);
        +      assert.equal(cc._color.alpha, 1);
        +
        +      cc.setGreen(44/255);
        +      assert.closeTo(cc._color.coords[0], 295, 1);
        +      assert.closeTo(cc._color.coords[1], 56, 1);
        +      assert.closeTo(cc._color.coords[2], 40, 1);
        +      assert.equal(cc._color.alpha, 1);
        +
        +      cc.setBlue(244/255);
        +      assert.closeTo(cc._color.coords[0], 256, 1);
        +      assert.closeTo(cc._color.coords[1], 81, 1);
        +      assert.closeTo(cc._color.coords[2], 95, 1);
        +      assert.equal(cc._color.alpha, 1);
             });
           });
         
           suite('p5.Color in HSB mode with Alpha', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSB);
        -      c = myp5.color(336, 100, 100, 0.8);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSB);
        +      c = mockP5Prototype.color(336, 100, 100, 0.8);
             });
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
             test('should correctly set RGBA property', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +      assert.deepEqual(c._color.coords, [336, 100, 100]);
        +      assert.equal(c._color.alpha, 0.8);
             });
         
             test('should correctly get hue/saturation/brightness/alpha', function() {
        @@ -797,34 +598,37 @@ suite('p5.Color', function() {
           });
         
           suite('p5.Color in HSB mode with custom range', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSB, 100, 200, 300, 10);
        -      c = myp5.color(93.33, 200, 300, 8);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSB, 100, 200, 300, 10);
        +      c = mockP5Prototype.color(93.33, 200, 300, 8);
             });
         
             test('should correctly get HSBA property', function() {
        -      assert.approximately(c._getHue(), 93, 0.5);
        -      assert.approximately(c._getSaturation(), 200, 0.5);
        -      assert.approximately(c._getBrightness(), 300, 0.5);
        -      assert.approximately(c._getAlpha(), 8, 0.5);
        +      assert.approximately(c._getHue(), 336, 0.5);
        +      assert.approximately(c._getSaturation(), 100, 0.5);
        +      assert.approximately(c._getBrightness(), 100, 0.5);
        +      assert.approximately(c._getAlpha(), 0.8, 0.5);
             });
         
             test('should correctly convert to RGBA', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +      assert.closeTo(c._color.coords[0], 336, 1);
        +      assert.equal(c._color.coords[1], 100);
        +      assert.equal(c._color.coords[2], 100);
        +      assert.equal(c._color.alpha, 0.8);
             });
         
             test('should correctly render color string', function() {
        -      assert.equal(c.toString(), 'rgba(255,0,102,0.8)');
        +      assert.equal(c.toString(), 'rgb(100% 0% 40.02% / 0.8)');
             });
           });
         
           suite('p5.Color in HSB mode with RGB string', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSB, 360, 100, 100, 1);
        -      c = myp5.color('rgba(255, 0, 102, 0.8)');
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSB, 360, 100, 100, 1);
        +      c = mockP5Prototype.color('rgba(255, 0, 102, 0.8)');
             });
         
        -    test('should correctly get HSBA property', function() {
        +    test.todo('should correctly get HSBA property', function() {
               assert.approximately(c._getHue(), 336, 0.5);
               assert.approximately(c._getSaturation(), 100, 0.5);
               assert.approximately(c._getBrightness(), 100, 0.5);
        @@ -832,18 +636,19 @@ suite('p5.Color', function() {
             });
         
             test('should correctly convert to RGBA', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +      assert.deepEqual(c._color.coords, [1, 0, 0.4]);
        +      assert.equal(c._color.alpha, 0.8);
             });
         
             test('should correctly render color string', function() {
        -      assert.equal(c.toString(), 'rgba(255,0,102,0.8)');
        +      assert.equal(c.toString(), 'rgb(100% 0% 40% / 0.8)');
             });
           });
         
           suite('p5.Color in HSB mode with HSB string', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSB, 360, 100, 100, 1);
        -      c = myp5.color('hsba(336, 100%, 100%, 0.8)');
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSB, 360, 100, 100, 1);
        +      c = mockP5Prototype.color('hsba(336, 100%, 100%, 0.8)');
             });
         
             test('should correctly get HSBA property', function() {
        @@ -854,21 +659,24 @@ suite('p5.Color', function() {
             });
         
             test('should correctly convert to RGBA', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +      assert.equal(+c._color.coords[0], 336);
        +      assert.equal(c._color.coords[1], 100);
        +      assert.equal(c._color.coords[2], 100);
        +      assert.equal(c._color.alpha, 0.8);
             });
         
             test('should correctly render color string', function() {
        -      assert.equal(c.toString(), 'rgba(255,0,102,0.8)');
        +      assert.equal(c.toString(), 'rgb(100% 0% 40% / 0.8)');
             });
           });
         
           suite('p5.Color in HSB mode with HSL string', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSB, 360, 100, 100, 1);
        -      c = myp5.color('hsla(336, 100%, 50%, 0.8)');
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSB, 360, 100, 100, 1);
        +      c = mockP5Prototype.color('hsla(336, 100%, 50%, 0.8)');
             });
         
        -    test('should correctly get HSBA property', function() {
        +    test.todo('should correctly get HSBA property', function() {
               assert.approximately(c._getHue(), 336, 0.5);
               assert.approximately(c._getSaturation(), 100, 0.5);
               assert.approximately(c._getBrightness(), 100, 0.5);
        @@ -876,125 +684,122 @@ suite('p5.Color', function() {
             });
         
             test('should correctly convert to RGBA', function() {
        -      assert.deepEqual(c.levels, [255, 0, 102, 204]);
        +      assert.equal(+c._color.coords[0], 336);
        +      assert.equal(c._color.coords[1], 100);
        +      assert.equal(c._color.coords[2], 50);
        +      assert.equal(c._color.alpha, 0.8);
             });
         
             test('should correctly render color string', function() {
        -      assert.equal(c.toString(), 'rgba(255,0,102,0.8)');
        +      assert.equal(c.toString(), 'hsl(336 100% 50% / 0.8)');
             });
           });
         
           suite('p5.Color in RGB mode with grayscale value', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.RGB);
        -      c = myp5.color(100);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.RGB);
        +      c = mockP5Prototype.color(100);
             });
         
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
         
             test('should correctly set RGB levels', function() {
        -      assert.deepEqual(c.levels, [100, 100, 100, 255]);
        +      assert.deepEqual(c._color.coords, [100/255, 100/255, 100/255]);
        +      assert.equal(c._color.alpha, 1);
             });
           });
         
           suite('p5.Color in RGB mode with grayscale value and alpha', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.RGB);
        -      c = myp5.color(100, 70);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.RGB);
        +      c = mockP5Prototype.color(100, 70);
             });
         
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
         
             test('should correctly set RGB levels', function() {
        -      assert.deepEqual(c.levels, [100, 100, 100, 70]);
        +      assert.deepEqual(c._color.coords, [100/255, 100/255, 100/255]);
        +      assert.equal(c._color.alpha, 70/255);
             });
           });
         
           suite('p5.Color in HSB mode with grayscale value', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSB);
        -      c = myp5.color(39.3);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSB);
        +      c = mockP5Prototype.color(39.3);
             });
         
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
         
             test('should correctly set RGB levels', function() {
        -      assert.deepEqual(c.levels, [100, 100, 100, 255]);
        +      assert.deepEqual(c._color.coords, [39.3, 39.3, 39.3]);
        +      assert.equal(c._color.alpha, 1);
             });
           });
         
           suite('p5.Color in HSB mode with grayscale value and alpha', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSB);
        -      c = myp5.color(39.3, 0.275);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSB);
        +      c = mockP5Prototype.color(39.3, 0.275);
             });
         
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
         
             test('should correctly set RGB levels', function() {
        -      assert.deepEqual(c.levels, [100, 100, 100, 70]);
        +      assert.deepEqual(c._color.coords, [39.3, 39.3, 39.3]);
        +      assert.equal(c._color.alpha, 0.275);
             });
           });
         
           suite('p5.Color in HSL mode with grayscale value', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSL);
        -      c = myp5.color(39.3);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSL);
        +      c = mockP5Prototype.color(39.3);
             });
         
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
         
             test('should correctly set RGB levels', function() {
        -      assert.deepEqual(c.levels, [100, 100, 100, 255]);
        +      assert.deepEqual(c._color.coords, [39.3, 39.3, 39.3]);
        +      assert.equal(c._color.alpha, 1);
             });
           });
         
           suite('p5.Color in HSL mode with grayscale value and alpha', function() {
        -    setup(function() {
        -      myp5.colorMode(myp5.HSL);
        -      c = myp5.color(39.3, 0.275);
        +    beforeEach(function() {
        +      mockP5Prototype.colorMode(mockP5Prototype.HSL);
        +      c = mockP5Prototype.color(39.3, 0.275);
             });
         
             test('should create instance of p5.Color', function() {
        -      assert.instanceOf(c, p5.Color);
        +      assert.instanceOf(c, Color);
             });
         
             test('should correctly set RGB levels', function() {
        -      assert.deepEqual(c.levels, [100, 100, 100, 70]);
        +      assert.deepEqual(c._color.coords, [39.3, 39.3, 39.3]);
        +      assert.equal(c._color.alpha, 0.275);
             });
           });
         
        -  suite('p5.Color.prototype.toString', function() {
        -    var colorStr;
        +  suite.todo('p5.Color.prototype.toString', function() {
        +    // var colorStr;
         
        -    setup(function() {
        -      myp5.colorMode(myp5.RGB, 255, 255, 255, 255);
        -      c = myp5.color(128, 0, 128, 128);
        -      colorStr = c.toString();
        -    });
        +    // beforeEach(function() {
        +    //   mockP5Prototype.colorMode(mockP5Prototype.RGB, 255, 255, 255, 255);
        +    //   c = mockP5Prototype.color(128, 0, 128, 128);
        +    //   colorStr = c.toString();
        +    // });
         
        -    test('should generate (r,g,b,a) color string with 0-1 normalized alpha', function() {
        -      // Will not exactly equal 0.5 due to math: test "0.5" substr of
        -      // 'rgba(128,0,128,0.5...' instead of checking the entire string
        -      assert.equal(colorStr.slice(15, 18), '0.5');
        -    });
        -
        -    test('should consistently generate the same output', function() {
        -      assert.equal(colorStr, '' + c);
        -    });
        -
        -    test('should not mutate color levels', function() {
        -      assert.deepEqual(c.levels, [128, 0, 128, 128]);
        -    });
        +    // NOTE: need some tests here
           });
         });
        diff --git a/test/unit/color/setting.js b/test/unit/color/setting.js
        index a9d88f2c94..12a44f195d 100644
        --- a/test/unit/color/setting.js
        +++ b/test/unit/color/setting.js
        @@ -1,57 +1,109 @@
        +import p5 from '../../../src/app.js';
        +
        +import { vi } from 'vitest';
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import creatingReading from '../../../src/color/creating_reading';
        +import setting from '../../../src/color/setting';
        +
        +// NOTE: Require ESM compatible libtess
         suite('color/Setting', function() {
           let myp5; // sketch without WEBGL Mode
           let my3D; // sketch with WEBGL mode
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -      };
        -    });
        -    new p5(function(p) {
        -      p.setup = function() {
        -        p.createCanvas(100, 100, p.WEBGL);
        -        my3D = p;
        -      };
        -    });
        -    done();
        +
        +  beforeAll(() => {
        +    creatingReading(mockP5, mockP5Prototype);
        +    setting(mockP5, mockP5Prototype);
        +    // mockP5Prototype.states = {
        +    //   colorMode: 'rgb',
        +    //   colorMaxes: {
        +    //     rgb: [255, 255, 255, 255],
        +    //     hsb: [360, 100, 100, 1],
        +    //     hsl: [360, 100, 100, 1]
        +    //   }
        +    // }
           });
         
        -  teardown(function() {
        +  afterAll(() => {
        +    delete mockP5Prototype._colorMaxes;
        +  });
        +
        +  beforeEach(async function() {
        +    await new Promise(resolve => {
        +      new p5(function(p) {
        +        p.setup = function() {
        +          p.createCanvas(100, 100, p.WEBGL);
        +          my3D = p;
        +          resolve();
        +        };
        +      });
        +    });
        +
        +    await new Promise(resolve => {
        +      new p5(function(p) {
        +        p.setup = function() {
        +          myp5 = p;
        +          resolve();
        +        };
        +      });
        +    });
        +  });
        +
        +  afterEach(function() {
             myp5.remove();
             my3D.remove();
           });
         
           suite('p5.prototype.erase', function() {
        +    beforeEach(() => {
        +      mockP5Prototype._renderer = {
        +        erase: vi.fn(),
        +        states: {
        +          colorMode: 'rgb',
        +          colorMaxes: {
        +            rgb: [255, 255, 255, 255],
        +            hsb: [360, 100, 100, 1],
        +            hsl: [360, 100, 100, 1]
        +          }
        +        }
        +      }
        +    });
        +
        +    afterEach(() => {
        +      vi.resetAllMocks();
        +    });
        +
             test('should be a function', function() {
        -      assert.ok(myp5.erase);
        +      assert.ok(mockP5Prototype.erase);
             });
         
        -    test('should set renderer to erasing state', function() {
        -      myp5.erase();
        -      assert.isTrue(myp5._renderer._isErasing);
        +    test('should call the renderer erase method with default arguments', function() {
        +      mockP5Prototype.erase();
        +      expect(mockP5Prototype._renderer.erase).toHaveBeenCalledWith(255, 255);
             });
         
        -    test('should cache renderer fill', function() {
        +    // TODO: test in renderer
        +    test.todo('should set renderer to erasing state');
        +    test.todo('should cache renderer fill', function() {
               myp5.fill(255, 0, 0);
               const fillStyle = myp5.drawingContext.fillStyle;
               myp5.erase();
               assert.deepEqual(myp5._renderer._cachedFillStyle, fillStyle);
             });
         
        -    test('should cache renderer stroke', function() {
        +    test.todo('should cache renderer stroke', function() {
               myp5.stroke(255, 0, 0);
               const strokeStyle = myp5.drawingContext.strokeStyle;
               myp5.erase();
               assert.deepEqual(myp5._renderer._cachedStrokeStyle, strokeStyle);
             });
         
        -    test('should cache renderer blend', function() {
        +    test.todo('should cache renderer blend', function() {
               myp5.blendMode(myp5.SCREEN);
               myp5.erase();
               assert.deepEqual(myp5._renderer._cachedBlendMode, myp5.SCREEN);
             });
         
        -    test('should set fill strength', function() {
        +    test.todo('should set fill strength', function() {
               myp5.erase(125);
               assert.equal(
                 myp5.color(myp5.drawingContext.fillStyle).array,
        @@ -59,7 +111,7 @@ suite('color/Setting', function() {
               );
             });
         
        -    test('should set stroke strength', function() {
        +    test.todo('should set stroke strength', function() {
               myp5.erase(255, 50);
               assert.equal(
                 myp5.color(myp5.drawingContext.strokeStyle).array,
        @@ -74,16 +126,16 @@ suite('color/Setting', function() {
               assert.isTrue(my3D._renderer._isErasing);
             });
         
        -    test('should cache renderer fill', function() {
        +    test.todo('should cache renderer fill', function() {
               my3D.fill(255, 0, 0);
        -      const curFillColor = my3D._renderer.curFillColor;
        +      const curFillColor = my3D._renderer.states.curFillColor;
               my3D.erase();
               assert.deepEqual(my3D._renderer._cachedFillStyle, curFillColor);
             });
         
        -    test('should cache renderer stroke', function() {
        +    test.todo('should cache renderer stroke', function() {
               my3D.stroke(255, 0, 0);
        -      const strokeStyle = my3D._renderer.curStrokeColor;
        +      const strokeStyle = my3D._renderer.states.curStrokeColor;
               my3D.erase();
               assert.deepEqual(my3D._renderer._cachedStrokeStyle, strokeStyle);
             });
        @@ -96,24 +148,24 @@ suite('color/Setting', function() {
         
             test('should set fill strength', function() {
               my3D.erase(125);
        -      assert.deepEqual(my3D._renderer.curFillColor, [1, 1, 1, 125 / 255]);
        +      assert.deepEqual(my3D._renderer.states.curFillColor, [1, 1, 1, 125 / 255]);
             });
         
             test('should set stroke strength', function() {
               my3D.erase(255, 50);
        -      assert.deepEqual(my3D._renderer.curStrokeColor, [1, 1, 1, 50 / 255]);
        +      assert.deepEqual(my3D._renderer.states.curStrokeColor, [1, 1, 1, 50 / 255]);
             });
         
             test('should set default values when no arguments', function() {
               my3D.erase();
        -      assert.deepEqual(my3D._renderer.curFillColor, [1, 1, 1, 1]);
        -      assert.deepEqual(my3D._renderer.curStrokeColor, [1, 1, 1, 1]);
        +      assert.deepEqual(my3D._renderer.states.curFillColor, [1, 1, 1, 1]);
        +      assert.deepEqual(my3D._renderer.states.curStrokeColor, [1, 1, 1, 1]);
             });
           });
         
           suite('p5.prototype.noErase', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.noErase);
        +      assert.ok(mockP5Prototype.noErase);
             });
         
             test('should turn off renderer erasing state', function() {
        @@ -122,7 +174,7 @@ suite('color/Setting', function() {
               assert.isFalse(myp5._renderer._isErasing);
             });
         
        -    test('should restore cached renderer fill', function() {
        +    test.todo('should restore cached renderer fill', function() {
               myp5.fill(255, 0, 0);
               const fillStyle = myp5.drawingContext.fillStyle;
               myp5.erase();
        @@ -130,7 +182,7 @@ suite('color/Setting', function() {
               assert.deepEqual(myp5.drawingContext.fillStyle, fillStyle);
             });
         
        -    test('should restore cached renderer stroke', function() {
        +    test.todo('should restore cached renderer stroke', function() {
               myp5.stroke(255, 0, 0);
               const strokeStyle = myp5.drawingContext.strokeStyle;
               myp5.erase();
        @@ -146,17 +198,17 @@ suite('color/Setting', function() {
               assert.isFalse(my3D._renderer._isErasing);
             });
         
        -    test('should restore cached renderer fill', function() {
        +    test.todo('should restore cached renderer fill', function() {
               my3D.fill(255, 0, 0);
        -      const fillStyle = my3D._renderer.curFillColor.slice();
        +      const fillStyle = my3D._renderer.states.curFillColor.slice();
               my3D.erase();
               my3D.noErase();
               assert.deepEqual([1, 0, 0, 1], fillStyle);
             });
         
        -    test('should restore cached renderer stroke', function() {
        +    test.todo('should restore cached renderer stroke', function() {
               my3D.stroke(255, 0, 0);
        -      const strokeStyle = my3D._renderer.curStrokeColor.slice();
        +      const strokeStyle = my3D._renderer.states.curStrokeColor.slice();
               my3D.erase();
               my3D.noErase();
               assert.deepEqual([1, 0, 0, 1], strokeStyle);
        @@ -165,123 +217,123 @@ suite('color/Setting', function() {
         
           suite('p5.prototype.colorMode', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.colorMode);
        +      assert.ok(mockP5Prototype.colorMode);
             });
         
             test('should set mode to RGB', function() {
        -      myp5.colorMode(myp5.RGB);
        -      assert.equal(myp5._colorMode, myp5.RGB);
        +      mockP5Prototype.colorMode('rgb');
        +      assert.equal(mockP5Prototype._renderer.states.colorMode, 'rgb');
             });
         
             test('should correctly set color RGB maxes', function() {
        -      assert.deepEqual(myp5._colorMaxes[myp5.RGB], [255, 255, 255, 255]);
        -      myp5.colorMode(myp5.RGB, 1, 1, 1);
        -      assert.deepEqual(myp5._colorMaxes[myp5.RGB], [1, 1, 1, 255]);
        -      myp5.colorMode(myp5.RGB, 1);
        -      assert.deepEqual(myp5._colorMaxes[myp5.RGB], [1, 1, 1, 1]);
        -      myp5.colorMode(myp5.RGB, 255, 255, 255, 1);
        -      assert.deepEqual(myp5._colorMaxes[myp5.RGB], [255, 255, 255, 1]);
        -      myp5.colorMode(myp5.RGB, 255);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['rgb'], [255, 255, 255, 255]);
        +      mockP5Prototype.colorMode('rgb', 1, 1, 1);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['rgb'], [1, 1, 1, 255]);
        +      mockP5Prototype.colorMode('rgb', 1);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['rgb'], [1, 1, 1, 1]);
        +      mockP5Prototype.colorMode('rgb', 255, 255, 255, 1);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['rgb'], [255, 255, 255, 1]);
        +      mockP5Prototype.colorMode('rgb', 255);
             });
         
             test('should set mode to HSL', function() {
        -      myp5.colorMode(myp5.HSL);
        -      assert.equal(myp5._colorMode, myp5.HSL);
        +      mockP5Prototype.colorMode('hsl');
        +      assert.equal(mockP5Prototype._renderer.states.colorMode, 'hsl');
             });
         
             test('should correctly set color HSL maxes', function() {
        -      assert.deepEqual(myp5._colorMaxes[myp5.HSL], [360, 100, 100, 1]);
        -      myp5.colorMode(myp5.HSL, 255, 255, 255);
        -      assert.deepEqual(myp5._colorMaxes[myp5.HSL], [255, 255, 255, 1]);
        -      myp5.colorMode(myp5.HSL, 360);
        -      assert.deepEqual(myp5._colorMaxes[myp5.HSL], [360, 360, 360, 360]);
        -      myp5.colorMode(myp5.HSL, 360, 100, 100, 1);
        -      assert.deepEqual(myp5._colorMaxes[myp5.HSL], [360, 100, 100, 1]);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsl'], [360, 100, 100, 1]);
        +      mockP5Prototype.colorMode('hsl', 255, 255, 255);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsl'], [255, 255, 255, 1]);
        +      mockP5Prototype.colorMode('hsl', 360);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsl'], [360, 360, 360, 360]);
        +      mockP5Prototype.colorMode('hsl', 360, 100, 100, 1);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsl'], [360, 100, 100, 1]);
             });
         
             test('should set mode to HSB', function() {
        -      myp5.colorMode(myp5.HSB);
        -      assert.equal(myp5._colorMode, myp5.HSB);
        +      mockP5Prototype.colorMode('hsb');
        +      assert.equal(mockP5Prototype._renderer.states.colorMode, 'hsb');
             });
         
             test('should correctly set color HSB maxes', function() {
        -      assert.deepEqual(myp5._colorMaxes[myp5.HSB], [360, 100, 100, 1]);
        -      myp5.colorMode(myp5.HSB, 255, 255, 255);
        -      assert.deepEqual(myp5._colorMaxes[myp5.HSB], [255, 255, 255, 1]);
        -      myp5.colorMode(myp5.HSB, 360);
        -      assert.deepEqual(myp5._colorMaxes[myp5.HSB], [360, 360, 360, 360]);
        -      myp5.colorMode(myp5.HSB, 360, 100, 100, 1);
        -      assert.deepEqual(myp5._colorMaxes[myp5.HSB], [360, 100, 100, 1]);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsb'], [360, 100, 100, 1]);
        +      mockP5Prototype.colorMode('hsb', 255, 255, 255);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsb'], [255, 255, 255, 1]);
        +      mockP5Prototype.colorMode('hsb', 360);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsb'], [360, 360, 360, 360]);
        +      mockP5Prototype.colorMode('hsb', 360, 100, 100, 1);
        +      assert.deepEqual(mockP5Prototype._renderer.states.colorMaxes['hsb'], [360, 100, 100, 1]);
             });
           });
         
           suite('p5.Color components', function() {
             test('setRed() correctly sets red component', function() {
        -      myp5.colorMode(myp5.RGB, 255);
        -      const c = myp5.color(0, 162, 205, 255);
        +      mockP5Prototype.colorMode('rgb', 255);
        +      const c = mockP5Prototype.color(0, 162, 205, 255);
               c.setRed(100);
        -      assert.equal(myp5.red(c), 100);
        -      assert.equal(myp5.green(c), 162);
        -      assert.equal(myp5.blue(c), 205);
        -      assert.equal(myp5.alpha(c), 255);
        +      assert.equal(mockP5Prototype.red(c), 100);
        +      assert.equal(mockP5Prototype.green(c), 162);
        +      assert.equal(mockP5Prototype.blue(c), 205);
        +      assert.equal(mockP5Prototype.alpha(c), 255);
             });
         
             test('setGreen() correctly sets green component', function() {
        -      myp5.colorMode(myp5.RGB, 255);
        -      const c = myp5.color(0, 162, 205, 255);
        +      mockP5Prototype.colorMode('rgb', 255);
        +      const c = mockP5Prototype.color(0, 162, 205, 255);
               c.setGreen(100);
        -      assert.equal(myp5.red(c), 0);
        -      assert.equal(myp5.green(c), 100);
        -      assert.equal(myp5.blue(c), 205);
        -      assert.equal(myp5.alpha(c), 255);
        +      assert.equal(mockP5Prototype.red(c), 0);
        +      assert.equal(mockP5Prototype.green(c), 100);
        +      assert.equal(mockP5Prototype.blue(c), 205);
        +      assert.equal(mockP5Prototype.alpha(c), 255);
             });
         
             test('setBlue() correctly sets blue component', function() {
        -      myp5.colorMode(myp5.RGB, 255);
        -      const c = myp5.color(0, 162, 205, 255);
        +      mockP5Prototype.colorMode('rgb', 255);
        +      const c = mockP5Prototype.color(0, 162, 205, 255);
               c.setBlue(100);
        -      assert.equal(myp5.red(c), 0);
        -      assert.equal(myp5.green(c), 162);
        -      assert.equal(myp5.blue(c), 100);
        -      assert.equal(myp5.alpha(c), 255);
        +      assert.equal(mockP5Prototype.red(c), 0);
        +      assert.equal(mockP5Prototype.green(c), 162);
        +      assert.equal(mockP5Prototype.blue(c), 100);
        +      assert.equal(mockP5Prototype.alpha(c), 255);
             });
         
             test('setAlpha correctly sets alpha component', function() {
        -      myp5.colorMode(myp5.RGB, 255);
        -      const c = myp5.color(0, 162, 205, 255);
        +      mockP5Prototype.colorMode('rgb', 255);
        +      const c = mockP5Prototype.color(0, 162, 205, 255);
               c.setAlpha(100);
        -      assert.equal(myp5.red(c), 0);
        -      assert.equal(myp5.green(c), 162);
        -      assert.equal(myp5.blue(c), 205);
        -      assert.equal(myp5.alpha(c), 100);
        +      assert.equal(mockP5Prototype.red(c), 0);
        +      assert.equal(mockP5Prototype.green(c), 162);
        +      assert.equal(mockP5Prototype.blue(c), 205);
        +      assert.equal(mockP5Prototype.alpha(c), 100);
             });
         
             test('changing the red/green/blue/alpha components should clear the cached HSL/HSB values', function() {
        -      myp5.colorMode(myp5.RGB, 255);
        -      const c = myp5.color(0, 162, 205, 255);
        +      mockP5Prototype.colorMode('rgb', 255);
        +      const c = mockP5Prototype.color(0, 162, 205, 255);
         
               // create HSL/HSB values
        -      myp5.lightness(c);
        -      myp5.brightness(c);
        -      c.setRed(100);
        +      mockP5Prototype.lightness(c);
        +      mockP5Prototype.brightness(c);
        +      c.setRed(100/255);
               assert(!c.hsba);
               assert(!c.hsla);
         
        -      myp5.lightness(c);
        -      myp5.brightness(c);
        -      c.setGreen(100);
        +      mockP5Prototype.lightness(c);
        +      mockP5Prototype.brightness(c);
        +      c.setGreen(100/255);
               assert(!c.hsba);
               assert(!c.hsla);
         
        -      myp5.lightness(c);
        -      myp5.brightness(c);
        -      c.setBlue(100);
        +      mockP5Prototype.lightness(c);
        +      mockP5Prototype.brightness(c);
        +      c.setBlue(100/255);
               assert(!c.hsba);
               assert(!c.hsla);
         
        -      myp5.lightness(c);
        -      myp5.brightness(c);
        -      c.setAlpha(100);
        +      mockP5Prototype.lightness(c);
        +      mockP5Prototype.brightness(c);
        +      c.setAlpha(100/255);
               assert(!c.hsba);
               assert(!c.hsla);
             });
        diff --git a/test/unit/core/2d_primitives.js b/test/unit/core/2d_primitives.js
        index 79903a0556..a85e07ae87 100644
        --- a/test/unit/core/2d_primitives.js
        +++ b/test/unit/core/2d_primitives.js
        @@ -1,283 +1,63 @@
        -suite('2D Primitives', function() {
        -  var myp5;
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import primitives from '../../../src/shape/2d_primitives';
         
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        -
        -  teardown(function() {
        -    myp5.remove();
        +suite('2D Primitives', function() {
        +  beforeAll(function() {
        +    primitives(mockP5, mockP5Prototype);
           });
         
           suite('p5.prototype.arc', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.arc);
        -      assert.typeOf(myp5.arc, 'function');
        -    });
        -    test('no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.arc(1, 1, 10.5, 10, 0, Math.PI, 'pie');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('missing param #4, #5', function() {
        -      assert.validationError(function() {
        -        myp5.arc(1, 1, 10.5, 10);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.arc('a', 1, 10.5, 10, 0, Math.PI, 'pie');
        -      });
        +      assert.ok(mockP5Prototype.arc);
        +      assert.typeOf(mockP5Prototype.arc, 'function');
             });
           });
         
           suite('p5.prototype.ellipse', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.ellipse);
        -      assert.typeOf(myp5.ellipse, 'function');
        -    });
        -    test('no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.ellipse(0, 0, 100);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('missing param #2', function() {
        -      assert.validationError(function() {
        -        myp5.ellipse(0, 0);
        -      });
        -    });
        -    test('missing param #2', function() {
        -      assert.validationError(function() {
        -        var size;
        -        myp5.ellipse(0, 0, size);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.ellipse('a', 0, 100, 100);
        -      });
        +      assert.ok(mockP5Prototype.ellipse);
        +      assert.typeOf(mockP5Prototype.ellipse, 'function');
             });
           });
         
           suite('p5.prototype.line', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.line);
        -      assert.typeOf(myp5.line, 'function');
        -    });
        -    test('no friendly-err-msg, 2D', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.line(0, 0, 100, 100);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg, 3D', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.line(0, 0, 100, 100, 20, Math.PI);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('missing param #3', function() {
        -      assert.validationError(function() {
        -        myp5.line(0, 0, Math.PI);
        -      });
        -    });
        -    test('missing param #4 ', function() {
        -      // this err case escapes
        -      assert.validationError(function() {
        -        var x3;
        -        myp5.line(0, 0, 100, 100, x3, Math.PI);
        -      });
        -    });
        -    test('wrong param type at #1', function() {
        -      assert.validationError(function() {
        -        myp5.line(0, 'a', 100, 100);
        -      });
        +      assert.ok(mockP5Prototype.line);
        +      assert.typeOf(mockP5Prototype.line, 'function');
             });
           });
         
           suite('p5.prototype.point', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.point);
        -      assert.typeOf(myp5.point, 'function');
        -    });
        -    test('no friendly-err-msg, 2D', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.point(Math.PI, 0);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg, 3D', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.point(Math.PI, 0, 100);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('missing param #1', function() {
        -      assert.validationError(function() {
        -        myp5.point(0);
        -      });
        -    });
        -    /* this is not an error because 2d point exists.
        -    test('missing param #3', function() {
        -      // this err case escapes
        -      assert.validationError(function() {
        -        var z;
        -        myp5.point(0, Math.PI, z);
        -      });
        -    });
        -    */
        -    test('wrong param type at #1', function() {
        -      assert.validationError(function() {
        -        myp5.point(Math.PI, 'a');
        -      });
        +      assert.ok(mockP5Prototype.point);
        +      assert.typeOf(mockP5Prototype.point, 'function');
             });
           });
         
           suite('p5.prototype.quad', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.quad);
        -      assert.typeOf(myp5.quad, 'function');
        -    });
        -    test('no friendly-err-msg, 2D', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.quad(Math.PI, 0, Math.PI, 5.1, 10, 5.1, 10, 0);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('missing param #7', function() {
        -      assert.validationError(function() {
        -        myp5.quad(Math.PI, 0, Math.PI, 5.1, 10, 5.1, 10);
        -      });
        -    });
        -    test('wrong param type at #1', function() {
        -      assert.validationError(function() {
        -        myp5.quad(Math.PI, 'a', Math.PI, 5.1, 10, 5.1, 10, 0);
        -      });
        +      assert.ok(mockP5Prototype.quad);
        +      assert.typeOf(mockP5Prototype.quad, 'function');
             });
           });
         
           suite('p5.prototype.rect', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.rect);
        -      assert.typeOf(myp5.rect, 'function');
        -    });
        -    test('no friendly-err-msg, format I', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.rect(0, 0, 100);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg, format II', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.rect(0, 0, 100, 100, 1, Math.PI, 1, Math.PI);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('missing param #4', function() {
        -      // this err case escapes
        -      assert.validationError(function() {
        -        var r1;
        -        myp5.rect(0, 0, 100, 100, r1, Math.PI, 1, Math.PI);
        -      });
        -    });
        -    test('wrong param type at #1', function() {
        -      assert.validationError(function() {
        -        myp5.rect(0, 'a', 100, 100);
        -      });
        +      assert.ok(mockP5Prototype.rect);
        +      assert.typeOf(mockP5Prototype.rect, 'function');
             });
           });
         
           suite('p5.prototype.triangle', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.triangle);
        -      assert.typeOf(myp5.triangle, 'function');
        -    });
        -    test('no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.triangle(Math.PI, 0, Math.PI, 5.1, 10, 5.1);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('missing param #5', function() {
        -      assert.validationError(function() {
        -        myp5.triangle(Math.PI, 0, Math.PI, 5.1, 10);
        -      });
        -    });
        -    test('wrong param type at #1', function() {
        -      assert.validationError(function() {
        -        myp5.triangle(Math.PI, 'a', Math.PI, 5.1, 10, 5.1);
        -      });
        +      assert.ok(mockP5Prototype.triangle);
        +      assert.typeOf(mockP5Prototype.triangle, 'function');
             });
           });
           suite('p5.prototype.square', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.square);
        -      assert.typeOf(myp5.square, 'function');
        -    });
        -    test('no friendly-err-msg, format I', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.square(0, 0, 100);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg, format II', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.square(0, 0, 100, 100, Math.PI);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('missing param #2', function() {
        -      assert.validationError(function() {
        -        myp5.square(0, 0);
        -      });
        -    });
        -    test('wrong param type at #1', function() {
        -      assert.validationError(function() {
        -        myp5.square(0, 'a', 100);
        -      });
        +      assert.ok(mockP5Prototype.square);
        +      assert.typeOf(mockP5Prototype.square, 'function');
             });
           });
         
        @@ -286,7 +66,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * i,
                     2 * Math.PI * j,
                     500,
        @@ -303,7 +83,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * i + 1,
                     2 * Math.PI * j + 1,
                     500,
        @@ -320,7 +100,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * i - 0.000001,
                     2 * Math.PI * j + 0.000001,
                     500,
        @@ -337,7 +117,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * i + 0.000001,
                     2 * Math.PI * j - 0.000001,
                     500,
        @@ -354,7 +134,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * i + 0.999999,
                     2 * Math.PI * j + 1.000001,
                     500,
        @@ -371,7 +151,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * i + 1.000001,
                     2 * Math.PI * j + 0.999999,
                     500,
        @@ -388,7 +168,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * i - 0.1,
                     2 * Math.PI * j + 0.1,
                     500,
        @@ -405,7 +185,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * i + 0.1,
                     2 * Math.PI * j - 0.1,
                     500,
        @@ -422,7 +202,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * i + 0.9,
                     2 * Math.PI * j + 1.1,
                     500,
        @@ -439,7 +219,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * i + 1.1,
                     2 * Math.PI * j + 0.9,
                     500,
        @@ -456,7 +236,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * (i + 40 / 360),
                     2 * Math.PI * (j + 230 / 360),
                     500,
        @@ -473,7 +253,7 @@ suite('2D Primitives', function() {
               var i, j, angles;
               for (i = -2; i <= 2; i++) {
                 for (j = -2; j <= 2; j++) {
        -          angles = myp5._normalizeArcAngles(
        +          angles = mockP5Prototype._normalizeArcAngles(
                     2 * Math.PI * (i + 320 / 360),
                     2 * Math.PI * (j + 130 / 360),
                     500,
        diff --git a/test/unit/core/attributes.js b/test/unit/core/attributes.js
        index 14e741f340..6cd0f416e2 100644
        --- a/test/unit/core/attributes.js
        +++ b/test/unit/core/attributes.js
        @@ -1,115 +1,57 @@
        -suite('Attributes', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import attributes from '../../../src/shape/attributes';
         
        -  teardown(function() {
        -    myp5.remove();
        +suite('Attributes', function() {
        +  beforeAll(function() {
        +    attributes(mockP5, mockP5Prototype);
           });
         
           suite('p5.prototype.ellipseMode', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.ellipseMode);
        -      assert.typeOf(myp5.ellipseMode, 'function');
        -    });
        -    test('missing param #0', function() {
        -      assert.validationError(function() {
        -        myp5.ellipseMode();
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.ellipseMode(myp5.BEVEL);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.ellipseMode(20);
        -      });
        +      assert.ok(mockP5Prototype.ellipseMode);
        +      assert.typeOf(mockP5Prototype.ellipseMode, 'function');
             });
           });
         
           suite('p5.prototype.rectMode', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.rectMode);
        -      assert.typeOf(myp5.rectMode, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.rectMode(myp5.MITER);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.rectMode(64);
        -      });
        +      assert.ok(mockP5Prototype.rectMode);
        +      assert.typeOf(mockP5Prototype.rectMode, 'function');
             });
           });
         
           suite('p5.prototype.noSmooth', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.noSmooth);
        -      assert.typeOf(myp5.noSmooth, 'function');
        +      assert.ok(mockP5Prototype.noSmooth);
        +      assert.typeOf(mockP5Prototype.noSmooth, 'function');
             });
           });
         
           suite('p5.prototype.smooth', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.smooth);
        -      assert.typeOf(myp5.smooth, 'function');
        +      assert.ok(mockP5Prototype.smooth);
        +      assert.typeOf(mockP5Prototype.smooth, 'function');
             });
           });
         
           suite('p5.prototype.strokeCap', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.strokeCap);
        -      assert.typeOf(myp5.strokeCap, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.strokeCap(myp5.CORNER);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.strokeCap(40);
        -      });
        +      assert.ok(mockP5Prototype.strokeCap);
        +      assert.typeOf(mockP5Prototype.strokeCap, 'function');
             });
           });
         
           suite('p5.prototype.strokeJoin', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.strokeJoin);
        -      assert.typeOf(myp5.strokeJoin, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.strokeJoin(myp5.CORNER);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.strokeJoin(35);
        -      });
        +      assert.ok(mockP5Prototype.strokeJoin);
        +      assert.typeOf(mockP5Prototype.strokeJoin, 'function');
             });
           });
         
           suite('p5.prototype.strokeWeight', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.strokeWeight);
        -      assert.typeOf(myp5.strokeWeight, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.strokeWeight('a');
        -      });
        +      assert.ok(mockP5Prototype.strokeWeight);
        +      assert.typeOf(mockP5Prototype.strokeWeight, 'function');
             });
           });
         });
        diff --git a/test/unit/core/curves.js b/test/unit/core/curves.js
        index d88c53ac8c..4840e273b1 100644
        --- a/test/unit/core/curves.js
        +++ b/test/unit/core/curves.js
        @@ -1,58 +1,37 @@
        -suite('Curves', function() {
        -  var myp5;
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import curves from '../../../src/shape/curves';
         
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        +suite('Curves', function() {
        +  beforeAll(function() {
        +    mockP5Prototype._renderer = {
        +      states: {
        +        splineProperties: {
        +          tightness: 0
        +        }
        +      }
        +    };
        +    curves(mockP5, mockP5Prototype);
           });
         
        -  teardown(function() {
        -    myp5.remove();
        +  afterAll(() => {
        +    delete mockP5Prototype._renderer;
           });
         
           suite('p5.prototype.bezier', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.bezier);
        -      assert.typeOf(myp5.bezier, 'function');
        -    });
        -    test('no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.bezier(85, 20, 10, 10, 90, 90, 15, 80);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg. missing param #6, #7', function() {
        -      assert.validationError(function() {
        -        myp5.bezier(85, 20, 10, 10, 90, 90);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.bezier('a', 20, 10, 10, 90, 90, 15, 80);
        -      });
        +      assert.ok(mockP5Prototype.bezier);
        +      assert.typeOf(mockP5Prototype.bezier, 'function');
             });
           });
         
           suite('p5.prototype.bezierPoint', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.bezierPoint);
        -      assert.typeOf(myp5.bezierPoint, 'function');
        -    });
        -    test('should return a number: missing param #0~4', function() {
        -      assert.validationError(function() {
        -        result = myp5.bezierPoint();
        -      });
        +      assert.ok(mockP5Prototype.bezierPoint);
        +      assert.typeOf(mockP5Prototype.bezierPoint, 'function');
             });
             test('should return the correct point on a Bezier Curve', function() {
        -      result = myp5.bezierPoint(85, 10, 90, 15, 0.5);
        +      result = mockP5Prototype.bezierPoint(85, 10, 90, 15, 0.5);
               assert.equal(result, 50);
               assert.notEqual(result, -1);
             });
        @@ -61,59 +40,30 @@ suite('Curves', function() {
           suite('p5.prototype.bezierTangent', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.bezierTangent);
        -      assert.typeOf(myp5.bezierTangent, 'function');
        -    });
        -    test('should return a number: missing param #0~4', function() {
        -      assert.validationError(function() {
        -        result = myp5.bezierTangent();
        -      });
        +      assert.ok(mockP5Prototype.bezierTangent);
        +      assert.typeOf(mockP5Prototype.bezierTangent, 'function');
             });
             test('should return the correct point on a Bezier Curve', function() {
        -      result = myp5.bezierTangent(95, 73, 73, 15, 0.5);
        +      result = mockP5Prototype.bezierTangent(95, 73, 73, 15, 0.5);
               assert.equal(result, -60);
             });
           });
         
           suite('p5.prototype.curve', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.curve);
        -      assert.typeOf(myp5.curve, 'function');
        -    });
        -    test('no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.curve(5, 26, 5, 26, 73, 24, 73, 61);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg. missing param #6, #7', function() {
        -      assert.validationError(function() {
        -        myp5.curve(5, 26, 5, 26, 73, 24);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.curve('a', 26, 5, 26, 73, 24, 73, 61);
        -      });
        +      assert.ok(mockP5Prototype.curve);
        +      assert.typeOf(mockP5Prototype.curve, 'function');
             });
           });
         
           suite('p5.prototype.curvePoint', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.curvePoint);
        -      assert.typeOf(myp5.curvePoint, 'function');
        -    });
        -    test('should return a number: missing param #0~4', function() {
        -      assert.validationError(function() {
        -        result = myp5.curvePoint();
        -      });
        +      assert.ok(mockP5Prototype.curvePoint);
        +      assert.typeOf(mockP5Prototype.curvePoint, 'function');
             });
             test('should return the correct point on a Catmull-Rom Curve', function() {
        -      result = myp5.curvePoint(5, 5, 73, 73, 0.5);
        +      result = mockP5Prototype.curvePoint(5, 5, 73, 73, 0.5);
               assert.equal(result, 39);
               assert.notEqual(result, -1);
             });
        @@ -122,16 +72,11 @@ suite('Curves', function() {
           suite('p5.prototype.curveTangent', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.curveTangent);
        -      assert.typeOf(myp5.curveTangent, 'function');
        -    });
        -    test('should return a number: missing param #0~4', function() {
        -      assert.validationError(function() {
        -        result = myp5.curveTangent();
        -      });
        +      assert.ok(mockP5Prototype.curveTangent);
        +      assert.typeOf(mockP5Prototype.curveTangent, 'function');
             });
             test('should return the correct point on a Catmull-Rom Curve', function() {
        -      result = myp5.curveTangent(95, 73, 73, 15, 0.5);
        +      result = mockP5Prototype.curveTangent(95, 73, 73, 15, 0.5);
               assert.equal(result, 10);
               assert.notEqual(result, -1);
             });
        diff --git a/test/unit/core/environment.js b/test/unit/core/environment.js
        index 4ab600a0e9..d7d758e1a1 100644
        --- a/test/unit/core/environment.js
        +++ b/test/unit/core/environment.js
        @@ -1,16 +1,18 @@
        +import p5 from '../../../src/app.js';
        +import { vi } from 'vitest';
        +
         suite('Environment', function() {
           var myp5;
         
        -  setup(function(done) {
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -94,7 +96,7 @@ suite('Environment', function() {
           });
         
           suite('p5.prototype.frameRate', function() {
        -    test('returns 0 on first draw call', function() {
        +    test.todo('returns 0 on first draw call', function() {
               assert.strictEqual(myp5.frameRate(), 0);
             });
         
        @@ -111,25 +113,19 @@ suite('Environment', function() {
               });
             });
         
        -    test('wrong param type. throws error.', function() {
        -      assert.validationError(function() {
        -        myp5.frameRate('a');
        -      });
        -    });
        -
        -    test('p5.prototype.getFrameRate', function() {
        +    test.todo('p5.prototype.getFrameRate', function() {
               assert.strictEqual(myp5.getFrameRate(), 0);
             });
         
        -    suite('drawing with target frame rates', function() {
        +    suite.todo('drawing with target frame rates', function() {
               let clock;
               let prevRequestAnimationFrame;
               let nextFrameCallback = () => {};
               let controlledP5;
         
        -      setup(function() {
        -        clock = sinon.useFakeTimers(0);
        -        sinon.stub(window.performance, 'now', Date.now);
        +      beforeEach(function() {
        +        clock = vi.useFakeTimers(0);
        +        vi.spyOn(window.performance, 'now', Date.now);
         
                 // Save the real requestAnimationFrame so we can restore it later
                 prevRequestAnimationFrame = window.requestAnimationFrame;
        @@ -153,8 +149,9 @@ suite('Environment', function() {
                 });
               });
         
        -      teardown(function() {
        -        clock.restore();
        +      afterEach(function() {
        +        // clock.restore();
        +        vi.restoreAllMocks();
                 window.performance.now.restore();
                 window.requestAnimationFrame = prevRequestAnimationFrame;
                 nextFrameCallback = function() {};
        @@ -162,7 +159,7 @@ suite('Environment', function() {
               });
         
               test('draw() is called at the correct frame rate given a faster display', function() {
        -        sinon.spy(controlledP5, 'draw');
        +        vi.spyOn(controlledP5, 'draw');
         
                 clock.tick(1000 / 200); // Simulate a 200Hz refresh rate
                 nextFrameCallback(); // trigger the next requestAnimationFrame
        @@ -226,12 +223,6 @@ suite('Environment', function() {
               myp5.pixelDensity(2);
               assert.strictEqual(myp5.pixelDensity(), 2);
             });
        -
        -    test('wrong param type. throws validationError.', function() {
        -      assert.validationError(function() {
        -        myp5.pixelDensity('a');
        -      });
        -    });
           });
         
           suite('p5.prototype.displayDensity', function() {
        @@ -245,4 +236,61 @@ suite('Environment', function() {
               assert.isNumber(myp5.displayDensity(), pd);
             });
           });
        +
        +  suite('2D context test', function() {
        +    beforeEach(function() {
        +      myp5.createCanvas(100, 100);
        +    });
        +
        +    test('worldToScreen for 2D context', function() {
        +      let worldPos = myp5.createVector(50, 50);
        +      let screenPos = myp5.worldToScreen(worldPos);
        +      assert.closeTo(screenPos.x, 50, 0.1);
        +      assert.closeTo(screenPos.y, 50, 0.1);
        +    });
        +
        +    test('worldToScreen with rotation in 2D', function() {
        +      myp5.push();
        +      myp5.translate(50, 50);
        +      myp5.rotate(myp5.PI / 2);
        +      let worldPos = myp5.createVector(10, 0);
        +      let screenPos = myp5.worldToScreen(worldPos);
        +      myp5.pop();
        +      assert.closeTo(screenPos.x, 50, 0.1);
        +      assert.closeTo(screenPos.y, 60, 0.1);
        +    });
        +  });
        +
        +  suite('3D context test', function() {
        +    beforeEach(function() {
        +      myp5.createCanvas(100, 100, myp5.WEBGL);
        +    });
        +
        +    test('worldToScreen for 3D context', function() {
        +      let worldPos = myp5.createVector(0, 0, 0);
        +      let screenPos = myp5.worldToScreen(worldPos);
        +      assert.closeTo(screenPos.x, 50, 0.1);
        +      assert.closeTo(screenPos.y, 50, 0.1);
        +    });
        +
        +    test('worldToScreen with rotation in 3D around Y-axis', function() {
        +      myp5.push();
        +      myp5.rotateY(myp5.PI / 2);
        +      let worldPos = myp5.createVector(50, 0, 0);
        +      let screenPos = myp5.worldToScreen(worldPos);
        +      myp5.pop();
        +      assert.closeTo(screenPos.x, 50, 0.1);
        +      assert.closeTo(screenPos.y, 50, 0.1);
        +    });
        +
        +    test('worldToScreen with rotation in 3D around Z-axis', function() {
        +      myp5.push();
        +      myp5.rotateZ(myp5.PI / 2);
        +      let worldPos = myp5.createVector(10, 0, 0);
        +      let screenPos = myp5.worldToScreen(worldPos);
        +      myp5.pop();
        +      assert.closeTo(screenPos.x, 50, 0.1);
        +      assert.closeTo(screenPos.y, 60, 0.1);
        +    });
        +  });
         });
        diff --git a/test/unit/core/error_helpers.js b/test/unit/core/error_helpers.js
        deleted file mode 100644
        index 75ad06e31f..0000000000
        --- a/test/unit/core/error_helpers.js
        +++ /dev/null
        @@ -1,1158 +0,0 @@
        -suite('Error Helpers', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        p5._clearValidateParamsCache();
        -        done();
        -      };
        -    });
        -  });
        -
        -  teardown(function() {
        -    myp5.remove();
        -  });
        -
        -  suite('friendly error logger', function() {
        -    test('basic', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          p5._friendlyError('basic', 'basic');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -  });
        -
        -  // unit tests for validateParameters
        -  suite('validateParameters: Numbers + optional Constant', function() {
        -    test('arc(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          p5._validateParameters('arc', [1, 1, 10.5, 10, 0, Math.PI, 'pie']);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('arc(): missing param #4, #5', function() {
        -      assert.validationError(function() {
        -        p5._validateParameters('arc', [1, 1, 10.5, 10]);
        -      });
        -    });
        -    test('arc(): missing param #0', function() {
        -      assert.validationError(function() {
        -        p5._validateParameters('arc', [
        -          undefined,
        -          1,
        -          10.5,
        -          10,
        -          0,
        -          Math.PI,
        -          'pie'
        -        ]);
        -      });
        -    });
        -    test('arc(): missing param #4', function() {
        -      assert.validationError(function() {
        -        p5._validateParameters('arc', [
        -          1,
        -          1,
        -          10.5,
        -          10,
        -          undefined,
        -          Math.PI,
        -          'pie'
        -        ]);
        -      });
        -    });
        -    test('arc(): missing param #5', function() {
        -      assert.validationError(function() {
        -        p5._validateParameters('arc', [1, 1, 10.5, 10, 0, undefined, 'pie']);
        -      });
        -    });
        -    test('arc(): missing param #6, no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          p5._validateParameters('arc', [1, 1, 10.5, 10, 0, Math.PI]);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('arc(): wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        p5._validateParameters('arc', ['a', 1, 10.5, 10, 0, Math.PI, 'pie']);
        -      });
        -    });
        -  });
        -
        -  suite('validateParameters: Numbers + optional Constant', function() {
        -    test('rect(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          p5._validateParameters('rect', [1, 1, 10.5, 10]);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('rect(): wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        p5._validateParameters('rect', ['a', 1, 10.5, 10, 0, Math.PI]);
        -      });
        -    });
        -  });
        -
        -  suite(
        -    'validateParameters: class, multi-types + optional Numbers',
        -    function() {
        -      test('ambientLight(): no friendly-err-msg', function() {
        -        assert.doesNotThrow(
        -          function() {
        -            var c = myp5.color(255, 204, 0);
        -            p5._validateParameters('ambientLight', [c]);
        -          },
        -          Error,
        -          'got unwanted exception'
        -        );
        -      });
        -    }
        -  );
        -
        -  suite('validateParameters: a few edge cases', function() {
        -    // testing for edge cases mentioned in
        -    // https://github.com/processing/p5.js/issues/2740
        -    testUnMinified('color: wrong type for optional parameter', function() {
        -      let err = assert.throws(function() {
        -        p5._validateParameters('color', [0, 0, 0, 'A']);
        -      }, p5.ValidationError);
        -      assert.strictEqual(
        -        err.type,
        -        'WRONG_TYPE',
        -        'ValidationError type is correct'
        -      );
        -    });
        -
        -    testUnMinified('color: superfluous parameter', function() {
        -      assert.throws(function() {
        -        p5._validateParameters('color', [[0, 0, 0], 0]);
        -      }, p5.ValidationError);
        -      // not performing a type check here as it could reasonably fit as
        -      // either WRONG_TYPE or TOO_MANY_ARGUMENTS
        -    });
        -
        -    testUnMinified('color: wrong element types', function() {
        -      let err = assert.throws(function() {
        -        p5._validateParameters('color', [['A', 'B', 'C']]);
        -      }, p5.ValidationError);
        -      assert.strictEqual(
        -        err.type,
        -        'WRONG_TYPE',
        -        'ValidationError type is correct'
        -      );
        -    });
        -
        -    testUnMinified('rect: null, non-trailing, optional parameter', function() {
        -      let err = assert.throws(function() {
        -        p5._validateParameters('rect', [0, 0, 0, 0, null, 0, 0, 0]);
        -      }, p5.ValidationError);
        -      assert.strictEqual(
        -        err.type,
        -        'EMPTY_VAR',
        -        'ValidationError type is correct'
        -      );
        -    });
        -
        -    testUnMinified('color: too many args + wrong types too', function() {
        -      let err = assert.throws(function() {
        -        p5._validateParameters('color', ['A', 'A', 0, 0, 0, 0, 0, 0, 0, 0]);
        -      }, p5.ValidationError);
        -      assert.strictEqual(
        -        err.type,
        -        'TOO_MANY_ARGUMENTS',
        -        'ValidationError type is correct'
        -      );
        -    });
        -
        -    testUnMinified('line: null string given', function() {
        -      let err = assert.throws(function() {
        -        p5._validateParameters('line', [1, 2, 4, 'null']);
        -      }, p5.ValidationError);
        -      assert.strictEqual(
        -        err.type,
        -        'WRONG_TYPE',
        -        'ValidationError type is correct'
        -      );
        -    });
        -
        -    testUnMinified('line: NaN value given', function() {
        -      let err = assert.throws(function() {
        -        p5._validateParameters('line', [1, 2, 4, NaN]);
        -      }, p5.ValidationError);
        -      assert.strictEqual(
        -        err.type,
        -        'WRONG_TYPE',
        -        'ValidationError type is correct'
        -      );
        -    });
        -  });
        -
        -  suite('validateParameters: trailing undefined arguments', function() {
        -    // see https://github.com/processing/p5.js/issues/4571 for details
        -
        -    test('color: missing params #1 #2', function() {
        -      // even though color can also be called with one argument, if 3 args
        -      // are passed, it is likely that them being undefined is an accident
        -      assert.validationError(function() {
        -        p5._validateParameters('color', [12, undefined, undefined]);
        -      });
        -    });
        -
        -    test('random: missing params #0 #1 (both optional)', function() {
        -      // even though the undefined params are optional, since they are passed
        -      // to the function, it is more likely that the user wanted to call the
        -      // function with 2 arguments.
        -      assert.validationError(function() {
        -        p5._validateParameters('random', [undefined, undefined]);
        -      });
        -    });
        -
        -    // compuslory argument trailing undefined
        -    testUnMinified('circle: missing compulsory param #2', function() {
        -      // should throw an EMPTY_VAR error instead of a TOO_FEW_ARGUMENTS error
        -      let err = assert.throws(function() {
        -        p5._validateParameters('circle', [5, 5, undefined]);
        -      }, p5.ValidationError);
        -      assert.strictEqual(
        -        err.type,
        -        'EMPTY_VAR',
        -        'ValidationError type is correct'
        -      );
        -    });
        -  });
        -
        -  suite('validateParameters: argument tree', function() {
        -    // should not throw a validation error for the same kind of wrong args
        -    // more than once. This prevents repetetive validation logs for a
        -    // function that is called in a loop or draw()
        -    testUnMinified(
        -      'no repeated validation error for the same wrong arguments',
        -      function() {
        -        assert.validationError(function() {
        -          myp5.color();
        -        });
        -
        -        assert.doesNotThrow(
        -          function() {
        -            myp5.color(); // Same type of wrong arguments as above
        -          },
        -          p5.ValidationError,
        -          'got unwanted ValidationError'
        -        );
        -      }
        -    );
        -
        -    testUnMinified(
        -      'should throw validation errors for different wrong args',
        -      function() {
        -        assert.validationError(function() {
        -          myp5.color();
        -        });
        -
        -        assert.validationError(function() {
        -          myp5.color(false);
        -        });
        -      }
        -    );
        -
        -    testUnMinified('arg tree is built properly', function() {
        -      let myArgTree = p5._getValidateParamsArgTree();
        -      myp5.random();
        -      myp5.random(50);
        -      myp5.random([50, 70, 10]);
        -      assert.strictEqual(
        -        myArgTree.random.seen,
        -        true,
        -        'tree built correctly for random()'
        -      );
        -      assert.strictEqual(
        -        myArgTree.random.number.seen,
        -        true,
        -        'tree built correctly for random(min: Number)'
        -      );
        -      assert.strictEqual(
        -        myArgTree.random.as.number.number.number.seen,
        -        true,
        -        'tree built correctly for random(choices: Array)'
        -      );
        -
        -      let c = myp5.color(10);
        -      myp5.alpha(c);
        -      assert.strictEqual(
        -        myArgTree.color.number.seen,
        -        true,
        -        'tree built correctly for color(gray: Number)'
        -      );
        -      assert.strictEqual(
        -        myArgTree.alpha.Color.seen,
        -        true,
        -        'tree built correctly for alpha(color: p5.Color)'
        -      );
        -    });
        -  });
        -
        -  suite('validateParameters: multi-format', function() {
        -    test('color(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          p5._validateParameters('color', [65]);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('color(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          p5._validateParameters('color', [65, 0.5]);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('color(): no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          p5._validateParameters('color', [255, 204, 0]);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('color(): optional parameter, incorrect type', function() {
        -      assert.validationError(function() {
        -        p5._validateParameters('color', [0, 0, 0, 'A']);
        -      });
        -    });
        -    test('color(): extra parameter', function() {
        -      assert.validationError(function() {
        -        p5._validateParameters('color', [[0, 0, 0], 0]);
        -      });
        -    });
        -    test('color(): incorrect element type', function() {
        -      assert.validationError(function() {
        -        p5._validateParameters('color', [['A', 'B', 'C']]);
        -      });
        -    });
        -    test('color(): incorrect parameter count', function() {
        -      assert.validationError(function() {
        -        p5._validateParameters('color', ['A', 'A', 0, 0, 0, 0, 0, 0]);
        -      });
        -    });
        -  });
        -
        -  suite('helpForMisusedAtTopLevelCode', function() {
        -    var help = function(msg) {
        -      var log = [];
        -      var logger = function(msg) {
        -        log.push(msg);
        -      };
        -
        -      p5.prototype._helpForMisusedAtTopLevelCode({ message: msg }, logger);
        -      assert.equal(log.length, 1);
        -      return log[0];
        -    };
        -
        -    test('help for constants is shown', function() {
        -      assert.match(
        -        help("'HALF_PI' is undefined"),
        -        /Did you just try to use p5\.js's HALF_PI constant\?/
        -      );
        -    });
        -
        -    test('help for functions is shown', function() {
        -      assert.match(
        -        help("'smooth' is undefined"),
        -        /Did you just try to use p5\.js's smooth\(\) function\?/
        -      );
        -    });
        -
        -    test('help for variables is shown', function() {
        -      assert.match(
        -        help("'focused' is undefined"),
        -        /Did you just try to use p5\.js's focused variable\?/
        -      );
        -    });
        -  });
        -
        -  suite('misspelling detection', function() {
        -    let log = [];
        -    const logger = function(err) {
        -      log.push(err);
        -    };
        -    let help = function(err) {
        -      p5._fesErrorMonitor(err);
        -      assert.equal(log.length, 1);
        -      return log[0];
        -    };
        -
        -    setup(function() {
        -      log = [];
        -      p5._fesLogger = logger;
        -    });
        -
        -    teardown(function() {
        -      p5._fesLogger = null;
        -    });
        -
        -    testUnMinified('detects capitalization mistakes', function() {
        -      const logMsg = help(new ReferenceError('MouseX is not defined'));
        -      assert.match(
        -        logMsg,
        -        /It seems that you may have accidentally written "MouseX"/
        -      );
        -      assert.match(logMsg, /mouseX/);
        -    });
        -
        -    testUnMinified('detects spelling mistakes', function() {
        -      const logMsg = help(new ReferenceError('colour is not defined'));
        -      assert.match(
        -        logMsg,
        -        /It seems that you may have accidentally written "colour"/
        -      );
        -      assert.match(logMsg, /color/);
        -    });
        -
        -    testUnMinified(
        -      'can give more than one closest matches, if applicable',
        -      function() {
        -        const logMsg = help(new ReferenceError('strok is not defined'));
        -        assert.match(
        -          logMsg,
        -          /It seems that you may have accidentally written "strok"/
        -        );
        -        assert.match(logMsg, /stroke/);
        -        assert.match(logMsg, /STROKE/);
        -      }
        -    );
        -
        -    testUnMinified('detects spelling + captialization mistakes', function() {
        -      const logMsg = help(new ReferenceError('RandomGossian is not defined'));
        -      assert.match(
        -        logMsg,
        -        /It seems that you may have accidentally written "RandomGossian"/
        -      );
        -      assert.match(logMsg, /randomGaussian/);
        -    });
        -  });
        -
        -  suite('caps mistakes for user-defined functions (instance mode)', function() {
        -    let myp5;
        -    let log;
        -    const logger = function(err) {
        -      log.push(err);
        -    };
        -    setup(function(done) {
        -      log = [];
        -      p5._fesLogger = logger;
        -      new p5(function(p) {
        -        // intentional capitalization mistake
        -        p.preLoad = function() {};
        -        p.setup = function() {
        -          myp5 = p;
        -          p._fesLogger = logger;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      p5._fesLogger = null;
        -      myp5.remove();
        -    });
        -
        -    testUnMinified(
        -      'detects capitatilization mistake in instance mode',
        -      function() {
        -        assert.strictEqual(log.length, 1, 'One message is displayed');
        -        assert.match(
        -          log[0],
        -          /It seems that you may have accidentally written preLoad instead of preload/
        -        );
        -      }
        -    );
        -  });
        -
        -  suite('caps mistakes for user-defined functions (global mode)', function() {
        -    let log;
        -    const logger = function(err) {
        -      log.push(err);
        -    };
        -    testUnMinified(
        -      'detects capitatilization mistake in global mode',
        -      function() {
        -        return new Promise(function(resolve) {
        -          iframe = createP5Iframe(
        -            [
        -              P5_SCRIPT_TAG,
        -              '<script>',
        -              'p5._fesLogger = window.logger',
        -              'function setup() { window.afterSetup();}',
        -              'function DRAW() {}',
        -              '</script>'
        -            ].join('\n')
        -          );
        -          log = [];
        -          iframe.elt.contentWindow.logger = logger;
        -          iframe.elt.contentWindow.afterSetup = resolve;
        -        }).then(function() {
        -          //let log = iframe.elt.contentWindow.log;
        -          assert.strictEqual(log.length, 1);
        -          assert.match(
        -            log[0],
        -            /It seems that you may have accidentally written DRAW instead of draw/
        -          );
        -        });
        -      }
        -    );
        -  });
        -});
        -
        -// seperating in another suite because these don't need to initialize myp5
        -// for each test. Instead they initialize p5 in the iframe. These tests are
        -// also slower than the above ones.
        -suite('Global Error Handling', function() {
        -  let log;
        -  const WAIT_AND_RESOLVE = [
        -    '<script>',
        -    'p5._fesLogger = window.logger',
        -    'let flag = false;',
        -    'setInterval(() => {',
        -    // just because the log has one element doesn't necessarily mean that the
        -    // handler has finished its job. The flag allows it to take some more time
        -    // after adding the first log message
        -    '  if (window.logger.length > 0) {',
        -    '    if (flag) window.afterSetup();',
        -    '    flag = true;',
        -    '  }',
        -    '}, 50);',
        -    '</script>'
        -  ].join('\n');
        -  const logger = function(err) {
        -    log.push(err);
        -  };
        -  setup(function() {
        -    log = [];
        -    p5._fesLogger = logger;
        -  });
        -
        -  teardown(function() {
        -    p5._fesLogger = null;
        -  });
        -
        -  const prepSyntaxTest = (arr, resolve) => {
        -    iframe = createP5Iframe(
        -      [P5_SCRIPT_TAG, WAIT_AND_RESOLVE, '<script>', ...arr, '</script>'].join(
        -        '\n'
        -      )
        -    );
        -    log = [];
        -    iframe.elt.contentWindow.logger = logger;
        -    iframe.elt.contentWindow.afterSetup = resolve;
        -    return iframe;
        -  };
        -
        -  testUnMinified('identifies errors happenning internally', function() {
        -    return new Promise(function(resolve) {
        -      // quite an unusual way to test, but the error listener doesn't work
        -      // under mocha. Also the stacktrace gets filled with mocha internal
        -      // function calls. Using this method solves both of these problems.
        -      // This method also allows us to test for SyntaxError without messing
        -      // with flow of the other tests
        -      prepSyntaxTest(
        -        [
        -          'function setup() {',
        -          'let cnv = createCanvas(400, 400);',
        -          'cnv.mouseClicked();', // Error in p5 library as no callback passed
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /inside the p5js library/);
        -      assert.match(log[0], /mouseClicked/);
        -    });
        -  });
        -
        -  testUnMinified(
        -    'identifies errors happenning internally in ES6 classes',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSyntaxTest(
        -          [
        -            'function setup() {',
        -            'let cnv = createCanvas(10, 10, WEBGL);',
        -            'let fbo = createFramebuffer();',
        -            'fbo.draw();', // Error in p5 library as no callback passed
        -            '}'
        -          ],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.strictEqual(log.length, 1);
        -        assert.match(log[0], /inside the p5js library/);
        -        assert.match(log[0], /draw/);
        -      });
        -    }
        -  );
        -
        -  testUnMinified('identifies errors in preload', function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function preload() {',
        -          'circle(5, 5, 2);', // error
        -          '}',
        -          'function setup() {',
        -          'createCanvas(10, 10);',
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /"circle" being called from preload/);
        -    });
        -  });
        -
        -  testUnMinified("identifies TypeError 'notDefined'", function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function setup() {',
        -          'let x = asdfg + 5;', // ReferenceError: asdfg is not defined
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /asdfg/);
        -      assert.match(log[0], /not defined in the current scope/);
        -    });
        -  });
        -
        -  testUnMinified(
        -    "identifies SyntaxError 'Invalid or unexpected Token'",
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSyntaxTest(
        -          [
        -            'function setup() {',
        -            'let x = “not a string”', // SyntaxError: Invalid or unexpected token
        -            '}'
        -          ],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.strictEqual(log.length, 1);
        -        assert.match(log[0], /Syntax Error/);
        -        assert.match(log[0], /JavaScript doesn't recognize/);
        -      });
        -    }
        -  );
        -
        -  testUnMinified("identifies SyntaxError 'unexpectedToken'", function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function setup() {',
        -          'for (let i = 0; i < 5,; ++i) {}', // SyntaxError: Unexpected token
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /Syntax Error/);
        -      assert.match(log[0], /typo/);
        -    });
        -  });
        -
        -  testUnMinified("identifies TypeError 'notFunc'", function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function setup() {',
        -          'let asdfg = 5',
        -          'asdfg()', // TypeError: asdfg is not a function
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /"asdfg" could not be called as a function/);
        -    });
        -  });
        -
        -  testUnMinified("identifies TypeError 'notFuncObj'", function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function setup() {',
        -          'let asdfg = {}',
        -          'asdfg.abcd()', // TypeError: abcd is not a function
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /"abcd" could not be called as a function/);
        -      assert.match(log[0], /"asdfg" has "abcd" in it/);
        -    });
        -  });
        -
        -  testUnMinified("identifies ReferenceError 'cannotAccess'", function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function setup() {',
        -          'console.log(x)', // ReferenceError: Cannot access 'x' before initialization
        -          'let x = 100',
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /Error/);
        -      assert.match(log[0], /used before declaration/);
        -    });
        -  });
        -
        -  testUnMinified("identifies SyntaxError 'badReturnOrYield'", function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        ['function setup() {', 'let x = 100;', '}', 'return;'],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /Syntax Error/);
        -      assert.match(log[0], /lies outside of a function/);
        -    });
        -  });
        -
        -  testUnMinified("identifies SyntaxError 'missingInitializer'", function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function setup() {',
        -          'const x;', //SyntaxError: Missing initializer in const declaration
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /Syntax Error/);
        -      assert.match(log[0], /but not initialized/);
        -    });
        -  });
        -
        -  testUnMinified("identifies SyntaxError 'redeclaredVariable'", function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function setup() {',
        -          'let x=100;',
        -          'let x=99;', //SyntaxError: Identifier 'x' has already been declared
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /Syntax Error/);
        -      assert.match(log[0], /JavaScript doesn't allow/);
        -    });
        -  });
        -
        -  testUnMinified("identifies TypeError 'constAssign'", function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function setup() {',
        -          'const x = 100;',
        -          'x = 10;', //TypeError: Assignment to constant variable
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /Error/);
        -      assert.match(log[0], /const variable is being/);
        -    });
        -  });
        -
        -  testUnMinified("identifies TypeError 'readFromNull'", function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function setup() {',
        -          'const x = null;',
        -          'console.log(x.prop);', //TypeError: Cannot read property 'prop' of null
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /Error/);
        -      assert.match(log[0], /property of null/);
        -    });
        -  });
        -
        -  testUnMinified("identifies TypeError 'readFromUndefined'", function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function setup() {',
        -          'const x = undefined;',
        -          'console.log(x.prop);', //TypeError: Cannot read property 'prop' of undefined
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /Error/);
        -      assert.match(log[0], /property of undefined/);
        -    });
        -  });
        -
        -  testUnMinified('builds friendlyStack', function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function myfun(){',
        -          'asdfg()', // ReferenceError
        -          '}',
        -          'function setup() {',
        -          'myfun()',
        -          '}'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 2);
        -      let temp = log[1].split('\n');
        -      temp = temp.filter(e => e.trim().length > 0);
        -      assert.strictEqual(temp.length, 4);
        -      assert.match(log[0], /"asdfg" is not defined/);
        -      assert.match(temp[1], /Error at/);
        -      assert.match(temp[1], /myfun/);
        -      assert.match(temp[3], /Called from/);
        -      assert.match(temp[3], /setup/);
        -    });
        -  });
        -
        -  testUnMinified('indentifies internal error - instance mode', function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function sketch(p) {',
        -          '  p.setup = function() {',
        -          '    p.stroke();', // error
        -          '  }',
        -          '}',
        -          'new p5(sketch);'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /stroke/);
        -      assert.match(log[0], /inside the p5js library/);
        -    });
        -  });
        -
        -  testUnMinified('indentifies error in preload - instance mode', function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function sketch(p) {',
        -          '  p.preload = function() {',
        -          '    p.circle(2, 2, 2);', // error
        -          '  }',
        -          '  p.setup = function() {',
        -          '    p.createCanvas(5, 5);',
        -          '  }',
        -          '}',
        -          'new p5(sketch);'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /"circle" being called from preload/);
        -    });
        -  });
        -
        -  testUnMinified('indentifies error in user code - instance mode', function() {
        -    return new Promise(function(resolve) {
        -      prepSyntaxTest(
        -        [
        -          'function sketch(p) {',
        -          '  p.setup = function() {',
        -          '    myfun();', // ReferenceError: myfun is not defined
        -          '  }',
        -          '}',
        -          'new p5(sketch);'
        -        ],
        -        resolve
        -      );
        -    }).then(function() {
        -      assert.strictEqual(log.length, 1);
        -      assert.match(log[0], /myfun/);
        -      assert.match(log[0], /is not defined in the current scope/);
        -    });
        -  });
        -});
        -
        -suite('Tests for p5.js sketch_reader', function() {
        -  const WAIT_AND_RESOLVE = [
        -    '<script>',
        -    'p5._fesLogger = window.logger',
        -    'let flag = false;',
        -    'setInterval(() => {',
        -    // just because the log has one element doesn't necessarily mean that the
        -    // handler has finished its job. The flag allows it to take some more time
        -    // after adding the first log message
        -    '  if (window.logger.length > 0) {',
        -    '    if (flag) window.afterSetup();',
        -    '    flag = true;',
        -    '  }',
        -    '}, 50);',
        -    '</script>'
        -  ].join('\n');
        -
        -  let log;
        -  const logger = function(err) {
        -    log.push(err);
        -  };
        -
        -  const prepSketchReaderTest = (arr, resolve) => {
        -    iframe = createP5Iframe(
        -      [P5_SCRIPT_TAG, WAIT_AND_RESOLVE, '<script>', ...arr, '</script>'].join(
        -        '\n'
        -      )
        -    );
        -    log = [];
        -    iframe.elt.contentWindow.logger = logger;
        -    iframe.elt.contentWindow.afterSetup = resolve;
        -    return iframe;
        -  };
        -
        -  testUnMinified(
        -    'detects reassignment of p5.js constant inside setup',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(
        -          ['function setup() {', 'let PI = 100', '}'],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.strictEqual(log.length, 1);
        -        assert.match(log[0], /you have used a p5.js reserved variable/);
        -      });
        -    }
        -  );
        -
        -  testUnMinified(
        -    'detects reassignment of p5.js function inside setup',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(
        -          ['function setup() {', 'let text = 100', '}'],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.strictEqual(log.length, 1);
        -        assert.match(log[0], /you have used a p5.js reserved function/);
        -      });
        -    }
        -  );
        -
        -  testUnMinified(
        -    'detects reassignment of p5.js constant outside setup',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(['let PI = 100', 'function setup() {}'], resolve);
        -      }).then(function() {
        -        assert.strictEqual(log.length, 1);
        -        assert.match(log[0], /you have used a p5.js reserved variable/);
        -      });
        -    }
        -  );
        -
        -  testUnMinified(
        -    'detects reassignment of p5.js function (text) outside setup',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(
        -          ['let text = 100', 'function setup() {}'],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.strictEqual(log.length, 1);
        -        assert.match(log[0], /you have used a p5.js reserved function/);
        -      });
        -    }
        -  );
        -
        -  testUnMinified(
        -    'detects reassignment of p5.js function (textSize from Typography) outside setup',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(
        -          ['let textSize = 100', 'function setup() {}'],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.strictEqual(log.length, 1);
        -        assert.match(log[0], /you have used a p5.js reserved function/);
        -      });
        -    }
        -  );
        -
        -  testUnMinified(
        -    'does not detect reassignment of p5.js function (size from TypedDict or Dom) outside setup',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(
        -          ['let size = 100', 'function setup() {}'],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.strictEqual(log.length, 0);
        -      });
        -    }
        -  );
        -
        -  testUnMinified(
        -    'detects reassignment of p5.js function (point from shape) outside setup',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(
        -          ['let point = 100', 'function setup() {}'],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.strictEqual(log.length, 1);
        -        assert.match(log[0], /you have used a p5.js reserved function/);
        -      });
        -    }
        -  );
        -
        -  testUnMinified(
        -    'detects reassignment of p5.js functions in declaration lists',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(
        -          ['function setup() {', 'let x = 2, text = 2;', '}'],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.strictEqual(log.length, 1);
        -        assert.match(log[0], /you have used a p5.js reserved function/);
        -      });
        -    }
        -  );
        -
        -  testUnMinified(
        -    'detects reassignment of p5.js functions in declaration lists after function calls',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(
        -          [
        -            'function setup() {',
        -            'let x = constrain(frameCount, 0, 1000), text = 2;',
        -            '}'
        -          ],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.strictEqual(log.length, 1);
        -        assert.match(log[0], /you have used a p5.js reserved function/);
        -      });
        -    }
        -  );
        -
        -  testUnMinified(
        -    'ignores p5.js functions used in the right hand side of assignment expressions',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(
        -          // This will still log an error, as `text` isn't being used correctly
        -          // here, but the important part is that it doesn't say that we're
        -          // trying to reassign a reserved function.
        -          ['function draw() {', 'let x = constrain(100, 0, text);', '}'],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.ok(
        -          !log.some(line =>
        -            line.match(/you have used a p5.js reserved function/)
        -          )
        -        );
        -      });
        -    }
        -  );
        -
        -  testUnMinified(
        -    'ignores p5.js function names used as function arguments',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(
        -          ['function draw() {', 'let myLog = (text) => print(text);', '}'],
        -          resolve
        -        );
        -      }).then(function() {
        -        assert.strictEqual(log.length, 0);
        -      });
        -    }
        -  );
        -
        -  testUnMinified(
        -    'fails gracefully on inputs too complicated to parse',
        -    function() {
        -      return new Promise(function(resolve) {
        -        prepSketchReaderTest(
        -          // This technically is redefining text, but it should stop parsing
        -          // after the double nested brackets rather than try and possibly
        -          // give a false positive error. This particular assignment will get
        -          // caught at runtime regardless by
        -          // `_createFriendlyGlobalFunctionBinder`.
        -          [
        -            'function draw() {',
        -            'let x = constrain(millis(), 0, text = 100)',
        -            '}'
        -          ],
        -          resolve
        -        );
        -      }).then(function() {
        -        console.log(log);
        -        assert.strictEqual(log.length, 0);
        -      });
        -    }
        -  );
        -});
        diff --git a/test/unit/core/main.js b/test/unit/core/main.js
        index 7df683542f..074aa32c7e 100644
        --- a/test/unit/core/main.js
        +++ b/test/unit/core/main.js
        @@ -1,131 +1,12 @@
        -const { expect, assert } = require('chai');
        +import p5 from '../../../src/app.js';
        +import { createP5Iframe, P5_SCRIPT_TAG, P5_SCRIPT_URL } from '../../js/p5_helpers';
        +import { vi } from 'vitest';
         
         suite('Core', function () {
        -  suite('p5.prototype.registerMethod', function () {
        -    teardown(function() {
        -      p5.prototype._registeredMethods.init = [];
        -      p5.prototype._registeredMethods.beforePreload = [];
        -      p5.prototype._registeredMethods.preload = [];
        -      p5.prototype._registeredMethods.afterPreload = [];
        -      p5.prototype._registeredMethods.beforeSetup = [];
        -      p5.prototype._registeredMethods.setup = [];
        -      p5.prototype._registeredMethods.afterSetup = [];
        -      p5.prototype._registeredMethods.pre = [];
        -      p5.prototype._registeredMethods.draw = [];
        -      p5.prototype._registeredMethods.post = [];
        -    });
        -    test('should register and call "init" methods', function () {
        -      var myp5, myInitCalled;
        -      p5.prototype.registerMethod('init', function myInit() {
        -        assert(
        -          !myInitCalled,
        -          'myInit should only be called once during test suite'
        -        );
        -        myInitCalled = true;
        -
        -        this.myInitCalled = true;
        -      });
        -
        -      myp5 = new p5(function (sketch) {
        -        assert(sketch.hasOwnProperty('myInitCalled'));
        -        assert(sketch.myInitCalled);
        -
        -        sketch.sketchFunctionCalled = true;
        -      });
        -
        -      assert(myp5.sketchFunctionCalled);
        -    });
        -    test('should register and call before and after "preload" hooks', function () {
        -      return new Promise(resolve => {
        -        let beforePreloadCalled = false;
        -        let preloadCalled = false;
        -        let afterPreloadCalled = false;
        -
        -        p5.prototype.registerMethod('beforePreload', () => {
        -          beforePreloadCalled = true;
        -        });
        -
        -        p5.prototype.registerMethod('preload', () => {
        -          assert.equal(beforePreloadCalled, true);
        -          preloadCalled = true;
        -        });
        -
        -        p5.prototype.registerMethod('afterPreload', () => {
        -          if (beforePreloadCalled && preloadCalled) afterPreloadCalled = true;
        -        });
        -
        -        myp5 = new p5(function (sketch) {
        -          sketch.preload = () => {};
        -          sketch.setup = () => {
        -            assert.equal(afterPreloadCalled, true);
        -            resolve();
        -          };
        -        });
        -      });
        -    });
        -    test('should register and call before and after "setup" hooks', function () {
        -      return new Promise(resolve => {
        -        let beforeSetupCalled = false;
        -        let setupCalled = false;
        -        let afterSetupCalled = false;
        -
        -        p5.prototype.registerMethod('beforeSetup', () => {
        -          beforeSetupCalled = true;
        -        });
        -
        -        p5.prototype.registerMethod('setup', () => {
        -          assert.equal(beforeSetupCalled, true);
        -          setupCalled = true;
        -        });
        -
        -        p5.prototype.registerMethod('afterSetup', () => {
        -          if (beforeSetupCalled && setupCalled) afterSetupCalled = true;
        -        });
        -
        -        myp5 = new p5(function (sketch) {
        -          sketch.setup = () => {};
        -          sketch.draw = () => {
        -            assert.equal(afterSetupCalled, true);
        -            resolve();
        -          };
        -        });
        -      });
        -    });
        -    test('should register and call pre and post "draw" hooks', function () {
        -      return new Promise(resolve => {
        -        let preDrawCalled = false;
        -        let drawCalled = false;
        -        let postDrawCalled = false;
        -
        -        p5.prototype.registerMethod('pre', () => {
        -          preDrawCalled = true;
        -        });
        -
        -        p5.prototype.registerMethod('draw', () => {
        -          assert.equal(preDrawCalled, true);
        -          drawCalled = true;
        -        });
        -
        -        p5.prototype.registerMethod('post', () => {
        -          if (preDrawCalled && drawCalled) postDrawCalled = true;
        -        });
        -
        -        myp5 = new p5(function (sketch) {
        -          sketch.draw = () => {
        -            if (sketch.frameCount === 2) {
        -              assert.equal(postDrawCalled, true);
        -              resolve();
        -            }
        -          };
        -        });
        -      });
        -    });
        -  });
        -
        -  suite('new p5() / global mode', function () {
        +  suite.todo('new p5() / global mode', function () {
             var iframe;
         
        -    teardown(function () {
        +    afterAll(function () {
               if (iframe) {
                 iframe.teardown();
                 iframe = null;
        @@ -190,19 +71,20 @@ suite('Core', function () {
             });
           });
         
        -  suite('p5.prototype._createFriendlyGlobalFunctionBinder', function () {
        +  // NOTE: need rewrite or will be taken care of by FES
        +  suite.todo('p5.prototype._createFriendlyGlobalFunctionBinder', function () {
             var noop = function () {};
             var createBinder = p5.prototype._createFriendlyGlobalFunctionBinder;
             var logMsg, globalObject, bind, iframe;
         
        -    teardown(function () {
        +    afterAll(function () {
               if (iframe) {
                 iframe.teardown();
                 iframe = null;
               }
             });
         
        -    setup(function () {
        +    beforeAll(function () {
               globalObject = {};
               logMsg = undefined;
               bind = createBinder({
        @@ -219,20 +101,19 @@ suite('Core', function () {
             });
             if (!window.IS_TESTING_MINIFIED_VERSION) {
               test('should warn when globals already exist', function () {
        -        const _friendlyErrorStub = sinon.stub(p5, '_friendlyError');
        +        const _friendlyErrorStub = vi.spyOn(p5, '_friendlyError');
                 try {
                   globalObject.text = 'hi';
                   bind('text', noop);
                   expect(
        -            _friendlyErrorStub.calledOnce,
        -            'p5._friendlyError was not called'
        -          ).to.be.true;
        +            _friendlyErrorStub
        +          ).toHaveBeenCalledTimes(1);
                 } finally {
        -          _friendlyErrorStub.restore();
        +          vi.restoreAllMocks();
                 }
               });
         
        -      test('should warn when globals are overwritten', function () {
        +      test.todo('should warn when globals are overwritten', function () {
                 bind('text', noop);
                 globalObject.text = 'boop';
         
        @@ -242,7 +123,7 @@ suite('Core', function () {
               });
             } else {
               test('should NOT warn when globals already exist', function () {
        -        const _friendlyErrorStub = sinon.stub(p5, '_friendlyError');
        +        const _friendlyErrorStub = vi.spyOn(p5, '_friendlyError');
                 try {
                   globalObject.text = 'hi';
                   bind('text', noop);
        @@ -251,7 +132,7 @@ suite('Core', function () {
                     'p5._friendlyError was called in minified p5.js'
                   ).to.be.false;
                 } finally {
        -          _friendlyErrorStub.restore();
        +          vi.restoreAllMocks();
                 }
               });
         
        @@ -291,50 +172,11 @@ suite('Core', function () {
               assert.isUndefined(logMsg);
             });
         
        -    // This is a regression test for
        -    // https://github.com/processing/p5.js/issues/1350.
        -    test('should not warn about overwriting preload methods', function () {
        -      globalObject.loadJSON = function () {
        -        throw new Error();
        -      };
        -      bind('loadJSON', noop);
        -      assert.equal(globalObject.loadJSON, noop);
        -      assert.isUndefined(logMsg);
        -    });
        -
             test('should not warn about overwriting non-functions', function () {
               bind('mouseX', 5);
               globalObject.mouseX = 50;
               assert.equal(globalObject.mouseX, 50);
               assert.isUndefined(logMsg);
             });
        -
        -    test('instance preload is independent of window', function () {
        -      // callback for p5 instance mode.
        -      // It does not define a preload.
        -      // This tests that we don't call the global preload accidentally.
        -      function cb(s) {
        -        s.setup = function () {
        -          window.afterSetup();
        -        };
        -      }
        -      return new Promise(function (resolve) {
        -        iframe = createP5Iframe(
        -          [
        -            P5_SCRIPT_TAG,
        -            '<script>',
        -            'globalPreloads = 0;',
        -            'function setup() { }',
        -            'function preload() { window.globalPreloads++; }',
        -            'new p5(' + cb.toString() + ');',
        -            '</script>'
        -          ].join('\n')
        -        );
        -        iframe.elt.contentWindow.afterSetup = resolve;
        -      }).then(function () {
        -        var win = iframe.elt.contentWindow;
        -        assert.strictEqual(win.globalPreloads, 1);
        -      });
        -    });
           });
         });
        diff --git a/test/unit/core/p5.Graphics.js b/test/unit/core/p5.Graphics.js
        index 216f4e9d26..3f73c4b682 100644
        --- a/test/unit/core/p5.Graphics.js
        +++ b/test/unit/core/p5.Graphics.js
        @@ -1,16 +1,18 @@
        +import p5 from '../../../src/app.js';
        +import { vi } from 'vitest';
        +
         suite('Graphics', function() {
           var myp5;
         
        -  setup(function(done) {
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -120,7 +122,7 @@ suite('Graphics', function() {
         
             afterEach(() => {
               if (glStub) {
        -        glStub.restore();
        +        vi.restoreAllMocks();
                 glStub = null;
               }
             });
        @@ -175,10 +177,11 @@ suite('Graphics', function() {
               assertValidPixels(graph, 19, 16, 2);
             });
         
        +    // NOTE: check this
             test('it resizes the graphics based on max texture size', function() {
        -      glStub = sinon.stub(p5.RendererGL.prototype, '_getParam');
        +      glStub = vi.spyOn(p5.RendererGL.prototype, '_getMaxTextureSize');
               const fakeMaxTextureSize = 100;
        -      glStub.returns(fakeMaxTextureSize);
        +      glStub.mockReturnValue(fakeMaxTextureSize);
               const graph = myp5.createGraphics(200, 200, myp5.WEBGL);
               assert(graph.width, 100);
               assert(graph.height, 100);
        diff --git a/test/unit/core/param_errors.js b/test/unit/core/param_errors.js
        new file mode 100644
        index 0000000000..5bce63c7b7
        --- /dev/null
        +++ b/test/unit/core/param_errors.js
        @@ -0,0 +1,225 @@
        +import validateParams from '../../../src/core/friendly_errors/param_validator.js';
        +import * as constants from '../../../src/core/constants.js';
        +
        +suite('Validate Params', function () {
        +  const mockP5 = {
        +    disableFriendlyErrors: false,
        +    Color: function () {
        +      return 'mock p5.Color';
        +    },
        +  };
        +  const mockP5Prototype = {};
        +
        +  beforeAll(function () {
        +    validateParams(mockP5, mockP5Prototype, {});
        +  });
        +
        +  afterAll(function () {
        +  });
        +
        +  suite('validateParams: multiple types allowed for single parameter', function () {
        +    test('saturation(): valid inputs', () => {
        +      const validInputs = [
        +        { input: ['rgb(255, 128, 128)'] },
        +        { input: [[0, 50, 100]] },
        +        { input: [new mockP5.Color()] }
        +      ];
        +
        +      validInputs.forEach(({ input }) => {
        +        const result = mockP5Prototype.validate('saturation', input);
        +        assert.isTrue(result.success);
        +      });
        +    });
        +
        +    test('saturation(): invalid inputs', () => {
        +      const invalidInputs = [
        +        { input: [true] },
        +        { input: [42] },
        +        { input: [{}] },
        +        { input: [null] }
        +      ];
        +
        +      invalidInputs.forEach(({ input }) => {
        +        const result = mockP5Prototype.validate('p5.saturation', input);
        +        assert.isTrue(result.error.startsWith("🌸 p5.js says: Expected Color or array or string at the first parameter, but received"));
        +      });
        +    });
        +  });
        +
        +  suite('validateParams: constant as parameter', function () {
        +    const validInputs = [
        +      { name: 'BLEND, no friendly-err-msg', input: constants.BLEND },
        +      { name: 'HARD_LIGHT, no friendly-err-msg', input: constants.HARD_LIGHT }
        +    ];
        +
        +    validInputs.forEach(({ name, input }) => {
        +      test(`blendMode(): ${name}`, () => {
        +        const result = mockP5Prototype.validate('p5.blendMode', [input]);
        +        assert.isTrue(result.success);
        +      });
        +    });
        +
        +    const FAKE_CONSTANT = 'fake-constant';
        +    const invalidInputs = [
        +      { name: 'invalid constant', input: FAKE_CONSTANT },
        +      { name: 'non-constant parameter', input: 100 }
        +    ];
        +
        +    invalidInputs.forEach(({ name, input }) => {
        +      test(`blendMode(): ${name}`, () => {
        +        const result = mockP5Prototype.validate('p5.blendMode', [input]);
        +        const expectedError = "🌸 p5.js says: Expected constant (please refer to documentation for allowed values) at the first parameter, but received " + input + " in p5.blendMode().";
        +        assert.equal(result.error, expectedError);
        +      });
        +    });
        +  });
        +
        +  suite('validateParams: numbers + optional constant for arc()', function () {
        +    const validInputs = [
        +      { name: 'no friendly-err-msg', input: [200, 100, 100, 80, 0, Math.PI, constants.PIE, 30] },
        +      { name: 'missing optional param #6 & #7, no friendly-err-msg', input: [200, 100, 100, 80, 0, Math.PI] }
        +    ];
        +    validInputs.forEach(({ name, input }) => {
        +      test(`arc(): ${name}`, () => {
        +        const result = mockP5Prototype.validate('p5.arc', input);
        +        assert.isTrue(result.success);
        +      });
        +    });
        +
        +    const invalidInputs = [
        +      { name: 'missing required arc parameters #4, #5', input: [200, 100, 100, 80], msg: '🌸 p5.js says: Expected at least 6 arguments, but received fewer in p5.arc(). For more information, see https://p5js.org/reference/p5/arc.' },
        +      { name: 'missing required param #0', input: [undefined, 100, 100, 80, 0, Math.PI, constants.PIE, 30], msg: '🌸 p5.js says: Expected number at the first parameter, but received undefined in p5.arc().' },
        +      { name: 'missing required param #4', input: [200, 100, 100, 80, undefined, 0], msg: '🌸 p5.js says: Expected number at the fifth parameter, but received undefined in p5.arc().' },
        +      { name: 'missing optional param #5', input: [200, 100, 100, 80, 0, undefined, Math.PI], msg: '🌸 p5.js says: Expected number at the sixth parameter, but received undefined in p5.arc().' },
        +      { name: 'wrong param type at #0', input: ['a', 100, 100, 80, 0, Math.PI, constants.PIE, 30], msg: '🌸 p5.js says: Expected number at the first parameter, but received string in p5.arc().' }
        +    ];
        +
        +    invalidInputs.forEach(({ name, input, msg }) => {
        +      test(`arc(): ${name}`, () => {
        +        const result = mockP5Prototype.validate('p5.arc', input);
        +        assert.equal(result.error, msg);
        +      });
        +    });
        +  });
        +
        +  suite('validateParams: class, multi-types + optional numbers', function () {
        +    test('ambientLight(): no firendly-err-msg', function () {
        +      const result = mockP5Prototype.validate('p5.ambientLight', [new mockP5.Color()]);
        +      assert.isTrue(result.success);
        +    })
        +  })
        +
        +  suite('validateParams: a few edge cases', function () {
        +    const invalidInputs = [
        +      { fn: 'color', name: 'wrong type for optional parameter', input: [0, 0, 0, 'A'], msg: '🌸 p5.js says: Expected number at the fourth parameter, but received string in p5.color().' },
        +      { fn: 'color', name: 'superfluous parameter', input: [[0, 0, 0], 0], msg: '🌸 p5.js says: Expected number at the first parameter, but received array in p5.color().' },
        +      { fn: 'color', name: 'wrong element types', input: [['A', 'B', 'C']], msg: '🌸 p5.js says: Expected number at the first parameter, but received array in p5.color().' },
        +      { fn: 'rect', name: 'null, non-trailing, optional parameter', input: [0, 0, 0, 0, null, 0, 0, 0], msg: '🌸 p5.js says: Expected number at the fifth parameter, but received null in p5.rect().' },
        +      { fn: 'color', name: 'too many args + wrong types too', input: ['A', 'A', 0, 0, 0, 0, 0, 0, 0, 0], msg: '🌸 p5.js says: Expected at most 4 arguments, but received more in p5.color(). For more information, see https://p5js.org/reference/p5/color.' },
        +      { fn: 'line', name: 'null string given', input: [1, 2, 4, 'null'], msg: '🌸 p5.js says: Expected number at the fourth parameter, but received string in p5.line().' },
        +      { fn: 'line', name: 'NaN value given', input: [1, 2, 4, NaN], msg: '🌸 p5.js says: Expected number at the fourth parameter, but received nan in p5.line().' }
        +    ];
        +
        +    invalidInputs.forEach(({ name, input, fn, msg }) => {
        +      test(`${fn}(): ${name}`, () => {
        +        const result = mockP5Prototype.validate(`p5.${fn}`, input);
        +        assert.equal(result.error, msg);
        +      });
        +    });
        +  });
        +
        +  suite('validateParams: trailing undefined arguments', function () {
        +    const invalidInputs = [
        +      { fn: 'color', name: 'missing params #1, #2', input: [12, undefined, undefined], msg: '🌸 p5.js says: Expected number at the second parameter, but received undefined in p5.color().' },
        +      // Even though the undefined arguments are technically allowed for
        +      // optional parameters, it is more likely that the user wanted to call
        +      // the function with meaningful arguments.
        +      { fn: 'random', name: 'missing params #0, #1', input: [undefined, undefined], msg: '🌸 p5.js says: All arguments for p5.random() are undefined. There is likely an error in the code.' },
        +      { fn: 'circle', name: 'missing compulsory parameter #2', input: [5, 5, undefined], msg: '🌸 p5.js says: Expected number at the third parameter, but received undefined in p5.circle().' }
        +    ];
        +
        +    invalidInputs.forEach(({ fn, name, input, msg }) => {
        +      test(`${fn}(): ${name}`, () => {
        +        const result = mockP5Prototype.validate(`p5.${fn}`, input);
        +        assert.equal(result.error, msg);
        +      });
        +    });
        +  });
        +
        +  suite('validateParams: multi-format', function () {
        +    const validInputs = [
        +      { name: 'no friendly-err-msg', input: [65] },
        +      { name: 'no friendly-err-msg', input: [65, 100] },
        +      { name: 'no friendly-err-msg', input: [65, 100, 100] }
        +    ];
        +    validInputs.forEach(({ name, input }) => {
        +      test(`color(): ${name}`, () => {
        +        const result = mockP5Prototype.validate('p5.color', input);
        +        assert.isTrue(result.success);
        +      });
        +    });
        +
        +    const invalidInputs = [
        +      { name: 'optional parameter, incorrect type', input: [65, 100, 100, 'a'], msg: '🌸 p5.js says: Expected number at the fourth parameter, but received string in p5.color().' },
        +      { name: 'extra parameter', input: [[65, 100, 100], 100], msg: '🌸 p5.js says: Expected number at the first parameter, but received array in p5.color().' },
        +      { name: 'incorrect element type', input: ['A', 'B', 'C'], msg: '🌸 p5.js says: Expected number at the first parameter, but received string in p5.color().' },
        +      { name: 'incorrect parameter count', input: ['A', 'A', 0, 0, 0, 0, 0, 0], msg: '🌸 p5.js says: Expected at most 4 arguments, but received more in p5.color(). For more information, see https://p5js.org/reference/p5/color.' }
        +    ];
        +
        +    invalidInputs.forEach(({ name, input, msg }) => {
        +      test(`color(): ${name}`, () => {
        +        const result = mockP5Prototype.validate('p5.color', input);
        +
        +        assert.equal(result.error, msg);
        +      });
        +    });
        +  });
        +
        +  suite('validateParameters: union types', function () {
        +    const validInputs = [
        +      { name: 'set() with Number', input: [0, 0, 0] },
        +      { name: 'set() with Number[]', input: [0, 0, [0, 0, 0, 255]] },
        +      { name: 'set() with Object', input: [0, 0, new mockP5.Color()] }
        +    ];
        +    validInputs.forEach(({ name, input }) => {
        +      test(`${name}`, function () {
        +        const result = mockP5Prototype.validate('p5.set', input);
        +        assert.isTrue(result.success);
        +      });
        +    });
        +
        +    test(`set() with Boolean (invalid)`, function () {
        +      const result = mockP5Prototype.validate('p5.set', [0, 0, true]);
        +      assert.equal(result.error, '🌸 p5.js says: Expected number or array or object at the third parameter, but received boolean in p5.set().');
        +    });
        +  });
        +
        +  suite('validateParams: web API objects', function () { // TODO: fix this p5 error
        +    const audioContext = new AudioContext();
        +    const gainNode = audioContext.createGain();
        +
        +    const testCases = [
        +      { fn: 'mouseMoved', name: 'no friendly-err-msg', input: [new MouseEvent('click')] },
        +      { fn: 'p5.MediaElement.connect', name: 'no friendly-err-msg', input: [gainNode] }
        +    ];
        +
        +    testCases.forEach(({ fn, name, input }) => {
        +      test(`${fn}(): ${name}`, function () {
        +        const result = mockP5Prototype.validate(fn, input);
        +        assert.isTrue(result.success);
        +      });
        +    });
        +  });
        +
        +  suite('validateParams: paletteLerp', function () {
        +    test('paletteLerp(): no firendly-err-msg', function () {
        +      const colorStops = [
        +        [new mockP5.Color(), 0.2],
        +        [new mockP5.Color(), 0.8],
        +        [new mockP5.Color(), 0.5]
        +      ];
        +      const result = mockP5Prototype.validate('p5.paletteLerp', [colorStops, 0.5]);
        +      assert.isTrue(result.success);
        +    })
        +  })
        +});
        diff --git a/test/unit/core/preload.js b/test/unit/core/preload.js
        deleted file mode 100644
        index 423b585283..0000000000
        --- a/test/unit/core/preload.js
        +++ /dev/null
        @@ -1,285 +0,0 @@
        -suite('preloads', () => {
        -  let preloadCache = null;
        -  setup(() => {
        -    preloadCache = p5.prototype._promisePreloads;
        -    p5.prototype._promisePreloads = [...preloadCache];
        -  });
        -
        -  teardown(() => {
        -    p5.prototype._promisePreloads = preloadCache;
        -  });
        -
        -  suite('From external sources', () => {
        -    test('Extension preload causes setup to wait', () => {
        -      let resolved = false;
        -      const target = {
        -        async testPreloadFunction() {
        -          await new Promise(res => setTimeout(res, 10));
        -          resolved = true;
        -        }
        -      };
        -
        -      p5.prototype._promisePreloads.push({
        -        target,
        -        method: 'testPreloadFunction'
        -      });
        -
        -      return promisedSketch((sketch, resolve, reject) => {
        -        sketch.preload = () => {
        -          target.testPreloadFunction();
        -        };
        -
        -        sketch.setup = () => {
        -          if (resolved) {
        -            resolve();
        -          } else {
        -            reject(new Error('Sketch enetered setup too early.'));
        -          }
        -        };
        -      });
        -    });
        -
        -    test('Extension preload error causes setup to not execute', () => {
        -      const target = {
        -        async testPreloadFunction() {
        -          throw new Error('Testing Error');
        -        }
        -      };
        -
        -      p5.prototype._promisePreloads.push({
        -        target,
        -        method: 'testPreloadFunction'
        -      });
        -
        -      return promisedSketch((sketch, resolve, reject) => {
        -        sketch.preload = () => {
        -          target.testPreloadFunction();
        -          setTimeout(resolve, 10);
        -        };
        -
        -        sketch.setup = () => {
        -          reject('Sketch should not enter setup');
        -        };
        -      });
        -    });
        -
        -    suite('addCallbacks', () => {
        -      test('Extension is passed all arguments when not using addCallbacks', () => {
        -        const target = {
        -          async testPreloadFunction(...args) {
        -            assert.lengthOf(args, 3);
        -          }
        -        };
        -
        -        p5.prototype._promisePreloads.push({
        -          target,
        -          method: 'testPreloadFunction',
        -          addCallbacks: false
        -        });
        -
        -        return promisedSketch((sketch, resolve, reject) => {
        -          sketch.preload = () => {
        -            target
        -              .testPreloadFunction(() => {}, () => {}, () => {})
        -              .catch(reject);
        -          };
        -
        -          sketch.setup = resolve;
        -        });
        -      });
        -
        -      test('Extension gets stripped arguments when using addCallbacks', () => {
        -        const target = {
        -          async testPreloadFunction(...args) {
        -            assert.lengthOf(args, 1);
        -          }
        -        };
        -
        -        p5.prototype._promisePreloads.push({
        -          target,
        -          method: 'testPreloadFunction',
        -          addCallbacks: true
        -        });
        -
        -        return promisedSketch((sketch, resolve, reject) => {
        -          sketch.preload = () => {
        -            target
        -              .testPreloadFunction(() => {}, () => {}, () => {})
        -              .catch(reject);
        -          };
        -
        -          sketch.setup = resolve;
        -        });
        -      });
        -
        -      test('Extension with addCallbacks supports success callback', () => {
        -        const target = {
        -          async testPreloadFunction(...args) {
        -            assert.lengthOf(args, 1);
        -          }
        -        };
        -
        -        p5.prototype._promisePreloads.push({
        -          target,
        -          method: 'testPreloadFunction',
        -          addCallbacks: true
        -        });
        -
        -        let success = 0;
        -
        -        return promisedSketch((sketch, resolve, reject) => {
        -          sketch.preload = () => {
        -            target
        -              .testPreloadFunction(0, () => {
        -                success++;
        -              })
        -              .catch(reject);
        -            target
        -              .testPreloadFunction(
        -                () => {},
        -                () => {
        -                  success++;
        -                },
        -                () => {
        -                  reject(new Error('Failure callback executed'));
        -                }
        -              )
        -              .catch(reject);
        -          };
        -
        -          sketch.setup = () => {
        -            if (success !== 2) {
        -              reject(
        -                new Error(`Not all success callbacks were run: ${success}/2`)
        -              );
        -            }
        -            resolve();
        -          };
        -        });
        -      });
        -    });
        -
        -    suite('legacyPreload', () => {
        -      test('Extension legacy preload causes setup to wait', () => {
        -        let resolved = false;
        -        const target = {
        -          async testPreloadFunction() {
        -            await new Promise(res => setTimeout(res, 10));
        -            resolved = true;
        -          }
        -        };
        -
        -        p5.prototype._promisePreloads.push({
        -          target,
        -          method: 'testPreloadFunction',
        -          legacyPreloadSetup: {
        -            method: 'testPreloadLegacy'
        -          }
        -        });
        -
        -        return promisedSketch((sketch, resolve, reject) => {
        -          sketch.preload = () => {
        -            target.testPreloadLegacy();
        -          };
        -
        -          sketch.setup = () => {
        -            if (resolved) {
        -              resolve();
        -            } else {
        -              reject(new Error('Sketch enetered setup too early.'));
        -            }
        -          };
        -        });
        -      });
        -
        -      test('Extension legacy preload error causes setup to not execute', () => {
        -        const target = {
        -          async testPreloadFunction() {
        -            throw new Error('Testing Error');
        -          }
        -        };
        -
        -        p5.prototype._promisePreloads.push({
        -          target,
        -          method: 'testPreloadFunction',
        -          legacyPreloadSetup: {
        -            method: 'testPreloadLegacy'
        -          }
        -        });
        -
        -        return promisedSketch((sketch, resolve, reject) => {
        -          sketch.preload = () => {
        -            target.testPreloadLegacy();
        -            setTimeout(resolve, 10);
        -          };
        -
        -          sketch.setup = () => {
        -            reject('Sketch should not enter setup');
        -          };
        -        });
        -      });
        -
        -      test('Extension legacy preload returns objects correctly', async () => {
        -        let testItem = {
        -          test: true,
        -          otherTest: []
        -        };
        -        const target = {
        -          async testPreloadFunction() {
        -            return testItem;
        -          }
        -        };
        -
        -        p5.prototype._promisePreloads.push({
        -          target,
        -          method: 'testPreloadFunction',
        -          legacyPreloadSetup: {
        -            method: 'testPreloadLegacy'
        -          }
        -        });
        -
        -        let testResult;
        -
        -        await promisedSketch((sketch, resolve, reject) => {
        -          sketch.preload = () => {
        -            testResult = target.testPreloadLegacy();
        -          };
        -
        -          sketch.setup = resolve();
        -        });
        -
        -        assert.deepEqual(testResult, testItem);
        -      });
        -
        -      test('Extension legacy preload returns arrays correctly', async () => {
        -        let testItem = [true, [], {}];
        -        const target = {
        -          async testPreloadFunction() {
        -            return testItem;
        -          }
        -        };
        -
        -        p5.prototype._promisePreloads.push({
        -          target,
        -          method: 'testPreloadFunction',
        -          legacyPreloadSetup: {
        -            method: 'testPreloadLegacy',
        -            createBaseObject: () => []
        -          }
        -        });
        -
        -        let testResult;
        -
        -        await promisedSketch((sketch, resolve, reject) => {
        -          sketch.preload = () => {
        -            testResult = target.testPreloadLegacy();
        -          };
        -
        -          sketch.setup = resolve();
        -        });
        -
        -        assert.deepEqual(testResult, testItem);
        -      });
        -    });
        -  });
        -});
        diff --git a/test/unit/core/rendering.js b/test/unit/core/rendering.js
        index 2f917a2ace..47b2fc2240 100644
        --- a/test/unit/core/rendering.js
        +++ b/test/unit/core/rendering.js
        @@ -1,16 +1,18 @@
        +import p5 from '../../../src/app.js';
        +import { vi } from 'vitest';
        +
         suite('Rendering', function() {
           var myp5;
         
        -  setup(function(done) {
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -23,21 +25,21 @@ suite('Rendering', function() {
         
           suite('p5.prototype.createCanvas', function() {
             test('should have correct initial colors', function() {
        -      var white = myp5.color(255, 255, 255).levels;
        -      var black = myp5.color(0, 0, 0).levels;
        -      assert.deepEqual(myp5.color(myp5._renderer._getFill()).levels, white);
        -      assert.deepEqual(myp5.color(myp5._renderer._getStroke()).levels, black);
        -      assert.deepEqual(myp5.color(myp5.drawingContext.fillStyle).levels, white);
        +      var white = myp5.color(255, 255, 255)._array;
        +      var black = myp5.color(0, 0, 0)._array;
        +      assert.deepEqual(myp5.color(myp5._renderer._getFill())._array, white);
        +      assert.deepEqual(myp5.color(myp5._renderer._getStroke())._array, black);
        +      assert.deepEqual(myp5.color(myp5.drawingContext.fillStyle)._array, white);
               assert.deepEqual(
        -        myp5.color(myp5.drawingContext.strokeStyle).levels,
        +        myp5.color(myp5.drawingContext.strokeStyle)._array,
                 black
               );
               myp5.createCanvas(100, 100);
        -      assert.deepEqual(myp5.color(myp5._renderer._getFill()).levels, white);
        -      assert.deepEqual(myp5.color(myp5._renderer._getStroke()).levels, black);
        -      assert.deepEqual(myp5.color(myp5.drawingContext.fillStyle).levels, white);
        +      assert.deepEqual(myp5.color(myp5._renderer._getFill())._array, white);
        +      assert.deepEqual(myp5.color(myp5._renderer._getStroke())._array, black);
        +      assert.deepEqual(myp5.color(myp5.drawingContext.fillStyle)._array, white);
               assert.deepEqual(
        -        myp5.color(myp5.drawingContext.strokeStyle).levels,
        +        myp5.color(myp5.drawingContext.strokeStyle)._array,
                 black
               );
             });
        @@ -48,7 +50,7 @@ suite('Rendering', function() {
         
             afterEach(() => {
               if (glStub) {
        -        glStub.restore();
        +        vi.restoreAllMocks();
                 glStub = null;
               }
             });
        @@ -83,7 +85,7 @@ suite('Rendering', function() {
               assert.equal(myp5.drawingContext.lineCap, myp5.PROJECT);
             });
         
        -    test('should resize framebuffers', function() {
        +    test.todo('should resize framebuffers', function() {
               myp5.createCanvas(10, 10, myp5.WEBGL);
               const fbo = myp5.createFramebuffer();
               myp5.resizeCanvas(5, 15);
        @@ -91,7 +93,7 @@ suite('Rendering', function() {
               assert.equal(fbo.height, 15);
             });
         
        -    test('should resize graphic framebuffers', function() {
        +    test.todo('should resize graphic framebuffers', function() {
               const graphic = myp5.createGraphics(10, 10, myp5.WEBGL);
               const fbo = graphic.createFramebuffer();
               graphic.resizeCanvas(5, 15);
        @@ -99,21 +101,25 @@ suite('Rendering', function() {
               assert.equal(fbo.height, 15);
             });
         
        -    test('should resize the dimensions of canvas based on max texture size', function() {
        -      glStub = sinon.stub(p5.RendererGL.prototype, '_getParam').restore();
        +    test('should limit dimensions of canvas based on max texture size on create', function() {
        +      glStub = vi.spyOn(p5.RendererGL.prototype, '_getMaxTextureSize');
               const fakeMaxTextureSize = 100;
        -      glStub.returns(fakeMaxTextureSize);
        +      glStub.mockReturnValue(fakeMaxTextureSize);
               myp5.createCanvas(10, 10, myp5.WEBGL);
        +      myp5.pixelDensity(1);
               myp5.resizeCanvas(200, 200);
               assert.equal(myp5.width, 100);
               assert.equal(myp5.height, 100);
             });
         
        -    test('should resize the dimensions of canvas based on max texture size', function() {
        -      glStub = sinon.stub(p5.RendererGL.prototype, '_getParam');
        +    test('should limit dimensions of canvas based on max texture size on resize', function() {
        +      glStub = vi.spyOn(p5.RendererGL.prototype, '_getMaxTextureSize');
               const fakeMaxTextureSize = 100;
        -      glStub.returns(fakeMaxTextureSize);
        +      glStub.mockReturnValue(fakeMaxTextureSize);
        +      const prevRatio = window.devicePixelRatio;
        +      window.devicePixelRatio = 1;
               myp5.createCanvas(200, 200, myp5.WEBGL);
        +      window.devicePixelRatio = prevRatio;
               assert.equal(myp5.width, 100);
               assert.equal(myp5.height, 100);
             });
        @@ -131,18 +137,18 @@ suite('Rendering', function() {
               assert.ok(myp5.blendMode);
               assert.typeOf(myp5.blendMode, 'function');
             });
        -    test('should be able to ADD', function() {
        +    test.todo('should be able to ADD', function() {
               myp5.blendMode(myp5.ADD);
               drawX();
             });
        -    test('should be able to MULTIPLY', function() {
        +    test.todo('should be able to MULTIPLY', function() {
               myp5.blendMode(myp5.MULTIPLY);
               drawX();
             });
           });
         
           suite('p5.prototype.setAttributes', function() {
        -    test('_glAttributes should be null at start', function() {
        +    test.todo('_glAttributes should be null at start', function() {
               assert.deepEqual(myp5._glAttributes, null);
             });
             test('_glAttributes should modify with setAttributes', function() {
        @@ -167,7 +173,7 @@ suite('Rendering', function() {
             'plane', 'box', 'sphere', 'cylinder', 'cone', 'ellipsoid', 'torus'
           ];
         
        -  suite('webgl assertions', function() {
        +  suite.todo('webgl assertions', function() {
             for (var i = 0; i < webglMethods.length; i++) {
               var webglMethod = webglMethods[i];
               test(
        diff --git a/test/unit/core/sketch_overrides.js b/test/unit/core/sketch_overrides.js
        new file mode 100644
        index 0000000000..7a8f398fde
        --- /dev/null
        +++ b/test/unit/core/sketch_overrides.js
        @@ -0,0 +1,263 @@
        +import {
        +  verifierUtils
        +} from '../../../src/core/friendly_errors/sketch_verifier.js';
        +
        +suite('Sketch Verifier', function () {
        +  const mockP5 = {
        +    _validateParameters: vi.fn(),
        +    Color: function () { },
        +    Vector: function () { },
        +    prototype: {
        +      rect: function () { },
        +      ellipse: function () { },
        +    }
        +  };
        +
        +  afterEach(() => {
        +    vi.restoreAllMocks()
        +    vi.unstubAllGlobals();
        +  });
        +
        +  suite('fetchScript()', function () {
        +    const url = 'https://www.p5test.com/sketch.js';
        +    const code = 'p.createCanvas(200, 200);';
        +
        +    test('Fetches script content from src', async function () {
        +      const mockFetch = vi.fn(() =>
        +        Promise.resolve({
        +          text: () => Promise.resolve(code)
        +        })
        +      );
        +      vi.stubGlobal('fetch', mockFetch);
        +
        +      const mockScript = { src: url };
        +      const result = await verifierUtils.fetchScript(mockScript);
        +
        +      expect(mockFetch).toHaveBeenCalledWith(url);
        +      expect(result).toBe(code);
        +    });
        +
        +    test('Fetches code when there is no src attribute', async function () {
        +      const mockScript = { textContent: code };
        +      const result = await verifierUtils.fetchScript(mockScript);
        +
        +      expect(result).toBe(code);
        +    });
        +  });
        +
        +  suite('getUserCode()', function () {
        +    const userCode = "let c = p5.Color(20, 20, 20);";
        +
        +    test('fetches the last script element', async function () {
        +      const fakeDocument = document.createElement('div');
        +      fakeDocument.innerHTML = `
        +        <script src="p5.js"></script>
        +        <script src="www.p5test.com/sketch.js"></script>
        +        <script>let c = p5.Color(20, 20, 20);</script>
        +      `;
        +      vi.spyOn(document, 'querySelectorAll')
        +        .mockImplementation((...args) => fakeDocument.querySelectorAll(...args));
        +
        +      vi.spyOn(verifierUtils, 'fetchScript')
        +        .mockImplementation(() => Promise.resolve(userCode));
        +
        +      const result = await verifierUtils.getUserCode();
        +
        +      expect(verifierUtils.fetchScript).toHaveBeenCalledTimes(1);
        +      expect(result).toBe(userCode);
        +    });
        +  });
        +
        +  suite('extractUserDefinedVariablesAndFuncs()', function () {
        +    test('Extracts user-defined variables and functions', function () {
        +      const code = `
        +        let x = 5;
        +        const y = 10;
        +        var z = 15;
        +        let v1, v2, v3
        +        function foo() {}
        +        const bar = () => {};
        +        const baz = (x) => x * 2;
        +      `;
        +
        +      const result = verifierUtils.extractUserDefinedVariablesAndFuncs(code);
        +      const expectedResult = {
        +        "functions": [
        +          {
        +            "line": 5,
        +            "name": "foo",
        +          },
        +          {
        +            "line": 6,
        +            "name": "bar",
        +          },
        +          {
        +            "line": 7,
        +            "name": "baz",
        +          },
        +        ],
        +        "variables": [
        +          {
        +            "line": 1,
        +            "name": "x",
        +          },
        +          {
        +            "line": 2,
        +            "name": "y",
        +          },
        +          {
        +            "line": 3,
        +            "name": "z",
        +          },
        +          {
        +            "line": 4,
        +            "name": "v1",
        +          },
        +          {
        +            "line": 4,
        +            "name": "v2",
        +          },
        +          {
        +            "line": 4,
        +            "name": "v3",
        +          },
        +        ],
        +      };
        +      expect(result).toEqual(expectedResult);
        +    });
        +
        +    // Sketch verifier should ignore the following types of lines:
        +    //    - Comments (both single line and multi-line)
        +    //    - Function calls
        +    //    - Non-declaration code
        +    test('Ignores other lines', function () {
        +      const code = `
        +        // This is a comment
        +        let x = 5;
        +        /* This is a multi-line comment.
        +         * This is a multi-line comment.
        +         */
        +        const y = 10;
        +        console.log("This is a statement");
        +        foo(5);
        +        p5.Math.random();
        +        if (true) {
        +          let z = 15;
        +        }
        +        for (let i = 0; i < 5; i++) {}
        +      `;
        +
        +      const result = verifierUtils.extractUserDefinedVariablesAndFuncs(code);
        +      const expectedResult = {
        +        "functions": [],
        +        "variables": [
        +          {
        +            "line": 2,
        +            "name": "x",
        +          },
        +          {
        +            "line": 6,
        +            "name": "y",
        +          },
        +          {
        +            "line": 11,
        +            "name": "z",
        +          },
        +          {
        +            "line": 13,
        +            "name": "i",
        +          },
        +        ],
        +      };
        +
        +      expect(result).toEqual(expectedResult);
        +    });
        +
        +    test('Handles parsing errors', function () {
        +      const invalidCode = 'let x = ;';
        +      const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
        +
        +      const result = verifierUtils.extractUserDefinedVariablesAndFuncs(invalidCode);
        +
        +      expect(consoleSpy).toHaveBeenCalled();
        +      expect(result).toEqual({ variables: [], functions: [] });
        +      consoleSpy.mockRestore();
        +    });
        +  });
        +
        +  suite('checkForConstsAndFuncs()', function () {
        +    // Set up for this suite of tests
        +    let consoleSpy;
        +
        +    class MockP5 {
        +      setup() {}
        +      draw() {}
        +      rect() {}
        +    }
        +
        +    beforeEach(function () {
        +      consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
        +    });
        +
        +    afterEach(function () {
        +      consoleSpy.mockRestore();
        +    });
        +
        +    test('Detects conflict with p5.js constant', function () {
        +      const userDefinitions = {
        +        variables: [{ name: 'PI', line: 1 }],
        +        functions: []
        +      };
        +      const result = verifierUtils.checkForConstsAndFuncs(userDefinitions, MockP5);
        +
        +      expect(result).toBe(true);
        +      expect(consoleSpy).toHaveBeenCalledWith(
        +        expect.stringContaining(
        +          'Constant "PI" on line 1 is being redeclared and conflicts with a p5.js constant'
        +        )
        +      );
        +    });
        +
        +    test('Detects conflict with p5.js global function', function () {
        +      const userDefinitions = {
        +        variables: [],
        +        functions: [{ name: 'rect', line: 2 }]
        +      };
        +      const result = verifierUtils.checkForConstsAndFuncs(userDefinitions, MockP5);
        +
        +      expect(result).toBe(true);
        +      expect(consoleSpy).toHaveBeenCalledWith(
        +        expect.stringContaining(
        +          'Function "rect" on line 2 is being redeclared and conflicts with a p5.js function'
        +        )
        +      );
        +    });
        +
        +    test('Allows redefinition of whitelisted functions', function () {
        +      const userDefinitions = {
        +        variables: [],
        +        functions: [
        +          { name: 'setup', line: 1 },
        +          { name: 'draw', line: 2 },
        +          { name: 'preload', line: 3 }
        +        ]
        +      };
        +
        +      const result = verifierUtils.checkForConstsAndFuncs(userDefinitions, MockP5);
        +
        +      expect(result).toBe(false);
        +      expect(consoleSpy).not.toHaveBeenCalled();
        +    });
        +
        +    test('Returns false when no conflicts are found', function () {
        +      const userDefinitions = {
        +        variables: [{ name: 'img', line: 1 }],
        +        functions: [{ name: 'cut', line: 2 }]
        +      };
        +
        +      const result = verifierUtils.checkForConstsAndFuncs(userDefinitions, MockP5);
        +
        +      expect(result).toBe(false);
        +    });
        +  });
        +});
        diff --git a/test/unit/core/structure.js b/test/unit/core/structure.js
        index cf94fadb0c..1dd708db78 100644
        --- a/test/unit/core/structure.js
        +++ b/test/unit/core/structure.js
        @@ -1,16 +1,18 @@
        +import p5 from '../../../src/app.js';
        +import { testSketchWithPromise, createP5Iframe, P5_SCRIPT_TAG } from '../../js/p5_helpers';
        +
         suite('Structure', function() {
           var myp5;
         
        -  setup(function(done) {
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -56,18 +58,7 @@ suite('Structure', function() {
         
           suite('p5.prototype.push and p5.prototype.pop', function() {
             function getRenderState() {
        -      var state = {};
        -      for (var key in myp5._renderer) {
        -        var value = myp5._renderer[key];
        -        if (
        -          typeof value !== 'function' &&
        -          key !== '_cachedFillStyle' &&
        -          key !== '_cachedStrokeStyle'
        -        ) {
        -          state[key] = value;
        -        }
        -      }
        -      return state;
        +      return { ...myp5._renderer.states };
             }
         
             function assertCanPreserveRenderState(work) {
        @@ -208,7 +199,7 @@ suite('Structure', function() {
           suite('p5.prototype.redraw', function() {
             var iframe;
         
        -    teardown(function() {
        +    afterAll(function() {
               if (iframe) {
                 iframe.teardown();
                 iframe = null;
        @@ -231,7 +222,7 @@ suite('Structure', function() {
               });
             });
         
        -    test('instance redraw is independent of window', function() {
        +    test.todo('instance redraw is independent of window', function() {
               // callback for p5 instance mode.
               // It does not call noLoop so redraw will be called many times.
               // Redraw is not supposed to call window.draw even though no draw is defined in cb
        diff --git a/test/unit/core/transform.js b/test/unit/core/transform.js
        index 17675886de..c4b0edbb8e 100644
        --- a/test/unit/core/transform.js
        +++ b/test/unit/core/transform.js
        @@ -1,144 +1,79 @@
        -suite('Transform', function() {
        -  var sketch1; // sketch without WEBGL Mode
        -  var sketch2; // skecth with WEBGL mode
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        sketch1 = p;
        -      };
        -    });
        -    new p5(function(p) {
        -      p.setup = function() {
        -        p.createCanvas(100, 100, p.WEBGL);
        -        sketch2 = p;
        -      };
        -    });
        -    done();
        -  });
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import transform from '../../../src/core/transform';
         
        -  teardown(function() {
        -    sketch1.remove();
        -    sketch2.remove();
        +suite('Transform', function() {
        +  beforeAll(function() {
        +    transform(mockP5, mockP5Prototype);
           });
         
           suite('p5.prototype.rotate', function() {
             test('should be a function', function() {
        -      assert.ok(sketch1.rotate);
        -      assert.typeOf(sketch1.rotate, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        sketch1.rotate('a');
        -      });
        -    });
        -    test('wrong param type at #1', function() {
        -      assert.validationError(function() {
        -        sketch1.rotate(sketch1.PI, 'x');
        -      });
        +      assert.ok(mockP5Prototype.rotate);
        +      assert.typeOf(mockP5Prototype.rotate, 'function');
             });
           });
         
           suite('p5.prototype.rotateX', function() {
             test('should be a function', function() {
        -      assert.ok(sketch1.rotateX);
        -      assert.typeOf(sketch1.rotateX, 'function');
        +      assert.ok(mockP5Prototype.rotateX);
        +      assert.typeOf(mockP5Prototype.rotateX, 'function');
             });
             test('throws error. should be used in WEBGL mode', function() {
               assert.throws(function() {
        -        sketch1.rotateX(100);
        +        mockP5Prototype.rotateX(100);
               }, Error);
             });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        sketch2.rotateX('x');
        -      });
        -    });
           });
         
           suite('p5.prototype.rotateY', function() {
             test('should be a function', function() {
        -      assert.ok(sketch1.rotateY);
        -      assert.typeOf(sketch1.rotateY, 'function');
        +      assert.ok(mockP5Prototype.rotateY);
        +      assert.typeOf(mockP5Prototype.rotateY, 'function');
             });
             test('throws error. should be used in WEBGL mode', function() {
               assert.throws(function() {
        -        sketch1.rotateY(100);
        +        mockP5Prototype.rotateY(100);
               }, Error);
             });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        sketch2.rotateY('x');
        -      });
        -    });
           });
         
           suite('p5.prototype.rotateZ', function() {
             test('should be a function', function() {
        -      assert.ok(sketch1.rotateZ);
        -      assert.typeOf(sketch1.rotateZ, 'function');
        +      assert.ok(mockP5Prototype.rotateZ);
        +      assert.typeOf(mockP5Prototype.rotateZ, 'function');
             });
             test('throws error. should be used in WEBGL mode', function() {
               assert.throws(function() {
        -        sketch1.rotateZ(100);
        +        mockP5Prototype.rotateZ(100);
               }, Error);
             });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        sketch2.rotateZ('x');
        -      });
        -    });
           });
         
           suite('p5.prototype.scale', function() {
             test('should be a function', function() {
        -      assert.ok(sketch1.scale);
        -      assert.typeOf(sketch1.scale, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        sketch1.scale('a');
        -      });
        +      assert.ok(mockP5Prototype.scale);
        +      assert.typeOf(mockP5Prototype.scale, 'function');
             });
           });
         
           suite('p5.prototype.shearX', function() {
             test('should be a function', function() {
        -      assert.ok(sketch1.shearX);
        -      assert.typeOf(sketch1.shearX, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        sketch1.shearX('a');
        -      });
        +      assert.ok(mockP5Prototype.shearX);
        +      assert.typeOf(mockP5Prototype.shearX, 'function');
             });
           });
         
           suite('p5.prototype.shearY', function() {
             test('should be a function', function() {
        -      assert.ok(sketch1.shearY);
        -      assert.typeOf(sketch1.shearY, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        sketch1.shearY('a');
        -      });
        +      assert.ok(mockP5Prototype.shearY);
        +      assert.typeOf(mockP5Prototype.shearY, 'function');
             });
           });
         
           suite('p5.prototype.translate', function() {
             test('should be a function', function() {
        -      assert.ok(sketch1.translate);
        -      assert.typeOf(sketch1.translate, 'function');
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        sketch1.translate('a', 10);
        -      });
        -    });
        -    test('missing param #1', function() {
        -      assert.validationError(function() {
        -        sketch1.translate(10);
        -      });
        +      assert.ok(mockP5Prototype.translate);
        +      assert.typeOf(mockP5Prototype.translate, 'function');
             });
           });
         });
        diff --git a/test/unit/core/version.js b/test/unit/core/version.js
        index 1cbce7478d..a69d33e654 100644
        --- a/test/unit/core/version.js
        +++ b/test/unit/core/version.js
        @@ -1,16 +1,17 @@
        +import p5 from '../../../src/app.js';
        +
         suite('Version', function() {
           var myp5;
         
        -  setup(function(done) {
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        diff --git a/test/unit/core/vertex.js b/test/unit/core/vertex.js
        index 1d58167d88..98077e03b1 100644
        --- a/test/unit/core/vertex.js
        +++ b/test/unit/core/vertex.js
        @@ -1,18 +1,21 @@
        +import p5 from '../../../src/app.js';
        +import { vi } from 'vitest';
        +
         suite('Vertex', function() {
           var myp5;
           let _friendlyErrorSpy;
        -  setup(function(done) {
        -    _friendlyErrorSpy = sinon.spy(p5, '_friendlyError');
        +
        +  beforeEach(function() {
        +    _friendlyErrorSpy = vi.spyOn(p5, '_friendlyError');
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        -    _friendlyErrorSpy.restore();
        +  afterEach(function() {
        +    vi.restoreAllMocks();
             myp5.remove();
           });
         
        @@ -21,25 +24,6 @@ suite('Vertex', function() {
               assert.ok(myp5.beginShape);
               assert.typeOf(myp5.beginShape, 'function');
             });
        -    test('no friendly-err-msg. missing param #0', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.beginShape();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.beginShape(myp5.BEVEL);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.beginShape(20);
        -      });
        -    });
           });
         
           suite('p5.prototype.quadraticVertex', function() {
        @@ -47,20 +31,6 @@ suite('Vertex', function() {
               assert.ok(myp5.quadraticVertex);
               assert.typeOf(myp5.quadraticVertex, 'function');
             });
        -    test('missing param #3', function() {
        -      assert.validationError(function() {
        -        myp5.quadraticVertex(80, 20, 50);
        -      });
        -    });
        -    test('missing param #5', function() {
        -      assert.validationError(function() {
        -        myp5.quadraticVertex(80, 20, 50, 50, 10);
        -      });
        -    });
        -    test('_friendlyError is called. vertex() should be used once before quadraticVertex()', function() {
        -      myp5.quadraticVertex(80, 20, 50, 50, 10, 20);
        -      assert(_friendlyErrorSpy.calledOnce, 'p5._friendlyError was not called');
        -    });
           });
         
           suite('p5.prototype.bezierVertex', function() {
        @@ -68,20 +38,6 @@ suite('Vertex', function() {
               assert.ok(myp5.bezierVertex);
               assert.typeOf(myp5.bezierVertex, 'function');
             });
        -    test('missing param #6', function() {
        -      assert.validationError(function() {
        -        myp5.bezierVertex(25, 30, 25, -30, -25);
        -      });
        -    });
        -    test('missing param #8-9', function() {
        -      assert.validationError(function() {
        -        myp5.bezierVertex(25, 30, 25, -30, -25, 30, 20);
        -      });
        -    });
        -    test('_friendlyError is called. vertex() should be used once before bezierVertex()', function() {
        -      myp5.bezierVertex(25, 30, 25, -30, -25, 30);
        -      assert(_friendlyErrorSpy.calledOnce, 'p5._friendlyError was not called');
        -    });
           });
         
           suite('p5.prototype.curveVertex', function() {
        @@ -89,11 +45,6 @@ suite('Vertex', function() {
               assert.ok(myp5.curveVertex);
               assert.typeOf(myp5.curveVertex, 'function');
             });
        -    test('missing param #1', function() {
        -      assert.validationError(function() {
        -        myp5.curveVertex(40);
        -      });
        -    });
           });
         
           suite('p5.prototype.endShape', function() {
        @@ -101,20 +52,6 @@ suite('Vertex', function() {
               assert.ok(myp5.endShape);
               assert.typeOf(myp5.endShape, 'function');
             });
        -    test('no friendly-err-msg. missing param #0', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.endShape();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.endShape(20);
        -      });
        -    });
           });
         
           suite('p5.prototype.vertex', function() {
        @@ -122,16 +59,5 @@ suite('Vertex', function() {
               assert.ok(myp5.vertex);
               assert.typeOf(myp5.vertex, 'function');
             });
        -    // p5.prototype.vertex parameter validation is absent
        -    test.skip('missing param #1', function() {
        -      assert.validationError(function() {
        -        myp5.vertex(10);
        -      });
        -    });
        -    test.skip('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.vertex('a', 1);
        -      });
        -    });
           });
         });
        diff --git a/test/unit/data/local_storage.js b/test/unit/data/local_storage.js
        index f6523b1ef5..6c8719c777 100644
        --- a/test/unit/data/local_storage.js
        +++ b/test/unit/data/local_storage.js
        @@ -1,83 +1,86 @@
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import storage from '../../../src/data/local_storage';
        +import p5Color from '../../../src/color/p5.Color';
        +import creatingReading from '../../../src/color/creating_reading';
        +import p5Vector from '../../../src/math/p5.Vector';
        +import math from '../../../src/math/math';
        +
         suite('local storage', function() {
        -  var myp5;
        -  var myBoolean = false;
        -  var myObject = { one: 1, two: { nested: true } };
        -  var myNumber = 46;
        -  var myString = 'coolio';
        -  var myColor;
        -  var myVector;
        +  const myBoolean = false;
        +  const myObject = { one: 1, two: { nested: true } };
        +  const myNumber = 46;
        +  const myString = 'coolio';
        +  let myColor;
        +  let myVector;
         
        -  var hardCodedTypeID = 'p5TypeID';
        +  const hardCodedTypeID = 'p5TypeID';
         
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        myColor = myp5.color(40, 100, 70);
        -        myVector = myp5.createVector(10, 20, 30);
        -        myp5.storeItem('myBoolean', myBoolean);
        -        myp5.storeItem('myObject', myObject);
        -        myp5.storeItem('myNumber', myNumber);
        -        myp5.storeItem('myString', myString);
        -        myp5.storeItem('myColor', myColor);
        -        myp5.storeItem('myVector', myVector);
        -        done();
        -      };
        -    });
        -  });
        +  beforeAll(function() {
        +    storage(mockP5, mockP5Prototype);
        +    p5Color(mockP5, mockP5Prototype, {});
        +    creatingReading(mockP5, mockP5Prototype);
        +    p5Vector(mockP5, mockP5Prototype);
        +    math(mockP5, mockP5Prototype);
        +
        +    mockP5Prototype.storeItem('myBoolean', myBoolean);
        +    mockP5Prototype.storeItem('myObject', myObject);
        +    mockP5Prototype.storeItem('myNumber', myNumber);
        +    mockP5Prototype.storeItem('myString', myString);
         
        -  teardown(function() {
        -    myp5.remove();
        +    myColor = mockP5Prototype.color(40, 100, 70);
        +    myVector = mockP5Prototype.createVector(10, 20, 30);
        +    mockP5Prototype.storeItem('myColor', myColor);
        +    mockP5Prototype.storeItem('myVector', myVector);
           });
         
           suite('all keys and type keys should exist in local storage', function() {
             test('boolean storage retrieval should work', function() {
        -      assert.isTrue(myp5.getItem('myBoolean') === false);
        +      assert.equal(mockP5Prototype.getItem('myBoolean'), false);
             });
             test('boolean storage should store the correct type ID', function() {
        -      assert.isTrue(
        -        localStorage.getItem('myBoolean' + hardCodedTypeID) === 'boolean'
        +      assert.equal(
        +        localStorage.getItem('myBoolean' + hardCodedTypeID), 'boolean'
               );
             });
             test('object storage should work', function() {
        -      assert.deepEqual(myp5.getItem('myObject'), {
        +      assert.deepEqual(mockP5Prototype.getItem('myObject'), {
                 one: 1,
                 two: { nested: true }
               });
             });
             test('object storage retrieval should store the correct type ID', function() {
        -      assert.isTrue(
        -        localStorage.getItem('myObject' + hardCodedTypeID) === 'object'
        +      assert.equal(
        +        localStorage.getItem('myObject' + hardCodedTypeID), 'object'
               );
             });
             test('number storage retrieval should work', function() {
        -      assert.isTrue(myp5.getItem('myNumber') === 46);
        +      assert.equal(mockP5Prototype.getItem('myNumber'), 46);
             });
             test('number storage should store the correct type ID', function() {
        -      assert.isTrue(
        -        localStorage.getItem('myNumber' + hardCodedTypeID) === 'number'
        +      assert.equal(
        +        localStorage.getItem('myNumber' + hardCodedTypeID), 'number'
               );
             });
             test('string storage retrieval should work', function() {
        -      assert.isTrue(myp5.getItem('myString') === 'coolio');
        +      assert.equal(mockP5Prototype.getItem('myString'), 'coolio');
             });
             test('string storage should store the correct type ID', function() {
        -      assert.isTrue(
        -        localStorage.getItem('myString' + hardCodedTypeID) === 'string'
        +      assert.equal(
        +        localStorage.getItem('myString' + hardCodedTypeID), 'string'
               );
             });
             test('p5 Color should retrieve as p5 Color', function() {
        -      assert.isTrue(myp5.getItem('myColor') instanceof p5.Color);
        +      assert.instanceOf(mockP5Prototype.getItem('myColor'), mockP5.Color);
             });
             test('p5 Vector should retrieve as p5 Vector', function() {
        -      assert.isTrue(myp5.getItem('myVector') instanceof p5.Vector);
        +      assert.instanceOf(mockP5Prototype.getItem('myVector'), mockP5.Vector);
             });
           });
         
           var checkRemoval = function(key) {
        -    myp5.removeItem(key);
        -    assert.deepEqual(myp5.getItem(key), null);
        -    assert.deepEqual(myp5.getItem(key + hardCodedTypeID), null);
        +    mockP5Prototype.removeItem(key);
        +    assert.deepEqual(mockP5Prototype.getItem(key), null);
        +    assert.deepEqual(mockP5Prototype.getItem(key + hardCodedTypeID), null);
           };
         
           suite('should be able to remove all items', function() {
        @@ -109,14 +112,14 @@ suite('local storage', function() {
           suite('should be able to clear all items at once', function () {
             test('should remove all items set by storeItem()', function () {
               localStorage.setItem('extra', 'stuff');
        -      myp5.clearStorage();
        -      assert.deepEqual(myp5.getItem('myBoolean'), null);
        -      assert.deepEqual(myp5.getItem('myNumber'), null);
        -      assert.deepEqual(myp5.getItem('myObject'), null);
        -      assert.deepEqual(myp5.getItem('myString'), null);
        -      assert.deepEqual(myp5.getItem('myColor'), null);
        -      assert.deepEqual(myp5.getItem('myVector'), null);
        -      assert.deepEqual(myp5.getItem('extra'), 'stuff');
        +      mockP5Prototype.clearStorage();
        +      assert.deepEqual(mockP5Prototype.getItem('myBoolean'), null);
        +      assert.deepEqual(mockP5Prototype.getItem('myNumber'), null);
        +      assert.deepEqual(mockP5Prototype.getItem('myObject'), null);
        +      assert.deepEqual(mockP5Prototype.getItem('myString'), null);
        +      assert.deepEqual(mockP5Prototype.getItem('myColor'), null);
        +      assert.deepEqual(mockP5Prototype.getItem('myVector'), null);
        +      assert.deepEqual(mockP5Prototype.getItem('extra'), 'stuff');
             });
           });
         });
        diff --git a/test/unit/data/p5.TypedDict.js b/test/unit/data/p5.TypedDict.js
        index a17f82fc67..331031a57e 100644
        --- a/test/unit/data/p5.TypedDict.js
        +++ b/test/unit/data/p5.TypedDict.js
        @@ -1,38 +1,31 @@
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import typeDict from '../../../src/data/p5.TypedDict';
        +
         suite('Dictionary Objects', function() {
        -  var myp5;
        -  var stringDict;
        -  var numberDict;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        stringDict = myp5.createStringDict('happy', 'coding');
        -        numberDict = myp5.createNumberDict(1, 2);
        -        done();
        -      };
        -    });
        -  });
        +  let stringDict;
        +  let numberDict;
         
        -  teardown(function() {
        -    myp5.remove();
        +  beforeAll(function() {
        +    typeDict(mockP5, mockP5Prototype);
        +    stringDict = mockP5Prototype.createStringDict('happy', 'coding');
        +    numberDict = mockP5Prototype.createNumberDict(1, 2);
           });
         
           suite('p5.prototype.stringDict', function() {
             test('should be created', function() {
        -      assert.isTrue(stringDict instanceof p5.StringDict);
        +      assert.instanceOf(stringDict, mockP5.StringDict);
             });
         
             test('has correct structure', function() {
               assert.deepEqual(
        -        JSON.stringify(stringDict),
        -        JSON.stringify({ data: { happy: 'coding' } })
        +        stringDict,
        +        { data: { happy: 'coding' } }
               );
             });
         
             test('should have correct size', function() {
               var amt = stringDict.size();
        -      assert.isTrue(amt === Object.keys(stringDict.data).length);
        +      assert.lengthOf(Object.keys(stringDict.data), amt);
             });
         
             test('should add new key-value pairs', function() {
        @@ -58,19 +51,19 @@ suite('Dictionary Objects', function() {
         
           suite('p5.prototype.numberDict', function() {
             test('should be created', function() {
        -      assert.isTrue(numberDict instanceof p5.NumberDict);
        +      assert.instanceOf(numberDict, mockP5.NumberDict);
             });
         
             test('has correct structure', function() {
               assert.deepEqual(
        -        JSON.stringify(numberDict),
        -        JSON.stringify({ data: { 1: 2 } })
        +        numberDict,
        +        { data: { 1: 2 } }
               );
             });
         
             test('should have correct size', function() {
               var amt = numberDict.size();
        -      assert.isTrue(amt === Object.keys(numberDict.data).length);
        +      assert.lengthOf(Object.keys(numberDict.data), amt);
             });
         
             test('should add new key-value pairs', function() {
        diff --git a/test/unit/dom/dom.js b/test/unit/dom/dom.js
        index 0ab5bb66dd..e6a8df86ab 100644
        --- a/test/unit/dom/dom.js
        +++ b/test/unit/dom/dom.js
        @@ -1,56 +1,66 @@
        +import { testSketchWithPromise } from '../../js/p5_helpers';
        +
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import dom from '../../../src/dom/dom';
        +import { Element } from '../../../src/dom/p5.Element';
        +import creatingReading from '../../../src/color/creating_reading';
        +import p5Color from '../../../src/color/p5.Color';
        +
         suite('DOM', function() {
        +  beforeAll(() => {
        +    dom(mockP5, mockP5Prototype);
        +    creatingReading(mockP5, mockP5Prototype);
        +    p5Color(mockP5, mockP5Prototype, {});
        +  });
        +
        +  // Selectors
           suite('p5.prototype.select', function() {
        -    /**
        -     * Uses p5 in instance-mode inside a custom container.
        -     * All elements are attached inside the container for testing
        -     * And cleaned up on teardown.
        -     */
        -    let myp5;
             let myp5Container;
         
        -    setup(function(done) {
        +    const generateButton = (name, className = null) => {
        +      const button = mockP5Prototype.createButton(name);
        +      if (className) {
        +        button.class(className);
        +      }
        +      return button;
        +    };
        +
        +    const generateDiv = (id = null, className = null) => {
        +      const div = mockP5Prototype.createDiv();
        +      if (id) {
        +        div.id(id);
        +      }
        +      if (className) {
        +        div.class(className);
        +      }
        +      return div;
        +    };
        +
        +    beforeEach(function() {
               myp5Container = document.createElement('div');
               document.body.appendChild(myp5Container);
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      }, myp5Container);
             });
         
        -    teardown(function() {
        -      myp5.remove();
        -      if (myp5Container && myp5Container.parentNode) {
        -        myp5Container.parentNode.removeChild(myp5Container);
        -      }
        -      p5Container = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should return only one p5.element if match is found', function() {
               // Adding 2 buttons to container
        -      myp5.createCheckbox('Text 1');
        -      myp5.createCheckbox('Text 2');
        -      const result = myp5.select('input');
        +      mockP5Prototype.createCheckbox('Text 1');
        +      mockP5Prototype.createCheckbox('Text 2');
        +      const result = mockP5Prototype.select('input');
         
        -      assert.instanceOf(result, p5.Element);
        +      assert.instanceOf(result, Element);
             });
         
        -    const generateButton = (name, className = null) => {
        -      const button = myp5.createButton(name);
        -      if (className) {
        -        button.class(className);
        -      }
        -      return button;
        -    };
        -
             test('should find element by class name', function() {
               // Creates 2 buttons with same class and test if it selects only one.
               const testClassName = 'test-button';
               const testButton = generateButton('Button 1', testClassName);
               generateButton('Button 2', testClassName);
         
        -      const result = myp5.select(`.${testClassName}`);
        +      const result = mockP5Prototype.select(`.${testClassName}`);
               assert.deepEqual(result.elt, testButton.elt);
             });
         
        @@ -59,10 +69,10 @@ suite('DOM', function() {
               const testClassName = 'test-button';
               generateButton('Button 1', testClassName);
               const testButton = generateButton('Button 2', testClassName);
        -      const testContainer = myp5.createDiv();
        +      const testContainer = mockP5Prototype.createDiv();
               testButton.parent(testContainer);
         
        -      const result = myp5.select(`.${testClassName}`, testContainer);
        +      const result = mockP5Prototype.select(`.${testClassName}`, testContainer);
               assert.deepEqual(testButton.elt, result.elt);
             });
         
        @@ -70,7 +80,7 @@ suite('DOM', function() {
               // Gives unused className and tests if it returns null
               const testClassName = 'test-button';
               generateButton('Test Button', testClassName);
        -      const result = myp5.select('.classNameWithTypo');
        +      const result = mockP5Prototype.select('.classNameWithTypo');
               assert.isNull(result);
             });
         
        @@ -78,7 +88,7 @@ suite('DOM', function() {
               // Creates 2 similar elements and tests if it selects only one.
               const testButton = generateButton('Button 1');
               generateButton('Button 2');
        -      const result = myp5.select('button');
        +      const result = mockP5Prototype.select('button');
               assert.deepEqual(result.elt, testButton.elt);
             });
         
        @@ -87,30 +97,19 @@ suite('DOM', function() {
               // selects inside the container
               generateButton('Button 1');
               const testButton = generateButton('Button 2');
        -      const testDiv = myp5.createDiv();
        +      const testDiv = mockP5Prototype.createDiv();
               testButton.parent(testDiv);
         
        -      const result = myp5.select('button', testDiv);
        +      const result = mockP5Prototype.select('button', testDiv);
               assert.deepEqual(result.elt, testButton.elt);
             });
         
             test('should return null when no matches are found by tag name', function() {
               generateButton('Test Button');
        -      const result = myp5.select('video', myp5Container);
        +      const result = mockP5Prototype.select('video', myp5Container);
               assert.isNull(result);
             });
         
        -    const generateDiv = (id = null, className = null) => {
        -      const div = myp5.createDiv();
        -      if (id) {
        -        div.id(id);
        -      }
        -      if (className) {
        -        div.class(className);
        -      }
        -      return div;
        -    };
        -
             test('should select element in container using CSS selector with ID', function() {
               const divID = 'divId';
               const testDiv = generateDiv(divID);
        @@ -118,7 +117,7 @@ suite('DOM', function() {
               generateButton('Button 2');
               testButton.parent(testDiv);
         
        -      const result = myp5.select(`#${divID} button`);
        +      const result = mockP5Prototype.select(`#${divID} button`);
               assert.deepEqual(result.elt, testButton.elt);
             });
         
        @@ -129,49 +128,37 @@ suite('DOM', function() {
               generateButton('Button 2');
               testButton.parent(testDiv);
         
        -      const result = myp5.select(`.${divClass} button`);
        +      const result = mockP5Prototype.select(`.${divClass} button`);
               assert.deepEqual(result.elt, testButton.elt);
             });
           });
         
           suite('p5.prototype.selectAll', function() {
        -    let myp5;
        -    let myp5Container;
        +    beforeEach(function() {
        +      const mydiv = document.createElement('div');
        +      mydiv.setAttribute('id', 'main');
         
        -    setup(function(done) {
        -      myp5Container = document.createElement('div');
        -      document.body.appendChild(myp5Container);
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          let mydiv = document.createElement('div');
        -          mydiv.setAttribute('id', 'main');
        -          let childbutton = document.createElement('button');
        -          childbutton.setAttribute('class', 'p5button');
        -          mydiv.append(childbutton);
        -          let otherbutton = document.createElement('button');
        -          otherbutton.setAttribute('class', 'p5button');
        -          myp5Container.append(mydiv, otherbutton);
        -          done();
        -        };
        -      }, myp5Container);
        +      const childbutton = document.createElement('button');
        +      childbutton.setAttribute('class', 'p5button');
        +      mydiv.append(childbutton);
        +
        +      const otherbutton = document.createElement('button');
        +      otherbutton.setAttribute('class', 'p5button');
        +
        +      document.body.append(mydiv, otherbutton);
             });
         
        -    teardown(function() {
        -      myp5.remove();
        -      if (myp5Container && myp5Container.parentNode) {
        -        myp5Container.parentNode.removeChild(myp5Container);
        -      }
        -      myp5Container = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should return an array', function() {
        -      const elements = myp5.selectAll('button');
        +      const elements = mockP5Prototype.selectAll('button');
               assert.isArray(elements);
             });
         
             test('should return empty array when no matching classes are found', function() {
        -      const elements = myp5.selectAll('.randomElements');
        +      const elements = mockP5Prototype.selectAll('.randomElements');
               assert.isArray(elements);
               assert.lengthOf(elements, 0);
             });
        @@ -185,15 +172,15 @@ suite('DOM', function() {
         
             test('should find all elements with matching class name', function() {
               const testClassName = 'p5button';
        -      const p5Results = myp5.selectAll(`.${testClassName}`);
        -      const domResults = myp5Container.getElementsByClassName(testClassName);
        +      const p5Results = mockP5Prototype.selectAll(`.${testClassName}`);
        +      const domResults = document.getElementsByClassName(testClassName);
               matchResults(p5Results, domResults);
             });
         
             test('should find all elements with matching class name in given container', function() {
               const testClassName = 'p5button';
               const parentContainerId = 'main';
        -      const p5Results = myp5.selectAll(
        +      const p5Results = mockP5Prototype.selectAll(
                 `.${testClassName}`,
                 `#${parentContainerId}`
               );
        @@ -204,15 +191,15 @@ suite('DOM', function() {
         
             test('should find all elements with matching tag name', function() {
               const testTagName = 'button';
        -      const p5Results = myp5.selectAll(testTagName);
        -      const domResults = myp5Container.getElementsByTagName(testTagName);
        +      const p5Results = mockP5Prototype.selectAll(testTagName);
        +      const domResults = document.getElementsByTagName(testTagName);
               matchResults(p5Results, domResults);
             });
         
             test('should find all elements with matching tag name in given container', function() {
               const testTagName = 'button';
               const parentContainerId = 'main';
        -      const p5Results = myp5.selectAll(testTagName, `#${parentContainerId}`);
        +      const p5Results = mockP5Prototype.selectAll(testTagName, `#${parentContainerId}`);
               const containerElement = document.getElementById(parentContainerId);
               const domResults = containerElement.getElementsByTagName(testTagName);
               matchResults(p5Results, domResults);
        @@ -221,57 +208,15 @@ suite('DOM', function() {
             test('should find all elements in container using CSS selector with id', function() {
               const testTagName = 'button';
               const parentContainerId = 'main';
        -      const p5Results = myp5.selectAll(`#${parentContainerId} ${testTagName}`);
        +      const p5Results = mockP5Prototype.selectAll(`#${parentContainerId} ${testTagName}`);
               const containerElement = document.getElementById(parentContainerId);
               const domResults = containerElement.getElementsByTagName(testTagName);
               matchResults(p5Results, domResults);
             });
           });
         
        -  suite('p5.prototype.removeElements', function() {
        -    let myp5;
        -    let myp5Container;
        -
        -    setup(function(done) {
        -      myp5Container = document.createElement('div');
        -      document.body.appendChild(myp5Container);
        -      new p5(function(p) {
        -        p.setup = function() {
        -          // configure p5 to not add a canvas by default.
        -          p.noCanvas();
        -          myp5 = p;
        -          done();
        -        };
        -      }, myp5Container);
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (myp5Container && myp5Container.parentNode) {
        -        myp5Container.parentNode.removeChild(myp5Container);
        -      }
        -      myp5Container = null;
        -    });
        -
        -    test('should remove all elements created by p5 except Canvas', function() {
        -      // creates 6 elements one of which is a canvas, then calls
        -      // removeElements and tests if only canvas is left.
        -      const tags = ['a', 'button', 'canvas', 'div', 'p', 'video'];
        -      for (const tag of tags) {
        -        myp5.createElement(tag);
        -      }
        -      // Check if all elements are created.
        -      assert.deepEqual(myp5Container.childElementCount, tags.length);
        -
        -      // Call removeElements and check if only canvas is remaining
        -      myp5.removeElements();
        -      assert.deepEqual(myp5Container.childElementCount, 1);
        -      const remainingElement = myp5Container.children[0];
        -      assert.instanceOf(remainingElement, HTMLCanvasElement);
        -    });
        -  });
        -
        -  suite('p5.Element.prototype.changed', function() {
        +  // Events
        +  suite.todo('p5.Element.prototype.changed', function() {
             testSketchWithPromise(
               'should trigger callback when element changes',
               function(sketch, resolve, reject) {
        @@ -298,7 +243,7 @@ suite('DOM', function() {
             );
           });
         
        -  suite('p5.Element.prototype.input', function() {
        +  suite.todo('p5.Element.prototype.input', function() {
             testSketchWithPromise(
               'should trigger callback when input is provided',
               function(sketch, resolve, reject) {
        @@ -325,211 +270,169 @@ suite('DOM', function() {
             );
           });
         
        -  suite('p5.prototype.createDiv', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        +  suite.todo('p5.prototype.drop', function() {
        +    testSketchWithPromise('drop fires multiple events', function(
        +      sketch,
        +      resolve,
        +      reject
        +    ) {
        +      let testElement;
        +      let fileFnCounter = 0;
        +      let eventFnCounter = 0;
        +      sketch.setup = function() {
        +        testElement = sketch.createDiv('Drop files inside');
        +
        +        // Setup test functions and constants
        +        const file1 = new File(['foo'], 'foo.txt', { type: 'text/plain' });
        +        const file2 = new File(['foo'], 'foo.txt', { type: 'text/plain' });
        +        const hasFinished = () => {
        +          if (fileFnCounter > 1 && eventFnCounter === 1) resolve();
                 };
        -      });
        +        const testFileFn = () => {
        +          fileFnCounter += 1;
        +          hasFinished();
        +        };
        +        const testEventFn = () => {
        +          eventFnCounter += 1;
        +          hasFinished();
        +        };
        +        testElement.drop(testFileFn, testEventFn);
        +
        +        // Fire a mock drop and test the method
        +        const mockedEvent = new Event('drop');
        +        mockedEvent.dataTransfer = { files: [file1, file2] };
        +        testElement.elt.dispatchEvent(mockedEvent);
        +      };
             });
        +  });
         
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +  // Add/remove elements
        +  suite('p5.prototype.createDiv', function() {
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
        -      assert.isFunction(myp5.createDiv);
        +      assert.isFunction(mockP5Prototype.createDiv);
             });
         
             test('should return a p5.Element of div type', function() {
        -      testElement = myp5.createDiv();
        -      assert.instanceOf(testElement, p5.Element);
        +      const testElement = mockP5Prototype.createDiv();
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLDivElement);
             });
         
             test('should set given param as innerHTML of div', function() {
               const testHTML = '<p>Hello</p>';
        -      testElement = myp5.createDiv(testHTML);
        +      const testElement = mockP5Prototype.createDiv(testHTML);
               assert.deepEqual(testElement.elt.innerHTML, testHTML);
             });
           });
         
           suite('p5.prototype.createP', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
        -      assert.isFunction(myp5.createP);
        +      assert.isFunction(mockP5Prototype.createP);
             });
         
             test('should return a p5.Element of p type', function() {
        -      testElement = myp5.createP();
        -      assert.instanceOf(testElement, p5.Element);
        +      const testElement = mockP5Prototype.createP();
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLParagraphElement);
             });
         
             test('should set given param as innerHTML of p', function() {
               const testHTML = '<b>Hello</b>';
        -      testElement = myp5.createP(testHTML);
        +      const testElement = mockP5Prototype.createP(testHTML);
               assert.deepEqual(testElement.elt.innerHTML, testHTML);
             });
           });
         
           suite('p5.prototype.createSpan', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
        -      assert.isFunction(myp5.createSpan);
        +      assert.isFunction(mockP5Prototype.createSpan);
             });
         
             test('should return a p5.Element of span type', function() {
        -      testElement = myp5.createSpan();
        -      assert.instanceOf(testElement, p5.Element);
        +      const testElement = mockP5Prototype.createSpan();
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLSpanElement);
             });
         
             test('should set given param as innerHTML of span', function() {
               const testHTML = '<em>Hello</em>';
        -      testElement = myp5.createSpan(testHTML);
        +      const testElement = mockP5Prototype.createSpan(testHTML);
               assert.deepEqual(testElement.elt.innerHTML, testHTML);
             });
           });
         
           suite('p5.prototype.createImg', function() {
        -    let myp5;
        -    let testElement;
        -    const imagePath = 'unit/assets/cat.jpg';
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        +    const imagePath = '/test/unit/assets/cat.jpg';
         
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
        -      assert.isFunction(myp5.createImg);
        +      assert.isFunction(mockP5Prototype.createImg);
             });
         
             test('should return p5.Element of image type', function() {
        -      testElement = myp5.createImg(imagePath, '');
        -      assert.instanceOf(testElement, p5.Element);
        +      const testElement = mockP5Prototype.createImg(imagePath, '');
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLImageElement);
             });
         
             test('should set src of image from params', function() {
        -      testElement = myp5.createImg(imagePath, '');
        +      const testElement = mockP5Prototype.createImg(imagePath, '');
               assert.isTrue(testElement.elt.src.endsWith(imagePath));
             });
         
             test('should set alt from params if given', function() {
               const alternativeText = 'Picture of a cat';
        -      testElement = myp5.createImg(imagePath, alternativeText);
        +      const testElement = mockP5Prototype.createImg(imagePath, alternativeText);
               assert.deepEqual(testElement.elt.alt, alternativeText);
             });
         
             test('should set crossOrigin from params if given', function() {
               const crossOrigin = 'anonymous';
        -      testElement = myp5.createImg(imagePath, '', crossOrigin);
        +      const testElement = mockP5Prototype.createImg(imagePath, '', crossOrigin);
               assert.deepEqual(testElement.elt.crossOrigin, crossOrigin);
             });
         
        -    testSketchWithPromise(
        -      'should trigger callback when image is loaded',
        -      function(sketch, resolve, reject) {
        -        sketch.setup = function() {
        -          testElement = sketch.createImg(imagePath, '', '', resolve);
        -          testElement.elt.dispatchEvent(new Event('load'));
        -        };
        -      }
        -    );
        +    // testSketchWithPromise(
        +    //   'should trigger callback when image is loaded',
        +    //   function(sketch, resolve, reject) {
        +    //     sketch.setup = function() {
        +    //       testElement = sketch.createImg(imagePath, '', '', resolve);
        +    //       testElement.elt.dispatchEvent(new Event('load'));
        +    //     };
        +    //   }
        +    // );
           });
         
           suite('p5.prototype.createA', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -        testElement = null;
        -      }
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should return a p5.Element of anchor type', () => {
        -      testElement = myp5.createA('', '');
        -      assert.instanceOf(testElement, p5.Element);
        +      const testElement = mockP5Prototype.createA('', '');
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLAnchorElement);
             });
         
             test('creates anchor with given link & text', function() {
               const testUrl = 'http://p5js.org/';
               const testText = 'p5js website';
        -      testElement = myp5.createA(testUrl, testText);
        +      const testElement = mockP5Prototype.createA(testUrl, testText);
         
               assert.deepEqual(testElement.elt.href, testUrl);
               assert.deepEqual(testElement.elt.text, testText);
        @@ -537,113 +440,66 @@ suite('DOM', function() {
         
             test('creates anchor with given target', function() {
               const testTarget = 'blank';
        -      testElement = myp5.createA('http://p5js.org', 'p5js website', testTarget);
        +      const testElement = mockP5Prototype.createA('http://p5js.org', 'p5js website', testTarget);
               assert.deepEqual(testElement.elt.target, testTarget);
             });
           });
         
           suite('p5.prototype.createSlider', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should return a p5.Element of slider type', () => {
        -      testElement = myp5.createSlider(0, 100, 0, 1);
        -      assert.instanceOf(testElement, p5.Element);
        +      const testElement = mockP5Prototype.createSlider(0, 100, 0, 1);
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLInputElement);
               assert.deepEqual(testElement.elt.type, 'range');
             });
         
             test('should set min and max values', function() {
        -      let testElement = myp5.createSlider(20, 80);
        +      const testElement = mockP5Prototype.createSlider(20, 80);
               assert.deepEqual(testElement.elt.min, '20');
               assert.deepEqual(testElement.elt.max, '80');
             });
         
             test('should set slider position', function() {
        -      let testElement = myp5.createSlider(20, 80, 50);
        +      const testElement = mockP5Prototype.createSlider(20, 80, 50);
               assert.deepEqual(testElement.elt.value, '50');
             });
         
             test('should set step value', function() {
        -      testElement = myp5.createSlider(20, 80, 10, 5);
        +      const testElement = mockP5Prototype.createSlider(20, 80, 10, 5);
               assert.deepEqual(testElement.elt.step, '5');
             });
           });
         
           suite('p5.prototype.createButton', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should return a p5.Element of button type', function() {
        -      testElement = myp5.createButton('testButton', 'testButton');
        -      assert.instanceOf(testElement, p5.Element);
        +      const testElement = mockP5Prototype.createButton('testButton', 'testButton');
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLButtonElement);
             });
         
        -    testSketchWithPromise(
        -      'should trigger callback when mouse is pressed',
        -      function(sketch, resolve, reject) {
        -        sketch.setup = function() {
        -          const testElement = sketch.createButton('test');
        -          testElement.mousePressed(resolve);
        -          testElement.elt.dispatchEvent(new Event('mousedown'));
        -        };
        -      }
        -    );
        +    // testSketchWithPromise(
        +    //   'should trigger callback when mouse is pressed',
        +    //   function(sketch, resolve, reject) {
        +    //     sketch.setup = function() {
        +    //       const testElement = sketch.createButton('test');
        +    //       testElement.mousePressed(resolve);
        +    //       testElement.elt.dispatchEvent(new Event('mousedown'));
        +    //     };
        +    //   }
        +    // );
           });
         
        -  // Tests for createCheckbox
           suite('p5.prototype.createCheckbox', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             // helper functions
        @@ -658,76 +514,60 @@ suite('DOM', function() {
                 : null;
         
             test('should be a function', function() {
        -      assert.isFunction(myp5.createCheckbox);
        +      assert.isFunction(mockP5Prototype.createCheckbox);
             });
         
             test('should return a p5.Element with checkbox as descendant', function() {
        -      testElement = myp5.createCheckbox();
        +      const testElement = mockP5Prototype.createCheckbox();
               const checkboxElement = getCheckboxElement(testElement);
         
        -      assert.instanceOf(testElement, p5.Element);
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(checkboxElement, HTMLInputElement);
             });
         
             test('calling createCheckbox(label) should create checkbox and set its label', function() {
               const labelValue = 'label';
        -      testElement = myp5.createCheckbox(labelValue);
        +      const testElement = mockP5Prototype.createCheckbox(labelValue);
               const spanElement = getSpanElement(testElement);
               const testElementLabelValue = getSpanElement(testElement)
                 ? getSpanElement(testElement).innerHTML
                 : '';
         
        -      assert.instanceOf(testElement, p5.Element);
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(spanElement, HTMLSpanElement);
               assert.deepEqual(testElementLabelValue, labelValue);
             });
         
             test('calling createCheckbox(label, true) should create a checked checkbox and set its label', function() {
               const labelValue = 'label';
        -      testElement = myp5.createCheckbox(labelValue, true);
        +      const testElement = mockP5Prototype.createCheckbox(labelValue, true);
         
               const spanElement = getSpanElement(testElement);
               const testElementLabelValue = getSpanElement(testElement)
                 ? getSpanElement(testElement).innerHTML
                 : '';
         
        -      assert.instanceOf(testElement, p5.Element);
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(spanElement, HTMLSpanElement);
               assert.deepEqual(testElementLabelValue, labelValue);
               assert.isTrue(testElement.checked());
             });
         
             test('calling checked() should return checked value of checkbox', function() {
        -      testElement = myp5.createCheckbox('', true);
        +      const testElement = mockP5Prototype.createCheckbox('', true);
               assert.isTrue(testElement.checked());
             });
         
             test('calling checked(true) should set checked value of checkbox', function() {
        -      testElement = myp5.createCheckbox('', false);
        +      const testElement = mockP5Prototype.createCheckbox('', false);
               testElement.checked(true);
               assert.isTrue(testElement.checked());
             });
           });
         
           suite('p5.prototype.createSelect', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -        testElement = null;
        -      }
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             const createHTMLSelect = options => {
        @@ -742,24 +582,24 @@ suite('DOM', function() {
             };
         
             test('should be a function', function() {
        -      assert.isFunction(myp5.createSelect);
        +      assert.isFunction(mockP5Prototype.createSelect);
             });
         
             test('should return p5.Element of select HTML Element', function() {
        -      testElement = myp5.createSelect();
        -      assert.instanceOf(testElement, p5.Element);
        +      const testElement = mockP5Prototype.createSelect();
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLSelectElement);
             });
         
             test('should return p5.Element when select element is passed', function() {
        -      selectElement = createHTMLSelect(['option1', 'option2']);
        -      testElement = myp5.createSelect(selectElement);
        +      const selectElement = createHTMLSelect(['option1', 'option2']);
        +      const testElement = mockP5Prototype.createSelect(selectElement);
               assert.deepEqual(testElement.elt, selectElement);
             });
         
             test('calling option(newName) should add a new option', function() {
               const testOptions = ['John', 'Bethany', 'Dwayne'];
        -      testElement = myp5.createSelect();
        +      const testElement = mockP5Prototype.createSelect();
               for (const optionName of testOptions) testElement.option(optionName);
         
               const htmlOptions = [];
        @@ -773,7 +613,7 @@ suite('DOM', function() {
             test('calling option(name, newValue) should update value of option', function() {
               const optionName = 'john';
               const testValues = [1, 'abc', true];
        -      testElement = myp5.createSelect();
        +      const testElement = mockP5Prototype.createSelect();
               testElement.option(optionName, 0);
         
               for (const newValue of testValues) {
        @@ -784,7 +624,7 @@ suite('DOM', function() {
             });
         
             test('calling value() should return current selected option', function() {
        -      testElement = myp5.createSelect();
        +      const testElement = mockP5Prototype.createSelect();
               testElement.option('Sunday');
               testElement.option('Monday');
               testElement.elt.options[1].selected = true;
        @@ -792,8 +632,8 @@ suite('DOM', function() {
             });
         
             test('calling selected() should return all selected options', function() {
        -      testElement = myp5.createSelect(true);
        -      options = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
        +      const testElement = mockP5Prototype.createSelect(true);
        +      const options = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
               for (const optionName of options) testElement.option(optionName);
         
               // Select multiple options
        @@ -808,7 +648,7 @@ suite('DOM', function() {
             });
         
             test('should update select value when HTML special characters are in the name', function() {
        -      testElement = myp5.createSelect(true);
        +      const testElement = mockP5Prototype.createSelect(true);
               testElement.option('&', 'foo');
               assert.equal(testElement.elt.options.length, 1);
               assert.equal(testElement.elt.options[0].value, 'foo');
        @@ -817,8 +657,8 @@ suite('DOM', function() {
             });
         
             test('calling selected(value) should updated selectedIndex', function() {
        -      testElement = myp5.createSelect(true);
        -      options = ['Sunday', 'Monday', 'Tuesday', 'Friday'];
        +      const testElement = mockP5Prototype.createSelect(true);
        +      const options = ['Sunday', 'Monday', 'Tuesday', 'Friday'];
               for (const optionName of options) testElement.option(optionName);
         
               // Select multiple options and check if the values get updated
        @@ -831,15 +671,15 @@ suite('DOM', function() {
             });
         
             test('calling disable() should disable the whole dropdown', function() {
        -      testElement = myp5.createSelect(true);
        +      const testElement = mockP5Prototype.createSelect(true);
               testElement.disable();
         
               assert.isTrue(testElement.elt.disabled);
             });
         
             test('should disable an option when disable() method invoked with option name', function() {
        -      testElement = myp5.createSelect(true);
        -      options = ['Sunday', 'Monday', 'Tuesday', 'Friday'];
        +      const testElement = mockP5Prototype.createSelect(true);
        +      const options = ['Sunday', 'Monday', 'Tuesday', 'Friday'];
               for (const optionName of options) testElement.option(optionName);
         
               const disabledIndex = 2;
        @@ -850,24 +690,8 @@ suite('DOM', function() {
           });
         
           suite('p5.prototype.createRadio', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -        testElement = null;
        -      }
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             // Helper functions
        @@ -896,19 +720,19 @@ suite('DOM', function() {
                 .map(el => (isRadioInput(el) ? el : el.firstElementChild));
         
             test('should be a function', function() {
        -      assert.isFunction(myp5.createRadio);
        +      assert.isFunction(mockP5Prototype.createRadio);
             });
         
             test('should return p5.Element of radio type', function() {
        -      testElement = myp5.createRadio();
        -      assert.instanceOf(testElement, p5.Element);
        +      const testElement = mockP5Prototype.createRadio();
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLDivElement);
             });
         
             test('should return p5.Element from existing radio Element', function() {
               const options = ['Saturday', 'Sunday'];
               const radioElement = createRadioElement(options);
        -      testElement = myp5.createRadio(radioElement);
        +      const testElement = mockP5Prototype.createRadio(radioElement);
         
               assert.deepEqual(testElement.elt, radioElement);
             });
        @@ -916,7 +740,7 @@ suite('DOM', function() {
             test('calling option(value) should return existing radio element', function() {
               const options = ['Saturday', 'Sunday'];
               const radioElement = createRadioElement(options);
        -      testElement = myp5.createRadio(radioElement);
        +      const testElement = mockP5Prototype.createRadio(radioElement);
               for (const radioInput of getChildren(radioElement)) {
                 const optionEl = testElement.option(radioInput.value);
                 assert.deepEqual(radioInput, optionEl);
        @@ -927,7 +751,7 @@ suite('DOM', function() {
             test('calling option(newValue) should create a new radio input', function() {
               const testName = 'defaultRadio';
               const options = ['Saturday', 'Sunday'];
        -      testElement = myp5.createRadio(testName);
        +      const testElement = mockP5Prototype.createRadio(testName);
               let count = 0;
               for (const option of options) {
                 const optionEl = testElement.option(option);
        @@ -945,7 +769,7 @@ suite('DOM', function() {
             test('calling option(value, label) should set label of option', function() {
               const testName = 'defaultRadio';
               const options = ['Saturday', 'Sunday'];
        -      testElement = myp5.createRadio(testName);
        +      const testElement = mockP5Prototype.createRadio(testName);
               for (const option of options) {
                 const optionLabel = `${option}-label`;
                 const optionEl = testElement.option(option, optionLabel);
        @@ -960,7 +784,7 @@ suite('DOM', function() {
               const testName = 'defaultRadio';
               const options = ['Saturday', 'Sunday'];
               const radioElement = createRadioElement(options);
        -      testElement = myp5.createRadio(radioElement, testName);
        +      const testElement = mockP5Prototype.createRadio(radioElement, testName);
         
               for (const option of options) {
                 const optionEl = testElement.option(option);
        @@ -971,7 +795,7 @@ suite('DOM', function() {
             test('calling remove(value) should remove option', function() {
               const options = ['Monday', 'Friday', 'Saturday', 'Sunday'];
               const radioElement = createRadioElement(options);
        -      testElement = myp5.createRadio(radioElement);
        +      const testElement = mockP5Prototype.createRadio(radioElement);
         
               // Remove element
               const testValue = 'Friday';
        @@ -987,7 +811,7 @@ suite('DOM', function() {
             test('calling value() should return selected value', function() {
               const options = ['Monday', 'Friday', 'Saturday', 'Sunday'];
               const selectedValue = options[1];
        -      testElement = myp5.createRadio();
        +      const testElement = mockP5Prototype.createRadio();
               for (const option of options) testElement.option(option);
               testElement.selected(selectedValue);
               assert.deepEqual(testElement.value(), selectedValue);
        @@ -995,7 +819,7 @@ suite('DOM', function() {
         
             test('calling selected(value) should select a value and return it', function() {
               const options = ['Monday', 'Friday', 'Saturday', 'Sunday'];
        -      testElement = myp5.createRadio();
        +      const testElement = mockP5Prototype.createRadio();
               for (const option of options) testElement.option(option);
         
               for (const option of options) {
        @@ -1007,7 +831,7 @@ suite('DOM', function() {
         
             test('calling selected() should return the currently selected option', function() {
               const options = ['Monday', 'Friday', 'Saturday', 'Sunday'];
        -      testElement = myp5.createRadio();
        +      const testElement = mockP5Prototype.createRadio();
               for (const option of options) testElement.option(option);
         
               const testOption = getChildren(testElement.elt)[1];
        @@ -1019,7 +843,7 @@ suite('DOM', function() {
         
             test('calling disable() should disable all the radio inputs', function() {
               const options = ['Monday', 'Friday', 'Saturday', 'Sunday'];
        -      testElement = myp5.createRadio();
        +      const testElement = mockP5Prototype.createRadio();
               for (const option of options) testElement.option(option);
         
               testElement.disable();
        @@ -1030,100 +854,69 @@ suite('DOM', function() {
           });
         
           suite('p5.prototype.createColorPicker', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
        -      assert.isFunction(myp5.createColorPicker);
        +      assert.isFunction(mockP5Prototype.createColorPicker);
             });
         
             test('should return p5.Element of input[color] type', function() {
        -      testElement = myp5.createColorPicker();
        +      const testElement = mockP5Prototype.createColorPicker();
         
        -      assert.instanceOf(testElement, p5.Element);
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLInputElement);
               assert.deepEqual(testElement.elt.type, 'color');
             });
         
        -    test('should accept a p5.Color as param', function() {
        -      const testColor = myp5.color('red');
        -      testElement = myp5.createColorPicker(testColor);
        +    // TODO: pending finalization of p5.Color implementation
        +    test.todo('should accept a p5.Color as param', function() {
        +      const testColor = mockP5Prototype.color('red');
        +      const testElement = mockP5Prototype.createColorPicker(testColor);
         
               assert.deepEqual(testElement.elt.value, testColor.toString('#rrggbb'));
             });
         
        -    test('should accept a string as param', function() {
        +    test.todo('should accept a string as param', function() {
               const testColorString = '#f00f00';
        -      testElement = myp5.createColorPicker(testColorString);
        +      const testElement = mockP5Prototype.createColorPicker(testColorString);
               assert.deepEqual(testElement.elt.value, testColorString);
             });
         
        -    test('calling color() should return the current color as p5.color', function() {
        +    test.todo('calling color() should return the current color as p5.color', function() {
               const testColorString = 'red';
        -      const testColor = myp5.color(testColorString);
        -      testElement = myp5.createColorPicker(testColorString);
        +      const testColor = mockP5Prototype.color(testColorString);
        +      const testElement = mockP5Prototype.createColorPicker(testColorString);
               assert.deepEqual(testElement.color(), testColor);
             });
         
        -    test('calling value() should return hex string of color', function() {
        -      const testColor = myp5.color('aqua');
        -      testElement = myp5.createColorPicker(testColor);
        +    test.todo('calling value() should return hex string of color', function() {
        +      const testColor = mockP5Prototype.color('aqua');
        +      const testElement = mockP5Prototype.createColorPicker(testColor);
               assert.deepEqual(testElement.value(), testColor.toString('#rrggbb'));
             });
           });
         
           suite('p5.prototype.createInput', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
        -      assert.isFunction(myp5.createInput);
        +      assert.isFunction(mockP5Prototype.createInput);
             });
         
             test('should return p5.Element of input type', function() {
        -      testElement = myp5.createInput();
        -      assert.instanceOf(testElement, p5.Element);
        +      const testElement = mockP5Prototype.createInput();
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLInputElement);
             });
         
             test('should set given value as input', function() {
               const testValues = ['123', '', 'Hello world'];
               for (const value of testValues) {
        -        testElement = myp5.createInput(value);
        +        const testElement = mockP5Prototype.createInput(value);
                 assert.deepEqual(testElement.elt.value, value);
               }
             });
        @@ -1131,37 +924,15 @@ suite('DOM', function() {
             test('should create input of given type and value', function() {
               const testType = 'password';
               const testValue = '1234056789';
        -      testElement = myp5.createInput(testValue, testType);
        +      const testElement = mockP5Prototype.createInput(testValue, testType);
               assert.deepEqual(testElement.elt.type, testType);
               assert.deepEqual(testElement.elt.value, testValue);
             });
           });
         
           suite('p5.prototype.createFileInput', function() {
        -    if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
        -      throw Error(
        -        'File API not supported in test environment. Cannot run tests'
        -      );
        -    }
        -
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        -      myp5.remove();
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             const emptyCallback = () => {};
        @@ -1172,374 +943,67 @@ suite('DOM', function() {
             };
         
             test('should be a function', function() {
        -      assert.isFunction(myp5.createFileInput);
        +      assert.isFunction(mockP5Prototype.createFileInput);
             });
         
             test('should return input of file input', function() {
        -      testElement = myp5.createFileInput(emptyCallback);
        -      assert.instanceOf(testElement, p5.Element);
        +      const testElement = mockP5Prototype.createFileInput(emptyCallback);
        +      assert.instanceOf(testElement, Element);
               assert.instanceOf(testElement.elt, HTMLInputElement);
               assert.deepEqual(testElement.elt.type, 'file');
             });
         
        -    testSketchWithPromise(
        -      'should trigger callback on input change event',
        -      function(sketch, resolve, reject) {
        -        sketch.setup = function() {
        -          testElement = myp5.createFileInput(resolve);
        -          const testFile = createDummyFile('file');
        -          testElement.files = testFile;
        -
        -          const mockedEvent = new Event('change');
        -          const mockedDataTransfer = new DataTransfer();
        -          mockedDataTransfer.items.add(testFile);
        -          testElement.elt.files = mockedDataTransfer.files;
        -          testElement.elt.dispatchEvent(mockedEvent);
        -        };
        -      }
        -    );
        +    // testSketchWithPromise(
        +    //   'should trigger callback on input change event',
        +    //   function(sketch, resolve, reject) {
        +    //     sketch.setup = function() {
        +    //       testElement = mockP5Prototype.createFileInput(resolve);
        +    //       const testFile = createDummyFile('file');
        +    //       testElement.files = testFile;
        +
        +    //       const mockedEvent = new Event('change');
        +    //       const mockedDataTransfer = new DataTransfer();
        +    //       mockedDataTransfer.items.add(testFile);
        +    //       testElement.elt.files = mockedDataTransfer.files;
        +    //       testElement.elt.dispatchEvent(mockedEvent);
        +    //     };
        +    //   }
        +    // );
         
             test('should accept multiple files if specified', function() {
        -      testElement = myp5.createFileInput(emptyCallback, true);
        +      const testElement = mockP5Prototype.createFileInput(emptyCallback, true);
               assert.isTrue(testElement.elt.multiple);
             });
         
        -    testSketchWithPromise(
        -      'should trigger callback for each file if multiple files are given',
        -      function(sketch, resolve, reject) {
        -        sketch.setup = function() {
        -          let totalTriggers = 0;
        -          let filesCount = 7;
        -
        -          const handleFiles = event => {
        -            totalTriggers += 1;
        -            if (totalTriggers === filesCount) resolve();
        -          };
        -
        -          const mockedEvent = new Event('change');
        -          const mockedDataTransfer = new DataTransfer();
        -          for (let i = 0; i < filesCount; i += 1) {
        -            mockedDataTransfer.items.add(createDummyFile(i.toString()));
        -          }
        -
        -          testElement = myp5.createFileInput(handleFiles, true);
        -          testElement.elt.files = mockedDataTransfer.files;
        -          testElement.elt.dispatchEvent(mockedEvent);
        -        };
        -      }
        -    );
        -  });
        -
        -  suite('p5.prototype.createVideo', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -        testElement = null;
        -      }
        -    });
        -
        -    const mediaSources = [
        -      '/test/unit/assets/nyan_cat.gif',
        -      '/test/unit/assets/target.gif'
        -    ];
        -
        -    test('should be a function', function() {
        -      assert.isFunction(myp5.createVideo);
        -    });
        -
        -    test('should return p5.Element of HTMLVideoElement', function() {
        -      testElement = myp5.createVideo('');
        -      assert.instanceOf(testElement, p5.MediaElement);
        -      assert.instanceOf(testElement.elt, HTMLVideoElement);
        -    });
        -
        -    test('should accept a singular media source', function() {
        -      const mediaSource = mediaSources[0];
        -      testElement = myp5.createVideo(mediaSource);
        -      const sourceEl = testElement.elt.children[0];
        -
        -      assert.deepEqual(testElement.elt.childElementCount, 1);
        -      assert.instanceOf(sourceEl, HTMLSourceElement);
        -      assert.isTrue(sourceEl.src.endsWith(mediaSource));
        -    });
        -
        -    test('should accept multiple media sources', function() {
        -      testElement = myp5.createVideo(mediaSources);
        -
        -      assert.deepEqual(testElement.elt.childElementCount, mediaSources.length);
        -      for (let index = 0; index < mediaSources.length; index += 1) {
        -        const sourceEl = testElement.elt.children[index];
        -        assert.instanceOf(sourceEl, HTMLSourceElement);
        -        assert.isTrue(sourceEl.src.endsWith(mediaSources[index]));
        -      }
        -    });
        -
        -    testSketchWithPromise(
        -      'should trigger callback on canplaythrough event',
        -      function(sketch, resolve, reject) {
        -        sketch.setup = function() {
        -          testElement = myp5.createVideo(mediaSources, resolve);
        -          testElement.elt.dispatchEvent(new Event('canplaythrough'));
        -        };
        -      }
        -    );
        -
        -    test('should work with tint()', function(done) {
        -      const imgElt = myp5.createImg('/test/unit/assets/cat.jpg', '');
        -      testElement = myp5.createVideo('/test/unit/assets/cat.webm', () => {
        -        // Workaround for headless tests, where the video data isn't loading
        -        // correctly: mock the video element using an image for this test
        -        const prevElt = testElement.elt;
        -        testElement.elt = imgElt.elt;
        -
        -        myp5.background(255);
        -        myp5.tint(255, 0, 0);
        -        myp5.image(testElement, 0, 0);
        -
        -        testElement.elt = prevElt;
        -        imgElt.remove();
        -
        -        myp5.loadPixels();
        -        testElement.loadPixels();
        -        assert.equal(myp5.pixels[0], testElement.pixels[0]);
        -        assert.equal(myp5.pixels[1], 0);
        -        assert.equal(myp5.pixels[2], 0);
        -        done();
        -      });
        -    });
        -
        -    test('should work with updatePixels()', function(done) {
        -      let loaded = false;
        -      let prevElt;
        -      const imgElt = myp5.createImg('/test/unit/assets/cat.jpg', '');
        -      testElement = myp5.createVideo('/test/unit/assets/cat.webm', () => {
        -        loaded = true;
        -        // Workaround for headless tests, where the video data isn't loading
        -        // correctly: mock the video element using an image for this test
        -        prevElt = testElement.elt;
        -        testElement.elt = imgElt.elt;
        -      });
        -
        -      let drewUpdatedPixels = false;
        -      myp5.draw = function() {
        -        if (!loaded) return;
        -        myp5.background(255);
        -
        -        if (!drewUpdatedPixels) {
        -          // First, update pixels and check that it draws the updated
        -          // pixels correctly
        -          testElement.loadPixels();
        -          for (let i = 0; i < testElement.pixels.length; i += 4) {
        -            // Set every pixel to red
        -            testElement.pixels[i] = 255;
        -            testElement.pixels[i + 1] = 0;
        -            testElement.pixels[i + 2] = 0;
        -            testElement.pixels[i + 3] = 255;
        -          }
        -          testElement.updatePixels();
        -          myp5.image(testElement, 0, 0);
        -
        -          // The element should have drawn using the updated red pixels
        -          myp5.loadPixels();
        -          assert.deepEqual([...myp5.pixels.slice(0, 4)], [255, 0, 0, 255]);
        -
        -          // Mark that we've done the first check so we can see whether
        -          // the video still updates on the next frame
        -          drewUpdatedPixels = true;
        -        } else {
        -          // Next, make sure it still updates with the real pixels from
        -          // the next frame of the video on the next frame of animation
        -          myp5.image(testElement, 0, 0);
        -
        -          myp5.loadPixels();
        -          testElement.loadPixels();
        -          expect([...testElement.pixels.slice(0, 4)])
        -            .to.not.deep.equal([255, 0, 0, 255]);
        -          assert.deepEqual(
        -            [...myp5.pixels.slice(0, 4)],
        -            [...testElement.pixels.slice(0, 4)]
        -          );
        -          testElement.elt = prevElt;
        -          imgElt.remove();
        -          done();
        -        }
        -      };
        -    });
        -  });
        -
        -  suite('p5.prototype.createAudio', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -        testElement = null;
        -      }
        -    });
        -
        -    const mediaSources = [
        -      '/test/unit/assets/beat.mp3',
        -      '/test/unit/assets/beat.mp3'
        -    ];
        -
        -    test('should be a function', function() {
        -      assert.isFunction(myp5.createAudio);
        -    });
        -
        -    test('should return p5.Element of HTMLAudioElement', function() {
        -      testElement = myp5.createAudio('');
        -      assert.instanceOf(testElement, p5.MediaElement);
        -      assert.instanceOf(testElement.elt, HTMLAudioElement);
        -    });
        -
        -    test('should accept a singular media source', function() {
        -      const mediaSource = mediaSources[0];
        -      testElement = myp5.createAudio(mediaSource);
        -      const sourceEl = testElement.elt.children[0];
        -
        -      assert.deepEqual(testElement.elt.childElementCount, 1);
        -      assert.instanceOf(sourceEl, HTMLSourceElement);
        -      assert.isTrue(sourceEl.src.endsWith(mediaSource));
        -    });
        -
        -    test('should accept multiple media sources', function() {
        -      testElement = myp5.createAudio(mediaSources);
        -
        -      assert.deepEqual(testElement.elt.childElementCount, mediaSources.length);
        -      for (let index = 0; index < mediaSources.length; index += 1) {
        -        const sourceEl = testElement.elt.children[index];
        -        assert.instanceOf(sourceEl, HTMLSourceElement);
        -        assert.isTrue(sourceEl.src.endsWith(mediaSources[index]));
        -      }
        -    });
        -
        -    testSketchWithPromise(
        -      'should trigger callback on canplaythrough event',
        -      function(sketch, resolve, reject) {
        -        sketch.setup = function() {
        -          testElement = myp5.createAudio(mediaSources, resolve);
        -          testElement.elt.dispatchEvent(new Event('canplaythrough'));
        -        };
        -      }
        -    );
        -  });
        -
        -  suite('p5.prototype.createCapture', function() {
        -    // to run these tests, getUserMedia is required to be supported
        -    if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
        -      throw Error(
        -        'Cannot run tests as getUserMedia not supported in this browser'
        -      );
        -    }
        -
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        -      myp5.remove();
        -    });
        -
        -    test('should be a function', function() {
        -      assert.isFunction(myp5.createCapture);
        -    });
        -
        -    test('should return p5.Element of video type', function() {
        -      testElement = myp5.createCapture(myp5.VIDEO);
        -      assert.instanceOf(testElement, p5.Element);
        -      assert.instanceOf(testElement.elt, HTMLVideoElement);
        -    });
        -
        -    test('should throw error if getUserMedia is not supported', function() {
        -      // Remove getUserMedia method and test
        -      const backup = navigator.mediaDevices.getUserMedia;
        -      navigator.mediaDevices.getUserMedia = undefined;
        -      try {
        -        testElement = myp5.createCapture(myp5.VIDEO);
        -        assert.fail();
        -      } catch (error) {
        -        assert.instanceOf(error, DOMException);
        -      }
        -
        -      // Restore backup, very important.
        -      navigator.mediaDevices.getUserMedia = backup;
        -    });
        -
        -    testSketchWithPromise(
        -      'triggers the callback after loading metadata',
        -      function(sketch, resolve, reject) {
        -        sketch.setup = function() {
        -          testElement = myp5.createCapture(myp5.VIDEO, resolve);
        -          const mockedEvent = new Event('loadedmetadata');
        -          testElement.elt.dispatchEvent(mockedEvent);
        -        };
        -      }
        -    );
        -
        -    // Required for ios 11 devices
        -    test('should have playsinline attribute to empty string on DOM element', function() {
        -      testElement = myp5.createCapture(myp5.VIDEO);
        -      console.log(testElement.elt);
        -      // Weird check, setter accepts : playinline, getter accepts playInline
        -      assert.isTrue(testElement.elt.playsInline);
        -    });
        +    // testSketchWithPromise(
        +    //   'should trigger callback for each file if multiple files are given',
        +    //   function(sketch, resolve, reject) {
        +    //     sketch.setup = function() {
        +    //       let totalTriggers = 0;
        +    //       let filesCount = 7;
        +
        +    //       const handleFiles = event => {
        +    //         totalTriggers += 1;
        +    //         if (totalTriggers === filesCount) resolve();
        +    //       };
        +
        +    //       const mockedEvent = new Event('change');
        +    //       const mockedDataTransfer = new DataTransfer();
        +    //       for (let i = 0; i < filesCount; i += 1) {
        +    //         mockedDataTransfer.items.add(createDummyFile(i.toString()));
        +    //       }
        +
        +    //       testElement = mockP5Prototype.createFileInput(handleFiles, true);
        +    //       testElement.elt.files = mockedDataTransfer.files;
        +    //       testElement.elt.dispatchEvent(mockedEvent);
        +    //     };
        +    //   }
        +    // );
           });
         
           suite('p5.prototype.createElement', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -        testElement = null;
        -      }
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             const testData = {
        @@ -1551,55 +1015,62 @@ suite('DOM', function() {
             };
         
             test('should be a function', function() {
        -      assert.isFunction(myp5.createElement);
        +      assert.isFunction(mockP5Prototype.createElement);
             });
         
             test('should return a p5.Element of appropriate type', function() {
               for (const [tag, domElName] of Object.entries(testData)) {
        -        testElement = myp5.createElement(tag);
        -        assert.instanceOf(testElement, p5.Element);
        +        const testElement = mockP5Prototype.createElement(tag);
        +        assert.instanceOf(testElement, Element);
                 assert.instanceOf(testElement.elt, domElName);
               }
             });
         
             test('should set given content as innerHTML', function() {
               const testContent = 'Lorem ipsum';
        -      testElement = myp5.createElement('div', testContent);
        +      const testElement = mockP5Prototype.createElement('div', testContent);
               assert.deepEqual(testElement.elt.innerHTML, testContent);
             });
           });
         
        -  // p5.Element.prototype.addClass
        -  suite('p5.Element.prototype.addClass', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        +  suite('p5.prototype.removeElements', function() {
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        +    test('should remove all elements created by p5 except Canvas', function() {
        +      // creates 6 elements one of which is a canvas, then calls
        +      // removeElements and tests if only canvas is left.
        +      const tags = ['a', 'button', 'canvas', 'div', 'p', 'video'];
        +      for (const tag of tags) {
        +        mockP5Prototype.createElement(tag);
               }
        -      testElement = null;
        +      // Check if all elements are created.
        +      assert.deepEqual(document.body.childElementCount, tags.length);
        +
        +      // Call removeElements and check if only canvas is remaining
        +      mockP5Prototype.removeElements();
        +      assert.deepEqual(document.body.childElementCount, 1);
        +      const remainingElement = document.body.children[0];
        +      assert.instanceOf(remainingElement, HTMLCanvasElement);
        +    });
        +  });
        +
        +  // p5.Element.prototype.addClass
        +  suite('p5.Element.prototype.addClass', function() {
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
               // Create any p5.Element
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               assert.isFunction(testElement.addClass);
             });
         
             test('should add provided string to class names', function() {
               const testClassName = 'jumbotron';
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               testElement.addClass(testClassName);
               assert.deepEqual(testElement.elt.className, testClassName);
             });
        @@ -1609,7 +1080,7 @@ suite('DOM', function() {
               const testClassName2 = 'container-fluid';
               const expectedClassName = testClassName1 + ' ' + testClassName2;
         
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               testElement.addClass(testClassName1);
               testElement.addClass(testClassName2);
         
        @@ -1621,29 +1092,13 @@ suite('DOM', function() {
         
           // p5.Element.prototype.removeClass
           suite('p5.Element.prototype.removeClass', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
               // Create any p5.Element
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               assert.isFunction(testElement.removeClass);
             });
         
        @@ -1651,7 +1106,7 @@ suite('DOM', function() {
               const defaultClassNames = 'col-md-9 col-sm-12';
               const testClassName = 'jumbotron';
         
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               testElement.addClass(defaultClassNames);
               testElement.addClass(testClassName);
         
        @@ -1664,7 +1119,7 @@ suite('DOM', function() {
               const testClassName1 = 'jumbotron';
               const testClassName2 = 'container-fluid';
         
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               testElement.addClass(testClassName1);
         
               // Handling the curse of 'this'
        @@ -1676,29 +1131,13 @@ suite('DOM', function() {
         
           // p5.Element.prototype.hasClass
           suite('p5.Element.prototype.hasClass', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
               // Create any p5.Element
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               assert.isFunction(testElement.hasClass);
             });
         
        @@ -1706,7 +1145,7 @@ suite('DOM', function() {
               const defaultClassNames = 'col-md-9 jumbotron';
               const testClassName = 'jumbotron';
         
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               testElement.addClass(defaultClassNames);
         
               assert.isTrue(testElement.hasClass(testClassName));
        @@ -1716,7 +1155,7 @@ suite('DOM', function() {
               const defaultClassNames = 'col-md-9 jumbotron';
               const testClassName = 'container-fluid';
         
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               testElement.addClass(defaultClassNames);
         
               assert.isFalse(testElement.hasClass(testClassName));
        @@ -1725,29 +1164,13 @@ suite('DOM', function() {
         
           // p5.Element.prototype.toggleClass
           suite('p5.Element.prototype.toggleClass', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
               // Create any p5.Element
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               assert.isFunction(testElement.toggleClass);
             });
         
        @@ -1755,7 +1178,7 @@ suite('DOM', function() {
               const defaultClassName = 'container-fluid';
               const testClassName = 'jumbotron';
         
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               testElement.addClass(defaultClassName);
               testElement.addClass(testClassName);
         
        @@ -1767,7 +1190,7 @@ suite('DOM', function() {
               const defaultClassName = 'container-fluid';
               const testClassName = 'jumbotron';
         
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               testElement.addClass(defaultClassName);
         
               testElement.toggleClass(testClassName);
        @@ -1780,34 +1203,18 @@ suite('DOM', function() {
         
           // p5.Element.prototype.child
           suite('p5.Element.prototype.child', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               assert.isFunction(testElement.child);
             });
         
             test('should return all child nodes by default', function() {
        -      testElement = myp5.createElement('div');
        -      const childElement = myp5.createElement('p');
        +      const testElement = mockP5Prototype.createElement('div');
        +      const childElement = mockP5Prototype.createElement('p');
         
               // Add child element by using DOM API
               testElement.elt.appendChild(childElement.elt);
        @@ -1821,8 +1228,8 @@ suite('DOM', function() {
             });
         
             test('should append p5 element as child', function() {
        -      testElement = myp5.createElement('div');
        -      const childElement = myp5.createElement('p');
        +      const testElement = mockP5Prototype.createElement('div');
        +      const childElement = mockP5Prototype.createElement('p');
         
               testElement.child(childElement);
               const childNodes = Array.from(testElement.elt.children);
        @@ -1830,8 +1237,8 @@ suite('DOM', function() {
             });
         
             test('should append dom element as child', function() {
        -      testElement = myp5.createElement('div');
        -      const childElement = myp5.createElement('p');
        +      const testElement = mockP5Prototype.createElement('div');
        +      const childElement = mockP5Prototype.createElement('p');
         
               testElement.child(childElement.elt);
               const childNodes = Array.from(testElement.elt.children);
        @@ -1839,9 +1246,9 @@ suite('DOM', function() {
             });
         
             test('should append element as child from a given id', function() {
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               const childId = 'testChildElement';
        -      const childElement = myp5.createElement('p');
        +      const childElement = mockP5Prototype.createElement('p');
               childElement.id(childId);
         
               testElement.child(childId);
        @@ -1850,7 +1257,7 @@ suite('DOM', function() {
             });
         
             test('should not throw error if mathcing element is not found from a given id', function() {
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               const randomChildId = 'testChildElement';
               expect(() => testElement.child(randomChildId)).to.not.throw();
             });
        @@ -1858,28 +1265,12 @@ suite('DOM', function() {
         
           // p5.Element.prototype.center
           suite('p5.Element.prototype.center', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               assert.isFunction(testElement.center);
             });
         
        @@ -1898,34 +1289,18 @@ suite('DOM', function() {
         
           // p5.Element.prototype.html
           suite('p5.Element.prototype.html', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
               // Create any p5.Element
        -      testElement = myp5.createElement('a');
        +      const testElement = mockP5Prototype.createElement('a');
               assert.isFunction(testElement.position);
             });
         
             test('should return the inner HTML of element if no argument is given', function() {
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               const testHTML = '<p>Hello World</p>';
         
               testElement.elt.innerHTML = testHTML;
        @@ -1933,7 +1308,7 @@ suite('DOM', function() {
             });
         
             test('should replace the inner HTML of element', function() {
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               const initialtestHTML = '<p>Hello World</p>';
               const modifiedtestHTML = '<p>Hello World !!!</p>';
         
        @@ -1945,7 +1320,7 @@ suite('DOM', function() {
             });
         
             test('should append to the inner HTML if second param is true', function() {
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               const testHTML1 = '<p>Hello World</p>';
               const testHTML2 = '<p>Hello World !!!</p>';
         
        @@ -1957,7 +1332,7 @@ suite('DOM', function() {
             });
         
             test('should replace the inner HTML if second param is false', function() {
        -      testElement = myp5.createElement('div');
        +      const testElement = mockP5Prototype.createElement('div');
               const testHTML1 = '<p>Hello World</p>';
               const testHTML2 = '<p>Hello World !!!</p>';
         
        @@ -1971,47 +1346,31 @@ suite('DOM', function() {
         
           // p5.Element.prototype.position
           suite('p5.Element.prototype.position', function() {
        -    let myp5;
        -    let testElement;
        -
        -    setup(function(done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          myp5 = p;
        -          done();
        -        };
        -      });
        -    });
        -
        -    teardown(function() {
        -      myp5.remove();
        -      if (testElement && testElement.parentNode) {
        -        testElement.parentNode.removeChild(testElement);
        -      }
        -      testElement = null;
        +    afterEach(function() {
        +      document.body.innerHTML = "";
             });
         
             test('should be a function', function() {
               // Create any p5.Element
        -      testElement = myp5.createElement('a');
        +      const testElement = mockP5Prototype.createElement('a');
               assert.isFunction(testElement.position);
             });
         
             test('should return current position if no args are given', function() {
        -      testElement = myp5.createButton('testButton');
        +      const testElement = mockP5Prototype.createButton('testButton');
               const position = testElement.position();
               assert.deepEqual(position.x, testElement.elt.offsetLeft);
               assert.deepEqual(position.y, testElement.elt.offsetTop);
             });
         
             test('should set default position as absolute', function() {
        -      testElement = myp5.createButton('testButton');
        +      const testElement = mockP5Prototype.createButton('testButton');
               testElement.position(20, 70);
               assert.deepEqual(testElement.elt.style.position, 'absolute');
             });
         
             test('should set given params as properties', function() {
        -      let testElement = myp5.createButton('testButton');
        +      const testElement = mockP5Prototype.createButton('testButton');
               testElement.position(20, 80, 'static');
         
               assert.deepEqual(testElement.elt.style.position, 'static');
        @@ -2040,42 +1399,6 @@ suite('DOM', function() {
         
           // p5.Element.prototype.remove
         
        -  suite('p5.prototype.drop', function() {
        -    testSketchWithPromise('drop fires multiple events', function(
        -      sketch,
        -      resolve,
        -      reject
        -    ) {
        -      let testElement;
        -      let fileFnCounter = 0;
        -      let eventFnCounter = 0;
        -      sketch.setup = function() {
        -        testElement = sketch.createDiv('Drop files inside');
        -
        -        // Setup test functions and constants
        -        const file1 = new File(['foo'], 'foo.txt', { type: 'text/plain' });
        -        const file2 = new File(['foo'], 'foo.txt', { type: 'text/plain' });
        -        const hasFinished = () => {
        -          if (fileFnCounter > 1 && eventFnCounter === 1) resolve();
        -        };
        -        const testFileFn = () => {
        -          fileFnCounter += 1;
        -          hasFinished();
        -        };
        -        const testEventFn = () => {
        -          eventFnCounter += 1;
        -          hasFinished();
        -        };
        -        testElement.drop(testFileFn, testEventFn);
        -
        -        // Fire a mock drop and test the method
        -        const mockedEvent = new Event('drop');
        -        mockedEvent.dataTransfer = { files: [file1, file2] };
        -        testElement.elt.dispatchEvent(mockedEvent);
        -      };
        -    });
        -  });
        -
           // p5.MediaElement
         
           // p5.MediaElement.play
        diff --git a/test/unit/core/p5.Element.js b/test/unit/dom/p5.Element.js
        similarity index 82%
        rename from test/unit/core/p5.Element.js
        rename to test/unit/dom/p5.Element.js
        index 63355f4c7f..1c94d2b81c 100644
        --- a/test/unit/core/p5.Element.js
        +++ b/test/unit/dom/p5.Element.js
        @@ -1,39 +1,53 @@
        +// import p5 from '../../../src/app.js';
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import dom from '../../../src/dom/dom';
        +
         suite('p5.Element', function() {
        -  var myp5 = new p5(function(sketch) {
        -    sketch.setup = function() {};
        -    sketch.draw = function() {};
        +  // const mockP5Prototype = new p5(function(sketch) {
        +  //   sketch.setup = function() {};
        +  //   sketch.draw = function() {};
        +  // });
        +
        +  // let elt;
        +
        +  // afterAll(function() {
        +  //   if (elt && elt.parentNode) {
        +  //     elt.parentNode.removeChild(elt);
        +  //     elt = null;
        +  //   }
        +  //   mockP5Prototype.remove();
        +  // });
        +
        +  beforeAll(() => {
        +    dom(mockP5, mockP5Prototype);
           });
         
        -  var elt;
        +  suite('p5.Element.prototype.parent', function() {
        +    let div0, div1;
         
        -  teardown(function() {
        -    if (elt && elt.parentNode) {
        -      elt.parentNode.removeChild(elt);
        -      elt = null;
        -    }
        -    myp5.remove();
        -  });
        +    beforeEach(() => {
        +      div0 = mockP5Prototype.createDiv('this is the parent');
        +      div1 = mockP5Prototype.createDiv('this is the child');
        +    });
        +
        +    afterEach(() => {
        +      div0.remove();
        +      div1.remove();
        +    });
         
        -  suite('p5.Element.prototype.parent', function() {
             test('attaches child to parent', function() {
        -      let div0 = myp5.createDiv('this is the parent');
        -      let div1 = myp5.createDiv('this is the child');
               div1.attribute('id', 'child');
               div1.parent(div0); //attaches div1 to div0
               assert.equal(document.getElementById('child').parentElement, div0.elt);
             });
         
             test('attaches child to parent using classname', function() {
        -      let div0 = myp5.createDiv('this is the parent');
        -      let div1 = myp5.createDiv('this is the child');
               div0.attribute('id', 'parent');
               div1.parent('parent'); //attaches div1 to div0 using classname
               assert.equal(div1.parent(), div0.elt); //returns parent of div1
             });
         
        -    test('attaches child to parent using classname', function() {
        -      let div0 = myp5.createDiv('this is the parent');
        -      let div1 = myp5.createDiv('this is the child');
        +    test('attaches child to parent using id', function() {
               div0.attribute('id', 'parent');
               div1.parent('#parent'); //attaches div1 to div0
               assert.equal(div1.parent(), div0.elt); //returns parent of div1 using id
        @@ -45,29 +59,29 @@ suite('p5.Element', function() {
               div1.setAttribute('id', 'child');
               div0.appendChild(div1);
               document.body.appendChild(div0);
        -      assert.equal(myp5.select('#child').parent(), div0);
        +      assert.equal(mockP5Prototype.select('#child').parent(), div0);
             });
           });
         
           suite('p5.Element.prototype.id', function() {
             test('attaches child to parent', function() {
        -      elt = myp5.createDiv();
        +      const elt = mockP5Prototype.createDiv();
               elt.id('test');
               assert.equal(document.getElementById('test'), elt.elt);
             });
         
             test('returns the id', function() {
        -      elt = document.createElement('div');
        +      const elt = document.createElement('div');
               elt.setAttribute('id', 'test');
               document.body.appendChild(elt);
        -      assert.equal(myp5.select('#child').id(), 'child');
        +      assert.equal(mockP5Prototype.select('#child').id(), 'child');
             });
           });
         
        -  suite('p5.Element.prototype.mousePressed', function() {
        +  suite.todo('p5.Element.prototype.mousePressed', function() {
             test('attaches and gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -81,7 +95,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -103,7 +117,7 @@ suite('p5.Element', function() {
           suite('p5.Element.prototype.mouseClicked', function() {
             test('attaches and gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -117,7 +131,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -137,7 +151,7 @@ suite('p5.Element', function() {
         
             test('detaches and does not get events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -154,7 +168,7 @@ suite('p5.Element', function() {
           suite('p5.Element.prototype.doubleClicked', function() {
             test('attaches and gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -168,7 +182,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -188,7 +202,7 @@ suite('p5.Element', function() {
         
             test('detaches and does not get events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -205,7 +219,7 @@ suite('p5.Element', function() {
           suite('p5.Element.prototype.mouseWheel', function() {
             test('attaches and gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function(event) {
                 if (event.deltaX > 0) {
        @@ -221,7 +235,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -243,7 +257,7 @@ suite('p5.Element', function() {
           suite('p5.Element.prototype.touchStarted', function() {
             test('attaches and gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function(event) {
                 myFnCounter++;
        @@ -257,7 +271,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -277,7 +291,7 @@ suite('p5.Element', function() {
         
             test('detaches and does not get events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -294,7 +308,7 @@ suite('p5.Element', function() {
           suite('p5.Element.prototype.touchMoved', function() {
             test('attaches and gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function(event) {
                 myFnCounter++;
        @@ -308,7 +322,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -328,7 +342,7 @@ suite('p5.Element', function() {
         
             test('detaches and does not get events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -345,7 +359,7 @@ suite('p5.Element', function() {
           suite('p5.Element.prototype.touchEnded', function() {
             test('attaches and gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function(event) {
                 myFnCounter++;
        @@ -359,7 +373,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -379,7 +393,7 @@ suite('p5.Element', function() {
         
             test('detaches and does not get events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -396,7 +410,7 @@ suite('p5.Element', function() {
           suite('p5.Element.prototype.mouseReleased', function() {
             test('attaches and gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -410,7 +424,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -430,7 +444,7 @@ suite('p5.Element', function() {
         
             test('detaches and does not get events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -447,7 +461,7 @@ suite('p5.Element', function() {
           suite('p5.Element.prototype.mouseMoved', function() {
             test('attaches and gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -461,7 +475,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -481,7 +495,7 @@ suite('p5.Element', function() {
         
             test('detaches and does not get events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -498,7 +512,7 @@ suite('p5.Element', function() {
           suite('p5.Element.prototype.mouseOver', function() {
             test('attaches and gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -512,7 +526,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -532,7 +546,7 @@ suite('p5.Element', function() {
         
             test('detaches and does not get events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -549,7 +563,7 @@ suite('p5.Element', function() {
           suite('p5.Element.prototype.mouseOut', function() {
             test('attaches and gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -563,7 +577,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -583,7 +597,7 @@ suite('p5.Element', function() {
         
             test('detaches and does not get events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -599,7 +613,7 @@ suite('p5.Element', function() {
         
           suite('p5.Element.prototype.dragOver', function() {
             test('attaches and gets events', function() {
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -613,7 +627,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -633,7 +647,7 @@ suite('p5.Element', function() {
         
             test('detaches and does not get events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -649,7 +663,7 @@ suite('p5.Element', function() {
         
           suite('p5.Element.prototype.dragLeave', function() {
             test('attaches and gets events', function() {
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -663,7 +677,7 @@ suite('p5.Element', function() {
         
             test('attaches multiple handlers and only latest gets events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -683,7 +697,7 @@ suite('p5.Element', function() {
         
             test('detaches and does not get events', function() {
               // setup
        -      elt = myp5.createDiv('hello');
        +      const elt = mockP5Prototype.createDiv('hello');
               var myFnCounter = 0;
               var myFn = function() {
                 myFnCounter++;
        @@ -698,75 +712,78 @@ suite('p5.Element', function() {
           });
         
           suite('operating with element classes', function() {
        -    test('should add class to element', function() {
        +    let elt;
        +
        +    beforeEach(() => {
               elt = document.createElement('div');
        +    });
        +
        +    afterEach(() => {
        +      elt.remove();
        +    });
        +
        +    test('should add class to element', function() {
               elt.setAttribute('id', 'testdiv');
               document.body.appendChild(elt);
         
        -      myp5.select('#testdiv').addClass('testclass');
        +      mockP5Prototype.select('#testdiv').addClass('testclass');
               assert.strictEqual(elt.getAttribute('class'), 'testclass');
             });
         
             test('should remove class from element with only one class', function() {
        -      elt = document.createElement('div');
               elt.setAttribute('id', 'testdiv');
               elt.setAttribute('class', 'testclass');
               document.body.appendChild(elt);
         
        -      myp5.select('#testdiv').removeClass('testclass');
        +      mockP5Prototype.select('#testdiv').removeClass('testclass');
               assert.strictEqual(elt.getAttribute('class'), '');
             });
         
             test('should remove class from element with several classes', function() {
        -      elt = document.createElement('div');
               elt.setAttribute('id', 'testdiv');
               elt.setAttribute('class', 'testclass1 testclass2 testclass3');
               document.body.appendChild(elt);
         
        -      myp5.select('#testdiv').removeClass('testclass2');
        +      mockP5Prototype.select('#testdiv').removeClass('testclass2');
               assert.strictEqual(elt.getAttribute('class'), 'testclass1 testclass3');
             });
         
             test('should return true if element has specified class', function() {
        -      elt = document.createElement('div');
               elt.setAttribute('id', 'testdiv');
               elt.setAttribute('class', 'testclass1 testclass2 testclass3');
               document.body.appendChild(elt);
         
        -      assert.strictEqual(myp5.select('#testdiv').hasClass('testclass2'), true);
        +      assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('testclass2'), true);
             });
         
             test('should return false if element has not specified class', function() {
        -      elt = document.createElement('div');
               elt.setAttribute('id', 'testdiv');
               elt.setAttribute('class', 'testclass1 testclass3');
               document.body.appendChild(elt);
         
        -      assert.strictEqual(myp5.select('#testdiv').hasClass('testclass2'), false);
        +      assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('testclass2'), false);
             });
         
             test('should return false if element has class that is partially similar as specified class', function() {
        -      elt = document.createElement('div');
               elt.setAttribute('id', 'testdiv');
               elt.setAttribute('class', 'testclass slideshow newtestsclas');
               document.body.appendChild(elt);
         
        -      assert.strictEqual(myp5.select('#testdiv').hasClass('show'), false);
        -      assert.strictEqual(myp5.select('#testdiv').hasClass('slide'), false);
        -      assert.strictEqual(myp5.select('#testdiv').hasClass('test'), false);
        -      assert.strictEqual(myp5.select('#testdiv').hasClass('class'), false);
        +      assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('show'), false);
        +      assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('slide'), false);
        +      assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('test'), false);
        +      assert.strictEqual(mockP5Prototype.select('#testdiv').hasClass('class'), false);
             });
         
             test('should toggle specified class on element', function() {
        -      elt = document.createElement('div');
               elt.setAttribute('id', 'testdiv');
               elt.setAttribute('class', 'testclass1 testclass2');
               document.body.appendChild(elt);
         
        -      myp5.select('#testdiv').toggleClass('testclass2');
        +      mockP5Prototype.select('#testdiv').toggleClass('testclass2');
               assert.strictEqual(elt.getAttribute('class'), 'testclass1');
         
        -      myp5.select('#testdiv').toggleClass('testclass2');
        +      mockP5Prototype.select('#testdiv').toggleClass('testclass2');
               assert.strictEqual(elt.getAttribute('class'), 'testclass1 testclass2');
             });
           });
        diff --git a/test/unit/dom/p5.MediaElement.js b/test/unit/dom/p5.MediaElement.js
        new file mode 100644
        index 0000000000..bfaaba88c0
        --- /dev/null
        +++ b/test/unit/dom/p5.MediaElement.js
        @@ -0,0 +1,238 @@
        +import { vi } from 'vitest';
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import { default as media, MediaElement } from '../../../src/dom/p5.MediaElement';
        +import { Element } from '../../../src/dom/p5.Element';
        +
        +suite('p5.MediaElement', () => {
        +  beforeAll(() => {
        +    media(mockP5, mockP5Prototype);
        +    navigator.mediaDevices.getUserMedia = vi.fn()
        +      .mockResolvedValue("stream-value");
        +  });
        +
        +  afterAll(() => {
        +    vi.restoreAllMocks();
        +  });
        +
        +  suite('p5.prototype.createVideo', function() {
        +    afterEach(function() {
        +      document.body.innerHTML = "";
        +    });
        +
        +    const mediaSources = [
        +      '/test/unit/assets/nyan_cat.gif',
        +      '/test/unit/assets/target.gif'
        +    ];
        +
        +    test('should be a function', function() {
        +      assert.isFunction(mockP5Prototype.createVideo);
        +    });
        +
        +    test('should return p5.Element of HTMLVideoElement', function() {
        +      const testElement = mockP5Prototype.createVideo('');
        +      assert.instanceOf(testElement, MediaElement);
        +      assert.instanceOf(testElement.elt, HTMLVideoElement);
        +    });
        +
        +    test('should accept a singular media source', function() {
        +      const mediaSource = mediaSources[0];
        +      const testElement = mockP5Prototype.createVideo(mediaSource);
        +      const sourceEl = testElement.elt.children[0];
        +
        +      assert.deepEqual(testElement.elt.childElementCount, 1);
        +      assert.instanceOf(sourceEl, HTMLSourceElement);
        +      assert.isTrue(sourceEl.src.endsWith(mediaSource));
        +    });
        +
        +    test('should accept multiple media sources', function() {
        +      const testElement = mockP5Prototype.createVideo(mediaSources);
        +
        +      assert.deepEqual(testElement.elt.childElementCount, mediaSources.length);
        +      for (let index = 0; index < mediaSources.length; index += 1) {
        +        const sourceEl = testElement.elt.children[index];
        +        assert.instanceOf(sourceEl, HTMLSourceElement);
        +        assert.isTrue(sourceEl.src.endsWith(mediaSources[index]));
        +      }
        +    });
        +
        +    // testSketchWithPromise(
        +    //   'should trigger callback on canplaythrough event',
        +    //   function(sketch, resolve, reject) {
        +    //     sketch.setup = function() {
        +    //       testElement = myp5.createVideo(mediaSources, resolve);
        +    //       testElement.elt.dispatchEvent(new Event('canplaythrough'));
        +    //     };
        +    //   }
        +    // );
        +
        +    // TODO: integration test
        +    test.todo('should work with tint()', function(done) {
        +      const imgElt = myp5.createImg('/test/unit/assets/cat.jpg', '');
        +      const testElement = myp5.createVideo('/test/unit/assets/cat.webm', () => {
        +        // Workaround for headless tests, where the video data isn't loading
        +        // correctly: mock the video element using an image for this test
        +        const prevElt = testElement.elt;
        +        testElement.elt = imgElt.elt;
        +
        +        myp5.background(255);
        +        myp5.tint(255, 0, 0);
        +        myp5.image(testElement, 0, 0);
        +
        +        testElement.elt = prevElt;
        +        imgElt.remove();
        +
        +        myp5.loadPixels();
        +        testElement.loadPixels();
        +        assert.equal(myp5.pixels[0], testElement.pixels[0]);
        +        assert.equal(myp5.pixels[1], 0);
        +        assert.equal(myp5.pixels[2], 0);
        +        done();
        +      });
        +    });
        +
        +    test.todo('should work with updatePixels()', function(done) {
        +      let loaded = false;
        +      let prevElt;
        +      const imgElt = myp5.createImg('/test/unit/assets/cat.jpg', '');
        +      const testElement = myp5.createVideo('/test/unit/assets/cat.webm', () => {
        +        loaded = true;
        +        // Workaround for headless tests, where the video data isn't loading
        +        // correctly: mock the video element using an image for this test
        +        prevElt = testElement.elt;
        +        testElement.elt = imgElt.elt;
        +      });
        +
        +      let drewUpdatedPixels = false;
        +      myp5.draw = function() {
        +        if (!loaded) return;
        +        myp5.background(255);
        +
        +        if (!drewUpdatedPixels) {
        +          // First, update pixels and check that it draws the updated
        +          // pixels correctly
        +          testElement.loadPixels();
        +          for (let i = 0; i < testElement.pixels.length; i += 4) {
        +            // Set every pixel to red
        +            testElement.pixels[i] = 255;
        +            testElement.pixels[i + 1] = 0;
        +            testElement.pixels[i + 2] = 0;
        +            testElement.pixels[i + 3] = 255;
        +          }
        +          testElement.updatePixels();
        +          myp5.image(testElement, 0, 0);
        +
        +          // The element should have drawn using the updated red pixels
        +          myp5.loadPixels();
        +          assert.deepEqual([...myp5.pixels.slice(0, 4)], [255, 0, 0, 255]);
        +
        +          // Mark that we've done the first check so we can see whether
        +          // the video still updates on the next frame
        +          drewUpdatedPixels = true;
        +        } else {
        +          // Next, make sure it still updates with the real pixels from
        +          // the next frame of the video on the next frame of animation
        +          myp5.image(testElement, 0, 0);
        +
        +          myp5.loadPixels();
        +          testElement.loadPixels();
        +          expect([...testElement.pixels.slice(0, 4)])
        +            .to.not.deep.equal([255, 0, 0, 255]);
        +          assert.deepEqual(
        +            [...myp5.pixels.slice(0, 4)],
        +            [...testElement.pixels.slice(0, 4)]
        +          );
        +          testElement.elt = prevElt;
        +          imgElt.remove();
        +          done();
        +        }
        +      };
        +    });
        +  });
        +
        +  suite('p5.prototype.createAudio', function() {
        +    afterEach(function() {
        +      document.body.innerHTML = "";
        +    });
        +
        +    const mediaSources = [
        +      '/test/unit/assets/beat.mp3',
        +      '/test/unit/assets/beat.mp3'
        +    ];
        +
        +    test('should be a function', function() {
        +      assert.isFunction(mockP5Prototype.createAudio);
        +    });
        +
        +    test('should return p5.Element of HTMLAudioElement', function() {
        +      const testElement = mockP5Prototype.createAudio('');
        +      assert.instanceOf(testElement, MediaElement);
        +      assert.instanceOf(testElement.elt, HTMLAudioElement);
        +    });
        +
        +    test('should accept a singular media source', function() {
        +      const mediaSource = mediaSources[0];
        +      const testElement = mockP5Prototype.createAudio(mediaSource);
        +      const sourceEl = testElement.elt.children[0];
        +
        +      assert.deepEqual(testElement.elt.childElementCount, 1);
        +      assert.instanceOf(sourceEl, HTMLSourceElement);
        +      assert.isTrue(sourceEl.src.endsWith(mediaSource));
        +    });
        +
        +    test('should accept multiple media sources', function() {
        +      const testElement = mockP5Prototype.createAudio(mediaSources);
        +
        +      assert.deepEqual(testElement.elt.childElementCount, mediaSources.length);
        +      for (let index = 0; index < mediaSources.length; index += 1) {
        +        const sourceEl = testElement.elt.children[index];
        +        assert.instanceOf(sourceEl, HTMLSourceElement);
        +        assert.isTrue(sourceEl.src.endsWith(mediaSources[index]));
        +      }
        +    });
        +
        +    // testSketchWithPromise(
        +    //   'should trigger callback on canplaythrough event',
        +    //   function(sketch, resolve, reject) {
        +    //     sketch.setup = function() {
        +    //       testElement = mockP5Prototype.createAudio(mediaSources, resolve);
        +    //       testElement.elt.dispatchEvent(new Event('canplaythrough'));
        +    //     };
        +    //   }
        +    // );
        +  });
        +
        +  suite('p5.prototype.createCapture', function() {
        +    afterEach(function() {
        +      document.body.innerHTML = "";
        +    });
        +
        +    test('should be a function', function() {
        +      assert.isFunction(mockP5Prototype.createCapture);
        +    });
        +
        +    test('should return p5.Element of video type', function() {
        +      const testElement = mockP5Prototype.createCapture(mockP5Prototype.VIDEO);
        +      assert.instanceOf(testElement, Element);
        +      assert.instanceOf(testElement.elt, HTMLVideoElement);
        +    });
        +
        +    // NOTE: play() failed because the user didn't interact with the document first.
        +    // testSketchWithPromise(
        +    //   'triggers the callback after loading metadata',
        +    //   function(sketch, resolve, reject) {
        +    //     sketch.setup = function() {
        +    //       testElement = myp5.createCapture(myp5.VIDEO, resolve);
        +    //       const mockedEvent = new Event('loadedmetadata');
        +    //       testElement.elt.dispatchEvent(mockedEvent);
        +    //     };
        +    //   }
        +    // );
        +
        +    // Required for ios 11 devices
        +    test('should have playsinline attribute to empty string on DOM element', function() {
        +      const testElement = mockP5Prototype.createCapture(mockP5Prototype.VIDEO);
        +      // Weird check, setter accepts : playinline, getter accepts playInline
        +      assert.isTrue(testElement.elt.playsInline);
        +    });
        +  });
        +});
        diff --git a/test/unit/events/acceleration.js b/test/unit/events/acceleration.js
        index 770f509b4e..6918ba1d1a 100644
        --- a/test/unit/events/acceleration.js
        +++ b/test/unit/events/acceleration.js
        @@ -1,15 +1,17 @@
        +import p5 from '../../../src/app.js';
        +
         suite('Acceleration Events', function() {
           var myp5;
        -  setup(function(done) {
        +
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -105,7 +107,7 @@ suite('Acceleration Events', function() {
             });
           });
         
        -  suite('deviceMoved', function() {
        +  suite.todo('deviceMoved', function() {
             test('deviceMoved must run when device is moved more than the threshold value', function() {
               let count = 0;
               myp5.deviceMoved = function() {
        @@ -143,7 +145,7 @@ suite('Acceleration Events', function() {
             });
           });
         
        -  suite('deviceTurned', function() {
        +  suite.todo('deviceTurned', function() {
             test('deviceTurned must run when device is turned more than 90 degrees', function() {
               let count = 0;
               myp5.deviceTurned = function() {
        @@ -173,7 +175,7 @@ suite('Acceleration Events', function() {
             });
           });
         
        -  suite('deviceShaken', function() {
        +  suite.todo('deviceShaken', function() {
             test('deviceShaken must run when device acceleration is more than the threshold value', function() {
               let count = 0;
               myp5.deviceShaken = function() {
        diff --git a/test/unit/events/keyboard.js b/test/unit/events/keyboard.js
        index 67fbc17c43..0e55657e88 100644
        --- a/test/unit/events/keyboard.js
        +++ b/test/unit/events/keyboard.js
        @@ -1,16 +1,18 @@
        +import p5 from '../../../src/app.js';
        +import { parallelSketches } from '../../js/p5_helpers';
        +
         suite('Keyboard Events', function() {
           var myp5;
         
        -  setup(function(done) {
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -36,39 +38,23 @@ suite('Keyboard Events', function() {
             });
           });
         
        -  suite('p5.prototype.isKeyPressed', function() {
        -    test('isKeyPressed should be a boolean', function() {
        -      assert.isBoolean(myp5.isKeyPressed);
        -    });
        -
        -    test('isKeyPressed should be true on key press', function() {
        -      window.dispatchEvent(new KeyboardEvent('keydown'));
        -      assert.strictEqual(myp5.isKeyPressed, true);
        -    });
        -
        -    test('isKeyPressed should be false on key up', function() {
        -      window.dispatchEvent(new KeyboardEvent('keyup'));
        -      assert.strictEqual(myp5.isKeyPressed, false);
        -    });
        -  });
        -
           suite('p5.prototype.key', function() {
        -    test('key should be a string', function() {
        +    test('key should be a string', async function() {
               window.dispatchEvent(new KeyboardEvent('keydown', { key: 's' }));
               assert.isString(myp5.key);
             });
         
        -    test('key should return the key pressed', function() {
        +    test.todo('key should return the key pressed', function() {
               window.dispatchEvent(new KeyboardEvent('keydown', { key: 'A' }));
               assert.strictEqual(myp5.key, 'A');
             });
         
        -    test('key should return the key pressed', function() {
        +    test.todo('key should return the key pressed', function() {
               window.dispatchEvent(new KeyboardEvent('keydown', { key: '9' }));
               assert.strictEqual(myp5.key, '9');
             });
         
        -    test('key should return the key pressed', function() {
        +    test.todo('key should return the key pressed', function() {
               window.dispatchEvent(new KeyboardEvent('keydown', { key: 'CapsLock' }));
               assert.strictEqual(myp5.key, 'CapsLock');
             });
        @@ -87,7 +73,7 @@ suite('Keyboard Events', function() {
           });
         
           suite('keyPressed', function() {
        -    test('keyPressed must run when key is pressed', function() {
        +    test.todo('keyPressed must run when key is pressed', function() {
               let count = 0;
               myp5.keyPressed = function() {
                 count += 1;
        @@ -188,7 +174,7 @@ suite('Keyboard Events', function() {
               assert.strictEqual(myp5.keyIsDown(35), true);
             });
         
        -    test('keyIsDown should return false if key is not down', function() {
        +    test.todo('keyIsDown should return false if key is not down', function() {
               assert.strictEqual(myp5.keyIsDown(35), false);
             });
           });
        diff --git a/test/unit/events/mouse.js b/test/unit/events/mouse.js
        index fd3003ce02..baa956925c 100644
        --- a/test/unit/events/mouse.js
        +++ b/test/unit/events/mouse.js
        @@ -1,4 +1,7 @@
        -suite('Mouse Events', function() {
        +import p5 from '../../../src/app.js';
        +import { parallelSketches } from '../../js/p5_helpers';
        +
        +suite.todo('Mouse Events', function() {
           let myp5;
         
           let canvas;
        @@ -10,7 +13,7 @@ suite('Mouse Events', function() {
           let touchEvent1;
           let touchEvent2;
         
        -  setup(function(done) {
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        @@ -36,17 +39,16 @@ suite('Mouse Events', function() {
                 touchEvent2 = new TouchEvent('touchmove', {
                   touches: [touchObj2]
                 });
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        -  let mouseEvent1 = new MouseEvent('mousemove', { clientX: 100, clientY: 100 });
        -  let mouseEvent2 = new MouseEvent('mousemove', { clientX: 200, clientY: 200 });
        +  let mouseEvent1 = new PointerEvent('pointermove', { clientX: 100, clientY: 100 });
        +  let mouseEvent2 = new PointerEvent('pointermove', { clientX: 200, clientY: 200 });
         
           suite('p5.prototype._hasMouseInteracted', function() {
             test('_hasMouseInteracted should be a boolean', function() {
        @@ -198,7 +200,7 @@ suite('Mouse Events', function() {
               assert.isNumber(myp5.pwinMouseY);
             });
         
        -    test('pwinMouseY should be previous vertical position of mouse relative to the window', function() {
        +    test('pwinMouseY should be previous vertical position of mouse relative to the window', async function() {
               window.dispatchEvent(mouseEvent1); // dispatch first mouse event
               window.dispatchEvent(mouseEvent2); // dispatch second mouse event
               assert.strictEqual(myp5.pwinMouseY, mouseEvent1.clientY);
        @@ -221,17 +223,17 @@ suite('Mouse Events', function() {
             });
         
             test('mouseButton should be "left" on left mouse button click', function() {
        -      window.dispatchEvent(new MouseEvent('mousedown', { button: 0 }));
        +      window.dispatchEvent(new PointerEvent('pointerdown', { button: 0 }));
               assert.strictEqual(myp5.mouseButton, 'left');
             });
         
             test('mouseButton should be "center" on auxillary mouse button click', function() {
        -      window.dispatchEvent(new MouseEvent('mousedown', { button: 1 }));
        +      window.dispatchEvent(new PointerEvent('pointerdown', { button: 1 }));
               assert.strictEqual(myp5.mouseButton, 'center');
             });
         
             test('mouseButton should be "right" on right mouse button click', function() {
        -      window.dispatchEvent(new MouseEvent('mousedown', { button: 2 }));
        +      window.dispatchEvent(new PointerEvent('pointerdown', { button: 2 }));
               assert.strictEqual(myp5.mouseButton, 'right');
             });
           });
        @@ -241,7 +243,7 @@ suite('Mouse Events', function() {
               assert.isBoolean(myp5.mouseIsPressed);
             });
         
        -    test('mouseIsPressed should be false if mouse is not pressed', function() {
        +    test.todo('mouseIsPressed should be false if mouse is not pressed', function() {
               assert.strictEqual(myp5.mouseIsPressed, false);
             });
         
        @@ -252,7 +254,7 @@ suite('Mouse Events', function() {
           });
         
           suite('mouseMoved', function() {
        -    test('mouseMoved function must run when mouse is moved', async function() {
        +    test.todo('mouseMoved function must run when mouse is moved', async function() {
               let count = 0;
         
               myp5.mouseMoved = function() {
        @@ -278,7 +280,7 @@ suite('Mouse Events', function() {
               };
               let sketches = parallelSketches([sketchFn, sketchFn]); //create two sketches
               await sketches.setup; //wait for all sketches to setup
        -      window.dispatchEvent(new MouseEvent('mousemove')); //dispatch a mouse event to trigger the mouseMoved functions
        +      window.dispatchEvent(new PointerEvent('pointermove')); //dispatch a mouse event to trigger the mouseMoved functions
               sketches.end(); //resolve all sketches by calling their finish functions
               let counts = await sketches.result; //get array holding number of times mouseMoved was called. Rejected sketches also thrown here
               assert.deepEqual(counts, [1, 1]);
        @@ -293,8 +295,8 @@ suite('Mouse Events', function() {
                 count += 1;
               };
         
        -      window.dispatchEvent(new MouseEvent('mousedown')); //dispatch a mousedown event
        -      window.dispatchEvent(new MouseEvent('mousemove')); //dispatch mousemove event while mouse is down to trigger mouseDragged
        +      window.dispatchEvent(new PointerEvent('pointerdown')); //dispatch a mousedown event
        +      window.dispatchEvent(new PointerEvent('pointermove')); //dispatch mousemove event while mouse is down to trigger mouseDragged
               assert.deepEqual(count, 1);
             });
         
        @@ -312,8 +314,8 @@ suite('Mouse Events', function() {
               };
               let sketches = parallelSketches([sketchFn, sketchFn]); //create two sketches
               await sketches.setup; //wait for all sketches to setup
        -      window.dispatchEvent(new MouseEvent('mousedown')); //dispatch a mousedown event
        -      window.dispatchEvent(new MouseEvent('mousemove')); //dispatch mousemove event while mouse is down to trigger mouseDragged
        +      window.dispatchEvent(new PointerEvent('pointerdown')); //dispatch a mousedown event
        +      window.dispatchEvent(new PointerEvent('pointermove')); //dispatch mousemove event while mouse is down to trigger mouseDragged
               sketches.end(); //resolve all sketches by calling their finish functions
               let counts = await sketches.result; //get array holding number of times mouseDragged was called. Rejected sketches also thrown here
               assert.deepEqual(counts, [1, 1]);
        @@ -328,7 +330,7 @@ suite('Mouse Events', function() {
                 count += 1;
               };
         
        -      window.dispatchEvent(new MouseEvent('mousedown'));
        +      window.dispatchEvent(new PointerEvent('pointerdown'));
               assert.deepEqual(count, 1);
             });
         
        @@ -346,7 +348,7 @@ suite('Mouse Events', function() {
               };
               let sketches = parallelSketches([sketchFn, sketchFn]); //create two sketches
               await sketches.setup; //wait for all sketches to setup
        -      window.dispatchEvent(new MouseEvent('mousedown'));
        +      window.dispatchEvent(new PointerEvent('pointerdown'));
               sketches.end(); //resolve all sketches by calling their finish functions
               let counts = await sketches.result; //get array holding number of times mouseDragged was called. Rejected sketches also thrown here
               assert.deepEqual(counts, [1, 1]);
        @@ -361,7 +363,7 @@ suite('Mouse Events', function() {
                 count += 1;
               };
         
        -      window.dispatchEvent(new MouseEvent('mouseup'));
        +      window.dispatchEvent(new PointerEvent('pointerup'));
               assert.deepEqual(count, 1);
             });
         
        @@ -379,7 +381,7 @@ suite('Mouse Events', function() {
               };
               let sketches = parallelSketches([sketchFn, sketchFn]); //create two sketches
               await sketches.setup; //wait for all sketches to setup
        -      window.dispatchEvent(new MouseEvent('mouseup'));
        +      window.dispatchEvent(new PointerEvent('pointerup'));
               sketches.end(); //resolve all sketches by calling their finish functions
               let counts = await sketches.result; //get array holding number of times mouseReleased was called. Rejected sketches also thrown here
               assert.deepEqual(counts, [1, 1]);
        diff --git a/test/unit/events/touch.js b/test/unit/events/touch.js
        index 776b1095c6..54677a59cc 100644
        --- a/test/unit/events/touch.js
        +++ b/test/unit/events/touch.js
        @@ -1,41 +1,35 @@
        +import p5 from '../../../src/app.js';
        +import { parallelSketches } from '../../js/p5_helpers';
        +
         suite('Touch Events', function() {
           let myp5;
         
        -  let canvas;
        -  let touchObj1;
        -  let touchObj2;
           let touchEvent1;
           let touchEvent2;
         
        -  setup(function(done) {
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        canvas = myp5._curElement.elt;
        -        touchObj1 = new Touch({
        -          target: canvas,
        +        touchEvent1 = new PointerEvent('pointerdown', {
        +          pointerId: 1,
                   clientX: 100,
                   clientY: 100,
        -          identifier: 36
        +          pointerType: 'touch'
                 });
        -        touchObj2 = new Touch({
        -          target: canvas,
        +
        +        // Simulate second touch event
        +        touchEvent2 = new PointerEvent('pointerdown', {
        +          pointerId: 2,
                   clientX: 200,
                   clientY: 200,
        -          identifier: 35
        -        });
        -        touchEvent1 = new TouchEvent('touchmove', {
        -          touches: [touchObj1, touchObj2]
        +          pointerType: 'touch'
                 });
        -        touchEvent2 = new TouchEvent('touchmove', {
        -          touches: [touchObj2]
        -        });
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -46,135 +40,12 @@ suite('Touch Events', function() {
         
             test('should be an array of multiple touches', function() {
               window.dispatchEvent(touchEvent1);
        +      window.dispatchEvent(touchEvent2);
               assert.strictEqual(myp5.touches.length, 2);
             });
         
             test('should contain the touch registered', function() {
        -      window.dispatchEvent(touchEvent2);
        -      assert.strictEqual(myp5.touches[0].id, 35);
        -    });
        -  });
        -
        -  suite('touchStarted', function() {
        -    test('touchStarted should be fired when a touch is registered', function() {
        -      let count = 0;
        -      myp5.touchStarted = function() {
        -        count += 1;
        -      };
        -      window.dispatchEvent(new TouchEvent('touchstart'));
        -      assert.strictEqual(count, 1);
        -    });
        -
        -    test('should be fired when a touch starts over the element', function() {
        -      let count = 0;
        -      let div = myp5.createDiv();
        -      let divTouchStarted = function() {
        -        count += 1;
        -      };
        -      div.touchStarted(divTouchStarted);
        -      div.elt.dispatchEvent(new TouchEvent('touchstart'));
        -      assert.strictEqual(count, 1);
        -    });
        -
        -    test('touchStarted functions on multiple instances must run once', async function() {
        -      let sketchFn = function(sketch, resolve, reject) {
        -        let count = 0;
        -        sketch.touchStarted = function() {
        -          count += 1;
        -        };
        -
        -        sketch.finish = function() {
        -          resolve(count);
        -        };
        -      };
        -      let sketches = parallelSketches([sketchFn, sketchFn]); //create two sketches
        -      await sketches.setup; //wait for all sketches to setup
        -      window.dispatchEvent(new TouchEvent('touchstart'));
        -      sketches.end(); //resolve all sketches by calling their finish functions
        -      let counts = await sketches.result;
        -      assert.deepEqual(counts, [1, 1]);
        -    });
        -  });
        -
        -  suite('touchMoved', function() {
        -    test('touchMoved should be fired when a touchmove is registered', function() {
        -      let count = 0;
        -      myp5.touchMoved = function() {
        -        count += 1;
        -      };
        -      window.dispatchEvent(touchEvent2);
        -      assert.strictEqual(count, 1);
        -    });
        -
        -    test('should be fired when a touchmove is registered over the element', function() {
        -      let count = 0;
        -      let div = myp5.createDiv();
        -      let divTouchMoved = function() {
        -        count += 1;
        -      };
        -      div.touchMoved(divTouchMoved);
        -      div.elt.dispatchEvent(touchEvent2);
        -      assert.strictEqual(count, 1);
        -    });
        -
        -    test('touchMoved functions on multiple instances must run once', async function() {
        -      let sketchFn = function(sketch, resolve, reject) {
        -        let count = 0;
        -        sketch.touchMoved = function() {
        -          count += 1;
        -        };
        -
        -        sketch.finish = function() {
        -          resolve(count);
        -        };
        -      };
        -      let sketches = parallelSketches([sketchFn, sketchFn]); //create two sketches
        -      await sketches.setup; //wait for all sketches to setup
        -      window.dispatchEvent(touchEvent2);
        -      sketches.end(); //resolve all sketches by calling their finish functions
        -      let counts = await sketches.result;
        -      assert.deepEqual(counts, [1, 1]);
        -    });
        -  });
        -
        -  suite('touchEnded', function() {
        -    test('touchEnded must run when a touch is registered', function() {
        -      let count = 0;
        -      myp5.touchEnded = function() {
        -        count += 1;
        -      };
        -      window.dispatchEvent(new TouchEvent('touchend'));
        -      assert.strictEqual(count, 1);
        -    });
        -
        -    test('should be fired when a touch starts over the element', function() {
        -      let count = 0;
        -      let div = myp5.createDiv();
        -      let divTouchEnded = function() {
        -        count += 1;
        -      };
        -      div.touchEnded(divTouchEnded);
        -      div.elt.dispatchEvent(new TouchEvent('touchend'));
        -      assert.strictEqual(count, 1);
        -    });
        -
        -    test('touchEnded functions on multiple instances must run once', async function() {
        -      let sketchFn = function(sketch, resolve, reject) {
        -        let count = 0;
        -        sketch.touchEnded = function() {
        -          count += 1;
        -        };
        -
        -        sketch.finish = function() {
        -          resolve(count);
        -        };
        -      };
        -      let sketches = parallelSketches([sketchFn, sketchFn]); //create two sketches
        -      await sketches.setup; //wait for all sketches to setup
        -      window.dispatchEvent(new TouchEvent('touchend'));
        -      sketches.end(); //resolve all sketches by calling their finish functions
        -      let counts = await sketches.result;
        -      assert.deepEqual(counts, [1, 1]);
        +      assert.strictEqual(myp5.touches[0].id, 1);
             });
           });
         });
        diff --git a/test/unit/image/downloading.js b/test/unit/image/downloading.js
        index fa3df09dd1..d2bdd4a794 100644
        --- a/test/unit/image/downloading.js
        +++ b/test/unit/image/downloading.js
        @@ -1,379 +1,199 @@
        -suite('downloading animated gifs', function() {
        -  let myp5;
        -  let myGif;
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import * as fileSaver from 'file-saver';
        +import { vi } from 'vitest';
        +import image from '../../../src/image/image';
        +import files from '../../../src/io/files';
        +import loading from '../../../src/image/loading_displaying';
        +import p5Image from '../../../src/image/p5.Image';
        +
        +vi.mock('file-saver');
        +
        +expect.extend({
        +  tobeGif: (received) => {
        +    if (received.type === 'image/gif') {
        +      return {
        +        message: 'expect blob to have type image/gif',
        +        pass: true
        +      }
        +    } else {
        +      return {
        +        message: 'expect blob to have type image/gif',
        +        pass: false
        +      }
        +    }
        +  },
        +  tobePng: (received) => {
        +    if (received.type === 'image/png') {
        +      return {
        +        message: 'expect blob to have type image/png',
        +        pass: true
        +      }
        +    } else {
        +      return {
        +        message: 'expect blob to have type image/png',
        +        pass: false
        +      }
        +    }
        +  },
        +  tobeJpg: (received) => {
        +    if (received.type === 'image/jpeg') {
        +      return {
        +        message: 'expect blob to have type image/jpeg',
        +        pass: true
        +      }
        +    } else {
        +      return {
        +        message: 'expect blob to have type image/jpeg',
        +        pass: false
        +      }
        +    }
        +  }
        +});
         
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +const wait = async (time) => {
        +  return new Promise(resolve => setTimeout(resolve, time));
        +}
         
        -  teardown(function() {
        -    myp5.remove();
        +suite('Downloading', () => {
        +  beforeAll(async function() {
        +    image(mockP5, mockP5Prototype);
        +    files(mockP5, mockP5Prototype);
        +    loading(mockP5, mockP5Prototype);
        +    p5Image(mockP5, mockP5Prototype);
           });
         
        -  let imagePath = 'unit/assets/nyan_cat.gif';
        -
        -  setup(function disableFileLoadError() {
        -    sinon.stub(p5, '_friendlyFileLoadError');
        +  afterEach(() => {
        +    vi.clearAllMocks();
           });
         
        -  teardown(function restoreFileLoadError() {
        -    p5._friendlyFileLoadError.restore();
        -  });
        +  suite('downloading animated gifs', function() {
        +    let myGif;
        +    const imagePath = '/test/unit/assets/nyan_cat.gif';
         
        -  setup(function loadMyGif(done) {
        -    myp5.loadImage(imagePath, function(pImg) {
        -      myGif = pImg;
        -      done();
        +    beforeAll(async function() {
        +      myGif = await mockP5Prototype.loadImage(imagePath);
             });
        -  });
         
        -  suite('p5.prototype.encodeAndDownloadGif', function() {
        -    test('should be a function', function() {
        -      assert.ok(myp5.encodeAndDownloadGif);
        -      assert.typeOf(myp5.encodeAndDownloadGif, 'function');
        -    });
        -    test('should not throw an error', function() {
        -      myp5.encodeAndDownloadGif(myGif);
        -    });
        -    testWithDownload('should download a gif', function(blobContainer) {
        -      myp5.encodeAndDownloadGif(myGif);
        -      let gifBlob = blobContainer.blob;
        -      assert.strictEqual(gifBlob.type, 'image/gif');
        -    });
        -  });
        -});
        +    suite('p5.prototype.encodeAndDownloadGif', function() {
        +      test('should be a function', function() {
        +        assert.ok(mockP5Prototype.encodeAndDownloadGif);
        +        assert.typeOf(mockP5Prototype.encodeAndDownloadGif, 'function');
        +      });
         
        -suite('p5.prototype.saveCanvas', function() {
        -  let myp5;
        -  let myCanvas;
        +      test('should not throw an error', function() {
        +        mockP5Prototype.encodeAndDownloadGif(myGif);
        +      });
         
        -  let waitForBlob = async function(blc) {
        -    let sleep = function(ms) {
        -      return new Promise(r => setTimeout(r, ms));
        -    };
        -    while (!blc.blob) {
        -      await sleep(5);
        -    }
        -  };
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        myCanvas = p.createCanvas(20, 20);
        -        p.background(255, 0, 0);
        -        done();
        -      };
        +      test('should download a gif', async () => {
        +        mockP5Prototype.encodeAndDownloadGif(myGif);
        +        expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +        expect(fileSaver.saveAs)
        +          .toHaveBeenCalledWith(
        +            expect.tobeGif(),
        +            'untitled.gif'
        +          );
        +      });
             });
           });
         
        -  teardown(function() {
        -    myp5.remove();
        -  });
        -
        -  test('should be a function', function() {
        -    assert.ok(myp5.saveCanvas);
        -    assert.typeOf(myp5.saveCanvas, 'function');
        -  });
        -
        -  // Why use testWithDownload for 'no friendly-err' tests here?
        -  // saveCanvas uses htmlcanvas.toBlob which uses a callback
        -  // mechanism and the download is triggered in its callback.
        -  // It may happen that the test get out of sync and the only way
        -  // to know if the callback has been called is if the blob has
        -  // been made available to us
        -  testWithDownload(
        -    'no friendly-err-msg I',
        -    async function(blc) {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.saveCanvas();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -      await waitForBlob(blc);
        -    },
        -    true
        -  );
        -  testWithDownload(
        -    'no friendly-err-msg II',
        -    async function(blc) {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.saveCanvas('filename');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -      await waitForBlob(blc);
        -    },
        -    true
        -  );
        -  testWithDownload(
        -    'no friendly-err-msg III',
        -    async function(blc) {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.saveCanvas('filename', 'png');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -      await waitForBlob(blc);
        -    },
        -    true
        -  );
        -  testWithDownload(
        -    'no friendly-err-msg IV',
        -    async function(blc) {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.saveCanvas(myCanvas, 'filename');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -      await waitForBlob(blc);
        -    },
        -    true
        -  );
        -  testWithDownload(
        -    'no friendly-err-msg V',
        -    async function(blc) {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.saveCanvas(myCanvas, 'filename', 'png');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    },
        -    true
        -  );
        -  testWithDownload(
        -    'no friendly-err-msg VI',
        -    async function(blc) {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.saveCanvas(myCanvas, 'filename', 'png');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -      await waitForBlob(blc);
        -    },
        -    true
        -  );
        -
        -  testUnMinified('wrong param type #0', function() {
        -    assert.validationError(function() {
        -      myp5.saveCanvas(5);
        +  suite('p5.prototype.saveCanvas', function() {
        +    test('should be a function', function() {
        +      assert.ok(mockP5Prototype.saveCanvas);
        +      assert.typeOf(mockP5Prototype.saveCanvas, 'function');
             });
        -  });
         
        -  testUnMinified('wrong param type #1', function() {
        -    assert.validationError(function() {
        -      myp5.saveCanvas(myCanvas, 5);
        +    test('should download a png file', async () => {
        +      mockP5Prototype.saveCanvas();
        +      await wait(100);
        +      expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +      expect(fileSaver.saveAs)
        +          .toHaveBeenCalledWith(
        +            expect.tobePng(),
        +            'untitled.png'
        +          );
             });
        -  });
         
        -  testUnMinified('wrong param type #2', function() {
        -    assert.validationError(function() {
        -      myp5.saveCanvas(myCanvas, 'filename', 5);
        +    test('should download a jpg file I', async () => {
        +      mockP5Prototype.saveCanvas('filename.jpg');
        +      await wait(100);
        +      expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +      expect(fileSaver.saveAs)
        +          .toHaveBeenCalledWith(
        +            expect.tobeJpg(),
        +            'filename.jpg'
        +          );
             });
        -  });
        -
        -  testWithDownload(
        -    'should download a png file',
        -    async function(blobContainer) {
        -      myp5.saveCanvas();
        -      // since a function with callback is used in saveCanvas
        -      // until the blob is made available to us.
        -      await waitForBlob(blobContainer);
        -      let myBlob = blobContainer.blob;
        -      assert.strictEqual(myBlob.type, 'image/png');
        -    },
        -    true
        -  );
        -
        -  testWithDownload(
        -    'should download a jpg file I',
        -    async function(blobContainer) {
        -      myp5.saveCanvas('filename.jpg');
        -      await waitForBlob(blobContainer);
        -      let myBlob = blobContainer.blob;
        -      assert.strictEqual(myBlob.type, 'image/jpeg');
        -    },
        -    true
        -  );
        -
        -  testWithDownload(
        -    'should download a jpg file II',
        -    async function(blobContainer) {
        -      myp5.saveCanvas('filename', 'jpg');
        -      await waitForBlob(blobContainer);
        -      let myBlob = blobContainer.blob;
        -      assert.strictEqual(myBlob.type, 'image/jpeg');
        -    },
        -    true
        -  );
        -});
         
        -suite('p5.prototype.saveFrames', function() {
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        p.createCanvas(10, 10);
        -        done();
        -      };
        +    test('should download a jpg file II', async () => {
        +      mockP5Prototype.saveCanvas('filename', 'jpg');
        +      await wait(100);
        +      expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +      expect(fileSaver.saveAs)
        +          .toHaveBeenCalledWith(
        +            expect.tobeJpg(),
        +            'filename.jpg'
        +          );
             });
           });
         
        -  teardown(function() {
        -    myp5.remove();
        -  });
        -
        -  test('should be a function', function() {
        -    assert.ok(myp5.saveFrames);
        -    assert.typeOf(myp5.saveFrames, 'function');
        -  });
        -
        -  test('no friendly-err-msg I', function() {
        -    assert.doesNotThrow(
        -      function() {
        -        myp5.saveFrames('out', 'png', 0.1, 25);
        -      },
        -      Error,
        -      'got unwanted exception'
        -    );
        -  });
        -  test('no friendly-err-msg II', function(done) {
        -    assert.doesNotThrow(
        -      function() {
        -        myp5.saveFrames('out', 'png', 0.1, 25, () => {
        -          done();
        +  suite('p5.prototype.saveFrames', function() {
        +    test('should be a function', function() {
        +      assert.ok(mockP5Prototype.saveFrames);
        +      assert.typeOf(mockP5Prototype.saveFrames, 'function');
        +    });
        +
        +    test('should get frames in callback (png)', async () => {
        +      return new Promise(resolve => {
        +        mockP5Prototype.saveFrames('aaa', 'png', 0.5, 25, function cb1(arr) {
        +          assert.typeOf(arr, 'array', 'we got an array');
        +          for (let i = 0; i < arr.length; i++) {
        +            assert.ok(arr[i].imageData);
        +            assert.equal(arr[i].ext, 'png');
        +            assert.equal(arr[i].filename, `aaa${i}`);
        +          }
        +          resolve();
                 });
        -      },
        -      Error,
        -      'got unwanted exception'
        -    );
        -  });
        -
        -  testUnMinified('missing param #2 #3', function() {
        -    assert.validationError(function() {
        -      myp5.saveFrames('out', 'png');
        -    });
        -  });
        -  testUnMinified('wrong param type #0', function() {
        -    assert.validationError(function() {
        -      myp5.saveFrames(0, 'png', 0.1, 25);
        -    });
        -  });
        -  testUnMinified('wrong param type #1', function() {
        -    assert.validationError(function() {
        -      myp5.saveFrames('out', 1, 0.1, 25);
        -    });
        -  });
        -  testUnMinified('wrong param type #2', function() {
        -    assert.validationError(function() {
        -      myp5.saveFrames('out', 'png', 'a', 25);
        -    });
        -  });
        -  testUnMinified('wrong param type #3', function() {
        -    assert.validationError(function() {
        -      myp5.saveFrames('out', 'png', 0.1, 'b');
        -    });
        -  });
        -  testUnMinified('wrong param type #4', function() {
        -    assert.validationError(function() {
        -      myp5.saveFrames('out', 'png', 0.1, 25, 5);
        -    });
        -  });
        -
        -  test('should get frames in callback (png)', function(done) {
        -    myp5.saveFrames('aaa', 'png', 0.5, 25, function cb1(arr) {
        -      assert.typeOf(arr, 'array', 'we got an array');
        -      for (let i = 0; i < arr.length; i++) {
        -        assert.ok(arr[i].imageData);
        -        assert.strictEqual(arr[i].ext, 'png');
        -        assert.strictEqual(arr[i].filename, `aaa${i}`);
        -      }
        -      done();
        -    });
        -  });
        -
        -  test('should get frames in callback (jpg)', function(done) {
        -    myp5.saveFrames('bbb', 'jpg', 0.5, 25, function cb2(arr2) {
        -      assert.typeOf(arr2, 'array', 'we got an array');
        -      for (let i = 0; i < arr2.length; i++) {
        -        assert.ok(arr2[i].imageData);
        -        assert.strictEqual(arr2[i].ext, 'jpg');
        -        assert.strictEqual(arr2[i].filename, `bbb${i}`);
        -      }
        -      done();
        +      });
        +    });
        +
        +    test('should get frames in callback (png)', async () => {
        +      return new Promise(resolve => {
        +        mockP5Prototype.saveFrames('aaa', 'jpg', 0.5, 25, function cb1(arr) {
        +          assert.typeOf(arr, 'array', 'we got an array');
        +          for (let i = 0; i < arr.length; i++) {
        +            assert.ok(arr[i].imageData);
        +            assert.equal(arr[i].ext, 'jpg');
        +            assert.equal(arr[i].filename, `aaa${i}`);
        +          }
        +          resolve();
        +        });
        +      });
             });
           });
        -});
         
        -suite('p5.prototype.saveGif', function() {
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        p.createCanvas(10, 10);
        -        done();
        -      };
        +  suite('p5.prototype.saveGif', function() {
        +    test('should be a function', function() {
        +      assert.ok(mockP5Prototype.saveGif);
        +      assert.typeOf(mockP5Prototype.saveGif, 'function');
             });
        -  });
         
        -  teardown(function() {
        -    myp5.remove();
        -  });
        -
        -  test('should be a function', function() {
        -    assert.ok(myp5.saveGif);
        -    assert.typeOf(myp5.saveGif, 'function');
        -  });
        -
        -  test('should not throw an error', function() {
        -    myp5.saveGif('myGif', 3);
        -  });
        -
        -  test('should not throw an error', function() {
        -    myp5.saveGif('myGif', 3, { delay: 2, frames: 'seconds' });
        -  });
        -
        -  test('wrong parameter type #0', function(done) {
        -    assert.validationError(function() {
        -      myp5.saveGif(2, 2);
        -      done();
        +    // TODO: this implementation need refactoring
        +    test.todo('should not throw an error', async () => {
        +      await mockP5Prototype.saveGif('myGif', 3);
             });
        -  });
         
        -  test('wrong parameter type #1', function(done) {
        -    assert.validationError(function() {
        -      myp5.saveGif('mySketch', '2');
        -      done();
        +    test.todo('should not throw an error', async () => {
        +      await mockP5Prototype.saveGif('myGif', 3, { delay: 2, frames: 'seconds' });
             });
        -  });
         
        -  test('wrong parameter type #2', function(done) {
        -    assert.validationError(function() {
        -      myp5.saveGif('mySketch', 2, 'delay');
        -      done();
        +    test.todo('should download a GIF', async () => {
        +      await mockP5Prototype.saveGif('myGif', 3, 2);
        +      expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +      expect(fileSaver.saveAs)
        +          .toHaveBeenCalledWith(
        +            expect.tobeGif(),
        +            'myGif.gif'
        +          );
             });
           });
        -
        -  testWithDownload('should download a GIF', async function(blobContainer) {
        -    myp5.saveGif(myGif, 3, 2);
        -    await waitForBlob(blobContainer);
        -    let gifBlob = blobContainer.blob;
        -    assert.strictEqual(gifBlob.type, 'image/gif');
        -  });
         });
        diff --git a/test/unit/image/filters.js b/test/unit/image/filters.js
        index e86383f698..9859b220e7 100644
        --- a/test/unit/image/filters.js
        +++ b/test/unit/image/filters.js
        @@ -1,7 +1,10 @@
        -suite('Filters', function() {
        +import p5 from '../../../src/app.js';
        +
        +suite.todo('Filters', function() {
           var myp5;
           let img;
        -  setup(function(done) {
        +
        +  beforeEach(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        @@ -15,12 +18,11 @@ suite('Filters', function() {
                   }
                 }
                 img.updatePixels();
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterEach(function() {
             myp5.remove();
           });
         
        diff --git a/test/unit/image/loading.js b/test/unit/image/loading.js
        index 8f8d23e66d..801705294e 100644
        --- a/test/unit/image/loading.js
        +++ b/test/unit/image/loading.js
        @@ -1,3 +1,10 @@
        +import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
        +import loadingDisplaying from '../../../src/image/loading_displaying';
        +import image from '../../../src/image/p5.Image';
        +
        +import p5 from '../../../src/app.js';
        +import { vi } from 'vitest';
        +
         /**
          * Expects an image file and a p5 instance with an image file loaded and drawn
          * and checks that they are exactly the same. Sends result to the callback.
        @@ -26,422 +33,242 @@ var testImageRender = function(file, sketch) {
         };
         
         suite('loading images', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -
        -      // Make sure draw() exists so timing functions still run each frame
        -      // and we can test gif animation
        -      p.draw = function() {};
        -    });
        -  });
        -
        -  teardown(function() {
        -    myp5.remove();
        -  });
        -
        -  var imagePath = 'unit/assets/cat.jpg';
        -
        -  setup(function disableFileLoadError() {
        -    sinon.stub(p5, '_friendlyFileLoadError');
        -  });
        -
        -  teardown(function restoreFileLoadError() {
        -    p5._friendlyFileLoadError.restore();
        -  });
        -
        -  test('should call successCallback when image loads', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage(imagePath, resolve, reject);
        -    }).then(function(pImg) {
        -      assert.ok(pImg, 'cat.jpg loaded');
        -      assert.isTrue(pImg instanceof p5.Image);
        -    });
        -  });
        -
        -  test('should call failureCallback when unable to load image', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage(
        -        'invalid path',
        -        function(pImg) {
        -          reject('Entered success callback.');
        -        },
        -        resolve
        -      );
        -    }).then(function(event) {
        -      assert.equal(event.type, 'error');
        -      assert.isTrue(p5._friendlyFileLoadError.called);
        -    });
        -  });
        -
        -  test('should draw image with defaults', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage('unit/assets/cat.jpg', resolve, reject);
        -    }).then(function(img) {
        -      myp5.image(img, 0, 0);
        -      return testImageRender('unit/assets/cat.jpg', myp5).then(function(res) {
        -        assert.isTrue(res);
        +  const imagePath = '/test/unit/assets/cat.jpg';
        +  const singleFrameGif = '/test/unit/assets/target_small.gif';
        +  const animatedGif = '/test/unit/assets/white_black.gif';
        +  const nyanCatGif = '/test/unit/assets/nyan_cat.gif';
        +  const disposeNoneGif = '/test/unit/assets/dispose_none.gif';
        +  const disposeBackgroundGif = '/test/unit/assets/dispose_background.gif';
        +  const disposePreviousGif = '/test/unit/assets/dispose_previous.gif';
        +  const invalidFile = '404file';
        +
        +  beforeAll(async function() {
        +    loadingDisplaying(mockP5, mockP5Prototype);
        +    image(mockP5, mockP5Prototype);
        +    await httpMock.start({quiet: true});
        +  });
        +
        +  test('throws error when encountering HTTP errors', async () => {
        +    await expect(mockP5Prototype.loadImage(invalidFile))
        +      .rejects
        +      .toThrow('Not Found');
        +  });
        +
        +  test('error callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadImage(invalidFile, () => {
        +        reject("Success callback executed");
        +      }, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
               });
             });
           });
         
        -  test('static image should not have gifProperties', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage('unit/assets/cat.jpg', resolve, reject);
        -    }).then(function(img) {
        -      assert.isTrue(img.gifProperties === null);
        -    });
        -  });
        -
        -  test('single frame GIF should not have gifProperties', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage('unit/assets/target_small.gif', resolve, reject);
        -    }).then(function(img) {
        -      assert.isTrue(img.gifProperties === null);
        -    });
        -  });
        -
        -  test('first frame of GIF should be painted after load', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage('unit/assets/white_black.gif', resolve, reject);
        -    }).then(function(img) {
        -      assert.deepEqual(img.get(0, 0), [255, 255, 255, 255]);
        -    });
        -  });
        -
        -  test('animated gifs animate correctly', function() {
        -    const wait = function(ms) {
        -      return new Promise(function(resolve) {
        -        setTimeout(resolve, ms);
        -      });
        -    };
        -    let img;
        -    return new Promise(function(resolve, reject) {
        -      img = myp5.loadImage('unit/assets/nyan_cat.gif', resolve, reject);
        -    }).then(function() {
        -      assert.equal(img.gifProperties.displayIndex, 0);
        -      myp5.image(img, 0, 0);
        -
        -      // This gif has frames that are around for 100ms each.
        -      // After 100ms has elapsed, the display index should
        -      // increment when we draw the image.
        -      return wait(100);
        -    }).then(function() {
        -      return new Promise(function(resolve) {
        -        window.requestAnimationFrame(resolve);
        +  test('success callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadImage(imagePath, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
        +      }, (err) => {
        +        reject(`Error callback called: ${err.toString()}`);
               });
        -    }).then(function() {
        -      myp5.image(img, 0, 0);
        -      assert.equal(img.gifProperties.displayIndex, 1);
        -    });
        -  });
        -
        -  var backgroundColor = [135, 206, 235, 255];
        -  var blue = [0, 0, 255, 255];
        -  var transparent = [0, 0, 0, 0];
        -  test('animated gifs work with no disposal', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage('unit/assets/dispose_none.gif', resolve, reject);
        -    }).then(function(img) {
        -      // Frame 0 shows the background
        -      assert.deepEqual(img.get(7, 12), backgroundColor);
        -      // Frame 1 draws on top of the background
        -      img.setFrame(1);
        -      assert.deepEqual(img.get(7, 12), blue);
        -      // Frame 2 does not erase untouched parts of frame 2
        -      img.setFrame(2);
        -      assert.deepEqual(img.get(7, 12), blue);
        -    });
        -  });
        -
        -  test('animated gifs work with background disposal', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage('unit/assets/dispose_background.gif', resolve, reject);
        -    }).then(function(img) {
        -      // Frame 0 shows the background
        -      assert.deepEqual(img.get(7, 12), backgroundColor);
        -      // Frame 1 draws on top of the background
        -      img.setFrame(1);
        -      assert.deepEqual(img.get(7, 12), blue);
        -      // Frame 2 erases the content added in frame 2
        -      img.setFrame(2);
        -      assert.deepEqual(img.get(7, 12), transparent);
             });
           });
         
        -  test('animated gifs work with previous disposal', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage('unit/assets/dispose_previous.gif', resolve, reject);
        -    }).then(function(img) {
        -      // Frame 0 shows the background
        -      assert.deepEqual(img.get(7, 12), backgroundColor);
        -      // Frame 1 draws on top of the background
        -      img.setFrame(1);
        -      assert.deepEqual(img.get(7, 12), blue);
        -      // Frame 2 returns the content added in frame 2 to its previous value
        -      img.setFrame(2);
        -      assert.deepEqual(img.get(7, 12), backgroundColor);
        -    });
        +  test('returns an object with correct data', async () => {
        +    const pImg = await mockP5Prototype.loadImage(imagePath);
        +    assert.ok(pImg, 'cat.jpg loaded');
        +    assert.isTrue(pImg instanceof mockP5.Image);
           });
         
        -  /* TODO: make this resilient to platform differences in image resizing.
        -  test('should draw cropped image', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage('unit/assets/target.gif', resolve, reject);
        -    }).then(function(img) {
        -      myp5.image(img, 0, 0, 6, 6, 5, 5, 6, 6);
        -      return testImageRender('unit/assets/target_small.gif', myp5).then(
        -        function(res) {
        -          assert.isTrue(res);
        -        }
        -      );
        +  test('passes an object with correct data to success callback', async () => {
        +    await mockP5Prototype.loadImage(imagePath, (pImg) => {
        +      assert.ok(pImg, 'cat.jpg loaded');
        +      assert.isTrue(pImg instanceof mockP5.Image);
             });
           });
        -  */
        -
        -  // Test loading image in preload() with success callback
        -  test('Test in preload() with success callback');
        -  test('Test in setup() after preload()');
        -  // These tests don't work correctly (You can't use suite and test like that)
        -  // they simply get added at the root level.
        -  var mySketch = function(this_p5) {
        -    var myImage;
        -    this_p5.preload = function() {
        -      suite('Test in preload() with success callback', function() {
        -        test('Load asynchronously and use success callback', function(done) {
        -          myImage = this_p5.loadImage('unit/assets/cat.jpg', function() {
        -            assert.ok(myImage);
        -            done();
        -          });
        -        });
        -      });
        -    };
        -
        -    this_p5.setup = function() {
        -      suite('setup() after preload() with success callback', function() {
        -        test('should be loaded if preload() finished', function(done) {
        -          assert.isTrue(myImage instanceof p5.Image);
        -          assert.isTrue(myImage.width > 0 && myImage.height > 0);
        -          done();
        -        });
        -      });
        -    };
        -  };
        -  new p5(mySketch, null, false);
        -
        -  // Test loading image in preload() without success callback
        -  mySketch = function(this_p5) {
        -    var myImage;
        -    this_p5.preload = function() {
        -      myImage = this_p5.loadImage('unit/assets/cat.jpg');
        -    };
        -
        -    this_p5.setup = function() {
        -      suite('setup() after preload() without success callback', function() {
        -        test('should be loaded now preload() finished', function(done) {
        -          assert.isTrue(myImage instanceof p5.Image);
        -          assert.isTrue(myImage.width > 0 && myImage.height > 0);
        -          done();
        -        });
        -      });
        -    };
        -  };
        -  new p5(mySketch, null, false);
        -
        -  // Test loading image failure in preload() without failure callback
        -  mySketch = function(this_p5) {
        -    this_p5.preload = function() {
        -      this_p5.loadImage('', function() {
        -        throw new Error('Should not be called');
        -      });
        -    };
        -
        -    this_p5.setup = function() {
        -      throw new Error('Should not be called');
        -    };
        -  };
        -  new p5(mySketch, null, false);
        -
        -  // Test loading image failure in preload() with failure callback
        -  mySketch = function(this_p5) {
        -    var myImage;
        -    this_p5.preload = function() {
        -      suite('Test loading image failure in preload() with failure callback', function() {
        -        test('Load fail and use failure callback', function(done) {
        -          myImage = this_p5.loadImage('', function() {
        -            assert.fail();
        -            done();
        -          }, function() {
        -            assert.ok(myImage);
        -            done();
        -          });
        -        });
        -      });
        -    };
         
        -    this_p5.setup = function() {
        -      suite('setup() after preload() failure with failure callback', function() {
        -        test('should be loaded now preload() finished', function(done) {
        -          assert.isTrue(myImage instanceof p5.Image);
        -          assert.isTrue(myImage.width === 1 && myImage.height === 1);
        -          done();
        -        });
        -      });
        +  // TODO: this is more of an integration test, possibly delegate to visual test
        +  // test('should draw image with defaults', function() {
        +  //   return new Promise(function(resolve, reject) {
        +  //     myp5.loadImage('unit/assets/cat.jpg', resolve, reject);
        +  //   }).then(function(img) {
        +  //     myp5.image(img, 0, 0);
        +  //     return testImageRender('unit/assets/cat.jpg', myp5).then(function(res) {
        +  //       assert.isTrue(res);
        +  //     });
        +  //   });
        +  // });
        +
        +  test('static image should not have gifProperties', async () => {
        +    const img = await mockP5Prototype.loadImage(imagePath);
        +    assert.isNull(img.gifProperties);
        +  });
        +
        +  test('single frame GIF should not have gifProperties', async () => {
        +    const img = await mockP5Prototype.loadImage(singleFrameGif);
        +    assert.isNull(img.gifProperties);
        +  });
        +
        +  test('first frame of GIF should be painted after load', async () => {
        +    const img = await mockP5Prototype.loadImage(animatedGif);
        +    assert.deepEqual(img.get(0, 0), [255, 255, 255, 255]);
        +  });
        +
        +  // test('animated gifs animate correctly', function() {
        +  //   const wait = function(ms) {
        +  //     return new Promise(function(resolve) {
        +  //       setTimeout(resolve, ms);
        +  //     });
        +  //   };
        +  //   let img;
        +  //   return new Promise(function(resolve, reject) {
        +  //     img = myp5.loadImage('unit/assets/nyan_cat.gif', resolve, reject);
        +  //   }).then(function() {
        +  //     assert.equal(img.gifProperties.displayIndex, 0);
        +  //     myp5.image(img, 0, 0);
        +
        +  //     // This gif has frames that are around for 100ms each.
        +  //     // After 100ms has elapsed, the display index should
        +  //     // increment when we draw the image.
        +  //     return wait(100);
        +  //   }).then(function() {
        +  //     return new Promise(function(resolve) {
        +  //       window.requestAnimationFrame(resolve);
        +  //     });
        +  //   }).then(function() {
        +  //     myp5.image(img, 0, 0);
        +  //     assert.equal(img.gifProperties.displayIndex, 1);
        +  //   });
        +  // });
        +
        +  const backgroundColor = [135, 206, 235, 255];
        +  const blue = [0, 0, 255, 255];
        +  const transparent = [0, 0, 0, 0];
        +  test('animated gifs work with no disposal', async () => {
        +    const img = await mockP5Prototype.loadImage(disposeNoneGif);
        +    // Frame 0 shows the background
        +    assert.deepEqual(img.get(7, 12), backgroundColor);
        +    // Frame 1 draws on top of the background
        +    img.setFrame(1);
        +    assert.deepEqual(img.get(7, 12), blue);
        +    // Frame 2 does not erase untouched parts of frame 2
        +    img.setFrame(2);
        +    assert.deepEqual(img.get(7, 12), blue);
        +  });
        +
        +  test('animated gifs work with background disposal', async () => {
        +    const img = await mockP5Prototype.loadImage(disposeBackgroundGif);
        +    // Frame 0 shows the background
        +    assert.deepEqual(img.get(7, 12), backgroundColor);
        +    // Frame 1 draws on top of the background
        +    img.setFrame(1);
        +    assert.deepEqual(img.get(7, 12), blue);
        +    // Frame 2 erases the content added in frame 2
        +    img.setFrame(2);
        +    assert.deepEqual(img.get(7, 12), transparent);
        +  });
        +
        +  test('animated gifs work with previous disposal', async () => {
        +    const img = await mockP5Prototype.loadImage(disposePreviousGif);
        +    // Frame 0 shows the background
        +    assert.deepEqual(img.get(7, 12), backgroundColor);
        +    // Frame 1 draws on top of the background
        +    img.setFrame(1);
        +    assert.deepEqual(img.get(7, 12), blue);
        +    // Frame 2 returns the content added in frame 2 to its previous value
        +    img.setFrame(2);
        +    assert.deepEqual(img.get(7, 12), backgroundColor);
        +  });
        +
        +  // /* TODO: make this resilient to platform differences in image resizing.
        +  // test('should draw cropped image', function() {
        +  //   return new Promise(function(resolve, reject) {
        +  //     myp5.loadImage('unit/assets/target.gif', resolve, reject);
        +  //   }).then(function(img) {
        +  //     myp5.image(img, 0, 0, 6, 6, 5, 5, 6, 6);
        +  //     return testImageRender('unit/assets/target_small.gif', myp5).then(
        +  //       function(res) {
        +  //         assert.isTrue(res);
        +  //       }
        +  //     );
        +  //   });
        +  // });
        +  // */
        +
        +  test('should construct gifProperties correctly after preload', async () => {
        +    const gifImage = await mockP5Prototype.loadImage(nyanCatGif);
        +    assert.isTrue(gifImage instanceof p5.Image);
        +
        +    const nyanCatGifProperties = {
        +      displayIndex: 0,
        +      loopCount: 0,
        +      loopLimit: null,
        +      numFrames: 6,
        +      playing: true,
        +      timeDisplayed: 0
             };
        -  };
        -  new p5(mySketch, null, false);
        -});
        -
        -suite('loading animated gif images', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        -
        -  teardown(function() {
        -    myp5.remove();
        -  });
        -
        -  var imagePath = 'unit/assets/nyan_cat.gif';
        -
        -  setup(function disableFileLoadError() {
        -    sinon.stub(p5, '_friendlyFileLoadError');
        -  });
        -
        -  teardown(function restoreFileLoadError() {
        -    p5._friendlyFileLoadError.restore();
        -  });
        -
        -  test('should call successCallback when image loads', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage(imagePath, resolve, reject);
        -    }).then(function(pImg) {
        -      assert.ok(pImg, 'nyan_cat.gif loaded');
        -      assert.isTrue(pImg instanceof p5.Image);
        -    });
        -  });
        -
        -  test('should call failureCallback when unable to load image', function() {
        -    return new Promise(function(resolve, reject) {
        -      myp5.loadImage(
        -        'invalid path',
        -        function(pImg) {
        -          reject('Entered success callback.');
        -        },
        -        resolve
        +    assert.isTrue(gifImage.gifProperties !== null);
        +    for (let prop in nyanCatGifProperties) {
        +      assert.deepEqual(
        +        gifImage.gifProperties[prop],
        +        nyanCatGifProperties[prop]
               );
        -    }).then(function(event) {
        -      assert.equal(event.type, 'error');
        -      assert.isTrue(p5._friendlyFileLoadError.called);
        -    });
        -  });
        -
        -  test('should construct gifProperties correctly after preload', function() {
        -    var mySketch = function(this_p5) {
        -      var gifImage;
        -      this_p5.preload = function() {
        -        suite('Test in preload() with success callback', function() {
        -          test('Load asynchronously and use success callback', function(done) {
        -            gifImage = this_p5.loadImage(imagePath, function() {
        -              assert.ok(gifImage);
        -              done();
        -            });
        -          });
        -        });
        -      };
        +    }
        +    assert.deepEqual(
        +      gifImage.gifProperties.numFrames,
        +      gifImage.gifProperties.frames.length
        +    );
        +    for (let i = 0; i < gifImage.gifProperties.numFrames; i++) {
        +      assert.isTrue(
        +        gifImage.gifProperties.frames[i].image instanceof ImageData
        +      );
        +      assert.isTrue(gifImage.gifProperties.frames[i].delay === 100);
        +    }
         
        -      this_p5.setup = function() {
        -        suite('setup() after preload() with success callback', function() {
        -          test('should be loaded if preload() finished', function(done) {
        -            assert.isTrue(gifImage instanceof p5.Image);
        -            assert.isTrue(gifImage.width > 0 && gifImage.height > 0);
        -            done();
        -          });
        -          test('gifProperties should be correct after preload', function done() {
        -            assert.isTrue(gifImage instanceof p5.Image);
        -            var nyanCatGifProperties = {
        -              displayIndex: 0,
        -              loopCount: 0,
        -              loopLimit: null,
        -              numFrames: 6,
        -              playing: true,
        -              timeDisplayed: 0
        -            };
        -            assert.isTrue(gifImage.gifProperties !== null);
        -            for (var prop in nyanCatGifProperties) {
        -              assert.deepEqual(
        -                gifImage.gifProperties[prop],
        -                nyanCatGifProperties[prop]
        -              );
        -            }
        -            assert.deepEqual(
        -              gifImage.gifProperties.numFrames,
        -              gifImage.gifProperties.frames.length
        -            );
        -            for (var i = 0; i < gifImage.gifProperties.numFrames; i++) {
        -              assert.isTrue(
        -                gifImage.gifProperties.frames[i].image instanceof ImageData
        -              );
        -              assert.isTrue(gifImage.gifProperties.frames[i].delay === 100);
        -            }
        -          });
        -          test('should be able to modify gifProperties state', function() {
        -            assert.isTrue(gifImage.gifProperties.timeDisplayed === 0);
        -            gifImage.pause();
        -            assert.isTrue(gifImage.gifProperties.playing === false);
        -            gifImage.play();
        -            assert.isTrue(gifImage.gifProperties.playing === true);
        -            gifImage.setFrame(2);
        -            assert.isTrue(gifImage.gifProperties.displayIndex === 2);
        -            gifImage.reset();
        -            assert.isTrue(gifImage.gifProperties.displayIndex === 0);
        -            assert.isTrue(gifImage.gifProperties.timeDisplayed === 0);
        -          });
        -        });
        -      };
        -    };
        -    new p5(mySketch, null, false);
        +    assert.equal(gifImage.gifProperties.timeDisplayed, 0);
        +    gifImage.pause();
        +    assert.isFalse(gifImage.gifProperties.playing);
        +    gifImage.play();
        +    assert.isTrue(gifImage.gifProperties.playing);
        +    gifImage.setFrame(2);
        +    assert.equal(gifImage.gifProperties.displayIndex, 2);
        +    gifImage.reset();
        +    assert.equal(gifImage.gifProperties.displayIndex, 0);
        +    assert.equal(gifImage.gifProperties.timeDisplayed, 0);
           });
         });
         
        -suite('displaying images', function() {
        +suite.todo('displaying images', function() {
           var myp5;
           var pImg;
           var imagePath = 'unit/assets/cat-with-hole.png';
           var chanNames = ['red', 'green', 'blue', 'alpha'];
         
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        myp5.pixelDensity(1);
        -        myp5.loadImage(
        -          imagePath,
        -          function(img) {
        -            pImg = img;
        -            myp5.resizeCanvas(pImg.width, pImg.height);
        -            done();
        -          },
        -          function() {
        -            throw new Error('Error loading image');
        -          }
        -        );
        -      };
        +  beforeAll(async function() {
        +    new Promise(resolve => {
        +      new p5(function(p) {
        +        p.setup = function() {
        +          myp5 = p;
        +          myp5.pixelDensity(1);
        +          myp5.loadImage(
        +            imagePath,
        +            function(img) {
        +              pImg = img;
        +              myp5.resizeCanvas(pImg.width, pImg.height);
        +              resolve();
        +            },
        +            function() {
        +              throw new Error('Error loading image');
        +            }
        +          );
        +        };
        +      });
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -519,56 +346,99 @@ suite('displaying images', function() {
         });
         
         suite('displaying images that use fit mode', function() {
        -  var myp5;
        +  var myp5, imageSpy;
         
        -  setup(function(done) {
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        +  beforeEach(() => {
        +    imageSpy = vi.spyOn(myp5._renderer, 'image');
        +  });
        +
        +  afterEach(() => {
        +    vi.restoreAllMocks();
        +  });
        +
           test('CONTAIN when source image is larger than destination', function() {
             let src = myp5.createImage(400, 1000);
        -    sinon.spy(myp5._renderer, 'image');
             myp5.image(src, 0, 0, 300, 400, 0, 0, 400, 1000, myp5.CONTAIN);
        -    assert(myp5._renderer.image.calledOnce);
        -    assert.equal(myp5._renderer.image.getCall(0).args[7], 400 / (1000 / 400)); //  dw
        -    assert.equal(myp5._renderer.image.getCall(0).args[8], 1000 / (1000 / 400)); // dh
        +    expect(imageSpy)
        +      .toHaveBeenCalledTimes(1)
        +      .toHaveBeenCalledWith(
        +        expect.anything(),
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number),
        +        400 / (1000 / 400),
        +        1000 / (1000 / 400)
        +      );
           });
         
           test('CONTAIN when source image is smaller than destination', function() {
             let src = myp5.createImage(40, 90);
        -    sinon.spy(myp5._renderer, 'image');
             myp5.image(src, 0, 0, 300, 500, 0, 0, 400, 1000, myp5.CONTAIN);
        -    assert(myp5._renderer.image.calledOnce);
        -    assert.equal(myp5._renderer.image.getCall(0).args[7], 40 / (90 / 500)); //  dw
        -    assert.equal(myp5._renderer.image.getCall(0).args[8], 90 / (90 / 500)); // dh
        +    expect(imageSpy)
        +      .toHaveBeenCalledTimes(1)
        +      .toHaveBeenCalledWith(
        +        expect.anything(),
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number),
        +        40 / (90 / 500),
        +        90 / (90 / 500)
        +      );
           });
         
           test('COVER when source image is larger than destination', function() {
             let src = myp5.createImage(400, 1000);
        -    sinon.spy(myp5._renderer, 'image');
             myp5.image(src, 0, 0, 300, 400, 0, 0, 400, 1000, myp5.COVER);
             const r = Math.max(300 / 400, 400 / 1000);
        -    assert(myp5._renderer.image.calledOnce);
        -    assert.equal(myp5._renderer.image.getCall(0).args[3], 300 / r); //  sw
        -    assert.equal(myp5._renderer.image.getCall(0).args[4], 400 / r); // sh
        +    expect(imageSpy)
        +      .toHaveBeenCalledTimes(1)
        +      .toHaveBeenCalledWith(
        +        expect.anything(),
        +        expect.any(Number),
        +        expect.any(Number),
        +        300 / r,
        +        400 / r,
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number)
        +      );
           });
         
           test('COVER when source image is smaller than destination', function() {
             let src = myp5.createImage(20, 100);
        -    sinon.spy(myp5._renderer, 'image');
             myp5.image(src, 0, 0, 300, 400, 0, 0, 20, 100, myp5.COVER);
             const r = Math.max(300 / 20, 400 / 100);
        -    assert(myp5._renderer.image.calledOnce);
        -    assert.equal(myp5._renderer.image.getCall(0).args[3], 300 / r); //  sw
        -    assert.equal(myp5._renderer.image.getCall(0).args[4], 400 / r); // sh
        +    expect(imageSpy)
        +      .toHaveBeenCalledTimes(1)
        +      .toHaveBeenCalledWith(
        +        expect.anything(),
        +        expect.any(Number),
        +        expect.any(Number),
        +        300 / r,
        +        400 / r,
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number),
        +        expect.any(Number)
        +      );
           });
         });
        diff --git a/test/unit/image/p5.Image.js b/test/unit/image/p5.Image.js
        index bba219a142..5195f2651d 100644
        --- a/test/unit/image/p5.Image.js
        +++ b/test/unit/image/p5.Image.js
        @@ -1,16 +1,17 @@
        +import p5 from '../../../src/app.js';
        +
         suite('p5.Image', function() {
           var myp5;
         
        -  setup(function(done) {
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -50,7 +51,7 @@ suite('p5.Image', function() {
             });
           });
         
        -  suite('p5.Image.prototype.mask', function() {
        +  suite.todo('p5.Image.prototype.mask', function() {
             for (const density of [1, 2]) {
               test(`it should mask the image at pixel density ${density}`, function() {
                 let img = myp5.createImage(10, 10);
        @@ -180,7 +181,7 @@ suite('p5.Image', function() {
             });
           });
         
        -  suite('p5.Graphics.get()', function() {
        +  suite.todo('p5.Graphics.get()', function() {
             for (const density of [1, 2]) {
               test(`width and height match at pixel density ${density}`, function() {
                 const g = myp5.createGraphics(10, 10);
        diff --git a/test/unit/image/pixels.js b/test/unit/image/pixels.js
        index 6afccee30e..e0b4958c71 100644
        --- a/test/unit/image/pixels.js
        +++ b/test/unit/image/pixels.js
        @@ -1,23 +1,24 @@
        -suite('pixels', function() {
        +import p5 from '../../../src/app.js';
        +
        +suite.todo('pixels', function() {
           var myp5;
         
        -  setup(function(done) {
        +  beforeAll(function() {
             new p5(function(p) {
               p.setup = function() {
                 myp5 = p;
        -        done();
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
           suite('p5.Image.get', function() {
             var img;
         
        -    setup(function() {
        +    beforeAll(function() {
               //create a 50 x 50 half red half green image
               img = myp5.createImage(50, 50);
               img.loadPixels();
        @@ -151,38 +152,6 @@ suite('pixels', function() {
                 }
               }
             });
        -
        -    test('wrong parameter at #8', function() {
        -      assert.validationError(function() {
        -        let img = myp5.createImage(50, 50);
        -        img.blend(0, 0, 10, 10, 10, 0, 10, 10, 'a');
        -      });
        -    });
        -
        -    test('no friendly-err-msg. missing param #0', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          let img = myp5.createImage(50, 50);
        -          img.blend(0, 0, 10, 10, 10, 0, 10, 10, myp5.OVERLAY);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -
        -    test('missing parameter at #3 ', function() {
        -      assert.throw(function() {
        -        let img = myp5.createImage(50, 50);
        -        img.blend(0, 0, 10, 10, 0, 10, 10, myp5.OVERLAY);
        -      });
        -    });
        -
        -    test('missing parameter at #8 ', function() {
        -      assert.throw(function() {
        -        let img = myp5.createImage(50, 50);
        -        img.blend(0, 0, 10, 10, 10, 0, 10, 10);
        -      });
        -    });
           });
         
           suite('p5.Image.copy', function() {
        @@ -212,23 +181,5 @@ suite('pixels', function() {
                 }
               }
             });
        -
        -    test('no friendly-err-msg. missing param #0', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          let img = myp5.createImage(50, 50);
        -          img.copy(0, 0, 10, 10, 10, 0, 10, 10);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -
        -    test('missing parameter at #3 ', function() {
        -      assert.throw(function() {
        -        let img = myp5.createImage(50, 50);
        -        img.copy(0, 0, 10, 10, 0, 10, 10);
        -      });
        -    });
           });
         });
        diff --git a/test/unit/io/files.js b/test/unit/io/files.js
        index 152d2d0dc3..bde79ddf91 100644
        --- a/test/unit/io/files.js
        +++ b/test/unit/io/files.js
        @@ -1,378 +1,167 @@
        -suite('Files', function() {
        -  var myp5;
        +import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
        +import files from '../../../src/io/files';
        +import { vi } from 'vitest';
        +import * as fileSaver from 'file-saver';
         
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        +vi.mock('file-saver');
        +
        +suite('Files', function() {
        +  beforeAll(async function() {
        +    files(mockP5, mockP5Prototype);
        +    await httpMock.start({ quiet: true });
           });
         
        -  teardown(function() {
        -    myp5.remove();
        +  afterEach(() => {
        +    vi.clearAllMocks();
           });
         
           // httpDo
           suite('httpDo()', function() {
        -    test('should be a function', function() {
        -      assert.ok(myp5.httpDo);
        -      assert.isFunction(myp5.httpDo);
        -    });
        -
        -    test('should work when provided with just a path', function() {
        -      return new Promise(function(resolve, reject) {
        -        myp5.httpDo('unit/assets/sentences.txt', resolve, reject);
        -      }).then(function(data) {
        -        assert.ok(data);
        -        assert.isString(data);
        -      });
        -    });
        -
        -    test('should accept method parameter', function() {
        -      return new Promise(function(resolve, reject) {
        -        myp5.httpDo('unit/assets/sentences.txt', 'GET', resolve, reject);
        -      }).then(function(data) {
        -        assert.ok(data);
        -        assert.isString(data);
        -      });
        -    });
        -
        -    test('should accept type parameter', function() {
        -      return new Promise(function(resolve, reject) {
        -        myp5.httpDo('unit/assets/array.json', 'text', resolve, reject);
        -      }).then(function(data) {
        -        assert.ok(data);
        -        assert.isString(data);
        -      });
        +    test('should work when provided with just a path', async function() {
        +      const data = await mockP5Prototype.httpDo('/test/unit/assets/sentences.txt');
        +      assert.ok(data);
        +      assert.isString(data);
             });
         
        -    test('should accept method and type parameter together', function() {
        -      return new Promise(function(resolve, reject) {
        -        myp5.httpDo('unit/assets/array.json', 'GET', 'text', resolve, reject);
        -      }).then(function(data) {
        -        assert.ok(data);
        -        assert.isString(data);
        -      });
        +    test('should accept method parameter', async function() {
        +      const data = await mockP5Prototype.httpDo('/test/unit/assets/sentences.txt', 'GET');
        +      assert.ok(data);
        +      assert.isString(data);
             });
         
        -    test('should pass error object to error callback function', function() {
        -      return new Promise(function(resolve, reject) {
        -        myp5.httpDo(
        -          'unit/assets/sen.txt',
        -          function(data) {
        -            reject('Incorrectly succeeded.');
        -          },
        -          resolve
        -        );
        -      }).then(function(err) {
        -        assert.isFalse(err.ok, 'err.ok is false');
        -        assert.equal(err.status, 404, 'Error status is 404');
        -      });
        +    test('should accept method and type parameter together', async function() {
        +      const data = await mockP5Prototype.httpDo('/test/unit/assets/sentences.txt', 'GET', 'text');
        +      assert.ok(data);
        +      assert.isString(data);
             });
         
        -    test('should return a promise', function() {
        -      var promise = myp5.httpDo('unit/assets/sentences.txt');
        -      assert.instanceOf(promise, Promise);
        -      return promise.then(function(data) {
        -        assert.ok(data);
        -        assert.isString(data);
        -      });
        -    });
        -
        -    test('should return a promise that rejects on error', function() {
        -      return new Promise(function(resolve, reject) {
        -        var promise = myp5.httpDo('404file');
        -        assert.instanceOf(promise, Promise);
        -        promise.then(function(data) {
        -          reject(new Error('promise resolved.'));
        -        });
        -        resolve(
        -          promise.catch(function(error) {
        -            assert.instanceOf(error, Error);
        -          })
        -        );
        -      });
        +    test('should handle promise error correctly', async function() {
        +      await expect(mockP5Prototype.httpDo('/test/unit/assets/sen.txt'))
        +        .rejects
        +        .toThrow('Not Found');
             });
           });
         
           // saveStrings()
           suite('p5.prototype.saveStrings', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.saveStrings);
        -      assert.typeOf(myp5.saveStrings, 'function');
        +      assert.ok(mockP5Prototype.saveStrings);
        +      assert.typeOf(mockP5Prototype.saveStrings, 'function');
             });
         
        -    test('no friendly-err-msg I', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          let strings = ['some', 'words'];
        -          myp5.saveStrings(strings, 'myfile');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg II', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          let strings = ['some', 'words'];
        -          myp5.saveStrings(strings, 'myfile', 'txt');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        +    test('should download a file with expected contents', async () => {
        +      const strings = ['some', 'words'];
        +      mockP5Prototype.saveStrings(strings, 'myfile');
         
        -    test('no friendly-err-msg III', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          let strings = ['some', 'words'];
        -          myp5.saveStrings(strings, 'myfile', 'txt', true);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        +      const saveData = new Blob([strings.join('\n')]);
        +      expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +      expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'myfile.txt');
             });
         
        -    testUnMinified('missing param #1', function() {
        -      assert.validationError(function() {
        -        let strings = ['some', 'words'];
        -        myp5.saveStrings(strings);
        -      });
        -    });
        +    test('should download a file with expected contents with CRLF', async () => {
        +      const strings = ['some', 'words'];
        +      mockP5Prototype.saveStrings(strings, 'myfile', 'txt', true);
         
        -    testUnMinified('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        let strings = 'some words';
        -        myp5.saveStrings(strings);
        -      });
        +      const saveData = new Blob([strings.join('\r\n')]);
        +      expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +      expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'myfile.txt');
             });
        -
        -    testWithDownload(
        -      'should download a file with expected contents',
        -      async function(blobContainer) {
        -        let strings = ['some', 'words'];
        -
        -        myp5.saveStrings(strings, 'myfile');
        -
        -        let myBlob = blobContainer.blob;
        -        let text = await myBlob.text();
        -        // Each element on a separate line with a trailing line-break
        -        assert.strictEqual(text, strings.join('\n') + '\n');
        -      },
        -      true
        -    );
        -
        -    testWithDownload(
        -      'should download a file with expected contents with CRLF',
        -      async function(blobContainer) {
        -        let strings = ['some', 'words'];
        -
        -        myp5.saveStrings(strings, 'myfile', 'txt', true);
        -        let myBlob = blobContainer.blob;
        -        let text = await myBlob.text();
        -        // Each element on a separate line with a trailing CRLF
        -        assert.strictEqual(text, strings.join('\r\n') + '\r\n');
        -      },
        -      true
        -    );
           });
         
           // saveJSON()
           suite('p5.prototype.saveJSON', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.saveJSON);
        -      assert.typeOf(myp5.saveJSON, 'function');
        -    });
        -
        -    test('no friendly-err-msg I', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          let myObj = { hi: 'hello' };
        -          myp5.saveJSON(myObj, 'myfile');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg II', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          let myObj = [{ hi: 'hello' }];
        -          myp5.saveJSON(myObj, 'myfile');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -
        -    test('no friendly-err-msg III', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          let myObj = { hi: 'hello' };
        -          myp5.saveJSON(myObj, 'myfile', true);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        +      assert.ok(mockP5Prototype.saveJSON);
        +      assert.typeOf(mockP5Prototype.saveJSON, 'function');
             });
         
        -    testUnMinified('missing param #1', function() {
        -      assert.validationError(function() {
        -        let myObj = { hi: 'hello' };
        -        myp5.saveJSON(myObj);
        -      });
        -    });
        +    test('should download a file with expected contents', async () => {
        +      const myObj = { hi: 'hello' };
        +      mockP5Prototype.saveJSON(myObj, 'myfile');
         
        -    testUnMinified('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        let myObj = 'some words';
        -        myp5.saveJSON(myObj);
        -      });
        +      const saveData = new Blob([JSON.stringify(myObj, null, 2)]);
        +      expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +      expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'myfile.json');
             });
        -
        -    testWithDownload(
        -      'should download a file with expected contents',
        -      async function(blobContainer) {
        -        let myObj = { hi: 'hello' };
        -
        -        myp5.saveJSON(myObj, 'myfile');
        -        let myBlob = blobContainer.blob;
        -        let text = await myBlob.text();
        -        let json = JSON.parse(text);
        -        // Each element on a separate line with a trailing line-break
        -        assert.deepEqual(myObj, json);
        -      },
        -      true // asyncFn = true
        -    );
           });
         
           // writeFile()
           suite('p5.prototype.writeFile', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.writeFile);
        -      assert.typeOf(myp5.writeFile, 'function');
        +      assert.ok(mockP5Prototype.writeFile);
        +      assert.typeOf(mockP5Prototype.writeFile, 'function');
             });
        -    testWithDownload(
        -      'should download a file with expected contents (text)',
        -      async function(blobContainer) {
        -        let myArray = ['hello', 'hi'];
         
        -        myp5.writeFile(myArray, 'myfile');
        -        let myBlob = blobContainer.blob;
        -        let text = await myBlob.text();
        -        assert.strictEqual(text, myArray.join(''));
        -      },
        -      true // asyncFn = true
        -    );
        +    test('should download a file with expected contents (text)', async () => {
        +      const myArray = ['hello', 'hi'];
        +      mockP5Prototype.writeFile(myArray, 'myfile');
        +
        +      const saveData = new Blob(myArray);
        +      expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +      expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'myfile');
        +    });
           });
         
           // downloadFile()
           suite('p5.prototype.downloadFile', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.writeFile);
        -      assert.typeOf(myp5.writeFile, 'function');
        +      assert.ok(mockP5Prototype.downloadFile);
        +      assert.typeOf(mockP5Prototype.downloadFile, 'function');
        +    });
        +
        +    test('should download a file with expected contents', async () => {
        +      const myArray = ['hello', 'hi'];
        +      const inBlob = new Blob(myArray);
        +      mockP5Prototype.downloadFile(inBlob, 'myfile');
        +
        +      const saveData = new Blob(myArray);
        +      expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +      expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'myfile');
             });
        -    testWithDownload(
        -      'should download a file with expected contents',
        -      async function(blobContainer) {
        -        let myArray = ['hello', 'hi'];
        -        let inBlob = new Blob(myArray);
        -        myp5.downloadFile(inBlob, 'myfile');
        -        let myBlob = blobContainer.blob;
        -        let text = await myBlob.text();
        -        assert.strictEqual(text, myArray.join(''));
        -      },
        -      true // asyncFn = true
        -    );
           });
         
           // save()
           suite('p5.prototype.save', function() {
             suite('saving images', function() {
        -      let waitForBlob = async function(blc) {
        -        let sleep = function(ms) {
        -          return new Promise(r => setTimeout(r, ms));
        -        };
        -        while (!blc.blob) {
        -          await sleep(5);
        -        }
        -      };
        -      setup(function(done) {
        -        myp5.createCanvas(20, 20);
        -        myp5.background(255, 0, 0);
        -        done();
        -      });
        -
               test('should be a function', function() {
        -        assert.ok(myp5.save);
        -        assert.typeOf(myp5.save, 'function');
        +        assert.ok(mockP5Prototype.save);
        +        assert.typeOf(mockP5Prototype.save, 'function');
               });
         
        -      testWithDownload(
        -        'should download a png file',
        -        async function(blobContainer) {
        -          myp5.save();
        -          await waitForBlob(blobContainer);
        -          let myBlob = blobContainer.blob;
        -          assert.strictEqual(myBlob.type, 'image/png');
        -
        -          blobContainer.blob = null;
        -          let gb = myp5.createGraphics(100, 100);
        -          myp5.save(gb);
        -          await waitForBlob(blobContainer);
        -          myBlob = blobContainer.blob;
        -          assert.strictEqual(myBlob.type, 'image/png');
        -        },
        -        true
        -      );
        -
        -      testWithDownload(
        -        'should download a jpg file',
        -        async function(blobContainer) {
        -          myp5.save('filename.jpg');
        -          await waitForBlob(blobContainer);
        -          let myBlob = blobContainer.blob;
        -          assert.strictEqual(myBlob.type, 'image/jpeg');
        +      // Test the call to `saveCanvas`
        +      // `saveCanvas` is responsible for testing download is correct
        +      test('should call saveCanvas', async () => {
        +        mockP5Prototype.save();
        +        expect(mockP5Prototype.saveCanvas).toHaveBeenCalledTimes(1);
        +        expect(mockP5Prototype.saveCanvas).toHaveBeenCalledWith(mockP5Prototype.elt);
        +      });
         
        -          blobContainer.blob = null;
        -          let gb = myp5.createGraphics(100, 100);
        -          myp5.save(gb, 'filename.jpg');
        -          await waitForBlob(blobContainer);
        -          myBlob = blobContainer.blob;
        -          assert.strictEqual(myBlob.type, 'image/jpeg');
        -        },
        -        true
        -      );
        +      test('should call saveCanvas with filename', async () => {
        +        mockP5Prototype.save('filename.jpg');
        +        expect(mockP5Prototype.saveCanvas).toHaveBeenCalledTimes(1);
        +        expect(mockP5Prototype.saveCanvas)
        +          .toHaveBeenCalledWith(mockP5Prototype.elt, 'filename.jpg');
        +      });
             });
         
             suite('saving strings and json', function() {
        -      testWithDownload(
        -        'should download a text file',
        -        async function(blobContainer) {
        -          let myStrings = ['aaa', 'bbb'];
        -          myp5.save(myStrings, 'filename');
        -          let myBlob = blobContainer.blob;
        -          let text = await myBlob.text();
        -          assert.strictEqual(text, myStrings.join('\n') + '\n');
        -        },
        -        true
        -      );
        +      test('should download a text file', async () => {
        +        const myStrings = ['aaa', 'bbb'];
        +        mockP5Prototype.save(myStrings, 'filename');
        +
        +        const saveData = new Blob([myStrings.join('\n')]);
        +        expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +        expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'filename.txt');
        +      });
        +
        +      test('should download a json file', async () => {
        +        const myObj = { hi: 'hello' };
        +        mockP5Prototype.save(myObj, 'filename.json');
         
        -      testWithDownload(
        -        'should download a json file',
        -        async function(blobContainer) {
        -          let myObj = { hi: 'hello' };
        -          myp5.save(myObj, 'filename.json');
        -          let myBlob = blobContainer.blob;
        -          let text = await myBlob.text();
        -          let outObj = JSON.parse(text);
        -          assert.deepEqual(outObj, myObj);
        -        },
        -        true
        -      );
        +        const saveData = new Blob([JSON.stringify(myObj, null, 2)]);
        +        expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +        expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'filename.json');
        +      });
             });
           });
         });
        diff --git a/test/unit/io/loadBytes.js b/test/unit/io/loadBytes.js
        index 0b57f297f1..34efe595a2 100644
        --- a/test/unit/io/loadBytes.js
        +++ b/test/unit/io/loadBytes.js
        @@ -1,149 +1,71 @@
        -suite('loadBytes', function() {
        -  var invalidFile = '404file';
        -  var validFile = 'unit/assets/nyan_cat.gif';
        -
        -  test('_friendlyFileLoadError is called', async function() {
        -    const _friendlyFileLoadErrorStub = sinon.stub(p5, '_friendlyFileLoadError');
        -    try {
        -      await promisedSketch(function(sketch, resolve, reject) {
        -        sketch.preload = function() {
        -          sketch.loadBytes(invalidFile, reject, resolve);
        -        };
        -      });
        -      expect(
        -        _friendlyFileLoadErrorStub.calledOnce,
        -        'p5._friendlyFileLoadError was not called'
        -      ).to.be.true;
        -    } finally {
        -      _friendlyFileLoadErrorStub.restore();
        -    }
        -  });
        -
        -  testSketchWithPromise('error prevents sketch continuing', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadBytes(invalidFile);
        -      setTimeout(resolve, 50);
        -    };
        +import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
        +import files from '../../../src/io/files';
         
        -    sketch.setup = function() {
        -      reject(new Error('Setup called'));
        -    };
        +suite('loadBytes', function() {
        +  const invalidFile = '404file';
        +  const validFile = '/test/unit/assets/nyan_cat.gif';
         
        -    sketch.draw = function() {
        -      reject(new Error('Draw called'));
        -    };
        +  beforeAll(async () => {
        +    files(mockP5, mockP5Prototype);
        +    await httpMock.start({ quiet: true });
           });
         
        -  testSketchWithPromise('error callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadBytes(
        -        invalidFile,
        -        function() {
        -          reject(new Error('Success callback executed.'));
        -        },
        -        function() {
        -          // Wait a bit so that if both callbacks are executed we will get an error.
        -          setTimeout(resolve, 50);
        -        }
        -      );
        -    };
        +  test('throws error when encountering HTTP errors', async () => {
        +    await expect(mockP5Prototype.loadBytes(invalidFile))
        +      .rejects
        +      .toThrow('Not Found');
           });
         
        -  testSketchWithPromise('loading correctly triggers setup', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadBytes(validFile);
        -    };
        -
        -    sketch.setup = function() {
        -      resolve();
        -    };
        +  test('error callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadBytes(invalidFile, () => {
        +        reject("Success callback executed");
        +      }, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
        +      });
        +    });
           });
         
        -  testSketchWithPromise('success callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    var hasBeenCalled = false;
        -    sketch.preload = function() {
        -      sketch.loadBytes(
        -        validFile,
        -        function() {
        -          hasBeenCalled = true;
        -        },
        -        function(err) {
        -          reject(new Error('Error callback was entered: ' + err));
        -        }
        -      );
        -    };
        -
        -    sketch.setup = function() {
        -      if (!hasBeenCalled) {
        -        reject(new Error('Setup called prior to success callback'));
        -      } else {
        +  test('success callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadBytes(validFile, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
                 setTimeout(resolve, 50);
        -      }
        -    };
        +      }, (err) => {
        +        reject(`Error callback called: ${err.toString()}`);
        +      });
        +    });
           });
         
        -  test('returns the correct object', async function() {
        -    const object = await promisedSketch(function(sketch, resolve, reject) {
        -      var _object;
        -      sketch.preload = function() {
        -        _object = sketch.loadBytes(validFile, function() {}, reject);
        -      };
        +  test('returns the correct object',  async () => {
        +    const data = await mockP5Prototype.loadBytes(validFile);
        +    assert.instanceOf(data, Uint8Array);
         
        -      sketch.setup = function() {
        -        resolve(_object);
        -      };
        -    });
        -    assert.isObject(object);
        -    // Check data format
        -    expect(object.bytes).to.satisfy(function(v) {
        -      return Array.isArray(v) || v instanceof Uint8Array;
        -    });
             // Validate data
        -    var str = 'GIF89a';
        +    const str = 'GIF89a';
             // convert the string to a byte array
        -    var rgb = str.split('').map(function(e) {
        +    const rgb = str.split('').map(function(e) {
               return e.charCodeAt(0);
             });
             // this will convert a Uint8Aray to [], if necessary:
        -    var loaded = Array.prototype.slice.call(object.bytes, 0, str.length);
        +    const loaded = Array.prototype.slice.call(data, 0, str.length);
             assert.deepEqual(loaded, rgb);
           });
         
        -  test('passes an object to success callback for object JSON', async function() {
        -    const object = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadBytes(validFile, resolve, reject);
        -      };
        -    });
        -    assert.isObject(object);
        -    // Check data format
        -    expect(object.bytes).to.satisfy(function(v) {
        -      return Array.isArray(v) || v instanceof Uint8Array;
        -    });
        -    // Validate data
        -    var str = 'GIF89a';
        -    // convert the string to a byte array
        -    var rgb = str.split('').map(function(e) {
        -      return e.charCodeAt(0);
        +  test('passes athe correct object to success callback',  async () => {
        +    await mockP5Prototype.loadBytes(validFile, (data) => {
        +      assert.instanceOf(data, Uint8Array);
        +
        +      // Validate data
        +      const str = 'GIF89a';
        +      // convert the string to a byte array
        +      const rgb = str.split('').map(function(e) {
        +        return e.charCodeAt(0);
        +      });
        +      // this will convert a Uint8Aray to [], if necessary:
        +      const loaded = Array.prototype.slice.call(data, 0, str.length);
        +      assert.deepEqual(loaded, rgb);
             });
        -    // this will convert a Uint8Aray to [], if necessary:
        -    var loaded = Array.prototype.slice.call(object.bytes, 0, str.length);
        -    assert.deepEqual(loaded, rgb);
           });
         });
        diff --git a/test/unit/io/loadImage.js b/test/unit/io/loadImage.js
        deleted file mode 100644
        index d3edad6ebe..0000000000
        --- a/test/unit/io/loadImage.js
        +++ /dev/null
        @@ -1,123 +0,0 @@
        -suite('loadImage', function() {
        -  var invalidFile = '404file';
        -  var validFile = 'unit/assets/nyan_cat.gif';
        -
        -  test('_friendlyFileLoadError is called', async function() {
        -    const _friendlyFileLoadErrorStub = sinon.stub(p5, '_friendlyFileLoadError');
        -    try {
        -      await promisedSketch(function(sketch, resolve, reject) {
        -        sketch.preload = function() {
        -          sketch.loadImage(invalidFile, reject, resolve);
        -        };
        -      });
        -      expect(
        -        _friendlyFileLoadErrorStub.calledOnce,
        -        'p5._friendlyFileLoadError was not called'
        -      ).to.be.true;
        -    } finally {
        -      _friendlyFileLoadErrorStub.restore();
        -    }
        -  });
        -
        -  testSketchWithPromise('error prevents sketch continuing', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadImage(invalidFile);
        -      setTimeout(resolve(), 50);
        -    };
        -
        -    sketch.setup = function() {
        -      reject(new Error('Setup called'));
        -    };
        -
        -    sketch.draw = function() {
        -      reject(new Error('Draw called'));
        -    };
        -  });
        -
        -  testSketchWithPromise('error callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadImage(
        -        invalidFile,
        -        function() {
        -          reject(new Error('Success callback executed.'));
        -        },
        -        function() {
        -          // Wait a bit so that if both callbacks are executed we will get an error.
        -          setTimeout(resolve, 50);
        -        }
        -      );
        -    };
        -  });
        -
        -  testSketchWithPromise('loading correctly triggers setup', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadImage(validFile);
        -    };
        -
        -    sketch.setup = function() {
        -      resolve();
        -    };
        -  });
        -
        -  testSketchWithPromise('success callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    var hasBeenCalled = false;
        -    sketch.preload = function() {
        -      sketch.loadImage(
        -        validFile,
        -        function() {
        -          hasBeenCalled = true;
        -        },
        -        function(err) {
        -          reject(new Error('Error callback was entered: ' + err));
        -        }
        -      );
        -    };
        -
        -    sketch.setup = function() {
        -      if (!hasBeenCalled) {
        -        reject(new Error('Setup called prior to success callback'));
        -      } else {
        -        setTimeout(resolve, 50);
        -      }
        -    };
        -  });
        -
        -  test('returns an object with correct data', async function() {
        -    const image = await promisedSketch(function(sketch, resolve, reject) {
        -      var _image;
        -      sketch.preload = function() {
        -        _image = sketch.loadImage(validFile, function() {}, reject);
        -      };
        -
        -      sketch.setup = function() {
        -        resolve(_image);
        -      };
        -    });
        -    assert.instanceOf(image, p5.Image);
        -  });
        -
        -  test('passes an object with correct data to callback', async function() {
        -    const image = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadImage(validFile, resolve, reject);
        -      };
        -    });
        -    assert.instanceOf(image, p5.Image);
        -  });
        -});
        diff --git a/test/unit/io/loadJSON.js b/test/unit/io/loadJSON.js
        index 3f1d016cd2..333695b042 100644
        --- a/test/unit/io/loadJSON.js
        +++ b/test/unit/io/loadJSON.js
        @@ -1,185 +1,66 @@
        -suite('loadJSON', function() {
        -  var invalidFile = '404file';
        -  var jsonArrayFile = 'unit/assets/array.json';
        -  var jsonObjectFile = 'unit/assets/object.json';
        -  var jsonpObjectFile = 'unit/assets/object.js';
        -  var jsonpArrayFile = 'unit/assets/array.js';
        -
        -  test('_friendlyFileLoadError is called', async function() {
        -    const _friendlyFileLoadErrorStub = sinon.stub(p5, '_friendlyFileLoadError');
        -    try {
        -      await promisedSketch(function(sketch, resolve, reject) {
        -        sketch.preload = function() {
        -          sketch.loadJSON(invalidFile, reject, resolve);
        -        };
        -      });
        -      expect(
        -        _friendlyFileLoadErrorStub.calledOnce,
        -        'p5._friendlyFileLoadError was not called'
        -      ).to.be.true;
        -    } finally {
        -      _friendlyFileLoadErrorStub.restore();
        -    }
        -  });
        +import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
        +import files from '../../../src/io/files';
         
        -  testSketchWithPromise('error prevents sketch continuing', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadJSON(invalidFile, reject, function() {
        -        setTimeout(resolve, 50);
        -      });
        -    };
        -
        -    sketch.setup = function() {
        -      reject(new Error('Entered setup'));
        -    };
        -
        -    sketch.draw = function() {
        -      reject(new Error('Entered draw'));
        -    };
        -  });
        +suite('loadJSON', function() {
        +  const invalidFile = '404file';
        +  const jsonArrayFile = '/test/unit/assets/array.json';
        +  const jsonObjectFile = '/test/unit/assets/object.json';
         
        -  testSketchWithPromise('error callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadJSON(
        -        invalidFile,
        -        function() {
        -          reject(new Error('Success callback executed.'));
        -        },
        -        function() {
        -          // Wait a bit so that if both callbacks are executed we will get an error.
        -          setTimeout(resolve, 50);
        -        }
        -      );
        -    };
        +  beforeAll(async () => {
        +    files(mockP5, mockP5Prototype);
        +    await httpMock.start({ quiet: true });
           });
         
        -  testSketchWithPromise('loading correctly triggers setup', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadJSON(jsonObjectFile);
        -    };
        -
        -    sketch.setup = function() {
        -      resolve();
        -    };
        +  test('throws error when encountering HTTP errors', async () => {
        +    await expect(mockP5Prototype.loadJSON(invalidFile))
        +      .rejects
        +      .toThrow('Not Found');
           });
         
        -  testSketchWithPromise('success callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    var hasBeenCalled = false;
        -    sketch.preload = function() {
        -      sketch.loadJSON(
        -        jsonObjectFile,
        -        function() {
        -          hasBeenCalled = true;
        -        },
        -        function(err) {
        -          reject(new Error('Error callback was entered: ' + err));
        -        }
        -      );
        -    };
        -
        -    sketch.setup = function() {
        -      if (!hasBeenCalled) {
        -        reject(new Error('Setup called prior to success callback'));
        -      } else {
        +  test('error callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadJSON(invalidFile, () => {
        +        reject("Success callback executed");
        +      }, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
                 setTimeout(resolve, 50);
        -      }
        -    };
        -  });
        -
        -  test('returns an object for object JSON.', async function() {
        -    const json = await promisedSketch(function(sketch, resolve, reject) {
        -      var json;
        -      sketch.preload = function() {
        -        json = sketch.loadJSON(jsonObjectFile, function() {}, reject);
        -      };
        -
        -      sketch.setup = function() {
        -        resolve(json);
        -      };
        +      });
             });
        -    assert.isObject(json);
           });
         
        -  test('passes an object to success callback for object JSON.', async function() {
        -    const json = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadJSON(jsonObjectFile, resolve, reject);
        -      };
        +  test('success callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadJSON(jsonObjectFile, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
        +      }, (err) => {
        +        reject(`Error callback called: ${err.toString()}`);
        +      });
             });
        -    assert.isObject(json);
           });
         
        -  // Does not work with the current loadJSON.
        -  test('returns an array for array JSON.');
        -  // test('returns an array for array JSON.', async function() {
        -  //   const json = await promisedSketch(function(sketch, resolve, reject) {
        -  //     var json;
        -  //     sketch.preload = function() {
        -  //       json = sketch.loadJSON(jsonArrayFile, function() {}, reject);
        -  //     };
        -  //
        -  //     sketch.setup = function() {
        -  //       resolve(json);
        -  //     };
        -  //   });
        -  //   assert.isArray(json);
        -  //   assert.lengthOf(json, 3);
        -  // });
        +  test('returns an object for object JSON.', async () => {
        +    const data = await mockP5Prototype.loadJSON(jsonObjectFile);
        +    assert.isObject(data);
        +    assert.isNotArray(data);
        +  });
         
        -  test('passes an array to success callback for array JSON.', async function() {
        -    const json = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadJSON(jsonArrayFile, resolve, reject);
        -      };
        +  test('passes an object to success callback for object JSON.', async () => {
        +    await mockP5Prototype.loadJSON(jsonObjectFile, (data) => {
        +      assert.isObject(data);
             });
        -    assert.isArray(json);
        -    assert.lengthOf(json, 3);
           });
         
        -  test('passes an object to success callback for object JSONP.', async function() {
        -    const json = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadJSON(
        -          jsonpObjectFile,
        -          { jsonpCallbackFunction: 'jsonpCallbackFunction' },
        -          'jsonp',
        -          resolve,
        -          reject
        -        );
        -      };
        -    });
        -    assert.isObject(json);
        +  test('returns an array for array JSON.', async () => {
        +    const data = await mockP5Prototype.loadJSON(jsonArrayFile);
        +    assert.isArray(data);
        +    assert.lengthOf(data, 3);
           });
         
        -  test('passes an array to success callback for array JSONP.', async function() {
        -    const json = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadJSON(
        -          jsonpArrayFile,
        -          { jsonpCallbackFunction: 'jsonpCallbackFunction' },
        -          'jsonp',
        -          resolve,
        -          reject
        -        );
        -      };
        +  test('passes an array to success callback for array JSON.', async function() {
        +    await mockP5Prototype.loadJSON(jsonArrayFile, (data) => {
        +      assert.isArray(data);
        +      assert.lengthOf(data, 3);
             });
        -    assert.isArray(json);
        -    assert.lengthOf(json, 3);
           });
         });
        diff --git a/test/unit/io/loadModel.js b/test/unit/io/loadModel.js
        index 3952ec5450..694f87e37f 100644
        --- a/test/unit/io/loadModel.js
        +++ b/test/unit/io/loadModel.js
        @@ -1,215 +1,118 @@
        -suite('loadModel', function() {
        -  var invalidFile = '404file';
        -  var validFile = 'unit/assets/teapot.obj';
        -  var validObjFileforMtl='unit/assets/octa-color.obj';
        -  var validSTLfile = 'unit/assets/ascii.stl';
        -  var inconsistentColorObjFile = 'unit/assets/eg1.obj';
        -  var objMtlMissing = 'unit/assets/objMtlMissing.obj';
        -  var validSTLfileWithoutExtension = 'unit/assets/ascii';
        -
        -  test('_friendlyFileLoadError is called', async function() {
        -    const _friendlyFileLoadErrorStub = sinon.stub(p5, '_friendlyFileLoadError');
        -    try {
        -      await promisedSketch(function(sketch, resolve, reject) {
        -        sketch.preload = function() {
        -          sketch.loadModel(invalidFile, reject, resolve);
        -        };
        -      });
        -      expect(
        -        _friendlyFileLoadErrorStub.calledOnce,
        -        'p5._friendlyFileLoadError was not called'
        -      ).to.be.true;
        -    } finally {
        -      _friendlyFileLoadErrorStub.restore();
        -    }
        -  });
        -
        -  testSketchWithPromise('error prevents sketch continuing', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadModel(invalidFile);
        -      setTimeout(resolve, 50);
        -    };
        -
        -    sketch.setup = function() {
        -      reject(new Error('Setup called'));
        -    };
        +import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
        +import loading from '../../../src/webgl/loading';
        +import { Geometry } from '../../../src/webgl/p5.Geometry';
         
        -    sketch.draw = function() {
        -      reject(new Error('Draw called'));
        -    };
        +suite('loadModel', function() {
        +  const invalidFile = '404file';
        +  const validFile = '/test/unit/assets/teapot.obj';
        +  const validObjFileforMtl = '/test/unit/assets/octa-color.obj';
        +  const validSTLfile = '/test/unit/assets/ascii.stl';
        +  const inconsistentColorObjFile = '/test/unit/assets/eg1.obj';
        +  const objMtlMissing = '/test/unit/assets/objMtlMissing.obj';
        +  const validSTLfileWithoutExtension = '/test/unit/assets/ascii';
        +
        +  beforeAll(async () => {
        +    loading(mockP5, mockP5Prototype);
        +    await httpMock.start({ quiet: true });
           });
         
        -  testSketchWithPromise('error callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadModel(
        -        invalidFile,
        -        function() {
        -          reject(new Error('Success callback executed.'));
        -        },
        -        function() {
        -          // Wait a bit so that if both callbacks are executed we will get an error.
        -          setTimeout(resolve, 50);
        -        }
        -      );
        -    };
        +  test('throws error when encountering HTTP errors', async () => {
        +    await expect(mockP5Prototype.loadModel(invalidFile))
        +      .rejects
        +      .toThrow('Not Found');
           });
         
        -  testSketchWithPromise('loading correctly triggers setup', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadModel(validFile);
        -    };
        -
        -    sketch.setup = function() {
        -      resolve();
        -    };
        +  test('error callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadModel(invalidFile, () => {
        +        reject("Success callback executed");
        +      }, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
        +      });
        +    });
           });
         
        -  testSketchWithPromise('success callback is called', function(
        -    sketch,
        -    done
        -  ) {
        -    var hasBeenCalled = false;
        -    sketch.preload = function() {
        -      sketch.loadModel(
        -        validFile,
        -        function() {
        -          hasBeenCalled = true;
        -          done();
        -        },
        -        function(err) {
        -          done(new Error('Error callback was entered: ' + err));
        -        }
        -      );
        -    };
        -
        -    sketch.setup = function() {
        -      if (!hasBeenCalled) {
        -        done(new Error('Setup called prior to success callback'));
        -      }
        -    };
        +  test('success callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadModel(validFile, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
        +      }, (err) => {
        +        reject(`Error callback called: ${err.toString()}`);
        +      });
        +    });
           });
         
           test('loads OBJ file with associated MTL file correctly', async function(){
        -    const model = await promisedSketch(function (sketch,resolve,reject){
        -      sketch.preload=function(){
        -        sketch.loadModel(validObjFileforMtl,resolve,reject);
        -      };
        -    });
        -    const expectedColors=[
        -      0, 0, 0.5,
        -      0, 0, 0.5,
        -      0, 0, 0.5,
        -      0, 0, 0.942654,
        -      0, 0, 0.942654,
        -      0, 0, 0.942654,
        -      0, 0.815632, 1,
        -      0, 0.815632, 1,
        -      0, 0.815632, 1,
        -      0, 0.965177, 1,
        -      0, 0.965177, 1,
        -      0, 0.965177, 1,
        -      0.848654, 1, 0.151346,
        -      0.848654, 1, 0.151346,
        -      0.848654, 1, 0.151346,
        -      1, 0.888635, 0,
        -      1, 0.888635, 0,
        -      1, 0.888635, 0,
        -      1, 0.77791, 0,
        -      1, 0.77791, 0,
        -      1, 0.77791, 0,
        -      0.5, 0, 0,
        -      0.5, 0, 0,
        -      0.5, 0, 0
        +    const model = await mockP5Prototype.loadModel(validObjFileforMtl);
        +
        +    const expectedColors = [
        +      0, 0, 0.5, 1,
        +      0, 0, 0.5, 1,
        +      0, 0, 0.5, 1,
        +      0, 0, 0.942654, 1,
        +      0, 0, 0.942654, 1,
        +      0, 0, 0.942654, 1,
        +      0, 0.815632, 1, 1,
        +      0, 0.815632, 1, 1,
        +      0, 0.815632, 1, 1,
        +      0, 0.965177, 1, 1,
        +      0, 0.965177, 1, 1,
        +      0, 0.965177, 1, 1,
        +      0.848654, 1, 0.151346, 1,
        +      0.848654, 1, 0.151346, 1,
        +      0.848654, 1, 0.151346, 1,
        +      1, 0.888635, 0, 1,
        +      1, 0.888635, 0, 1,
        +      1, 0.888635, 0, 1,
        +      1, 0.77791, 0, 1,
        +      1, 0.77791, 0, 1,
        +      1, 0.77791, 0, 1,
        +      0.5, 0, 0, 1,
        +      0.5, 0, 0, 1,
        +      0.5, 0, 0, 1
             ];
        -    assert.deepEqual(model.vertexColors,expectedColors);
        +
        +    assert.deepEqual(model.vertexColors, expectedColors);
           });
        +
           test('inconsistent vertex coloring throws error', async function() {
             // Attempt to load the model and catch the error
        -    let errorCaught = null;
        -    try {
        -      await promisedSketch(function(sketch, resolve, reject) {
        -        sketch.preload = function() {
        -          sketch.loadModel(inconsistentColorObjFile, resolve, reject);
        -        };
        -      });
        -    } catch (error) {
        -      errorCaught = error;
        -    }
        -
        -    // Assert that an error was caught and that it has the expected message
        -    assert.instanceOf(errorCaught, Error, 'No error thrown for inconsistent vertex coloring');
        -    assert.equal(errorCaught.message, 'Model coloring is inconsistent. Either all vertices should have colors or none should.', 'Unexpected error message for inconsistent vertex coloring');
        +    await expect(mockP5Prototype.loadModel(inconsistentColorObjFile))
        +      .rejects
        +      .toThrow('Model coloring is inconsistent. Either all vertices should have colors or none should.');
           });
         
           test('missing MTL file shows OBJ model without vertexColors', async function() {
        -    const model = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadModel(objMtlMissing, resolve, reject);
        -      };
        -    });
        -    assert.instanceOf(model, p5.Geometry);
        +    const model = await mockP5Prototype.loadModel(objMtlMissing);
        +    assert.instanceOf(model, Geometry);
             assert.equal(model.vertexColors.length, 0, 'Model should not have vertex colors');
           });
         
           test('returns an object with correct data', async function() {
        -    const model = await promisedSketch(function(sketch, resolve, reject) {
        -      var _model;
        -      sketch.preload = function() {
        -        _model = sketch.loadModel(validFile, function() {}, reject);
        -      };
        -
        -      sketch.setup = function() {
        -        resolve(_model);
        -      };
        -    });
        -    assert.instanceOf(model, p5.Geometry);
        +    const model = await mockP5Prototype.loadModel(validFile);
        +    assert.instanceOf(model, Geometry);
           });
         
           test('passes an object with correct data to callback', async function() {
        -    const model = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadModel(validFile, resolve, reject);
        -      };
        +    await mockP5Prototype.loadModel(validFile, (model) => {
        +      assert.instanceOf(model, Geometry);
             });
        -    assert.instanceOf(model, p5.Geometry);
           });
         
           test('resolves STL file correctly', async function() {
        -    const model = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadModel(validSTLfile, resolve, reject);
        -      };
        -    });
        -    assert.instanceOf(model, p5.Geometry);
        +    const model = await mockP5Prototype.loadModel(validSTLfile);
        +    assert.instanceOf(model, Geometry);
           });
         
           test('resolves STL file correctly with explicit extension', async function() {
        -    const model = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadModel(validSTLfileWithoutExtension, resolve, reject, '.stl');
        -      };
        -    });
        -    assert.instanceOf(model, p5.Geometry);
        +    const model = await mockP5Prototype.loadModel(validSTLfileWithoutExtension, '.stl');
        +    assert.instanceOf(model, Geometry);
           });
         
           test('resolves STL file correctly with case insensitive extension', async function() {
        -    const model = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadModel(validSTLfileWithoutExtension, resolve, reject, '.STL');
        -      };
        -    });
        -    assert.instanceOf(model, p5.Geometry);
        +    const model = await mockP5Prototype.loadModel(validSTLfileWithoutExtension, '.STL');
        +    assert.instanceOf(model, Geometry);
           });
         });
        diff --git a/test/unit/io/loadShader.js b/test/unit/io/loadShader.js
        index 4171555c0b..51aef19716 100644
        --- a/test/unit/io/loadShader.js
        +++ b/test/unit/io/loadShader.js
        @@ -1,176 +1,70 @@
        -suite('loadShader', function() {
        -  var invalidFile = '404file';
        -  var vertFile = 'unit/assets/vert.glsl';
        -  var fragFile = 'unit/assets/frag.glsl';
        -
        -  test('_friendlyFileLoadError is called', async function() {
        -    const _friendlyFileLoadErrorStub = sinon.stub(p5, '_friendlyFileLoadError');
        -    try {
        -      await promisedSketch(function(sketch, resolve, reject) {
        -        sketch.preload = function() {
        -          sketch.loadShader(invalidFile, invalidFile, reject, resolve);
        -        };
        -      });
        -      expect(
        -        _friendlyFileLoadErrorStub.calledOnce,
        -        'p5._friendlyFileLoadError was not called'
        -      ).to.be.true;
        -    } finally {
        -      _friendlyFileLoadErrorStub.restore();
        -    }
        -  });
        -
        -  testSketchWithPromise('error with vert prevents sketch continuing', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadShader(invalidFile, fragFile);
        -      setTimeout(resolve, 50);
        -    };
        +import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
        +import material from '../../../src/webgl/material';
        +import { Shader } from '../../../src/webgl/p5.Shader';
         
        -    sketch.setup = function() {
        -      reject(new Error('Setup called'));
        -    };
        +suite('loadShader', function() {
        +  const invalidFile = '404file';
        +  const vertFile = '/test/unit/assets/vert.glsl';
        +  const fragFile = '/test/unit/assets/frag.glsl';
         
        -    sketch.draw = function() {
        -      reject(new Error('Draw called'));
        -    };
        +  beforeAll(async () => {
        +    material(mockP5, mockP5Prototype);
        +    await httpMock.start({ quiet: true });
           });
         
        -  testSketchWithPromise('error with frag prevents sketch continuing', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadShader(vertFile, invalidFile);
        -      setTimeout(resolve, 50);
        -    };
        -
        -    sketch.setup = function() {
        -      reject(new Error('Setup called'));
        -    };
        -
        -    sketch.draw = function() {
        -      reject(new Error('Draw called'));
        -    };
        +  test('throws error when encountering HTTP errors in vert shader', async () => {
        +    await expect(mockP5Prototype.loadShader(invalidFile, fragFile))
        +      .rejects
        +      .toThrow('Not Found');
           });
         
        -  testSketchWithPromise('error callback is called for vert', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadShader(
        -        invalidFile,
        -        fragFile,
        -        function() {
        -          reject(new Error('Success callback executed.'));
        -        },
        -        function() {
        -          // Wait a bit so that if both callbacks are executed we will get an error.
        -          setTimeout(resolve, 50);
        -        }
        -      );
        -    };
        +  test('throws error when encountering HTTP errors in frag shader', async () => {
        +    await expect(mockP5Prototype.loadShader(vertFile, invalidFile))
        +      .rejects
        +      .toThrow('Not Found');
           });
         
        -  testSketchWithPromise('error callback is called for frag', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadShader(
        -        vertFile,
        -        invalidFile,
        -        function() {
        -          reject(new Error('Success callback executed.'));
        -        },
        -        function() {
        -          // Wait a bit so that if both callbacks are executed we will get an error.
        -          setTimeout(resolve, 50);
        -        }
        -      );
        -    };
        +  test('error callback is called for vert shader', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadShader(invalidFile, fragFile, () => {
        +        reject("Success callback executed");
        +      }, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
        +      });
        +    });
           });
         
        -  testSketchWithPromise('loading correctly triggers setup', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadShader(vertFile, fragFile);
        -    };
        -
        -    sketch.setup = function() {
        -      resolve();
        -    };
        +  test('error callback is called for frag shader', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadShader(vertFile, invalidFile, () => {
        +        reject("Success callback executed");
        +      }, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
        +      });
        +    });
           });
         
        -  testSketchWithPromise('success callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    var hasBeenCalled = false;
        -    sketch.preload = function() {
        -      sketch.loadShader(
        -        vertFile,
        -        fragFile,
        -        function() {
        -          hasBeenCalled = true;
        -        },
        -        function(err) {
        -          reject(new Error('Error callback was entered: ' + err));
        -        }
        -      );
        -    };
        -
        -    sketch.setup = function() {
        -      if (!hasBeenCalled) {
        -        reject(new Error('Setup called prior to success callback'));
        -      } else {
        +  test('success callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadShader(vertFile, fragFile, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
                 setTimeout(resolve, 50);
        -      }
        -    };
        +      }, (err) => {
        +        reject(`Error callback called: ${err.toString()}`);
        +      });
        +    });
           });
         
           test('returns an object with correct data', async function() {
        -    const shader = await promisedSketch(function(sketch, resolve, reject) {
        -      var _shader;
        -      sketch.preload = function() {
        -        _shader = sketch.loadShader(vertFile, fragFile, function() {}, reject);
        -      };
        -
        -      sketch.setup = function() {
        -        resolve(_shader);
        -      };
        -    });
        -    assert.instanceOf(shader, p5.Shader);
        +    const shader = await mockP5Prototype.loadShader(vertFile, fragFile);
        +    assert.instanceOf(shader, Shader);
           });
         
           test('passes an object with correct data to callback', async function() {
        -    const model = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadShader(vertFile, fragFile, resolve, reject);
        -      };
        -    });
        -    assert.instanceOf(model, p5.Shader);
        -  });
        -
        -  test('does not run setup after complete when called outside of preload', async function() {
        -    let setupCallCount = 0;
        -    await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.setup = function() {
        -        setupCallCount++;
        -        sketch.loadShader(vertFile, fragFile, resolve, reject);
        -      };
        +    await mockP5Prototype.loadShader(vertFile, fragFile, (shader) => {
        +      assert.instanceOf(shader, Shader);
             });
        -    assert.equal(setupCallCount, 1);
           });
         });
        diff --git a/test/unit/io/loadStrings.js b/test/unit/io/loadStrings.js
        index f6d4ee38d0..b8db23cb53 100644
        --- a/test/unit/io/loadStrings.js
        +++ b/test/unit/io/loadStrings.js
        @@ -1,139 +1,70 @@
        +import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
        +import files from '../../../src/io/files';
        +
         suite('loadStrings', function() {
           const invalidFile = '404file';
        -  const validFile = 'unit/assets/sentences.txt';
        -  const fileWithEmptyLines = 'unit/assets/empty_lines.txt';
        -  const fileWithManyLines = 'unit/assets/many_lines.txt';
        -
        -  test('_friendlyFileLoadError is called', async function() {
        -    const _friendlyFileLoadErrorStub = sinon.stub(p5, '_friendlyFileLoadError');
        -    try {
        -      await promisedSketch(function(sketch, resolve, reject) {
        -        sketch.preload = function() {
        -          sketch.loadStrings(invalidFile, reject, resolve);
        -        };
        -      });
        -      expect(
        -        _friendlyFileLoadErrorStub.calledOnce,
        -        'p5._friendlyFileLoadError was not called'
        -      ).to.be.true;
        -    } finally {
        -      _friendlyFileLoadErrorStub.restore();
        -    }
        -  });
        -
        -  testSketchWithPromise('error prevents sketch continuing', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadStrings(invalidFile, reject, function() {
        -        setTimeout(resolve, 50);
        -      });
        -    };
        -
        -    sketch.setup = function() {
        -      reject(new Error('Entered setup'));
        -    };
        +  const validFile = '/test/unit/assets/sentences.txt';
        +  const fileWithEmptyLines = '/test/unit/assets/empty_lines.txt';
        +  const fileWithManyLines = '/test/unit/assets/many_lines.txt';
         
        -    sketch.draw = function() {
        -      reject(new Error('Entered draw'));
        -    };
        +  beforeAll(async () => {
        +    files(mockP5, mockP5Prototype);
        +    await httpMock.start({ quiet: true });
           });
         
        -  testSketchWithPromise('error callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadStrings(
        -        invalidFile,
        -        function() {
        -          reject(new Error('Success callback executed.'));
        -        },
        -        function() {
        -          // Wait a bit so that if both callbacks are executed we will get an error.
        -          setTimeout(resolve, 50);
        -        }
        -      );
        -    };
        -  });
        -
        -  testSketchWithPromise('loading correctly triggers setup', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadStrings(validFile);
        -    };
        -
        -    sketch.setup = resolve();
        +  test('throws error when encountering HTTP errors', async () => {
        +    await expect(mockP5Prototype.loadStrings(invalidFile))
        +      .rejects
        +      .toThrow('Not Found');
           });
         
        -  testSketchWithPromise('success callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadStrings(validFile, resolve, function(err) {
        -        reject(new Error('Error callback was entered: ' + err));
        +  test('error callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadStrings(invalidFile, () => {
        +        reject("Success callback executed");
        +      }, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
               });
        -    };
        -
        -    sketch.setup = function() {
        -      reject(new Error('Setup called prior to success callback'));
        -    };
        +    });
           });
         
        -  test('returns an array of strings', async function() {
        -    const strings = await promisedSketch(function(sketch, resolve, reject) {
        -      let strings;
        -      sketch.preload = function() {
        -        strings = sketch.loadStrings(validFile, function() {}, reject);
        -      };
        -
        -      sketch.setup = function() {
        -        resolve(strings);
        -      };
        +  test('success callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadStrings(validFile, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
        +      }, (err) => {
        +        reject(`Error callback called: ${err.toString()}`);
        +      });
             });
        +  });
         
        +  test('returns an array of strings',  async () => {
        +    const strings = await mockP5Prototype.loadStrings(validFile);
             assert.isArray(strings);
        -    for (let i = 0; i < strings.length; i++) {
        -      assert.isString(strings[i]);
        +    for(let string of strings){
        +      assert.isString(string);
             }
           });
         
        -  test('passes an array to success callback', async function() {
        -    const strings = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadStrings(validFile, resolve, reject);
        -      };
        +  test('passes an array to success callback',  async () => {
        +    await mockP5Prototype.loadStrings(validFile, (strings) => {
        +      assert.isArray(strings);
        +      for(let string of strings){
        +        assert.isString(string);
        +      }
             });
        -    assert.isArray(strings);
        -    for (let i = 0; i < strings.length; i++) {
        -      assert.isString(strings[i]);
        -    }
           });
         
        -  test('should include empty strings', async function() {
        -    const strings = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadStrings(fileWithEmptyLines, resolve, reject);
        -      };
        -    });
        +  test('should include empty strings',  async () => {
        +    const strings = await mockP5Prototype.loadStrings(fileWithEmptyLines);
             assert.isArray(strings, 'Array passed to callback function');
             assert.lengthOf(strings, 6, 'length of data is 6');
           });
         
        -  test('can load file with many lines', async function() {
        -    const strings = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadStrings(fileWithManyLines, resolve, reject);
        -      };
        -    });
        +  test('can load file with many lines',  async () => {
        +    const strings = await mockP5Prototype.loadStrings(fileWithManyLines);
             assert.isArray(strings, 'Array passed to callback function');
             assert.lengthOf(strings, 131073, 'length of data is 131073');
           });
        diff --git a/test/unit/io/loadTable.js b/test/unit/io/loadTable.js
        index 09557ce95f..171d12f7c3 100644
        --- a/test/unit/io/loadTable.js
        +++ b/test/unit/io/loadTable.js
        @@ -1,169 +1,78 @@
        -suite('loadTable', function() {
        -  var invalidFile = '404file';
        -  var validFile = 'unit/assets/csv.csv';
        -
        -  test('_friendlyFileLoadError is called', async function() {
        -    const _friendlyFileLoadErrorStub = sinon.stub(p5, '_friendlyFileLoadError');
        -    try {
        -      await promisedSketch(function(sketch, resolve, reject) {
        -        sketch.preload = function() {
        -          sketch.loadTable(invalidFile, reject, resolve);
        -        };
        -      });
        -      expect(
        -        _friendlyFileLoadErrorStub.calledOnce,
        -        'p5._friendlyFileLoadError was not called'
        -      ).to.be.true;
        -    } finally {
        -      _friendlyFileLoadErrorStub.restore();
        -    }
        -  });
        +import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
        +import files from '../../../src/io/files';
        +import table from '../../../src/io/p5.Table';
        +import tableRow from '../../../src/io/p5.TableRow';
         
        -  testSketchWithPromise('error prevents sketch continuing', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadTable(invalidFile);
        -      setTimeout(resolve, 50);
        -    };
        -
        -    sketch.setup = function() {
        -      reject(new Error('Setup called'));
        -    };
        -
        -    sketch.draw = function() {
        -      reject(new Error('Draw called'));
        -    };
        +suite('loadTable', function() {
        +  const invalidFile = '404file';
        +  const validFile = '/test/unit/assets/csv.csv';
        +
        +  beforeAll(async () => {
        +    files(mockP5, mockP5Prototype);
        +    table(mockP5, mockP5Prototype);
        +    tableRow(mockP5, mockP5Prototype);
        +    await httpMock.start({ quiet: true });
           });
         
        -  testSketchWithPromise('error callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadTable(
        -        invalidFile,
        -        function() {
        -          reject(new Error('Success callback executed.'));
        -        },
        -        function() {
        -          // Wait a bit so that if both callbacks are executed we will get an error.
        -          setTimeout(resolve, 50);
        -        }
        -      );
        -    };
        +  test('throws error when encountering HTTP errors', async () => {
        +    await expect(mockP5Prototype.loadTable(invalidFile))
        +      .rejects
        +      .toThrow('Not Found');
           });
         
        -  testSketchWithPromise('loading correctly triggers setup', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadTable(validFile);
        -    };
        -
        -    sketch.setup = function() {
        -      resolve();
        -    };
        +  test('error callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadTable(invalidFile, () => {
        +        reject("Success callback executed");
        +      }, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
        +      });
        +    });
           });
         
        -  testSketchWithPromise('success callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    var hasBeenCalled = false;
        -    sketch.preload = function() {
        -      sketch.loadTable(
        -        validFile,
        -        function() {
        -          hasBeenCalled = true;
        -        },
        -        function(err) {
        -          reject(new Error('Error callback was entered: ' + err));
        -        }
        -      );
        -    };
        -
        -    sketch.setup = function() {
        -      if (!hasBeenCalled) {
        -        reject(new Error('Setup called prior to success callback'));
        -      } else {
        +  test('success callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadTable(validFile, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
                 setTimeout(resolve, 50);
        -      }
        -    };
        +      }, (err) => {
        +        reject(`Error callback called: ${err.toString()}`);
        +      });
        +    });
           });
         
        -  test('returns an object with correct data', async function() {
        -    const table = await promisedSketch(function(sketch, resolve, reject) {
        -      let _table;
        -      sketch.preload = function() {
        -        _table = sketch.loadTable(validFile, function() {}, reject);
        -      };
        -
        -      sketch.setup = function() {
        -        resolve(_table);
        -      };
        -    });
        +  test('returns an object with correct data', async () => {
        +    const table = await mockP5Prototype.loadTable(validFile);
             assert.equal(table.getRowCount(), 4);
             assert.strictEqual(table.getRow(1).getString(0), 'David');
             assert.strictEqual(table.getRow(1).getNum(1), 31);
           });
         
        -  test('passes an object to success callback for object JSON', async function() {
        -    const table = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadTable(validFile, resolve, reject);
        -      };
        +  test('passes an object with correct data to success callback', async () => {
        +    await mockP5Prototype.loadTable(validFile, (table) => {
        +      assert.equal(table.getRowCount(), 4);
        +      assert.strictEqual(table.getRow(1).getString(0), 'David');
        +      assert.strictEqual(table.getRow(1).getNum(1), 31);
             });
        -    assert.equal(table.getRowCount(), 4);
        -    assert.strictEqual(table.getRow(1).getString(0), 'David');
        -    assert.strictEqual(table.getRow(1).getNum(1), 31);
           });
         
        -  test('csv option returns the correct data', async function() {
        -    const table = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadTable(validFile, 'csv', resolve, reject);
        -      };
        -    });
        +  test('separator option returns the correct data', async () => {
        +    const table = await mockP5Prototype.loadTable(validFile, ',');
             assert.equal(table.getRowCount(), 4);
             assert.strictEqual(table.getRow(1).getString(0), 'David');
             assert.strictEqual(table.getRow(1).getNum(1), 31);
           });
         
        -  test('using the header option works', async function() {
        -    const table = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadTable(validFile, 'header', resolve, reject);
        -      };
        -    });
        +  test('using the header option works', async () => {
        +    const table = await mockP5Prototype.loadTable(validFile, ',', true);
             assert.equal(table.getRowCount(), 3);
        -    assert.strictEqual(table.getRow(0).getString('name'), 'David');
        -    assert.strictEqual(table.getRow(0).getNum('age'), 31);
        +    assert.strictEqual(table.getRow(0).getString(0), 'David');
        +    assert.strictEqual(table.getRow(0).getNum(1), 31);
           });
         
        -  test('allows the csv and header options together', async function() {
        -    const table = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadTable(validFile, 'csv', 'header', resolve, reject);
        -      };
        -    });
        -    assert.equal(table.getRowCount(), 3);
        -    assert.strictEqual(table.getRow(0).getString('name'), 'David');
        -    assert.strictEqual(table.getRow(0).getNum('age'), 31);
        -  });
        -
        -  test('CSV files should handle commas within quoted fields', async function() {
        -    const table = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadTable(validFile, resolve, reject);
        -      };
        -    });
        +  test('CSV files should handle commas within quoted fields', async () => {
        +    const table = await mockP5Prototype.loadTable(validFile);
             assert.equal(table.getRowCount(), 4);
             assert.equal(table.getRow(2).get(0), 'David, Jr.');
             assert.equal(table.getRow(2).getString(0), 'David, Jr.');
        @@ -171,12 +80,9 @@ suite('loadTable', function() {
             assert.equal(table.getRow(2).getString(1), 11);
           });
         
        -  test('CSV files should handle escaped quotes and returns within quoted fields', async function() {
        -    const table = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadTable(validFile, resolve, reject);
        -      };
        -    });
        +  test('CSV files should handle escaped quotes and returns within quoted fields', async () => {
        +    // TODO: Current parsing does not handle quoted fields
        +    const table = await mockP5Prototype.loadTable(validFile);
             assert.equal(table.getRowCount(), 4);
             assert.equal(table.getRow(3).get(0), 'David,\nSr. "the boss"');
           });
        diff --git a/test/unit/io/loadXML.js b/test/unit/io/loadXML.js
        index db906b86e8..9ed8109e68 100644
        --- a/test/unit/io/loadXML.js
        +++ b/test/unit/io/loadXML.js
        @@ -1,127 +1,58 @@
        -suite('loadXML', function() {
        -  var invalidFile = '404file';
        -  var validFile = 'unit/assets/books.xml';
        -
        -  test('_friendlyFileLoadError is called', async function() {
        -    const _friendlyFileLoadErrorStub = sinon.stub(p5, '_friendlyFileLoadError');
        -    try {
        -      await promisedSketch(function(sketch, resolve, reject) {
        -        sketch.preload = function() {
        -          sketch.loadXML(invalidFile, reject, resolve);
        -        };
        -      });
        -      expect(
        -        _friendlyFileLoadErrorStub.calledOnce,
        -        'p5._friendlyFileLoadError was not called'
        -      ).to.be.true;
        -    } finally {
        -      _friendlyFileLoadErrorStub.restore();
        -    }
        -  });
        -
        -  testSketchWithPromise('error prevents sketch continuing', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadXML(invalidFile);
        -      setTimeout(resolve, 50);
        -    };
        +import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
        +import files from '../../../src/io/files';
        +import xml from '../../../src/io/p5.XML';
         
        -    sketch.setup = function() {
        -      reject(new Error('Setup called'));
        -    };
        +suite('loadXML', function() {
        +  const invalidFile = '404file';
        +  const validFile = '/test/unit/assets/books.xml';
         
        -    sketch.draw = function() {
        -      reject(new Error('Draw called'));
        -    };
        +  beforeAll(async () => {
        +    files(mockP5, mockP5Prototype);
        +    xml(mockP5, mockP5Prototype);
        +    await httpMock.start({ quiet: true });
           });
         
        -  testSketchWithPromise('error callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadXML(
        -        invalidFile,
        -        function() {
        -          reject(new Error('Success callback executed.'));
        -        },
        -        function() {
        -          // Wait a bit so that if both callbacks are executed we will get an error.
        -          setTimeout(resolve, 50);
        -        }
        -      );
        -    };
        +  test('throws error when encountering HTTP errors', async () => {
        +    await expect(mockP5Prototype.loadXML(invalidFile))
        +      .rejects
        +      .toThrow('Not Found');
           });
         
        -  testSketchWithPromise('loading correctly triggers setup', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    sketch.preload = function() {
        -      sketch.loadXML(validFile);
        -    };
        -
        -    sketch.setup = function() {
        -      resolve();
        -    };
        +  test('error callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadXML(invalidFile, () => {
        +        console.log("here");
        +        reject("Success callback executed");
        +      }, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
        +        setTimeout(resolve, 50);
        +      });
        +    });
           });
         
        -  testSketchWithPromise('success callback is called', function(
        -    sketch,
        -    resolve,
        -    reject
        -  ) {
        -    var hasBeenCalled = false;
        -    sketch.preload = function() {
        -      sketch.loadXML(
        -        validFile,
        -        function() {
        -          hasBeenCalled = true;
        -        },
        -        function(err) {
        -          reject(new Error('Error callback was entered: ' + err));
        -        }
        -      );
        -    };
        -
        -    sketch.setup = function() {
        -      if (!hasBeenCalled) {
        -        reject(new Error('Setup called prior to success callback'));
        -      } else {
        +  test('success callback is called', async () => {
        +    await new Promise((resolve, reject) => {
        +      mockP5Prototype.loadXML(validFile, () => {
        +        // Wait a bit so that if both callbacks are executed we will get an error.
                 setTimeout(resolve, 50);
        -      }
        -    };
        +      }, (err) => {
        +        reject(`Error callback called: ${err.toString()}`);
        +      });
        +    });
           });
         
        -  test('returns an object with correct data', async function() {
        -    const xml = await promisedSketch(function(sketch, resolve, reject) {
        -      let _xml;
        -      sketch.preload = function() {
        -        _xml = sketch.loadXML(validFile, function() {}, reject);
        -      };
        -
        -      sketch.setup = function() {
        -        resolve(_xml);
        -      };
        -    });
        +  test('returns an object with correct data', async () => {
        +    const xml = await mockP5Prototype.loadXML(validFile);
             assert.isObject(xml);
        -    var children = xml.getChildren('book');
        +    const children = xml.getChildren('book');
             assert.lengthOf(children, 12);
           });
         
        -  test('passes an object with correct data', async function() {
        -    const xml = await promisedSketch(function(sketch, resolve, reject) {
        -      sketch.preload = function() {
        -        sketch.loadXML(validFile, resolve, reject);
        -      };
        +  test('passes an object with correct data to success callback', async () => {
        +    await mockP5Prototype.loadXML(validFile, (xml) => {
        +      assert.isObject(xml);
        +      const children = xml.getChildren('book');
        +      assert.lengthOf(children, 12);
             });
        -    assert.isObject(xml);
        -    var children = xml.getChildren('book');
        -    assert.lengthOf(children, 12);
           });
         });
        diff --git a/test/unit/io/saveModel.js b/test/unit/io/saveModel.js
        deleted file mode 100644
        index b15d76685f..0000000000
        --- a/test/unit/io/saveModel.js
        +++ /dev/null
        @@ -1,113 +0,0 @@
        -suite('saveModel',function() {
        -  var myp5;
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        -  teardown(function() {
        -    myp5.remove();
        -  });
        -  testWithDownload(
        -    'should download an .obj file with expected contents',
        -    async function(blobContainer) {
        -      //.obj content as a string
        -      const objContent = `v 100 0 0
        -      v 0 -100 0
        -      v 0 0 -100
        -      v 0 100 0
        -      v 100 0 0
        -      v 0 0 -100
        -      v 0 100 0
        -      v 0 0 100
        -      v 100 0 0
        -      v 0 100 0
        -      v 0 0 -100
        -      v -100 0 0
        -      v -100 0 0
        -      v 0 -100 0
        -      v 0 0 100
        -      v 0 0 -100
        -      v 0 -100 0
        -      v -100 0 0
        -      v 0 100 0
        -      v -100 0 0
        -      v 0 0 100
        -      v 0 0 100
        -      v 0 -100 0
        -      v 100 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vt 0 0
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      vn 0 0 1
        -      f 1 2 3
        -      f 4 5 6
        -      f 7 8 9
        -      f 10 11 12
        -      f 13 14 15
        -      f 16 17 18
        -      f 19 20 21
        -      f 22 23 24
        -      `;
        -
        -      const objBlob = new Blob([objContent], { type: 'text/plain' });
        -
        -      myp5.downloadFile(objBlob, 'model', 'obj');
        -
        -      let myBlob = blobContainer.blob;
        -
        -      let text = await myBlob.text();
        -
        -      assert.strictEqual(text, objContent);
        -    },
        -    true
        -  );
        -
        -});
        diff --git a/test/unit/io/saveTable.js b/test/unit/io/saveTable.js
        index 52b0c58980..d6edf15fe8 100644
        --- a/test/unit/io/saveTable.js
        +++ b/test/unit/io/saveTable.js
        @@ -1,142 +1,92 @@
        -suite('saveTable', function() {
        -  let validFile = 'unit/assets/csv.csv';
        -  let myp5;
        -  let myTable;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import * as fileSaver from 'file-saver';
        +import { vi } from 'vitest';
        +import files from '../../../src/io/files';
        +import table from '../../../src/io/p5.Table';
        +import tableRow from '../../../src/io/p5.TableRow';
         
        -  teardown(function() {
        -    myp5.remove();
        -  });
        +vi.mock('file-saver');
         
        -  setup(function disableFileLoadError() {
        -    sinon.stub(p5, '_friendlyFileLoadError');
        -  });
        +suite('saveTable', function() {
        +  const validFile = '/test/unit/assets/csv.csv';
        +  let myTable;
         
        -  teardown(function restoreFileLoadError() {
        -    p5._friendlyFileLoadError.restore();
        +  beforeAll(async function() {
        +    files(mockP5, mockP5Prototype);
        +    table(mockP5, mockP5Prototype);
        +    tableRow(mockP5, mockP5Prototype);
        +    myTable = await mockP5Prototype.loadTable(validFile, ',', 'header');
           });
         
        -  setup(function loadMyTable(done) {
        -    myp5.loadTable(validFile, 'csv', 'header', function(table) {
        -      myTable = table;
        -      done();
        -    });
        +  afterEach(() => {
        +    vi.clearAllMocks();
           });
         
           test('should be a function', function() {
        -    assert.ok(myp5.saveTable);
        -    assert.typeOf(myp5.saveTable, 'function');
        +    assert.ok(mockP5Prototype.saveTable);
        +    assert.typeOf(mockP5Prototype.saveTable, 'function');
           });
         
        -  test('no friendly-err-msg I', function() {
        -    assert.doesNotThrow(
        -      function() {
        -        myp5.saveTable(myTable, 'myfile');
        -      },
        -      Error,
        -      'got unwanted exception'
        -    );
        -  });
        +  test('should download a file with expected contents', async () => {
        +    mockP5Prototype.saveTable(myTable, 'filename');
         
        -  test('no friendly-err-msg II', function() {
        -    assert.doesNotThrow(
        -      function() {
        -        myp5.saveTable(myTable, 'myfile', 'csv');
        -      },
        -      Error,
        -      'got unwanted exception'
        -    );
        +    // TODO: Need comprehensive way to compare blobs in spy call
        +    expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +    expect(fileSaver.saveAs)
        +      .toHaveBeenCalledWith(
        +        expect.any(Blob),
        +        'filename.csv'
        +      );
           });
         
        -  testUnMinified('missing param #1', function() {
        -    assert.validationError(function() {
        -      myp5.saveTable(myTable);
        -    });
        -  });
        +  test('should download a file with expected contents (tsv)', async () => {
        +    mockP5Prototype.saveTable(myTable, 'filename', 'tsv');
         
        -  testUnMinified('wrong param type #0', function() {
        -    assert.validationError(function() {
        -      myp5.saveTable('myTable', 'myfile');
        -    });
        +    // TODO: Need comprehensive way to compare blobs in spy call
        +    expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +    expect(fileSaver.saveAs)
        +      .toHaveBeenCalledWith(
        +        expect.any(Blob),
        +        'filename.tsv'
        +      );
           });
         
        -  testUnMinified('wrong param type #1', function() {
        -    assert.validationError(function() {
        -      myp5.saveTable(myTable, 2);
        -    });
        -  });
        +  test('should download a file with expected contents (html)', async () => {
        +    mockP5Prototype.saveTable(myTable, 'filename', 'html');
         
        -  testUnMinified('wrong param type #2', function() {
        -    assert.validationError(function() {
        -      myp5.saveTable(myTable, 'myfile', 2);
        -    });
        +    expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
        +    expect(fileSaver.saveAs)
        +      .toHaveBeenCalledWith(
        +        expect.any(Blob),
        +        'filename.html'
        +      );
           });
        -
        -  testWithDownload(
        -    'should download a file with expected contents',
        -    async function(blobContainer) {
        -      myp5.saveTable(myTable, 'filename');
        -      let myBlob = blobContainer.blob;
        -      let text = await myBlob.text();
        -      let myTableStr = myTable.columns.join(',') + '\n';
        -      for (let i = 0; i < myTable.rows.length; i++) {
        -        myTableStr += myTable.rows[i].arr.join(',') + '\n';
        -      }
        -
        -      assert.strictEqual(text, myTableStr);
        -    },
        -    true
        -  );
        -
        -  testWithDownload(
        -    'should download a file with expected contents (tsv)',
        -    async function(blobContainer) {
        -      myp5.saveTable(myTable, 'filename', 'tsv');
        -      let myBlob = blobContainer.blob;
        -      let text = await myBlob.text();
        -      let myTableStr = myTable.columns.join('\t') + '\n';
        -      for (let i = 0; i < myTable.rows.length; i++) {
        -        myTableStr += myTable.rows[i].arr.join('\t') + '\n';
        -      }
        -      assert.strictEqual(text, myTableStr);
        -    },
        -    true
        -  );
        -
        -  testWithDownload(
        -    'should download a file with expected contents (html)',
        -    async function(blobContainer) {
        -      myp5.saveTable(myTable, 'filename', 'html');
        -      let myBlob = blobContainer.blob;
        -      let text = await myBlob.text();
        -      let domparser = new DOMParser();
        -      let htmldom = domparser.parseFromString(text, 'text/html');
        -      let trs = htmldom.querySelectorAll('tr');
        -      for (let i = 0; i < trs.length; i++) {
        -        let tds = trs[i].querySelectorAll('td');
        -        for (let j = 0; j < tds.length; j++) {
        -          // saveTable generates an HTML file with indentation spaces and line-breaks. The browser ignores these
        -          // while displaying. But they will still remain a part of the parsed DOM and hence must be removed.
        -          // More info at: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
        -          let tdText = tds[j].innerHTML.trim().replace(/\n/g, '');
        -          let tbText;
        -          if (i === 0) {
        -            tbText = myTable.columns[j].trim().replace(/\n/g, '');
        -          } else {
        -            tbText = myTable.rows[i - 1].arr[j].trim().replace(/\n/g, '');
        -          }
        -          assert.strictEqual(tdText, tbText);
        -        }
        -      }
        -    },
        -    true
        -  );
        +  // testWithDownload(
        +  //   'should download a file with expected contents (html)',
        +  //   async function(blobContainer) {
        +  //     myp5.saveTable(myTable, 'filename', 'html');
        +  //     let myBlob = blobContainer.blob;
        +  //     let text = await myBlob.text();
        +  //     let domparser = new DOMParser();
        +  //     let htmldom = domparser.parseFromString(text, 'text/html');
        +  //     let trs = htmldom.querySelectorAll('tr');
        +  //     for (let i = 0; i < trs.length; i++) {
        +  //       let tds = trs[i].querySelectorAll('td');
        +  //       for (let j = 0; j < tds.length; j++) {
        +  //         // saveTable generates an HTML file with indentation spaces and line-breaks. The browser ignores these
        +  //         // while displaying. But they will still remain a part of the parsed DOM and hence must be removed.
        +  //         // More info at: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
        +  //         let tdText = tds[j].innerHTML.trim().replace(/\n/g, '');
        +  //         let tbText;
        +  //         if (i === 0) {
        +  //           tbText = myTable.columns[j].trim().replace(/\n/g, '');
        +  //         } else {
        +  //           tbText = myTable.rows[i - 1].arr[j].trim().replace(/\n/g, '');
        +  //         }
        +  //         assert.strictEqual(tdText, tbText);
        +  //       }
        +  //     }
        +  //   },
        +  //   true
        +  // );
         });
        diff --git a/test/unit/math/calculation.js b/test/unit/math/calculation.js
        index 72d6cfe93e..1e0e62c1d0 100644
        --- a/test/unit/math/calculation.js
        +++ b/test/unit/math/calculation.js
        @@ -1,31 +1,31 @@
        +import calculation from '../../../src/math/calculation.js';
        +import { vi } from 'vitest';
        +
         suite('Calculation', function() {
        -  var myp5;
        +  const mockP5 = {
        +    _validateParameters: vi.fn()
        +  };
        +  const mockP5Prototype = {};
         
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        +  beforeAll(function() {
        +    calculation(mockP5, mockP5Prototype);
           });
         
        -  teardown(function() {
        -    myp5.remove();
        +  afterAll(function() {
           });
         
           suite('p5.prototype.abs', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.abs);
        -      assert.typeOf(myp5.abs, 'function');
        +      assert.ok(mockP5Prototype.abs);
        +      assert.typeOf(mockP5Prototype.abs, 'function');
             });
             test('should return a number', function() {
        -      result = myp5.abs();
        +      result = mockP5Prototype.abs();
               assert.typeOf(result, 'number');
             });
             test('should return an absolute value', function() {
        -      result = myp5.abs(-1);
        +      result = mockP5Prototype.abs(-1);
               assert.equal(result, 1);
               assert.notEqual(result, -1);
             });
        @@ -34,19 +34,19 @@ suite('Calculation', function() {
           suite('p5.prototype.ceil', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.ceil);
        -      assert.typeOf(myp5.ceil, 'function');
        +      assert.ok(mockP5Prototype.ceil);
        +      assert.typeOf(mockP5Prototype.ceil, 'function');
             });
             test('should return ceil value given negative value', function() {
        -      result = myp5.ceil(-1.9);
        +      result = mockP5Prototype.ceil(-1.9);
               assert.equal(result, -1);
             });
             test('should return a ceil value given positive value', function() {
        -      result = myp5.ceil(0.1);
        +      result = mockP5Prototype.ceil(0.1);
               assert.equal(result, 1);
             });
             test('should return same number', function() {
        -      result = myp5.ceil(1);
        +      result = mockP5Prototype.ceil(1);
               assert.equal(result, 1);
             });
           });
        @@ -54,83 +54,83 @@ suite('Calculation', function() {
           suite('p5.prototype.dist', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.dist);
        -      assert.typeOf(myp5.dist, 'function');
        +      assert.ok(mockP5Prototype.dist);
        +      assert.typeOf(mockP5Prototype.dist, 'function');
             });
             test('should return a number', function() {
        -      result = myp5.dist(0, 0, 2, 3);
        +      result = mockP5Prototype.dist(0, 0, 2, 3);
               assert.typeOf(result, 'number');
             });
             test('should return correct distance', function() {
        -      result = myp5.dist(0, 0, 2, 3);
        +      result = mockP5Prototype.dist(0, 0, 2, 3);
               assert.approximately(result, 3.605551, 0.000001); // Math.hypot(2, 3)
             });
             test('should return positive  distance', function() {
        -      result = myp5.dist(0, 0, -2, -3);
        +      result = mockP5Prototype.dist(0, 0, -2, -3);
               assert.approximately(result, 3.605551, 0.000001); // Math.hypot(2, 3)
             });
             test('should return correct distance', function() {
        -      result = myp5.dist(0, 0, 0, 2, 3, 5);
        +      result = mockP5Prototype.dist(0, 0, 0, 2, 3, 5);
               assert.approximately(result, 6.164414, 0.000001); // Math.hypot(2, 3, 5)
             });
             test('should return positive  distance', function() {
        -      result = myp5.dist(0, 0, 0, -2, -3, 5);
        +      result = mockP5Prototype.dist(0, 0, 0, -2, -3, 5);
               assert.approximately(result, 6.164414, 0.000001); // Math.hypot(2, 3, 5)
             });
             test('should not underflow', function() {
        -      result = myp5.dist(0, 0, 1e-200, 2e-200);
        +      result = mockP5Prototype.dist(0, 0, 1e-200, 2e-200);
               assert.notEqual(result, 0);
             });
             test('should not overflow', function() {
        -      result = myp5.dist(0, 0, 1e200, 2e200);
        +      result = mockP5Prototype.dist(0, 0, 1e200, 2e200);
               assert.notEqual(result, Infinity);
             });
             test('should return 0 for identical 2D points', function() {
        -      result = myp5.dist(2, 3, 2, 3);
        +      result = mockP5Prototype.dist(2, 3, 2, 3);
               assert.equal(result, 0);
             });
             test('should return 0 for identical 3D points', function() {
        -      result = myp5.dist(2, 3, 5, 2, 3, 5);
        +      result = mockP5Prototype.dist(2, 3, 5, 2, 3, 5);
               assert.equal(result, 0);
             });
             test('should return infinity if coordinate of a point is at infinity (2D)', function() {
        -      result = myp5.dist(0, 0, Infinity, 0);
        +      result = mockP5Prototype.dist(0, 0, Infinity, 0);
               assert.equal(result, Infinity);
             });
             test('should return infinity if coordinate of a point is at -infinity (2D)', function() {
        -      result = myp5.dist(0, 0, -Infinity, 0);
        +      result = mockP5Prototype.dist(0, 0, -Infinity, 0);
               assert.equal(result, Infinity);
             });
             test('should handle overflow correctly (2D)', function() {
        -      result = myp5.dist(0, 1e200, 0, 1e199);
        +      result = mockP5Prototype.dist(0, 1e200, 0, 1e199);
               assert.equal(result, 9e199);
             });
             test('should handle rounding correctly (2D)', function() {
        -      result = myp5.dist(0, 1e-200, 0, 1e-199);
        +      result = mockP5Prototype.dist(0, 1e-200, 0, 1e-199);
               assert.equal(result, 9e-200);
             });
             test('should handle string parameters correctly (2D)', function() {
        -      result = myp5.dist(0, 0, '4', '3');
        +      result = mockP5Prototype.dist(0, 0, '4', '3');
               assert.equal(result, 5);
             });
             test('should return infinity if coordinate of a point is at infinity (3D)', function() {
        -      result = myp5.dist(0, 0, 0, Infinity, 0, 0);
        +      result = mockP5Prototype.dist(0, 0, 0, Infinity, 0, 0);
               assert.equal(result, Infinity);
             });
             test('should return infinity if coordinate of a point is at -infinity (3D)', function() {
        -      result = myp5.dist(0, 0, 0, -Infinity, 0, 0);
        +      result = mockP5Prototype.dist(0, 0, 0, -Infinity, 0, 0);
               assert.equal(result, Infinity);
             });
             test('should handle overflow correctly (3D)', function() {
        -      result = myp5.dist(0, 0, 1e200, 0, 0, 1e199);
        +      result = mockP5Prototype.dist(0, 0, 1e200, 0, 0, 1e199);
               assert.equal(result, 9e199);
             });
             test('should handle rounding correctly (3D)', function() {
        -      result = myp5.dist(0, 0, 1e-200, 0, 0, 1e-199);
        +      result = mockP5Prototype.dist(0, 0, 1e-200, 0, 0, 1e-199);
               assert.equal(result, 9e-200);
             });
             test('should handle string parameters correctly (3D)', function() {
        -      result = myp5.dist(0, 0, 0, '4', '4', '2');
        +      result = mockP5Prototype.dist(0, 0, 0, '4', '4', '2');
               assert.equal(result, 6);
             });
           });
        @@ -138,19 +138,19 @@ suite('Calculation', function() {
           suite('p5.prototype.exp', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.exp);
        -      assert.typeOf(myp5.exp, 'function');
        +      assert.ok(mockP5Prototype.exp);
        +      assert.typeOf(mockP5Prototype.exp, 'function');
             });
             test('should return exp value given negative value', function() {
        -      result = myp5.exp(-1);
        +      result = mockP5Prototype.exp(-1);
               assert.approximately(result, Math.exp(-1), 0.000001);
             });
             test('should return exp value given positive value', function() {
        -      result = myp5.exp(1);
        +      result = mockP5Prototype.exp(1);
               assert.approximately(result, Math.exp(1), 0.000001);
             });
             test('should return 1', function() {
        -      result = myp5.exp(0);
        +      result = mockP5Prototype.exp(0);
               assert.equal(result, 1);
             });
           });
        @@ -158,19 +158,19 @@ suite('Calculation', function() {
           suite('p5.prototype.floor', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.floor);
        -      assert.typeOf(myp5.floor, 'function');
        +      assert.ok(mockP5Prototype.floor);
        +      assert.typeOf(mockP5Prototype.floor, 'function');
             });
             test('should return floor value given negative value', function() {
        -      result = myp5.floor(-1.9);
        +      result = mockP5Prototype.floor(-1.9);
               assert.equal(result, -2);
             });
             test('should return a floor value given positive value', function() {
        -      result = myp5.floor(0.1);
        +      result = mockP5Prototype.floor(0.1);
               assert.equal(result, 0);
             });
             test('should return same number', function() {
        -      result = myp5.floor(1);
        +      result = mockP5Prototype.floor(1);
               assert.equal(result, 1);
             });
           });
        @@ -178,19 +178,19 @@ suite('Calculation', function() {
           suite('p5.prototype.lerp', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.lerp);
        -      assert.typeOf(myp5.lerp, 'function');
        +      assert.ok(mockP5Prototype.lerp);
        +      assert.typeOf(mockP5Prototype.lerp, 'function');
             });
             test('should return start', function() {
        -      result = myp5.lerp(0, 5, 0);
        +      result = mockP5Prototype.lerp(0, 5, 0);
               assert.equal(result, 0);
             });
             test('should return average', function() {
        -      result = myp5.lerp(0, 5, 0.5);
        +      result = mockP5Prototype.lerp(0, 5, 0.5);
               assert.equal(result, 2.5);
             });
             test('should return stop', function() {
        -      result = myp5.lerp(0, 5, 1);
        +      result = mockP5Prototype.lerp(0, 5, 1);
               assert.equal(result, 5);
             });
           });
        @@ -198,19 +198,19 @@ suite('Calculation', function() {
           suite('p5.prototype.log', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.log);
        -      assert.typeOf(myp5.log, 'function');
        +      assert.ok(mockP5Prototype.log);
        +      assert.typeOf(mockP5Prototype.log, 'function');
             });
             test('should return log value given negative value', function() {
        -      result = myp5.log(Math.exp(-1));
        +      result = mockP5Prototype.log(Math.exp(-1));
               assert.approximately(result, -1, 0.0001);
             });
             test('should return log value given positive value', function() {
        -      result = myp5.log(Math.exp(1));
        +      result = mockP5Prototype.log(Math.exp(1));
               assert.approximately(result, 1, 0.0001);
             });
             test('should return 0', function() {
        -      result = myp5.log(Math.exp(0));
        +      result = mockP5Prototype.log(Math.exp(0));
               assert.equal(result, 0);
             });
           });
        @@ -218,19 +218,19 @@ suite('Calculation', function() {
           suite('p5.prototype.mag', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.mag);
        -      assert.typeOf(myp5.mag, 'function');
        +      assert.ok(mockP5Prototype.mag);
        +      assert.typeOf(mockP5Prototype.mag, 'function');
             });
             test('should return a number', function() {
        -      result = myp5.mag(2, 3);
        +      result = mockP5Prototype.mag(2, 3);
               assert.typeOf(result, 'number');
             });
             test('should return correct magitude', function() {
        -      result = myp5.mag(2, 3);
        +      result = mockP5Prototype.mag(2, 3);
               assert.approximately(result, 3.605551, 0.000001); // Math.hypot(2, 3)
             });
             test('should return positive magnitude given negative inputs', function() {
        -      result = myp5.mag(-2, -3);
        +      result = mockP5Prototype.mag(-2, -3);
               assert.approximately(result, 3.605551, 0.000001); // Math.hypot(2, 3)
             });
           });
        @@ -238,59 +238,65 @@ suite('Calculation', function() {
           suite('p5.prototype.map', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.map);
        -      assert.typeOf(myp5.map, 'function');
        +      assert.ok(mockP5Prototype.map);
        +      assert.typeOf(mockP5Prototype.map, 'function');
             });
             test('should return a number', function() {
        -      result = myp5.map(1, 0, 10, 0, 20);
        +      result = mockP5Prototype.map(1, 0, 10, 0, 20);
               assert.typeOf(result, 'number');
             });
             test('should return scaled value', function() {
        -      result = myp5.map(1, 0, 10, 0, 20);
        +      result = mockP5Prototype.map(1, 0, 10, 0, 20);
               assert.equal(result, 2);
             });
             test('should extrapolate by default', function() {
        -      assert.approximately(myp5.map(10, 0, 1, 10, 11), 20, 0.01);
        -      assert.approximately(myp5.map(-1, 0, 1, 10, 11), 9, 0.01);
        -      assert.approximately(myp5.map(2, 0, 1, 20, 10), 0, 0.01);
        +      assert.approximately(mockP5Prototype.map(10, 0, 1, 10, 11), 20, 0.01);
        +      assert.approximately(mockP5Prototype.map(-1, 0, 1, 10, 11), 9, 0.01);
        +      assert.approximately(mockP5Prototype.map(2, 0, 1, 20, 10), 0, 0.01);
             });
             test('shaould clamp correctly', function() {
        -      assert.approximately(myp5.map(1, 0, 10, 0, 20, true), 2, 0.01);
        -
        -      assert.approximately(myp5.map(10, 0, 1, 10, 11, true), 11, 0.01);
        -      assert.approximately(myp5.map(-1, 0, 1, 10, 11, true), 10, 0.01);
        -      assert.approximately(myp5.map(2, 0, 1, 20, 10, true), 10, 0.01);
        +      assert.approximately(mockP5Prototype.map(1, 0, 10, 0, 20, true), 2, 0.01);
        +
        +      assert.approximately(
        +        mockP5Prototype.map(10, 0, 1, 10, 11, true), 11, 0.01
        +      );
        +      assert.approximately(
        +        mockP5Prototype.map(-1, 0, 1, 10, 11, true), 10, 0.01
        +      );
        +      assert.approximately(
        +        mockP5Prototype.map(2, 0, 1, 20, 10, true), 10, 0.01
        +      );
             });
           });
         
           suite('p5.prototype.max', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.max);
        -      assert.typeOf(myp5.max, 'function');
        +      assert.ok(mockP5Prototype.max);
        +      assert.typeOf(mockP5Prototype.max, 'function');
             });
             test('should return larger left argument', function() {
        -      result = myp5.max(10, -1);
        +      result = mockP5Prototype.max(10, -1);
               assert.equal(result, 10);
             });
             test('should return larger right argument', function() {
        -      result = myp5.max(-1, 10);
        +      result = mockP5Prototype.max(-1, 10);
               assert.equal(result, 10);
             });
             test('should return single value', function() {
        -      result = myp5.max(10, 10);
        +      result = mockP5Prototype.max(10, 10);
               assert.equal(result, 10);
             });
             test('should return larger value from array', function() {
        -      result = myp5.max([10, -1]);
        +      result = mockP5Prototype.max([10, -1]);
               assert.equal(result, 10);
             });
             test('should return larger value from array', function() {
        -      result = myp5.max(-1, 10);
        +      result = mockP5Prototype.max(-1, 10);
               assert.equal(result, 10);
             });
             test('should return single value from array', function() {
        -      result = myp5.max([10, 10]);
        +      result = mockP5Prototype.max([10, 10]);
               assert.equal(result, 10);
             });
           });
        @@ -298,31 +304,31 @@ suite('Calculation', function() {
           suite('p5.prototype.min', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.min);
        -      assert.typeOf(myp5.min, 'function');
        +      assert.ok(mockP5Prototype.min);
        +      assert.typeOf(mockP5Prototype.min, 'function');
             });
             test('should return smaller right  argument', function() {
        -      result = myp5.min(10, -1);
        +      result = mockP5Prototype.min(10, -1);
               assert.equal(result, -1);
             });
             test('should return smaller left  argument', function() {
        -      result = myp5.min(-1, 10);
        +      result = mockP5Prototype.min(-1, 10);
               assert.equal(result, -1);
             });
             test('should return single value', function() {
        -      result = myp5.min(10, 10);
        +      result = mockP5Prototype.min(10, 10);
               assert.equal(result, 10);
             });
             test('should return smaller value from array', function() {
        -      result = myp5.min([10, -1]);
        +      result = mockP5Prototype.min([10, -1]);
               assert.equal(result, -1);
             });
             test('should return smaller value from array', function() {
        -      result = myp5.min([-1, 10]);
        +      result = mockP5Prototype.min([-1, 10]);
               assert.equal(result, -1);
             });
             test('should return single value from array', function() {
        -      result = myp5.min([10, 10]);
        +      result = mockP5Prototype.min([10, 10]);
               assert.equal(result, 10);
             });
           });
        @@ -330,12 +336,12 @@ suite('Calculation', function() {
           suite('p5.prototype.norm', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.norm);
        -      assert.typeOf(myp5.norm, 'function');
        +      assert.ok(mockP5Prototype.norm);
        +      assert.typeOf(mockP5Prototype.norm, 'function');
             });
             test('should return scaled decimal value', function() {
               // note: there is currently scoping issues with "this" keyword
        -      result = myp5.norm(20, 0, 50);
        +      result = mockP5Prototype.norm(20, 0, 50);
               assert.equal(result, 0.4);
             });
           });
        @@ -343,22 +349,22 @@ suite('Calculation', function() {
           suite('p5.prototype.constrain', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.constrain);
        -      assert.typeOf(myp5.constrain, 'function');
        +      assert.ok(mockP5Prototype.constrain);
        +      assert.typeOf(mockP5Prototype.constrain, 'function');
             });
         
             test('should return same number', function() {
        -      result = myp5.constrain(1, 3, 5);
        +      result = mockP5Prototype.constrain(1, 3, 5);
               assert.equal(result, 3);
             });
         
             test('should return lower bound', function() {
        -      result = myp5.constrain(1, -1, 5);
        +      result = mockP5Prototype.constrain(1, -1, 5);
               assert.equal(result, 1);
             });
         
             test('should return upper bound', function() {
        -      result = myp5.constrain(1, 10, 5);
        +      result = mockP5Prototype.constrain(1, 10, 5);
               assert.equal(result, 10);
             });
           });
        @@ -366,17 +372,17 @@ suite('Calculation', function() {
           suite('p5.prototype.sq', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.sq);
        -      assert.typeOf(myp5.sq, 'function');
        +      assert.ok(mockP5Prototype.sq);
        +      assert.typeOf(mockP5Prototype.sq, 'function');
             });
         
             test('should return sauare value', function() {
        -      result = myp5.sq(10);
        +      result = mockP5Prototype.sq(10);
               assert.equal(result, 100);
             });
         
             test('should return squared value given negative number', function() {
        -      result = myp5.sq(-10);
        +      result = mockP5Prototype.sq(-10);
               assert.equal(result, 100);
             });
           });
        @@ -384,17 +390,17 @@ suite('Calculation', function() {
           suite('p5.prototype.pow', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.pow);
        -      assert.typeOf(myp5.pow, 'function');
        +      assert.ok(mockP5Prototype.pow);
        +      assert.typeOf(mockP5Prototype.pow, 'function');
             });
         
             test('should return pow for negative exponential', function() {
        -      result = myp5.pow(2, -1);
        +      result = mockP5Prototype.pow(2, -1);
               assert.equal(result, 0.5);
             });
         
             test('should return pow for positive exponential', function() {
        -      result = myp5.pow(2, 4);
        +      result = mockP5Prototype.pow(2, 4);
               assert.equal(result, 16);
             });
           });
        @@ -402,37 +408,37 @@ suite('Calculation', function() {
           suite('p5.prototype.round', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.round);
        -      assert.typeOf(myp5.round, 'function');
        +      assert.ok(mockP5Prototype.round);
        +      assert.typeOf(mockP5Prototype.round, 'function');
             });
         
             test('should round down', function() {
        -      result = myp5.round(2.1);
        +      result = mockP5Prototype.round(2.1);
               assert.equal(result, 2);
             });
         
             test('should round up from midpoint', function() {
        -      result = myp5.round(2.5);
        +      result = mockP5Prototype.round(2.5);
               assert.equal(result, 3);
             });
         
             test('should round up', function() {
        -      result = myp5.round(2.8);
        +      result = mockP5Prototype.round(2.8);
               assert.equal(result, 3);
             });
         
             test('should round two decimal places', function() {
        -      result = myp5.round(12.31833, 2);
        +      result = mockP5Prototype.round(12.31833, 2);
               assert.equal(result, 12.32);
             });
         
             test('should round very small numbers to zero', function() {
        -      result = myp5.round(1.234567e-14);
        +      result = mockP5Prototype.round(1.234567e-14);
               assert.equal(result, 0);
             });
         
             test('should round very small numbers to zero when decimal places are specified', function() {
        -      result = myp5.round(1.234567e-14, 2);
        +      result = mockP5Prototype.round(1.234567e-14, 2);
               assert.equal(result, 0);
             });
           });
        @@ -440,12 +446,12 @@ suite('Calculation', function() {
           suite('p5.prototype.sqrt', function() {
             var result;
             test('should be a function', function() {
        -      assert.ok(myp5.sqrt);
        -      assert.typeOf(myp5.sqrt, 'function');
        +      assert.ok(mockP5Prototype.sqrt);
        +      assert.typeOf(mockP5Prototype.sqrt, 'function');
             });
         
             test('should return square root', function() {
        -      result = myp5.sqrt(100);
        +      result = mockP5Prototype.sqrt(100);
               assert.equal(result, 10);
             });
           });
        diff --git a/test/unit/math/noise.js b/test/unit/math/noise.js
        index c0a6f730ac..3b95dd403f 100644
        --- a/test/unit/math/noise.js
        +++ b/test/unit/math/noise.js
        @@ -1,17 +1,17 @@
        +import noise from '../../../src/math/noise.js';
        +import { vi } from 'vitest';
        +
         suite('Noise', function() {
        -  var myp5;
        +  const mockP5 = {
        +    _validateParameters: vi.fn()
        +  };
        +  const mockP5Prototype = {};
         
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        +  beforeAll(function() {
        +    noise(mockP5, mockP5Prototype);
           });
         
        -  teardown(function() {
        -    myp5.remove();
        +  afterAll(function() {
           });
         
           // This could use some better  testing!
        @@ -22,8 +22,8 @@ suite('Noise', function() {
           var results = [];
         
           suite('p5.prototype.noise', function() {
        -    setup(function() {
        -      result = myp5.noise(0);
        +    beforeEach(function() {
        +      result = mockP5Prototype.noise(0);
             });
             test('should return a number', function() {
               assert.typeOf(result, 'number');
        @@ -36,17 +36,17 @@ suite('Noise', function() {
         
           // Test for noiseSeed
           suite('p5.prototype.noiseSeed', function() {
        -    setup(function() {
        -      myp5.noiseSeed(99);
        +    beforeEach(function() {
        +      mockP5Prototype.noiseSeed(99);
               var t = 0;
               for (var i = 0; i < 5; i++) {
        -        results[i] = myp5.noise(t);
        +        results[i] = mockP5Prototype.noise(t);
                 t += 0.01;
               }
        -      myp5.noiseSeed(99);
        +      mockP5Prototype.noiseSeed(99);
               t = 0;
               for (i = 5; i < 10; i++) {
        -        results[i] = myp5.noise(t);
        +        results[i] = mockP5Prototype.noise(t);
                 t += 0.01;
               }
             });
        diff --git a/test/unit/math/p5.Matrix.js b/test/unit/math/p5.Matrix.js
        new file mode 100644
        index 0000000000..51e684db18
        --- /dev/null
        +++ b/test/unit/math/p5.Matrix.js
        @@ -0,0 +1,528 @@
        +import { describe, it, expect, beforeAll, afterAll, test } from "vitest";
        +import p5 from "../../../src/app.js";
        +
        +const toArray = (typedArray) => Array.from(typedArray);
        +/* eslint-disable indent */
        +var mat4 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
        +
        +var other = [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16];
        +
        +var mat3 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
        +/* eslint-enable indent */
        +
        +suite("p5.Matrix", function () {
        +  var myp5;
        +
        +  beforeAll(function () {
        +    new p5(function (p) {
        +      p.setup = function () {
        +        myp5 = p;
        +      };
        +    });
        +  });
        +
        +  afterAll(function () {
        +    myp5.remove();
        +  });
        +
        +  suite("construction", function () {
        +    test("new p5.Matrix(4)", function () {
        +      var m = new p5.Matrix(4);
        +      assert.instanceOf(m, p5.Matrix);
        +      assert.isUndefined(m.mat3);
        +      /* eslint-disable indent */
        +      assert.deepEqual(
        +        [].slice.call(m.mat4),
        +        [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
        +      );
        +      /* eslint-enable indent */
        +    });
        +
        +    test("new p5.Matrix(array)", function () {
        +      var m = new p5.Matrix(mat4);
        +      assert.instanceOf(m, p5.Matrix);
        +      assert.isUndefined(m.mat3);
        +      expect(m.mat4).toEqual(mat4);
        +    });
        +
        +    test("new p5.Matrix(mat3)", function () {
        +      var m = new p5.Matrix(mat3);
        +      assert.instanceOf(m, p5.Matrix);
        +      assert.isUndefined(m.mat4);
        +      assert.deepEqual([].slice.call(m.mat3), mat3);
        +    });
        +
        +    test("identity()", function () {
        +      var m = new p5.Matrix(4);
        +      assert.instanceOf(m, p5.Matrix);
        +      assert.isUndefined(m.mat3);
        +      /* eslint-disable indent */
        +      expect(toArray(m.mat4)).toEqual([
        +        1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
        +      ]);
        +      /* eslint-enable indent */
        +    });
        +  });
        +
        +  describe("reset", function () {
        +    it("should reset a 4x4 matrix to the identity matrix", function () {
        +      const m = new p5.Matrix([
        +        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
        +      ]);
        +      m.reset();
        +      expect(toArray(m.mat4)).toEqual([
        +        1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
        +      ]);
        +    });
        +
        +    it("should reset a 3x3 matrix to the identity matrix", function () {
        +      const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +      m.reset();
        +      expect(toArray(m.mat3)).toEqual([1, 0, 0, 0, 1, 0, 0, 0, 1]);
        +    });
        +  });
        +
        +  suite("set", function () {
        +    test("p5.Matrix", function () {
        +      var m = new p5.Matrix(4);
        +      m.set(new p5.Matrix(mat4));
        +      expect(m.mat4).toEqual(mat4);
        +      // assert.deepEqual([].slice.call(m.mat4), mat4);
        +    });
        +
        +    test("array", function () {
        +      var m = new p5.Matrix(4);
        +      m.set(mat4);
        +      assert.deepEqual([].slice.call(m.mat4), mat4);
        +    });
        +
        +    test("arguments", function () {
        +      var m = new p5.Matrix(4);
        +      m.set(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6);
        +      expect(m.mat4).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6]);
        +    });
        +  });
        +
        +  it("should clone a 4x4 matrix correctly", () => {
        +    const original = new p5.Matrix([
        +      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
        +    ]);
        +    const clone = original.clone();
        +
        +    expect(clone).not.toBe(original);
        +    expect(toArray(clone.mat4)).toEqual(toArray(original.mat4));
        +  });
        +
        +  it("should clone a 3x3 matrix correctly", () => {
        +    const original = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +    const clone = original.clone();
        +
        +    expect(clone).not.toBe(original);
        +    expect(toArray(clone.mat3)).toEqual(original.mat3);
        +  });
        +
        +  it("should clone an identity matrix correctly", () => {
        +    const original = new p5.Matrix(4);
        +    const clone = original.clone();
        +
        +    expect(clone).not.toBe(original);
        +    expect(toArray(clone.mat4)).toEqual(toArray(original.mat4));
        +  });
        +
        +  suite("get / copy", function () {
        +    test("get", function () {
        +      var m = new p5.Matrix(mat4);
        +      var m2 = m.get();
        +      assert.notEqual(m, m2);
        +      expect(m.mat4).toEqual(m2.mat4);
        +    });
        +    test("copy", function () {
        +      var m = new p5.Matrix(mat4);
        +      var m2 = m.copy();
        +      assert.notEqual(m, m2);
        +      assert.notEqual(m.mat4, m2.mat4);
        +      assert.deepEqual([].slice.call(m.mat4), [].slice.call(m2.mat4));
        +    });
        +  });
        +
        +  suite.todo("add", () => {});
        +
        +  suite("mult", function () {
        +    /* eslint-disable indent */
        +    var mm = [
        +      30, 70, 110, 150, 70, 174, 278, 382, 110, 278, 446, 614, 150, 382, 614,
        +      846,
        +    ];
        +    /* eslint-enable indent */
        +
        +    test("self", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      m.mult(m);
        +      /* eslint-disable indent */
        +      assert.deepEqual(
        +        [].slice.call(m.mat4),
        +        [
        +          90, 100, 110, 120, 202, 228, 254, 280, 314, 356, 398, 440, 426, 484,
        +          542, 600,
        +        ]
        +      );
        +      /* eslint-enable indent */
        +    });
        +
        +    test("p5.Matrix", function () {
        +      var m1 = new p5.Matrix(mat4.slice());
        +      var m2 = new p5.Matrix(other);
        +      m1.mult(m2);
        +      assert.deepEqual([].slice.call(m1.mat4), mm);
        +    });
        +
        +    test("array", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      m.mult(other);
        +      assert.deepEqual([].slice.call(m.mat4), mm);
        +    });
        +
        +    test.todo("arguments", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      m.mult.apply(m, other);
        +      assert.deepEqual([].slice.call(m.mat4), mm);
        +    });
        +  });
        +
        +  suite("apply", function () {
        +    /* eslint-disable indent */
        +    var am = [
        +      276, 304, 332, 360, 304, 336, 368, 400, 332, 368, 404, 440, 360, 400, 440,
        +      480,
        +    ];
        +    /* eslint-enable indent */
        +
        +    test("self", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      m.apply(m);
        +      /* eslint-disable indent */
        +      assert.deepEqual(
        +        [].slice.call(m.mat4),
        +        [
        +          90, 100, 110, 120, 202, 228, 254, 280, 314, 356, 398, 440, 426, 484,
        +          542, 600,
        +        ]
        +      );
        +      /* eslint-enable indent */
        +    });
        +
        +    test("p5.Matrix", function () {
        +      var m1 = new p5.Matrix(mat4.slice());
        +      var m2 = new p5.Matrix(other);
        +      m1.apply(m2);
        +      assert.deepEqual([].slice.call(m1.mat4), am);
        +    });
        +
        +    test("array", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      m.apply(other);
        +      assert.deepEqual([].slice.call(m.mat4), am);
        +    });
        +
        +    test("arguments", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      m.apply.apply(m, other);
        +      assert.deepEqual([].slice.call(m.mat4), am);
        +    });
        +  });
        +
        +  suite("scale", function () {
        +    /* eslint-disable indent */
        +    var sm = [2, 4, 6, 8, 15, 18, 21, 24, 45, 50, 55, 60, 13, 14, 15, 16];
        +    /* eslint-enable indent */
        +
        +    var mat4 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
        +    test("p5.Vector", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      var v = myp5.createVector(2, 3, 5);
        +      m.scale(v);
        +      assert.notEqual(m.mat4, mat4);
        +      assert.deepEqual([].slice.call(m.mat4), sm);
        +    });
        +
        +    test("array", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      m.scale([2, 3, 5]);
        +      assert.notEqual(m.mat4, mat4);
        +      assert.deepEqual([].slice.call(m.mat4), sm);
        +    });
        +
        +    test("arguments", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      m.scale(2, 3, 5);
        +      assert.notEqual(m.mat4, mat4);
        +      assert.deepEqual([].slice.call(m.mat4), sm);
        +    });
        +  });
        +
        +  suite("rotate", function () {
        +    /* eslint-disable max-len */
        +    var rm = [
        +      1.433447866601989, 2.5241247073503885, 3.6148015480987885,
        +      4.7054783888471885, 6.460371405020393, 7.054586073938033,
        +      7.648800742855675, 8.243015411773316, 7.950398010346969,
        +      9.157598472697025, 10.36479893504708, 11.571999397397136, 13, 14, 15, 16,
        +    ];
        +    /* eslint-enable max-len */
        +
        +    test("p5.Vector", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      var v = myp5.createVector(2, 3, 5);
        +      m.rotate4x4(45 * myp5.DEG_TO_RAD, v);
        +      assert.deepEqual([].slice.call(m.mat4), rm);
        +    });
        +
        +    test("array", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      m.rotate4x4(45 * myp5.DEG_TO_RAD, [2, 3, 5]);
        +      assert.deepEqual([].slice.call(m.mat4), rm);
        +    });
        +
        +    test("arguments", function () {
        +      var m = new p5.Matrix(mat4.slice());
        +      m.rotate4x4(45 * myp5.DEG_TO_RAD, 2, 3, 5);
        +      assert.deepEqual([].slice.call(m.mat4), rm);
        +    });
        +  });
        +
        +  suite("p5.Matrix3x3", function () {
        +    test("apply copy() to 3x3Matrix", function () {
        +      const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +      const mCopy = m.copy();
        +
        +      // The matrix created by copying is different from the original matrix
        +      assert.notEqual(m, mCopy);
        +      assert.notEqual(m.mat3, mCopy.mat3);
        +
        +      // The matrix created by copying has the same elements as the original matrix
        +      assert.deepEqual([].slice.call(m.mat3), [].slice.call(mCopy.mat3));
        +    });
        +    test("transpose()", function () {
        +      const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +      const mTp = new p5.Matrix([1, 4, 7, 2, 5, 8, 3, 6, 9]);
        +
        +      // If no arguments, transpose itself
        +      m.transpose();
        +      assert.deepEqual([].slice.call(m.mat3), [].slice.call(mTp.mat3));
        +
        +      // // If there is an array of arguments, set it by transposing it
        +      m.transpose([1, 2, 3, 10, 20, 30, 100, 200, 300]);
        +      assert.deepEqual(
        +        [].slice.call(m.mat3),
        +        [1, 10, 100, 2, 20, 200, 3, 30, 300]
        +      );
        +    });
        +
        +    test("mult a 3x3 matrix with matrix as argument", function () {
        +      const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +      const multMatrix = new p5.Matrix([1, 1, 1, 0, 1, 1, 1, 0, 1]);
        +      // When taking a matrix as an argument
        +      m.mult(multMatrix);
        +      expect(m.mat3).toEqual([ 4, 3, 6, 10, 9, 15, 16, 15, 24 ]);
        +    });
        +    
        +    test("mult a 3x3 matrix with array as argument", function () {
        +      const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +      m.mult([1, 1, 1, 0, 1, 1, 1, 0, 1])
        +      expect(m.mat3).toEqual([ 4, 3, 6, 10, 9, 15, 16, 15, 24 ]);
        +    });
        +
        +    test("mult a 3x3 matrix with arguments non array", function () {
        +      const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +      m.mult(1, 1, 1, 0, 1, 1, 1, 0, 1)
        +      expect(m.mat3).toEqual([ 4, 3, 6, 10, 9, 15, 16, 15, 24 ]);
        +    });
        +
        +    test("column() and row()", function () {
        +      // The matrix data is stored column-major, so each line below is
        +      // a column rather than a row. Imagine you are looking at the
        +      // transpose of the matrix in the source code.
        +      const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +      const column0 = m.column(0);
        +      const column1 = m.column(1);
        +      const column2 = m.column(2);
        +      expect(column0.array()).toStrictEqual([1, 2, 3]);
        +      expect(column1.array()).toStrictEqual([4, 5, 6]);
        +      expect(column2.array()).toStrictEqual([7, 8, 9]);
        +      const row0 = m.row(0);
        +      const row1 = m.row(1);
        +      const row2 = m.row(2);
        +      expect(row0.array()).toStrictEqual([1, 4, 7]);
        +      expect(row1.array()).toStrictEqual([2, 5, 8]);
        +      expect(row2.array()).toStrictEqual([3, 6, 9]);
        +    });
        +    test("diagonal()", function () {
        +      const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +      const m4x4 = new p5.Matrix([
        +        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
        +      ]);
        +      assert.deepEqual(m.diagonal(), [1, 5, 9]);
        +      assert.deepEqual(m4x4.diagonal(), [1, 6, 11, 16]);
        +    });
        +    test("multiplyVec version 3", function () {
        +      const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +      const multVector = new p5.Vector(3, 2, 1);
        +      const result = m.multiplyVec(multVector);
        +      assert.deepEqual(result.array(), [18, 24, 30]);
        +      // If there is a target, set result and return that.
        +      const target = new p5.Vector();
        +      m.multiplyVec(multVector, target);
        +      assert.deepEqual(target.array(), [18, 24, 30]);
        +    });
        +    test("createSubMatrix3x3", function () {
        +      const m4x4 = new p5.Matrix([
        +        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
        +      ]);
        +      const result = new p5.Matrix([1, 2, 3, 5, 6, 7, 9, 10, 11]);
        +      const subMatrix3x3 = m4x4.createSubMatrix3x3();
        +      assert.deepEqual(
        +        [].slice.call(result.mat3),
        +        [].slice.call(subMatrix3x3.mat3)
        +      );
        +    });
        +  });
        +
        +  ///
        +  describe("transpose", () => {
        +    it("should transpose a 4x4 matrix correctly", () => {
        +      const mat = new p5.Matrix([
        +        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
        +      ]);
        +
        +      mat.transpose(mat);
        +
        +      expect(mat.mat4).toEqual([
        +        1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16,
        +      ]);
        +    });
        +
        +    it("should transpose a 4x4 matrix from an array correctly", () => {
        +      const mat = new p5.Matrix([
        +        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
        +      ]);
        +
        +      mat.transpose([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
        +
        +      expect(mat.mat4).toEqual([
        +        1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16,
        +      ]);
        +    });
        +
        +    // TODO: matrix transpose This needs to be added to the legacy tests
        +    it.skip("should transpose a 3x3 matrix correctly", () => {
        +      const mat = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +      mat.transpose(mat);
        +      expect(mat.mat3).toEqual([1, 4, 7, 2, 5, 8, 3, 6, 9]);
        +    });
        +
        +    // TODO: matrix transpose This needs to be added to the legacy tests
        +    it.skip("should transpose a 3x3 matrix from an array correctly", () => {
        +      const mat = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +
        +      mat.transpose([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +
        +      expect(mat.mat3).toEqual([1, 4, 7, 2, 5, 8, 3, 6, 9]);
        +    });
        +  });
        +  describe.skip("Determinant", () => { // TODO: Cristian, when this is public we'll add tests
        +    it("should calculate the determinant of a 4x4 matrix", () => {
        +      const mat4 = new p5.Matrix([
        +        1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
        +      ]);
        +      const det = mat4.determinant4x4();
        +      expect(det).toBeCloseTo(1);
        +    });
        +
        +    it("should return 0 for a singular 4x4 matrix", () => {
        +      const mat4 = new p5.Matrix([
        +        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
        +      ]);
        +      const det = mat4.determinant4x4();
        +      expect(det).toBeCloseTo(0);
        +    });
        +  });
        +
        +  describe("invert", () => {
        +    it("should correctly invert a 4x4 matrix", () => {
        +      const matrix = new p5.Matrix([
        +        1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
        +      ]);
        +
        +      const invertedMatrix = matrix.invert(matrix);
        +
        +      expect(invertedMatrix.mat4).toEqual([
        +        1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
        +      ]);
        +    });
        +
        +    it("should return null for a non-invertible matrix", () => {
        +      const matrix = new p5.Matrix([
        +        1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        +      ]);
        +
        +      const invertedMatrix = matrix.invert(matrix);
        +
        +      expect(invertedMatrix).toBeNull();
        +    });
        +
        +    it("should correctly invert a non-identity 4x4 matrix", () => {
        +      const matrix = new p5.Matrix([
        +        1, 1, 1, 1, 1, -1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0,
        +      ]);
        +
        +      const invertedMatrix = matrix.invert(matrix);
        +
        +      expect(invertedMatrix.mat4).toEqual([
        +        0, 0, 0, 1, 0, 0, 1, -1, 0, 1, 1, -2, 1, -1, -2, 2,
        +      ]);
        +    });
        +  });
        +  //
        +  describe("invert", () => {
        +    it("should correctly invert a 3x3 matrix", () => {
        +      const matrix = new p5.Matrix([1, 2, 3, 0, 1, 4, 5, 6, 0]);
        +      const invertedMatrix = matrix.invert();
        +
        +      expect(invertedMatrix.mat3).toEqual([-24, 18, 5, 20, -15, -4, -5, 4, 1]);
        +    });
        +
        +    it("should return null for a non-invertible 3x3 matrix", () => {
        +      const matrix = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]);
        +      const invertedMatrix = matrix.invert();
        +
        +      expect(invertedMatrix).toBeNull();
        +    });
        +
        +    it("should return the identity matrix when inverting the identity matrix", () => {
        +      const matrix = new p5.Matrix([1, 0, 0, 0, 1, 0, 0, 0, 1]);
        +      const invertedMatrix = matrix.invert();
        +
        +      expect(invertedMatrix.mat3).toEqual([1, 0, 0, 0, 1, 0, 0, 0, 1]);
        +    });
        +  });
        +
        +  describe("mat set element", () => {
        +    it("should set element of mat4 matrix", () => {
        +      const matrix = new p5.Matrix([
        +        1, 2, 3, 5, 0, 1, 4, 5, 5, 6, 0, 5, 5, 6, 0, 5,
        +      ]);
        +      const invertedMatrix = matrix.setElement(2, 0);
        +
        +      expect(invertedMatrix.mat4).toEqual([
        +        1, 2, 0, 5, 0, 1, 4, 5, 5, 6, 0, 5, 5, 6, 0, 5,
        +      ]);
        +    });
        +
        +    it("should set element of mat3 matrix", () => {
        +      const matrix = new p5.Matrix([1, 2, 3, 0, 1, 4, 5, 6, 0]);
        +      const invertedMatrix = matrix.setElement(2, 0);
        +
        +      expect(invertedMatrix.mat3).toEqual([1, 2, 0, 0, 1, 4, 5, 6, 0]);
        +    });
        +  });
        +});
        diff --git a/test/unit/math/p5.Vector.js b/test/unit/math/p5.Vector.js
        index ef3570ee0b..a2dc405716 100644
        --- a/test/unit/math/p5.Vector.js
        +++ b/test/unit/math/p5.Vector.js
        @@ -1,141 +1,140 @@
        -suite('p5.Vector', function() {
        -  var RADIANS = 'radians';
        -  var DEGREES = 'degrees';
        +import vector from "../../../src/math/p5.Vector.js";
        +import { vi } from "vitest";
         
        -  var myp5;
        +suite("p5.Vector", function () {
           var v;
         
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +  const mockP5 = {
        +    _validateParameters: vi.fn(),
        +  };
        +  const mockP5Prototype = {};
         
        -  teardown(function() {
        -    myp5.remove();
        +  beforeEach(async function () {
        +    vector(mockP5, mockP5Prototype);
           });
         
        -  suite('p5.prototype.setHeading() RADIANS', function() {
        -    setup(function() {
        -      myp5.angleMode(RADIANS);
        -      v = myp5.createVector(1, 1);
        +  afterEach(function () {});
        +
        +  suite.todo("p5.prototype.setHeading() RADIANS", function () {
        +    beforeEach(function () {
        +      mockP5Prototype.angleMode(mockP5.RADIANS);
        +      v = mockP5Prototype.createVector(1, 1);
               v.setHeading(1);
             });
        -    test('should have heading() value of 1 (RADIANS)', function() {
        +    test("should have heading() value of 1 (RADIANS)", function () {
               assert.closeTo(v.heading(), 1, 0.001);
             });
           });
         
        -  suite('p5.prototype.setHeading() DEGREES', function() {
        -    setup(function() {
        -      myp5.angleMode(DEGREES);
        -      v = myp5.createVector(1, 1);
        +  suite.todo("p5.prototype.setHeading() DEGREES", function () {
        +    beforeEach(function () {
        +      mockP5Prototype.angleMode(mockP5.DEGREES);
        +      v = mockP5Prototype.createVector(1, 1);
               v.setHeading(1);
             });
        -    test('should have heading() value of 1 (DEGREES)', function() {
        +    test("should have heading() value of 1 (DEGREES)", function () {
               assert.closeTo(v.heading(), 1, 0.001);
             });
           });
         
        -  suite('p5.prototype.createVector()', function() {
        -    setup(function() {
        -      v = myp5.createVector();
        +  // NOTE: test this in a separate file or move `createVector` to p5.Vector file
        +  // Prefer latter
        +  suite.todo("p5.prototype.createVector()", function () {
        +    beforeEach(function () {
        +      v = mockP5Prototype.createVector();
             });
        -    test('should create instance of p5.Vector', function() {
        -      assert.instanceOf(v, p5.Vector);
        +    test("should create instance of p5.Vector", function () {
        +      assert.instanceOf(v, mockP5.Vector);
             });
         
        -    test('should have x, y, z be initialized to 0', function() {
        +    test("should have x, y, z be initialized to 0", function () {
               assert.equal(v.x, 0);
               assert.equal(v.y, 0);
               assert.equal(v.z, 0);
             });
           });
         
        -  suite('p5.prototype.createVector(1, 2, 3)', function() {
        -    setup(function() {
        -      v = myp5.createVector(1, 2, 3);
        +  suite.todo("p5.prototype.createVector(1, 2, 3)", function () {
        +    beforeEach(function () {
        +      v = mockP5Prototype.createVector(1, 2, 3);
             });
         
        -    test('should have x, y, z be initialized to 1,2,3', function() {
        +    test("should have x, y, z be initialized to 1,2,3", function () {
               assert.equal(v.x, 1);
               assert.equal(v.y, 2);
               assert.equal(v.z, 3);
             });
           });
         
        -  suite('new p5.Vector()', function() {
        -    setup(function() {
        -      v = new p5.Vector();
        +  suite("new p5.Vector()", function () {
        +    beforeEach(function () {
        +      v = new mockP5.Vector();
             });
        -    test('should set constant to DEGREES', function() {
        -      assert.instanceOf(v, p5.Vector);
        +    test("should set constant to DEGREES", function () {
        +      assert.instanceOf(v, mockP5.Vector);
             });
         
        -    test('should have x, y, z be initialized to 0', function() {
        +    test("should have x, y, z be initialized to 0", function () {
               assert.equal(v.x, 0);
               assert.equal(v.y, 0);
               assert.equal(v.z, 0);
             });
           });
         
        -  suite('new p5.Vector(1, 2, 3)', function() {
        -    setup(function() {
        -      v = new p5.Vector(1, 2, 3);
        +  suite("new p5.Vector(1, 2, 3)", function () {
        +    beforeEach(function () {
        +      v = new mockP5.Vector(1, 2, 3);
             });
         
        -    test('should have x, y, z be initialized to 1,2,3', function() {
        +    test("should have x, y, z be initialized to 1,2,3", function () {
               assert.equal(v.x, 1);
               assert.equal(v.y, 2);
               assert.equal(v.z, 3);
             });
           });
         
        -  suite('new p5.Vector(1,2,undefined)', function() {
        -    setup(function() {
        -      v = new p5.Vector(1, 2, undefined);
        +  suite("new p5.Vector(1,2,undefined)", function () {
        +    beforeEach(function () {
        +      v = new mockP5.Vector(1, 2, undefined);
             });
         
        -    test('should have x, y, z be initialized to 1,2,0', function() {
        +    test("should have x, y, z be initialized to 1,2,0", function () {
               assert.equal(v.x, 1);
               assert.equal(v.y, 2);
               assert.equal(v.z, 0);
             });
           });
         
        -  suite('rotate', function() {
        -    suite('p5.Vector.prototype.rotate() [INSTANCE]', function() {
        -      test('should return the same object', function() {
        -        v = myp5.createVector(0, 1);
        +  suite("rotate", function () {
        +    suite("p5.Vector.prototype.rotate() [INSTANCE]", function () {
        +      test("should return the same object", function () {
        +        v = new mockP5.Vector(0, 1);
                 expect(v.rotate(Math.PI)).to.eql(v);
               });
         
        -      suite('radians', function() {
        -        setup(function() {
        -          myp5.angleMode(RADIANS);
        +      suite.todo("radians", function () {
        +        beforeEach(function () {
        +          mockP5Prototype.angleMode(mockP5.RADIANS);
                 });
         
        -        test('should rotate the vector [0, 1, 0] by pi radians to [0, -1, 0]', function() {
        -          v = myp5.createVector(0, 1, 0);
        +        test("should rotate the vector [0, 1, 0] by pi radians to [0, -1, 0]", function () {
        +          v = mockP5Prototype.createVector(0, 1, 0);
                   v.rotate(Math.PI);
                   expect(v.x).to.be.closeTo(0, 0.01);
                   expect(v.y).to.be.closeTo(-1, 0.01);
                   expect(v.z).to.be.closeTo(0, 0.01);
                 });
         
        -        test('should rotate the vector [1, 0, 0] by -pi/2 radians to [0, -1, 0]', function() {
        -          v = myp5.createVector(1, 0, 0);
        +        test("should rotate the vector [1, 0, 0] by -pi/2 radians to [0, -1, 0]", function () {
        +          v = mockP5Prototype.createVector(1, 0, 0);
                   v.rotate(-Math.PI / 2);
                   expect(v.x).to.be.closeTo(0, 0.01);
                   expect(v.y).to.be.closeTo(-1, 0.01);
                   expect(v.z).to.be.closeTo(0, 0.01);
                 });
         
        -        test('should rotate the vector [1, 0, 0] by pi radians to [-1, 0, 0]', function() {
        -          v = myp5.createVector(1, 0, 0);
        +        test("should rotate the vector [1, 0, 0] by pi radians to [-1, 0, 0]", function () {
        +          v = mockP5Prototype.createVector(1, 0, 0);
                   v.rotate(Math.PI);
                   expect(v.x).to.be.closeTo(-1, 0.01);
                   expect(v.y).to.be.closeTo(0, 0.01);
        @@ -143,21 +142,21 @@ suite('p5.Vector', function() {
                 });
               });
         
        -      suite('degrees', function() {
        -        setup(function() {
        -          myp5.angleMode(DEGREES);
        +      suite.todo("degrees", function () {
        +        beforeEach(function () {
        +          mockP5Prototype.angleMode(mockP5.DEGREES);
                 });
         
        -        test('should rotate the vector [0, 1, 0] by 180 degrees to [0, -1, 0]', function() {
        -          v = myp5.createVector(0, 1, 0);
        +        test("should rotate the vector [0, 1, 0] by 180 degrees to [0, -1, 0]", function () {
        +          v = mockP5Prototype.createVector(0, 1, 0);
                   v.rotate(180);
                   expect(v.x).to.be.closeTo(0, 0.01);
                   expect(v.y).to.be.closeTo(-1, 0.01);
                   expect(v.z).to.be.closeTo(0, 0.01);
                 });
         
        -        test('should rotate the vector [1, 0, 0] by -90 degrees to [0, -1, 0]', function() {
        -          v = myp5.createVector(1, 0, 0);
        +        test("should rotate the vector [1, 0, 0] by -90 degrees to [0, -1, 0]", function () {
        +          v = mockP5Prototype.createVector(1, 0, 0);
                   v.rotate(-90);
                   expect(v.x).to.be.closeTo(0, 0.01);
                   expect(v.y).to.be.closeTo(-1, 0.01);
        @@ -166,30 +165,30 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('p5.Vector.rotate() [CLASS]', function() {
        -      setup(function() {
        -        myp5.angleMode(RADIANS);
        +    suite.todo("p5.Vector.rotate() [CLASS]", function () {
        +      beforeEach(function () {
        +        mockP5Prototype.angleMode(mockP5.RADIANS);
               });
         
        -      test('should not change the original object', function() {
        -        v = myp5.createVector(1, 0, 0);
        -        p5.Vector.rotate(v, Math.PI / 2);
        +      test("should not change the original object", function () {
        +        v = mockP5Prototype.createVector(1, 0, 0);
        +        mockP5.Vector.rotate(v, Math.PI / 2);
                 expect(v.x).to.equal(1);
                 expect(v.y).to.equal(0);
                 expect(v.z).to.equal(0);
               });
         
        -      test('should rotate the vector [0, 1, 0] by pi radians to [0, -1, 0]', function() {
        -        v = myp5.createVector(0, 1, 0);
        -        const v1 = p5.Vector.rotate(v, Math.PI);
        +      test("should rotate the vector [0, 1, 0] by pi radians to [0, -1, 0]", function () {
        +        v = mockP5Prototype.createVector(0, 1, 0);
        +        const v1 = mockP5.Vector.rotate(v, Math.PI);
                 expect(v1.x).to.be.closeTo(0, 0.01);
                 expect(v1.y).to.be.closeTo(-1, 0.01);
                 expect(v1.z).to.be.closeTo(0, 0.01);
               });
         
        -      test('should rotate the vector [1, 0, 0] by -pi/2 radians to [0, -1, 0]', function() {
        -        v = myp5.createVector(1, 0, 0);
        -        const v1 = p5.Vector.rotate(v, -Math.PI / 2);
        +      test("should rotate the vector [1, 0, 0] by -pi/2 radians to [0, -1, 0]", function () {
        +        v = mockP5Prototype.createVector(1, 0, 0);
        +        const v1 = mockP5.Vector.rotate(v, -Math.PI / 2);
                 expect(v1.x).to.be.closeTo(0, 0.01);
                 expect(v1.y).to.be.closeTo(-1, 0.01);
                 expect(v1.z).to.be.closeTo(0, 0.01);
        @@ -197,141 +196,147 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('angleBetween', function() {
        +  suite("angleBetween", function () {
             let v1, v2;
        -    setup(function() {
        -      v1 = new p5.Vector(1, 0, 0);
        -      v2 = new p5.Vector(2, 2, 0);
        +    beforeEach(function () {
        +      v1 = new mockP5.Vector(1, 0, 0);
        +      v2 = new mockP5.Vector(2, 2, 0);
             });
         
        -    suite('p5.Vector.prototype.angleBetween() [INSTANCE]', function() {
        -      test('should return a Number', function() {
        +    suite("p5.Vector.prototype.angleBetween() [INSTANCE]", function () {
        +      test("should return a Number", function () {
                 const res = v1.angleBetween(v2);
        -        expect(typeof res).to.eql('number');
        +        expect(typeof res).to.eql("number");
               });
         
        -      test('should not trip on rounding issues in 2D space', function() {
        -        v1 = new p5.Vector(-11, -20);
        -        v2 = new p5.Vector(-5.5, -10);
        -        const v3 = new p5.Vector(5.5, 10);
        +      test("should not trip on rounding issues in 2D space", function () {
        +        v1 = new mockP5.Vector(-11, -20);
        +        v2 = new mockP5.Vector(-5.5, -10);
        +        const v3 = new mockP5.Vector(5.5, 10);
         
                 expect(v1.angleBetween(v2)).to.be.closeTo(0, 0.00001);
                 expect(v1.angleBetween(v3)).to.be.closeTo(Math.PI, 0.00001);
               });
         
        -      test('should not trip on rounding issues in 3D space', function() {
        -        v1 = new p5.Vector(1, 1.1, 1.2);
        -        v2 = new p5.Vector(2, 2.2, 2.4);
        +      test("should not trip on rounding issues in 3D space", function () {
        +        v1 = new mockP5.Vector(1, 1.1, 1.2);
        +        v2 = new mockP5.Vector(2, 2.2, 2.4);
                 expect(v1.angleBetween(v2)).to.be.closeTo(0, 0.00001);
               });
         
        -      test('should return NaN for zero vector', function() {
        -        v1 = new p5.Vector(0, 0, 0);
        -        v2 = new p5.Vector(2, 3, 4);
        +      test("should return NaN for zero vector", function () {
        +        v1 = new mockP5.Vector(0, 0, 0);
        +        v2 = new mockP5.Vector(2, 3, 4);
                 expect(v1.angleBetween(v2)).to.be.NaN;
                 expect(v2.angleBetween(v1)).to.be.NaN;
               });
         
        -      test('between [1,0,0] and [1,0,0] should be 0 degrees', function() {
        -        myp5.angleMode(DEGREES);
        -        v1 = myp5.createVector(1, 0, 0);
        -        v2 = myp5.createVector(1, 0, 0);
        +      test.todo("between [1,0,0] and [1,0,0] should be 0 degrees", function () {
        +        mockP5Prototype.angleMode(mockP5.DEGREES);
        +        v1 = new mockP5.Vector(1, 0, 0);
        +        v2 = new mockP5.Vector(1, 0, 0);
                 expect(v1.angleBetween(v2)).to.equal(0);
               });
         
        -      test('between [0,3,0] and [0,-3,0] should be 180 degrees', function() {
        -        myp5.angleMode(DEGREES);
        -        v1 = myp5.createVector(0, 3, 0);
        -        v2 = myp5.createVector(0, -3, 0);
        -        expect(v1.angleBetween(v2)).to.be.closeTo(180, 0.01);
        -      });
        +      test.todo(
        +        "between [0,3,0] and [0,-3,0] should be 180 degrees",
        +        function () {
        +          mockP5Prototype.angleMode(mockP5.DEGREES);
        +          v1 = new mockP5.Vector(0, 3, 0);
        +          v2 = new mockP5.Vector(0, -3, 0);
        +          expect(v1.angleBetween(v2)).to.be.closeTo(180, 0.01);
        +        }
        +      );
         
        -      test('between [1,0,0] and [2,2,0] should be 1/4 PI radians', function() {
        -        v1 = new p5.Vector(1, 0, 0);
        -        v2 = new p5.Vector(2, 2, 0);
        +      test("between [1,0,0] and [2,2,0] should be 1/4 PI radians", function () {
        +        v1 = new mockP5.Vector(1, 0, 0);
        +        v2 = new mockP5.Vector(2, 2, 0);
                 expect(v1.angleBetween(v2)).to.be.closeTo(Math.PI / 4, 0.01);
        -        expect(v2.angleBetween(v1)).to.be.closeTo(-1 * Math.PI / 4, 0.01);
        +        expect(v2.angleBetween(v1)).to.be.closeTo((-1 * Math.PI) / 4, 0.01);
               });
         
        -      test('between [2,0,0] and [-2,0,0] should be PI radians', function() {
        -        v1 = new p5.Vector(2, 0, 0);
        -        v2 = new p5.Vector(-2, 0, 0);
        +      test("between [2,0,0] and [-2,0,0] should be PI radians", function () {
        +        v1 = new mockP5.Vector(2, 0, 0);
        +        v2 = new mockP5.Vector(-2, 0, 0);
                 expect(v1.angleBetween(v2)).to.be.closeTo(Math.PI, 0.01);
               });
         
        -      test('between [2,0,0] and [-2,-2,0] should be -3/4 PI radians  ', function() {
        -        v1 = new p5.Vector(2, 0, 0);
        -        v2 = new p5.Vector(-2, -2, 0);
        +      test("between [2,0,0] and [-2,-2,0] should be -3/4 PI radians  ", function () {
        +        v1 = new mockP5.Vector(2, 0, 0);
        +        v2 = new mockP5.Vector(-2, -2, 0);
                 expect(v1.angleBetween(v2)).to.be.closeTo(
                   -1 * (Math.PI / 2 + Math.PI / 4),
                   0.01
                 );
               });
         
        -      test('between [-2,-2,0] and [2,0,0] should be 3/4 PI radians', function() {
        -        v1 = new p5.Vector(-2, -2, 0);
        -        v2 = new p5.Vector(2, 0, 0);
        +      test("between [-2,-2,0] and [2,0,0] should be 3/4 PI radians", function () {
        +        v1 = new mockP5.Vector(-2, -2, 0);
        +        v2 = new mockP5.Vector(2, 0, 0);
                 expect(v1.angleBetween(v2)).to.be.closeTo(
                   Math.PI / 2 + Math.PI / 4,
                   0.01
                 );
               });
         
        -      test('For the same vectors, the angle between them should always be 0.', function() {
        -        v1 = myp5.createVector(288, 814);
        -        v2 = myp5.createVector(288, 814);
        +      test("For the same vectors, the angle between them should always be 0.", function () {
        +        v1 = new mockP5.Vector(288, 814);
        +        v2 = new mockP5.Vector(288, 814);
                 expect(v1.angleBetween(v2)).to.equal(0);
               });
         
        -      test('The angle between vectors pointing in opposite is always PI.', function() {
        -        v1 = myp5.createVector(219, 560);
        -        v2 = myp5.createVector(-219, -560);
        +      test("The angle between vectors pointing in opposite is always PI.", function () {
        +        v1 = new mockP5.Vector(219, 560);
        +        v2 = new mockP5.Vector(-219, -560);
                 expect(v1.angleBetween(v2)).to.be.closeTo(Math.PI, 0.0000001);
               });
             });
         
        -    suite('p5.Vector.angleBetween() [CLASS]', function() {
        -      test('should return NaN for zero vector', function() {
        -        v1 = new p5.Vector(0, 0, 0);
        -        v2 = new p5.Vector(2, 3, 4);
        -        expect(p5.Vector.angleBetween(v1, v2)).to.be.NaN;
        -        expect(p5.Vector.angleBetween(v2, v1)).to.be.NaN;
        +    suite("p5.Vector.angleBetween() [CLASS]", function () {
        +      test("should return NaN for zero vector", function () {
        +        v1 = new mockP5.Vector(0, 0, 0);
        +        v2 = new mockP5.Vector(2, 3, 4);
        +        expect(mockP5.Vector.angleBetween(v1, v2)).to.be.NaN;
        +        expect(mockP5.Vector.angleBetween(v2, v1)).to.be.NaN;
               });
         
        -      test('between [1,0,0] and [0,-1,0] should be -90 degrees', function() {
        -        myp5.angleMode(DEGREES);
        -        v1 = myp5.createVector(1, 0, 0);
        -        v2 = myp5.createVector(0, -1, 0);
        -        expect(p5.Vector.angleBetween(v1, v2)).to.be.closeTo(-90, 0.01);
        -      });
        +      test.todo(
        +        "between [1,0,0] and [0,-1,0] should be -90 degrees",
        +        function () {
        +          mockP5Prototype.angleMode(mockP5.DEGREES);
        +          v1 = new mockP5.Vector(1, 0, 0);
        +          v2 = new mockP5.Vector(0, -1, 0);
        +          expect(mockP5.Vector.angleBetween(v1, v2)).to.be.closeTo(-90, 0.01);
        +        }
        +      );
         
        -      test('between [0,3,0] and [0,-3,0] should be PI radians', function() {
        -        v1 = new p5.Vector(0, 3, 0);
        -        v2 = new p5.Vector(0, -3, 0);
        -        expect(p5.Vector.angleBetween(v1, v2)).to.be.closeTo(Math.PI, 0.01);
        +      test("between [0,3,0] and [0,-3,0] should be PI radians", function () {
        +        v1 = new mockP5.Vector(0, 3, 0);
        +        v2 = new mockP5.Vector(0, -3, 0);
        +        expect(mockP5.Vector.angleBetween(v1, v2)).to.be.closeTo(Math.PI, 0.01);
               });
             });
           });
         
        -  suite('set()', function() {
        -    suite('with p5.Vector', function() {
        -      test("should have x, y, z be initialized to the vector's x, y, z", function() {
        -        v.set(new p5.Vector(2, 5, 6));
        +  suite("set()", function () {
        +    suite("with p5.Vector", function () {
        +      test("should have x, y, z be initialized to the vector's x, y, z", function () {
        +        v.set(new mockP5.Vector(2, 5, 6));
                 expect(v.x).to.eql(2);
                 expect(v.y).to.eql(5);
                 expect(v.z).to.eql(6);
               });
             });
         
        -    suite('with Array', function() {
        -      test('[2,4] should set x === 2, y === 4, z === 0', function() {
        +    suite("with Array", function () {
        +      test("[2,4] should set x === 2, y === 4, z === 0", function () {
                 v.set([2, 4]);
                 expect(v.x).to.eql(2);
                 expect(v.y).to.eql(4);
                 expect(v.z).to.eql(0);
               });
         
        -      test("should have x, y, z be initialized to the array's 0,1,2 index", function() {
        +      test("should have x, y, z be initialized to the array's 0,1,2 index", function () {
                 v.set([2, 5, 6]);
                 expect(v.x).to.eql(2);
                 expect(v.y).to.eql(5);
        @@ -339,8 +344,8 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('set(1,2,3)', function() {
        -      test('should have x, y, z be initialized to the 1, 2, 3', function() {
        +    suite("set(1,2,3)", function () {
        +      test("should have x, y, z be initialized to the 1, 2, 3", function () {
                 v.set(1, 2, 3);
                 expect(v.x).to.eql(1);
                 expect(v.y).to.eql(2);
        @@ -349,18 +354,18 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('copy', function() {
        -    setup(function() {
        -      v = new p5.Vector(2, 3, 4);
        +  suite("copy", function () {
        +    beforeEach(function () {
        +      v = new mockP5.Vector(2, 3, 4);
             });
         
        -    suite('p5.Vector.prototype.copy() [INSTANCE]', function() {
        -      test('should not return the same instance', function() {
        +    suite("p5.Vector.prototype.copy() [INSTANCE]", function () {
        +      test("should not return the same instance", function () {
                 var newObject = v.copy();
                 expect(newObject).to.not.equal(v);
               });
         
        -      test("should return the calling object's x, y, z", function() {
        +      test("should return the calling object's x, y, z", function () {
                 var newObject = v.copy();
                 expect(newObject.x).to.eql(2);
                 expect(newObject.y).to.eql(3);
        @@ -368,14 +373,14 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('p5.Vector.copy() [CLASS]', function() {
        -      test('should not return the same instance', function() {
        -        var newObject = p5.Vector.copy(v);
        +    suite("p5.Vector.copy() [CLASS]", function () {
        +      test("should not return the same instance", function () {
        +        var newObject = mockP5.Vector.copy(v);
                 expect(newObject).to.not.equal(v);
               });
         
        -      test("should return the passed object's x, y, z", function() {
        -        var newObject = p5.Vector.copy(v);
        +      test("should return the passed object's x, y, z", function () {
        +        var newObject = mockP5.Vector.copy(v);
                 expect(newObject.x).to.eql(2);
                 expect(newObject.y).to.eql(3);
                 expect(newObject.z).to.eql(4);
        @@ -383,23 +388,23 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('add()', function() {
        -    setup(function() {
        -      v = new p5.Vector();
        +  suite("add()", function () {
        +    beforeEach(function () {
        +      v = new mockP5.Vector();
             });
         
        -    suite('with p5.Vector', function() {
        -      test('should add x, y, z  from the vector argument', function() {
        -        v.add(new p5.Vector(1, 5, 6));
        +    suite("with p5.Vector", function () {
        +      test("should add x, y, z  from the vector argument", function () {
        +        v.add(new mockP5.Vector(1, 5, 6));
                 expect(v.x).to.eql(1);
                 expect(v.y).to.eql(5);
                 expect(v.z).to.eql(6);
               });
             });
         
        -    suite('with Array', function() {
        -      suite('add([2, 4])', function() {
        -        test('should add the x and y components', function() {
        +    suite("with Array", function () {
        +      suite("add([2, 4])", function () {
        +        test("should add the x and y components", function () {
                   v.add([2, 4]);
                   expect(v.x).to.eql(2);
                   expect(v.y).to.eql(4);
        @@ -407,7 +412,7 @@ suite('p5.Vector', function() {
                 });
               });
         
        -      test("should add the array's 0,1,2 index", function() {
        +      test("should add the array's 0,1,2 index", function () {
                 v.add([2, 5, 6]);
                 expect(v.x).to.eql(2);
                 expect(v.y).to.eql(5);
        @@ -415,8 +420,8 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('add(3,5)', function() {
        -      test('should add the x and y components', function() {
        +    suite("add(3,5)", function () {
        +      test("should add the x and y components", function () {
                 v.add(3, 5);
                 expect(v.x).to.eql(3);
                 expect(v.y).to.eql(5);
        @@ -424,8 +429,8 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('add(2,3,4)', function() {
        -      test('should add the x, y, z components', function() {
        +    suite("add(2,3,4)", function () {
        +      test("should add the x, y, z components", function () {
                 v.add(5, 5, 5);
                 expect(v.x).to.eql(5);
                 expect(v.y).to.eql(5);
        @@ -433,20 +438,28 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('p5.Vector.add(v1, v2)', function() {
        +    suite("add(2,3,4)", function () {
        +      test("should add the x, y, z components", function () {
        +        v.add([1, 2, 3]);
        +        expect(v.x).to.eql(1);
        +        expect(v.y).to.eql(2);
        +      });
        +    });
        +
        +    suite("p5.Vector.add(v1, v2)", function () {
               var v1, v2, res;
        -      setup(function() {
        -        v1 = new p5.Vector(2, 0, 3);
        -        v2 = new p5.Vector(0, 1, 3);
        -        res = p5.Vector.add(v1, v2);
        +      beforeEach(function () {
        +        v1 = new mockP5.Vector(2, 0, 3);
        +        v2 = new mockP5.Vector(0, 1, 3);
        +        res = mockP5.Vector.add(v1, v2);
               });
         
        -      test('should return neither v1 nor v2', function() {
        +      test("should return neither v1 nor v2", function () {
                 expect(res).to.not.eql(v1);
                 expect(res).to.not.eql(v2);
               });
         
        -      test('should be sum of the two p5.Vectors', function() {
        +      test("should be sum of the two p5.Vectors", function () {
                 expect(res.x).to.eql(v1.x + v2.x);
                 expect(res.y).to.eql(v1.y + v2.y);
                 expect(res.z).to.eql(v1.z + v2.z);
        @@ -454,104 +467,104 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('rem()', function() {
        -    setup(function() {
        -      v = myp5.createVector(3, 4, 5);
        +  suite("rem()", function () {
        +    beforeEach(function () {
        +      v = new mockP5.Vector(3, 4, 5);
             });
         
        -    test('should give same vector if nothing passed as parameter', function() {
        +    test("should give same vector if nothing passed as parameter", function () {
               v.rem();
               expect(v.x).to.eql(3);
               expect(v.y).to.eql(4);
               expect(v.z).to.eql(5);
             });
         
        -    test('should give correct output if passed only one numeric value', function() {
        +    test("should give correct output if passed only one numeric value", function () {
               v.rem(2);
               expect(v.x).to.eql(1);
               expect(v.y).to.eql(0);
               expect(v.z).to.eql(1);
             });
         
        -    test('should give correct output if passed two numeric value', function() {
        +    test("should give correct output if passed two numeric value", function () {
               v.rem(2, 3);
               expect(v.x).to.eql(1);
               expect(v.y).to.eql(1);
               expect(v.z).to.eql(5);
             });
         
        -    test('should give correct output if passed three numeric value', function() {
        +    test("should give correct output if passed three numeric value", function () {
               v.rem(2, 3, 4);
               expect(v.x).to.eql(1);
               expect(v.y).to.eql(1);
               expect(v.z).to.eql(1);
             });
         
        -    suite('with p5.Vector', function() {
        -      test('should return correct output if only one component is non-zero', function() {
        -        v.rem(new p5.Vector(0, 0, 4));
        +    suite("with p5.Vector", function () {
        +      test("should return correct output if only one component is non-zero", function () {
        +        v.rem(new mockP5.Vector(0, 0, 4));
                 expect(v.x).to.eql(3);
                 expect(v.y).to.eql(4);
                 expect(v.z).to.eql(1);
               });
         
        -      test('should return correct output if x component is zero', () => {
        -        v.rem(new p5.Vector(0, 3, 4));
        +      test("should return correct output if x component is zero", () => {
        +        v.rem(new mockP5.Vector(0, 3, 4));
                 expect(v.x).to.eql(3);
                 expect(v.y).to.eql(1);
                 expect(v.z).to.eql(1);
               });
         
        -      test('should return correct output if all components are non-zero', () => {
        -        v.rem(new p5.Vector(2, 3, 4));
        +      test("should return correct output if all components are non-zero", () => {
        +        v.rem(new mockP5.Vector(2, 3, 4));
                 expect(v.x).to.eql(1);
                 expect(v.y).to.eql(1);
                 expect(v.z).to.eql(1);
               });
         
        -      test('should return same vector if all components are zero', () => {
        -        v.rem(new p5.Vector(0, 0, 0));
        +      test("should return same vector if all components are zero", () => {
        +        v.rem(new mockP5.Vector(0, 0, 0));
                 expect(v.x).to.eql(3);
                 expect(v.y).to.eql(4);
                 expect(v.z).to.eql(5);
               });
             });
         
        -    suite('with negative vectors', function() {
        +    suite("with negative vectors", function () {
               let v;
        -      setup(function() {
        -        v = new p5.Vector(-15, -5, -2);
        +      beforeEach(function () {
        +        v = new mockP5.Vector(-15, -5, -2);
               });
        -      test('should return correct output', () => {
        -        v.rem(new p5.Vector(2, 3, 3));
        +      test("should return correct output", () => {
        +        v.rem(new mockP5.Vector(2, 3, 3));
                 expect(v.x).to.eql(-1);
                 expect(v.y).to.eql(-2);
                 expect(v.z).to.eql(-2);
               });
             });
         
        -    suite('with Arrays', function() {
        -      test('should return remainder of vector components for 3D vector', function() {
        +    suite("with Arrays", function () {
        +      test("should return remainder of vector components for 3D vector", function () {
                 v.rem([2, 3, 0]);
                 expect(v.x).to.eql(1);
                 expect(v.y).to.eql(1);
                 expect(v.z).to.eql(5);
               });
        -      test('should return remainder of vector components for 2D vector', function() {
        +      test("should return remainder of vector components for 2D vector", function () {
                 v.rem([2, 3]);
                 expect(v.x).to.eql(1);
                 expect(v.y).to.eql(1);
                 expect(v.z).to.eql(5);
               });
         
        -      test('should return correct output if x,y components are zero for 2D vector', () => {
        +      test("should return correct output if x,y components are zero for 2D vector", () => {
                 v.rem([0, 0]);
                 expect(v.x).to.eql(3);
                 expect(v.y).to.eql(4);
                 expect(v.z).to.eql(5);
               });
         
        -      test('should return same vector if any vector component is non-finite number', () => {
        +      test("should return same vector if any vector component is non-finite number", () => {
                 v.rem([2, 3, Infinity]);
                 expect(v.x).to.eql(3);
                 expect(v.y).to.eql(4);
        @@ -559,20 +572,20 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('p5.Vector.rem(v1,v2)', function() {
        +    suite("p5.Vector.rem(v1,v2)", function () {
               let v1, v2, res;
        -      setup(function() {
        -        v1 = new p5.Vector(2, 3, 4);
        -        v2 = new p5.Vector(1, 2, 3);
        -        res = p5.Vector.rem(v1, v2);
        +      beforeEach(function () {
        +        v1 = new mockP5.Vector(2, 3, 4);
        +        v2 = new mockP5.Vector(1, 2, 3);
        +        res = mockP5.Vector.rem(v1, v2);
               });
         
        -      test('should return neither v1 nor v2', function() {
        +      test("should return neither v1 nor v2", function () {
                 expect(res).to.not.eql(v1);
                 expect(res).to.not.eql(v2);
               });
         
        -      test('should be v1 % v2', function() {
        +      test("should be v1 % v2", function () {
                 expect(res.x).to.eql(v1.x % v2.x);
                 expect(res.y).to.eql(v1.y % v2.y);
                 expect(res.z).to.eql(v1.z % v2.z);
        @@ -580,24 +593,24 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('sub()', function() {
        -    setup(function() {
        +  suite("sub()", function () {
        +    beforeEach(function () {
               v.x = 0;
               v.y = 0;
               v.z = 0;
             });
        -    suite('with p5.Vector', function() {
        -      test('should sub x, y, z  from the vector argument', function() {
        -        v.sub(new p5.Vector(2, 5, 6));
        +    suite("with p5.Vector", function () {
        +      test("should sub x, y, z  from the vector argument", function () {
        +        v.sub(new mockP5.Vector(2, 5, 6));
                 expect(v.x).to.eql(-2);
                 expect(v.y).to.eql(-5);
                 expect(v.z).to.eql(-6);
               });
             });
         
        -    suite('with Array', function() {
        -      suite('sub([2, 4])', function() {
        -        test('should sub the x and y components', function() {
        +    suite("with Array", function () {
        +      suite("sub([2, 4])", function () {
        +        test("should sub the x and y components", function () {
                   v.sub([2, 4]);
                   expect(v.x).to.eql(-2);
                   expect(v.y).to.eql(-4);
        @@ -605,7 +618,7 @@ suite('p5.Vector', function() {
                 });
               });
         
        -      test("should subtract from the array's 0,1,2 index", function() {
        +      test("should subtract from the array's 0,1,2 index", function () {
                 v.sub([2, 5, 6]);
                 expect(v.x).to.eql(-2);
                 expect(v.y).to.eql(-5);
        @@ -613,8 +626,8 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('sub(3,5)', function() {
        -      test('should subtract the x and y components', function() {
        +    suite("sub(3,5)", function () {
        +      test("should subtract the x and y components", function () {
                 v.sub(3, 5);
                 expect(v.x).to.eql(-3);
                 expect(v.y).to.eql(-5);
        @@ -622,8 +635,8 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('sub(2,3,4)', function() {
        -      test('should subtract the x, y, z components', function() {
        +    suite("sub(2,3,4)", function () {
        +      test("should subtract the x, y, z components", function () {
                 v.sub(5, 5, 5);
                 expect(v.x).to.eql(-5);
                 expect(v.y).to.eql(-5);
        @@ -631,20 +644,20 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('p5.Vector.sub(v1, v2)', function() {
        +    suite("p5.Vector.sub(v1, v2)", function () {
               var v1, v2, res;
        -      setup(function() {
        -        v1 = new p5.Vector(2, 0, 3);
        -        v2 = new p5.Vector(0, 1, 3);
        -        res = p5.Vector.sub(v1, v2);
        +      beforeEach(function () {
        +        v1 = new mockP5.Vector(2, 0, 3);
        +        v2 = new mockP5.Vector(0, 1, 3);
        +        res = mockP5.Vector.sub(v1, v2);
               });
         
        -      test('should return neither v1 nor v2', function() {
        +      test("should return neither v1 nor v2", function () {
                 expect(res).to.not.eql(v1);
                 expect(res).to.not.eql(v2);
               });
         
        -      test('should be v1 - v2', function() {
        +      test("should be v1 - v2", function () {
                 expect(res.x).to.eql(v1.x - v2.x);
                 expect(res.y).to.eql(v1.y - v2.y);
                 expect(res.z).to.eql(v1.z - v2.z);
        @@ -652,31 +665,31 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('mult()', function() {
        -    setup(function() {
        -      v = new p5.Vector(1, 1, 1);
        +  suite("mult()", function () {
        +    beforeEach(function () {
        +      v = new mockP5.Vector(1, 1, 1);
             });
         
        -    test('should return the same object', function() {
        +    test("should return the same object", function () {
               expect(v.mult(1)).to.eql(v);
             });
         
        -    test('should not change x, y, z if no argument is given', function() {
        +    test("should not change x, y, z if no argument is given", function () {
               v.mult();
               expect(v.x).to.eql(1);
               expect(v.y).to.eql(1);
               expect(v.z).to.eql(1);
             });
         
        -    test('should not change x, y, z if n is not a finite number', function() {
        +    test("should not change x, y, z if n is not a finite number", function () {
               v.mult(NaN);
               expect(v.x).to.eql(1);
               expect(v.y).to.eql(1);
               expect(v.z).to.eql(1);
             });
         
        -    suite('with scalar', function() {
        -      test('multiply the x, y, z with the scalar', function() {
        +    suite("with scalar", function () {
        +      test("multiply the x, y, z with the scalar", function () {
                 v.mult(2);
                 expect(v.x).to.eql(2);
                 expect(v.y).to.eql(2);
        @@ -684,78 +697,78 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('v0.mult(v1)', function() {
        +    suite("v0.mult(v1)", function () {
               var v0, v1;
        -      setup(function() {
        -        v0 = new p5.Vector(1, 2, 3);
        -        v1 = new p5.Vector(2, 3, 4);
        +      beforeEach(function () {
        +        v0 = new mockP5.Vector(1, 2, 3);
        +        v1 = new mockP5.Vector(2, 3, 4);
                 v0.mult(v1);
               });
         
        -      test('should do component wise multiplication', function() {
        +      test("should do component wise multiplication", function () {
                 expect(v0.x).to.eql(2);
                 expect(v0.y).to.eql(6);
                 expect(v0.z).to.eql(12);
               });
             });
         
        -    suite('v0.mult(arr)', function() {
        +    suite("v0.mult(arr)", function () {
               var v0, arr;
        -      setup(function() {
        -        v0 = new p5.Vector(1, 2, 3);
        +      beforeEach(function () {
        +        v0 = new mockP5.Vector(1, 2, 3);
                 arr = [2, 3, 4];
                 v0.mult(arr);
               });
         
        -      test('should do component wise multiplication from an array', function() {
        +      test("should do component wise multiplication from an array", function () {
                 expect(v0.x).to.eql(2);
                 expect(v0.y).to.eql(6);
                 expect(v0.z).to.eql(12);
               });
             });
         
        -    suite('p5.Vector.mult(v, n)', function() {
        +    suite("p5.Vector.mult(v, n)", function () {
               var v, res;
        -      setup(function() {
        -        v = new p5.Vector(1, 2, 3);
        -        res = p5.Vector.mult(v, 4);
        +      beforeEach(function () {
        +        v = new mockP5.Vector(1, 2, 3);
        +        res = mockP5.Vector.mult(v, 4);
               });
         
        -      test('should return a new p5.Vector', function() {
        +      test("should return a new p5.Vector", function () {
                 expect(res).to.not.eql(v);
               });
         
        -      test('should multiply the scalar', function() {
        +      test("should multiply the scalar", function () {
                 expect(res.x).to.eql(4);
                 expect(res.y).to.eql(8);
                 expect(res.z).to.eql(12);
               });
             });
         
        -    suite('p5.Vector.mult(v, v', function() {
        +    suite("p5.Vector.mult(v, v", function () {
               var v0, v1, res;
        -      setup(function() {
        -        v0 = new p5.Vector(1, 2, 3);
        -        v1 = new p5.Vector(2, 3, 4);
        -        res = p5.Vector.mult(v0, v1);
        +      beforeEach(function () {
        +        v0 = new mockP5.Vector(1, 2, 3);
        +        v1 = new mockP5.Vector(2, 3, 4);
        +        res = mockP5.Vector.mult(v0, v1);
               });
         
        -      test('should return new vector from component wise multiplication', function() {
        +      test("should return new vector from component wise multiplication", function () {
                 expect(res.x).to.eql(2);
                 expect(res.y).to.eql(6);
                 expect(res.z).to.eql(12);
               });
             });
         
        -    suite('p5.Vector.mult(v, arr', function() {
        +    suite("p5.Vector.mult(v, arr", function () {
               var v0, arr, res;
        -      setup(function() {
        -        v0 = new p5.Vector(1, 2, 3);
        +      beforeEach(function () {
        +        v0 = new mockP5.Vector(1, 2, 3);
                 arr = [2, 3, 4];
        -        res = p5.Vector.mult(v0, arr);
        +        res = mockP5.Vector.mult(v0, arr);
               });
         
        -      test('should return new vector from component wise multiplication with an array', function() {
        +      test("should return new vector from component wise multiplication with an array", function () {
                 expect(res.x).to.eql(2);
                 expect(res.y).to.eql(6);
                 expect(res.z).to.eql(12);
        @@ -763,38 +776,38 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('div()', function() {
        -    setup(function() {
        -      v = new p5.Vector(1, 1, 1);
        +  suite("div()", function () {
        +    beforeEach(function () {
        +      v = new mockP5.Vector(1, 1, 1);
             });
         
        -    test('should return the same object', function() {
        +    test("should return the same object", function () {
               expect(v.div(1)).to.eql(v);
             });
         
        -    test('should not change x, y, z if no argument is given', function() {
        +    test("should not change x, y, z if no argument is given", function () {
               v.div();
               expect(v.x).to.eql(1);
               expect(v.y).to.eql(1);
               expect(v.z).to.eql(1);
             });
         
        -    test('should not change x, y, z if n is not a finite number', function() {
        +    test("should not change x, y, z if n is not a finite number", function () {
               v.div(NaN);
               expect(v.x).to.eql(1);
               expect(v.y).to.eql(1);
               expect(v.z).to.eql(1);
             });
         
        -    suite('with scalar', function() {
        -      test('divide the x, y, z with the scalar', function() {
        +    suite("with scalar", function () {
        +      test("divide the x, y, z with the scalar", function () {
                 v.div(2);
                 expect(v.x).to.be.closeTo(0.5, 0.01);
                 expect(v.y).to.be.closeTo(0.5, 0.01);
                 expect(v.z).to.be.closeTo(0.5, 0.01);
               });
         
        -      test('should not change x, y, z if n is 0', function() {
        +      test("should not change x, y, z if n is 0", function () {
                 v.div(0);
                 expect(v.x).to.eql(1);
                 expect(v.y).to.eql(1);
        @@ -802,73 +815,73 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('p5.Vector.div(v, n)', function() {
        +    suite("p5.Vector.div(v, n)", function () {
               var v, res;
        -      setup(function() {
        -        v = new p5.Vector(1, 1, 1);
        -        res = p5.Vector.div(v, 4);
        +      beforeEach(function () {
        +        v = new mockP5.Vector(1, 1, 1);
        +        res = mockP5.Vector.div(v, 4);
               });
         
        -      test('should not be undefined', function() {
        +      test("should not be undefined", function () {
                 expect(res).to.not.eql(undefined);
               });
         
        -      test('should return a new p5.Vector', function() {
        +      test("should return a new p5.Vector", function () {
                 expect(res).to.not.eql(v);
               });
         
        -      test('should divide the scalar', function() {
        +      test("should divide the scalar", function () {
                 expect(res.x).to.eql(0.25);
                 expect(res.y).to.eql(0.25);
                 expect(res.z).to.eql(0.25);
               });
             });
         
        -    suite('v0.div(v1)', function() {
        +    suite("v0.div(v1)", function () {
               var v0, v1, v2, v3;
        -      setup(function() {
        -        v0 = new p5.Vector(2, 6, 9);
        -        v1 = new p5.Vector(2, 2, 3);
        -        v2 = new p5.Vector(1, 1, 1);
        -        v3 = new p5.Vector(0, 0, 0);
        +      beforeEach(function () {
        +        v0 = new mockP5.Vector(2, 6, 9);
        +        v1 = new mockP5.Vector(2, 2, 3);
        +        v2 = new mockP5.Vector(1, 1, 1);
        +        v3 = new mockP5.Vector(0, 0, 0);
         
                 v0.div(v1);
               });
         
        -      test('should do component wise division', function() {
        +      test("should do component wise division", function () {
                 expect(v0.x).to.eql(1);
                 expect(v0.y).to.eql(3);
                 expect(v0.z).to.eql(3);
               });
         
        -      test('should not change x, y, z if v3 is all 0', function() {
        +      test("should not change x, y, z if v3 is all 0", function () {
                 v2.div(v3);
                 expect(v2.x).to.eql(1);
                 expect(v2.y).to.eql(1);
                 expect(v2.z).to.eql(1);
               });
         
        -      test('should work on 2D vectors', function() {
        -        const v = new p5.Vector(1, 1);
        -        const divisor = new p5.Vector(2, 2);
        +      test("should work on 2D vectors", function () {
        +        const v = new mockP5.Vector(1, 1);
        +        const divisor = new mockP5.Vector(2, 2);
                 v.div(divisor);
                 expect(v.x).to.eql(0.5);
                 expect(v.y).to.eql(0.5);
                 expect(v.z).to.eql(0);
               });
         
        -      test('should work when the dividend has 0', function() {
        -        const v = new p5.Vector(1, 0);
        -        const divisor = new p5.Vector(2, 2);
        +      test("should work when the dividend has 0", function () {
        +        const v = new mockP5.Vector(1, 0);
        +        const divisor = new mockP5.Vector(2, 2);
                 v.div(divisor);
                 expect(v.x).to.eql(0.5);
                 expect(v.y).to.eql(0);
                 expect(v.z).to.eql(0);
               });
         
        -      test('should do nothing when the divisor has 0', function() {
        -        const v = new p5.Vector(1, 1);
        -        const divisor = new p5.Vector(0, 2);
        +      test("should do nothing when the divisor has 0", function () {
        +        const v = new mockP5.Vector(1, 1);
        +        const divisor = new mockP5.Vector(0, 2);
                 v.div(divisor);
                 expect(v.x).to.eql(1);
                 expect(v.y).to.eql(1);
        @@ -876,22 +889,22 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('v0.div(arr)', function() {
        +    suite("v0.div(arr)", function () {
               var v0, v1, arr;
        -      setup(function() {
        -        v0 = new p5.Vector(2, 6, 9);
        -        v1 = new p5.Vector(1, 1, 1);
        +      beforeEach(function () {
        +        v0 = new mockP5.Vector(2, 6, 9);
        +        v1 = new mockP5.Vector(1, 1, 1);
                 arr = [2, 2, 3];
                 v0.div(arr);
               });
         
        -      test('should do component wise division with an array', function() {
        +      test("should do component wise division with an array", function () {
                 expect(v0.x).to.eql(1);
                 expect(v0.y).to.eql(3);
                 expect(v0.z).to.eql(3);
               });
         
        -      test('should not change x, y, z if array contains 0', function() {
        +      test("should not change x, y, z if array contains 0", function () {
                 v1.div([0, 0, 0]);
                 expect(v1.x).to.eql(1);
                 expect(v1.y).to.eql(1);
        @@ -899,30 +912,30 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('p5.Vector.div(v, v', function() {
        +    suite("p5.Vector.div(v, v", function () {
               var v0, v1, res;
        -      setup(function() {
        -        v0 = new p5.Vector(2, 6, 9);
        -        v1 = new p5.Vector(2, 2, 3);
        -        res = p5.Vector.div(v0, v1);
        +      beforeEach(function () {
        +        v0 = new mockP5.Vector(2, 6, 9);
        +        v1 = new mockP5.Vector(2, 2, 3);
        +        res = mockP5.Vector.div(v0, v1);
               });
         
        -      test('should return new vector from component wise division', function() {
        +      test("should return new vector from component wise division", function () {
                 expect(res.x).to.eql(1);
                 expect(res.y).to.eql(3);
                 expect(res.z).to.eql(3);
               });
             });
         
        -    suite('p5.Vector.div(v, arr', function() {
        +    suite("p5.Vector.div(v, arr", function () {
               var v0, arr, res;
        -      setup(function() {
        -        v0 = new p5.Vector(2, 6, 9);
        +      beforeEach(function () {
        +        v0 = new mockP5.Vector(2, 6, 9);
                 arr = [2, 2, 3];
        -        res = p5.Vector.div(v0, arr);
        +        res = mockP5.Vector.div(v0, arr);
               });
         
        -      test('should return new vector from component wise division with an array', function() {
        +      test("should return new vector from component wise division with an array", function () {
                 expect(res.x).to.eql(1);
                 expect(res.y).to.eql(3);
                 expect(res.z).to.eql(3);
        @@ -930,90 +943,90 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('dot', function() {
        -    setup(function() {
        +  suite("dot", function () {
        +    beforeEach(function () {
               v.x = 1;
               v.y = 1;
               v.z = 1;
             });
         
        -    test('should return a number', function() {
        -      expect(typeof v.dot(new p5.Vector()) === 'number').to.eql(true);
        +    test("should return a number", function () {
        +      expect(typeof v.dot(new mockP5.Vector()) === "number").to.eql(true);
             });
         
        -    suite('with p5.Vector', function() {
        -      test('should be the dot product of the vector', function() {
        -        expect(v.dot(new p5.Vector(2, 2))).to.eql(4);
        +    suite("with p5.Vector", function () {
        +      test("should be the dot product of the vector", function () {
        +        expect(v.dot(new mockP5.Vector(2, 2))).to.eql(4);
               });
             });
         
        -    suite('with x, y, z', function() {
        -      test('should be the dot product with x, y', function() {
        +    suite("with x, y, z", function () {
        +      test("should be the dot product with x, y", function () {
                 expect(v.dot(2, 2)).to.eql(4);
               });
         
        -      test('should be the dot product with x, y, z', function() {
        +      test("should be the dot product with x, y, z", function () {
                 expect(v.dot(2, 2, 2)).to.eql(6);
               });
             });
         
        -    suite('p5.Vector.dot(v, n)', function() {
        +    suite("p5.Vector.dot(v, n)", function () {
               var v1, v2, res;
        -      setup(function() {
        -        v1 = new p5.Vector(1, 1, 1);
        -        v2 = new p5.Vector(2, 3, 4);
        -        res = p5.Vector.dot(v1, v2);
        +      beforeEach(function () {
        +        v1 = new mockP5.Vector(1, 1, 1);
        +        v2 = new mockP5.Vector(2, 3, 4);
        +        res = mockP5.Vector.dot(v1, v2);
               });
         
        -      test('should return a number', function() {
        -        expect(typeof res === 'number').to.eql(true);
        +      test("should return a number", function () {
        +        expect(typeof res === "number").to.eql(true);
               });
         
        -      test('should be the dot product of the two vectors', function() {
        +      test("should be the dot product of the two vectors", function () {
                 expect(res).to.eql(9);
               });
             });
           });
         
        -  suite('cross', function() {
        +  suite("cross", function () {
             var res;
        -    setup(function() {
        +    beforeEach(function () {
               v.x = 1;
               v.y = 1;
               v.z = 1;
             });
         
        -    test('should return a new product', function() {
        -      expect(v.cross(new p5.Vector())).to.not.eql(v);
        +    test("should return a new product", function () {
        +      expect(v.cross(new mockP5.Vector())).to.not.eql(v);
             });
         
        -    suite('with p5.Vector', function() {
        -      test('should cross x, y, z  from the vector argument', function() {
        -        res = v.cross(new p5.Vector(2, 5, 6));
        +    suite("with p5.Vector", function () {
        +      test("should cross x, y, z  from the vector argument", function () {
        +        res = v.cross(new mockP5.Vector(2, 5, 6));
                 expect(res.x).to.eql(1); //this.y * v.z - this.z * v.y
                 expect(res.y).to.eql(-4); //this.z * v.x - this.x * v.z
                 expect(res.z).to.eql(3); //this.x * v.y - this.y * v.x
               });
             });
         
        -    suite('p5.Vector.cross(v1, v2)', function() {
        +    suite("p5.Vector.cross(v1, v2)", function () {
               var v1, v2, res;
        -      setup(function() {
        -        v1 = new p5.Vector(3, 6, 9);
        -        v2 = new p5.Vector(1, 1, 1);
        -        res = p5.Vector.cross(v1, v2);
        +      beforeEach(function () {
        +        v1 = new mockP5.Vector(3, 6, 9);
        +        v2 = new mockP5.Vector(1, 1, 1);
        +        res = mockP5.Vector.cross(v1, v2);
               });
         
        -      test('should not be undefined', function() {
        +      test("should not be undefined", function () {
                 expect(res).to.not.eql(undefined);
               });
         
        -      test('should return neither v1 nor v2', function() {
        +      test("should return neither v1 nor v2", function () {
                 expect(res).to.not.eql(v1);
                 expect(res).to.not.eql(v2);
               });
         
        -      test('should the cross product of v1 and v2', function() {
        +      test("should the cross product of v1 and v2", function () {
                 expect(res.x).to.eql(-3);
                 expect(res.y).to.eql(6);
                 expect(res.z).to.eql(-3);
        @@ -1021,60 +1034,58 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('dist', function() {
        +  suite("dist", function () {
             var b, c;
        -    setup(function() {
        -      v.x = 0;
        -      v.y = 0;
        -      v.z = 1;
        -      b = new p5.Vector(0, 0, 5);
        -      c = new p5.Vector(3, 4, 1);
        +    beforeEach(function () {
        +      v = new mockP5.Vector(0, 0, 1);
        +      b = new mockP5.Vector(0, 0, 5);
        +      c = new mockP5.Vector(3, 4, 1);
             });
         
        -    test('should return a number', function() {
        -      expect(typeof v.dist(b) === 'number').to.eql(true);
        +    test("should return a number", function () {
        +      expect(typeof v.dist(b) === "number").to.eql(true);
             });
         
        -    test('should return distance between two vectors', function() {
        +    test("should return distance between two vectors", function () {
               expect(v.dist(b)).to.eql(4);
             });
         
        -    test('should return distance between two vectors', function() {
        +    test("should return distance between two vectors", function () {
               expect(v.dist(c)).to.eql(5);
             });
         
        -    test('should be commutative', function() {
        +    test("should be commutative", function () {
               expect(b.dist(c)).to.eql(c.dist(b));
             });
           });
         
        -  suite('p5.Vector.dist(v1, v2)', function() {
        +  suite("p5.Vector.dist(v1, v2)", function () {
             var v1, v2;
        -    setup(function() {
        -      v1 = new p5.Vector(0, 0, 0);
        -      v2 = new p5.Vector(0, 3, 4);
        +    beforeEach(function () {
        +      v1 = new mockP5.Vector(0, 0, 0);
        +      v2 = new mockP5.Vector(0, 3, 4);
             });
         
        -    test('should return a number', function() {
        -      expect(typeof p5.Vector.dist(v1, v2) === 'number').to.eql(true);
        +    test("should return a number", function () {
        +      expect(typeof mockP5.Vector.dist(v1, v2) === "number").to.eql(true);
             });
         
        -    test('should be commutative', function() {
        -      expect(p5.Vector.dist(v1, v2)).to.eql(p5.Vector.dist(v2, v1));
        +    test("should be commutative", function () {
        +      expect(mockP5.Vector.dist(v1, v2)).to.eql(mockP5.Vector.dist(v2, v1));
             });
           });
         
        -  suite('normalize', function() {
        -    suite('p5.Vector.prototype.normalize() [INSTANCE]', function() {
        -      setup(function() {
        -        v = myp5.createVector(1, 1, 1);
        +  suite("normalize", function () {
        +    suite("p5.Vector.prototype.normalize() [INSTANCE]", function () {
        +      beforeEach(function () {
        +        v = new mockP5.Vector(1, 1, 1);
               });
         
        -      test('should return the same object', function() {
        +      test("should return the same object", function () {
                 expect(v.normalize()).to.eql(v);
               });
         
        -      test('unit vector should not change values', function() {
        +      test("unit vector should not change values", function () {
                 v.x = 1;
                 v.y = 0;
                 v.z = 0;
        @@ -1084,7 +1095,7 @@ suite('p5.Vector', function() {
                 expect(v.z).to.eql(0);
               });
         
        -      test('2,2,1 should normalize to ~0.66,0.66,0.33', function() {
        +      test("2,2,1 should normalize to ~0.66,0.66,0.33", function () {
                 v.x = 2;
                 v.y = 2;
                 v.z = 1;
        @@ -1095,32 +1106,32 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('p5.Vector.normalize(v) [CLASS]', function() {
        +    suite("p5.Vector.normalize(v) [CLASS]", function () {
               var res;
        -      setup(function() {
        -        v = myp5.createVector(1, 0, 0);
        -        res = p5.Vector.normalize(v);
        +      beforeEach(function () {
        +        v = new mockP5.Vector(1, 0, 0);
        +        res = mockP5.Vector.normalize(v);
               });
         
        -      test('should not be undefined', function() {
        +      test("should not be undefined", function () {
                 expect(res).to.not.eql(undefined);
               });
         
        -      test('should not return same object v', function() {
        +      test("should not return same object v", function () {
                 expect(res).to.not.equal(v);
               });
         
        -      test('unit vector 1,0,0 should normalize to 1,0,0', function() {
        +      test("unit vector 1,0,0 should normalize to 1,0,0", function () {
                 expect(res.x).to.eql(1);
                 expect(res.y).to.eql(0);
                 expect(res.z).to.eql(0);
               });
         
        -      test('2,2,1 should normalize to ~0.66,0.66,0.33', function() {
        +      test("2,2,1 should normalize to ~0.66,0.66,0.33", function () {
                 v.x = 2;
                 v.y = 2;
                 v.z = 1;
        -        res = p5.Vector.normalize(v);
        +        res = mockP5.Vector.normalize(v);
                 expect(res.x).to.be.closeTo(0.6666, 0.01);
                 expect(res.y).to.be.closeTo(0.6666, 0.01);
                 expect(res.z).to.be.closeTo(0.3333, 0.01);
        @@ -1128,20 +1139,20 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('limit', function() {
        +  suite("limit", function () {
             let v;
         
        -    setup(function() {
        -      v = new p5.Vector(5, 5, 5);
        +    beforeEach(function () {
        +      v = new mockP5.Vector(5, 5, 5);
             });
         
        -    suite('p5.Vector.prototype.limit() [INSTANCE]', function() {
        -      test('should return the same object', function() {
        +    suite("p5.Vector.prototype.limit() [INSTANCE]", function () {
        +      test("should return the same object", function () {
                 expect(v.limit()).to.equal(v);
               });
         
        -      suite('with a vector larger than the limit', function() {
        -        test('should limit the vector', function() {
        +      suite("with a vector larger than the limit", function () {
        +        test("should limit the vector", function () {
                   v.limit(1);
                   expect(v.x).to.be.closeTo(0.5773, 0.01);
                   expect(v.y).to.be.closeTo(0.5773, 0.01);
        @@ -1149,8 +1160,8 @@ suite('p5.Vector', function() {
                 });
               });
         
        -      suite('with a vector smaller than the limit', function() {
        -        test('should not limit the vector', function() {
        +      suite("with a vector smaller than the limit", function () {
        +        test("should not limit the vector", function () {
                   v.limit(8.67);
                   expect(v.x).to.eql(5);
                   expect(v.y).to.eql(5);
        @@ -1159,33 +1170,33 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('p5.Vector.limit() [CLASS]', function() {
        -      test('should not return the same object', function() {
        -        expect(p5.Vector.limit(v)).to.not.equal(v);
        +    suite("p5.Vector.limit() [CLASS]", function () {
        +      test("should not return the same object", function () {
        +        expect(mockP5.Vector.limit(v)).to.not.equal(v);
               });
         
        -      suite('with a vector larger than the limit', function() {
        -        test('should limit the vector', function() {
        -          const res = p5.Vector.limit(v, 1);
        +      suite("with a vector larger than the limit", function () {
        +        test("should limit the vector", function () {
        +          const res = mockP5.Vector.limit(v, 1);
                   expect(res.x).to.be.closeTo(0.5773, 0.01);
                   expect(res.y).to.be.closeTo(0.5773, 0.01);
                   expect(res.z).to.be.closeTo(0.5773, 0.01);
                 });
               });
         
        -      suite('with a vector smaller than the limit', function() {
        -        test('should not limit the vector', function() {
        -          const res = p5.Vector.limit(v, 8.67);
        +      suite("with a vector smaller than the limit", function () {
        +        test("should not limit the vector", function () {
        +          const res = mockP5.Vector.limit(v, 8.67);
                   expect(res.x).to.eql(5);
                   expect(res.y).to.eql(5);
                   expect(res.z).to.eql(5);
                 });
               });
         
        -      suite('when given a target vector', function() {
        -        test('should store limited vector in the target', function() {
        -          const target = new p5.Vector(0, 0, 0);
        -          p5.Vector.limit(v, 1, target);
        +      suite("when given a target vector", function () {
        +        test("should store limited vector in the target", function () {
        +          const target = new mockP5.Vector(0, 0, 0);
        +          mockP5.Vector.limit(v, 1, target);
                   expect(target.x).to.be.closeTo(0.5773, 0.01);
                   expect(target.y).to.be.closeTo(0.5773, 0.01);
                   expect(target.z).to.be.closeTo(0.5773, 0.01);
        @@ -1194,26 +1205,26 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('setMag', function() {
        +  suite("setMag", function () {
             let v;
         
        -    setup(function() {
        -      v = new p5.Vector(1, 0, 0);
        +    beforeEach(function () {
        +      v = new mockP5.Vector(1, 0, 0);
             });
         
        -    suite('p5.Vector.setMag() [INSTANCE]', function() {
        -      test('should return the same object', function() {
        +    suite("p5.Vector.setMag() [INSTANCE]", function () {
        +      test("should return the same object", function () {
                 expect(v.setMag(2)).to.equal(v);
               });
         
        -      test('should set the magnitude of the vector', function() {
        +      test("should set the magnitude of the vector", function () {
                 v.setMag(4);
                 expect(v.x).to.eql(4);
                 expect(v.y).to.eql(0);
                 expect(v.z).to.eql(0);
               });
         
        -      test('should set the magnitude of the vector', function() {
        +      test("should set the magnitude of the vector", function () {
                 v.x = 2;
                 v.y = 3;
                 v.z = -6;
        @@ -1224,32 +1235,32 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('p5.Vector.prototype.setMag() [CLASS]', function() {
        -      test('should not return the same object', function() {
        -        expect(p5.Vector.setMag(v, 2)).to.not.equal(v);
        +    suite("p5.Vector.prototype.setMag() [CLASS]", function () {
        +      test("should not return the same object", function () {
        +        expect(mockP5.Vector.setMag(v, 2)).to.not.equal(v);
               });
         
        -      test('should set the magnitude of the vector', function() {
        -        const res = p5.Vector.setMag(v, 4);
        +      test("should set the magnitude of the vector", function () {
        +        const res = mockP5.Vector.setMag(v, 4);
                 expect(res.x).to.eql(4);
                 expect(res.y).to.eql(0);
                 expect(res.z).to.eql(0);
               });
         
        -      test('should set the magnitude of the vector', function() {
        +      test("should set the magnitude of the vector", function () {
                 v.x = 2;
                 v.y = 3;
                 v.z = -6;
        -        const res = p5.Vector.setMag(v, 14);
        +        const res = mockP5.Vector.setMag(v, 14);
                 expect(res.x).to.eql(4);
                 expect(res.y).to.eql(6);
                 expect(res.z).to.eql(-12);
               });
         
        -      suite('when given a target vector', function() {
        -        test('should set the magnitude on the target', function() {
        -          const target = new p5.Vector(0, 1, 0);
        -          const res = p5.Vector.setMag(v, 4, target);
        +      suite("when given a target vector", function () {
        +        test("should set the magnitude on the target", function () {
        +          const target = new mockP5.Vector(0, 1, 0);
        +          const res = mockP5.Vector.setMag(v, 4, target);
                   expect(target).to.equal(res);
                   expect(target.x).to.eql(4);
                   expect(target.y).to.eql(0);
        @@ -1259,57 +1270,57 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('heading', function() {
        -    setup(function() {
        -      v = myp5.createVector();
        +  suite("heading", function () {
        +    beforeEach(function () {
        +      v = new mockP5.Vector();
             });
         
        -    suite('p5.Vector.prototype.heading() [INSTANCE]', function() {
        -      test('should return a number', function() {
        -        expect(typeof v.heading() === 'number').to.eql(true);
        +    suite("p5.Vector.prototype.heading() [INSTANCE]", function () {
        +      test("should return a number", function () {
        +        expect(typeof v.heading() === "number").to.eql(true);
               });
         
        -      test('heading for vector pointing right is 0', function() {
        +      test("heading for vector pointing right is 0", function () {
                 v.x = 1;
                 v.y = 0;
                 v.z = 0;
                 expect(v.heading()).to.be.closeTo(0, 0.01);
               });
         
        -      test('heading for vector pointing down is PI/2', function() {
        +      test("heading for vector pointing down is PI/2", function () {
                 v.x = 0;
                 v.y = 1;
                 v.z = 0;
                 expect(v.heading()).to.be.closeTo(Math.PI / 2, 0.01);
               });
         
        -      test('heading for vector pointing left is PI', function() {
        +      test("heading for vector pointing left is PI", function () {
                 v.x = -1;
                 v.y = 0;
                 v.z = 0;
                 expect(v.heading()).to.be.closeTo(Math.PI, 0.01);
               });
         
        -      suite('with `angleMode(DEGREES)`', function() {
        -        setup(function() {
        -          myp5.angleMode(DEGREES);
        +      suite.todo("with `angleMode(DEGREES)`", function () {
        +        beforeEach(function () {
        +          mockP5Prototype.angleMode(mockP5.DEGREES);
                 });
         
        -        test('heading for vector pointing right is 0', function() {
        +        test("heading for vector pointing right is 0", function () {
                   v.x = 1;
                   v.y = 0;
                   v.z = 0;
                   expect(v.heading()).to.equal(0);
                 });
         
        -        test('heading for vector pointing down is 90', function() {
        +        test("heading for vector pointing down is 90", function () {
                   v.x = 0;
                   v.y = 1;
                   v.z = 0;
                   expect(v.heading()).to.equal(90);
                 });
         
        -        test('heading for vector pointing left is 180', function() {
        +        test("heading for vector pointing left is 180", function () {
                   v.x = -1;
                   v.y = 0;
                   v.z = 0;
        @@ -1318,36 +1329,36 @@ suite('p5.Vector', function() {
               });
             });
         
        -    suite('p5.Vector.heading() [CLASS]', function() {
        -      test('should return a number', function() {
        -        expect(typeof p5.Vector.heading(v) === 'number').to.eql(true);
        +    suite("p5.Vector.heading() [CLASS]", function () {
        +      test("should return a number", function () {
        +        expect(typeof mockP5.Vector.heading(v) === "number").to.eql(true);
               });
         
        -      test('heading for vector pointing right is 0', function() {
        +      test("heading for vector pointing right is 0", function () {
                 v.x = 1;
                 v.y = 0;
                 v.z = 0;
        -        expect(p5.Vector.heading(v)).to.be.closeTo(0, 0.01);
        +        expect(mockP5.Vector.heading(v)).to.be.closeTo(0, 0.01);
               });
         
        -      test('heading for vector pointing down is PI/2', function() {
        +      test("heading for vector pointing down is PI/2", function () {
                 v.x = 0;
                 v.y = 1;
                 v.z = 0;
        -        expect(p5.Vector.heading(v)).to.be.closeTo(Math.PI / 2, 0.01);
        +        expect(mockP5.Vector.heading(v)).to.be.closeTo(Math.PI / 2, 0.01);
               });
         
        -      test('heading for vector pointing left is PI', function() {
        +      test("heading for vector pointing left is PI", function () {
                 v.x = -1;
                 v.y = 0;
                 v.z = 0;
        -        expect(p5.Vector.heading(v)).to.be.closeTo(Math.PI, 0.01);
        +        expect(mockP5.Vector.heading(v)).to.be.closeTo(Math.PI, 0.01);
               });
             });
           });
         
        -  suite('lerp', function() {
        -    test('should return the same object', function() {
        +  suite("lerp", function () {
        +    test("should return the same object", function () {
               expect(v.lerp()).to.eql(v);
             });
         
        @@ -1360,29 +1371,29 @@ suite('p5.Vector', function() {
             //   });
             // });
         
        -    suite('with x, y, z, amt', function() {
        -      setup(function() {
        +    suite("with x, y, z, amt", function () {
        +      beforeEach(function () {
                 v.x = 0;
                 v.y = 0;
                 v.z = 0;
                 v.lerp(2, 2, 2, 0.5);
               });
         
        -      test('should lerp x by amt', function() {
        +      test("should lerp x by amt", function () {
                 expect(v.x).to.eql(1);
               });
         
        -      test('should lerp y by amt', function() {
        +      test("should lerp y by amt", function () {
                 expect(v.y).to.eql(1);
               });
         
        -      test('should lerp z by amt', function() {
        +      test("should lerp z by amt", function () {
                 expect(v.z).to.eql(1);
               });
             });
         
        -    suite('with no amt', function() {
        -      test('should assume 0 amt', function() {
        +    suite("with no amt", function () {
        +      test("should assume 0 amt", function () {
                 v.x = 0;
                 v.y = 0;
                 v.z = 0;
        @@ -1394,56 +1405,56 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('p5.Vector.lerp(v1, v2, amt)', function() {
        +  suite("p5.Vector.lerp(v1, v2, amt)", function () {
             var res, v1, v2;
        -    setup(function() {
        -      v1 = new p5.Vector(0, 0, 0);
        -      v2 = new p5.Vector(2, 2, 2);
        -      res = p5.Vector.lerp(v1, v2, 0.5);
        +    beforeEach(function () {
        +      v1 = new mockP5.Vector(0, 0, 0);
        +      v2 = new mockP5.Vector(2, 2, 2);
        +      res = mockP5.Vector.lerp(v1, v2, 0.5);
             });
         
        -    test('should not be undefined', function() {
        +    test("should not be undefined", function () {
               expect(res).to.not.eql(undefined);
             });
         
        -    test('should be a p5.Vector', function() {
        -      expect(res).to.be.an.instanceof(p5.Vector);
        +    test("should be a p5.Vector", function () {
        +      expect(res).to.be.an.instanceof(mockP5.Vector);
             });
         
        -    test('should return neither v1 nor v2', function() {
        +    test("should return neither v1 nor v2", function () {
               expect(res).to.not.eql(v1);
               expect(res).to.not.eql(v2);
             });
         
        -    test('should res to be [1, 1, 1]', function() {
        +    test("should res to be [1, 1, 1]", function () {
               expect(res.x).to.eql(1);
               expect(res.y).to.eql(1);
               expect(res.z).to.eql(1);
             });
           });
         
        -  suite('v.slerp(w, amt)', function() {
        +  suite("v.slerp(w, amt)", function () {
             var w;
        -    setup(function() {
        +    beforeEach(function () {
               v.set(1, 2, 3);
        -      w = new p5.Vector(4, 6, 8);
        +      w = new mockP5.Vector(4, 6, 8);
             });
         
        -    test('if amt is 0, returns original vector', function() {
        +    test("if amt is 0, returns original vector", function () {
               v.slerp(w, 0);
               expect(v.x).to.eql(1);
               expect(v.y).to.eql(2);
               expect(v.z).to.eql(3);
             });
         
        -    test('if amt is 1, returns argument vector', function() {
        +    test("if amt is 1, returns argument vector", function () {
               v.slerp(w, 1);
               expect(v.x).to.eql(4);
               expect(v.y).to.eql(6);
               expect(v.z).to.eql(8);
             });
         
        -    test('if both v and w are 2D, then result will also be 2D.', function() {
        +    test("if both v and w are 2D, then result will also be 2D.", function () {
               v.set(2, 3, 0);
               w.set(3, -2, 0);
               v.slerp(w, 0.3);
        @@ -1455,7 +1466,7 @@ suite('p5.Vector', function() {
               expect(v.z).to.eql(0);
             });
         
        -    test('if one side is a zero vector, linearly interpolate.', function() {
        +    test("if one side is a zero vector, linearly interpolate.", function () {
               v.set(0, 0, 0);
               w.set(2, 4, 6);
               v.slerp(w, 0.5);
        @@ -1464,7 +1475,7 @@ suite('p5.Vector', function() {
               expect(v.z).to.eql(3);
             });
         
        -    test('If they are pointing in the same direction, linearly interpolate.', function() {
        +    test("If they are pointing in the same direction, linearly interpolate.", function () {
               v.set(5, 11, 16);
               w.set(15, 33, 48);
               v.slerp(w, 0.5);
        @@ -1474,159 +1485,183 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('p5.Vector.slerp(v1, v2, amt)', function() {
        +  suite("p5.Vector.slerp(v1, v2, amt)", function () {
             var res, v1, v2;
        -    setup(function() {
        -      v1 = new p5.Vector(1, 0, 0);
        -      v2 = new p5.Vector(0, 0, 1);
        -      res = p5.Vector.slerp(v1, v2, 1/3);
        +    beforeEach(function () {
        +      v1 = new mockP5.Vector(1, 0, 0);
        +      v2 = new mockP5.Vector(0, 0, 1);
        +      res = mockP5.Vector.slerp(v1, v2, 1 / 3);
             });
         
        -    test('should not be undefined', function() {
        +    test("should not be undefined", function () {
               expect(res).to.not.eql(undefined);
             });
         
        -    test('should be a p5.Vector', function() {
        -      expect(res).to.be.an.instanceof(p5.Vector);
        +    test("should be a p5.Vector", function () {
        +      expect(res).to.be.an.instanceof(mockP5.Vector);
             });
         
        -    test('should return neither v1 nor v2', function() {
        +    test("should return neither v1 nor v2", function () {
               expect(res).to.not.eql(v1);
               expect(res).to.not.eql(v2);
             });
         
        -    test('Make sure the interpolation in 1/3 is correct', function() {
        -      expect(res.x).to.be.closeTo(Math.cos(Math.PI/6), 0.00001);
        +    test("Make sure the interpolation in 1/3 is correct", function () {
        +      expect(res.x).to.be.closeTo(Math.cos(Math.PI / 6), 0.00001);
               expect(res.y).to.be.closeTo(0, 0.00001);
        -      expect(res.z).to.be.closeTo(Math.sin(Math.PI/6), 0.00001);
        +      expect(res.z).to.be.closeTo(Math.sin(Math.PI / 6), 0.00001);
             });
         
        -    test('Make sure the interpolation in -1/3 is correct', function() {
        -      p5.Vector.slerp(v1, v2, -1/3, res);
        -      expect(res.x).to.be.closeTo(Math.cos(-Math.PI/6), 0.00001);
        +    test("Make sure the interpolation in -1/3 is correct", function () {
        +      mockP5.Vector.slerp(v1, v2, -1 / 3, res);
        +      expect(res.x).to.be.closeTo(Math.cos(-Math.PI / 6), 0.00001);
               expect(res.y).to.be.closeTo(0, 0.00001);
        -      expect(res.z).to.be.closeTo(Math.sin(-Math.PI/6), 0.00001);
        +      expect(res.z).to.be.closeTo(Math.sin(-Math.PI / 6), 0.00001);
             });
         
        -    test('Make sure the interpolation in 5/3 is correct', function() {
        -      p5.Vector.slerp(v1, v2, 5/3, res);
        -      expect(res.x).to.be.closeTo(Math.cos(5*Math.PI/6), 0.00001);
        +    test("Make sure the interpolation in 5/3 is correct", function () {
        +      mockP5.Vector.slerp(v1, v2, 5 / 3, res);
        +      expect(res.x).to.be.closeTo(Math.cos((5 * Math.PI) / 6), 0.00001);
               expect(res.y).to.be.closeTo(0, 0.00001);
        -      expect(res.z).to.be.closeTo(Math.sin(5*Math.PI/6), 0.00001);
        +      expect(res.z).to.be.closeTo(Math.sin((5 * Math.PI) / 6), 0.00001);
             });
           });
         
        -  suite('p5.Vector.fromAngle(angle)', function() {
        +  suite("p5.Vector.fromAngle(angle)", function () {
             var res, angle;
        -    setup(function() {
        +    beforeEach(function () {
               angle = Math.PI / 2;
        -      res = p5.Vector.fromAngle(angle);
        +      res = mockP5.Vector.fromAngle(angle);
             });
         
        -    test('should be a p5.Vector with values (0,1)', function() {
        +    test("should be a p5.Vector with values (0,1)", function () {
               expect(res.x).to.be.closeTo(0, 0.01);
               expect(res.y).to.be.closeTo(1, 0.01);
             });
           });
         
        -  suite('p5.Vector.random2D()', function() {
        +  suite("p5.Vector.random2D()", function () {
             var res;
        -    setup(function() {
        -      res = p5.Vector.random2D();
        +    beforeEach(function () {
        +      res = mockP5.Vector.random2D();
             });
         
        -    test('should be a unit p5.Vector', function() {
        +    test("should be a unit p5.Vector", function () {
               expect(res.mag()).to.be.closeTo(1, 0.01);
             });
           });
         
        -  suite('p5.Vector.random3D()', function() {
        +  suite("p5.Vector.random3D()", function () {
             var res;
        -    setup(function() {
        -      res = p5.Vector.random3D();
        +    beforeEach(function () {
        +      res = mockP5.Vector.random3D();
             });
        -    test('should be a unit p5.Vector', function() {
        +    test("should be a unit p5.Vector", function () {
               expect(res.mag()).to.be.closeTo(1, 0.01);
             });
           });
         
        -  suite('array', function() {
        -    setup(function() {
        -      v = new p5.Vector(1, 23, 4);
        +  suite("array", function () {
        +    beforeEach(function () {
        +      v = new mockP5.Vector(1, 23, 4);
             });
         
        -    suite('p5.Vector.prototype.array() [INSTANCE]', function() {
        -      test('should return an array', function() {
        +    suite("p5.Vector.prototype.array() [INSTANCE]", function () {
        +      test("should return an array", function () {
                 expect(v.array()).to.be.instanceof(Array);
               });
         
        -      test('should return an with the x y and z components', function() {
        +      test("should return an with the x y and z components", function () {
                 expect(v.array()).to.eql([1, 23, 4]);
               });
             });
         
        -    suite('p5.Vector.array() [CLASS]', function() {
        -      test('should return an array', function() {
        -        expect(p5.Vector.array(v)).to.be.instanceof(Array);
        +    suite("p5.Vector.array() [CLASS]", function () {
        +      test("should return an array", function () {
        +        expect(mockP5.Vector.array(v)).to.be.instanceof(Array);
               });
         
        -      test('should return an with the x y and z components', function() {
        -        expect(p5.Vector.array(v)).to.eql([1, 23, 4]);
        +      test("should return an with the x y and z components", function () {
        +        expect(mockP5.Vector.array(v)).to.eql([1, 23, 4]);
               });
             });
           });
         
        -  suite('reflect', function() {
        -    suite('p5.Vector.prototype.reflect() [INSTANCE]', function() {
        -      setup(function() {
        +  suite("reflect", function () {
        +    suite("p5.Vector.prototype.reflect() [INSTANCE]", function () {
        +      let incoming_x, incoming_y, incoming_z, original_incoming;
        +      let x_normal, y_normal, z_normal;
        +      let x_bounce_incoming,
        +        x_bounce_outgoing,
        +        y_bounce_incoming,
        +        y_bounce_outgoing,
        +        z_bounce_incoming,
        +        z_bounce_outgoing;
        +      beforeEach(function () {
                 incoming_x = 1;
                 incoming_y = 1;
                 incoming_z = 1;
        -        original_incoming = new p5.Vector(incoming_x, incoming_y, incoming_z);
        +        original_incoming = new mockP5.Vector(
        +          incoming_x,
        +          incoming_y,
        +          incoming_z
        +        );
         
        -        x_normal = new p5.Vector(3, 0, 0);
        -        y_normal = new p5.Vector(0, 3, 0);
        -        z_normal = new p5.Vector(0, 0, 3);
        +        x_normal = new mockP5.Vector(3, 0, 0);
        +        y_normal = new mockP5.Vector(0, 3, 0);
        +        z_normal = new mockP5.Vector(0, 0, 3);
         
        -        x_bounce_incoming = new p5.Vector(incoming_x, incoming_y, incoming_z);
        +        x_bounce_incoming = new mockP5.Vector(
        +          incoming_x,
        +          incoming_y,
        +          incoming_z
        +        );
                 x_bounce_outgoing = x_bounce_incoming.reflect(x_normal);
         
        -        y_bounce_incoming = new p5.Vector(incoming_x, incoming_y, incoming_z);
        +        y_bounce_incoming = new mockP5.Vector(
        +          incoming_x,
        +          incoming_y,
        +          incoming_z
        +        );
                 y_bounce_outgoing = y_bounce_incoming.reflect(y_normal);
         
        -        z_bounce_incoming = new p5.Vector(incoming_x, incoming_y, incoming_z);
        +        z_bounce_incoming = new mockP5.Vector(
        +          incoming_x,
        +          incoming_y,
        +          incoming_z
        +        );
                 z_bounce_outgoing = z_bounce_incoming.reflect(z_normal);
               });
         
        -      test('should return a p5.Vector', function() {
        -        expect(x_bounce_incoming).to.be.an.instanceof(p5.Vector);
        -        expect(y_bounce_incoming).to.be.an.instanceof(p5.Vector);
        -        expect(z_bounce_incoming).to.be.an.instanceof(p5.Vector);
        +      test("should return a p5.Vector", function () {
        +        expect(x_bounce_incoming).to.be.an.instanceof(mockP5.Vector);
        +        expect(y_bounce_incoming).to.be.an.instanceof(mockP5.Vector);
        +        expect(z_bounce_incoming).to.be.an.instanceof(mockP5.Vector);
               });
         
        -      test('should update this', function() {
        +      test("should update this", function () {
                 assert.equal(x_bounce_incoming, x_bounce_outgoing);
                 assert.equal(y_bounce_incoming, y_bounce_outgoing);
                 assert.equal(z_bounce_incoming, z_bounce_outgoing);
               });
         
        -      test('x-normal should flip incoming x component and maintain y,z components', function() {
        +      test("x-normal should flip incoming x component and maintain y,z components", function () {
                 expect(x_bounce_outgoing.x).to.be.closeTo(-1, 0.01);
                 expect(x_bounce_outgoing.y).to.be.closeTo(1, 0.01);
                 expect(x_bounce_outgoing.z).to.be.closeTo(1, 0.01);
               });
        -      test('y-normal should flip incoming y component and maintain x,z components', function() {
        +      test("y-normal should flip incoming y component and maintain x,z components", function () {
                 expect(y_bounce_outgoing.x).to.be.closeTo(1, 0.01);
                 expect(y_bounce_outgoing.y).to.be.closeTo(-1, 0.01);
                 expect(y_bounce_outgoing.z).to.be.closeTo(1, 0.01);
               });
        -      test('z-normal should flip incoming z component and maintain x,y components', function() {
        +      test("z-normal should flip incoming z component and maintain x,y components", function () {
                 expect(z_bounce_outgoing.x).to.be.closeTo(1, 0.01);
                 expect(z_bounce_outgoing.y).to.be.closeTo(1, 0.01);
                 expect(z_bounce_outgoing.z).to.be.closeTo(-1, 0.01);
               });
         
        -      test('angle of incidence should match angle of reflection', function() {
        +      test("angle of incidence should match angle of reflection", function () {
                 expect(
                   Math.abs(x_normal.angleBetween(original_incoming))
                 ).to.be.closeTo(
        @@ -1646,7 +1681,7 @@ suite('p5.Vector', function() {
                   0.01
                 );
               });
        -      test('should not update surface normal', function() {
        +      test("should not update surface normal", function () {
                 const tolerance = 0.001;
                 assert.closeTo(x_normal.x, 3, tolerance);
                 assert.closeTo(x_normal.y, 0, tolerance);
        @@ -1660,58 +1695,83 @@ suite('p5.Vector', function() {
                 assert.closeTo(z_normal.y, 0, tolerance);
                 assert.closeTo(z_normal.z, 3, tolerance);
               });
        -
             });
         
        -    suite('p5.Vector.reflect() [CLASS]', function() {
        -      setup(function() {
        +    suite("p5.Vector.reflect() [CLASS]", function () {
        +      let incoming_x, incoming_y, incoming_z, original_incoming;
        +      let x_target, y_target, z_target;
        +      let x_normal, y_normal, z_normal;
        +      let x_bounce_incoming,
        +        x_bounce_outgoing,
        +        y_bounce_incoming,
        +        y_bounce_outgoing,
        +        z_bounce_incoming,
        +        z_bounce_outgoing;
        +
        +      beforeEach(function () {
                 incoming_x = 1;
                 incoming_y = 1;
                 incoming_z = 1;
        -        original_incoming = new p5.Vector(incoming_x, incoming_y, incoming_z);
        -        x_target = new p5.Vector();
        -        y_target = new p5.Vector();
        -        z_target = new p5.Vector();
        -
        -        x_normal = new p5.Vector(3, 0, 0);
        -        y_normal = new p5.Vector(0, 3, 0);
        -        z_normal = new p5.Vector(0, 0, 3);
        -
        -        x_bounce_incoming = new p5.Vector(incoming_x, incoming_y, incoming_z);
        -        x_bounce_outgoing = p5.Vector.reflect(
        +        original_incoming = new mockP5.Vector(
        +          incoming_x,
        +          incoming_y,
        +          incoming_z
        +        );
        +        x_target = new mockP5.Vector();
        +        y_target = new mockP5.Vector();
        +        z_target = new mockP5.Vector();
        +
        +        x_normal = new mockP5.Vector(3, 0, 0);
        +        y_normal = new mockP5.Vector(0, 3, 0);
        +        z_normal = new mockP5.Vector(0, 0, 3);
        +
        +        x_bounce_incoming = new mockP5.Vector(
        +          incoming_x,
        +          incoming_y,
        +          incoming_z
        +        );
        +        x_bounce_outgoing = mockP5.Vector.reflect(
                   x_bounce_incoming,
                   x_normal,
                   x_target
                 );
         
        -        y_bounce_incoming = new p5.Vector(incoming_x, incoming_y, incoming_z);
        -        y_bounce_outgoing = p5.Vector.reflect(
        +        y_bounce_incoming = new mockP5.Vector(
        +          incoming_x,
        +          incoming_y,
        +          incoming_z
        +        );
        +        y_bounce_outgoing = mockP5.Vector.reflect(
                   y_bounce_incoming,
                   y_normal,
                   y_target
                 );
         
        -        z_bounce_incoming = new p5.Vector(incoming_x, incoming_y, incoming_z);
        -        z_bounce_outgoing = p5.Vector.reflect(
        +        z_bounce_incoming = new mockP5.Vector(
        +          incoming_x,
        +          incoming_y,
        +          incoming_z
        +        );
        +        z_bounce_outgoing = mockP5.Vector.reflect(
                   z_bounce_incoming,
                   z_normal,
                   z_target
                 );
               });
         
        -      test('should return a p5.Vector', function() {
        -        expect(x_bounce_incoming).to.be.an.instanceof(p5.Vector);
        -        expect(y_bounce_incoming).to.be.an.instanceof(p5.Vector);
        -        expect(z_bounce_incoming).to.be.an.instanceof(p5.Vector);
        +      test("should return a p5.Vector", function () {
        +        expect(x_bounce_incoming).to.be.an.instanceof(mockP5.Vector);
        +        expect(y_bounce_incoming).to.be.an.instanceof(mockP5.Vector);
        +        expect(z_bounce_incoming).to.be.an.instanceof(mockP5.Vector);
               });
         
        -      test('should not update this', function() {
        +      test("should not update this", function () {
                 expect(x_bounce_incoming).to.not.equal(x_bounce_outgoing);
                 expect(y_bounce_incoming).to.not.equal(y_bounce_outgoing);
                 expect(z_bounce_incoming).to.not.equal(z_bounce_outgoing);
               });
         
        -      test('should not update surface normal', function() {
        +      test("should not update surface normal", function () {
                 const tolerance = 0.001;
                 assert.closeTo(x_normal.x, 3, tolerance);
                 assert.closeTo(x_normal.y, 0, tolerance);
        @@ -1726,30 +1786,29 @@ suite('p5.Vector', function() {
                 assert.closeTo(z_normal.z, 3, tolerance);
               });
         
        -
        -      test('should update target', function() {
        +      test("should update target", function () {
                 assert.equal(x_target, x_bounce_outgoing);
                 assert.equal(y_target, y_bounce_outgoing);
                 assert.equal(z_target, z_bounce_outgoing);
               });
         
        -      test('x-normal should flip incoming x component and maintain y,z components', function() {
        +      test("x-normal should flip incoming x component and maintain y,z components", function () {
                 expect(x_bounce_outgoing.x).to.be.closeTo(-1, 0.01);
                 expect(x_bounce_outgoing.y).to.be.closeTo(1, 0.01);
                 expect(x_bounce_outgoing.z).to.be.closeTo(1, 0.01);
               });
        -      test('y-normal should flip incoming y component and maintain x,z components', function() {
        +      test("y-normal should flip incoming y component and maintain x,z components", function () {
                 expect(y_bounce_outgoing.x).to.be.closeTo(1, 0.01);
                 expect(y_bounce_outgoing.y).to.be.closeTo(-1, 0.01);
                 expect(y_bounce_outgoing.z).to.be.closeTo(1, 0.01);
               });
        -      test('z-normal should flip incoming z component and maintain x,y components', function() {
        +      test("z-normal should flip incoming z component and maintain x,y components", function () {
                 expect(z_bounce_outgoing.x).to.be.closeTo(1, 0.01);
                 expect(z_bounce_outgoing.y).to.be.closeTo(1, 0.01);
                 expect(z_bounce_outgoing.z).to.be.closeTo(-1, 0.01);
               });
         
        -      test('angle of incidence should match angle of reflection', function() {
        +      test("angle of incidence should match angle of reflection", function () {
                 expect(
                   Math.abs(x_normal.angleBetween(original_incoming))
                 ).to.be.closeTo(
        @@ -1772,115 +1831,238 @@ suite('p5.Vector', function() {
             });
           });
         
        -  suite('mag', function() {
        +  suite("mag", function () {
             const MAG = 3.7416573867739413; // sqrt(1*1 + 2*2 + 3*3)
         
             let v0;
             let v1;
         
        -    setup(function() {
        -      v0 = new p5.Vector(0, 0, 0);
        -      v1 = new p5.Vector(1, 2, 3);
        +    beforeEach(function () {
        +      v0 = new mockP5.Vector(0, 0, 0);
        +      v1 = new mockP5.Vector(1, 2, 3);
             });
         
        -    suite('p5.Vector.prototype.mag() [INSTANCE]', function() {
        -      test('should return the magnitude of the vector', function() {
        +    suite("p5.Vector.prototype.mag() [INSTANCE]", function () {
        +      test("should return the magnitude of the vector", function () {
                 expect(v0.mag()).to.eql(0);
                 expect(v1.mag()).to.eql(MAG);
               });
             });
         
        -    suite('p5.Vector.mag() [CLASS]', function() {
        -      test('should return the magnitude of the vector', function() {
        -        expect(p5.Vector.mag(v0)).to.eql(0);
        -        expect(p5.Vector.mag(v1)).to.eql(MAG);
        +    suite("p5.Vector.mag() [CLASS]", function () {
        +      test("should return the magnitude of the vector", function () {
        +        expect(mockP5.Vector.mag(v0)).to.eql(0);
        +        expect(mockP5.Vector.mag(v1)).to.eql(MAG);
               });
             });
           });
         
        -  suite('magSq', function() {
        +  suite("magSq", function () {
             const MAG = 14; // 1*1 + 2*2 + 3*3
         
             let v0;
             let v1;
         
        -    setup(function() {
        -      v0 = new p5.Vector(0, 0, 0);
        -      v1 = new p5.Vector(1, 2, 3);
        +    beforeEach(function () {
        +      v0 = new mockP5.Vector(0, 0, 0);
        +      v1 = new mockP5.Vector(1, 2, 3);
             });
         
        -    suite('p5.Vector.prototype.magSq() [INSTANCE]', function() {
        -      test('should return the magnitude of the vector', function() {
        +    suite("p5.Vector.prototype.magSq() [INSTANCE]", function () {
        +      test("should return the magnitude of the vector", function () {
                 expect(v0.magSq()).to.eql(0);
                 expect(v1.magSq()).to.eql(MAG);
               });
             });
         
        -    suite('p5.Vector.magSq() [CLASS]', function() {
        -      test('should return the magnitude of the vector', function() {
        -        expect(p5.Vector.magSq(v0)).to.eql(0);
        -        expect(p5.Vector.magSq(v1)).to.eql(MAG);
        +    suite("p5.Vector.magSq() [CLASS]", function () {
        +      test("should return the magnitude of the vector", function () {
        +        expect(mockP5.Vector.magSq(v0)).to.eql(0);
        +        expect(mockP5.Vector.magSq(v1)).to.eql(MAG);
               });
             });
           });
         
        -  suite('equals', function() {
        -    suite('p5.Vector.prototype.equals() [INSTANCE]', function() {
        -      test('should return false for parameters inequal to the vector', function() {
        -        const v1 = new p5.Vector(0, -1, 1);
        -        const v2 = new p5.Vector(1, 2, 3);
        +  suite("equals", function () {
        +    suite("p5.Vector.prototype.equals() [INSTANCE]", function () {
        +      test("should return false for parameters inequal to the vector", function () {
        +        const v1 = new mockP5.Vector(0, -1, 1);
        +        const v2 = new mockP5.Vector(1, 2, 3);
                 const a2 = [1, 2, 3];
                 expect(v1.equals(v2)).to.be.false;
                 expect(v1.equals(a2)).to.be.false;
                 expect(v1.equals(1, 2, 3)).to.be.false;
               });
         
        -      test('should return true for equal vectors', function() {
        -        const v1 = new p5.Vector(0, -1, 1);
        -        const v2 = new p5.Vector(0, -1, 1);
        +      test("should return true for equal vectors", function () {
        +        const v1 = new mockP5.Vector(0, -1, 1);
        +        const v2 = new mockP5.Vector(0, -1, 1);
                 expect(v1.equals(v2)).to.be.true;
               });
         
        -      test('should return true for arrays equal to the vector', function() {
        -        const v1 = new p5.Vector(0, -1, 1);
        +      test("should return true for arrays equal to the vector", function () {
        +        const v1 = new mockP5.Vector(0, -1, 1);
                 const a1 = [0, -1, 1];
                 expect(v1.equals(a1)).to.be.true;
               });
         
        -      test('should return true for arguments equal to the vector', function() {
        -        const v1 = new p5.Vector(0, -1, 1);
        +      test("should return true for arguments equal to the vector", function () {
        +        const v1 = new mockP5.Vector(0, -1, 1);
                 expect(v1.equals(0, -1, 1)).to.be.true;
               });
             });
         
        -    suite('p5.Vector.equals() [CLASS]', function() {
        -      test('should return false for inequal parameters', function() {
        -        const v1 = new p5.Vector(0, -1, 1);
        -        const v2 = new p5.Vector(1, 2, 3);
        +    suite("p5.Vector.equals() [CLASS]", function () {
        +      test("should return false for inequal parameters", function () {
        +        const v1 = new mockP5.Vector(0, -1, 1);
        +        const v2 = new mockP5.Vector(1, 2, 3);
                 const a2 = [1, 2, 3];
        -        expect(p5.Vector.equals(v1, v2)).to.be.false;
        -        expect(p5.Vector.equals(v1, a2)).to.be.false;
        -        expect(p5.Vector.equals(a2, v1)).to.be.false;
        +        expect(mockP5.Vector.equals(v1, v2)).to.be.false;
        +        expect(mockP5.Vector.equals(v1, a2)).to.be.false;
        +        expect(mockP5.Vector.equals(a2, v1)).to.be.false;
               });
         
        -      test('should return true for equal vectors', function() {
        -        const v1 = new p5.Vector(0, -1, 1);
        -        const v2 = new p5.Vector(0, -1, 1);
        -        expect(p5.Vector.equals(v1, v2)).to.be.true;
        +      test("should return true for equal vectors", function () {
        +        const v1 = new mockP5.Vector(0, -1, 1);
        +        const v2 = new mockP5.Vector(0, -1, 1);
        +        expect(mockP5.Vector.equals(v1, v2)).to.be.true;
               });
         
        -      test('should return true for equal vectors and arrays', function() {
        -        const v1 = new p5.Vector(0, -1, 1);
        +      test("should return true for equal vectors and arrays", function () {
        +        const v1 = new mockP5.Vector(0, -1, 1);
                 const a1 = [0, -1, 1];
        -        expect(p5.Vector.equals(v1, a1)).to.be.true;
        -        expect(p5.Vector.equals(a1, v1)).to.be.true;
        +        expect(mockP5.Vector.equals(v1, a1)).to.be.true;
        +        expect(mockP5.Vector.equals(a1, v1)).to.be.true;
               });
         
        -      test('should return true for equal arrays', function() {
        +      test("should return true for equal arrays", function () {
                 const a1 = [0, -1, 1];
                 const a2 = [0, -1, 1];
        -        expect(p5.Vector.equals(a1, a2)).to.be.true;
        +        expect(mockP5.Vector.equals(a1, a2)).to.be.true;
               });
             });
           });
        +
        +  suite("set values", function () {
        +    beforeEach(function () {
        +      v = new mockP5.Vector();
        +    });
        +
        +    test("should set values to [0,0,0] if values array is empty", function () {
        +      v.values = [];
        +      assert.equal(v.x, 0);
        +      assert.equal(v.y, 0);
        +      assert.equal(v.z, 0);
        +      assert.equal(v.dimensions, 2);
        +    });
        +  });
        +  suite("get value", function () {
        +    test("should return element in range of a non empty vector", function () {
        +      let vect = new mockP5.Vector(1, 2, 3, 4);
        +      assert.equal(vect.getValue(0), 1);
        +      assert.equal(vect.getValue(1), 2);
        +      assert.equal(vect.getValue(2), 3);
        +      assert.equal(vect.getValue(3), 4);
        +    });
        +
        +    test.fails(
        +      "should throw friendly error if attempting to get element outside lenght",
        +      function () {
        +        let vect = new mockP5.Vector(1, 2, 3, 4);
        +        assert.equal(vect.getValue(5), 1);
        +      }
        +    );
        +  });
        +
        +  suite("set value", function () {
        +    test("should set value of element in range", function () {
        +      let vect = new mockP5.Vector(1, 2, 3, 4);
        +      vect.setValue(0, 7);
        +      assert.equal(vect.getValue(0), 7);
        +      assert.equal(vect.getValue(1), 2);
        +      assert.equal(vect.getValue(2), 3);
        +      assert.equal(vect.getValue(3), 4);
        +    });
        +
        +    test.fails(
        +      "should throw friendly error if attempting to set element outside lenght",
        +      function () {
        +        let vect = new mockP5.Vector(1, 2, 3, 4);
        +        vect.setValue(100, 7);
        +      }
        +    );
        +  });
        +
        +  describe("get w", () => {
        +    it("should return the w component of the vector", () => {
        +      v = new mockP5.Vector(1, 2, 3, 4);
        +      expect(v.w).toBe(4);
        +    });
        +
        +    it("should return 0 if w component is not set", () => {
        +      v = new mockP5.Vector(1, 2, 3);
        +      expect(v.w).toBe(0);
        +    });
        +  });
        +
        +  describe("set w", () => {
        +    it("should set 4th dimension of vector to w value if it exists", () => {
        +      v = new mockP5.Vector(1, 2, 3, 4);
        +      v.w = 7;
        +      expect(v.x).toBe(1);
        +      expect(v.y).toBe(2);
        +      expect(v.z).toBe(3);
        +      expect(v.w).toBe(7);
        +    });
        +
        +    it("should throw error if trying to set w if vector dimensions is less than 4", () => {
        +      v = new mockP5.Vector(1, 2);
        +      v.w = 5;
        +      console.log(v);
        +      console.log(v.w);
        +      expect(v.w).toBe(0); //TODO: Check this, maybe this should fail
        +    });
        +  });
        +
        +  describe("vector to string", () => {
        +    it("should return the string version of a vector", () => {
        +      v = new mockP5.Vector(1, 2, 3, 4);
        +      expect(v.toString()).toBe("[1, 2, 3, 4]");
        +    });
        +  });
        +
        +  describe("set heading", () => {
        +    it("should rotate a 2D vector by specified angle without changing magnitude", () => {
        +      v = new mockP5.Vector(0, 2);
        +      const mag = v.mag();
        +      expect(v.setHeading(2 * Math.PI).mag()).toBe(mag);
        +      expect(v.x).toBe(2);
        +      expect(v.y).toBe(-4.898587196589413e-16);
        +    });
        +  });
        +
        +  describe("clamp to zero", () => {
        +    it("should clamp values cloze to zero to zero, with Number.epsilon value", () => {
        +      v = new mockP5.Vector(0, 1, 0.5, 0.1, 0.0000000000000001);
        +      expect(v.clampToZero().values).toEqual([0, 1, 0.5, 0.1, 0]);
        +    });
        +  });
        +
        +  suite("p5.Vector.fromAngles()", function () {
        +    it("should create a v3ctor froma pair of ISO spherical angles", () => {
        +      let vect = mockP5.Vector.fromAngles(0, 0);
        +      expect(vect.values).toEqual([0, -1, 0]);
        +    });
        +  });
        +
        +  suite("p5.Vector.rotate()", function () {
        +    it("should rotate the vector (only 2D vectors) by the given angle; magnitude remains the same.", () => {
        +      v = new mockP5.Vector(0, 1, 2);
        +      let target = new mockP5.Vector();
        +      mockP5.Vector.rotate(v, 1 * Math.PI, target);
        +      expect(target.values).toEqual([
        +        -4.10759023698152e-16, -2.23606797749979, 2,
        +      ]);
        +    });
        +  });
         });
        diff --git a/test/unit/math/random.js b/test/unit/math/random.js
        index 6e8a5ea7ab..0105b5ade8 100644
        --- a/test/unit/math/random.js
        +++ b/test/unit/math/random.js
        @@ -1,17 +1,15 @@
        -suite('Random', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +import random from '../../../src/math/random.js';
        +import { vi } from 'vitest';
         
        -  teardown(function() {
        -    myp5.remove();
        +suite('Random', function() {
        +  const mockP5 = {
        +    _validateParameters: vi.fn()
        +  };
        +  const mockP5Prototype = {
        +  };
        +
        +  beforeAll(function() {
        +    random(mockP5, mockP5Prototype);
           });
         
           suite('p5.prototype.random', function() {
        @@ -20,14 +18,14 @@ suite('Random', function() {
             var results = [];
         
             suite('random()', function() {
        -      setup(function() {
        -        myp5.randomSeed(99);
        +      beforeEach(function() {
        +        mockP5Prototype.randomSeed(99);
                 for (var i = 0; i < 5; i++) {
        -          results[i] = myp5.random();
        +          results[i] = mockP5Prototype.random();
                 }
        -        myp5.randomSeed(99);
        +        mockP5Prototype.randomSeed(99);
                 for (i = 5; i < 10; i++) {
        -          results[i] = myp5.random();
        +          results[i] = mockP5Prototype.random();
                 }
               });
               test('should return a number', function() {
        @@ -50,7 +48,7 @@ suite('Random', function() {
         
             suite('random(5)', function() {
               test('should return a number 0 <= n < 5', function() {
        -        result = myp5.random(5);
        +        result = mockP5Prototype.random(5);
                 assert.isTrue(result >= 0);
                 assert.isTrue(result < 5);
               });
        @@ -58,7 +56,7 @@ suite('Random', function() {
         
             suite('random(1, 10)', function() {
               test('should return a number 1 <= n < 10', function() {
        -        result = myp5.random(1, 10);
        +        result = mockP5Prototype.random(1, 10);
                 assert.isTrue(result >= 1);
                 assert.isTrue(result < 10);
               });
        @@ -67,33 +65,26 @@ suite('Random', function() {
             suite('random(["apple", "pear", "orange", "grape"])', function() {
               test('should return a fruit', function() {
                 var fruits = ['apple', 'pear', 'orange', 'grape'];
        -        result = myp5.random(fruits);
        +        result = mockP5Prototype.random(fruits);
                 assert.include(fruits, result);
               });
             });
           });
        -  suite('instance mode', function() {
        +  suite.skip('instance mode', function() {
             var instances = [];
         
        -    function addInstance(max, done) {
        -      new p5(function(p) {
        -        p.setup = function() {
        -          instances.push(p);
        -          if (instances.length >= max) {
        -            done();
        -          }
        -        };
        -      });
        -    }
        -
        -    setup(function(done) {
        +    beforeEach(async function() {
               var instanceCount = 2;
               for (var i = 0; i < instanceCount; i++) {
        -        addInstance(instanceCount, done);
        +        new p5(function(p) {
        +          p.setup = function() {
        +            instances.push(p);
        +          };
        +        });
               }
             });
         
        -    teardown(function() {
        +    afterEach(function() {
               instances.forEach(function(instance) {
                 instance.remove();
               });
        @@ -120,28 +111,21 @@ suite('Random', function() {
           });
         
           suite('p5.prototype.randomGaussian', function() {
        -    suite('instance mode', function() {
        +    suite.skip('instance mode', function() {
               var instances = [];
         
        -      function addInstance(max, done) {
        -        new p5(function(p) {
        -          p.setup = function() {
        -            instances.push(p);
        -            if (instances.length >= max) {
        -              done();
        -            }
        -          };
        -        });
        -      }
        -
        -      setup(function(done) {
        +      beforeEach(async function() {
                 var instanceCount = 2;
                 for (var i = 0; i < instanceCount; i++) {
        -          addInstance(instanceCount, done);
        +          new p5(function(p) {
        +            p.setup = function() {
        +              instances.push(p);
        +            };
        +          });
                 }
               });
         
        -      teardown(function() {
        +      afterEach(function() {
                 instances.forEach(function(instance) {
                   instance.remove();
                 });
        @@ -169,7 +153,7 @@ suite('Random', function() {
         
             suite('randomGaussian(42, 0)', function() {
               test('should return 42', function() {
        -        result = myp5.randomGaussian(42, 0);
        +        let result = mockP5Prototype.randomGaussian(42, 0);
                 assert.isTrue(result === 42);
               });
             });
        diff --git a/test/unit/math/trigonometry.js b/test/unit/math/trigonometry.js
        index 3445baa4c3..b8c2c86e2a 100644
        --- a/test/unit/math/trigonometry.js
        +++ b/test/unit/math/trigonometry.js
        @@ -1,144 +1,142 @@
        +import trigonometry from '../../../src/math/trigonometry.js';
        +import { vi } from 'vitest';
        +
         suite('Trigonometry', function() {
        -  var theta = 90;
        -  var x = 0;
        -  var y = 1;
        -  var ratio = 0.5;
        -  var RADIANS = 'radians';
        -  var DEGREES = 'degrees';
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        +  let theta = 90;
        +  let x = 0;
        +  let y = 1;
        +  let ratio = 0.5;
        +
        +  const mockP5 = {
        +    _validateParameters: vi.fn()
        +  };
        +  const mockP5Prototype = {
        +  };
        +
        +  beforeEach(async function() {
        +    trigonometry(mockP5, mockP5Prototype);
           });
         
        -  teardown(function() {
        -    myp5.remove();
        +  afterAll(function() {
           });
         
           var handleDegreesAndRadians = function(func) {
             test('should handle degrees', function() {
        -      myp5.angleMode(DEGREES);
        -      var degToRad = myp5.radians(theta);
        -      assert.equal(Math[func](degToRad), myp5[func](theta));
        +      mockP5Prototype.angleMode(mockP5.DEGREES);
        +      var degToRad = mockP5Prototype.radians(theta);
        +      assert.equal(Math[func](degToRad), mockP5Prototype[func](theta));
             });
         
             test('should handle radians', function() {
        -      myp5.angleMode(RADIANS);
        -      assert.equal(Math[func](theta), myp5[func](theta));
        +      mockP5Prototype.angleMode(mockP5.RADIANS);
        +      assert.equal(Math[func](theta), mockP5Prototype[func](theta));
             });
           };
         
           var ahandleDegreesAndRadians = function(func) {
             test('should handle degrees', function() {
        -      myp5.angleMode(DEGREES);
        -      assert.equal(myp5.degrees(Math[func](ratio)), myp5[func](ratio));
        +      mockP5Prototype.angleMode(mockP5.DEGREES);
        +      assert.equal(
        +        mockP5Prototype.degrees(Math[func](ratio)), mockP5Prototype[func](ratio)
        +      );
             });
         
             test('should handle radians', function() {
        -      myp5.angleMode(RADIANS);
        -      assert.equal(Math[func](ratio), myp5[func](ratio));
        +      mockP5Prototype.angleMode(mockP5.RADIANS);
        +      assert.equal(Math[func](ratio), mockP5Prototype[func](ratio));
             });
           };
         
        -  suite('p5.prototype.angleMode', function() {
        +  suite.todo('p5.prototype.angleMode', function() {
             test('should set constant to DEGREES', function() {
        -      myp5.angleMode(DEGREES);
        -      assert.equal(myp5.angleMode(), 'degrees');
        +      mockP5Prototype.angleMode(mockP5.DEGREES);
        +      assert.equal(mockP5Prototype.angleMode(), mockP5.DEGREES);
             });
         
             test('should set constant to RADIANS', function() {
        -      myp5.angleMode(RADIANS);
        -      assert.equal(myp5.angleMode(), 'radians');
        -    });
        -
        -    test('wrong param type', function() {
        -      assert.validationError(function() {
        -        myp5.angleMode('wtflolzkk');
        -      });
        +      mockP5Prototype.angleMode(mockP5Prototype.RADIANS);
        +      assert.equal(mockP5Prototype.angleMode(), mockP5.RADIANS);
             });
         
             test('should return radians', function() {
        -      myp5.angleMode(RADIANS);
        -      assert.equal(myp5.angleMode(), 'radians');
        +      mockP5Prototype.angleMode(mockP5.RADIANS);
        +      assert.equal(mockP5Prototype.angleMode(), mockP5.RADIANS);
             });
         
             test('should return degrees', function() {
        -      myp5.angleMode(DEGREES);
        -      assert.equal(myp5.angleMode(), 'degrees');
        +      mockP5Prototype.angleMode(mockP5.DEGREES);
        +      assert.equal(mockP5Prototype.angleMode(), mockP5.DEGREES);
             });
         
             test('should always be RADIANS or DEGREES', function() {
        -      myp5.angleMode('wtflolzkk');
        -      assert.equal(myp5.angleMode(), 'radians');
        +      mockP5Prototype.angleMode('wtflolzkk');
        +      assert.equal(mockP5Prototype.angleMode(), mockP5.RADIANS);
             });
           });
         
           suite('p5.prototype.degrees', function() {
             test('should return the angle in radians when angleMode is DEGREES', function() {
        -      myp5.angleMode(DEGREES);
        +      mockP5Prototype.angleMode(mockP5.DEGREES);
               var angleInRad = 360 * theta / (2 * Math.PI); // This is degToRad conversion
        -      assert.equal(myp5.degrees(theta), angleInRad);
        +      assert.equal(mockP5Prototype.degrees(theta), angleInRad);
             });
         
             test('should return the angle in radians when angleMode is RADIANS', function() {
        -      myp5.angleMode(RADIANS);
        +      mockP5Prototype.angleMode(mockP5.RADIANS);
               var angleInRad = 360 * theta / (2 * Math.PI); // This is degToRad conversion
        -      assert.equal(myp5.degrees(theta), angleInRad);
        +      assert.equal(mockP5Prototype.degrees(theta), angleInRad);
             });
           });
         
           suite('p5.prototype.radians', function() {
             test('should return the angle in degrees when angleMode is RADIANS', function() {
        -      myp5.angleMode(RADIANS);
        +      mockP5Prototype.angleMode(mockP5.RADIANS);
               var angleInDeg = 2 * Math.PI * theta / 360; // This is RadToDeg conversion
        -      assert.equal(myp5.radians(theta), angleInDeg);
        +      assert.equal(mockP5Prototype.radians(theta), angleInDeg);
             });
         
             test('should return the angle in degrees when angleMode is DEGREES', function() {
        -      myp5.angleMode(DEGREES);
        +      mockP5Prototype.angleMode(mockP5.DEGREES);
               var angleInDeg = 2 * Math.PI * theta / 360; // This is RadToDeg conversion
        -      assert.equal(myp5.radians(theta), angleInDeg);
        +      assert.equal(mockP5Prototype.radians(theta), angleInDeg);
             });
           });
         
        -  suite('p5.prototype.asin', function() {
        +  suite.todo('p5.prototype.asin', function() {
             ahandleDegreesAndRadians('asin');
           });
         
        -  suite('p5.prototype.atan', function() {
        +  suite.todo('p5.prototype.atan', function() {
             ahandleDegreesAndRadians('atan');
           });
         
        -  suite('p5.prototype.acos', function() {
        +  suite.todo('p5.prototype.acos', function() {
             ahandleDegreesAndRadians('acos');
           });
         
        -  suite('p5.prototype.sin', function() {
        +  suite.todo('p5.prototype.sin', function() {
             handleDegreesAndRadians('sin');
           });
         
        -  suite('p5.prototype.cos', function() {
        +  suite.todo('p5.prototype.cos', function() {
             handleDegreesAndRadians('cos');
           });
         
        -  suite('p5.prototype.tan', function() {
        +  suite.todo('p5.prototype.tan', function() {
             handleDegreesAndRadians('tan');
           });
         
        -  suite('p5.prototype.atan2', function() {
        +  suite.todo('p5.prototype.atan2', function() {
             test('should handle degrees', function() {
        -      myp5.angleMode(DEGREES);
        -      assert.equal(myp5.degrees(Math.atan2(y, x)), myp5.atan2(y, x));
        +      mockP5Prototype.angleMode(mockP5.DEGREES);
        +      assert.equal(
        +        mockP5Prototype.degrees(Math.atan2(y, x)), mockP5Prototype.atan2(y, x)
        +      );
             });
         
             test('should handle radians', function() {
        -      myp5.angleMode(RADIANS);
        -      assert.equal(Math.atan2(y, x), myp5.atan2(y, x));
        +      mockP5Prototype.angleMode(mockP5.RADIANS);
        +      assert.equal(Math.atan2(y, x), mockP5Prototype.atan2(y, x));
             });
           });
         });
        diff --git a/test/unit/spec.js b/test/unit/spec.js
        index 664fbba11a..fd9bbc759f 100644
        --- a/test/unit/spec.js
        +++ b/test/unit/spec.js
        @@ -10,8 +10,10 @@ var spec = {
             'main',
             'p5.Element',
             'p5.Graphics',
        +    'param_errors',
             'preload',
             'rendering',
        +    'sketch_overrides',
             'structure',
             'transform',
             'version',
        @@ -59,8 +61,8 @@ var spec = {
         document.write(
           '<script src="unit/visual/visualTest.js" type="text/javascript"></script>'
         );
        -Object.keys(spec).map(function(folder) {
        -  spec[folder].map(function(file) {
        +Object.keys(spec).map(function (folder) {
        +  spec[folder].map(function (file) {
             var string = [
               '<script src="unit/',
               folder,
        diff --git a/test/unit/type/attributes.js b/test/unit/type/attributes.js
        new file mode 100644
        index 0000000000..01d9a39551
        --- /dev/null
        +++ b/test/unit/type/attributes.js
        @@ -0,0 +1,77 @@
        +import p5 from '../../../src/app.js';
        +
        +suite('Typography Attributes', function() {
        +  var myp5;
        +
        +  beforeEach(function () {
        +    myp5 = new p5(function (p) {
        +      p.setup = function () { };
        +      p.draw = function () { };
        +    });
        +  });
        +
        +  afterEach(function () {
        +    myp5.remove();
        +  });
        +
        +  suite('textLeading', function() {
        +    test('sets and gets the leading value', function() {
        +      myp5.textLeading(20);
        +      assert.strictEqual(myp5.textLeading(), 20);
        +    });
        +    test('should work for negative leadings', function() {
        +      myp5.textLeading(-20);
        +      assert.strictEqual(myp5.textLeading(), -20);
        +    });
        +  });
        +
        +  suite('textSize', function() {
        +    test('sets and gets the text size', function() {
        +      myp5.textSize(24);
        +      assert.strictEqual(myp5.textSize(), 24);
        +    });
        +  });
        +
        +  suite('textStyle', function() {
        +    test('sets and gets the text style', function() {
        +      myp5.textStyle(myp5.ITALIC);
        +      assert.strictEqual(myp5.textStyle(), myp5.ITALIC);
        +    });
        +  });
        +
        +  suite('textWidth', function() {
        +    test('should return a number for char input', function() {
        +      assert.isNumber(myp5.textWidth('P'));
        +    });
        +    test('should return a number for string input.', function () {
        +      assert.isNumber(myp5.textWidth('p5.js'));
        +    });
        +    // Either should not throw error
        +    test('should return a number for number input', function() {
        +      assert.isNumber(myp5.textWidth(100));
        +    });
        +  });
        +
        +  suite('textAscent', function() {
        +    test('should return a number', function() {
        +      assert.isNumber(myp5.textAscent());
        +    });
        +  });
        +
        +  suite('textDescent', function() {
        +    test('should return a number', function() {
        +      assert.isNumber(myp5.textDescent());
        +    });
        +  });
        +
        +  suite('textWrap', function() {
        +    test('gets the default text wrap attribute', function() {
        +      assert.strictEqual(myp5.textWrap(), myp5.WORD);
        +    });
        +    test('sets and gets the text wrap value', function() {
        +      myp5.textWrap(myp5.CHAR);
        +      assert.strictEqual(myp5.textWrap(), myp5.CHAR);
        +    });
        +
        +  });
        +});
        diff --git a/test/unit/type/loading.js b/test/unit/type/loading.js
        new file mode 100644
        index 0000000000..c9c7d0ea5d
        --- /dev/null
        +++ b/test/unit/type/loading.js
        @@ -0,0 +1,47 @@
        +import p5 from '../../../src/app.js';
        +
        +suite('Loading Fonts', function () {
        +  var myp5;
        +
        +  beforeEach(function () {
        +    myp5 = new p5(function (p) {
        +      p.setup = function () { };
        +      p.draw = function () { };
        +    });
        +  });
        +
        +  afterEach(function () {
        +    myp5.remove();
        +  });
        +
        +  // tests ////////////////////////////////////////////////
        +  const fontFile = '/unit/assets/acmesa.ttf';
        +
        +  test('loadFont.await', async () => {
        +    const pFont = await myp5.loadFont(fontFile, 'fredTheFont');
        +    assert.ok(pFont, 'acmesa.ttf loaded');
        +    assert.equal(pFont.name, 'fredTheFont');
        +    assert.isTrue(pFont instanceof p5.Font);
        +  });
        +
        +  test('loadFont.then', async () => new Promise(done => {
        +
        +    myp5.loadFont(fontFile, 'acmesa').then(pFont => {
        +      assert.ok(pFont, 'acmesa.ttf loaded');
        +      assert.equal(pFont.name, 'acmesa');
        +      assert.isTrue(pFont instanceof p5.Font);
        +      done();
        +    });
        +
        +  }));
        +
        +  test.skip('loadFont.callback', async () => new Promise(done => {
        +    myp5.loadFont(fontFile, (pFont) => {
        +      assert.ok(pFont, 'acmesa.ttf loaded');
        +      assert.equal(pFont.name, 'A.C.M.E. Secret Agent');
        +      assert.isTrue(pFont instanceof p5.Font);
        +      done();
        +    });
        +  }));
        +
        +});
        diff --git a/test/unit/type/p5.Font.js b/test/unit/type/p5.Font.js
        new file mode 100644
        index 0000000000..4428df7f86
        --- /dev/null
        +++ b/test/unit/type/p5.Font.js
        @@ -0,0 +1,42 @@
        +import p5 from '../../../src/app.js';
        +
        +suite('p5.Font', function () {
        +  var myp5;
        +
        +  beforeEach(function () {
        +    myp5 = new p5(function (p) {
        +      p.setup = function () { };
        +      p.draw = function () { };
        +    });
        +  });
        +
        +  afterEach(function () {
        +    myp5.remove();
        +  });
        +
        +  // tests ////////////////////////////////////////////////
        +  const fontFile = '/unit/assets/acmesa.ttf';
        +  const textString = 'Lorem ipsum dolor sit amet.';
        +
        +  test('textBounds', async () => {
        +    const pFont = await myp5.loadFont(fontFile);
        +    let bbox = pFont.textBounds(textString, 10, 30, 12);
        +    //console.log(bbox);
        +    assert.isObject(bbox);
        +    assert.property(bbox, 'x');
        +    assert.property(bbox, 'y');
        +    assert.property(bbox, 'w');
        +    assert.property(bbox, 'h');
        +  });
        +
        +  test('fontBounds', async () => {
        +    const pFont = await myp5.loadFont(fontFile);
        +    let bbox = pFont.fontBounds(textString, 10, 30, 12);
        +    //console.log(bbox);
        +    assert.isObject(bbox);
        +    assert.property(bbox, 'x');
        +    assert.property(bbox, 'y');
        +    assert.property(bbox, 'w');
        +    assert.property(bbox, 'h');
        +  });
        +});
        diff --git a/test/unit/typography/attributes.js b/test/unit/typography/attributes.js
        deleted file mode 100644
        index ebfa6b552f..0000000000
        --- a/test/unit/typography/attributes.js
        +++ /dev/null
        @@ -1,120 +0,0 @@
        -suite('Typography Attributes', function() {
        -  let myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        -
        -  teardown(function() {
        -    myp5.remove();
        -  });
        -
        -  suite('p5.prototype.textAlign', function() {
        -    test('wrong param at #0', function() {
        -      assert.validationError(function() {
        -        myp5.textAlign('a');
        -      });
        -    });
        -    test('wrong param at #1', function() {
        -      assert.validationError(function() {
        -        myp5.textAlign(myp5.CENTER, 'a');
        -      });
        -    });
        -    test('wrong param at #0. vertAlign as #0 param.', function() {
        -      assert.validationError(function() {
        -        myp5.textAlign(myp5.BOTTOM);
        -      });
        -    });
        -    test('wrong param at #1. horizAlign as #1 param.', function() {
        -      assert.validationError(function() {
        -        myp5.textAlign(myp5.CENTER, myp5.LEFT);
        -      });
        -    });
        -  });
        -
        -  suite('p5.prototype.textLeading', function() {
        -    test('sets and gets the spacing value', function() {
        -      myp5.textLeading(20);
        -      assert.strictEqual(myp5.textLeading(), 20);
        -    });
        -    test('should work for negative spacing value', function() {
        -      myp5.textLeading(-20);
        -      assert.strictEqual(myp5.textLeading(), -20);
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.textLeading('C');
        -      });
        -    });
        -    test.skip('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.textLeading('25');
        -      });
        -    });
        -  });
        -
        -  suite('p5.prototype.textSize', function() {
        -    test('sets and gets the font size', function() {
        -      myp5.textSize(24);
        -      assert.strictEqual(myp5.textSize(), 24);
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.textSize('A');
        -      });
        -    });
        -    test.skip('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.textSize('30');
        -      });
        -    });
        -  });
        -
        -  suite('p5.prototype.textStyle', function() {
        -    test('sets and gets the font style', function() {
        -      myp5.textStyle(myp5.ITALIC);
        -      assert.strictEqual(myp5.textStyle(), myp5.ITALIC);
        -    });
        -    test('wrong param at #0', function() {
        -      assert.validationError(function() {
        -        myp5.textStyle('a');
        -      });
        -    });
        -  });
        -
        -  suite('p5.prototype.textWidth', function() {
        -    test('should return a number for char input', function() {
        -      assert.isNumber(myp5.textWidth('P'));
        -    });
        -    test('should return a number for string input.', function() {
        -      assert.isNumber(myp5.textWidth('p5.js'));
        -    });
        -    // Either should not throw error
        -    test('should return a number for number input', function() {
        -      assert.isNumber(myp5.textWidth('p5.js'));
        -    });
        -  });
        -
        -  suite('p5.prototype.textAscent', function() {
        -    test('should return a number', function() {
        -      assert.isNumber(myp5.textAscent());
        -    });
        -  });
        -
        -  suite('p5.prototype.textDescent', function() {
        -    test('should return a number', function() {
        -      assert.isNumber(myp5.textDescent());
        -    });
        -  });
        -
        -  suite('p5.prototype.textWrap', function() {
        -    test('returns textWrap text attribute', function() {
        -      assert.strictEqual(myp5.textWrap(myp5.WORD), myp5.WORD);
        -    });
        -  });
        -});
        diff --git a/test/unit/typography/loadFont.js b/test/unit/typography/loadFont.js
        deleted file mode 100644
        index b0c4351d34..0000000000
        --- a/test/unit/typography/loadFont.js
        +++ /dev/null
        @@ -1,162 +0,0 @@
        -suite('Loading Displaying Fonts', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        -
        -  teardown(function() {
        -    myp5.remove();
        -  });
        -
        -  suite('p5.prototype.loadFont', function() {
        -    var invalidFile = '404file';
        -    var fontFile = 'manual-test-examples/p5.Font/acmesa.ttf';
        -
        -    test('_friendlyFileLoadError is called', async function() {
        -      const _friendlyFileLoadErrorStub = sinon.stub(
        -        p5,
        -        '_friendlyFileLoadError'
        -      );
        -      try {
        -        await promisedSketch(function(sketch, resolve, reject) {
        -          sketch.preload = function() {
        -            sketch.loadFont(invalidFile, reject, resolve);
        -          };
        -        });
        -        expect(
        -          _friendlyFileLoadErrorStub.calledOnce,
        -          'p5._friendlyFileLoadError was not called'
        -        ).to.be.true;
        -      } finally {
        -        _friendlyFileLoadErrorStub.restore();
        -      }
        -    });
        -
        -    testSketchWithPromise('error prevents sketch continuing', function(
        -      sketch,
        -      resolve,
        -      reject
        -    ) {
        -      sketch.preload = function() {
        -        sketch.loadFont(invalidFile, reject, function() {
        -          setTimeout(resolve, 50);
        -        });
        -      };
        -
        -      sketch.setup = function() {
        -        reject(new Error('Setup called'));
        -      };
        -
        -      sketch.draw = function() {
        -        reject(new Error('Draw called'));
        -      };
        -    });
        -
        -    testSketchWithPromise('error callback is called', function(
        -      sketch,
        -      resolve,
        -      reject
        -    ) {
        -      sketch.preload = function() {
        -        sketch.loadFont(
        -          invalidFile,
        -          function() {
        -            reject(new Error('Success callback executed.'));
        -          },
        -          function() {
        -            // Wait a bit so that if both callbacks are executed we will get an error.
        -            setTimeout(resolve, 50);
        -          }
        -        );
        -      };
        -    });
        -
        -    testSketchWithPromise('loading correctly triggers setup', function(
        -      sketch,
        -      resolve,
        -      reject
        -    ) {
        -      sketch.preload = function() {
        -        sketch.loadFont(fontFile);
        -      };
        -
        -      sketch.setup = function() {
        -        resolve();
        -      };
        -    });
        -
        -    testSketchWithPromise('success callback is called', function(
        -      sketch,
        -      resolve,
        -      reject
        -    ) {
        -      var hasBeenCalled = false;
        -      sketch.preload = function() {
        -        sketch.loadFont(
        -          fontFile,
        -          function() {
        -            hasBeenCalled = true;
        -          },
        -          function(err) {
        -            reject(new Error('Error callback was entered: ' + err));
        -          }
        -        );
        -      };
        -
        -      sketch.setup = function() {
        -        if (!hasBeenCalled) {
        -          reject(new Error('Setup called prior to success callback'));
        -        } else {
        -          setTimeout(resolve, 50);
        -        }
        -      };
        -    });
        -
        -    test('returns a p5.Font object', async function() {
        -      const font = await promisedSketch(function(sketch, resolve, reject) {
        -        let _font;
        -        sketch.preload = function() {
        -          _font = sketch.loadFont(fontFile, function() {}, reject);
        -        };
        -
        -        sketch.setup = function() {
        -          resolve(_font);
        -        };
        -      });
        -      assert.instanceOf(font, p5.Font);
        -    });
        -
        -    test('passes a p5.Font object to success callback', async function() {
        -      const font = await promisedSketch(function(sketch, resolve, reject) {
        -        sketch.preload = function() {
        -          sketch.loadFont(fontFile, resolve, reject);
        -        };
        -      });
        -      assert.isObject(font);
        -    });
        -  });
        -
        -  suite('p5.prototype.textFont', function() {
        -    test('sets the current font as Georgia', function() {
        -      myp5.textFont('Georgia');
        -      assert.strictEqual(myp5.textFont(), 'Georgia');
        -    });
        -
        -    test('sets the current font as Helvetica', function() {
        -      myp5.textFont('Helvetica');
        -      assert.strictEqual(myp5.textFont(), 'Helvetica');
        -    });
        -
        -    test('sets the current font and text size', function() {
        -      myp5.textFont('Courier New', 24);
        -      assert.strictEqual(myp5.textFont(), 'Courier New');
        -      assert.strictEqual(myp5.textSize(), 24);
        -    });
        -  });
        -});
        diff --git a/test/unit/typography/p5.Font.js b/test/unit/typography/p5.Font.js
        deleted file mode 100644
        index cff62c28d0..0000000000
        --- a/test/unit/typography/p5.Font.js
        +++ /dev/null
        @@ -1,63 +0,0 @@
        -suite('p5.Font', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        -
        -  teardown(function() {
        -    myp5.remove();
        -  });
        -
        -  suite('p5.Font.prototype.textBounds', function() {
        -    test('returns a tight bounding box for the given text string', async function() {
        -      let fontFile = 'manual-test-examples/p5.Font/acmesa.ttf';
        -      const bbox = await promisedSketch(function(sketch, resolve, reject) {
        -        let _font;
        -        let textString = 'Lorem ipsum dolor sit amet.';
        -        sketch.preload = function() {
        -          _font = sketch.loadFont(fontFile, function() {}, reject);
        -        };
        -        sketch.setup = function() {
        -          let _bbox = _font.textBounds(textString, 10, 30, 12);
        -          resolve(_bbox);
        -        };
        -      });
        -      assert.isObject(bbox);
        -      assert.property(bbox, 'x');
        -      assert.property(bbox, 'y');
        -      assert.property(bbox, 'w');
        -      assert.property(bbox, 'h');
        -    });
        -  });
        -
        -  suite('p5.Font.prototype.textToPoints', function() {
        -    test('returns array of points', async function() {
        -      let fontFile = 'manual-test-examples/p5.Font/acmesa.ttf';
        -      const points = await promisedSketch(function(sketch, resolve, reject) {
        -        let _font;
        -        sketch.preload = function() {
        -          _font = sketch.loadFont(fontFile, function() {}, reject);
        -        };
        -        sketch.setup = function() {
        -          let _points = _font.textToPoints('p5', 0, 0, 10, {
        -            sampleFactor: 5,
        -            simplifyThreshold: 0
        -          });
        -          resolve(_points);
        -        };
        -      });
        -      assert.isArray(points);
        -      points.forEach(p => {
        -        assert.property(p, 'x');
        -        assert.property(p, 'y');
        -        assert.property(p, 'alpha');
        -      });
        -    });
        -  });
        -});
        diff --git a/test/unit/utilities/array_functions.js b/test/unit/utilities/array_functions.js
        index 3a92de35ef..c34e92715b 100644
        --- a/test/unit/utilities/array_functions.js
        +++ b/test/unit/utilities/array_functions.js
        @@ -1,60 +1,53 @@
        -suite('Array', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import arrayFunctions from '../../../src/utilities/array_functions';
        +import random from '../../../src/math/random';
         
        -  teardown(function() {
        -    myp5.remove();
        +suite('Array', function() {
        +  beforeAll(function() {
        +    arrayFunctions(mockP5, mockP5Prototype);
        +    random(mockP5, mockP5Prototype);
           });
         
        -  var result;
        -
           suite('p5.prototype.append', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.append);
        -      assert.typeOf(myp5.append, 'function');
        +      assert.ok(mockP5Prototype.append);
        +      assert.typeOf(mockP5Prototype.append, 'function');
             });
        +
             test('should return an array with appended value', function() {
        -      result = myp5.append([], 1);
        +      const result = mockP5Prototype.append([], 1);
               assert.typeOf(result, 'Array');
               assert.deepEqual(result, [1]);
             });
           });
         
           suite('p5.prototype.arrayCopy', function() {
        -    var src, dest;
        -    setup(function() {
        +    let src, dest;
        +    beforeEach(function() {
               src = [1, 2, 3, 4, 5];
               dest = [6, 7, 8];
             });
         
             test('should be a function', function() {
        -      assert.ok(myp5.arrayCopy);
        -      assert.typeOf(myp5.arrayCopy, 'function');
        +      assert.ok(mockP5Prototype.arrayCopy);
        +      assert.typeOf(mockP5Prototype.arrayCopy, 'function');
             });
         
             suite('src, dst', function() {
               test('should return fully copied array', function() {
        -        myp5.arrayCopy(src, dest);
        +        mockP5Prototype.arrayCopy(src, dest);
                 assert.deepEqual(dest, src);
               });
             });
         
             suite('src, dst, len', function() {
               test('should return an array with first 2 elements copied over', function() {
        -        myp5.arrayCopy(src, dest, 2);
        +        mockP5Prototype.arrayCopy(src, dest, 2);
                 assert.deepEqual(dest, [1, 2, 8]);
               });
         
               test('should return an array with first 4 elements copied over', function() {
        -        myp5.arrayCopy(src, dest, 4);
        +        mockP5Prototype.arrayCopy(src, dest, 4);
                 assert.deepEqual(dest, [1, 2, 3, 4]);
               });
             });
        @@ -62,17 +55,17 @@ suite('Array', function() {
             suite('src, srcPosition, dst, dstPosition, length', function() {
               // src[1 - 2] is src[1] and src[2]
               test('should copy src[1 - 2] to dst[0 - 1]', function() {
        -        myp5.arrayCopy(src, 1, dest, 0, 2);
        +        mockP5Prototype.arrayCopy(src, 1, dest, 0, 2);
                 assert.deepEqual(dest, [2, 3, 8]);
               });
         
               test('should copy src[1 - 2] to dst [1 - 2]', function() {
        -        myp5.arrayCopy(src, 1, dest, 1, 2);
        +        mockP5Prototype.arrayCopy(src, 1, dest, 1, 2);
                 assert.deepEqual(dest, [6, 2, 3]);
               });
         
               test('should copy src[3 - 4] to dst[0 - 1]', function() {
        -        myp5.arrayCopy(src, 3, dest, 0, 2);
        +        mockP5Prototype.arrayCopy(src, 3, dest, 0, 2);
                 assert.deepEqual(dest, [4, 5, 8]);
               });
             });
        @@ -80,44 +73,44 @@ suite('Array', function() {
         
           suite('p5.prototype.concat', function() {
             test('should concat empty arrays', function() {
        -      result = myp5.concat([], []);
        +      const result = mockP5Prototype.concat([], []);
               assert.deepEqual(result, []);
             });
         
             test('should concat arrays', function() {
        -      result = myp5.concat([1], [2, 3]);
        +      const result = mockP5Prototype.concat([1], [2, 3]);
               assert.deepEqual(result, [1, 2, 3]);
             });
           });
         
           suite('p5.prototype.reverse', function() {
             test('should reverse empty array', function() {
        -      result = myp5.reverse([]);
        +      const result = mockP5Prototype.reverse([]);
               assert.deepEqual(result, []);
             });
         
             test('should reverse array', function() {
        -      result = myp5.reverse([1, 2, 3]);
        +      const result = mockP5Prototype.reverse([1, 2, 3]);
               assert.deepEqual(result, [3, 2, 1]);
             });
           });
         
           suite('p5.prototype.shorten', function() {
             test('should not have error for shortening empty array', function() {
        -      result = myp5.shorten([]);
        +      const result = mockP5Prototype.shorten([]);
               assert.deepEqual(result, []);
             });
         
             test('should shorten array', function() {
        -      result = myp5.shorten([1, 2, 3]);
        +      const result = mockP5Prototype.shorten([1, 2, 3]);
               assert.deepEqual(result, [1, 2]);
             });
           });
         
           suite('p5.prototype.shuffle', function() {
             test('should contain all the elements of the original array', function() {
        -      let regularArr = ['ABC', 'def', myp5.createVector(), myp5.TAU, Math.E];
        -      let newArr = myp5.shuffle(regularArr);
        +      let regularArr = ['ABC', 'def', {}, Math.PI * 2, Math.E];
        +      let newArr = mockP5Prototype.shuffle(regularArr);
               let flag = true;
               for (let i = 0; i < regularArr.length; i++) {
                 if (!newArr.includes(regularArr[i])) {
        @@ -133,46 +126,46 @@ suite('Array', function() {
         
           suite('p5.prototype.sort', function() {
             test('should not have error for sorting empty array', function() {
        -      result = myp5.sort([]);
        +      const result = mockP5Prototype.sort([]);
               assert.deepEqual(result, []);
             });
         
             test('should sort alphabetic array lexicographically', function() {
        -      result = myp5.sort(['c', 'b', 'a']);
        +      const result = mockP5Prototype.sort(['c', 'b', 'a']);
               assert.deepEqual(result, ['a', 'b', 'c']);
             });
         
             test('should sort numerical array from smallest to largest', function() {
        -      result = myp5.sort([2, 1, 11]);
        +      const result = mockP5Prototype.sort([2, 1, 11]);
               assert.deepEqual(result, [1, 2, 11]);
             });
         
             test('should sort numerical array from smallest to largest for only first 2 elements', function() {
        -      result = myp5.sort([3, 1, 2, 0], 2);
        +      const result = mockP5Prototype.sort([3, 1, 2, 0], 2);
               assert.deepEqual(result, [1, 3, 2, 0]);
             });
           });
         
           suite('p5.prototype.splice', function() {
             test('should insert 4 into position 1', function() {
        -      result = myp5.splice([1, 2, 3], 4, 1);
        +      const result = mockP5Prototype.splice([1, 2, 3], 4, 1);
               assert.deepEqual(result, [1, 4, 2, 3]);
             });
         
             test('should splice in array of values', function() {
        -      result = myp5.splice([1, 2, 3], [4, 5], 1);
        +      const result = mockP5Prototype.splice([1, 2, 3], [4, 5], 1);
               assert.deepEqual(result, [1, 4, 5, 2, 3]);
             });
           });
         
           suite('p5.prototype.subset', function() {
             test('should get subset from index 1 to end', function() {
        -      result = myp5.subset([1, 2, 3], 1);
        +      const result = mockP5Prototype.subset([1, 2, 3], 1);
               assert.deepEqual(result, [2, 3]);
             });
         
             test('should subset arr[1 - 2]', function() {
        -      result = myp5.subset([1, 2, 3, 4], 1, 2);
        +      const result = mockP5Prototype.subset([1, 2, 3, 4], 1, 2);
               assert.deepEqual(result, [2, 3]);
             });
           });
        diff --git a/test/unit/utilities/conversion.js b/test/unit/utilities/conversion.js
        index f53ce969a3..a4a217cb9e 100644
        --- a/test/unit/utilities/conversion.js
        +++ b/test/unit/utilities/conversion.js
        @@ -1,50 +1,40 @@
        -suite('Conversion', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import conversion from '../../../src/utilities/conversion';
         
        -  teardown(function() {
        -    myp5.remove();
        +suite('Conversion', function() {
        +  beforeAll(function() {
        +    conversion(mockP5, mockP5Prototype);
           });
         
        -  var result;
        -
           suite('p5.prototype.float', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.float);
        -      assert.typeOf(myp5.float, 'function');
        +      assert.ok(mockP5Prototype.float);
        +      assert.typeOf(mockP5Prototype.float, 'function');
             });
         
             test('should convert a string to its floating point representation', function() {
        -      result = myp5.float('56.99998');
        +      const result = mockP5Prototype.float('56.99998');
               assert.typeOf(result, 'Number');
               assert.strictEqual(result, 56.99998);
             });
         
             test('should return NaN for invalid string', function() {
        -      result = myp5.float('cat');
        +      const result = mockP5Prototype.float('cat');
               assert.isNaN(result);
             });
         
             test('should return Infinity for Infinity', function() {
        -      result = myp5.float(Infinity);
        +      const result = mockP5Prototype.float(Infinity);
               assert.strictEqual(result, Infinity);
             });
         
             test('should return -Infinity for -Infinity', function() {
        -      result = myp5.float(-Infinity);
        +      const result = mockP5Prototype.float(-Infinity);
               assert.strictEqual(result, -Infinity);
             });
         
             test('should return array of floating points and Nan', function() {
        -      result = myp5.float(['1', '2.0', '3.1', 'giraffe']);
        +      const result = mockP5Prototype.float(['1', '2.0', '3.1', 'giraffe']);
               assert.typeOf(result, 'Array');
               assert.deepEqual(result, [1, 2.0, 3.1, NaN]);
             });
        @@ -52,48 +42,48 @@ suite('Conversion', function() {
         
           suite('p5.prototype.int', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.int);
        -      assert.typeOf(myp5.int, 'function');
        +      assert.ok(mockP5Prototype.int);
        +      assert.typeOf(mockP5Prototype.int, 'function');
             });
         
             test('should convert false to its integer representation i.e. 0', function() {
        -      result = myp5.int(false);
        +      const result = mockP5Prototype.int(false);
               assert.typeOf(result, 'Number');
               assert.strictEqual(result, 0);
             });
         
             test('should convert true to its integer representation i.e. 1', function() {
        -      result = myp5.int(true);
        +      const result = mockP5Prototype.int(true);
               assert.strictEqual(result, 1);
             });
         
             test('should convert a string to its integer representation', function() {
        -      result = myp5.int('1001');
        +      const result = mockP5Prototype.int('1001');
               assert.strictEqual(result, 1001);
             });
         
             test('should return NaN for invalid string', function() {
        -      result = myp5.int('cat');
        +      const result = mockP5Prototype.int('cat');
               assert.isNaN(result);
             });
         
             test('should return Infinity for Infinity', function() {
        -      result = myp5.int(Infinity);
        +      const result = mockP5Prototype.int(Infinity);
               assert.strictEqual(result, Infinity);
             });
         
             test('should return -Infinity for -Infinity', function() {
        -      result = myp5.int(-Infinity);
        +      const result = mockP5Prototype.int(-Infinity);
               assert.strictEqual(result, -Infinity);
             });
         
             test('should convert float to its integer representation', function() {
        -      result = myp5.int('-1001.9');
        +      const result = mockP5Prototype.int('-1001.9');
               assert.strictEqual(result, -1001);
             });
         
             test('should return array of integers and NaN', function() {
        -      result = myp5.int(['1', '2.3', '-3.5', 'giraffe', false, 4.7]);
        +      const result = mockP5Prototype.int(['1', '2.3', '-3.5', 'giraffe', false, 4.7]);
               assert.typeOf(result, 'Array');
               assert.deepEqual(result, [1, 2, -3, NaN, 0, 4]);
             });
        @@ -101,28 +91,28 @@ suite('Conversion', function() {
         
           suite('p5.prototype.str', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.str);
        -      assert.typeOf(myp5.str, 'function');
        +      assert.ok(mockP5Prototype.str);
        +      assert.typeOf(mockP5Prototype.str, 'function');
             });
         
             test('should convert false to string', function() {
        -      result = myp5.str(false);
        +      const result = mockP5Prototype.str(false);
               assert.typeOf(result, 'String');
               assert.strictEqual(result, 'false');
             });
         
             test('should convert true to string', function() {
        -      result = myp5.str(true);
        +      const result = mockP5Prototype.str(true);
               assert.strictEqual(result, 'true');
             });
         
             test('should convert a number to string', function() {
        -      result = myp5.str(45);
        +      const result = mockP5Prototype.str(45);
               assert.strictEqual(result, '45');
             });
         
             test('should return array of strings', function() {
        -      result = myp5.str([1, 2.3, true, -4.5]);
        +      const result = mockP5Prototype.str([1, 2.3, true, -4.5]);
               assert.typeOf(result, 'Array');
               assert.deepEqual(result, ['1', '2.3', 'true', '-4.5']);
             });
        @@ -130,52 +120,52 @@ suite('Conversion', function() {
         
           suite('p5.prototype.boolean', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.boolean);
        -      assert.typeOf(myp5.boolean, 'function');
        +      assert.ok(mockP5Prototype.boolean);
        +      assert.typeOf(mockP5Prototype.boolean, 'function');
             });
         
             test('should convert 1 to true', function() {
        -      result = myp5.boolean(1);
        +      const result = mockP5Prototype.boolean(1);
               assert.strictEqual(result, true);
             });
         
             test('should convert a number to true', function() {
        -      result = myp5.boolean(154);
        +      const result = mockP5Prototype.boolean(154);
               assert.strictEqual(result, true);
             });
         
             test('should return true for Infinity', function() {
        -      result = myp5.boolean(Infinity);
        +      const result = mockP5Prototype.boolean(Infinity);
               assert.strictEqual(result, true);
             });
         
             test('should convert 0 to false', function() {
        -      result = myp5.boolean(0);
        +      const result = mockP5Prototype.boolean(0);
               assert.strictEqual(result, false);
             });
         
             test('should convert a string to false', function() {
        -      result = myp5.boolean('1');
        +      const result = mockP5Prototype.boolean('1');
               assert.strictEqual(result, false);
             });
         
             test('should convert a string to false', function() {
        -      result = myp5.boolean('0');
        +      const result = mockP5Prototype.boolean('0');
               assert.strictEqual(result, false);
             });
         
             test('should convert "true" to true', function() {
        -      result = myp5.boolean('true');
        +      const result = mockP5Prototype.boolean('true');
               assert.strictEqual(result, true);
             });
         
             test('should return false for empty string', function() {
        -      result = myp5.boolean('');
        +      const result = mockP5Prototype.boolean('');
               assert.strictEqual(result, false);
             });
         
             test('should return array of boolean', function() {
        -      result = myp5.boolean([1, true, -4.5, Infinity, 'cat', '23']);
        +      const result = mockP5Prototype.boolean([1, true, -4.5, Infinity, 'cat', '23']);
               assert.typeOf(result, 'Array');
               assert.deepEqual(result, [true, true, true, true, false, false]);
             });
        @@ -183,42 +173,42 @@ suite('Conversion', function() {
         
           suite('p5.prototype.byte', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.byte);
        -      assert.typeOf(myp5.byte, 'function');
        +      assert.ok(mockP5Prototype.byte);
        +      assert.typeOf(mockP5Prototype.byte, 'function');
             });
         
             test('should return 127 for 127', function() {
        -      result = myp5.byte(127);
        +      const result = mockP5Prototype.byte(127);
               assert.strictEqual(result, 127);
             });
         
             test('should return -128 for 128', function() {
        -      result = myp5.byte(128);
        +      const result = mockP5Prototype.byte(128);
               assert.strictEqual(result, -128);
             });
         
             test('should return 23 for 23.4', function() {
        -      result = myp5.byte(23.4);
        +      const result = mockP5Prototype.byte(23.4);
               assert.strictEqual(result, 23);
             });
         
             test('should return 1 for true', function() {
        -      result = myp5.byte(true);
        +      const result = mockP5Prototype.byte(true);
               assert.strictEqual(result, 1);
             });
         
             test('should return 23 for "23.4"', function() {
        -      result = myp5.byte('23.4');
        +      const result = mockP5Prototype.byte('23.4');
               assert.strictEqual(result, 23);
             });
         
             test('should return NaN for invalid string', function() {
        -      result = myp5.byte('cat');
        +      const result = mockP5Prototype.byte('cat');
               assert.isNaN(result);
             });
         
             test('should return array', function() {
        -      result = myp5.byte([0, 255, '100']);
        +      const result = mockP5Prototype.byte([0, 255, '100']);
               assert.typeOf(result, 'Array');
               assert.deepEqual(result, [0, -1, 100]);
             });
        @@ -226,23 +216,23 @@ suite('Conversion', function() {
         
           suite('p5.prototype.char', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.char);
        -      assert.typeOf(myp5.char, 'function');
        +      assert.ok(mockP5Prototype.char);
        +      assert.typeOf(mockP5Prototype.char, 'function');
             });
         
             test('should return the char representation of the number', function() {
        -      result = myp5.char(65);
        +      const result = mockP5Prototype.char(65);
               assert.typeOf(result, 'String');
               assert.strictEqual(result, 'A');
             });
         
             test('should return the char representation of the string', function() {
        -      result = myp5.char('65');
        +      const result = mockP5Prototype.char('65');
               assert.strictEqual(result, 'A');
             });
         
             test('should return array', function() {
        -      result = myp5.char([65, 66, '67']);
        +      const result = mockP5Prototype.char([65, 66, '67']);
               assert.typeOf(result, 'Array');
               assert.deepEqual(result, ['A', 'B', 'C']);
             });
        @@ -250,18 +240,18 @@ suite('Conversion', function() {
         
           suite('p5.prototype.unchar', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.unchar);
        -      assert.typeOf(myp5.unchar, 'function');
        +      assert.ok(mockP5Prototype.unchar);
        +      assert.typeOf(mockP5Prototype.unchar, 'function');
             });
         
             test('should return the integer representation of char', function() {
        -      result = myp5.unchar('A');
        +      const result = mockP5Prototype.unchar('A');
               assert.typeOf(result, 'Number');
               assert.strictEqual(result, 65);
             });
         
             test('should return array of numbers', function() {
        -      result = myp5.unchar(['A', 'B', 'C']);
        +      const result = mockP5Prototype.unchar(['A', 'B', 'C']);
               assert.typeOf(result, 'Array');
               assert.deepEqual(result, [65, 66, 67]);
             });
        @@ -269,30 +259,30 @@ suite('Conversion', function() {
         
           suite('p5.prototype.hex', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.hex);
        -      assert.typeOf(myp5.hex, 'function');
        +      assert.ok(mockP5Prototype.hex);
        +      assert.typeOf(mockP5Prototype.hex, 'function');
             });
         
             test('should return the hex representation of the number', function() {
        -      result = myp5.hex(65);
        +      const result = mockP5Prototype.hex(65);
               assert.typeOf(result, 'String');
               assert.strictEqual(result, '00000041');
             });
         
             test('should return FFFFFFFF for Infinity', function() {
        -      result = myp5.hex(Infinity);
        +      const result = mockP5Prototype.hex(Infinity);
               assert.typeOf(result, 'String');
               assert.strictEqual(result, 'FFFFFFFF');
             });
         
             test('should return 00000000 for -Infinity', function() {
        -      result = myp5.hex(-Infinity);
        +      const result = mockP5Prototype.hex(-Infinity);
               assert.typeOf(result, 'String');
               assert.strictEqual(result, '00000000');
             });
         
             test('should return array', function() {
        -      result = myp5.hex([65, 66, 67]);
        +      const result = mockP5Prototype.hex([65, 66, 67]);
               assert.typeOf(result, 'Array');
               assert.deepEqual(result, ['00000041', '00000042', '00000043']);
             });
        @@ -300,28 +290,28 @@ suite('Conversion', function() {
         
           suite('p5.prototype.unhex', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.unhex);
        -      assert.typeOf(myp5.unchar, 'function');
        +      assert.ok(mockP5Prototype.unhex);
        +      assert.typeOf(mockP5Prototype.unchar, 'function');
             });
         
             test('should return the integer representation of hex', function() {
        -      result = myp5.unhex('00000041');
        +      const result = mockP5Prototype.unhex('00000041');
               assert.typeOf(result, 'Number');
               assert.strictEqual(result, 65);
             });
         
             test('should return the NaN for empty string', function() {
        -      result = myp5.unhex('');
        +      const result = mockP5Prototype.unhex('');
               assert.isNaN(result);
             });
         
        -    test.skip('should return the NaN for invalid hex string', function() {
        -      result = myp5.unhex('cat');
        +    test('should return the NaN for invalid hex string', function() {
        +      const result = mockP5Prototype.unhex('lorem');
               assert.isNaN(result);
             });
         
             test('should return array of numbers', function() {
        -      result = myp5.unhex(['00000041', '00000042', '00000043']);
        +      const result = mockP5Prototype.unhex(['00000041', '00000042', '00000043']);
               assert.typeOf(result, 'Array');
               assert.deepEqual(result, [65, 66, 67]);
             });
        diff --git a/test/unit/utilities/string_functions.js b/test/unit/utilities/string_functions.js
        index 97ceed0a01..fb443d580c 100644
        --- a/test/unit/utilities/string_functions.js
        +++ b/test/unit/utilities/string_functions.js
        @@ -1,218 +1,198 @@
        -suite('String functions', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import stringFunctions from '../../../src/utilities/string_functions';
         
        -  teardown(function() {
        -    myp5.remove();
        +suite('String functions', function() {
        +  beforeAll(function() {
        +    stringFunctions(mockP5, mockP5Prototype);
           });
         
        -  var result;
        -
           suite('p5.prototype.join', function() {
        -    var join = p5.prototype.join;
             test('should be a function', function() {
        -      assert.ok(join);
        +      assert.ok(mockP5Prototype.join);
             });
         
             test('should return joined string', function() {
               var arr = ['foo', 'bar'];
               var sep = '-';
        -      result = myp5.join(arr, sep);
        +      const result = mockP5Prototype.join(arr, sep);
               assert.equal(result, 'foo-bar');
             });
           });
         
           suite('p5.prototype.match', function() {
        -    var match = p5.prototype.match;
             test('should be a function', function() {
        -      assert.ok(match);
        +      assert.ok(mockP5Prototype.match);
             });
         
             test('should return correct index of match strings', function() {
               var str = 'Where is the duckling in this ducky duck string?';
               var regexp = 'duck';
        -      result = myp5.match(str, regexp);
        +      const result = mockP5Prototype.match(str, regexp);
               assert.equal(result.index, 13);
             });
           });
         
           suite('p5.prototype.matchAll', function() {
        -    var matchAll = p5.prototype.matchAll;
             test('should be a function', function() {
        -      assert.ok(matchAll);
        +      assert.ok(mockP5Prototype.matchAll);
             });
         
             test('should return correct array of strings', function() {
               var str = 'Where is the duckling in this ducky duck string?';
               var regexp = 'duck';
        -      result = myp5.matchAll(str, regexp);
        +      const result = mockP5Prototype.matchAll(str, regexp);
               assert.equal(result.length, 3);
             });
           });
         
           suite('p5.prototype.nf', function() {
        -    var nf = p5.prototype.nf;
             test('should be a function', function() {
        -      assert.ok(nf);
        +      assert.ok(mockP5Prototype.nf);
             });
         
             test('should return correct string', function() {
               var num = 1234;
        -      result = myp5.nf(num, 3);
        +      const result = mockP5Prototype.nf(num, 3);
               assert.equal(result, '1234');
             });
         
             test('should return correct string', function() {
               var num = 1234;
        -      result = myp5.nf(num, 5);
        +      const result = mockP5Prototype.nf(num, 5);
               assert.equal(result, '01234');
             });
         
             test('should return correct string', function() {
               var num = 1234;
        -      result = myp5.nf(num, 3, 3);
        +      const result = mockP5Prototype.nf(num, 3, 3);
               assert.equal(result, '1234.000');
             });
         
             test('should return correct string', function() {
               var num = 3.141516;
        -      result = myp5.nf(num, '2'); // automatic conversion?
        +      const result = mockP5Prototype.nf(num, '2'); // automatic conversion?
               assert.equal(result, '03.141516');
             });
         
             test('should return correct string', function() {
               var num = 3.141516;
        -      result = myp5.nf(num, '2', '2'); // automatic conversion?
        +      const result = mockP5Prototype.nf(num, '2', '2'); // automatic conversion?
               assert.equal(result, '03.14');
             });
         
             test('should return correct string', function() {
               var num = 3.141516e-2;
        -      result = myp5.nf(num, '3', '4'); // automatic conversion?
        +      const result = mockP5Prototype.nf(num, '3', '4'); // automatic conversion?
               assert.equal(result, '000.0314');
             });
         
             test('should return correct string', function() {
               var num = 3.141516e7;
        -      result = myp5.nf(num, '3', '4'); // automatic conversion?
        +      const result = mockP5Prototype.nf(num, '3', '4'); // automatic conversion?
               assert.equal(result, '31415160.0000');
             });
         
             test('should return correct string', function() {
               var num = 123.45;
        -      result = myp5.nf(num, 3, 0);
        +      const result = mockP5Prototype.nf(num, 3, 0);
               assert.equal(result, '123');
             });
         
             test('should return correct string', function() {
               var num = -123;
        -      result = myp5.nf(num, 5);
        +      const result = mockP5Prototype.nf(num, 5);
               assert.equal(result, '-00123');
             });
           });
         
           suite('p5.prototype.nfc', function() {
        -    var nfc = p5.prototype.nfc;
             test('should be a function', function() {
        -      assert.ok(nfc);
        +      assert.ok(mockP5Prototype.nfc);
             });
         
             test('should return correct string', function() {
               var num = 32000;
        -      result = myp5.nfc(num, 3);
        +      const result = mockP5Prototype.nfc(num, 3);
               assert.equal(result, '32,000.000');
             });
         
             test('should return correct string', function() {
               var num = 32000;
        -      result = myp5.nfc(num, '3'); // automatic conversion?
        +      const result = mockP5Prototype.nfc(num, '3'); // automatic conversion?
               assert.equal(result, '32,000.000');
             });
           });
         
           suite('p5.prototype.nfp', function() {
        -    var nfp = p5.prototype.nfp;
             test('should be a function', function() {
        -      assert.ok(nfp);
        +      assert.ok(mockP5Prototype.nfp);
             });
         
             test('should return correct string', function() {
               var num = -32000;
        -      result = myp5.nfp(num, 3);
        +      const result = mockP5Prototype.nfp(num, 3);
               assert.equal(result, '-32000');
             });
         
             test('should return correct string', function() {
               var num = 32000;
        -      result = myp5.nfp(num, 3); // automatic conversion?
        +      const result = mockP5Prototype.nfp(num, 3); // automatic conversion?
               assert.equal(result, '+32000');
             });
           });
         
           suite('p5.prototype.nfs', function() {
        -    var nfs = p5.prototype.nfs;
             test('should be a function', function() {
        -      assert.ok(nfs);
        +      assert.ok(mockP5Prototype.nfs);
             });
         
             test('should return correct string', function() {
               var num = -32000;
        -      result = myp5.nfs(num, 3);
        +      const result = mockP5Prototype.nfs(num, 3);
               assert.equal(result, '-32000');
             });
         
             test('should return correct string', function() {
               var num = 32000;
        -      result = myp5.nfs(num, 3); // automatic conversion?
        +      const result = mockP5Prototype.nfs(num, 3); // automatic conversion?
               assert.equal(result, ' 32000');
             });
           });
         
           suite('p5.prototype.split', function() {
        -    var split = p5.prototype.split;
             test('should be a function', function() {
        -      assert.ok(split);
        +      assert.ok(mockP5Prototype.split);
             });
         
             test('should return correct index of match strings', function() {
               var str = 'parsely, sage, rosemary, thyme';
               var regexp = ',';
        -      result = myp5.split(str, regexp);
        +      const result = mockP5Prototype.split(str, regexp);
               assert.equal(result.length, 4);
             });
           });
         
           suite('p5.prototype.splitTokens', function() {
        -    var splitTokens = p5.prototype.splitTokens;
             test('should be a function', function() {
        -      assert.ok(splitTokens);
        +      assert.ok(mockP5Prototype.splitTokens);
             });
         
             test('should return correct index of match strings', function() {
               var str = 'parsely, sage, rosemary, thyme';
               var regexp = ',';
        -      result = myp5.splitTokens(str, regexp);
        +      const result = mockP5Prototype.splitTokens(str, regexp);
               assert.equal(result.length, 4);
             });
           });
         
           suite('p5.prototype.trim', function() {
        -    var trim = p5.prototype.trim;
             test('should be a function', function() {
        -      assert.ok(trim);
        +      assert.ok(mockP5Prototype.trim);
             });
         
             test('should return correct strings', function() {
               var str = '     oh so roomy     ';
        -      result = myp5.trim(str);
        +      const result = mockP5Prototype.trim(str);
               assert.equal(result, 'oh so roomy');
             });
           });
        diff --git a/test/unit/utilities/time_date.js b/test/unit/utilities/time_date.js
        index df95c29847..189d7c8e68 100644
        --- a/test/unit/utilities/time_date.js
        +++ b/test/unit/utilities/time_date.js
        @@ -1,29 +1,19 @@
        -suite('time and date', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        +import { mockP5, mockP5Prototype } from '../../js/mocks';
        +import timeDate from '../../../src/utilities/time_date';
         
        -  teardown(function() {
        -    myp5.remove();
        +suite('time and date', function() {
        +  beforeAll(function() {
        +    timeDate(mockP5, mockP5Prototype);
           });
         
        -  var result;
        -
           suite('p5.prototype.year', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.year);
        -      assert.typeOf(myp5.year, 'function');
        +      assert.ok(mockP5Prototype.year);
        +      assert.typeOf(mockP5Prototype.year, 'function');
             });
         
             test('should return this year', function() {
        -      result = myp5.year();
        +      const result = mockP5Prototype.year();
               var jsYear = new Date().getFullYear();
               assert.equal(result, jsYear);
             });
        @@ -31,25 +21,25 @@ suite('time and date', function() {
         
           suite('p5.prototype.day', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.day);
        -      assert.typeOf(myp5.day, 'function');
        +      assert.ok(mockP5Prototype.day);
        +      assert.typeOf(mockP5Prototype.day, 'function');
             });
         
             test('should return todays day', function() {
               var jsDay = new Date().getDate();
        -      result = myp5.day();
        +      const result = mockP5Prototype.day();
               assert.equal(result, jsDay);
             });
           });
         
           suite('p5.prototype.month', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.month);
        -      assert.typeOf(myp5.month, 'function');
        +      assert.ok(mockP5Prototype.month);
        +      assert.typeOf(mockP5Prototype.month, 'function');
             });
         
             test("should return today's month", function() {
        -      result = myp5.month();
        +      const result = mockP5Prototype.month();
               var jsMonth = new Date().getMonth() + 1;
               assert.equal(result, jsMonth);
             });
        @@ -57,39 +47,39 @@ suite('time and date', function() {
         
           suite('p5.prototype.hour', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.hour);
        -      assert.typeOf(myp5.hour, 'function');
        +      assert.ok(mockP5Prototype.hour);
        +      assert.typeOf(mockP5Prototype.hour, 'function');
             });
         
             test('should return this hour', function() {
               var jsHour = new Date().getHours();
        -      result = myp5.hour();
        +      const result = mockP5Prototype.hour();
               assert.equal(result, jsHour);
             });
           });
         
           suite('p5.prototype.second', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.second);
        -      assert.typeOf(myp5.second, 'function');
        +      assert.ok(mockP5Prototype.second);
        +      assert.typeOf(mockP5Prototype.second, 'function');
             });
         
             test('should return this second', function() {
               var jsSecond = new Date().getSeconds();
        -      result = myp5.second();
        +      const result = mockP5Prototype.second();
               assert.equal(result, jsSecond); //(Math.abs(jsSecond - result), '==', 0, 'everything is ok'); // in my testing, found this might be off by 1 second
             });
           });
         
           suite('p5.prototype.minute', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.minute);
        -      assert.typeOf(myp5.minute, 'function');
        +      assert.ok(mockP5Prototype.minute);
        +      assert.typeOf(mockP5Prototype.minute, 'function');
             });
         
             test('should return a number that is this minute', function() {
               var jsMinute = new Date().getMinutes();
        -      result = myp5.minute();
        +      const result = mockP5Prototype.minute();
               assert.isNumber(result);
               assert.isNumber(jsMinute);
               assert.equal(result, jsMinute);
        @@ -98,33 +88,34 @@ suite('time and date', function() {
         
           suite('p5.prototype.millis', function() {
             test('should be a function', function() {
        -      assert.ok(myp5.millis);
        -      assert.typeOf(myp5.millis, 'function');
        +      assert.ok(mockP5Prototype.millis);
        +      assert.typeOf(mockP5Prototype.millis, 'function');
             });
         
             test('result should be a number', function() {
        -      assert.isNumber(myp5.millis());
        +      assert.isNumber(mockP5Prototype.millis());
             });
         
        -    test('result should be greater than running time', function() {
        +    // TODO: need to move internal state to module
        +    test.todo('result should be greater than running time', function() {
               var runningTime = 50;
               var init_date = window.performance.now();
               // wait :\
               while (window.performance.now() - init_date <= runningTime) {
                 /* no-op */
               }
        -      assert.operator(myp5.millis(), '>', runningTime, 'everything is ok');
        +      assert.operator(mockP5Prototype.millis(), '>', runningTime, 'everything is ok');
             });
         
        -    test('result should be > newResult', function() {
        +    test.todo('result should be > newResult', function() {
               var runningTime = 50;
               var init_date = Date.now();
        -      var result = myp5.millis();
        +      const result = mockP5Prototype.millis();
               // wait :\
               while (Date.now() - init_date <= runningTime) {
                 /* no-op */
               }
        -      var newResult = myp5.millis();
        +      const newResult = mockP5Prototype.millis();
               assert.operator(newResult, '>', result, 'everything is ok');
             });
           });
        diff --git a/test/unit/visual/cases/shape_modes.js b/test/unit/visual/cases/shape_modes.js
        index 8fa4c0f951..b4020ac8b6 100644
        --- a/test/unit/visual/cases/shape_modes.js
        +++ b/test/unit/visual/cases/shape_modes.js
        @@ -1,3 +1,5 @@
        +import { visualSuite, visualTest } from "../visualTest";
        +
         /*
           Helper function that draws a shape using the specified shape mode
           p5 ............... The p5 Instance
        @@ -62,7 +64,7 @@ function shapeCorners(p5, shape, mode, x1, y1, x2, y2) {
         }
         
         
        -visualSuite('Shape Modes', function(...args) {
        +visualSuite('Shape Modes', function() {
           /*
             Comprehensive test for rendering ellipse(), arc(), and rect()
             with the different ellipseMode() / rectMode() values: CORNERS, CORNER, CENTER, RADIUS.
        diff --git a/test/unit/visual/cases/shapes.js b/test/unit/visual/cases/shapes.js
        new file mode 100644
        index 0000000000..83ec314049
        --- /dev/null
        +++ b/test/unit/visual/cases/shapes.js
        @@ -0,0 +1,504 @@
        +import { visualSuite, visualTest } from '../visualTest';
        +
        +visualSuite('Shape drawing', function() {
        +  for (const mode of ['2D', 'WebGL']) {
        +    visualSuite(`${mode} mode`, function() {
        +      const setup = (p5) => {
        +        p5.createCanvas(50, 50, mode === '2D' ? p5.P2D : p5.WEBGL);
        +        if (mode !== '2D') {
        +          p5.translate(-p5.width / 2, -p5.height / 2);
        +        }
        +        p5.background(200);
        +        p5.fill(255);
        +        p5.stroke(0);
        +      }
        +
        +      visualTest('Drawing polylines', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.vertex(10, 10);
        +        p5.vertex(15, 40);
        +        p5.vertex(40, 35);
        +        p5.vertex(25, 15);
        +        p5.vertex(15, 25);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with contours', function(p5, screenshot) {
        +        setup(p5);
        +
        +        const vertexCircle = (x, y, r, direction) => {
        +          for (let i = 0; i <= 12; i++) {
        +            const angle = p5.map(i, 0, 12, 0, p5.TWO_PI) * direction;
        +            p5.vertex(x + r * p5.cos(angle), y + r * p5.sin(angle));
        +          }
        +        }
        +
        +        p5.beginShape();
        +        vertexCircle(15, 15, 10, 1);
        +
        +        // Inner cutout
        +        p5.beginContour();
        +        vertexCircle(15, 15, 5, -1);
        +        p5.endContour();
        +
        +        // Outer shape
        +        p5.beginContour();
        +        vertexCircle(30, 30, 8, -1);
        +        p5.endContour();
        +        p5.endShape();
        +
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing triangle fans', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape(p5.TRIANGLE_FAN);
        +        p5.vertex(25, 25);
        +        for (let i = 0; i <= 12; i++) {
        +          const angle = p5.map(i, 0, 12, 0, p5.TWO_PI);
        +          p5.vertex(25 + 10*p5.cos(angle), 25 + 10*p5.sin(angle));
        +        }
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing triangle strips', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape(p5.TRIANGLE_STRIP);
        +        p5.vertex(10, 10);
        +        p5.vertex(30, 10);
        +        p5.vertex(15, 20);
        +        p5.vertex(35, 20);
        +        p5.vertex(10, 40);
        +        p5.vertex(30, 40);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing quad strips', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape(p5.QUAD_STRIP);
        +        p5.vertex(10, 10);
        +        p5.vertex(30, 10);
        +        p5.vertex(15, 20);
        +        p5.vertex(35, 20);
        +        p5.vertex(10, 40);
        +        p5.vertex(30, 40);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing closed polylines', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.vertex(10, 10);
        +        p5.vertex(15, 40);
        +        p5.vertex(40, 35);
        +        p5.vertex(25, 15);
        +        p5.vertex(15, 25);
        +        p5.endShape(p5.CLOSE);
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with curves', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.splineVertex(10, 10);
        +        p5.splineVertex(15, 40);
        +        p5.splineVertex(40, 35);
        +        p5.splineVertex(25, 15);
        +        p5.splineVertex(15, 25);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with curves in the middle of other shapes', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.vertex(10, 10);
        +        p5.vertex(40, 10);
        +        p5.splineVertex(40, 40);
        +        p5.splineVertex(10, 40);
        +        p5.endShape(p5.CLOSE);
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with curves with hidden ends', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.splineProperty('ends', p5.EXCLUDE);
        +        p5.splineVertex(10, 10);
        +        p5.splineVertex(15, 40);
        +        p5.splineVertex(40, 35);
        +        p5.splineVertex(25, 15);
        +        p5.splineVertex(15, 25);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing closed curves', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.splineVertex(10, 10);
        +        p5.splineVertex(15, 40);
        +        p5.splineVertex(40, 35);
        +        p5.splineVertex(25, 15);
        +        p5.splineVertex(15, 25);
        +        p5.endShape(p5.CLOSE);
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing simple closed curves', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.splineVertex(10, 10);
        +        p5.splineVertex(15, 40);
        +        p5.splineVertex(40, 35);
        +        p5.endShape(p5.CLOSE);
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with curves with tightness', function(p5, screenshot) {
        +        setup(p5);
        +        p5.splineProperty('tightness', -1);
        +        p5.beginShape();
        +        p5.splineVertex(10, 10);
        +        p5.splineVertex(15, 40);
        +        p5.splineVertex(40, 35);
        +        p5.splineVertex(25, 15);
        +        p5.splineVertex(15, 25);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing closed curve loops', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.splineProperty('ends', p5.EXCLUDE);
        +        p5.splineVertex(10, 10);
        +        p5.splineVertex(15, 40);
        +        p5.splineVertex(40, 35);
        +        p5.splineVertex(25, 15);
        +        p5.splineVertex(15, 25);
        +        // Repeat first 3 points
        +        p5.splineVertex(10, 10);
        +        p5.splineVertex(15, 40);
        +        p5.splineVertex(40, 35);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with cubic beziers', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.vertex(10, 10);
        +        p5.bezierVertex(10, 10, 15, 40, 40, 35);
        +        p5.bezierVertex(25, 15, 15, 25, 15, 25);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with quadratic beziers', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.vertex(10, 10);
        +        p5.quadraticVertex(10, 10, 15, 40);
        +        p5.quadraticVertex(40, 35, 25, 15);
        +        p5.quadraticVertex(15, 25, 10, 10);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Combining quadratic and cubic beziers', function (p5, screenshot) {
        +        setup(p5);
        +        p5.strokeWeight(5);
        +        p5.beginShape();
        +        p5.vertex(10, 10);
        +        p5.vertex(30, 10);
        +
        +        // Default cubic
        +        p5.bezierVertex(35, 10);
        +        p5.bezierVertex(40, 15);
        +        p5.bezierVertex(40, 20);
        +
        +        p5.vertex(40, 30);
        +
        +        p5.bezierOrder(2);
        +        p5.bezierVertex(40, 40);
        +        p5.bezierVertex(30, 40);
        +
        +        p5.vertex(10, 40);
        +
        +        p5.endShape(p5.CLOSE);
        +
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with points', function(p5, screenshot) {
        +        setup(p5);
        +        p5.strokeWeight(5);
        +        p5.beginShape(p5.POINTS);
        +        p5.vertex(10, 10);
        +        p5.vertex(15, 40);
        +        p5.vertex(40, 35);
        +        p5.vertex(25, 15);
        +        p5.vertex(15, 25);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with lines', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape(p5.LINES);
        +        p5.vertex(10, 10);
        +        p5.vertex(15, 40);
        +        p5.vertex(40, 35);
        +        p5.vertex(25, 15);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with triangles', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape(p5.TRIANGLES);
        +        p5.vertex(10, 10);
        +        p5.vertex(15, 40);
        +        p5.vertex(40, 35);
        +        p5.vertex(25, 15);
        +        p5.vertex(10, 10);
        +        p5.vertex(15, 25);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with quads', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape(p5.QUADS);
        +        p5.vertex(10, 10);
        +        p5.vertex(15, 10);
        +        p5.vertex(15, 15);
        +        p5.vertex(10, 15);
        +        p5.vertex(25, 25);
        +        p5.vertex(30, 25);
        +        p5.vertex(30, 30);
        +        p5.vertex(25, 30);
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with a single closed contour', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.vertex(10, 10);
        +        p5.vertex(40, 10);
        +        p5.vertex(40, 40);
        +        p5.vertex(10, 40);
        +
        +        p5.beginContour();
        +        p5.vertex(20, 20);
        +        p5.vertex(20, 30);
        +        p5.vertex(30, 30);
        +        p5.vertex(30, 20);
        +        p5.endContour(p5.CLOSE);
        +
        +        p5.endShape(p5.CLOSE);
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with a single unclosed contour', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.vertex(10, 10);
        +        p5.vertex(40, 10);
        +        p5.vertex(40, 40);
        +        p5.vertex(10, 40);
        +
        +        p5.beginContour();
        +        p5.vertex(20, 20);
        +        p5.vertex(20, 30);
        +        p5.vertex(30, 30);
        +        p5.vertex(30, 20);
        +        p5.endContour();
        +
        +        p5.endShape(p5.CLOSE);
        +        screenshot();
        +      });
        +
        +      visualTest('Drawing with every subshape in a contour', function(p5, screenshot) {
        +        setup(p5);
        +        p5.beginShape();
        +        p5.beginContour();
        +        p5.vertex(10, 10);
        +        p5.vertex(40, 10);
        +        p5.vertex(40, 40);
        +        p5.vertex(10, 40);
        +        p5.endContour(p5.CLOSE);
        +
        +        p5.beginContour();
        +        p5.vertex(20, 20);
        +        p5.vertex(20, 30);
        +        p5.vertex(30, 30);
        +        p5.vertex(30, 20);
        +        p5.endContour(p5.CLOSE);
        +
        +        p5.endShape();
        +        screenshot();
        +      });
        +
        +      if (mode === 'WebGL') {
        +        visualTest('3D vertex coordinates', function(p5, screenshot) {
        +          setup(p5);
        +
        +          p5.beginShape(p5.QUAD_STRIP);
        +          p5.vertex(10, 10, 0);
        +          p5.vertex(10, 40, -150);
        +          p5.vertex(40, 10, 150);
        +          p5.vertex(40, 40, 200);
        +          p5.endShape();
        +
        +          screenshot();
        +        });
        +
        +        visualTest('3D quadratic coordinates', function(p5, screenshot) {
        +          setup(p5);
        +
        +          p5.beginShape();
        +          p5.vertex(10, 10, 0);
        +          p5.vertex(10, 40, -150);
        +          p5.quadraticVertex(40, 40, 200, 40, 10, 150);
        +          p5.endShape(p5.CLOSE);
        +
        +          screenshot();
        +        });
        +
        +        visualTest('3D cubic coordinates', function(p5, screenshot) {
        +          setup(p5);
        +
        +          p5.beginShape();
        +          p5.vertex(10, 10, 0);
        +          p5.vertex(10, 40, -150);
        +          p5.bezierVertex(40, 40, 200, 40, 10, 150, 10, 10, 0);
        +          p5.endShape();
        +
        +          screenshot();
        +        });
        +
        +        visualTest('Texture coordinates', async function(p5, screenshot) {
        +          const tex = await p5.loadImage('/unit/assets/cat.jpg');
        +          setup(p5);
        +          p5.texture(tex);
        +          p5.beginShape(p5.QUAD_STRIP);
        +          p5.vertex(10, 10, 0, 0, 0);
        +          p5.vertex(45, 5, 0, tex.width, 0);
        +          p5.vertex(15, 35, 0, 0, tex.height);
        +          p5.vertex(40, 45, 0, tex.width, tex.height);
        +          p5.endShape();
        +
        +          screenshot();
        +        });
        +
        +        visualTest('Normalized texture coordinates', async function(p5, screenshot) {
        +          const tex = await p5.loadImage('/unit/assets/cat.jpg');
        +          setup(p5);
        +          p5.texture(tex);
        +          p5.textureMode(p5.NORMAL);
        +          p5.beginShape(p5.QUAD_STRIP);
        +          p5.vertex(10, 10, 0, 0, 0);
        +          p5.vertex(45, 5, 0, 1, 0);
        +          p5.vertex(15, 35, 0, 0, 1);
        +          p5.vertex(40, 45, 0, 1, 1);
        +          p5.endShape();
        +
        +          screenshot();
        +        });
        +
        +        visualTest('Per-vertex fills', function(p5, screenshot) {
        +          setup(p5);
        +          p5.beginShape(p5.QUAD_STRIP);
        +          p5.fill(0);
        +          p5.vertex(10, 10);
        +          p5.fill(255, 0, 0);
        +          p5.vertex(45, 5);
        +          p5.fill(0, 255, 0);
        +          p5.vertex(15, 35);
        +          p5.fill(255, 255, 0);
        +          p5.vertex(40, 45);
        +          p5.endShape();
        +
        +          screenshot();
        +        });
        +
        +        visualTest('Per-vertex strokes', function(p5, screenshot) {
        +          setup(p5);
        +          p5.strokeWeight(5);
        +          p5.beginShape(p5.QUAD_STRIP);
        +          p5.stroke(0);
        +          p5.vertex(10, 10);
        +          p5.stroke(255, 0, 0);
        +          p5.vertex(45, 5);
        +          p5.stroke(0, 255, 0);
        +          p5.vertex(15, 35);
        +          p5.stroke(255, 255, 0);
        +          p5.vertex(40, 45);
        +          p5.endShape();
        +
        +          screenshot();
        +        });
        +
        +        visualTest('Per-vertex normals', function(p5, screenshot) {
        +          setup(p5);
        +          p5.normalMaterial();
        +          p5.beginShape(p5.QUAD_STRIP);
        +          p5.normal(-1, -1, 1);
        +          p5.vertex(10, 10);
        +          p5.normal(1, -1, 1);
        +          p5.vertex(45, 5);
        +          p5.normal(-1, 1, 1);
        +          p5.vertex(15, 35);
        +          p5.normal(1, 1, 1);
        +          p5.vertex(40, 45);
        +          p5.endShape();
        +
        +          screenshot();
        +        });
        +
        +        visualTest('Per-control point fills', function (p5, screenshot) {
        +          setup(p5);
        +
        +          p5.noStroke();
        +          p5.beginShape();
        +          p5.bezierOrder(2);
        +          p5.fill('red');
        +          p5.vertex(10, 10);
        +          p5.fill('lime');
        +          p5.bezierVertex(40, 25);
        +          p5.fill('blue');
        +          p5.bezierVertex(10, 40);
        +          p5.endShape();
        +
        +          screenshot();
        +        });
        +
        +        visualTest('Per-control point strokes', function (p5, screenshot) {
        +          setup(p5);
        +
        +          p5.noFill();
        +          p5.strokeWeight(5);
        +          p5.beginShape();
        +          p5.bezierOrder(2);
        +          p5.stroke('red');
        +          p5.vertex(10, 10);
        +          p5.stroke('lime');
        +          p5.bezierVertex(40, 25);
        +          p5.stroke('blue');
        +          p5.bezierVertex(10, 40);
        +          p5.endShape();
        +
        +          screenshot();
        +        });
        +      }
        +    });
        +  }
        +});
        diff --git a/test/unit/visual/cases/typography.js b/test/unit/visual/cases/typography.js
        index 664c7b71ca..63cd0f9452 100644
        --- a/test/unit/visual/cases/typography.js
        +++ b/test/unit/visual/cases/typography.js
        @@ -1,20 +1,576 @@
        -visualSuite('Typography', function() {
        -  visualSuite('textFont() with default fonts', function() {
        -    visualTest('With the default font', function (p5, screenshot) {
        +import { visualSuite, visualTest } from "../visualTest";
        +
        +visualSuite("Typography", function () {
        +  visualSuite("textFont", function () {
        +    visualTest("with the default font", function (p5, screenshot) {
               p5.createCanvas(50, 50);
               p5.textSize(20);
               p5.textAlign(p5.LEFT, p5.TOP);
        -      p5.text('test', 0, 0);
        +      p5.text("test", 0, 0);
               screenshot();
             });
         
        -    visualTest('With the default monospace font', function (p5, screenshot) {
        +    visualTest("with the default monospace font", function (p5, screenshot) {
               p5.createCanvas(50, 50);
               p5.textSize(20);
        -      p5.textFont('monospace');
        +      p5.textFont("monospace");
        +      p5.textAlign(p5.LEFT, p5.TOP);
        +      p5.text("test", 0, 0);
        +      screenshot();
        +    });
        +
        +    visualTest('with a Google Font URL', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      const font = await p5.loadFont(
        +        'https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&display=swap'
        +      );
        +      p5.textFont(font);
        +      p5.textAlign(p5.LEFT, p5.TOP);
        +      p5.textSize(35);
        +      p5.text('p5*js', 0, 10, p5.width);
        +      screenshot();
        +    });
        +
        +    visualTest('with a font file', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      const font = await p5.loadFont(
        +        '/unit/assets/Inconsolata-Bold.ttf'
        +      );
        +      p5.textFont(font);
        +      p5.textAlign(p5.LEFT, p5.TOP);
        +      p5.textSize(35);
        +      p5.text('p5*js', 0, 10, p5.width);
        +      screenshot();
        +    });
        +
        +    visualTest('with a woff font file', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      const font = await p5.loadFont(
        +        '/unit/assets/Lato-Regular.woff'
        +      );
        +      p5.textFont(font);
               p5.textAlign(p5.LEFT, p5.TOP);
        -      p5.text('test', 0, 0);
        +      p5.textSize(35);
        +      p5.text('p5*js', 0, 10, p5.width);
        +      screenshot();
        +    });
        +
        +    visualTest('with a directly set font string', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      p5.textFont(`italic bold 32px serif`);
        +      p5.text('p5*js', 0, 10, p5.width);
        +      screenshot();
        +    });
        +
        +    visualTest('with a font file in WebGL', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100, p5.WEBGL);
        +      const font = await p5.loadFont(
        +        '/unit/assets/Inconsolata-Bold.ttf'
        +      );
        +      p5.textFont(font);
        +      p5.textAlign(p5.LEFT, p5.TOP);
        +      p5.textSize(35);
        +      p5.text('p5*js', -p5.width / 2, -p5.height / 2 + 10, p5.width);
        +      screenshot();
        +    });
        +  });
        +
        +  visualSuite('textWeight', function () {
        +    visualTest('can control non-variable fonts', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      const font = await p5.loadFont(
        +        'https://fonts.googleapis.com/css2?family=Sniglet:wght@400;800&display=swap'
        +      );
        +
        +      for (const weight of [400, 800]) {
        +        p5.background(255);
        +        p5.textFont(font);
        +        p5.textAlign(p5.LEFT, p5.TOP);
        +        p5.textSize(35);
        +        p5.textWeight(weight);
        +        p5.text('p5*js', 0, 10, p5.width);
        +        screenshot();
        +      }
        +    });
        +
        +    visualTest('can control variable fonts', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      const font = await p5.loadFont(
        +        'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap'
        +      );
        +      for (let weight = 400; weight <= 800; weight += 100) {
        +        p5.background(255);
        +        p5.textFont(font);
        +        p5.textAlign(p5.LEFT, p5.TOP);
        +        p5.textSize(35);
        +        p5.textWeight(weight);
        +        p5.text('p5*js', 0, 10, p5.width);
        +        screenshot();
        +      }
        +    });
        +
        +    visualTest('can control variable fonts from files', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      const font = await p5.loadFont(
        +        '/unit/assets/BricolageGrotesque-Variable.ttf',
        +        { weight: '200 800' }
        +      );
        +      for (let weight = 400; weight <= 800; weight += 100) {
        +        p5.background(255);
        +        p5.textFont(font);
        +        p5.textAlign(p5.LEFT, p5.TOP);
        +        p5.textSize(35);
        +        p5.textWeight(weight);
        +        p5.text('p5*js', 0, 10, p5.width);
        +        screenshot();
        +      }
        +    });
        +  });
        +
        +  visualSuite("textAlign", function () {
        +    for (const mode of ['2d', 'webgl']) {
        +      visualSuite(`${mode} mode`, () => {
        +        visualTest("all alignments with single word", async function (p5, screenshot) {
        +          const alignments = [
        +            { alignX: p5.LEFT, alignY: p5.TOP },
        +            { alignX: p5.CENTER, alignY: p5.TOP },
        +            { alignX: p5.RIGHT, alignY: p5.TOP },
        +            { alignX: p5.LEFT, alignY: p5.CENTER },
        +            { alignX: p5.CENTER, alignY: p5.CENTER },
        +            { alignX: p5.RIGHT, alignY: p5.CENTER },
        +            { alignX: p5.LEFT, alignY: p5.BOTTOM },
        +            { alignX: p5.CENTER, alignY: p5.BOTTOM },
        +            { alignX: p5.RIGHT, alignY: p5.BOTTOM },
        +          ];
        +
        +          p5.createCanvas(300, 300, mode === 'webgl' ? p5.WEBGL : undefined);
        +          if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2);
        +          p5.textSize(60);
        +          const font = await p5.loadFont(
        +            '/unit/assets/Inconsolata-Bold.ttf'
        +          );
        +          p5.textFont(font);
        +          alignments.forEach((alignment) => {
        +            p5.background(255);
        +            p5.textAlign(alignment.alignX, alignment.alignY);
        +            p5.text("Single Line", p5.width / 2, p5.height / 2);
        +            const bb = p5.textBounds("Single Line", p5.width / 2, p5.height / 2);
        +            p5.push();
        +            p5.noFill();
        +            p5.stroke("red");
        +            p5.rect(bb.x, bb.y, bb.w, bb.h);
        +            p5.pop();
        +            screenshot();
        +          })
        +        });
        +
        +        visualTest("all alignments with single line", async function (p5, screenshot) {
        +          const alignments = [
        +            { alignX: p5.LEFT, alignY: p5.TOP },
        +            { alignX: p5.CENTER, alignY: p5.TOP },
        +            { alignX: p5.RIGHT, alignY: p5.TOP },
        +            { alignX: p5.LEFT, alignY: p5.CENTER },
        +            { alignX: p5.CENTER, alignY: p5.CENTER },
        +            { alignX: p5.RIGHT, alignY: p5.CENTER },
        +            { alignX: p5.LEFT, alignY: p5.BOTTOM },
        +            { alignX: p5.CENTER, alignY: p5.BOTTOM },
        +            { alignX: p5.RIGHT, alignY: p5.BOTTOM },
        +          ];
        +
        +          p5.createCanvas(300, 300, mode === 'webgl' ? p5.WEBGL : undefined);
        +          if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2);
        +          p5.textSize(45);
        +          const font = await p5.loadFont(
        +            '/unit/assets/Inconsolata-Bold.ttf'
        +          );
        +          p5.textFont(font);
        +          alignments.forEach((alignment) => {
        +            p5.background(255);
        +            p5.textAlign(alignment.alignX, alignment.alignY);
        +            p5.text("Single Line", p5.width / 2, p5.height / 2);
        +            const bb = p5.textBounds("Single Line", p5.width / 2, p5.height / 2);
        +            p5.push();
        +            p5.noFill();
        +            p5.stroke("red");
        +            p5.rect(bb.x, bb.y, bb.w, bb.h);
        +            p5.pop();
        +            screenshot();
        +          });
        +        });
        +
        +        visualTest("all alignments with multi-lines and wrap word",
        +          async function (p5, screenshot) {
        +            const alignments = [
        +              { alignX: p5.LEFT, alignY: p5.TOP },
        +              { alignX: p5.CENTER, alignY: p5.TOP },
        +              { alignX: p5.RIGHT, alignY: p5.TOP },
        +              { alignX: p5.LEFT, alignY: p5.CENTER },
        +              { alignX: p5.CENTER, alignY: p5.CENTER },
        +              { alignX: p5.RIGHT, alignY: p5.CENTER },
        +              { alignX: p5.LEFT, alignY: p5.BOTTOM },
        +              { alignX: p5.CENTER, alignY: p5.BOTTOM },
        +              { alignX: p5.RIGHT, alignY: p5.BOTTOM },
        +            ];
        +
        +            p5.createCanvas(150, 100, mode === 'webgl' ? p5.WEBGL : undefined);
        +            if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2);
        +            p5.textSize(20);
        +            p5.textWrap(p5.WORD);
        +            const font = await p5.loadFont(
        +              '/unit/assets/Inconsolata-Bold.ttf'
        +            );
        +            p5.textFont(font);
        +
        +            let xPos = 20;
        +            let yPos = 20;
        +            const boxWidth = 100;
        +            const boxHeight = 60;
        +
        +            alignments.forEach((alignment, i) => {
        +              p5.background(255);
        +              p5.push();
        +              p5.textAlign(alignment.alignX, alignment.alignY);
        +
        +              p5.noFill();
        +              p5.strokeWeight(2);
        +              p5.stroke(200);
        +              p5.rect(xPos, yPos, boxWidth, boxHeight);
        +
        +              p5.fill(0);
        +              p5.noStroke();
        +              p5.text(
        +                "A really long text that should wrap automatically as it reaches the end of the box",
        +                xPos,
        +                yPos,
        +                boxWidth,
        +                boxHeight
        +              );
        +              const bb = p5.textBounds(
        +                "A really long text that should wrap automatically as it reaches the end of the box",
        +                xPos,
        +                yPos,
        +                boxWidth,
        +                boxHeight
        +              );
        +              p5.noFill();
        +              p5.stroke("red");
        +              p5.rect(bb.x, bb.y, bb.w, bb.h);
        +              p5.pop();
        +
        +              screenshot();
        +            });
        +          }
        +        );
        +
        +        visualTest(
        +          "all alignments with multi-lines and wrap char",
        +          async function (p5, screenshot) {
        +            const alignments = [
        +              { alignX: p5.LEFT, alignY: p5.TOP },
        +              { alignX: p5.CENTER, alignY: p5.TOP },
        +              { alignX: p5.RIGHT, alignY: p5.TOP },
        +              { alignX: p5.LEFT, alignY: p5.CENTER },
        +              { alignX: p5.CENTER, alignY: p5.CENTER },
        +              { alignX: p5.RIGHT, alignY: p5.CENTER },
        +              { alignX: p5.LEFT, alignY: p5.BOTTOM },
        +              { alignX: p5.CENTER, alignY: p5.BOTTOM },
        +              { alignX: p5.RIGHT, alignY: p5.BOTTOM },
        +            ];
        +
        +            p5.createCanvas(150, 100, mode === 'webgl' ? p5.WEBGL : undefined);
        +            if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2);
        +            p5.textSize(20);
        +            p5.textWrap(p5.CHAR);
        +            const font = await p5.loadFont(
        +              '/unit/assets/Inconsolata-Bold.ttf'
        +            );
        +            p5.textFont(font);
        +
        +            let xPos = 20;
        +            let yPos = 20;
        +            const boxWidth = 100;
        +            const boxHeight = 60;
        +
        +            alignments.forEach((alignment, i) => {
        +              p5.background(255);
        +              p5.push();
        +              p5.textAlign(alignment.alignX, alignment.alignY);
        +
        +              p5.noFill();
        +              p5.strokeWeight(2);
        +              p5.stroke(200);
        +              p5.rect(xPos, yPos, boxWidth, boxHeight);
        +
        +              p5.fill(0);
        +              p5.noStroke();
        +              p5.text(
        +                "A really long text that should wrap automatically as it reaches the end of the box",
        +                xPos,
        +                yPos,
        +                boxWidth,
        +                boxHeight
        +              );
        +              const bb = p5.textBounds(
        +                "A really long text that should wrap automatically as it reaches the end of the box",
        +                xPos,
        +                yPos,
        +                boxWidth,
        +                boxHeight
        +              );
        +              p5.noFill();
        +              p5.stroke("red");
        +              p5.rect(bb.x, bb.y, bb.w, bb.h);
        +              p5.pop();
        +
        +              screenshot();
        +            });
        +          }
        +        );
        +
        +        visualTest(
        +          "all alignments with multi-line manual text",
        +          async function (p5, screenshot) {
        +            const alignments = [
        +              { alignX: p5.LEFT, alignY: p5.TOP },
        +              { alignX: p5.CENTER, alignY: p5.TOP },
        +              { alignX: p5.RIGHT, alignY: p5.TOP },
        +              { alignX: p5.LEFT, alignY: p5.CENTER },
        +              { alignX: p5.CENTER, alignY: p5.CENTER },
        +              { alignX: p5.RIGHT, alignY: p5.CENTER },
        +              { alignX: p5.LEFT, alignY: p5.BOTTOM },
        +              { alignX: p5.CENTER, alignY: p5.BOTTOM },
        +              { alignX: p5.RIGHT, alignY: p5.BOTTOM },
        +            ];
        +
        +            p5.createCanvas(150, 100, mode === 'webgl' ? p5.WEBGL : undefined);
        +            if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2);
        +            p5.textSize(20);
        +
        +            const font = await p5.loadFont(
        +              '/unit/assets/Inconsolata-Bold.ttf'
        +            );
        +            p5.textFont(font);
        +
        +            let xPos = 20;
        +            let yPos = 20;
        +            const boxWidth = 100;
        +            const boxHeight = 60;
        +
        +            alignments.forEach((alignment, i) => {
        +              p5.background(255);
        +              p5.push();
        +              p5.textAlign(alignment.alignX, alignment.alignY);
        +
        +              p5.noFill();
        +              p5.stroke(200);
        +              p5.strokeWeight(2);
        +              p5.rect(xPos, yPos, boxWidth, boxHeight);
        +
        +              p5.fill(0);
        +              p5.noStroke();
        +              p5.text("Line 1\nLine 2\nLine 3", xPos, yPos, boxWidth, boxHeight);
        +              const bb = p5.textBounds(
        +                "Line 1\nLine 2\nLine 3",
        +                xPos,
        +                yPos,
        +                boxWidth,
        +                boxHeight
        +              );
        +              p5.noFill();
        +              p5.stroke("red");
        +              p5.rect(bb.x, bb.y, bb.w, bb.h);
        +              p5.pop();
        +
        +              screenshot();
        +            });
        +          }
        +        );
        +      });
        +    }
        +  });
        +
        +  visualSuite("textStyle", function () {
        +    visualTest("all text styles", async function (p5, screenshot) {
        +      p5.createCanvas(150, 150);
        +      const font = await p5.loadFont(
        +        'https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&display=swap'
        +      );
        +      p5.textSize(20);
        +      p5.textFont(font);
        +      p5.textAlign(p5.LEFT, p5.TOP);
        +
        +      p5.text("Regular Text", 0, 0);
        +      p5.textStyle(p5.BOLD);
        +      p5.text("Bold Text", 0, 30);
        +      p5.textStyle(p5.ITALIC);
        +      p5.text("Italic Text", 0, 60);
        +      p5.textStyle(p5.BOLDITALIC);
        +      p5.text("Bold Italic Text", 0, 90);
        +      screenshot();
        +    });
        +  });
        +
        +  visualSuite("textSize", function () {
        +    const units = ["px"];
        +    const sizes = [12, 16, 20, 24, 30];
        +
        +    visualTest("text sizes comparison", function (p5, screenshot) {
        +      p5.createCanvas(300, 200);
        +      let yOffset = 0;
        +
        +      units.forEach((unit) => {
        +        sizes.forEach((size) => {
        +          p5.textSize(size);
        +          p5.textAlign(p5.LEFT, p5.TOP);
        +          p5.text(`Size: ${size}${unit}`, 0, yOffset);
        +          yOffset += 30;
        +        });
        +      });
        +      screenshot();
        +    });
        +  });
        +
        +  visualSuite("textLeading", function () {
        +    visualTest("text leading with different values", function (p5, screenshot) {
        +      p5.createCanvas(300, 200);
        +      const leadingValues = [10, 20, 30];
        +      let yOffset = 0;
        +
        +      p5.textSize(20);
        +      p5.textAlign(p5.LEFT, p5.TOP);
        +
        +      leadingValues.forEach((leading) => {
        +        p5.textLeading(leading);
        +        p5.text(`Leading: ${leading}`, 0, yOffset);
        +        p5.text("This is a line of text.", 0, yOffset + 30);
        +        p5.text("This is another line of text.", 0, yOffset + 30 + leading);
        +        yOffset += 30 + leading;
        +      });
        +      screenshot();
        +    });
        +  });
        +
        +  visualSuite("textWidth", function () {
        +    visualTest("verify width of a string", function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      p5.textSize(20);
        +      const text = "Width Test";
        +      const width = p5.textWidth(text);
        +      p5.text(text, 0, 30);
        +      p5.noFill();
        +      p5.stroke("red");
        +      p5.rect(0, 30 - 20, width, 20);
        +      screenshot();
        +    });
        +  });
        +
        +  visualSuite('textToPoints', function () {
        +    visualTest('Fonts can be converted to points', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      const font = await p5.loadFont(
        +        '/unit/assets/Inconsolata-Bold.ttf'
        +      );
        +      p5.background(255);
        +      p5.strokeWeight(2);
        +      p5.textSize(50);
        +      const pts = font.textToPoints('p5*js', 0, 50);
        +      p5.beginShape(p5.POINTS);
        +      for (const { x, y } of pts) p5.vertex(x, y);
        +      p5.endShape();
        +      screenshot();
        +    });
        +
        +    visualTest('Sampling density can be changed', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      const font = await p5.loadFont(
        +        '/unit/assets/Inconsolata-Bold.ttf'
        +      );
        +      p5.background(255);
        +      p5.strokeWeight(2);
        +      p5.textSize(50);
        +      const pts = font.textToPoints('p5*js', 0, 50, { sampleFactor: 0.5 });
        +      p5.beginShape(p5.POINTS);
        +      for (const { x, y } of pts) p5.vertex(x, y);
        +      p5.endShape();
        +      screenshot();
        +    });
        +
        +    for (const mode of ['RADIANS', 'DEGREES']) {
        +      visualTest(`Fonts point angles work in ${mode} mode`, async function (p5, screenshot) {
        +        p5.createCanvas(100, 100);
        +        const font = await p5.loadFont(
        +          '/unit/assets/Inconsolata-Bold.ttf'
        +        );
        +        p5.background(255);
        +        p5.strokeWeight(2);
        +        p5.textSize(50);
        +        p5.angleMode(p5[mode]);
        +        const pts = font.textToPoints('p5*js', 0, 50, { sampleFactor: 0.25 });
        +        p5.beginShape(p5.LINES);
        +        for (const { x, y, angle } of pts) {
        +          p5.vertex(
        +            x - 5 * p5.cos(angle),
        +            y - 5 * p5.sin(angle)
        +          );
        +          p5.vertex(
        +            x + 5 * p5.cos(angle),
        +            y + 5 * p5.sin(angle)
        +          );
        +        }
        +        p5.endShape();
        +        screenshot();
        +      });
        +    }
        +  });
        +
        +  visualSuite('textToContours', function () {
        +    visualTest('Fonts can be converted to points grouped by contour', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      const font = await p5.loadFont(
        +        '/unit/assets/Inconsolata-Bold.ttf'
        +      );
        +      p5.background(200);
        +      p5.strokeWeight(2);
        +      p5.textSize(50);
        +      const contours = font.textToContours('p5*js', 0, 50, { sampleFactor: 0.5 })
        +      p5.beginShape();
        +      for (const pts of contours) {
        +        p5.beginContour();
        +        for (const { x, y } of pts) p5.vertex(x, y);
        +        p5.endContour(p5.CLOSE);
        +      }
        +      p5.endShape();
        +      screenshot();
        +    });
        +  });
        +
        +  visualSuite('textToPaths', function () {
        +    visualTest('Fonts can be converted to drawing context commands', async function (p5, screenshot) {
        +      p5.createCanvas(100, 100);
        +      const font = await p5.loadFont(
        +        '/unit/assets/Inconsolata-Bold.ttf'
        +      );
        +      p5.background(200);
        +      p5.strokeWeight(2);
        +      p5.textSize(50);
        +      const cmds = font.textToPaths('p5*js', 0, 50)
        +      p5.drawingContext.beginPath();
        +      for (const [type, ...args] of cmds) {
        +        if (type === 'M') {
        +          p5.drawingContext.moveTo(...args);
        +        } else if (type === 'L') {
        +          p5.drawingContext.lineTo(...args);
        +        } else if (type === 'C') {
        +          p5.drawingContext.bezierCurveTo(...args);
        +        } else if (type === 'Q') {
        +          p5.drawingContext.quadraticCurveTo(...args);
        +        } else if (type === 'Z') {
        +          p5.drawingContext.closePath();
        +        }
        +      }
        +      p5.drawingContext.fill();
        +      p5.drawingContext.stroke();
               screenshot();
             });
           });
        -});
        +}, { shiftThreshold: 3 });
        diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js
        index 2822b1ead8..bf112aab2a 100644
        --- a/test/unit/visual/cases/webgl.js
        +++ b/test/unit/visual/cases/webgl.js
        @@ -1,3 +1,5 @@
        +import { visualSuite, visualTest } from '../visualTest';
        +
         visualSuite('WebGL', function() {
           visualSuite('Camera', function() {
             visualTest('2D objects maintain correct size', function(p5, screenshot) {
        @@ -68,6 +70,23 @@ visualSuite('WebGL', function() {
                 screenshot();
               }
             );
        +
        +    for (const mode of ['webgl', '2d']) {
        +      visualSuite(`In ${mode} mode`, function() {
        +        visualTest('It can combine multiple filter passes', function(p5, screenshot) {
        +          p5.createCanvas(50, 50, mode === 'webgl' ? p5.WEBGL : p5.P2D);
        +          if (mode === 'webgl') p5.translate(-p5.width/2, -p5.height/2);
        +          p5.background(255);
        +          p5.fill(0);
        +          p5.noStroke();
        +          p5.circle(15, 15, 20);
        +          p5.circle(30, 30, 20);
        +          p5.filter(p5.BLUR, 5);
        +          p5.filter(p5.THRESHOLD);
        +          screenshot();
        +        });
        +      });
        +    }
           });
         
           visualSuite('Lights', function() {
        @@ -85,7 +104,7 @@ visualSuite('WebGL', function() {
             visualTest('OBJ model with MTL file displays diffuse colors correctly', function(p5, screenshot) {
               return new Promise(resolve => {
                 p5.createCanvas(50, 50, p5.WEBGL);
        -        p5.loadModel('unit/assets/octa-color.obj', model => {
        +        p5.loadModel('/unit/assets/octa-color.obj', model => {
                   p5.background(255);
                   p5.rotateX(10 * 0.01);
                   p5.rotateY(10 * 0.01);
        @@ -99,7 +118,7 @@ visualSuite('WebGL', function() {
             visualTest('Object with no colors takes on fill color', function(p5, screenshot) {
               return new Promise(resolve => {
                 p5.createCanvas(50, 50, p5.WEBGL);
        -        p5.loadModel('unit/assets/cube.obj', model => {
        +        p5.loadModel('/unit/assets/cube.obj', model => {
                   p5.background(255);
                   p5.fill('blue'); // Setting a fill color
                   p5.rotateX(p5.frameCount * 0.01);
        @@ -115,8 +134,8 @@ visualSuite('WebGL', function() {
               'Object with different texture coordinates per use of vertex keeps the coordinates intact',
               async function(p5, screenshot) {
                 p5.createCanvas(50, 50, p5.WEBGL);
        -        const tex = await new Promise(resolve => p5.loadImage('unit/assets/cat.jpg', resolve));
        -        const cube = await new Promise(resolve => p5.loadModel('unit/assets/cube-textures.obj', resolve));
        +        const tex = await p5.loadImage('/unit/assets/cat.jpg');
        +        const cube = await new Promise(resolve => p5.loadModel('/unit/assets/cube-textures.obj', resolve));
                 cube.normalize();
                 p5.background(255);
                 p5.texture(tex);
        @@ -128,4 +147,463 @@ visualSuite('WebGL', function() {
               }
             );
           });
        +
        +  visualSuite('vertexProperty', function(){
        +    const vertSrc = `#version 300 es
        +    precision mediump float;
        +    uniform mat4 uProjectionMatrix;
        +    uniform mat4 uModelViewMatrix;
        +    in vec3 aPosition;
        +    in vec3 aCol;
        +    out vec3 vCol;
        +    void main(){
        +      vCol = aCol;
        +      gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
        +    }`;
        +    const fragSrc = `#version 300 es
        +      precision mediump float;
        +      in vec3 vCol;
        +      out vec4 outColor;
        +      void main(){
        +        outColor = vec4(vCol, 1.0);
        +      }`;
        +    visualTest(
        +      'on PATH shape mode', function(p5, screenshot) {
        +        p5.createCanvas(50, 50, p5.WEBGL);
        +        p5.background('white');
        +        const myShader = p5.createShader(vertSrc, fragSrc);
        +        p5.shader(myShader);
        +        p5.beginShape(p5.PATH);
        +        p5.noStroke();
        +        for (let i = 0; i < 20; i++){
        +          let x = 20 * p5.sin(i/20*p5.TWO_PI);
        +          let y = 20 * p5.cos(i/20*p5.TWO_PI);
        +          p5.vertexProperty('aCol', [x/20, -y/20, 0]);
        +          p5.vertex(x, y);
        +        }
        +        p5.endShape();
        +        screenshot();
        +      }
        +    );
        +    visualTest(
        +      'on QUADS shape mode', function(p5, screenshot) {
        +        p5.createCanvas(50, 50, p5.WEBGL);
        +        p5.background('white');
        +        const myShader = p5.createShader(vertSrc, fragSrc);
        +        p5.shader(myShader)
        +        p5.beginShape(p5.QUADS);
        +        p5.noStroke();
        +        p5.translate(-25,-25);
        +        for (let i = 0; i < 5; i++){
        +          for (let j = 0; j < 5; j++){
        +            let x1 = i * 10;
        +            let x2 = x1 + 10;
        +            let y1 = j * 10;
        +            let y2 = y1 + 10;
        +            p5.vertexProperty('aCol', [1, 0, 0]);
        +            p5.vertex(x1, y1);
        +            p5.vertexProperty('aCol', [0, 0, 1]);
        +            p5.vertex(x2, y1);
        +            p5.vertexProperty('aCol', [0, 1, 1]);
        +            p5.vertex(x2, y2);
        +            p5.vertexProperty('aCol', [1, 1, 1]);
        +            p5.vertex(x1, y2);
        +          }
        +        }
        +        p5.endShape();
        +        screenshot();
        +      }
        +    );
        +    visualTest(
        +      'on buildGeometry outputs containing 3D primitives', function(p5, screenshot) {
        +        p5.createCanvas(50, 50, p5.WEBGL);
        +        p5.background('white');
        +        const myShader = p5.createShader(vertSrc, fragSrc);
        +        p5.shader(myShader);
        +        const shape = p5.buildGeometry(() => {
        +          p5.push();
        +          p5.translate(15,-10,0);
        +          p5.sphere(5);
        +          p5.pop();
        +          p5.beginShape(p5.TRIANGLES);
        +          p5.vertexProperty('aCol', [1,0,0])
        +          p5.vertex(-5, 5, 0);
        +          p5.vertexProperty('aCol', [0,1,0])
        +          p5.vertex(5, 5, 0);
        +          p5.vertexProperty('aCol', [0,0,1])
        +          p5.vertex(0, -5, 0);
        +          p5.endShape(p5.CLOSE);
        +          p5.push();
        +          p5.translate(-15,10,0);
        +          p5.box(10);
        +          p5.pop();
        +        })
        +        p5.model(shape);
        +        screenshot();
        +      }
        +    );
        +  });
        +
        +  visualSuite('ShaderFunctionality', function() {
        +    visualTest('FillShader', async (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      const img = await p5.loadImage('/unit/assets/cat.jpg');
        +      const fillShader = p5.createShader(
        +        `
        +      attribute vec3 aPosition;
        +      void main() {
        +        gl_Position = vec4(aPosition, 1.0);
        +      }
        +      `,
        +        `
        +      precision mediump float;
        +      void main() {
        +        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        +      }
        +      `
        +      );
        +      p5.shader(fillShader);
        +      p5.lights();
        +      p5.texture(img);
        +      p5.noStroke();
        +      p5.rect(-p5.width / 2, -p5.height / 2, p5.width, p5.height);
        +      screenshot();
        +    });
        +
        +    visualTest('StrokeShader', (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      // Create a stroke shader with a fading effect based on distance
        +      const strokeshader = p5.baseStrokeShader().modify({
        +        'Inputs getPixelInputs': `(Inputs inputs) {
        +        float opacity = 1.0 - smoothstep(
        +          0.0,
        +          15.0,
        +          length(inputs.position - inputs.center)
        +        );
        +        inputs.color *= opacity;
        +        return inputs;
        +      }`
        +      });
        +
        +      p5.strokeShader(strokeshader);
        +      p5.strokeWeight(15);
        +      p5.line(
        +        -p5.width / 3,
        +        p5.sin(0.2) * p5.height / 4,
        +        p5.width / 3,
        +        p5.sin(1.2) * p5.height / 4
        +      );
        +      screenshot();
        +    });
        +
        +    visualTest('ImageShader', async (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      const img = await p5.loadImage('/unit/assets/cat.jpg');
        +      const imgShader = p5.createShader(
        +        `
        +      precision mediump float;
        +      attribute vec3 aPosition;
        +      attribute vec2 aTexCoord;
        +      varying vec2 vTexCoord;
        +      uniform mat4 uModelViewMatrix;
        +      uniform mat4 uProjectionMatrix;
        +
        +      void main() {
        +        vTexCoord = aTexCoord;
        +        gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
        +      }
        +      `,
        +        `
        +      precision mediump float;
        +      varying vec2 vTexCoord;
        +      uniform sampler2D uTexture;
        +
        +      void main() {
        +        vec4 texColor = texture2D(uTexture, vTexCoord);
        +        gl_FragColor = texColor * vec4(1.5, 0.5, 0.5, 1.0);
        +      }
        +      `
        +      );
        +
        +      p5.imageShader(imgShader);
        +      imgShader.setUniform('uTexture', img);
        +      p5.noStroke();
        +      p5.image(img, -p5.width / 2, -p5.height / 2, p5.width, p5.height);
        +      screenshot();
        +    });
        +  });
        +
        +  visualSuite('Strokes', function() {
        +    visualTest('Strokes do not cut into fills in ortho mode', (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      p5.background(220);
        +      p5.stroke(8);
        +      p5.ortho();
        +      p5.rotateX(p5.PI/4);
        +      p5.rotateY(p5.PI/4);
        +      p5.box(30);
        +      screenshot();
        +    })
        +  })
        +
        +  visualSuite('Opacity', function() {
        +    visualTest('Basic colors have opacity applied correctly', (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      p5.background(255);
        +      p5.fill(255, 0, 0, 100);
        +      p5.circle(0, 0, 50);
        +      screenshot();
        +    });
        +
        +    visualTest('Colors have opacity applied correctly when lights are used', (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      p5.background(255);
        +      p5.ambientLight(255);
        +      p5.fill(255, 0, 0, 100);
        +      p5.circle(0, 0, 50);
        +      screenshot();
        +    });
        +
        +    visualTest('Colors in shader hooks have opacity applied correctly', (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      const myShader = p5.baseMaterialShader().modify({
        +        'Inputs getPixelInputs': `(Inputs inputs) {
        +          inputs.color = vec4(1., 0., 0., 100./255.);
        +          return inputs;
        +        }`
        +      })
        +      p5.background(255);
        +      p5.shader(myShader);
        +      p5.circle(0, 0, 50);
        +      screenshot();
        +    });
        +
        +    visualTest('Colors in textures have opacity applied correctly', (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      const tex = p5.createFramebuffer();
        +      tex.draw(() => p5.background(255, 0, 0, 100));
        +      p5.background(255);
        +      p5.texture(tex);
        +      p5.circle(0, 0, 50);
        +      screenshot();
        +    });
        +
        +    visualTest('Colors in tinted textures have opacity applied correctly', (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      const tex = p5.createFramebuffer();
        +      tex.draw(() => p5.background(255, 0, 0, 255));
        +      p5.background(255);
        +      p5.texture(tex);
        +      p5.tint(255, 100);
        +      p5.circle(0, 0, 50);
        +      screenshot();
        +    });
        +  });
        +
        +  visualSuite('Hooks coordinate spaces', () => {
        +    for (const base of ['baseMaterialShader', 'baseColorShader', 'baseNormalShader']) {
        +      visualSuite(base, () => {
        +        visualTest('Object space', (p5, screenshot) => {
        +          p5.createCanvas(50, 50, p5.WEBGL);
        +          const myShader = p5[base]().modify({
        +            'Vertex getObjectInputs': `(Vertex inputs) {
        +              inputs.position.x += 0.25;
        +              inputs.normal.x += 0.5 * sin(inputs.position.y * 2.);
        +              inputs.normal = normalize(inputs.normal);
        +              return inputs;
        +            }`
        +          });
        +          p5.background(255);
        +          p5.lights();
        +          p5.fill('red');
        +          p5.noStroke();
        +          p5.rotateY(p5.PI/2);
        +          p5.camera(-800, 0, 0, 0, 0, 0);
        +          p5.shader(myShader);
        +          p5.sphere(20);
        +          screenshot();
        +        });
        +
        +        visualTest('World space', (p5, screenshot) => {
        +          p5.createCanvas(50, 50, p5.WEBGL);
        +          const myShader = p5[base]().modify({
        +            'Vertex getWorldInputs': `(Vertex inputs) {
        +              inputs.position.x += 10.;
        +              inputs.normal.x += 0.5 * sin(inputs.position.y * 2.);
        +              inputs.normal = normalize(inputs.normal);
        +              return inputs;
        +            }`
        +          });
        +          p5.background(255);
        +          p5.lights();
        +          p5.fill('red');
        +          p5.noStroke();
        +          p5.rotateY(p5.PI/2);
        +          p5.camera(-800, 0, 0, 0, 0, 0);
        +          p5.shader(myShader);
        +          p5.sphere(20);
        +          screenshot();
        +        });
        +
        +        visualTest('Camera space', (p5, screenshot) => {
        +          p5.createCanvas(50, 50, p5.WEBGL);
        +          const myShader = p5[base]().modify({
        +            'Vertex getCameraInputs': `(Vertex inputs) {
        +              inputs.position.x += 10.;
        +              inputs.normal.x += 0.5 * sin(inputs.position.y * 2.);
        +              inputs.normal = normalize(inputs.normal);
        +              return inputs;
        +            }`
        +          });
        +          p5.background(255);
        +          p5.lights();
        +          p5.fill('red');
        +          p5.noStroke();
        +          p5.rotateY(p5.PI/2);
        +          p5.camera(-800, 0, 0, 0, 0, 0);
        +          p5.shader(myShader);
        +          p5.sphere(20);
        +          screenshot();
        +        });
        +
        +        visualTest('Combined vs split matrices', (p5, screenshot) => {
        +          p5.createCanvas(50, 50, p5.WEBGL);
        +            for (const space of ['Object', 'World', 'Camera']) {
        +              const myShader = p5[base]().modify({
        +                [`Vertex get${space}Inputs`]: `(Vertex inputs) {
        +                  // No-op
        +                  return inputs;
        +                }`
        +              });
        +              p5.background(255);
        +              p5.push();
        +              p5.lights();
        +              p5.fill('red');
        +              p5.noStroke();
        +              p5.translate(20, -10, 5);
        +              p5.rotate(p5.PI * 0.1);
        +              p5.camera(-800, 0, 0, 0, 0, 0);
        +              p5.shader(myShader);
        +              p5.box(30);
        +              p5.pop();
        +              screenshot();
        +            }
        +        });
        +      });
        +    }
        +  });
        +
        +  visualSuite('textToModel', () => {
        +    visualTest('Flat', async (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      const font = await p5.loadFont(
        +        '/unit/assets/Inconsolata-Bold.ttf'
        +      );
        +      p5.textSize(20);
        +      const geom = font.textToModel('p5*js', 0, 0, {
        +        sampleFactor: 2
        +      });
        +      geom.normalize();
        +      p5.background(255);
        +      p5.normalMaterial();
        +      p5.rotateX(p5.PI*0.1);
        +      p5.rotateY(p5.PI*0.1);
        +      p5.scale(50/200);
        +      p5.model(geom);
        +      screenshot();
        +    });
        +
        +    visualTest('Extruded', async (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      const font = await p5.loadFont(
        +        '/unit/assets/Inconsolata-Bold.ttf'
        +      );
        +      p5.textSize(20);
        +      const geom = font.textToModel('p5*js', 0, 0, {
        +        extrude: 10,
        +        sampleFactor: 2
        +      });
        +      geom.normalize();
        +      p5.background(255);
        +      p5.normalMaterial();
        +      p5.rotateX(p5.PI*0.1);
        +      p5.rotateY(p5.PI*0.1);
        +      p5.scale(50/200);
        +      p5.model(geom);
        +      screenshot();
        +    });
        +  });
        +
        +  visualSuite('erase()', () => {
        +    visualTest('on the main canvas', (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      p5.background(0);
        +      p5.fill('red');
        +      p5.rect(-20, -20, 40, 40);
        +      p5.erase();
        +      p5.circle(0, 0, 10);
        +      p5.noErase();
        +      screenshot();
        +    });
        +
        +    visualTest('on a framebuffer', (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +      p5.background(0);
        +      const fbo = p5.createFramebuffer();
        +      fbo.begin();
        +      p5.fill('red');
        +      p5.rect(-20, -20, 40, 40);
        +      p5.erase();
        +      p5.circle(0, 0, 10);
        +      p5.noErase();
        +      fbo.end();
        +      p5.imageMode(p5.CENTER);
        +      p5.image(fbo, 0, 0);
        +      screenshot();
        +    });
        +  });
        +
        +  visualSuite('buildGeometry()', () => {
        +    visualTest('can draw models', (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +
        +      const sphere = p5.buildGeometry(() => {
        +        p5.scale(0.25);
        +        p5.sphere();
        +      });
        +
        +      const geom = p5.buildGeometry(() => {
        +        p5.model(sphere);
        +      });
        +
        +      p5.background(255);
        +      p5.lights();
        +      p5.model(geom);
        +      screenshot();
        +    });
        +
        +    visualTest('only fills set in buildGeometry are kept', (p5, screenshot) => {
        +      p5.createCanvas(50, 50, p5.WEBGL);
        +
        +      const geom = p5.buildGeometry(() => {
        +        p5.push();
        +        p5.translate(-p5.width*0.2, 0);
        +        p5.scale(0.15);
        +        p5.sphere();
        +        p5.pop();
        +
        +        p5.push();
        +        p5.fill('red');
        +        p5.translate(p5.width*0.2, 0);
        +        p5.scale(0.15);
        +        p5.sphere();
        +        p5.pop();
        +      });
        +
        +      p5.fill('blue');
        +      p5.noStroke();
        +      p5.model(geom);
        +      screenshot();
        +    });
        +  });
         });
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/000.png
        new file mode 100644
        index 0000000000..88a283cca1
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default font/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/metadata.json
        similarity index 100%
        rename from test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default font/metadata.json
        rename to test/unit/visual/screenshots/Shape drawing/2D mode/Combining quadratic and cubic beziers/metadata.json
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curve loops/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curve loops/000.png
        new file mode 100644
        index 0000000000..80536c3fec
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curve loops/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default monospace font/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curve loops/metadata.json
        similarity index 100%
        rename from test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default monospace font/metadata.json
        rename to test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curve loops/metadata.json
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curves/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curves/000.png
        new file mode 100644
        index 0000000000..80536c3fec
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curves/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curves/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curves/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed curves/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed polylines/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed polylines/000.png
        new file mode 100644
        index 0000000000..b095ec8054
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed polylines/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed polylines/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed polylines/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing closed polylines/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing polylines/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing polylines/000.png
        new file mode 100644
        index 0000000000..e7fb86f784
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing polylines/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing polylines/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing polylines/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing polylines/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing quad strips/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing quad strips/000.png
        new file mode 100644
        index 0000000000..6991e9e2db
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing quad strips/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing quad strips/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing quad strips/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing quad strips/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing simple closed curves/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing simple closed curves/000.png
        new file mode 100644
        index 0000000000..feca4096f6
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing simple closed curves/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing simple closed curves/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing simple closed curves/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing simple closed curves/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle fans/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle fans/000.png
        new file mode 100644
        index 0000000000..1802b73568
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle fans/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle fans/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle fans/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle fans/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle strips/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle strips/000.png
        new file mode 100644
        index 0000000000..b91dfd95af
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle strips/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle strips/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle strips/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangle strips/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangles/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangles/000.png
        new file mode 100644
        index 0000000000..597cdeebda
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangles/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangles/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangles/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing triangles/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single closed contour/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single closed contour/000.png
        new file mode 100644
        index 0000000000..551b1d0380
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single closed contour/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single closed contour/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single closed contour/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single closed contour/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single unclosed contour/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single unclosed contour/000.png
        new file mode 100644
        index 0000000000..e2c2b8aa95
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single unclosed contour/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single unclosed contour/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single unclosed contour/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with a single unclosed contour/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with contours/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with contours/000.png
        new file mode 100644
        index 0000000000..179d949b9d
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with contours/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with contours/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with contours/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with contours/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with cubic beziers/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with cubic beziers/000.png
        new file mode 100644
        index 0000000000..4ed36210fc
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with cubic beziers/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with cubic beziers/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with cubic beziers/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with cubic beziers/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves in the middle of other shapes/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves in the middle of other shapes/000.png
        new file mode 100644
        index 0000000000..3645b1eabc
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves in the middle of other shapes/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves in the middle of other shapes/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves in the middle of other shapes/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves in the middle of other shapes/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/000.png
        new file mode 100644
        index 0000000000..7956ab38f6
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with hidden ends/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with tightness/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with tightness/000.png
        new file mode 100644
        index 0000000000..36ee10117a
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with tightness/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with tightness/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with tightness/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves with tightness/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves/000.png
        new file mode 100644
        index 0000000000..593867d9c8
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with curves/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/000.png
        new file mode 100644
        index 0000000000..551b1d0380
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with every subshape in a contour/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with lines/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with lines/000.png
        new file mode 100644
        index 0000000000..a48c6dc9fe
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with lines/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with lines/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with lines/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with lines/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with points/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with points/000.png
        new file mode 100644
        index 0000000000..1dbab4cbff
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with points/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with points/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with points/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with points/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quadratic beziers/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quadratic beziers/000.png
        new file mode 100644
        index 0000000000..7fbf3699b2
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quadratic beziers/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quadratic beziers/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quadratic beziers/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quadratic beziers/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quads/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quads/000.png
        new file mode 100644
        index 0000000000..18b521de2a
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quads/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quads/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quads/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with quads/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with triangles/000.png b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with triangles/000.png
        new file mode 100644
        index 0000000000..2e795d0883
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with triangles/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with triangles/metadata.json b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with triangles/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/2D mode/Drawing with triangles/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D cubic coordinates/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D cubic coordinates/000.png
        new file mode 100644
        index 0000000000..6c6530c43f
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D cubic coordinates/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D cubic coordinates/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D cubic coordinates/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D cubic coordinates/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D quadratic coordinates/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D quadratic coordinates/000.png
        new file mode 100644
        index 0000000000..939a41d90b
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D quadratic coordinates/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D quadratic coordinates/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D quadratic coordinates/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D quadratic coordinates/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D vertex coordinates/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D vertex coordinates/000.png
        new file mode 100644
        index 0000000000..fe084aace4
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D vertex coordinates/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D vertex coordinates/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D vertex coordinates/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/3D vertex coordinates/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Combining quadratic and cubic beziers/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Combining quadratic and cubic beziers/000.png
        new file mode 100644
        index 0000000000..cff0b299d6
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Combining quadratic and cubic beziers/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Combining quadratic and cubic beziers/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Combining quadratic and cubic beziers/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Combining quadratic and cubic beziers/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curve loops/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curve loops/000.png
        new file mode 100644
        index 0000000000..3a429788b4
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curve loops/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curve loops/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curve loops/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curve loops/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curves/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curves/000.png
        new file mode 100644
        index 0000000000..3342850bbc
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curves/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curves/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curves/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed curves/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed polylines/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed polylines/000.png
        new file mode 100644
        index 0000000000..793b78b064
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed polylines/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed polylines/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed polylines/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing closed polylines/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing polylines/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing polylines/000.png
        new file mode 100644
        index 0000000000..fc9303bbc9
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing polylines/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing polylines/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing polylines/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing polylines/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing quad strips/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing quad strips/000.png
        new file mode 100644
        index 0000000000..e5c03d3f00
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing quad strips/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing quad strips/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing quad strips/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing quad strips/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing simple closed curves/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing simple closed curves/000.png
        new file mode 100644
        index 0000000000..fb77dde4ed
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing simple closed curves/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing simple closed curves/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing simple closed curves/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing simple closed curves/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle fans/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle fans/000.png
        new file mode 100644
        index 0000000000..0f6c7b6186
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle fans/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle fans/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle fans/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle fans/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle strips/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle strips/000.png
        new file mode 100644
        index 0000000000..f9c830f511
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle strips/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle strips/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle strips/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangle strips/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangles/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangles/000.png
        new file mode 100644
        index 0000000000..378333ea08
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangles/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangles/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangles/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing triangles/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single closed contour/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single closed contour/000.png
        new file mode 100644
        index 0000000000..9d5d7ff4cf
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single closed contour/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single closed contour/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single closed contour/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single closed contour/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single unclosed contour/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single unclosed contour/000.png
        new file mode 100644
        index 0000000000..fbc9111225
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single unclosed contour/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single unclosed contour/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single unclosed contour/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with a single unclosed contour/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with contours/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with contours/000.png
        new file mode 100644
        index 0000000000..f8aee2de44
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with contours/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with contours/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with contours/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with contours/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with cubic beziers/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with cubic beziers/000.png
        new file mode 100644
        index 0000000000..c19dfb55c6
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with cubic beziers/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with cubic beziers/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with cubic beziers/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with cubic beziers/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves in the middle of other shapes/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves in the middle of other shapes/000.png
        new file mode 100644
        index 0000000000..141baf4e29
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves in the middle of other shapes/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves in the middle of other shapes/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves in the middle of other shapes/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves in the middle of other shapes/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with hidden ends/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with hidden ends/000.png
        new file mode 100644
        index 0000000000..88984a153a
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with hidden ends/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with hidden ends/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with hidden ends/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with hidden ends/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with tightness/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with tightness/000.png
        new file mode 100644
        index 0000000000..e9b6d00541
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with tightness/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with tightness/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with tightness/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves with tightness/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves/000.png
        new file mode 100644
        index 0000000000..83b03bbe38
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with curves/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with every subshape in a contour/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with every subshape in a contour/000.png
        new file mode 100644
        index 0000000000..9d5d7ff4cf
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with every subshape in a contour/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with every subshape in a contour/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with every subshape in a contour/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with every subshape in a contour/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with lines/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with lines/000.png
        new file mode 100644
        index 0000000000..aaadb8a325
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with lines/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with lines/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with lines/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with lines/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with points/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with points/000.png
        new file mode 100644
        index 0000000000..e3057c630b
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with points/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with points/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with points/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with points/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quadratic beziers/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quadratic beziers/000.png
        new file mode 100644
        index 0000000000..3da041c6d4
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quadratic beziers/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quadratic beziers/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quadratic beziers/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quadratic beziers/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quads/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quads/000.png
        new file mode 100644
        index 0000000000..f3822025ec
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quads/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quads/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quads/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with quads/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with triangles/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with triangles/000.png
        new file mode 100644
        index 0000000000..86d7ff195c
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with triangles/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with triangles/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with triangles/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Drawing with triangles/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Normalized texture coordinates/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Normalized texture coordinates/000.png
        new file mode 100644
        index 0000000000..1e42d47ee4
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Normalized texture coordinates/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Normalized texture coordinates/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Normalized texture coordinates/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Normalized texture coordinates/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point fills/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point fills/000.png
        new file mode 100644
        index 0000000000..e07875af6e
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point fills/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point fills/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point fills/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point fills/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point strokes/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point strokes/000.png
        new file mode 100644
        index 0000000000..b5d899f13a
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point strokes/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point strokes/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point strokes/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-control point strokes/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex fills/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex fills/000.png
        new file mode 100644
        index 0000000000..7911c2bf40
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex fills/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex fills/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex fills/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex fills/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex normals/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex normals/000.png
        new file mode 100644
        index 0000000000..c2a04de090
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex normals/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex normals/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex normals/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex normals/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex strokes/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex strokes/000.png
        new file mode 100644
        index 0000000000..dfe5ec29a2
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex strokes/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex strokes/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex strokes/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Per-vertex strokes/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Texture coordinates/000.png b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Texture coordinates/000.png
        new file mode 100644
        index 0000000000..1e42d47ee4
        Binary files /dev/null and b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Texture coordinates/000.png differ
        diff --git a/test/unit/visual/screenshots/Shape drawing/WebGL mode/Texture coordinates/metadata.json b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Texture coordinates/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Shape drawing/WebGL mode/Texture coordinates/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/000.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/000.png
        new file mode 100644
        index 0000000000..65fe692c5f
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/001.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/001.png
        new file mode 100644
        index 0000000000..cd50d79636
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/002.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/002.png
        new file mode 100644
        index 0000000000..5cc24cf1e8
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/003.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/003.png
        new file mode 100644
        index 0000000000..46668250ed
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/004.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/004.png
        new file mode 100644
        index 0000000000..47dc26976f
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/005.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/005.png
        new file mode 100644
        index 0000000000..c6bc09365c
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/005.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/006.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/006.png
        new file mode 100644
        index 0000000000..0b478046d5
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/006.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/007.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/007.png
        new file mode 100644
        index 0000000000..8166bd360e
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/007.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/008.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/008.png
        new file mode 100644
        index 0000000000..0084c70136
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/008.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/metadata.json b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/metadata.json
        new file mode 100644
        index 0000000000..5147b8612d
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-line manual text/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 9
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/000.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/000.png
        new file mode 100644
        index 0000000000..8c532567f2
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/001.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/001.png
        new file mode 100644
        index 0000000000..8c532567f2
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/002.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/002.png
        new file mode 100644
        index 0000000000..8c532567f2
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/003.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/003.png
        new file mode 100644
        index 0000000000..aa31ca421c
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/004.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/004.png
        new file mode 100644
        index 0000000000..aa31ca421c
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/005.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/005.png
        new file mode 100644
        index 0000000000..aa31ca421c
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/005.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/006.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/006.png
        new file mode 100644
        index 0000000000..24c31bf9f0
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/006.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/007.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/007.png
        new file mode 100644
        index 0000000000..24c31bf9f0
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/007.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/008.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/008.png
        new file mode 100644
        index 0000000000..24c31bf9f0
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/008.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/metadata.json b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/metadata.json
        new file mode 100644
        index 0000000000..5147b8612d
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap char/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 9
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/000.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/000.png
        new file mode 100644
        index 0000000000..625cff297e
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/001.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/001.png
        new file mode 100644
        index 0000000000..0c8e6cc5f4
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/002.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/002.png
        new file mode 100644
        index 0000000000..107b414cf1
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/003.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/003.png
        new file mode 100644
        index 0000000000..66b399acab
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/004.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/004.png
        new file mode 100644
        index 0000000000..b7c16ef103
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/005.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/005.png
        new file mode 100644
        index 0000000000..13d890b939
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/005.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/006.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/006.png
        new file mode 100644
        index 0000000000..998744baee
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/006.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/007.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/007.png
        new file mode 100644
        index 0000000000..88b1067dae
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/007.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/008.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/008.png
        new file mode 100644
        index 0000000000..e8ec79f527
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/008.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/metadata.json b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/metadata.json
        new file mode 100644
        index 0000000000..5147b8612d
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with multi-lines and wrap word/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 9
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/000.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/000.png
        new file mode 100644
        index 0000000000..12ef9095ea
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/001.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/001.png
        new file mode 100644
        index 0000000000..b15fc433da
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/002.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/002.png
        new file mode 100644
        index 0000000000..167a0e0b89
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/003.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/003.png
        new file mode 100644
        index 0000000000..0a6d247b8f
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/004.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/004.png
        new file mode 100644
        index 0000000000..2cede116be
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/005.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/005.png
        new file mode 100644
        index 0000000000..5b8e47a3fb
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/005.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/006.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/006.png
        new file mode 100644
        index 0000000000..b073f4f280
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/006.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/007.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/007.png
        new file mode 100644
        index 0000000000..31ff320e6d
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/007.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/008.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/008.png
        new file mode 100644
        index 0000000000..a28d50a1a7
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/008.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/metadata.json b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/metadata.json
        new file mode 100644
        index 0000000000..5147b8612d
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single line/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 9
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/000.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/000.png
        new file mode 100644
        index 0000000000..17cd52e212
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/001.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/001.png
        new file mode 100644
        index 0000000000..73543ad5ca
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/002.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/002.png
        new file mode 100644
        index 0000000000..950fed450f
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/003.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/003.png
        new file mode 100644
        index 0000000000..0a0cf124ca
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/004.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/004.png
        new file mode 100644
        index 0000000000..291357ab16
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/005.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/005.png
        new file mode 100644
        index 0000000000..1550ac57ec
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/005.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/006.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/006.png
        new file mode 100644
        index 0000000000..be4b764ade
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/006.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/007.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/007.png
        new file mode 100644
        index 0000000000..146c39bbd9
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/007.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/008.png b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/008.png
        new file mode 100644
        index 0000000000..537831c815
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/008.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/metadata.json b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/metadata.json
        new file mode 100644
        index 0000000000..5147b8612d
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textAlign/2d mode/all alignments with single word/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 9
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/000.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/000.png
        new file mode 100644
        index 0000000000..339f1bd5d0
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/001.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/001.png
        new file mode 100644
        index 0000000000..90698c7f28
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/002.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/002.png
        new file mode 100644
        index 0000000000..a5a9490fe8
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/003.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/003.png
        new file mode 100644
        index 0000000000..1a9a110c48
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/004.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/004.png
        new file mode 100644
        index 0000000000..493cc85c51
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/005.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/005.png
        new file mode 100644
        index 0000000000..1524a41e81
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/005.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/006.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/006.png
        new file mode 100644
        index 0000000000..8e34453dbb
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/006.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/007.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/007.png
        new file mode 100644
        index 0000000000..b75b92977d
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/007.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/008.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/008.png
        new file mode 100644
        index 0000000000..12020de4a6
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/008.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/metadata.json b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/metadata.json
        new file mode 100644
        index 0000000000..5147b8612d
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-line manual text/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 9
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/000.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/000.png
        new file mode 100644
        index 0000000000..a85c0d7e3f
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/001.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/001.png
        new file mode 100644
        index 0000000000..a85c0d7e3f
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/002.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/002.png
        new file mode 100644
        index 0000000000..a85c0d7e3f
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/003.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/003.png
        new file mode 100644
        index 0000000000..d3986bad49
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/004.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/004.png
        new file mode 100644
        index 0000000000..d3986bad49
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/005.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/005.png
        new file mode 100644
        index 0000000000..d3986bad49
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/005.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/006.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/006.png
        new file mode 100644
        index 0000000000..75490d5dad
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/006.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/007.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/007.png
        new file mode 100644
        index 0000000000..75490d5dad
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/007.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/008.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/008.png
        new file mode 100644
        index 0000000000..75490d5dad
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/008.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/metadata.json b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/metadata.json
        new file mode 100644
        index 0000000000..5147b8612d
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap char/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 9
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/000.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/000.png
        new file mode 100644
        index 0000000000..f571f42148
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/001.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/001.png
        new file mode 100644
        index 0000000000..462e6e8df6
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/002.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/002.png
        new file mode 100644
        index 0000000000..496c73d326
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/003.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/003.png
        new file mode 100644
        index 0000000000..2b72b85b6c
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/004.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/004.png
        new file mode 100644
        index 0000000000..174c540232
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/005.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/005.png
        new file mode 100644
        index 0000000000..3f4b37651c
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/005.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/006.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/006.png
        new file mode 100644
        index 0000000000..e2fe3738a2
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/006.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/007.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/007.png
        new file mode 100644
        index 0000000000..465d86afba
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/007.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/008.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/008.png
        new file mode 100644
        index 0000000000..e7987b1e45
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/008.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/metadata.json b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/metadata.json
        new file mode 100644
        index 0000000000..5147b8612d
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with multi-lines and wrap word/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 9
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/000.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/000.png
        new file mode 100644
        index 0000000000..5f67b23cd0
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/001.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/001.png
        new file mode 100644
        index 0000000000..2d80d846ad
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/002.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/002.png
        new file mode 100644
        index 0000000000..9ee769a72c
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/003.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/003.png
        new file mode 100644
        index 0000000000..4929fd0907
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/004.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/004.png
        new file mode 100644
        index 0000000000..99f991da03
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/005.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/005.png
        new file mode 100644
        index 0000000000..c50b267675
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/005.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/006.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/006.png
        new file mode 100644
        index 0000000000..f2ea8e27ca
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/006.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/007.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/007.png
        new file mode 100644
        index 0000000000..846a1bf51a
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/007.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/008.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/008.png
        new file mode 100644
        index 0000000000..5cc2a7fea3
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/008.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/metadata.json b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/metadata.json
        new file mode 100644
        index 0000000000..5147b8612d
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single line/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 9
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/000.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/000.png
        new file mode 100644
        index 0000000000..23f2678a80
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/001.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/001.png
        new file mode 100644
        index 0000000000..4931c86268
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/002.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/002.png
        new file mode 100644
        index 0000000000..b321783592
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/003.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/003.png
        new file mode 100644
        index 0000000000..f4be694605
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/004.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/004.png
        new file mode 100644
        index 0000000000..0e5b6b0a03
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/005.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/005.png
        new file mode 100644
        index 0000000000..4638c4b353
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/005.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/006.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/006.png
        new file mode 100644
        index 0000000000..52c39dadb4
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/006.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/007.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/007.png
        new file mode 100644
        index 0000000000..0bf414478d
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/007.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/008.png b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/008.png
        new file mode 100644
        index 0000000000..aba3082227
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/008.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/metadata.json b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/metadata.json
        new file mode 100644
        index 0000000000..5147b8612d
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textAlign/webgl mode/all alignments with single word/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 9
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default font/000.png b/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default font/000.png
        deleted file mode 100644
        index 2df2de7125..0000000000
        Binary files a/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default font/000.png and /dev/null differ
        diff --git a/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default monospace font/000.png b/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default monospace font/000.png
        deleted file mode 100644
        index a30e8a60b5..0000000000
        Binary files a/test/unit/visual/screenshots/Typography/textFont() with default fonts/With the default monospace font/000.png and /dev/null differ
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with a Google Font URL/000.png b/test/unit/visual/screenshots/Typography/textFont/with a Google Font URL/000.png
        new file mode 100644
        index 0000000000..f3c32fe1eb
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textFont/with a Google Font URL/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with a Google Font URL/metadata.json b/test/unit/visual/screenshots/Typography/textFont/with a Google Font URL/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textFont/with a Google Font URL/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with a directly set font string/000.png b/test/unit/visual/screenshots/Typography/textFont/with a directly set font string/000.png
        new file mode 100644
        index 0000000000..93d9d23ac9
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textFont/with a directly set font string/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with a directly set font string/metadata.json b/test/unit/visual/screenshots/Typography/textFont/with a directly set font string/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textFont/with a directly set font string/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with a font file in WebGL/000.png b/test/unit/visual/screenshots/Typography/textFont/with a font file in WebGL/000.png
        new file mode 100644
        index 0000000000..b1807c2d3e
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textFont/with a font file in WebGL/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with a font file in WebGL/metadata.json b/test/unit/visual/screenshots/Typography/textFont/with a font file in WebGL/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textFont/with a font file in WebGL/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with a font file/000.png b/test/unit/visual/screenshots/Typography/textFont/with a font file/000.png
        new file mode 100644
        index 0000000000..cfb3d88363
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textFont/with a font file/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with a font file/metadata.json b/test/unit/visual/screenshots/Typography/textFont/with a font file/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textFont/with a font file/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with a woff font file/000.png b/test/unit/visual/screenshots/Typography/textFont/with a woff font file/000.png
        new file mode 100644
        index 0000000000..525c649b2d
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textFont/with a woff font file/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with a woff font file/metadata.json b/test/unit/visual/screenshots/Typography/textFont/with a woff font file/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textFont/with a woff font file/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with the default font/000.png b/test/unit/visual/screenshots/Typography/textFont/with the default font/000.png
        new file mode 100644
        index 0000000000..9412be3440
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textFont/with the default font/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with the default font/metadata.json b/test/unit/visual/screenshots/Typography/textFont/with the default font/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textFont/with the default font/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with the default monospace font/000.png b/test/unit/visual/screenshots/Typography/textFont/with the default monospace font/000.png
        new file mode 100644
        index 0000000000..a88804ae71
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textFont/with the default monospace font/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textFont/with the default monospace font/metadata.json b/test/unit/visual/screenshots/Typography/textFont/with the default monospace font/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textFont/with the default monospace font/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textLeading/text leading with different values/000.png b/test/unit/visual/screenshots/Typography/textLeading/text leading with different values/000.png
        new file mode 100644
        index 0000000000..40e44165a0
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textLeading/text leading with different values/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textLeading/text leading with different values/metadata.json b/test/unit/visual/screenshots/Typography/textLeading/text leading with different values/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textLeading/text leading with different values/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textSize/text sizes comparison/000.png b/test/unit/visual/screenshots/Typography/textSize/text sizes comparison/000.png
        new file mode 100644
        index 0000000000..c98bbf4e94
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textSize/text sizes comparison/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textSize/text sizes comparison/metadata.json b/test/unit/visual/screenshots/Typography/textSize/text sizes comparison/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textSize/text sizes comparison/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textStyle/all text styles/000.png b/test/unit/visual/screenshots/Typography/textStyle/all text styles/000.png
        new file mode 100644
        index 0000000000..9a94bb6f6e
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textStyle/all text styles/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textStyle/all text styles/metadata.json b/test/unit/visual/screenshots/Typography/textStyle/all text styles/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textStyle/all text styles/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textToContours/Fonts can be converted to points grouped by contour/000.png b/test/unit/visual/screenshots/Typography/textToContours/Fonts can be converted to points grouped by contour/000.png
        new file mode 100644
        index 0000000000..b00b2470d5
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textToContours/Fonts can be converted to points grouped by contour/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textToContours/Fonts can be converted to points grouped by contour/metadata.json b/test/unit/visual/screenshots/Typography/textToContours/Fonts can be converted to points grouped by contour/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textToContours/Fonts can be converted to points grouped by contour/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textToPaths/Fonts can be converted to drawing context commands/000.png b/test/unit/visual/screenshots/Typography/textToPaths/Fonts can be converted to drawing context commands/000.png
        new file mode 100644
        index 0000000000..9e2d6fabec
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textToPaths/Fonts can be converted to drawing context commands/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textToPaths/Fonts can be converted to drawing context commands/metadata.json b/test/unit/visual/screenshots/Typography/textToPaths/Fonts can be converted to drawing context commands/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textToPaths/Fonts can be converted to drawing context commands/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textToPoints/Fonts can be converted to points/000.png b/test/unit/visual/screenshots/Typography/textToPoints/Fonts can be converted to points/000.png
        new file mode 100644
        index 0000000000..863932ae3b
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textToPoints/Fonts can be converted to points/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textToPoints/Fonts can be converted to points/metadata.json b/test/unit/visual/screenshots/Typography/textToPoints/Fonts can be converted to points/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textToPoints/Fonts can be converted to points/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in DEGREES mode/000.png b/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in DEGREES mode/000.png
        new file mode 100644
        index 0000000000..6dbe5f2148
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in DEGREES mode/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in DEGREES mode/metadata.json b/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in DEGREES mode/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in DEGREES mode/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in RADIANS mode/000.png b/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in RADIANS mode/000.png
        new file mode 100644
        index 0000000000..6dbe5f2148
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in RADIANS mode/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in RADIANS mode/metadata.json b/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in RADIANS mode/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textToPoints/Fonts point angles work in RADIANS mode/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textToPoints/Sampling density can be changed/000.png b/test/unit/visual/screenshots/Typography/textToPoints/Sampling density can be changed/000.png
        new file mode 100644
        index 0000000000..b9ee9088a4
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textToPoints/Sampling density can be changed/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textToPoints/Sampling density can be changed/metadata.json b/test/unit/visual/screenshots/Typography/textToPoints/Sampling density can be changed/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textToPoints/Sampling density can be changed/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control non-variable fonts/000.png b/test/unit/visual/screenshots/Typography/textWeight/can control non-variable fonts/000.png
        new file mode 100644
        index 0000000000..87dcda79df
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control non-variable fonts/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control non-variable fonts/001.png b/test/unit/visual/screenshots/Typography/textWeight/can control non-variable fonts/001.png
        new file mode 100644
        index 0000000000..786f2a8b71
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control non-variable fonts/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control non-variable fonts/metadata.json b/test/unit/visual/screenshots/Typography/textWeight/can control non-variable fonts/metadata.json
        new file mode 100644
        index 0000000000..ebf58a6cb0
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textWeight/can control non-variable fonts/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 2
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/000.png b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/000.png
        new file mode 100644
        index 0000000000..a0a4df3428
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/001.png b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/001.png
        new file mode 100644
        index 0000000000..a0a4df3428
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/002.png b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/002.png
        new file mode 100644
        index 0000000000..f8284ba852
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/003.png b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/003.png
        new file mode 100644
        index 0000000000..5e8e4be2f2
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/004.png b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/004.png
        new file mode 100644
        index 0000000000..3d79f196a4
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/metadata.json b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/metadata.json
        new file mode 100644
        index 0000000000..01dd8f26ca
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts from files/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 5
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/000.png b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/000.png
        new file mode 100644
        index 0000000000..2e1331d994
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/001.png b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/001.png
        new file mode 100644
        index 0000000000..2e1331d994
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/001.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/002.png b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/002.png
        new file mode 100644
        index 0000000000..0ce9cff826
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/002.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/003.png b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/003.png
        new file mode 100644
        index 0000000000..5ba14b1f2c
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/003.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/004.png b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/004.png
        new file mode 100644
        index 0000000000..0b18dff4af
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/004.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/metadata.json b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/metadata.json
        new file mode 100644
        index 0000000000..01dd8f26ca
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textWeight/can control variable fonts/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 5
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/Typography/textWidth/verify width of a string/000.png b/test/unit/visual/screenshots/Typography/textWidth/verify width of a string/000.png
        new file mode 100644
        index 0000000000..7f196450e2
        Binary files /dev/null and b/test/unit/visual/screenshots/Typography/textWidth/verify width of a string/000.png differ
        diff --git a/test/unit/visual/screenshots/Typography/textWidth/verify width of a string/metadata.json b/test/unit/visual/screenshots/Typography/textWidth/verify width of a string/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/Typography/textWidth/verify width of a string/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/3DModel/OBJ model with MTL file displays diffuse colors correctly/000.png b/test/unit/visual/screenshots/WebGL/3DModel/OBJ model with MTL file displays diffuse colors correctly/000.png
        index 0426415f31..ddc88b1044 100644
        Binary files a/test/unit/visual/screenshots/WebGL/3DModel/OBJ model with MTL file displays diffuse colors correctly/000.png and b/test/unit/visual/screenshots/WebGL/3DModel/OBJ model with MTL file displays diffuse colors correctly/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/3DModel/Object with different texture coordinates per use of vertex keeps the coordinates intact/000.png b/test/unit/visual/screenshots/WebGL/3DModel/Object with different texture coordinates per use of vertex keeps the coordinates intact/000.png
        index dcba4010ac..bbf407af53 100644
        Binary files a/test/unit/visual/screenshots/WebGL/3DModel/Object with different texture coordinates per use of vertex keeps the coordinates intact/000.png and b/test/unit/visual/screenshots/WebGL/3DModel/Object with different texture coordinates per use of vertex keeps the coordinates intact/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/3DModel/Object with no colors takes on fill color/000.png b/test/unit/visual/screenshots/WebGL/3DModel/Object with no colors takes on fill color/000.png
        index 5d7801aa04..28dd2a7fd5 100644
        Binary files a/test/unit/visual/screenshots/WebGL/3DModel/Object with no colors takes on fill color/000.png and b/test/unit/visual/screenshots/WebGL/3DModel/Object with no colors takes on fill color/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Camera/2D objects maintain correct size/000.png b/test/unit/visual/screenshots/WebGL/Camera/2D objects maintain correct size/000.png
        index d2daba574f..f56c898bc5 100644
        Binary files a/test/unit/visual/screenshots/WebGL/Camera/2D objects maintain correct size/000.png and b/test/unit/visual/screenshots/WebGL/Camera/2D objects maintain correct size/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/000.png b/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/000.png
        index b3cc6ed609..d4000e5a14 100644
        Binary files a/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/000.png and b/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/001.png b/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/001.png
        index 74fb513043..788178ce2f 100644
        Binary files a/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/001.png and b/test/unit/visual/screenshots/WebGL/Camera/Custom camera before and after resize/001.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Camera space/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Camera space/000.png
        new file mode 100644
        index 0000000000..5878075344
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Camera space/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Camera space/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Camera space/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Camera space/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/000.png
        new file mode 100644
        index 0000000000..ee9d8c2748
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/001.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/001.png
        new file mode 100644
        index 0000000000..ee9d8c2748
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/001.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/002.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/002.png
        new file mode 100644
        index 0000000000..ee9d8c2748
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/002.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/metadata.json
        new file mode 100644
        index 0000000000..0c316a63ab
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Combined vs split matrices/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 3
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Object space/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Object space/000.png
        new file mode 100644
        index 0000000000..9692230099
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Object space/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Object space/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Object space/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/Object space/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/World space/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/World space/000.png
        new file mode 100644
        index 0000000000..075543a498
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/World space/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/World space/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/World space/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseColorShader/World space/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Camera space/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Camera space/000.png
        new file mode 100644
        index 0000000000..41f35381dd
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Camera space/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Camera space/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Camera space/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Camera space/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/000.png
        new file mode 100644
        index 0000000000..1b77843e48
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/001.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/001.png
        new file mode 100644
        index 0000000000..1b77843e48
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/001.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/002.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/002.png
        new file mode 100644
        index 0000000000..1b77843e48
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/002.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/metadata.json
        new file mode 100644
        index 0000000000..0c316a63ab
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Combined vs split matrices/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 3
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Object space/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Object space/000.png
        new file mode 100644
        index 0000000000..f6fab1658c
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Object space/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Object space/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Object space/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/Object space/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/World space/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/World space/000.png
        new file mode 100644
        index 0000000000..f545c7da45
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/World space/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/World space/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/World space/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseMaterialShader/World space/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Camera space/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Camera space/000.png
        new file mode 100644
        index 0000000000..1a5e70fea6
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Camera space/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Camera space/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Camera space/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Camera space/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/000.png
        new file mode 100644
        index 0000000000..62eb6320b8
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/001.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/001.png
        new file mode 100644
        index 0000000000..62eb6320b8
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/001.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/002.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/002.png
        new file mode 100644
        index 0000000000..62eb6320b8
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/002.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/metadata.json
        new file mode 100644
        index 0000000000..0c316a63ab
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Combined vs split matrices/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 3
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Object space/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Object space/000.png
        new file mode 100644
        index 0000000000..2f428f5f74
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Object space/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Object space/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Object space/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/Object space/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/World space/000.png b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/World space/000.png
        new file mode 100644
        index 0000000000..3e03667ebb
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/World space/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/World space/metadata.json b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/World space/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Hooks coordinate spaces/baseNormalShader/World space/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Lights/Fill color and default ambient material/000.png b/test/unit/visual/screenshots/WebGL/Lights/Fill color and default ambient material/000.png
        index 346374eb47..f6b1feb5b8 100644
        Binary files a/test/unit/visual/screenshots/WebGL/Lights/Fill color and default ambient material/000.png and b/test/unit/visual/screenshots/WebGL/Lights/Fill color and default ambient material/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Opacity/Basic colors have opacity applied correctly/000.png b/test/unit/visual/screenshots/WebGL/Opacity/Basic colors have opacity applied correctly/000.png
        new file mode 100644
        index 0000000000..b885704e16
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Opacity/Basic colors have opacity applied correctly/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Opacity/Basic colors have opacity applied correctly/metadata.json b/test/unit/visual/screenshots/WebGL/Opacity/Basic colors have opacity applied correctly/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Opacity/Basic colors have opacity applied correctly/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Opacity/Colors have opacity applied correctly when lights are used/000.png b/test/unit/visual/screenshots/WebGL/Opacity/Colors have opacity applied correctly when lights are used/000.png
        new file mode 100644
        index 0000000000..b885704e16
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Opacity/Colors have opacity applied correctly when lights are used/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Opacity/Colors have opacity applied correctly when lights are used/metadata.json b/test/unit/visual/screenshots/WebGL/Opacity/Colors have opacity applied correctly when lights are used/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Opacity/Colors have opacity applied correctly when lights are used/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Opacity/Colors in shader hooks have opacity applied correctly/000.png b/test/unit/visual/screenshots/WebGL/Opacity/Colors in shader hooks have opacity applied correctly/000.png
        new file mode 100644
        index 0000000000..b885704e16
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Opacity/Colors in shader hooks have opacity applied correctly/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Opacity/Colors in shader hooks have opacity applied correctly/metadata.json b/test/unit/visual/screenshots/WebGL/Opacity/Colors in shader hooks have opacity applied correctly/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Opacity/Colors in shader hooks have opacity applied correctly/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Opacity/Colors in textures have opacity applied correctly/000.png b/test/unit/visual/screenshots/WebGL/Opacity/Colors in textures have opacity applied correctly/000.png
        new file mode 100644
        index 0000000000..b885704e16
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Opacity/Colors in textures have opacity applied correctly/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Opacity/Colors in textures have opacity applied correctly/metadata.json b/test/unit/visual/screenshots/WebGL/Opacity/Colors in textures have opacity applied correctly/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Opacity/Colors in textures have opacity applied correctly/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Opacity/Colors in tinted textures have opacity applied correctly/000.png b/test/unit/visual/screenshots/WebGL/Opacity/Colors in tinted textures have opacity applied correctly/000.png
        new file mode 100644
        index 0000000000..b885704e16
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Opacity/Colors in tinted textures have opacity applied correctly/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Opacity/Colors in tinted textures have opacity applied correctly/metadata.json b/test/unit/visual/screenshots/WebGL/Opacity/Colors in tinted textures have opacity applied correctly/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Opacity/Colors in tinted textures have opacity applied correctly/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/FillShader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/FillShader/000.png
        new file mode 100644
        index 0000000000..c2c2842507
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/FillShader/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/FillShader/metadata.json b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/FillShader/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/FillShader/metadata.json
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/ImageShader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/ImageShader/000.png
        new file mode 100644
        index 0000000000..135079c7f7
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/ImageShader/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/ImageShader/metadata.json b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/ImageShader/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/ImageShader/metadata.json
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/StrokeShader/000.png b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/StrokeShader/000.png
        new file mode 100644
        index 0000000000..398d95eca4
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/StrokeShader/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/ShaderFunctionality/StrokeShader/metadata.json b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/StrokeShader/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/ShaderFunctionality/StrokeShader/metadata.json
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/Strokes/Strokes do not cut into fills in ortho mode/000.png b/test/unit/visual/screenshots/WebGL/Strokes/Strokes do not cut into fills in ortho mode/000.png
        new file mode 100644
        index 0000000000..344fa7ceb8
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/Strokes/Strokes do not cut into fills in ortho mode/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/Strokes/Strokes do not cut into fills in ortho mode/metadata.json b/test/unit/visual/screenshots/WebGL/Strokes/Strokes do not cut into fills in ortho mode/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/Strokes/Strokes do not cut into fills in ortho mode/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/buildGeometry()/can draw models/000.png b/test/unit/visual/screenshots/WebGL/buildGeometry()/can draw models/000.png
        new file mode 100644
        index 0000000000..777ade92ce
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/buildGeometry()/can draw models/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/buildGeometry()/can draw models/metadata.json b/test/unit/visual/screenshots/WebGL/buildGeometry()/can draw models/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/buildGeometry()/can draw models/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/buildGeometry()/only fills set in buildGeometry are kept/000.png b/test/unit/visual/screenshots/WebGL/buildGeometry()/only fills set in buildGeometry are kept/000.png
        new file mode 100644
        index 0000000000..24ac984988
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/buildGeometry()/only fills set in buildGeometry are kept/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/buildGeometry()/only fills set in buildGeometry are kept/metadata.json b/test/unit/visual/screenshots/WebGL/buildGeometry()/only fills set in buildGeometry are kept/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/buildGeometry()/only fills set in buildGeometry are kept/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/erase()/on a framebuffer/000.png b/test/unit/visual/screenshots/WebGL/erase()/on a framebuffer/000.png
        new file mode 100644
        index 0000000000..39c1f82597
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/erase()/on a framebuffer/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/erase()/on a framebuffer/metadata.json b/test/unit/visual/screenshots/WebGL/erase()/on a framebuffer/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/erase()/on a framebuffer/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/erase()/on the main canvas/000.png b/test/unit/visual/screenshots/WebGL/erase()/on the main canvas/000.png
        new file mode 100644
        index 0000000000..464ffec3a4
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/erase()/on the main canvas/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/erase()/on the main canvas/metadata.json b/test/unit/visual/screenshots/WebGL/erase()/on the main canvas/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/erase()/on the main canvas/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/000.png b/test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/000.png
        new file mode 100644
        index 0000000000..9679aa86dd
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/metadata.json b/test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/filter/In 2d mode/It can combine multiple filter passes/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/filter/In webgl mode/It can combine multiple filter passes/000.png b/test/unit/visual/screenshots/WebGL/filter/In webgl mode/It can combine multiple filter passes/000.png
        new file mode 100644
        index 0000000000..ed3273a6a6
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/filter/In webgl mode/It can combine multiple filter passes/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/filter/In webgl mode/It can combine multiple filter passes/metadata.json b/test/unit/visual/screenshots/WebGL/filter/In webgl mode/It can combine multiple filter passes/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/filter/In webgl mode/It can combine multiple filter passes/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/filter/On a framebuffer sized differently from the main canvas/000.png b/test/unit/visual/screenshots/WebGL/filter/On a framebuffer sized differently from the main canvas/000.png
        index 90ccca1c5d..197629c93f 100644
        Binary files a/test/unit/visual/screenshots/WebGL/filter/On a framebuffer sized differently from the main canvas/000.png and b/test/unit/visual/screenshots/WebGL/filter/On a framebuffer sized differently from the main canvas/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/filter/On a framebuffer/000.png b/test/unit/visual/screenshots/WebGL/filter/On a framebuffer/000.png
        index 90ccca1c5d..197629c93f 100644
        Binary files a/test/unit/visual/screenshots/WebGL/filter/On a framebuffer/000.png and b/test/unit/visual/screenshots/WebGL/filter/On a framebuffer/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/textToModel/Extruded/000.png b/test/unit/visual/screenshots/WebGL/textToModel/Extruded/000.png
        new file mode 100644
        index 0000000000..5751dac472
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/textToModel/Extruded/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/textToModel/Extruded/metadata.json b/test/unit/visual/screenshots/WebGL/textToModel/Extruded/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/textToModel/Extruded/metadata.json
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/textToModel/Flat/000.png b/test/unit/visual/screenshots/WebGL/textToModel/Flat/000.png
        new file mode 100644
        index 0000000000..194da82c84
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/textToModel/Flat/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/textToModel/Flat/metadata.json b/test/unit/visual/screenshots/WebGL/textToModel/Flat/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/textToModel/Flat/metadata.json
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/vertexProperty/on PATH shape mode/000.png b/test/unit/visual/screenshots/WebGL/vertexProperty/on PATH shape mode/000.png
        new file mode 100644
        index 0000000000..3dcaf9bc31
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/vertexProperty/on PATH shape mode/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/vertexProperty/on PATH shape mode/metadata.json b/test/unit/visual/screenshots/WebGL/vertexProperty/on PATH shape mode/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/vertexProperty/on PATH shape mode/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/vertexProperty/on QUADS shape mode/000.png b/test/unit/visual/screenshots/WebGL/vertexProperty/on QUADS shape mode/000.png
        new file mode 100644
        index 0000000000..75018122a4
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/vertexProperty/on QUADS shape mode/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/vertexProperty/on QUADS shape mode/metadata.json b/test/unit/visual/screenshots/WebGL/vertexProperty/on QUADS shape mode/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/vertexProperty/on QUADS shape mode/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/screenshots/WebGL/vertexProperty/on buildGeometry outputs containing 3D primitives/000.png b/test/unit/visual/screenshots/WebGL/vertexProperty/on buildGeometry outputs containing 3D primitives/000.png
        new file mode 100644
        index 0000000000..065fd3b563
        Binary files /dev/null and b/test/unit/visual/screenshots/WebGL/vertexProperty/on buildGeometry outputs containing 3D primitives/000.png differ
        diff --git a/test/unit/visual/screenshots/WebGL/vertexProperty/on buildGeometry outputs containing 3D primitives/metadata.json b/test/unit/visual/screenshots/WebGL/vertexProperty/on buildGeometry outputs containing 3D primitives/metadata.json
        new file mode 100644
        index 0000000000..2d4bfe30da
        --- /dev/null
        +++ b/test/unit/visual/screenshots/WebGL/vertexProperty/on buildGeometry outputs containing 3D primitives/metadata.json	
        @@ -0,0 +1,3 @@
        +{
        +  "numScreenshots": 1
        +}
        \ No newline at end of file
        diff --git a/test/unit/visual/visualTest.js b/test/unit/visual/visualTest.js
        index b9e902445d..068e8c7247 100644
        --- a/test/unit/visual/visualTest.js
        +++ b/test/unit/visual/visualTest.js
        @@ -1,13 +1,26 @@
        -/**
        - * A helper class to contain an error and also the screenshot data that
        - * caused the error.
        - */
        -class ScreenshotError extends Error {
        -  constructor(message, actual, expected) {
        -    super(message);
        -    this.actual = actual;
        -    this.expected = expected;
        -  }
        +import p5 from '../../../src/app.js';
        +import { server } from '@vitest/browser/context'
        +import { THRESHOLD, DIFFERENCE, ERODE } from '../../../src/core/constants.js';
        +const { readFile, writeFile } = server.commands
        +
        +// By how much can each color channel value (0-255) differ before
        +// we call it a mismatch? This should be large enough to not trigger
        +// based on antialiasing.
        +const COLOR_THRESHOLD = 25;
        +
        +// The max side length to shrink test images down to before
        +// comparing, for performance.
        +const MAX_SIDE = 50;
        +
        +// The background color to composite test cases onto before
        +// diffing. This is used because canvas DIFFERENCE blend mode
        +// does not handle alpha well. This should be a color that is
        +// unlikely to be in the images originally.
        +const BG = '#F0F';
        +
        +function writeImageFile(filename, base64Data) {
        +  const prefix = /^data:image\/\w+;base64,/;
        +  writeFile(filename, base64Data.replace(prefix, ''), 'base64');
         }
         
         function toBase64(img) {
        @@ -21,6 +34,11 @@ function escapeName(name) {
         
         let namePrefix = '';
         
        +// By how many pixels can the snapshot shift? This is
        +// often useful to accommodate different text rendering
        +// across environments.
        +let shiftThreshold = 2;
        +
         /**
          * A helper to define a category of visual tests.
          *
        @@ -30,57 +48,96 @@ let namePrefix = '';
          * @param [options] An options object with optional additional settings. Set its
          * key `focus` to true to only run this test, or its `skip` key to skip it.
          */
        -window.visualSuite = function(
        +export function visualSuite(
           name,
           callback,
        -  { focus = false, skip = false } = {}
        +  { focus = false, skip = false, shiftThreshold: newShiftThreshold } = {}
         ) {
        -  const lastPrefix = namePrefix;
        -  namePrefix += escapeName(name) + '/';
        -
        -  let suiteFn = suite;
        +  let suiteFn = describe;
           if (focus) {
             suiteFn = suiteFn.only;
           }
           if (skip) {
             suiteFn = suiteFn.skip;
           }
        -  suiteFn(name, callback);
        +  suiteFn(name, () => {
        +    let lastShiftThreshold
        +    let lastPrefix;
        +    let lastDeviceRatio = window.devicePixelRatio;
        +    beforeAll(() => {
        +      lastPrefix = namePrefix;
        +      namePrefix += escapeName(name) + '/';
        +      lastShiftThreshold = shiftThreshold;
        +      if (newShiftThreshold !== undefined) {
        +        shiftThreshold = newShiftThreshold
        +      }
         
        -  namePrefix = lastPrefix;
        -};
        +      // Force everything to be 1x
        +      window.devicePixelRatio = 1;
        +    })
        +
        +    callback()
        +
        +    afterAll(() => {
        +      namePrefix = lastPrefix;
        +      window.devicePixelRatio = lastDeviceRatio;
        +      shiftThreshold = lastShiftThreshold;
        +    });
        +  });
        +}
        +
        +export async function checkMatch(actual, expected, p5) {
        +  let scale = Math.min(MAX_SIDE/expected.width, MAX_SIDE/expected.height);
        +
        +  // Long screenshots end up super tiny when fit to a small square, so we
        +  // can double the max side length for these
        +  const ratio = expected.width / expected.height;
        +  const narrow = ratio !== 1;
        +  if (narrow) {
        +    scale *= 2;
        +  }
         
        -window.checkMatch = function(actual, expected, p5) {
        -  const maxSide = 50;
        -  const scale = Math.min(maxSide/expected.width, maxSide/expected.height);
           for (const img of [actual, expected]) {
             img.resize(
               Math.ceil(img.width * scale),
               Math.ceil(img.height * scale)
             );
           }
        -  // The below algorithm to calculate differences can produce false negatives in certain cases.
        -  // Immediately return true for exact matches.
        -  if (toBase64(expected) === toBase64(actual)) {
        -    return { ok: true };
        +
        +  const expectedWithBg = p5.createGraphics(expected.width, expected.height);
        +  expectedWithBg.pixelDensity(1);
        +  expectedWithBg.background(BG);
        +  expectedWithBg.image(expected, 0, 0);
        +
        +  const cnv = p5.createGraphics(actual.width, actual.height);
        +  cnv.pixelDensity(1);
        +  cnv.background(BG);
        +  cnv.image(actual, 0, 0);
        +  cnv.blendMode(DIFFERENCE);
        +  cnv.image(expectedWithBg, 0, 0);
        +  for (let i = 0; i < shiftThreshold; i++) {
        +    cnv.filter(ERODE, false);
           }
        -  const diff = p5.createImage(actual.width, actual.height);
        -  diff.drawingContext.drawImage(actual.canvas, 0, 0);
        -  diff.drawingContext.globalCompositeOperation = 'difference';
        -  diff.drawingContext.drawImage(expected.canvas, 0, 0);
        -  diff.filter(p5.ERODE, false);
        +  const diff = cnv.get();
        +  cnv.remove();
           diff.loadPixels();
        +  expectedWithBg.remove();
         
           let ok = true;
        -  for (let i = 0; i < diff.pixels.length; i++) {
        -    if (i % 4 === 3) continue; // Skip alpha checks
        -    if (Math.abs(diff.pixels[i]) > 10) {
        +  for (let i = 0; i < diff.pixels.length; i += 4) {
        +    let diffSum = 0;
        +    for (let off = 0; off < 3; off++) {
        +      diffSum += diff.pixels[i+off]
        +    }
        +    diffSum /= 3;
        +    if (diffSum > COLOR_THRESHOLD) {
               ok = false;
               break;
             }
           }
        +
           return { ok, diff };
        -};
        +}
         
         /**
          * A helper to define a visual test, where we will assert that a sketch matches
        @@ -101,13 +158,12 @@ window.checkMatch = function(actual, expected, p5) {
          * @param [options] An options object with optional additional settings. Set its
          * key `focus` to true to only run this test, or its `skip` key to skip it.
          */
        -window.visualTest = function(
        +export function visualTest(
           testName,
           callback,
           { focus = false, skip = false } = {}
         ) {
        -  const name = namePrefix + escapeName(testName);
        -  let suiteFn = suite;
        +  let suiteFn = describe;
           if (focus) {
             suiteFn = suiteFn.only;
           }
        @@ -116,9 +172,11 @@ window.visualTest = function(
           }
         
           suiteFn(testName, function() {
        +    let name;
             let myp5;
         
        -    setup(function() {
        +    beforeAll(function() {
        +      name = namePrefix + escapeName(testName);
               return new Promise(res => {
                 myp5 = new p5(function(p) {
                   p.setup = function() {
        @@ -128,32 +186,29 @@ window.visualTest = function(
               });
             });
         
        -    teardown(function() {
        +    afterAll(function() {
               myp5.remove();
             });
         
             test('matches expected screenshots', async function() {
               let expectedScreenshots;
               try {
        -        metadata = await fetch(
        -          `unit/visual/screenshots/${name}/metadata.json`
        -        ).then(res => res.json());
        +        const metadata = JSON.parse(await readFile(
        +          `../screenshots/${name}/metadata.json`
        +        ));
                 expectedScreenshots = metadata.numScreenshots;
               } catch (e) {
        +        console.log(e);
                 expectedScreenshots = 0;
               }
         
        -      if (!window.shouldGenerateScreenshots && !expectedScreenshots) {
        -        // If running on CI, all expected screenshots should already
        -        // be generated
        -        throw new Error('No expected screenshots found');
        -      }
        -
               const actual = [];
         
               // Generate screenshots
               await callback(myp5, () => {
        -        actual.push(myp5.get());
        +        const img = myp5.get();
        +        img.pixelDensity(1);
        +        actual.push(img);
               });
         
         
        @@ -166,34 +221,31 @@ window.visualTest = function(
                 );
               }
               if (!expectedScreenshots) {
        -        writeTextFile(
        -          `unit/visual/screenshots/${name}/metadata.json`,
        +        await writeFile(
        +          `../screenshots/${name}/metadata.json`,
                   JSON.stringify({ numScreenshots: actual.length }, null, 2)
                 );
               }
         
               const expectedFilenames = actual.map(
        -        (_, i) => `unit/visual/screenshots/${name}/${i.toString().padStart(3, '0')}.png`
        +        (_, i) => `../screenshots/${name}/${i.toString().padStart(3, '0')}.png`
               );
               const expected = expectedScreenshots
                 ? (
                   await Promise.all(
        -            expectedFilenames.map(path => new Promise((resolve, reject) => {
        -              myp5.loadImage(path, resolve, reject);
        -            }))
        +            expectedFilenames.map(path => myp5.loadImage('/unit/visual' + path.slice(2)))
                   )
                 )
                 : [];
         
               for (let i = 0; i < actual.length; i++) {
                 if (expected[i]) {
        -          if (!checkMatch(actual[i], expected[i], myp5).ok) {
        -            throw new ScreenshotError(
        -              `Screenshots do not match! Expected:\n${toBase64(expected[i])}\n\nReceived:\n${toBase64(actual[i])}\n\n` +
        -              'If this is unexpected, paste these URLs into your browser to inspect them, or run grunt yui:dev and go to http://127.0.0.1:9001/test/visual.html.\n\n' +
        -              `If this change is expected, please delete the test/unit/visual/screenshots/${name} folder and run tests again to generate a new screenshot.`,
        -              actual[i],
        -              expected[i]
        +          const result = await checkMatch(actual[i], expected[i], myp5);
        +          if (!result.ok) {
        +            throw new Error(
        +              `Screenshots do not match! Expected:\n${toBase64(expected[i])}\n\nReceived:\n${toBase64(actual[i])}\n\nDiff:\n${toBase64(result.diff)}\n\n` +
        +              'If this is unexpected, paste these URLs into your browser to inspect them.\n\n' +
        +              `If this change is expected, please delete the screenshots/${name} folder and run tests again to generate a new screenshot.`,
                     );
                   }
                 } else {
        @@ -202,4 +254,4 @@ window.visualTest = function(
               }
             });
           });
        -};
        +}
        diff --git a/test/unit/webgl/3d_primitives.js b/test/unit/webgl/3d_primitives.js
        index 520cb531af..849c0db326 100644
        --- a/test/unit/webgl/3d_primitives.js
        +++ b/test/unit/webgl/3d_primitives.js
        @@ -1,11 +1,9 @@
        +import p5 from '../../../src/app.js';
        +
         suite('3D Primitives', function() {
           var myp5;
         
        -  if (!window.Modernizr.webgl) {
        -    return;
        -  }
        -
        -  setup(function() {
        +  beforeAll(function() {
             myp5 = new p5(function(p) {
               p.setup = function() {
                 p.createCanvas(100, 100, p.WEBGL);
        @@ -13,7 +11,7 @@ suite('3D Primitives', function() {
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -22,29 +20,6 @@ suite('3D Primitives', function() {
               assert.ok(myp5.plane);
               assert.typeOf(myp5.plane, 'function');
             });
        -    test('no friendly-err-msg. missing height param #1.', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.plane(20);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.plane('a', 10);
        -      });
        -    });
        -    test('no friendly-err-msg. no parameters', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.plane();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
           });
         
           suite('p5.prototype.box', function() {
        @@ -52,38 +27,6 @@ suite('3D Primitives', function() {
               assert.ok(myp5.box);
               assert.typeOf(myp5.box, 'function');
             });
        -    test('wrong param type at #0 and #2', function() {
        -      assert.validationError(function() {
        -        myp5.box('a', 10, 'c');
        -      });
        -    });
        -    test('no friendly-err-msg. missing height, depth; param #1, #2.', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.box(20, 20);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg. missing depth param #2.', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.box(20, 20);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg. no parameters', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.box();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
           });
         
           suite('p5.prototype.sphere', function() {
        @@ -91,20 +34,6 @@ suite('3D Primitives', function() {
               assert.ok(myp5.sphere);
               assert.typeOf(myp5.sphere, 'function');
             });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.sphere('a');
        -      });
        -    });
        -    test('no friendly-err-msg. no parameters', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.sphere();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
           });
         
           suite('p5.prototype.cylinder', function() {
        @@ -112,35 +41,6 @@ suite('3D Primitives', function() {
               assert.ok(myp5.cylinder);
               assert.typeOf(myp5.cylinder, 'function');
             });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.cylinder('r', 10, 10);
        -      });
        -    });
        -    test('no friendly-err-msg. missing height; param #1', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.cylinder(20);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    //Parameter validation not done for bottomCap, topCap
        -    test.skip('wrong param type at #4', function() {
        -      assert.validationError(function() {
        -        myp5.cylinder('r', 10, 10, 14, 'a');
        -      });
        -    });
        -    test('no friendly-err-msg. no parameters', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.cylinder();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
           });
         
           suite('p5.prototype.cone', function() {
        @@ -148,35 +48,6 @@ suite('3D Primitives', function() {
               assert.ok(myp5.cone);
               assert.typeOf(myp5.cone, 'function');
             });
        -    test('wrong param type at #0 and #1', function() {
        -      assert.validationError(function() {
        -        myp5.cone('r', false, 10);
        -      });
        -    });
        -    test('no friendly-err-msg. missing height; param #1', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.cone(20);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    //Parameter validation not done for cap
        -    test.skip('wrong param type at #4', function() {
        -      assert.validationError(function() {
        -        myp5.cone(10, 10, 10, 14, 'false');
        -      });
        -    });
        -    test('no friendly-err-msg. no parameters', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.cone();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
           });
         
           suite('p5.prototype.ellipsoid', function() {
        @@ -184,29 +55,6 @@ suite('3D Primitives', function() {
               assert.ok(myp5.ellipsoid);
               assert.typeOf(myp5.ellipsoid, 'function');
             });
        -    test('wrong param type at #0 and #1', function() {
        -      assert.validationError(function() {
        -        myp5.ellipsoid('x', 'y', 10);
        -      });
        -    });
        -    test('no friendly-err-msg. missing param #1 #2', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.ellipsoid(10);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg. no parameters', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.ellipsoid();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
           });
         
           suite('p5.prototype.torus', function() {
        @@ -214,113 +62,5 @@ suite('3D Primitives', function() {
               assert.ok(myp5.torus);
               assert.typeOf(myp5.torus, 'function');
             });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.torus(false, 10);
        -      });
        -    });
        -    test('no friendly-err-msg. missing param #1', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.torus(30);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg. no parameters', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.torus();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -  });
        -
        -  suite('p5.RendererGL.prototype.ellipse', function() {
        -    test('should be a function', function() {
        -      assert.ok(myp5._renderer.ellipse);
        -      assert.typeOf(myp5._renderer.ellipse, 'function');
        -    });
        -    test('no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.ellipse(0, 0, 100);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('missing param #2', function() {
        -      assert.validationError(function() {
        -        myp5.ellipse(0, 0);
        -      });
        -    });
        -    test('missing param #2', function() {
        -      assert.validationError(function() {
        -        var size;
        -        myp5.ellipse(0, 0, size);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.ellipse('a', 0, 100, 100);
        -      });
        -    });
        -    test('no friendly-err-msg. detail parameter > 50', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.ellipse(50, 50, 120, 30, 51);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -  });
        -
        -  suite('p5.RendererGL.prototype.arc', function() {
        -    test('should be a function', function() {
        -      assert.ok(myp5._renderer.arc);
        -      assert.typeOf(myp5._renderer.arc, 'function');
        -    });
        -    test('no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.arc(1, 1, 10.5, 10, 0, Math.PI, 'pie');
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('missing param #4, #5', function() {
        -      assert.validationError(function() {
        -        myp5.arc(1, 1, 10.5, 10);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.arc('a', 1, 10.5, 10, 0, Math.PI, 'pie');
        -      });
        -    });
        -    test('no friendly-err-msg. detail parameter > 50', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.arc(1, 1, 100, 100, 0, Math.PI / 2, 'chord', 51);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('no friendly-err-msg. default mode', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.arc(1, 1, 100, 100, Math.PI / 4, Math.PI / 3);
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
           });
         });
        diff --git a/test/unit/webgl/interaction.js b/test/unit/webgl/interaction.js
        index b490d6aba6..f03433d841 100644
        --- a/test/unit/webgl/interaction.js
        +++ b/test/unit/webgl/interaction.js
        @@ -1,10 +1,9 @@
        +import p5 from '../../../src/app.js';
        +
         suite('Interaction', function() {
           var myp5;
        -  if (!window.Modernizr.webgl) {
        -    return;
        -  }
         
        -  setup(function() {
        +  beforeAll(function() {
             myp5 = new p5(function(p) {
               p.setup = function() {
                 p.createCanvas(100, 100, p.WEBGL);
        @@ -12,7 +11,7 @@ suite('Interaction', function() {
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -21,20 +20,6 @@ suite('Interaction', function() {
               assert.ok(myp5.orbitControl);
               assert.typeOf(myp5.orbitControl, 'function');
             });
        -    test('missing params. no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.orbitControl();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('wrong param type #0', function() {
        -      assert.validationError(function() {
        -        myp5.orbitControl('s');
        -      });
        -    });
           });
         
           suite('p5.prototype.debugMode', function() {
        @@ -42,30 +27,6 @@ suite('Interaction', function() {
               assert.ok(myp5.debugMode);
               assert.typeOf(myp5.debugMode, 'function');
             });
        -    test('missing params. no friendly-err-msg', function() {
        -      assert.doesNotThrow(
        -        function() {
        -          myp5.debugMode();
        -        },
        -        Error,
        -        'got unwanted exception'
        -      );
        -    });
        -    test('wrong param type #0', function() {
        -      assert.validationError(function() {
        -        myp5.debugMode(myp5.CORNER);
        -      });
        -    });
        -    test('wrong param type #2', function() {
        -      assert.validationError(function() {
        -        myp5.debugMode(myp5.AXES, 1, 1, 'a', 2);
        -      });
        -    });
        -    test('wrong param type #2', function() {
        -      assert.validationError(function() {
        -        myp5.debugMode(myp5.GRID, 1, 1, 'a');
        -      });
        -    });
           });
         
           suite('p5.prototype.noDebugMode', function() {
        diff --git a/test/unit/webgl/light.js b/test/unit/webgl/light.js
        index bf14889d2e..3f8785a5c9 100644
        --- a/test/unit/webgl/light.js
        +++ b/test/unit/webgl/light.js
        @@ -1,11 +1,9 @@
        +import p5 from '../../../src/app.js';
        +
         suite('light', function() {
           var myp5;
         
        -  if (!window.Modernizr.webgl) {
        -    return;
        -  }
        -
        -  setup(function() {
        +  beforeAll(function() {
             myp5 = new p5(function(p) {
               p.setup = function() {
                 p.createCanvas(100, 100, p.WEBGL);
        @@ -13,31 +11,31 @@ suite('light', function() {
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
           suite('Light', function() {
             test('lightFalloff is initialised and set properly', function() {
        -      assert.deepEqual(myp5._renderer.constantAttenuation, 1);
        -      assert.deepEqual(myp5._renderer.linearAttenuation, 0);
        -      assert.deepEqual(myp5._renderer.quadraticAttenuation, 0);
        +      assert.deepEqual(myp5._renderer.states.constantAttenuation, 1);
        +      assert.deepEqual(myp5._renderer.states.linearAttenuation, 0);
        +      assert.deepEqual(myp5._renderer.states.quadraticAttenuation, 0);
               myp5.lightFalloff(2, 3, 4);
        -      assert.deepEqual(myp5._renderer.constantAttenuation, 2);
        -      assert.deepEqual(myp5._renderer.linearAttenuation, 3);
        -      assert.deepEqual(myp5._renderer.quadraticAttenuation, 4);
        +      assert.deepEqual(myp5._renderer.states.constantAttenuation, 2);
        +      assert.deepEqual(myp5._renderer.states.linearAttenuation, 3);
        +      assert.deepEqual(myp5._renderer.states.quadraticAttenuation, 4);
             });
         
             test('specularColor is initialised and set properly', function() {
        -      assert.deepEqual(myp5._renderer.specularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.pointLightSpecularColors, []);
        -      assert.deepEqual(myp5._renderer.directionalLightSpecularColors, []);
        +      assert.deepEqual(myp5._renderer.states.specularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.pointLightSpecularColors, []);
        +      assert.deepEqual(myp5._renderer.states.directionalLightSpecularColors, []);
               myp5.specularColor(255, 0, 0);
        -      assert.deepEqual(myp5._renderer.specularColors, [1, 0, 0]);
        +      assert.deepEqual(myp5._renderer.states.specularColors, [1, 0, 0]);
               myp5.pointLight(255, 0, 0, 1, 0, 0);
               myp5.directionalLight(255, 0, 0, 0, 0, 0);
        -      assert.deepEqual(myp5._renderer.pointLightSpecularColors, [1, 0, 0]);
        -      assert.deepEqual(myp5._renderer.directionalLightSpecularColors, [
        +      assert.deepEqual(myp5._renderer.states.pointLightSpecularColors, [1, 0, 0]);
        +      assert.deepEqual(myp5._renderer.states.directionalLightSpecularColors, [
                 1,
                 0,
                 0
        @@ -52,19 +50,19 @@ suite('light', function() {
               myp5.shininess(50);
         
               myp5.noLights();
        -      assert.deepEqual([], myp5._renderer.ambientLightColors);
        -      assert.deepEqual([], myp5._renderer.pointLightDiffuseColors);
        -      assert.deepEqual([], myp5._renderer.pointLightSpecularColors);
        -      assert.deepEqual([], myp5._renderer.pointLightPositions);
        -      assert.deepEqual([], myp5._renderer.directionalLightDiffuseColors);
        -      assert.deepEqual([], myp5._renderer.directionalLightSpecularColors);
        -      assert.deepEqual([], myp5._renderer.directionalLightDirections);
        -      assert.deepEqual([1, 1, 1], myp5._renderer.specularColors);
        -      assert.deepEqual([], myp5._renderer.spotLightDiffuseColors);
        -      assert.deepEqual([], myp5._renderer.spotLightSpecularColors);
        -      assert.deepEqual([], myp5._renderer.spotLightPositions);
        -      assert.deepEqual([], myp5._renderer.spotLightDirections);
        -      assert.deepEqual(1, myp5._renderer._useShininess);
        +      assert.deepEqual([], myp5._renderer.states.ambientLightColors);
        +      assert.deepEqual([], myp5._renderer.states.pointLightDiffuseColors);
        +      assert.deepEqual([], myp5._renderer.states.pointLightSpecularColors);
        +      assert.deepEqual([], myp5._renderer.states.pointLightPositions);
        +      assert.deepEqual([], myp5._renderer.states.directionalLightDiffuseColors);
        +      assert.deepEqual([], myp5._renderer.states.directionalLightSpecularColors);
        +      assert.deepEqual([], myp5._renderer.states.directionalLightDirections);
        +      assert.deepEqual([1, 1, 1], myp5._renderer.states.specularColors);
        +      assert.deepEqual([], myp5._renderer.states.spotLightDiffuseColors);
        +      assert.deepEqual([], myp5._renderer.states.spotLightSpecularColors);
        +      assert.deepEqual([], myp5._renderer.states.spotLightPositions);
        +      assert.deepEqual([], myp5._renderer.states.spotLightDirections);
        +      assert.deepEqual(1, myp5._renderer.states._useShininess);
             });
           });
         
        @@ -75,266 +73,264 @@ suite('light', function() {
             let conc = 7;
             let defaultConc = 100;
             test('default', function() {
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, []);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, []);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, []);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, []);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, []);
        -      assert.deepEqual(myp5._renderer.spotLightConc, []);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, []);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, []);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, []);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, []);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, []);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, []);
             });
             test('color,positions,directions', function() {
               let color = myp5.color(255, 0, 255);
               let positions = new p5.Vector(1, 2, 3);
               let directions = new p5.Vector(0, 1, 0);
               myp5.spotLight(color, positions, directions);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [defaultAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('color,positions,directions,angle', function() {
               let color = myp5.color(255, 0, 255);
               let positions = new p5.Vector(1, 2, 3);
               let directions = new p5.Vector(0, 1, 0);
               myp5.spotLight(color, positions, directions, angle);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('color,positions,directions,angle,conc', function() {
               let color = myp5.color(255, 0, 255);
               let positions = new p5.Vector(1, 2, 3);
               let directions = new p5.Vector(0, 1, 0);
        -      // debugger;
               myp5.spotLight(color, positions, directions, angle, conc);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [conc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [conc]);
             });
             test('c1,c2,c3,positions,directions', function() {
               let positions = new p5.Vector(1, 2, 3);
               let directions = new p5.Vector(0, 1, 0);
               myp5.spotLight(255, 0, 255, positions, directions);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [defaultAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('color,p1,p2,p3,directions', function() {
               let color = myp5.color(255, 0, 255);
               let directions = new p5.Vector(0, 1, 0);
               myp5.spotLight(color, 1, 2, 3, directions);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [defaultAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('color,positions,r1,r2,r3', function() {
               let color = myp5.color(255, 0, 255);
               let positions = new p5.Vector(1, 2, 3);
               myp5.spotLight(color, positions, 0, 1, 0);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [defaultAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('c1,c2,c3,positions,directions,angle', function() {
               let positions = new p5.Vector(1, 2, 3);
               let directions = new p5.Vector(0, 1, 0);
               myp5.spotLight(255, 0, 255, positions, directions, angle);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('color,p1,p2,p3,directions,angle', function() {
               let color = myp5.color(255, 0, 255);
               let directions = new p5.Vector(0, 1, 0);
               myp5.spotLight(color, 1, 2, 3, directions, angle);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('color,positions,r1,r2,r3,angle', function() {
               let color = myp5.color(255, 0, 255);
               let positions = new p5.Vector(1, 2, 3);
               myp5.spotLight(color, positions, 0, 1, 0, angle);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('c1,c2,c3,positions,directions,angle,conc', function() {
               let positions = new p5.Vector(1, 2, 3);
               let directions = new p5.Vector(0, 1, 0);
               myp5.spotLight(255, 0, 255, positions, directions, angle, conc);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [7]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [7]);
             });
             test('color,p1,p2,p3,directions,angle,conc', function() {
               let color = myp5.color(255, 0, 255);
               let directions = new p5.Vector(0, 1, 0);
               myp5.spotLight(color, 1, 2, 3, directions, angle, conc);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [7]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [7]);
             });
             test('color,positions,r1,r2,r3,angle,conc', function() {
               let color = myp5.color(255, 0, 255);
               let positions = new p5.Vector(1, 2, 3);
               myp5.spotLight(color, positions, 0, 1, 0, angle, conc);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [7]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [7]);
             });
             test('c1,c2,c3,p1,p2,p3,directions', function() {
               let directions = new p5.Vector(0, 1, 0);
               myp5.spotLight(255, 0, 255, 1, 2, 3, directions);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [defaultAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('c1,c2,c3,positions,r1,r2,r3', function() {
               let positions = new p5.Vector(1, 2, 3);
               myp5.spotLight(255, 0, 255, positions, 0, 1, 0);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [defaultAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('color,p1,p2,p3,r1,r2,r3', function() {
               let color = myp5.color(255, 0, 255);
               myp5.spotLight(color, 1, 2, 3, 0, 1, 0);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [defaultAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('c1,c2,c3,p1,p2,p3,directions,angle', function() {
               let directions = new p5.Vector(0, 1, 0);
               myp5.spotLight(255, 0, 255, 1, 2, 3, directions, angle);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('c1,c2,c3,positions,r1,r2,r3,angle', function() {
               let positions = new p5.Vector(1, 2, 3);
               myp5.spotLight(255, 0, 255, positions, 0, 1, 0, angle);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('color,p1,p2,p3,r1,r2,r3,angle', function() {
               let color = myp5.color(255, 0, 255);
               myp5.spotLight(color, 1, 2, 3, 0, 1, 0, angle);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('c1,c2,c3,p1,p2,p3,directions,angle,conc', function() {
               let directions = new p5.Vector(0, 1, 0);
               myp5.spotLight(255, 0, 255, 1, 2, 3, directions, angle, conc);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [7]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [7]);
             });
             test('c1,c2,c3,positions,r1,r2,r3,angle,conc', function() {
               let positions = new p5.Vector(1, 2, 3);
               myp5.spotLight(255, 0, 255, positions, 0, 1, 0, angle, conc);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [7]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [7]);
             });
             test('color,p1,p2,p3,r1,r2,r3,angle,conc', function() {
               let color = myp5.color(255, 0, 255);
               myp5.spotLight(color, 1, 2, 3, 0, 1, 0, angle, conc);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [7]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [7]);
             });
             test('c1,c2,c3,p1,p2,p3,r1,r2,r3', function() {
               myp5.spotLight(255, 0, 255, 1, 2, 3, 0, 1, 0);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [defaultAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [defaultAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('c1,c2,c3,p1,p2,p3,r1,r2,r3,angle', function() {
               myp5.spotLight(255, 0, 255, 1, 2, 3, 0, 1, 0, angle);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [defaultConc]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [defaultConc]);
             });
             test('c1,c2,c3,p1,p2,p3,r1,r2,r3,angle,conc', function() {
        -      // debugger;
               myp5.spotLight(255, 0, 255, 1, 2, 3, 0, 1, 0, angle, 7);
        -      assert.deepEqual(myp5._renderer.spotLightDiffuseColors, [1, 0, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightSpecularColors, [1, 1, 1]);
        -      assert.deepEqual(myp5._renderer.spotLightPositions, [1, 2, 3]);
        -      assert.deepEqual(myp5._renderer.spotLightDirections, [0, 1, 0]);
        -      assert.deepEqual(myp5._renderer.spotLightAngle, [cosAngle]);
        -      assert.deepEqual(myp5._renderer.spotLightConc, [7]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDiffuseColors, [1, 0, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightSpecularColors, [1, 1, 1]);
        +      assert.deepEqual(myp5._renderer.states.spotLightPositions, [1, 2, 3]);
        +      assert.deepEqual(myp5._renderer.states.spotLightDirections, [0, 1, 0]);
        +      assert.deepEqual(myp5._renderer.states.spotLightAngle, [cosAngle]);
        +      assert.deepEqual(myp5._renderer.states.spotLightConc, [7]);
             });
           });
         });
        diff --git a/test/unit/webgl/normal.js b/test/unit/webgl/normal.js
        index d8254ef7d3..cbb7870f70 100644
        --- a/test/unit/webgl/normal.js
        +++ b/test/unit/webgl/normal.js
        @@ -1,11 +1,9 @@
        +import p5 from '../../../src/app.js';
        +
         suite('', function() {
           var myp5;
         
        -  if (!window.Modernizr.webgl) {
        -    return;
        -  }
        -
        -  setup(function() {
        +  beforeAll(function() {
             myp5 = new p5(function(p) {
               p.setup = function() {
                 p.createCanvas(100, 100, p.WEBGL);
        @@ -13,7 +11,7 @@ suite('', function() {
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -22,16 +20,6 @@ suite('', function() {
               assert.ok(myp5.normal);
               assert.typeOf(myp5.normal, 'function');
             });
        -    test('missing param #1', function() {
        -      assert.validationError(function() {
        -        myp5.normal(10);
        -      });
        -    });
        -    test('wrong param type at #0', function() {
        -      assert.validationError(function() {
        -        myp5.normal('a', 1);
        -      });
        -    });
             test('accepts numeric arguments', function() {
               assert.doesNotThrow(
                 function() {
        diff --git a/test/unit/webgl/p5.Camera.js b/test/unit/webgl/p5.Camera.js
        index b746a5053e..f3e6e24850 100644
        --- a/test/unit/webgl/p5.Camera.js
        +++ b/test/unit/webgl/p5.Camera.js
        @@ -1,4 +1,6 @@
        +import p5 from '../../../src/app.js';
         import { HALF_PI } from '../../../src/core/constants';
        +import '../../js/chai_helpers';
         
         suite('p5.Camera', function() {
           var myp5;
        @@ -20,51 +22,59 @@ suite('p5.Camera', function() {
             };
           };
         
        -  if (!window.Modernizr.webgl) {
        -    return;
        -  }
        -
        -  setup(function() {
        +  beforeAll(function() {
             myp5 = new p5(function(p) {
               p.setup = function() {
                 p.createCanvas(100, 100, p.WEBGL);
                 myCam = p.createCamera();
        -        // set camera defaults below according to current default values
        -        // all the 'expectedMatrix' matrices below are based on these defaults...
        -        myCam.camera(
        -          0,
        -          0,
        -          100 / 2.0 / Math.tan(Math.PI * 30.0 / 180.0),
        -          0,
        -          0,
        -          0,
        -          0,
        -          1,
        -          0
        -        );
        -        myCam.perspective(
        -          Math.PI / 3.0,
        -          1,
        -          myCam.eyeZ / 10.0,
        -          myCam.eyeZ * 10.0
        -        );
               };
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        +  beforeEach(function() {
        +    myp5.angleMode(myp5.RADIANS);
        +
        +    // set camera defaults below according to current default values
        +    // all the 'expectedMatrix' matrices below are based on these defaults...
        +    myCam.camera(
        +      0,
        +      0,
        +      100 / 2.0 / Math.tan(Math.PI * 30.0 / 180.0),
        +      0,
        +      0,
        +      0,
        +      0,
        +      1,
        +      0
        +    );
        +    myCam.perspective(
        +      Math.PI / 3.0,
        +      1,
        +      myCam.eyeZ / 10.0,
        +      myCam.eyeZ * 10.0
        +    );
        +    myp5.setCamera(myCam);
        +  });
        +
           suite('createCamera()', function() {
             test('creates a p5.Camera object', function() {
               var myCam2 = myp5.createCamera();
               assert.instanceOf(myCam2, p5.Camera);
             });
         
        -    test('createCamera attaches p5.Camera to renderer', function() {
        +    test('createCamera does not immediately attach to renderer', function() {
               var myCam2 = myp5.createCamera();
        -      assert.deepEqual(myCam2, myp5._renderer._curCamera);
        +      assert.notEqual(myCam2, myp5._renderer.states.curCamera);
        +    });
        +
        +    test('setCamera() attaches a camera to renderer', function() {
        +      var myCam2 = myp5.createCamera();
        +      myp5.setCamera(myCam2);
        +      assert.equal(myCam2, myp5._renderer.states.curCamera);
             });
           });
         
        @@ -82,7 +92,7 @@ suite('p5.Camera', function() {
         
               myCam.pan(1);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
               assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
               assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        @@ -106,7 +116,7 @@ suite('p5.Camera', function() {
         
               myCam.pan(-1);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
               assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
               assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        @@ -127,7 +137,7 @@ suite('p5.Camera', function() {
         
               myCam.pan(0);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
               assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
               assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        @@ -151,7 +161,7 @@ suite('p5.Camera', function() {
         
               myCam.tilt(1.5);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
               assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
               assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        @@ -171,7 +181,7 @@ suite('p5.Camera', function() {
         
               myCam.tilt(-1.5);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
               assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
               assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        @@ -189,7 +199,7 @@ suite('p5.Camera', function() {
         
               myCam.tilt(0);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
               assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
               assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        @@ -209,7 +219,7 @@ suite('p5.Camera', function() {
         
               myCam.roll(HALF_PI);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
               assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
               assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        @@ -221,15 +231,15 @@ suite('p5.Camera', function() {
               var orig = getVals(myCam);
         
               var expectedMatrix = new Float32Array([
        -        0, 1, 0, 0,
        -        -1, 0, 0, 0,
        +        0, -1, 0, 0,
        +        1, 0, 0, 0,
                 0, 0, 1, 0,
                 0, 0, -86.6025390625, 1
               ]);
         
        -      myCam.tilt(HALF_PI);
        +      myCam.roll(HALF_PI);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
               assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
               assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        @@ -248,7 +258,7 @@ suite('p5.Camera', function() {
         
               myCam.roll(0);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
               assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
               assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        @@ -280,12 +290,12 @@ suite('p5.Camera', function() {
           });
         
           suite('Rotation with angleMode(DEGREES)', function() {
        -    setup(function() {
        +    beforeEach(function() {
               myp5.push();
               myp5.angleMode(myp5.DEGREES);
             });
         
        -    teardown(function() {
        +    afterEach(function() {
               myp5.pop();
             });
         
        @@ -302,7 +312,7 @@ suite('p5.Camera', function() {
         
               myCam.pan(1 * 180 / Math.PI);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
               assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
               assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        @@ -325,7 +335,7 @@ suite('p5.Camera', function() {
         
               myCam.tilt(1.5 * 180 / Math.PI);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
               assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
               assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        @@ -345,11 +355,11 @@ suite('p5.Camera', function() {
         
               myCam.roll(90);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
         
        -      assert.strictEqual(myCam.eyeX, orig.eyeX, 'eye X pos changed');
        -      assert.strictEqual(myCam.eyeY, orig.eyeY, 'eye Y pos changed');
        -      assert.strictEqual(myCam.eyeZ, orig.eyeZ, 'eye Z pos changed');
        +      assert.strictEqual(myCam.eyeX, orig.ex, 'eye X pos changed');
        +      assert.strictEqual(myCam.eyeY, orig.ey, 'eye Y pos changed');
        +      assert.strictEqual(myCam.eyeZ, orig.ez, 'eye Z pos changed');
             });
           });
         
        @@ -398,7 +408,7 @@ suite('p5.Camera', function() {
               ]);
         
               myCam.move(1, 2, 3);
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
         
             test('Move() with negative parameters sets correct matrix', function() {
        @@ -410,7 +420,7 @@ suite('p5.Camera', function() {
               ]);
         
               myCam.move(-1, -2, -3);
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
         
             test('Move(0,0,0) sets correct matrix', function() {
        @@ -422,7 +432,7 @@ suite('p5.Camera', function() {
               ]);
         
               myCam.move(0, 0, 0);
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
         
             test('SetPosition() with positive parameters sets correct matrix', function() {
        @@ -435,7 +445,7 @@ suite('p5.Camera', function() {
         
               myCam.setPosition(1, 2, 3);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('SetPosition() with negative parameters sets correct matrix', function() {
               var expectedMatrix = new Float32Array([
        @@ -447,7 +457,7 @@ suite('p5.Camera', function() {
         
               myCam.setPosition(-1, -2, -3);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('SetPosition(0,0,0) sets correct matrix', function() {
               var expectedMatrix = new Float32Array([
        @@ -459,7 +469,7 @@ suite('p5.Camera', function() {
         
               myCam.setPosition(0, 0, 0);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
         
             test('test for matrix manipulation with set()', function() {
        @@ -471,17 +481,17 @@ suite('p5.Camera', function() {
         
               // Confirmation that the argument camera and the matrix of the camera
               // that received set() match
        -      assert.deepEqual(copyCam.cameraMatrix.mat4, myCam.cameraMatrix.mat4);
        -      assert.deepEqual(copyCam.projMatrix.mat4, myCam.projMatrix.mat4);
        +      assert.deepCloseTo(copyCam.cameraMatrix.mat4, myCam.cameraMatrix.mat4);
        +      assert.deepCloseTo(copyCam.projMatrix.mat4, myCam.projMatrix.mat4);
               // If the set()ed camera is active,
               // the renderer's matrix will also change.
        -      assert.deepEqual(
        +      assert.deepCloseTo(
                 copyCam.cameraMatrix.mat4,
        -        myp5._renderer.uViewMatrix.mat4
        +        myp5._renderer.states.uViewMatrix.mat4
               );
        -      assert.deepEqual(
        +      assert.deepCloseTo(
                 copyCam.projMatrix.mat4,
        -        myp5._renderer.uPMatrix.mat4
        +        myp5._renderer.states.uPMatrix.mat4
               );
             });
         
        @@ -495,7 +505,7 @@ suite('p5.Camera', function() {
         
               myCam._orbit(1, 0, 0);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('_orbit(0,1,0) sets correct matrix', function() {
               var expectedMatrix = new Float32Array([
        @@ -507,7 +517,7 @@ suite('p5.Camera', function() {
         
               myCam._orbit(0, 1, 0);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('_orbit(0,0,1) sets correct matrix', function() {
               var expectedMatrix = new Float32Array([
        @@ -519,7 +529,7 @@ suite('p5.Camera', function() {
         
               myCam._orbit(0, 0, 1);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('_orbit(-1,0,0) sets correct matrix', function() {
               var expectedMatrix = new Float32Array([
        @@ -531,7 +541,7 @@ suite('p5.Camera', function() {
         
               myCam._orbit(-1, 0, 0);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('_orbit(0,-1,0) sets correct matrix', function() {
               var expectedMatrix = new Float32Array([
        @@ -543,7 +553,7 @@ suite('p5.Camera', function() {
         
               myCam._orbit(0, -1, 0);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('_orbit(0,0,-1) sets correct matrix', function() {
               var expectedMatrix = new Float32Array([
        @@ -555,7 +565,7 @@ suite('p5.Camera', function() {
         
               myCam._orbit(0, 0, -1);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('_orbit() does not force up vector to be parallel to y-axis', function() {
               // Check the shape of the camera matrix to make sure that the up vector
        @@ -570,7 +580,7 @@ suite('p5.Camera', function() {
               myCam.camera(100, 100, 100, 0, 0, 0, 0, 0, -1);
               myCam._orbit(1, 1, 1);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('up vector of an arbitrary direction reverses by _orbit(0,PI,0)', function() {
               // Make sure the up vector is reversed by doing _orbit(0, Math.PI, 0)
        @@ -593,7 +603,7 @@ suite('p5.Camera', function() {
               myCam._orbit(0, -Math.PI, 0);
               // upY should switch from 1(dPhi=0) to -1 (dPhi=-PI)
               // myCam.upY should be -1
        -      assert(myCam.upY === -1);
        +      assert.equal(myCam.upY, -1);
             });
             test('_orbit() ensures myCam.upY switches direction (from -1 to 1) at camPhi <= 0', function() {
               // the following should produce the upY with inverted direction(from -1 to 1)
        @@ -602,7 +612,7 @@ suite('p5.Camera', function() {
               myCam._orbit(0, Math.PI, 0);
               // upY should switch from -1(dPhi=-PI) to 1 (dPhi=PI)
               // myCam.upY should be 1
        -      assert(myCam.upY === 1);
        +      assert.equal(myCam.upY, 1);
             });
             test('_orbit() ensures myCam.upY switches direction (from 1 to -1) at camPhi >= PI', function() {
               // the following should produce the upY with inverted direction(from 1 to -1)
        @@ -610,7 +620,7 @@ suite('p5.Camera', function() {
               myCam._orbit(0, Math.PI, 0);
               // upY should switch from 1(dPhi=0) to -1 (dPhi=PI)
               // myCam.upY should be -1
        -      assert(myCam.upY === -1);
        +      assert.equal(myCam.upY, -1);
             });
             test('_orbit() ensures myCam.upY switches direction (from -1 to 1) at camPhi >= PI', function() {
               // the following should produce the upY with inverted direction(from -1 to 1)
        @@ -619,7 +629,7 @@ suite('p5.Camera', function() {
               myCam._orbit(0, -Math.PI, 0);
               // upY should switch from -1(dPhi=PI) to 1 (dPhi=-PI)
               // myCam.upY should be 1
        -      assert(myCam.upY === 1);
        +      assert.equal(myCam.upY, 1);
             });
             test('_orbit() ensures camera can do multiple continuous 360deg rotations', function() {
               // the following should produce two camera objects having same properties.
        @@ -654,7 +664,7 @@ suite('p5.Camera', function() {
               var myCamCopy = myCam.copy();
               myCamCopy._orbit(0, 0, -100);
               myCam._orbit(0, 0, -250);
        -      assert.deepEqual(myCam.cameraMatrix.mat4, myCamCopy.cameraMatrix.mat4, 'deep equal is failing');
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, myCamCopy.cameraMatrix.mat4, 4, 'deep equal is failing');
             });
             test('_orbitFree(1,0,0) sets correct matrix', function() {
               var expectedMatrix = new Float32Array([
        @@ -666,7 +676,7 @@ suite('p5.Camera', function() {
         
               myCam._orbitFree(1, 0, 0);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('_orbitFree(0,1,0) sets correct matrix', function() {
               var expectedMatrix = new Float32Array([
        @@ -678,7 +688,7 @@ suite('p5.Camera', function() {
         
               myCam._orbitFree(0, 1, 0);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('_orbitFree(0,0,1) sets correct matrix', function() {
               var expectedMatrix = new Float32Array([
        @@ -690,7 +700,7 @@ suite('p5.Camera', function() {
         
               myCam._orbitFree(0, 0, 1);
         
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedMatrix);
             });
             test('Rotate camera 360° with _orbitFree() returns it to its original position', function() {
               // Rotate the camera 360 degrees in any direction using _orbitFree()
        @@ -715,12 +725,12 @@ suite('p5.Camera', function() {
               test('ortho() sets renderer uPMatrix', function() {
                 myCam.ortho(-10, 10, -10, 10, 0, 100);
         
        -        assert.deepEqual(myCam.projMatrix.mat4, myp5._renderer.uPMatrix.mat4);
        +        assert.deepCloseTo(myCam.projMatrix.mat4, myp5._renderer.states.uPMatrix.mat4);
               });
         
               test('ortho() sets projection matrix correctly', function() {
                 // expectedMatrix array needs to match Float32Array type of
        -        // p5.Camera projMatrix's mat4 array for deepEqual to work
        +        // p5.Camera projMatrix's mat4 array for deepCloseTo to work
                 /* eslint-disable indent */
                 var expectedMatrix = new Float32Array([
                    1,  0,  0,  0,
        @@ -732,7 +742,7 @@ suite('p5.Camera', function() {
         
                 myCam.ortho(-1, 1, -1, 1, 0, 2);
         
        -        assert.deepEqual(myCam.projMatrix.mat4, expectedMatrix);
        +        assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix);
               });
         
               test('ortho() with no parameters specified (sets default with added far)', function() {
        @@ -743,19 +753,19 @@ suite('p5.Camera', function() {
                   -0, -0, -1, 1
                 ]);
                 myCam.ortho();
        -        assert.deepEqual(myCam.projMatrix.mat4, expectedMatrix);
        +        assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix);
               });
         
        -      test('ortho() with sets cameraType to custom', function() {
        +      test('ortho() sets cameraType to custom', function() {
                 myCam.ortho();
        -        assert.deepEqual(myCam.cameraType, 'custom');
        +        assert.equal(myCam.cameraType, 'custom');
               });
             });
             suite('perspective()', function() {
               test('perspective() sets renderer uPMatrix', function() {
                 myCam.perspective(Math.PI / 3.0, 1, 1, 100);
         
        -        assert.deepEqual(myCam.projMatrix.mat4, myp5._renderer.uPMatrix.mat4);
        +        assert.deepCloseTo(myCam.projMatrix.mat4, myp5._renderer.states.uPMatrix.mat4);
               });
               test('perspective() sets projection matrix correctly', function() {
                 var expectedMatrix = new Float32Array([
        @@ -767,7 +777,7 @@ suite('p5.Camera', function() {
         
                 myCam.perspective(Math.PI / 2, 1, 10, 20);
         
        -        assert.deepEqual(myCam.projMatrix.mat4, expectedMatrix);
        +        assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix);
               });
         
               test('perspective() with no parameters specified (sets default)', function() {
        @@ -780,19 +790,19 @@ suite('p5.Camera', function() {
         
                 myCam.perspective();
         
        -        assert.deepEqual(myCam.projMatrix.mat4, expectedMatrix);
        +        assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix);
               });
         
               test('perspective() with no parameters sets cameraType to default', function() {
                 myCam.perspective();
        -        assert.deepEqual(myCam.cameraType, 'default');
        +        assert.equal(myCam.cameraType, 'default');
               });
             });
             suite('frustum()', function() {
               test('frustum() sets renderer uPMatrix', function() {
                 myCam.frustum(-10, 10, -20, 20, -100, 100);
         
        -        assert.deepEqual(myCam.projMatrix.mat4, myp5._renderer.uPMatrix.mat4);
        +        assert.deepCloseTo(myCam.projMatrix.mat4, myp5._renderer.states.uPMatrix.mat4);
               });
               test('frustum() sets projection matrix correctly', function() {
                 /* eslint-disable indent */
        @@ -806,7 +816,7 @@ suite('p5.Camera', function() {
         
                 myCam.frustum(-1, 1, -1, 1, -2, 2);
         
        -        assert.deepEqual(myCam.projMatrix.mat4, expectedMatrix);
        +        assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix);
               });
         
               test('frustum() with no parameters specified (sets default)', function() {
        @@ -819,12 +829,12 @@ suite('p5.Camera', function() {
         
                 myCam.frustum();
         
        -        assert.deepEqual(myCam.projMatrix.mat4, expectedMatrix);
        +        assert.deepCloseTo(myCam.projMatrix.mat4, expectedMatrix);
               });
         
               test('frustum() sets cameraType to custom', function() {
                 myCam.frustum(-1, 1, -1, 1, -2, 2);
        -        assert.deepEqual(myCam.cameraType, 'custom');
        +        assert.equal(myCam.cameraType, 'custom');
               });
             });
           });
        @@ -845,11 +855,11 @@ suite('p5.Camera', function() {
         
               // if amt is 0, cam is set to cam0.
               myCam.slerp(cam0, cam1, 0);
        -      assert.deepEqual(myCam.cameraMatrix.mat4, cam0.cameraMatrix.mat4);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, cam0.cameraMatrix.mat4);
         
               // if amt is 1, cam is set to cam1.
               myCam.slerp(cam0, cam1, 1);
        -      assert.deepEqual(myCam.cameraMatrix.mat4, cam1.cameraMatrix.mat4);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, cam1.cameraMatrix.mat4);
             });
             test('Behavior of slerp() for camera moved by pan()', function() {
               myCam = myp5.createCamera();
        @@ -1021,7 +1031,7 @@ suite('p5.Camera', function() {
                 -0.010798640549182892, -0.3977023959159851, 0.9174509048461914, 0,
                 -66.60199737548828, -260.3179016113281, -1242.9371337890625, 1
               ]);
        -      assert.deepEqual(myCam.cameraMatrix.mat4, expectedSlerpedMatrix);
        +      assert.deepCloseTo(myCam.cameraMatrix.mat4, expectedSlerpedMatrix);
             });
           });
         
        @@ -1043,8 +1053,8 @@ suite('p5.Camera', function() {
         
               assert.equal(newCam.cameraType, myCam.cameraType);
         
        -      assert.deepEqual(newCam.cameraMatrix.mat4, myCam.cameraMatrix.mat4);
        -      assert.deepEqual(newCam.projMatrix.mat4, myCam.projMatrix.mat4);
        +      assert.deepCloseTo(newCam.cameraMatrix.mat4, myCam.cameraMatrix.mat4);
        +      assert.deepCloseTo(newCam.projMatrix.mat4, myCam.projMatrix.mat4);
             });
         
             test('_getLocalAxes() returns three normalized, orthogonal vectors', function() {
        @@ -1063,9 +1073,9 @@ suite('p5.Camera', function() {
               var vecZ = myp5.createVector(local.z[0], local.z[1], local.z[2]);
         
               // assert vectors are normalized
        -      assert.equal(vecX.mag(), 1, 'local X vector is not unit vector');
        -      assert.equal(vecY.mag(), 1, 'local Y vector is not unit vector');
        -      assert.equal(vecZ.mag(), 1, 'local Z vector is not unit vector');
        +      assert.closeTo(vecX.mag(), 1, delta, 'local X vector is not unit vector');
        +      assert.closeTo(vecY.mag(), 1, delta, 'local Y vector is not unit vector');
        +      assert.closeTo(vecZ.mag(), 1, delta, 'local Z vector is not unit vector');
         
               // Assert vectors are orthogonal
               assert.closeTo(
        @@ -1093,6 +1103,7 @@ suite('p5.Camera', function() {
             test('_isActive() returns true for a camera created with createCamera(),\
              and false for another p5.Camera', function() {
               var myCam2 = myp5.createCamera();
        +      myp5.setCamera(myCam2);
               assert.isTrue(myCam2._isActive());
               assert.isFalse(myCam._isActive());
             });
        @@ -1100,17 +1111,17 @@ suite('p5.Camera', function() {
               var myCam2 = myp5.createCamera();
               var myCam3 = myp5.createCamera();
               myp5.setCamera(myCam2);
        -      assert.deepEqual(myCam2, myp5._renderer._curCamera);
        +      assert.deepCloseTo(myCam2, myp5._renderer.states.curCamera);
               myp5.setCamera(myCam3);
        -      assert.deepEqual(myCam3, myp5._renderer._curCamera);
        +      assert.deepCloseTo(myCam3, myp5._renderer.states.curCamera);
               myp5.setCamera(myCam);
        -      assert.deepEqual(myCam, myp5._renderer._curCamera);
        +      assert.deepCloseTo(myCam, myp5._renderer.states.curCamera);
             });
             test("Camera's Renderer is correctly set after setAttributes", function() {
               var myCam2 = myp5.createCamera();
        -      assert.deepEqual(myCam2, myp5._renderer._curCamera);
        +      assert.deepCloseTo(myCam2, myp5._renderer.states.curCamera);
               myp5.setAttributes('antialias', true);
        -      assert.deepEqual(myCam2._renderer, myp5._renderer);
        +      assert.deepCloseTo(myCam2._renderer, myp5._renderer);
             });
           });
         
        @@ -1121,6 +1132,7 @@ suite('p5.Camera', function() {
               myp5.pixelDensity(1);
         
               let cam = myp5.createCamera();
        +      myp5.setCamera(cam);
         
               myp5.fill(255, 0, 0);
         
        @@ -1151,6 +1163,7 @@ suite('p5.Camera', function() {
               myp5.pixelDensity(1);
         
               let cam = myp5.createCamera();
        +      myp5.setCamera(cam);
         
               myp5.fill(255, 0, 0);
         
        @@ -1160,16 +1173,16 @@ suite('p5.Camera', function() {
               };
         
               testShape();
        -      assert.deepEqual(myp5.get(0, 0), [255, 0, 0, 255]);
        +      assert.deepCloseTo(myp5.get(0, 0), [255, 0, 0, 255]);
         
               cam.pan(10);
               testShape();
        -      assert.deepEqual(myp5.get(0, 0), [0, 0, 0, 0]);
        +      assert.deepCloseTo(myp5.get(0, 0), [0, 0, 0, 0]);
         
               myp5.resizeCanvas(2, 1);
               myp5.resizeCanvas(1, 1);
               testShape();
        -      assert.deepEqual(myp5.get(0, 0), [0, 0, 0, 0]);
        +      assert.deepCloseTo(myp5.get(0, 0), [0, 0, 0, 0]);
             });
           });
         });
        diff --git a/test/unit/webgl/p5.Framebuffer.js b/test/unit/webgl/p5.Framebuffer.js
        index 950cc79f26..9dfe186ff1 100644
        --- a/test/unit/webgl/p5.Framebuffer.js
        +++ b/test/unit/webgl/p5.Framebuffer.js
        @@ -1,19 +1,22 @@
        +import p5 from '../../../src/app.js';
        +import { vi } from 'vitest';
        +
         suite('p5.Framebuffer', function() {
           let myp5;
        +  let prevPixelRatio;
         
        -  if (!window.Modernizr.webgl) {
        -    return;
        -  }
        -
        -  setup(function() {
        +  beforeAll(function() {
        +    prevPixelRatio = window.devicePixelRatio;
        +    window.devicePixelRatio = 1;
             myp5 = new p5(function(p) {
               p.setup = function() {};
               p.draw = function() {};
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
        +    window.devicePixelRatio = prevPixelRatio;
           });
         
           suite('formats and channels', function() {
        @@ -93,7 +96,7 @@ suite('p5.Framebuffer', function() {
         
             afterEach(() => {
               if (glStub) {
        -        glStub.restore();
        +        vi.restoreAllMocks();
                 glStub = null;
               }
             });
        @@ -165,7 +168,7 @@ suite('p5.Framebuffer', function() {
             suite('resizing', function() {
               let fbo;
               let oldTexture;
        -      setup(function() {
        +      beforeEach(function() {
                 myp5.createCanvas(10, 10, myp5.WEBGL);
                 myp5.pixelDensity(1);
                 fbo = myp5.createFramebuffer();
        @@ -199,22 +202,26 @@ suite('p5.Framebuffer', function() {
               });
         
               test('resizes the framebuffer by createFramebuffer based on max texture size', function() {
        -        glStub = sinon.stub(p5.RendererGL.prototype, '_getParam');
        -        const fakeMaxTextureSize = 100;
        -        glStub.returns(fakeMaxTextureSize);
                 myp5.createCanvas(10, 10, myp5.WEBGL);
        +        delete myp5._renderer._maxTextureSize;
        +        glStub = vi.spyOn(myp5._renderer, '_getMaxTextureSize');
        +        const fakeMaxTextureSize = 100;
        +        glStub.mockReturnValue(fakeMaxTextureSize);
                 const fbo = myp5.createFramebuffer({ width: 200, height: 200 });
        +        delete myp5._renderer._maxTextureSize;
                 expect(fbo.width).to.equal(100);
                 expect(fbo.height).to.equal(100);
               });
         
               test('resizes the framebuffer by resize method based on max texture size', function() {
        -        glStub = sinon.stub(p5.RendererGL.prototype, '_getParam');
        -        const fakeMaxTextureSize = 100;
        -        glStub.returns(fakeMaxTextureSize);
                 myp5.createCanvas(10, 10, myp5.WEBGL);
        +        delete myp5._renderer._maxTextureSize;
        +        glStub = vi.spyOn(myp5._renderer, '_getMaxTextureSize');
        +        const fakeMaxTextureSize = 100;
        +        glStub.mockReturnValue(fakeMaxTextureSize);
                 const fbo = myp5.createFramebuffer({ width: 10, height: 10 });
        -        myp5.resize(200, 200);
        +        fbo.resize(200, 200);
        +        delete myp5._renderer._maxTextureSize;
                 expect(fbo.width).to.equal(100);
                 expect(fbo.height).to.equal(100);
               });
        @@ -282,7 +289,6 @@ suite('p5.Framebuffer', function() {
           });
         
           test('Framebuffers work on p5.Graphics', function() {
        -    myp5.createCanvas(10, 10);
             const graphic = myp5.createGraphics(10, 10, myp5.WEBGL);
         
             // Draw a box to the framebuffer
        @@ -334,7 +340,7 @@ suite('p5.Framebuffer', function() {
         
           suite('defaultCamera', function() {
             let fbo;
        -    setup(function() {
        +    beforeEach(function() {
               myp5.createCanvas(10, 10, myp5.WEBGL);
               myp5.pixelDensity(1);
               fbo = myp5.createFramebuffer({ width: 5, height: 15 });
        @@ -553,7 +559,7 @@ suite('p5.Framebuffer', function() {
               suite(`with antialiasing ${antialias ? 'on' : 'off'}`, function() {
                 let fbo1;
                 let fbo2;
        -        setup(function() {
        +        beforeEach(function() {
                   myp5.createCanvas(10, 10, myp5.WEBGL);
                   myp5.pixelDensity(1);
                   fbo1 = myp5.createFramebuffer({ antialias });
        diff --git a/test/unit/webgl/p5.Geometry.js b/test/unit/webgl/p5.Geometry.js
        index a7dd900f80..cb42cb32a2 100644
        --- a/test/unit/webgl/p5.Geometry.js
        +++ b/test/unit/webgl/p5.Geometry.js
        @@ -1,35 +1,32 @@
        +import p5 from '../../../src/app.js';
        +import { vi } from 'vitest';
        +
         suite('p5.Geometry', function() {
           let myp5;
         
        -  if (!window.Modernizr.webgl) {
        -    return;
        -  }
        -
        -  setup(function() {
        +  beforeAll(function() {
             myp5 = new p5(function(p) {
               p.setup = function() {};
               p.draw = function() {};
             });
           });
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
           suite('generating edge geometry', function() {
             let geom;
         
        -    setup(function() {
        -      geom = new p5.Geometry();
        -      sinon.spy(geom, '_addCap');
        -      sinon.spy(geom, '_addJoin');
        -      sinon.spy(geom, '_addSegment');
        +    beforeEach(function() {
        +      geom = new p5.Geometry(undefined, undefined, undefined, myp5._renderer);
        +      vi.spyOn(geom, '_addCap');
        +      vi.spyOn(geom, '_addJoin');
        +      vi.spyOn(geom, '_addSegment');
             });
         
        -    teardown(function() {
        -      geom._addCap.restore();
        -      geom._addJoin.restore();
        -      geom._addSegment.restore();
        +    afterEach(function() {
        +      vi.restoreAllMocks();
             });
         
             test('single polyline', function() {
        @@ -42,9 +39,9 @@ suite('p5.Geometry', function() {
               geom.edges.push([0, 1], [1, 2], [2, 3]);
               geom._edgesToVertices();
         
        -      assert.equal(geom._addSegment.callCount, 3);
        -      assert.equal(geom._addCap.callCount, 2);
        -      assert.equal(geom._addJoin.callCount, 2);
        +      expect(geom._addSegment).toHaveBeenCalledTimes(3);
        +      expect(geom._addCap).toHaveBeenCalledTimes(2);
        +      expect(geom._addJoin).toHaveBeenCalledTimes(2);
             });
         
             test('straight line', function() {
        @@ -56,10 +53,10 @@ suite('p5.Geometry', function() {
               geom.edges.push([0, 1], [1, 2]);
               geom._edgesToVertices();
         
        -      assert.equal(geom._addSegment.callCount, 2);
        -      assert.equal(geom._addCap.callCount, 2);
        +      expect(geom._addSegment).toHaveBeenCalledTimes(2);
        +      expect(geom._addCap).toHaveBeenCalledTimes(2);
               // No joins since the directions of each segment are the same
        -      assert.equal(geom._addJoin.callCount, 0);
        +      expect(geom._addJoin).toHaveBeenCalledTimes(0);
             });
         
             test('two disconnected polylines', function() {
        @@ -72,9 +69,9 @@ suite('p5.Geometry', function() {
               geom.edges.push([0, 1], [2, 3]);
               geom._edgesToVertices();
         
        -      assert.equal(geom._addSegment.callCount, 2);
        -      assert.equal(geom._addCap.callCount, 4);
        -      assert.equal(geom._addJoin.callCount, 0);
        +      expect(geom._addSegment).toHaveBeenCalledTimes(2);
        +      expect(geom._addCap).toHaveBeenCalledTimes(4);
        +      expect(geom._addJoin).toHaveBeenCalledTimes(0);
             });
         
             test('polyline that loops back', function() {
        @@ -87,9 +84,9 @@ suite('p5.Geometry', function() {
               geom.edges.push([0, 1], [1, 2], [2, 3], [3, 0]);
               geom._edgesToVertices();
         
        -      assert.equal(geom._addSegment.callCount, 4);
        -      assert.equal(geom._addCap.callCount, 0);
        -      assert.equal(geom._addJoin.callCount, 4);
        +      expect(geom._addSegment).toHaveBeenCalledTimes(4);
        +      expect(geom._addCap).toHaveBeenCalledTimes(0);
        +      expect(geom._addJoin).toHaveBeenCalledTimes(4);
             });
         
             test('calculateBoundingBox()', function() {
        @@ -119,9 +116,9 @@ suite('p5.Geometry', function() {
         
               // The degenerate edge should be skipped without breaking the
               // polyline into multiple pieces
        -      assert.equal(geom._addSegment.callCount, 3);
        -      assert.equal(geom._addCap.callCount, 2);
        -      assert.equal(geom._addJoin.callCount, 2);
        +      expect(geom._addSegment).toHaveBeenCalledTimes(3);
        +      expect(geom._addCap).toHaveBeenCalledTimes(2);
        +      expect(geom._addJoin).toHaveBeenCalledTimes(2);
             });
         
             test('degenerate edge at the end', function() {
        @@ -137,9 +134,9 @@ suite('p5.Geometry', function() {
         
               // The degenerate edge should be skipped and caps should still be added
               // from the previous non degenerate edge
        -      assert.equal(geom._addSegment.callCount, 3);
        -      assert.equal(geom._addCap.callCount, 2);
        -      assert.equal(geom._addJoin.callCount, 2);
        +      expect(geom._addSegment).toHaveBeenCalledTimes(3);
        +      expect(geom._addCap).toHaveBeenCalledTimes(2);
        +      expect(geom._addJoin).toHaveBeenCalledTimes(2);
             });
         
             test('degenerate edge between two disconnected polylines', function() {
        @@ -153,9 +150,9 @@ suite('p5.Geometry', function() {
               geom.edges.push([0, 1], [1, 2], [3, 4]);
               geom._edgesToVertices();
         
        -      assert.equal(geom._addSegment.callCount, 2);
        -      assert.equal(geom._addCap.callCount, 4);
        -      assert.equal(geom._addJoin.callCount, 0);
        +      expect(geom._addSegment).toHaveBeenCalledTimes(2);
        +      expect(geom._addCap).toHaveBeenCalledTimes(4);
        +      expect(geom._addJoin).toHaveBeenCalledTimes(0);
             });
           });
         
        @@ -184,7 +181,7 @@ suite('p5.Geometry', function() {
                 drawGeometry();
                 myp5.pop();
                 myp5.resetShader();
        -        const regularImage = myp5._renderer.elt.toDataURL();
        +        const regularImage = myp5._renderer.canvas.toDataURL();
         
                 // Geometry mode
                 myp5.fill(255);
        @@ -195,7 +192,7 @@ suite('p5.Geometry', function() {
                 myp5.model(geom);
                 myp5.pop();
                 myp5.resetShader();
        -        const geometryImage = myp5._renderer.elt.toDataURL();
        +        const geometryImage = myp5._renderer.canvas.toDataURL();
         
                 assert.equal(regularImage, geometryImage);
               }
        @@ -211,7 +208,7 @@ suite('p5.Geometry', function() {
                   myp5.rotateY(myp5.PI * 0.2);
                 }
                 myp5.pop();
        -      }, [checkLights, checkMaterials, checkNormals]);
        +      }, [checkMaterials]);
             });
         
             test('Immediate mode constructs are translated correctly', function() {
        diff --git a/test/unit/webgl/p5.Matrix.js b/test/unit/webgl/p5.Matrix.js
        deleted file mode 100644
        index 88550e35a4..0000000000
        --- a/test/unit/webgl/p5.Matrix.js
        +++ /dev/null
        @@ -1,412 +0,0 @@
        -/* eslint-disable indent */
        -var mat4 = [
        -   1,  2,  3,  4,
        -   5,  6,  7,  8,
        -   9, 10, 11, 12,
        -  13, 14, 15, 16
        -];
        -
        -var other = [
        -  1,  5,  9, 13,
        -  2,  6, 10, 14,
        -  3,  7, 11, 15,
        -  4,  8, 12, 16
        -];
        -
        -var mat3 = [
        -  1, 2, 3,
        -  4, 5, 6,
        -  7, 8, 9
        -];
        -/* eslint-enable indent */
        -
        -suite('p5.Matrix', function() {
        -  var myp5;
        -
        -  setup(function(done) {
        -    new p5(function(p) {
        -      p.setup = function() {
        -        myp5 = p;
        -        done();
        -      };
        -    });
        -  });
        -
        -  teardown(function() {
        -    myp5.remove();
        -  });
        -
        -  suite('construction', function() {
        -    test('new p5.Matrix()', function() {
        -      var m = new p5.Matrix();
        -      assert.instanceOf(m, p5.Matrix);
        -      assert.isUndefined(m.mat3);
        -      /* eslint-disable indent */
        -      assert.deepEqual([].slice.call(m.mat4), [
        -				1, 0, 0, 0,
        -				0, 1, 0, 0,
        -				0, 0, 1, 0,
        -				0, 0, 0, 1
        -			]);
        -      /* eslint-enable indent */
        -    });
        -
        -    test('new p5.Matrix(array)', function() {
        -      var m = new p5.Matrix(mat4);
        -      assert.instanceOf(m, p5.Matrix);
        -      assert.isUndefined(m.mat3);
        -      assert.deepEqual([].slice.call(m.mat4), mat4);
        -    });
        -
        -    test('new p5.Matrix(mat3)', function() {
        -      var m = new p5.Matrix('mat3', mat3);
        -      assert.instanceOf(m, p5.Matrix);
        -      assert.isUndefined(m.mat4);
        -      assert.deepEqual([].slice.call(m.mat3), mat3);
        -    });
        -
        -    test('identity()', function() {
        -      var m = p5.Matrix.identity();
        -      assert.instanceOf(m, p5.Matrix);
        -      assert.isUndefined(m.mat3);
        -      /* eslint-disable indent */
        -      assert.deepEqual([].slice.call(m.mat4), [
        -				1, 0, 0, 0,
        -				0, 1, 0, 0,
        -				0, 0, 1, 0,
        -				0, 0, 0, 1
        -			]);
        -      /* eslint-enable indent */
        -    });
        -  });
        -
        -  suite('set', function() {
        -    test('p5.Matrix', function() {
        -      var m = new p5.Matrix();
        -      m.set(new p5.Matrix(mat4));
        -      assert.deepEqual([].slice.call(m.mat4), mat4);
        -    });
        -
        -    test('array', function() {
        -      var m = new p5.Matrix();
        -      m.set(mat4);
        -      assert.deepEqual([].slice.call(m.mat4), mat4);
        -    });
        -
        -    test('arguments', function() {
        -      var m = new p5.Matrix();
        -      m.set.apply(m, mat4);
        -      assert.notEqual(m.mat4, mat4);
        -      assert.deepEqual([].slice.call(m.mat4), mat4);
        -    });
        -  });
        -
        -  suite('get / copy', function() {
        -    test('get', function() {
        -      var m = new p5.Matrix(mat4);
        -      var m2 = m.get();
        -      assert.notEqual(m, m2);
        -      assert.equal(m.mat4, m2.mat4);
        -    });
        -    test('copy', function() {
        -      var m = new p5.Matrix(mat4);
        -      var m2 = m.copy();
        -      assert.notEqual(m, m2);
        -      assert.notEqual(m.mat4, m2.mat4);
        -      assert.deepEqual([].slice.call(m.mat4), [].slice.call(m2.mat4));
        -    });
        -  });
        -
        -  suite('mult', function() {
        -    /* eslint-disable indent */
        -    var mm = [
        -       30,  70, 110, 150,
        -       70, 174, 278, 382,
        -      110, 278, 446, 614,
        -      150, 382, 614, 846
        -    ];
        -    /* eslint-enable indent */
        -
        -    test('self', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      m.mult(m);
        -      /* eslint-disable indent */
        -      assert.deepEqual([].slice.call(m.mat4), [
        -         90, 100, 110, 120,
        -        202, 228, 254, 280,
        -        314, 356, 398, 440,
        -        426, 484, 542, 600
        -      ]);
        -      /* eslint-enable indent */
        -    });
        -
        -    test('p5.Matrix', function() {
        -      var m1 = new p5.Matrix(mat4.slice());
        -      var m2 = new p5.Matrix(other);
        -      m1.mult(m2);
        -      assert.deepEqual([].slice.call(m1.mat4), mm);
        -    });
        -
        -    test('array', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      m.mult(other);
        -      assert.deepEqual([].slice.call(m.mat4), mm);
        -    });
        -
        -    test('arguments', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      m.mult.apply(m, other);
        -      assert.deepEqual([].slice.call(m.mat4), mm);
        -    });
        -  });
        -
        -  suite('apply', function() {
        -    /* eslint-disable indent */
        -    var am = [
        -      276, 304, 332, 360,
        -      304, 336, 368, 400,
        -      332, 368, 404, 440,
        -      360, 400, 440, 480
        -    ];
        -    /* eslint-enable indent */
        -
        -    test('self', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      m.apply(m);
        -      /* eslint-disable indent */
        -      assert.deepEqual([].slice.call(m.mat4), [
        -         90, 100, 110, 120,
        -        202, 228, 254, 280,
        -        314, 356, 398, 440,
        -        426, 484, 542, 600
        -      ]);
        -      /* eslint-enable indent */
        -    });
        -
        -    test('p5.Matrix', function() {
        -      var m1 = new p5.Matrix(mat4.slice());
        -      var m2 = new p5.Matrix(other);
        -      m1.apply(m2);
        -      assert.deepEqual([].slice.call(m1.mat4), am);
        -    });
        -
        -    test('array', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      m.apply(other);
        -      assert.deepEqual([].slice.call(m.mat4), am);
        -    });
        -
        -    test('arguments', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      m.apply.apply(m, other);
        -      assert.deepEqual([].slice.call(m.mat4), am);
        -    });
        -  });
        -
        -  suite('scale', function() {
        -    /* eslint-disable indent */
        -    var sm = [
        -       2,  4,  6,  8,
        -      15, 18, 21, 24,
        -      45, 50, 55, 60,
        -      13, 14, 15, 16
        -    ];
        -    /* eslint-enable indent */
        -
        -    test('p5.Vector', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      var v = myp5.createVector(2, 3, 5);
        -      m.scale(v);
        -      assert.notEqual(m.mat4, mat4);
        -      assert.deepEqual([].slice.call(m.mat4), sm);
        -    });
        -
        -    test('array', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      m.scale([2, 3, 5]);
        -      assert.notEqual(m.mat4, mat4);
        -      assert.deepEqual([].slice.call(m.mat4), sm);
        -    });
        -
        -    test('arguments', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      m.scale(2, 3, 5);
        -      assert.notEqual(m.mat4, mat4);
        -      assert.deepEqual([].slice.call(m.mat4), sm);
        -    });
        -  });
        -
        -  suite('rotate', function() {
        -    /* eslint-disable max-len */
        -    var rm = [
        -      1.433447866601989, 2.5241247073503885, 3.6148015480987885, 4.7054783888471885,
        -      6.460371405020393, 7.054586073938033,  7.648800742855675,  8.243015411773316,
        -      7.950398010346969, 9.157598472697025, 10.36479893504708,  11.571999397397136,
        -      13, 14, 15, 16
        -    ];
        -    /* eslint-enable max-len */
        -
        -    test('p5.Vector', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      var v = myp5.createVector(2, 3, 5);
        -      m.rotate(45 * myp5.DEG_TO_RAD, v);
        -      assert.deepEqual([].slice.call(m.mat4), rm);
        -    });
        -
        -    test('array', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      m.rotate(45 * myp5.DEG_TO_RAD, [2, 3, 5]);
        -      assert.deepEqual([].slice.call(m.mat4), rm);
        -    });
        -
        -    test('arguments', function() {
        -      var m = new p5.Matrix(mat4.slice());
        -      m.rotate(45 * myp5.DEG_TO_RAD, 2, 3, 5);
        -      assert.deepEqual([].slice.call(m.mat4), rm);
        -    });
        -  });
        -
        -
        -  suite('p5.Matrix3x3', function() {
        -    test('apply copy() to 3x3Matrix', function() {
        -      const m = new p5.Matrix('mat3', [
        -        1, 2, 3,
        -        4, 5, 6,
        -        7, 8, 9
        -      ]);
        -      const mCopy = m.copy();
        -
        -      // The matrix created by copying is different from the original matrix
        -      assert.notEqual(m, mCopy);
        -      assert.notEqual(m.mat3, mCopy.mat3);
        -
        -      // The matrix created by copying has the same elements as the original matrix
        -      assert.deepEqual([].slice.call(m.mat3), [].slice.call(mCopy.mat3));
        -    });
        -    test('transpose3x3()', function() {
        -      const m = new p5.Matrix('mat3', [
        -        1, 2, 3,
        -        4, 5, 6,
        -        7, 8, 9
        -      ]);
        -      const mTp = new p5.Matrix('mat3', [
        -        1, 4, 7,
        -        2, 5, 8,
        -        3, 6, 9
        -      ]);
        -
        -      // If no arguments, transpose itself
        -      m.transpose3x3();
        -      assert.deepEqual([].slice.call(m.mat3), [].slice.call(mTp.mat3));
        -
        -      // If there is an array of arguments, set it by transposing it
        -      m.transpose3x3([
        -        1, 2, 3,
        -        10, 20, 30,
        -        100, 200, 300
        -      ]);
        -      assert.deepEqual([].slice.call(m.mat3), [
        -        1, 10, 100,
        -        2, 20, 200,
        -        3, 30, 300
        -      ]);
        -    });
        -    test('mult3x3()', function() {
        -      const m = new p5.Matrix('mat3', [
        -        1, 2, 3,
        -        4, 5, 6,
        -        7, 8, 9
        -      ]);
        -      const m1 = m.copy();
        -      const m2 = m.copy();
        -      const multMatrix = new p5.Matrix('mat3', [
        -        1, 1, 1,
        -        0, 1, 1,
        -        1, 0, 1
        -      ]);
        -
        -      // When taking a matrix as an argument
        -      m.mult3x3(multMatrix);
        -      assert.deepEqual([].slice.call(m.mat3), [
        -        4, 3, 6,
        -        10, 9, 15,
        -        16, 15, 24
        -      ]);
        -
        -      // if the argument is an array or an enumerated number
        -      m1.mult3x3(1, 1, 1, 0, 1, 1, 1, 0, 1);
        -      m2.mult3x3([1, 1, 1, 0, 1, 1, 1, 0, 1]);
        -      assert.deepEqual([].slice.call(m.mat3), [].slice.call(m1.mat3));
        -      assert.deepEqual([].slice.call(m.mat3), [].slice.call(m2.mat3));
        -    });
        -    test('column() and row()', function(){
        -      const m = new p5.Matrix('mat3', [
        -        // The matrix data is stored column-major, so each line below is
        -        // a column rather than a row. Imagine you are looking at the
        -        // transpose of the matrix in the source code.
        -        1, 2, 3,
        -        4, 5, 6,
        -        7, 8, 9
        -      ]);
        -      const column0 = m.column(0);
        -      const column1 = m.column(1);
        -      const column2 = m.column(2);
        -      assert.deepEqual(column0.array(), [1, 2, 3]);
        -      assert.deepEqual(column1.array(), [4, 5, 6]);
        -      assert.deepEqual(column2.array(), [7, 8, 9]);
        -      const row0 = m.row(0);
        -      const row1 = m.row(1);
        -      const row2 = m.row(2);
        -      assert.deepEqual(row0.array(), [1, 4, 7]);
        -      assert.deepEqual(row1.array(), [2, 5, 8]);
        -      assert.deepEqual(row2.array(), [3, 6, 9]);
        -    });
        -    test('diagonal()', function() {
        -      const m = new p5.Matrix('mat3', [
        -        1, 2, 3,
        -        4, 5, 6,
        -        7, 8, 9
        -      ]);
        -      const m4x4 = new p5.Matrix([
        -        1, 2, 3, 4,
        -        5, 6, 7, 8,
        -        9, 10, 11, 12,
        -        13, 14, 15, 16
        -      ]);
        -      assert.deepEqual(m.diagonal(), [1, 5, 9]);
        -      assert.deepEqual(m4x4.diagonal(), [1, 6, 11, 16]);
        -    });
        -    test('multiplyVec3', function() {
        -      const m = new p5.Matrix('mat3', [
        -        1, 2, 3,
        -        4, 5, 6,
        -        7, 8, 9
        -      ]);
        -      const multVector = new p5.Vector(3, 2, 1);
        -      const result = m.multiplyVec3(multVector);
        -      assert.deepEqual(result.array(), [18, 24, 30]);
        -      // If there is a target, set result and return that.
        -      const target = new p5.Vector();
        -      m.multiplyVec3(multVector, target);
        -      assert.deepEqual(target.array(), [18, 24, 30]);
        -    });
        -    test('createSubMatrix3x3', function() {
        -      const m4x4 = new p5.Matrix([
        -        1, 2, 3, 4,
        -        5, 6, 7, 8,
        -        9, 10, 11, 12,
        -        13, 14, 15, 16
        -      ]);
        -      const result = new p5.Matrix('mat3', [
        -        1, 2, 3,
        -        5, 6, 7,
        -        9, 10, 11
        -      ]);
        -      const subMatrix3x3 = m4x4.createSubMatrix3x3();
        -      assert.deepEqual(
        -        [].slice.call(result.mat3),
        -        [].slice.call(subMatrix3x3.mat3)
        -      );
        -    });
        -  });
        -});
        diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js
        index 3c7e3df1a2..3897bf7fb2 100644
        --- a/test/unit/webgl/p5.RendererGL.js
        +++ b/test/unit/webgl/p5.RendererGL.js
        @@ -1,18 +1,18 @@
        +import p5 from '../../../src/app.js';
        +import '../../js/chai_helpers';
        +const toArray = (typedArray) => Array.from(typedArray);
        +
         suite('p5.RendererGL', function() {
           var myp5;
         
        -  if (!window.Modernizr.webgl) {
        -    return;
        -  }
        -
        -  setup(function() {
        +  beforeEach(function() {
             myp5 = new p5(function(p) {
               p.setup = function() {};
               p.draw = function() {};
             });
           });
         
        -  teardown(function() {
        +  afterEach(function() {
             myp5.remove();
           });
         
        @@ -35,7 +35,8 @@ suite('p5.RendererGL', function() {
               assert.equal(myp5.webglVersion, myp5.WEBGL);
             });
         
        -    test('works on p5.Graphics', function() {
        +    // NOTE: should graphics always create WebGL2 canvas?
        +    test.skip('works on p5.Graphics', function() {
               myp5.createCanvas(10, 10, myp5.WEBGL);
               myp5.setAttributes({ version: 1 });
               const g = myp5.createGraphics(10, 10, myp5.WEBGL);
        @@ -45,7 +46,7 @@ suite('p5.RendererGL', function() {
         
             suite('when WebGL2 is unavailable', function() {
               let prevGetContext;
        -      setup(function() {
        +      beforeAll(function() {
                 prevGetContext = HTMLCanvasElement.prototype.getContext;
                 // Mock WebGL2 being unavailable
                 HTMLCanvasElement.prototype.getContext = function(type, attrs) {
        @@ -57,7 +58,7 @@ suite('p5.RendererGL', function() {
                 };
               });
         
        -      teardown(function() {
        +      afterAll(function() {
                 // Put back the actual implementation
                 HTMLCanvasElement.prototype.getContext = prevGetContext;
               });
        @@ -69,31 +70,98 @@ suite('p5.RendererGL', function() {
             });
           });
         
        +  suite('texture binding', function() {
        +    test('setting a custom texture works', function() {
        +      myp5.createCanvas(10, 10, myp5.WEBGL);
        +      myp5.background(255);
        +
        +      const myShader = myp5.baseMaterialShader().modify({
        +        uniforms: {
        +          'sampler2D myTex': undefined,
        +        },
        +        'Inputs getPixelInputs': `(Inputs inputs) {
        +          inputs.color = texture(myTex, inputs.texCoord);
        +          return inputs;
        +        }`
        +      })
        +
        +      // Make a red texture
        +      const tex = myp5.createFramebuffer();
        +      tex.draw(() => myp5.background('red'));
        +      console.log(tex.get().canvas.toDataURL());
        +
        +      myp5.shader(myShader);
        +      myp5.fill('red')
        +      myp5.noStroke();
        +      myShader.setUniform('myTex', tex);
        +
        +      myp5.rectMode(myp5.CENTER)
        +      myp5.rect(0, 0, myp5.width, myp5.height);
        +
        +      // It should be red
        +      assert.deepEqual(myp5.get(5, 5), [255, 0, 0, 255]);
        +    })
        +    test('textures remain bound after each draw call', function() {
        +      myp5.createCanvas(20, 10, myp5.WEBGL);
        +      myp5.background(255);
        +
        +      const myShader = myp5.baseMaterialShader().modify({
        +        uniforms: {
        +          'sampler2D myTex': undefined,
        +        },
        +        'Inputs getPixelInputs': `(Inputs inputs) {
        +          inputs.color = texture(myTex, inputs.texCoord);
        +          return inputs;
        +        }`
        +      })
        +
        +      // Make a red texture
        +      const tex = myp5.createFramebuffer();
        +      tex.draw(() => myp5.background('red'));
        +
        +      myp5.shader(myShader);
        +      myp5.noStroke();
        +      myShader.setUniform('myTex', tex);
        +
        +      myp5.translate(-myp5.width/2, -myp5.height/2);
        +      myp5.rectMode(myp5.CORNER);
        +
        +      // Draw once to the left
        +      myp5.rect(0, 0, 10, 10);
        +
        +      // Draw once to the right
        +      myp5.rect(10, 0, 10, 10);
        +
        +      // Both rectangles should be red
        +      assert.deepEqual(myp5.get(5, 5), [255, 0, 0, 255]);
        +      assert.deepEqual(myp5.get(15, 5), [255, 0, 0, 255]);
        +    })
        +  });
        +
           suite('default stroke shader', function() {
        -    test('check activate and deactivating fill and stroke', function(done) {
        +    test('check activate and deactivating fill and stroke', function() {
               myp5.noStroke();
               assert(
        -        !myp5._renderer._doStroke,
        +        !myp5._renderer.states.strokeColor,
                 'stroke shader still active after noStroke()'
               );
        -      assert.isTrue(
        -        myp5._renderer._doFill,
        +      assert(
        +        !myp5._renderer.states.doFill,
                 'fill shader deactivated by noStroke()'
               );
               myp5.stroke(0);
               myp5.noFill();
               assert(
        -        myp5._renderer._doStroke,
        +        !!myp5._renderer.states.strokeColor,
                 'stroke shader not active after stroke()'
               );
               assert.isTrue(
        -        !myp5._renderer._doFill,
        +        !myp5._renderer.states.fillColor,
                 'fill shader still active after noFill()'
               );
        -      done();
             });
         
        -    test('coplanar strokes match 2D', function(done) {
        +    test('coplanar strokes match 2D', function() {
               const getColors = function(mode) {
                 myp5.createCanvas(20, 20, mode);
                 myp5.pixelDensity(1);
        @@ -113,13 +181,24 @@ suite('p5.RendererGL', function() {
                 return [...myp5.pixels];
               };
         
        -      assert.deepEqual(getColors(myp5.P2D), getColors(myp5.WEBGL));
        -      done();
        +      const getPixel = (colors, x, y) => {
        +        const idx = (y * 20 + x) * 4
        +        return colors.slice(idx, idx + 4)
        +      };
        +
        +      const colors2D = getColors(myp5.P2D);
        +      const colorsGL = getColors(myp5.WEBGL);
        +
        +      assert.deepEqual(getPixel(colorsGL, 10, 10), getPixel(colors2D, 10, 10));
        +      assert.deepEqual(getPixel(colorsGL, 15, 15), getPixel(colors2D, 15, 15));
             });
           });
         
           suite('filter shader', function() {
        -    setup(function() {
        +    let frag;
        +    let notAllBlack;
        +
        +    beforeAll(function() {
               frag = `precision highp float;
               varying vec2 vTexCoord;
         
        @@ -275,9 +354,9 @@ suite('p5.RendererGL', function() {
             test('stroke and other settings are unaffected after filter', function() {
               let c = myp5.createCanvas(5, 5, myp5.WEBGL);
               let getShapeAttributes = () => [
        -        c._ellipseMode,
        +        c.states.ellipseMode,
                 c.drawingContext.imageSmoothingEnabled,
        -        c._rectMode,
        +        c.states.rectMode,
                 c.curStrokeWeight,
                 c.curStrokeCap,
                 c.curStrokeJoin,
        @@ -334,18 +413,6 @@ suite('p5.RendererGL', function() {
               assert.doesNotThrow(testDefaultParams, 'this should not throw');
             });
         
        -    test('filter() uses WEBGL implementation behind main P2D canvas', function() {
        -      let renderer = myp5.createCanvas(3,3);
        -      myp5.filter(myp5.BLUR);
        -      assert.isDefined(renderer.filterGraphicsLayer);
        -    });
        -
        -    test('filter() can opt out of WEBGL implementation', function() {
        -      let renderer = myp5.createCanvas(3,3);
        -      myp5.filter(myp5.BLUR, useWebGL=false);
        -      assert.isUndefined(renderer.filterGraphicsLayer);
        -    });
        -
             test('filters make changes to canvas', function() {
               myp5.createCanvas(20,20);
               myp5.circle(10,10,12);
        @@ -363,7 +430,7 @@ suite('p5.RendererGL', function() {
                 myp5.filter(operation);
                 myp5.loadPixels();
                 assert(notAllBlack(myp5.pixels));
        -        assert(notAllBlack(myp5.pixels, invert=true));
        +        assert(notAllBlack(myp5.pixels, true));
               }
             });
         
        @@ -387,7 +454,7 @@ suite('p5.RendererGL', function() {
               }
               let p2 = getPixels();
         
        -      assert.deepEqual(p1, p2);
        +      assert.arrayApproximately(p1, p2, 1);
             });
         
             test('createFilterShader() accepts shader fragments in webgl version 2', function() {
        @@ -476,6 +543,7 @@ suite('p5.RendererGL', function() {
         
             suite('external context', function() {
               const cases = [
        +        ['no modification', () => {}],
                 ['corner rectMode', () => myp5.rectMode(myp5.CORNER)],
                 ['corners rectMode', () => myp5.rectMode(myp5.CORNERS)],
                 ['center rectMode', () => myp5.rectMode(myp5.CENTER)],
        @@ -488,18 +556,19 @@ suite('p5.RendererGL', function() {
               ];
         
               const getFilteredPixels = (mode, initialize, filterType) => {
        -        myp5.createCanvas(10, 10, mode);
        +        myp5.createCanvas(10, 10, mode === 'p2d' ? myp5.P2D : myp5.WEBGL);
        +        myp5.pixelDensity(1);
                 myp5.background(255);
                 if (mode === 'webgl') {
                   myp5.translate(-5, -5);
                 }
                 myp5.noStroke();
                 myp5.fill(255, 0, 0);
        +        myp5.rectMode(myp5.CORNER);
                 myp5.rect(3, 3, 4, 4);
                 initialize();
                 myp5.filter(filterType);
                 myp5.loadPixels();
        -        console.log(myp5._renderer.elt.toDataURL());
                 const pixels = [...myp5.pixels];
                 myp5.remove();
                 return pixels;
        @@ -510,8 +579,11 @@ suite('p5.RendererGL', function() {
                   for (const mode of ['p2d', 'webgl']) {
                     suite(`${mode} mode`, function() {
                       let defaultPixels;
        -              setup(() => {
        -                defaultPixels = getFilteredPixels('p2d', () => {}, filterType);
        +              beforeEach(() => {
        +                defaultPixels = getFilteredPixels(
        +                  'p2d',
        +                  () => {}, filterType
        +                );
                       });
         
                       for (const [name, initialize] of cases) {
        @@ -556,18 +628,30 @@ suite('p5.RendererGL', function() {
               myp5.endContour();
               myp5.endShape(myp5.CLOSE);
               myp5.loadPixels();
        -      return [...myp5.pixels];
        +      const img = myp5._renderer.canvas.toDataURL();
        +      return { pixels: [...myp5.pixels], img };
             };
         
        -    assert.deepEqual(getColors(myp5.P2D), getColors(myp5.WEBGL));
        +    let ok = true;
        +    const colors2D = getColors(myp5.P2D);
        +    const colorsGL = getColors(myp5.WEBGL);
        +    for (let i = 0; i < colors2D.pixels.length; i++) {
        +      if (colors2D.pixels[i] !== colorsGL.pixels[i]) {
        +        ok = false;
        +        break;
        +      }
        +    }
        +    if (!ok) {
        +      throw new Error(`Expected match:\n\n2D: ${colors2D.img}\n\nWebGL: ${colorsGL.img}`);
        +    }
           });
         
           suite('text shader', function() {
        -    test('rendering looks the same in WebGL1 and 2', function(done) {
        -      myp5.loadFont('manual-test-examples/p5.Font/Inconsolata-Bold.ttf', function(font) {
        +    test.todo('rendering looks the same in WebGL1 and 2', function() {
        +      myp5.loadFont('/test/unit/assets/Inconsolata-Bold.ttf', function(font) {
                 const webgl2 = myp5.createGraphics(100, 20, myp5.WEBGL);
                 const webgl1 = myp5.createGraphics(100, 20, myp5.WEBGL);
        -        webgl1.setAttributes({ version: 1 });
        +        webgl1.setAttributes({ version: 1 }); // no longer exists ?
         
                 for (const graphic of [webgl1, webgl2]) {
                   graphic.background(255);
        @@ -585,16 +669,15 @@ suite('p5.RendererGL', function() {
                 }
         
                 assert.deepEqual(webgl1.pixels, webgl2.pixels);
        -        done();
               });
             });
           });
         
           suite('push() and pop() work in WEBGL Mode', function() {
        -    test('push/pop and translation works as expected in WEBGL Mode', function(done) {
        +    test('push/pop and translation works as expected in WEBGL Mode', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
        -      var modelMatrixBefore = myp5._renderer.uModelMatrix.copy();
        -      var viewMatrixBefore = myp5._renderer.uViewMatrix.copy();
        +      var modelMatrixBefore = myp5._renderer.states.uModelMatrix.copy();
        +      var viewMatrixBefore = myp5._renderer.states.uViewMatrix.copy();
         
               myp5.push();
               // Change view
        @@ -604,196 +687,187 @@ suite('p5.RendererGL', function() {
               myp5.translate(20, 100, 5);
               // Check if the model matrix has changed
               assert.notDeepEqual(modelMatrixBefore.mat4,
        -        myp5._renderer.uModelMatrix.mat4);
        +        myp5._renderer.states.uModelMatrix.mat4);
               // Check if the view matrix has changed
               assert.notDeepEqual(viewMatrixBefore.mat4,
        -        myp5._renderer.uViewMatrix.mat4);
        +        myp5._renderer.states.uViewMatrix.mat4);
               myp5.pop();
               // Check if both the model and view matrices are restored after popping
               assert.deepEqual(modelMatrixBefore.mat4,
        -        myp5._renderer.uModelMatrix.mat4);
        -      assert.deepEqual(viewMatrixBefore.mat4, myp5._renderer.uViewMatrix.mat4);
        -      done();
        +        myp5._renderer.states.uModelMatrix.mat4);
        +      assert.deepEqual(viewMatrixBefore.mat4, myp5._renderer.states.uViewMatrix.mat4);
             });
         
        -    test('push/pop and directionalLight() works', function(done) {
        +    test('push/pop and directionalLight() works', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.directionalLight(255, 0, 0, 0, 0, 0);
               var dirDiffuseColors =
        -        myp5._renderer.directionalLightDiffuseColors.slice();
        +        myp5._renderer.states.directionalLightDiffuseColors.slice();
               var dirSpecularColors =
        -        myp5._renderer.directionalLightSpecularColors.slice();
        +        myp5._renderer.states.directionalLightSpecularColors.slice();
               var dirLightDirections =
        -        myp5._renderer.directionalLightDirections.slice();
        +        myp5._renderer.states.directionalLightDirections.slice();
               myp5.push();
               myp5.directionalLight(0, 0, 255, 0, 10, 5);
               assert.notEqual(
                 dirDiffuseColors,
        -        myp5._renderer.directionalLightDiffuseColors
        +        myp5._renderer.states.directionalLightDiffuseColors
               );
               assert.notEqual(
                 dirSpecularColors,
        -        myp5._renderer.directionalLightSpecularColors
        +        myp5._renderer.states.directionalLightSpecularColors
               );
               assert.notEqual(
                 dirLightDirections,
        -        myp5._renderer.directionalLightDirections
        +        myp5._renderer.states.directionalLightDirections
               );
               myp5.pop();
               assert.deepEqual(
                 dirDiffuseColors,
        -        myp5._renderer.directionalLightDiffuseColors
        +        myp5._renderer.states.directionalLightDiffuseColors
               );
               assert.deepEqual(
                 dirSpecularColors,
        -        myp5._renderer.directionalLightSpecularColors
        +        myp5._renderer.states.directionalLightSpecularColors
               );
               assert.deepEqual(
                 dirLightDirections,
        -        myp5._renderer.directionalLightDirections
        +        myp5._renderer.states.directionalLightDirections
               );
        -      done();
             });
         
        -    test('push/pop and ambientLight() works', function(done) {
        +    test('push/pop and ambientLight() works', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.ambientLight(100, 0, 100);
               myp5.ambientLight(0, 0, 200);
        -      var ambColors = myp5._renderer.ambientLightColors.slice();
        +      var ambColors = myp5._renderer.states.ambientLightColors.slice();
               myp5.push();
               myp5.ambientLight(0, 0, 0);
        -      assert.notEqual(ambColors, myp5._renderer.ambientLightColors);
        +      assert.notEqual(ambColors, myp5._renderer.states.ambientLightColors);
               myp5.pop();
        -      assert.deepEqual(ambColors, myp5._renderer.ambientLightColors);
        -      done();
        +      assert.deepEqual(ambColors, myp5._renderer.states.ambientLightColors);
             });
         
        -    test('push/pop and pointLight() works', function(done) {
        +    test('push/pop and pointLight() works', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.pointLight(255, 0, 0, 0, 0, 0);
        -      var pointDiffuseColors = myp5._renderer.pointLightDiffuseColors.slice();
        -      var pointSpecularColors = myp5._renderer.pointLightSpecularColors.slice();
        -      var pointLocs = myp5._renderer.pointLightPositions.slice();
        +      var pointDiffuseColors = myp5._renderer.states.pointLightDiffuseColors.slice();
        +      var pointSpecularColors = myp5._renderer.states.pointLightSpecularColors.slice();
        +      var pointLocs = myp5._renderer.states.pointLightPositions.slice();
               myp5.push();
               myp5.pointLight(0, 0, 255, 0, 10, 5);
               assert.notEqual(
                 pointDiffuseColors,
        -        myp5._renderer.pointLightDiffuseColors
        +        myp5._renderer.states.pointLightDiffuseColors
               );
               assert.notEqual(
                 pointSpecularColors,
        -        myp5._renderer.pointLightSpecularColors
        +        myp5._renderer.states.pointLightSpecularColors
               );
        -      assert.notEqual(pointLocs, myp5._renderer.pointLightPositions);
        +      assert.notEqual(pointLocs, myp5._renderer.states.pointLightPositions);
               myp5.pop();
               assert.deepEqual(
                 pointDiffuseColors,
        -        myp5._renderer.pointLightDiffuseColors
        +        myp5._renderer.states.pointLightDiffuseColors
               );
               assert.deepEqual(
                 pointSpecularColors,
        -        myp5._renderer.pointLightSpecularColors
        +        myp5._renderer.states.pointLightSpecularColors
               );
        -      assert.deepEqual(pointLocs, myp5._renderer.pointLightPositions);
        -      done();
        +      assert.deepEqual(pointLocs, myp5._renderer.states.pointLightPositions);
             });
         
        -    test('push/pop and specularColor() works', function(done) {
        +    test('push/pop and specularColor() works', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.specularColor(255, 0, 0);
        -      var specularColors = myp5._renderer.specularColors.slice();
        +      var specularColors = myp5._renderer.states.specularColors.slice();
               myp5.push();
               myp5.specularColor(0, 0, 255);
        -      assert.notEqual(specularColors, myp5._renderer.specularColors);
        +      assert.notEqual(specularColors, myp5._renderer.states.specularColors);
               myp5.pop();
        -      assert.deepEqual(specularColors, myp5._renderer.specularColors);
        -      done();
        +      assert.deepEqual(specularColors, myp5._renderer.states.specularColors);
             });
         
        -    test('push/pop and spotLight() works', function(done) {
        +    test('push/pop and spotLight() works', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.spotLight(255, 0, 255, 1, 2, 3, 0, 1, 0, Math.PI / 4, 7);
               let spotLightDiffuseColors =
        -        myp5._renderer.spotLightDiffuseColors.slice();
        +        myp5._renderer.states.spotLightDiffuseColors.slice();
               let spotLightSpecularColors =
        -        myp5._renderer.spotLightSpecularColors.slice();
        -      let spotLightPositions = myp5._renderer.spotLightPositions.slice();
        -      let spotLightDirections = myp5._renderer.spotLightDirections.slice();
        -      let spotLightAngle = myp5._renderer.spotLightAngle.slice();
        -      let spotLightConc = myp5._renderer.spotLightConc.slice();
        +        myp5._renderer.states.spotLightSpecularColors.slice();
        +      let spotLightPositions = myp5._renderer.states.spotLightPositions.slice();
        +      let spotLightDirections = myp5._renderer.states.spotLightDirections.slice();
        +      let spotLightAngle = myp5._renderer.states.spotLightAngle.slice();
        +      let spotLightConc = myp5._renderer.states.spotLightConc.slice();
               myp5.push();
               myp5.spotLight(255, 0, 0, 2, 2, 3, 1, 0, 0, Math.PI / 3, 8);
               assert.notEqual(
                 spotLightDiffuseColors,
        -        myp5._renderer.spotLightDiffuseColors
        +        myp5._renderer.states.spotLightDiffuseColors
               );
               assert.notEqual(
                 spotLightSpecularColors,
        -        myp5._renderer.spotLightSpecularColors
        +        myp5._renderer.states.spotLightSpecularColors
               );
        -      assert.notEqual(spotLightPositions, myp5._renderer.spotLightPositions);
        -      assert.notEqual(spotLightDirections, myp5._renderer.spotLightDirections);
        -      assert.notEqual(spotLightAngle, myp5._renderer.spotLightAngle);
        -      assert.notEqual(spotLightConc, myp5._renderer.spotLightConc);
        +      assert.notEqual(spotLightPositions, myp5._renderer.states.spotLightPositions);
        +      assert.notEqual(spotLightDirections, myp5._renderer.states.spotLightDirections);
        +      assert.notEqual(spotLightAngle, myp5._renderer.states.spotLightAngle);
        +      assert.notEqual(spotLightConc, myp5._renderer.states.spotLightConc);
               myp5.pop();
               assert.deepEqual(
                 spotLightDiffuseColors,
        -        myp5._renderer.spotLightDiffuseColors
        +        myp5._renderer.states.spotLightDiffuseColors
               );
               assert.deepEqual(
                 spotLightSpecularColors,
        -        myp5._renderer.spotLightSpecularColors
        +        myp5._renderer.states.spotLightSpecularColors
               );
        -      assert.deepEqual(spotLightPositions, myp5._renderer.spotLightPositions);
        -      assert.deepEqual(spotLightDirections, myp5._renderer.spotLightDirections);
        -      assert.deepEqual(spotLightAngle, myp5._renderer.spotLightAngle);
        -      assert.deepEqual(spotLightConc, myp5._renderer.spotLightConc);
        -      done();
        +      assert.deepEqual(spotLightPositions, myp5._renderer.states.spotLightPositions);
        +      assert.deepEqual(spotLightDirections, myp5._renderer.states.spotLightDirections);
        +      assert.deepEqual(spotLightAngle, myp5._renderer.states.spotLightAngle);
        +      assert.deepEqual(spotLightConc, myp5._renderer.states.spotLightConc);
             });
         
        -    test('push/pop and noLights() works', function(done) {
        +    test('push/pop and noLights() works', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.ambientLight(0, 0, 200);
        -      var ambColors = myp5._renderer.ambientLightColors.slice();
        +      var ambColors = myp5._renderer.states.ambientLightColors.slice();
               myp5.push();
               myp5.ambientLight(0, 200, 0);
        -      var ambPopColors = myp5._renderer.ambientLightColors.slice();
        +      var ambPopColors = myp5._renderer.states.ambientLightColors.slice();
               myp5.noLights();
        -      assert.notEqual(ambColors, myp5._renderer.ambientLightColors);
        -      assert.notEqual(ambPopColors, myp5._renderer.ambientLightColors);
        +      assert.notEqual(ambColors, myp5._renderer.states.ambientLightColors);
        +      assert.notEqual(ambPopColors, myp5._renderer.states.ambientLightColors);
               myp5.pop();
        -      assert.deepEqual(ambColors, myp5._renderer.ambientLightColors);
        -      done();
        +      assert.deepEqual(ambColors, myp5._renderer.states.ambientLightColors);
             });
         
        -    test('push/pop and texture() works', function(done) {
        +    test('push/pop and texture() works', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               var tex1 = myp5.createGraphics(1, 1);
               myp5.texture(tex1);
        -      assert.equal(tex1, myp5._renderer._tex);
        +      assert.equal(tex1, myp5._renderer.states._tex);
               myp5.push();
               var tex2 = myp5.createGraphics(2, 2);
               myp5.texture(tex2);
        -      assert.equal(tex2, myp5._renderer._tex);
        -      assert.notEqual(tex1, myp5._renderer._tex);
        +      assert.equal(tex2, myp5._renderer.states._tex);
        +      assert.notEqual(tex1, myp5._renderer.states._tex);
               myp5.pop();
        -      assert.equal(tex1, myp5._renderer._tex);
        -      done();
        +      assert.equal(tex1, myp5._renderer.states._tex);
             });
         
        -    test('ambientLight() changes when metalness is applied', function (done) {
        +    test('ambientLight() changes when metalness is applied', function () {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.ambientLight(255, 255, 255);
               myp5.noStroke();
               myp5.metalness(100000);
               myp5.sphere(50);
               expect(myp5._renderer.mixedAmbientLight).to.not.deep.equal(
        -        myp5._renderer.ambientLightColors);
        -      done();
        +        myp5._renderer.states.ambientLightColors);
             });
         
             test('specularColor transforms to fill color when metalness is applied',
        -      function (done) {
        +      function () {
                 myp5.createCanvas(100, 100, myp5.WEBGL);
                 myp5.fill(0, 0, 0, 0);
                 myp5.specularMaterial(255, 255, 255, 255);
        @@ -801,26 +875,24 @@ suite('p5.RendererGL', function() {
                 myp5.metalness(100000);
                 myp5.sphere(50);
                 expect(myp5._renderer.mixedSpecularColor).to.deep.equal(
        -          myp5._renderer.curFillColor);
        -        done();
        +          myp5._renderer.states.curFillColor);
               });
         
        -    test('push/pop and shader() works with fill', function(done) {
        +    test('push/pop and shader() works with fill shaders by default', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               var fillShader1 = myp5._renderer._getLightShader();
               var fillShader2 = myp5._renderer._getColorShader();
               myp5.shader(fillShader1);
        -      assert.equal(fillShader1, myp5._renderer.userFillShader);
        +      assert.equal(fillShader1, myp5._renderer.states.userFillShader);
               myp5.push();
               myp5.shader(fillShader2);
        -      assert.equal(fillShader2, myp5._renderer.userFillShader);
        -      assert.notEqual(fillShader1, myp5._renderer.userFillShader);
        +      assert.equal(fillShader2, myp5._renderer.states.userFillShader);
        +      assert.notEqual(fillShader1, myp5._renderer.states.userFillShader);
               myp5.pop();
        -      assert.equal(fillShader1, myp5._renderer.userFillShader);
        -      done();
        +      assert.equal(fillShader1, myp5._renderer.states.userFillShader);
             });
         
        -    test('push/pop builds/unbuilds stack properly', function(done) {
        +    test('push/pop builds/unbuilds stack properly', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               var col1 = myp5.color(255, 0, 0);
               var col2 = myp5.color(0, 255, 0);
        @@ -834,14 +906,13 @@ suite('p5.RendererGL', function() {
               }
               for (var j = i; j > 0; j--) {
                 if (j % 2 === 0) {
        -          assert.deepEqual(col2._array, myp5._renderer.curFillColor);
        +          assert.deepEqual(col2._array, myp5._renderer.states.curFillColor);
                 } else {
        -          assert.deepEqual(col1._array, myp5._renderer.curFillColor);
        +          assert.deepEqual(col1._array, myp5._renderer.states.curFillColor);
                 }
                 myp5.pop();
               }
               assert.isTrue(myp5._styles.length === 0);
        -      done();
             });
           });
         
        @@ -849,7 +920,7 @@ suite('p5.RendererGL', function() {
             test('changing cameras keeps transforms', function() {
               myp5.createCanvas(50, 50, myp5.WEBGL);
         
        -      const origModelMatrix = myp5._renderer.uModelMatrix.copy();
        +      const origModelMatrix = myp5._renderer.states.uModelMatrix.copy();
         
               const cam2 = myp5.createCamera();
               cam2.setPosition(0, 0, -500);
        @@ -858,21 +929,21 @@ suite('p5.RendererGL', function() {
               // cam1 is applied right now so technically this is redundant
               myp5.setCamera(cam1);
               const cam1Matrix = cam1.cameraMatrix.copy();
        -      assert.deepEqual(myp5._renderer.uViewMatrix.mat4, cam1Matrix.mat4);
        +      assert.deepEqual(toArray(myp5._renderer.states.uViewMatrix.mat4), toArray(cam1Matrix.mat4));
         
               // Translation only changes the model matrix
               myp5.translate(100, 0, 0);
               assert.notDeepEqual(
        -        myp5._renderer.uModelMatrix.mat4,
        +        myp5._renderer.states.uModelMatrix.mat4,
                 origModelMatrix.mat4
               );
        -      assert.deepEqual(myp5._renderer.uViewMatrix.mat4, cam1Matrix.mat4);
        +      assert.deepEqual(toArray(myp5._renderer.states.uViewMatrix.mat4), toArray(cam1Matrix.mat4));
         
               // Switchnig cameras only changes the view matrix
        -      const transformedModel = myp5._renderer.uModelMatrix.copy();
        +      const transformedModel = myp5._renderer.states.uModelMatrix.copy();
               myp5.setCamera(cam2);
        -      assert.deepEqual(myp5._renderer.uModelMatrix.mat4, transformedModel.mat4);
        -      assert.notDeepEqual(myp5._renderer.uViewMatrix.mat4, cam1Matrix.mat4);
        +      assert.deepEqual(toArray(myp5._renderer.states.uModelMatrix.mat4), toArray(transformedModel.mat4));
        +      assert.notDeepEqual(myp5._renderer.states.uViewMatrix.mat4, cam1Matrix.mat4);
             });
           });
         
        @@ -964,24 +1035,22 @@ suite('p5.RendererGL', function() {
           });
         
           suite('loadpixels()', function() {
        -    test('loadPixels color check', function(done) {
        +    test('loadPixels color check', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.background(0, 100, 0);
               myp5.loadPixels();
               var pixels = myp5.pixels;
               assert.deepEqual(pixels[1], 100);
               assert.deepEqual(pixels[3], 255);
        -      done();
             });
         
        -    test('get() singlePixel color and size, with loadPixels', function(done) {
        +    test('get() singlePixel color and size, with loadPixels', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.background(100, 115, 100);
               myp5.loadPixels();
               var img = myp5.get(0, 0);
               assert.isTrue(img[1] === 115);
               assert.isTrue(img.length === 4);
        -      done();
             });
         
             test('updatePixels() matches 2D mode', function() {
        @@ -1023,20 +1092,18 @@ suite('p5.RendererGL', function() {
         
           suite('get()', function() {
             var img;
        -    test('get() size check', function(done) {
        +    test('get() size check', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               img = myp5.get();
               assert.deepEqual(img.width, myp5.width);
        -      done();
             });
         
        -    test('get() can create p5.Image', function(done) {
        +    test('get() can create p5.Image', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               assert.isTrue(img instanceof p5.Image);
        -      done();
             });
         
        -    test('get() singlePixel color and size', function(done) {
        +    test('get() singlePixel color and size', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.background(100, 115, 100);
               img = myp5.get(0, 0);
        @@ -1046,14 +1113,13 @@ suite('p5.RendererGL', function() {
               img = myp5.get(0, 0);
               assert.isTrue(img[1] === 115);
               assert.isTrue(img.length === 4);
        -      done();
             });
           });
         
           suite('GL Renderer clear()', function() {
             var pg;
             var pixel;
        -    test('webgl graphics background draws into webgl canvas', function(done) {
        +    test('webgl graphics background draws into webgl canvas', function() {
               myp5.createCanvas(50, 50, myp5.WEBGL);
               myp5.background(0, 255, 255, 255);
               pg = myp5.createGraphics(25, 50, myp5.WEBGL);
        @@ -1061,10 +1127,9 @@ suite('p5.RendererGL', function() {
               myp5.image(pg, -myp5.width / 2, -myp5.height / 2);
               pixel = myp5.get(0, 0);
               assert.deepEqual(pixel, [0, 0, 0, 255]);
        -      done();
             });
         
        -    test('transparent GL graphics with GL canvas', function(done) {
        +    test('transparent GL graphics with GL canvas', function() {
               myp5.createCanvas(50, 50, myp5.WEBGL);
               pg = myp5.createGraphics(25, 50, myp5.WEBGL);
               myp5.background(0, 255, 255);
        @@ -1072,10 +1137,9 @@ suite('p5.RendererGL', function() {
               myp5.image(pg, -myp5.width / 2, -myp5.height / 2);
               pixel = myp5.get(0, 0);
               assert.deepEqual(pixel, [0, 255, 255, 255]);
        -      done();
             });
         
        -    test('clear color with rgba arguments', function(done) {
        +    test('clear color with rgba arguments', function() {
               myp5.createCanvas(50, 50, myp5.WEBGL);
               myp5.clear(1, 0, 0, 1);
               pixel = myp5.get(0, 0);
        @@ -1084,10 +1148,9 @@ suite('p5.RendererGL', function() {
               pg.clear(1, 0, 0, 1);
               pixel = pg.get(0, 0);
               assert.deepEqual(pixel, [255, 0, 0, 255]);
        -      done();
             });
         
        -    test('semi-transparent GL graphics with GL canvas', function(done) {
        +    test('semi-transparent GL graphics with GL canvas', function() {
               myp5.createCanvas(50, 50, myp5.WEBGL);
               pg = myp5.createGraphics(25, 50, myp5.WEBGL);
               myp5.background(0, 255, 255);
        @@ -1095,10 +1158,9 @@ suite('p5.RendererGL', function() {
               myp5.image(pg, -myp5.width / 2, -myp5.height / 2);
               pixel = myp5.get(0, 0);
               assert.deepEqual(pixel, [39, 194, 194, 255]);
        -      done();
             });
         
        -    test('webgl graphics background draws into 2D canvas', function(done) {
        +    test('webgl graphics background draws into 2D canvas', function() {
               myp5.createCanvas(50, 50);
               myp5.background(0, 255, 255, 255);
               pg = myp5.createGraphics(25, 50, myp5.WEBGL);
        @@ -1106,10 +1168,9 @@ suite('p5.RendererGL', function() {
               myp5.image(pg, 0, 0);
               pixel = myp5.get(0, 0);
               assert.deepEqual(pixel, [0, 0, 0, 255]);
        -      done();
             });
         
        -    test('transparent GL graphics with 2D canvas', function(done) {
        +    test('transparent GL graphics with 2D canvas', function() {
               myp5.createCanvas(50, 50);
               pg = myp5.createGraphics(25, 50, myp5.WEBGL);
               myp5.background(0, 255, 255);
        @@ -1117,10 +1178,9 @@ suite('p5.RendererGL', function() {
               myp5.image(pg, 0, 0);
               pixel = myp5.get(0, 0);
               assert.deepEqual(pixel, [0, 255, 255, 255]);
        -      done();
             });
         
        -    test('semi-transparent GL graphics with 2D canvas', function(done) {
        +    test('semi-transparent GL graphics with 2D canvas', function() {
               myp5.createCanvas(50, 50);
               pg = myp5.createGraphics(25, 50, myp5.WEBGL);
               myp5.background(0, 255, 255);
        @@ -1128,7 +1188,6 @@ suite('p5.RendererGL', function() {
               myp5.image(pg, 0, 0);
               pixel = myp5.get(0, 0);
               assert.deepEqual(pixel, [39, 194, 194, 255]);
        -      done();
             });
           });
         
        @@ -1175,10 +1234,10 @@ suite('p5.RendererGL', function() {
           suite('blendMode()', function() {
             var testBlend = function(mode, intended) {
               myp5.blendMode(mode);
        -      assert.deepEqual(intended, myp5._renderer.curBlendMode);
        +      assert.deepEqual(intended, myp5._renderer.states.curBlendMode);
             };
         
        -    test('blendMode sets _curBlendMode correctly', function(done) {
        +    test('blendMode sets _curBlendMode correctly', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               testBlend(myp5.ADD, myp5.ADD);
               testBlend(myp5.REPLACE, myp5.REPLACE);
        @@ -1188,10 +1247,9 @@ suite('p5.RendererGL', function() {
               testBlend(myp5.MULTIPLY, myp5.MULTIPLY);
               testBlend(myp5.LIGHTEST, myp5.LIGHTEST);
               testBlend(myp5.DARKEST, myp5.DARKEST);
        -      done();
             });
         
        -    test('blendMode doesnt change when mode unavailable in 3D', function(done) {
        +    test('blendMode doesnt change when mode unavailable in 3D', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.blendMode(myp5.DARKEST);
               testBlend(myp5.BURN, myp5.DARKEST);
        @@ -1199,7 +1257,6 @@ suite('p5.RendererGL', function() {
               testBlend(myp5.SOFT_LIGHT, myp5.DARKEST);
               testBlend(myp5.HARD_LIGHT, myp5.DARKEST);
               testBlend(myp5.OVERLAY, myp5.DARKEST);
        -      done();
             });
         
             var mixAndReturn = function(mode, bgCol) {
        @@ -1212,7 +1269,7 @@ suite('p5.RendererGL', function() {
               return myp5.get(5, 5);
             };
         
        -    test('blendModes change pixel colors as expected', function(done) {
        +    test('blendModes change pixel colors as expected', function() {
               myp5.createCanvas(10, 10, myp5.WEBGL);
               myp5.noStroke();
               assert.deepEqual([122, 0, 122, 255], mixAndReturn(myp5.ADD, 0));
        @@ -1225,72 +1282,70 @@ suite('p5.RendererGL', function() {
               assert.deepEqual([133, 69, 133, 255], mixAndReturn(myp5.MULTIPLY, 255));
               assert.deepEqual([122, 0, 122, 255], mixAndReturn(myp5.LIGHTEST, 0));
               assert.deepEqual([0, 0, 0, 255], mixAndReturn(myp5.DARKEST, 255));
        -      done();
             });
         
        -    test('blendModes match 2D mode', function(done) {
        +    test('blendModes match 2D mode', function() {
               myp5.createCanvas(10, 10, myp5.WEBGL);
        -      myp5.setAttributes({ alpha: true });
               const ref = myp5.createGraphics(myp5.width, myp5.height);
               ref.translate(ref.width / 2, ref.height / 2); // Match WebGL mode
         
               const testBlend = function(target, colorA, colorB, mode) {
                 target.clear();
                 target.push();
        -        target.background(colorA);
        +        target.background(0);
                 target.blendMode(mode);
        +        target.rectMode(myp5.CENTER);
                 target.noStroke();
        +        target.fill(colorA);
        +        target.rect(0, 0, target.width, target.height);
                 target.fill(colorB);
        -        target.rectMode(target.CENTER);
                 target.rect(0, 0, target.width, target.height);
                 target.pop();
        +        console.log(`${colorA} ${mode} ${colorB}: ` + target.canvas.toDataURL())
                 return target.get(0, 0);
               };
         
               const assertSameIn2D = function(colorA, colorB, mode) {
                 const refColor = testBlend(myp5, colorA, colorB, mode);
                 const webglColor = testBlend(ref, colorA, colorB, mode);
        -        assert.deepEqual(
        +        // console.log(`Blending ${colorA} with ${colorB} using ${mode}: ${JSON.stringify(refColor)}, ${JSON.stringify(webglColor)}`)
        +        assert.arrayApproximately(
                   refColor,
                   webglColor,
        +          10,
                   `Blending ${colorA} with ${colorB} using ${mode}`
                 );
               };
         
        -      for (const alpha of [255, 200]) {
        +      for (const alpha of [1, 200/255]) {
                 const red = myp5.color('#F53');
                 const blue = myp5.color('#13F');
                 red.setAlpha(alpha);
                 blue.setAlpha(alpha);
                 assertSameIn2D(red, blue, myp5.BLEND);
                 assertSameIn2D(red, blue, myp5.ADD);
        -        assertSameIn2D(red, blue, myp5.DARKEST);
        -        assertSameIn2D(red, blue, myp5.LIGHTEST);
                 assertSameIn2D(red, blue, myp5.EXCLUSION);
                 assertSameIn2D(red, blue, myp5.MULTIPLY);
                 assertSameIn2D(red, blue, myp5.SCREEN);
        -        assertSameIn2D(red, blue, myp5.REPLACE);
                 assertSameIn2D(red, blue, myp5.REMOVE);
        -        done();
               }
             });
         
        -    test('blendModes are included in push/pop', function(done) {
        +    test('blendModes are included in push/pop', function() {
               myp5.createCanvas(10, 10, myp5.WEBGL);
               myp5.blendMode(myp5.MULTIPLY);
               myp5.push();
               myp5.blendMode(myp5.ADD);
        -      assert.equal(myp5._renderer.curBlendMode, myp5.ADD, 'Changed to ADD');
        +      assert.equal(myp5._renderer.states.curBlendMode, myp5.ADD, 'Changed to ADD');
               myp5.pop();
               assert.equal(
        -        myp5._renderer.curBlendMode,
        +        myp5._renderer.states.curBlendMode,
                 myp5.MULTIPLY,
                 'Resets to MULTIPLY'
               );
        -      done();
             });
         
        -    test('blendModes are applied to point drawing', function(done) {
        +    test('blendModes are applied to point drawing', function() {
               myp5.createCanvas(32, 32, myp5.WEBGL);
               myp5.background(0);
               myp5.blendMode(myp5.ADD);
        @@ -1300,7 +1355,6 @@ suite('p5.RendererGL', function() {
               myp5.stroke(0, 0, 255);
               myp5.point(0, 0, 0);
               assert.deepEqual(myp5.get(16, 16), [255, 0, 255, 255]);
        -      done();
             });
         
             test('transparency works the same with per-vertex colors', function() {
        @@ -1329,14 +1383,15 @@ suite('p5.RendererGL', function() {
           });
         
           suite('BufferDef', function() {
        -    test('render buffer properties are correctly set', function(done) {
        +    test('render buffer properties are correctly set', function() {
               var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
         
               myp5.fill(255);
               myp5.stroke(255);
               myp5.triangle(0, 0, 1, 0, 0, 1);
         
        -      var buffers = renderer.retainedMode.geometry['tri'];
        +      const buffers = renderer.geometryBufferCache.getCachedID('tri');
        +      const geom = renderer.geometryBufferCache.getGeometryByID('tri');
         
               assert.isObject(buffers);
               assert.isDefined(buffers.indexBuffer);
        @@ -1348,46 +1403,45 @@ suite('p5.RendererGL', function() {
               assert.isDefined(buffers.lineTangentsOutBuffer);
               assert.isDefined(buffers.vertexBuffer);
         
        -      assert.equal(buffers.vertexCount, 3);
        +      assert.equal(geom.faces.length, 1);
         
               //   6 verts per line segment x3 (each is a quad made of 2 triangles)
               // + 12 verts per join x3 (2 quads each, 1 is discarded in the shader)
               // + 6 verts per line cap x0 (1 quad each)
               // = 54
        -      assert.equal(buffers.lineVertexCount, 54);
        +      assert.equal(geom.lineVertices.length, 54 * 3);
         
        -      done();
             });
           });
         
           suite('tint() in WEBGL mode', function() {
             test('default tint value is set and not null', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
        -      assert.deepEqual(myp5._renderer._tint, [255, 255, 255, 255]);
        +      assert.deepEqual(myp5._renderer.states.tint, [255, 255, 255, 255]);
             });
         
             test('tint value is modified correctly when tint() is called', function() {
               myp5.createCanvas(100, 100, myp5.WEBGL);
               myp5.tint(0, 153, 204, 126);
        -      assert.deepEqual(myp5._renderer._tint, [0, 153, 204, 126]);
        +      assert.deepEqual(myp5._renderer.states.tint, [0, 153, 204, 126]);
               myp5.tint(100, 120, 140);
        -      assert.deepEqual(myp5._renderer._tint, [100, 120, 140, 255]);
        +      assert.deepEqual(myp5._renderer.states.tint, [100, 120, 140, 255]);
               myp5.tint('violet');
        -      assert.deepEqual(myp5._renderer._tint, [238, 130, 238, 255]);
        +      assert.deepEqual(myp5._renderer.states.tint, [238, 130, 238, 255]);
               myp5.tint(100);
        -      assert.deepEqual(myp5._renderer._tint, [100, 100, 100, 255]);
        +      assert.deepEqual(myp5._renderer.states.tint, [100, 100, 100, 255]);
               myp5.tint(100, 126);
        -      assert.deepEqual(myp5._renderer._tint, [100, 100, 100, 126]);
        +      assert.deepEqual(myp5._renderer.states.tint, [100, 100, 100, 126]);
               myp5.tint([100, 126, 0, 200]);
        -      assert.deepEqual(myp5._renderer._tint, [100, 126, 0, 200]);
        +      assert.deepEqual(myp5._renderer.states.tint, [100, 126, 0, 200]);
               myp5.tint([100, 126, 0]);
        -      assert.deepEqual(myp5._renderer._tint, [100, 126, 0, 255]);
        +      assert.deepEqual(myp5._renderer.states.tint, [100, 126, 0, 255]);
               myp5.tint([100]);
        -      assert.deepEqual(myp5._renderer._tint, [100, 100, 100, 255]);
        +      assert.deepEqual(myp5._renderer.states.tint, [100, 100, 100, 255]);
               myp5.tint([100, 126]);
        -      assert.deepEqual(myp5._renderer._tint, [100, 100, 100, 126]);
        +      assert.deepEqual(myp5._renderer.states.tint, [100, 100, 100, 126]);
               myp5.tint(myp5.color(255, 204, 0));
        -      assert.deepEqual(myp5._renderer._tint, [255, 204, 0, 255]);
        +      assert.deepEqual(myp5._renderer.states.tint, [255, 204, 0, 255]);
             });
         
             test('tint should be reset after draw loop', function() {
        @@ -1398,7 +1452,7 @@ suite('p5.RendererGL', function() {
                   };
                   p.draw = function() {
                     if (p.frameCount === 2) {
        -              resolve(p._renderer._tint);
        +              resolve(p._renderer.states.tint);
                     }
                     p.tint(0, 153, 204, 126);
                   };
        @@ -1410,36 +1464,36 @@ suite('p5.RendererGL', function() {
           });
         
           suite('beginShape() in WEBGL mode', function() {
        -    test('QUADS mode converts into triangles', function(done) {
        +    test('QUADS mode converts into triangles', function() {
               var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
               myp5.textureMode(myp5.NORMAL);
        -      renderer.beginShape(myp5.QUADS);
        -      renderer.fill(255, 0, 0);
        -      renderer.normal(0, 1, 2);
        -      renderer.vertex(0, 0, 0, 0, 0);
        -      renderer.fill(0, 255, 0);
        -      renderer.normal(3, 4, 5);
        -      renderer.vertex(0, 1, 1, 0, 1);
        -      renderer.fill(0, 0, 255);
        -      renderer.normal(6, 7, 8);
        -      renderer.vertex(1, 0, 2, 1, 0);
        -      renderer.fill(255, 0, 255);
        -      renderer.normal(9, 10, 11);
        -      renderer.vertex(1, 1, 3, 1, 1);
        -
        -      renderer.fill(255, 0, 0);
        -      renderer.normal(12, 13, 14);
        -      renderer.vertex(2, 0, 4, 0, 0);
        -      renderer.fill(0, 255, 0);
        -      renderer.normal(15, 16, 17);
        -      renderer.vertex(2, 1, 5, 0, 1);
        -      renderer.fill(0, 0, 255);
        -      renderer.normal(18, 19, 20);
        -      renderer.vertex(3, 0, 6, 1, 0);
        -      renderer.fill(255, 0, 255);
        -      renderer.normal(21, 22, 23);
        -      renderer.vertex(3, 1, 7, 1, 1);
        -      renderer.endShape();
        +      myp5.beginShape(myp5.QUADS);
        +      myp5.fill(255, 0, 0);
        +      myp5.normal(0, 1, 2);
        +      myp5.vertex(0, 0, 0, 0, 0);
        +      myp5.fill(0, 255, 0);
        +      myp5.normal(3, 4, 5);
        +      myp5.vertex(0, 1, 1, 0, 1);
        +      myp5.fill(0, 0, 255);
        +      myp5.normal(6, 7, 8);
        +      myp5.vertex(1, 0, 2, 1, 0);
        +      myp5.fill(255, 0, 255);
        +      myp5.normal(9, 10, 11);
        +      myp5.vertex(1, 1, 3, 1, 1);
        +
        +      myp5.fill(255, 0, 0);
        +      myp5.normal(12, 13, 14);
        +      myp5.vertex(2, 0, 4, 0, 0);
        +      myp5.fill(0, 255, 0);
        +      myp5.normal(15, 16, 17);
        +      myp5.vertex(2, 1, 5, 0, 1);
        +      myp5.fill(0, 0, 255);
        +      myp5.normal(18, 19, 20);
        +      myp5.vertex(3, 0, 6, 1, 0);
        +      myp5.fill(255, 0, 255);
        +      myp5.normal(21, 22, 23);
        +      myp5.vertex(3, 1, 7, 1, 1);
        +      myp5.endShape();
         
               const expectedVerts = [
                 [0, 0, 0],
        @@ -1459,13 +1513,13 @@ suite('p5.RendererGL', function() {
                 [3, 1, 7]
               ];
               assert.equal(
        -        renderer.immediateMode.geometry.vertices.length,
        +        renderer.shapeBuilder.geometry.vertices.length,
                 expectedVerts.length
               );
               expectedVerts.forEach(function([x, y, z], i) {
        -        assert.equal(renderer.immediateMode.geometry.vertices[i].x, x);
        -        assert.equal(renderer.immediateMode.geometry.vertices[i].y, y);
        -        assert.equal(renderer.immediateMode.geometry.vertices[i].z, z);
        +        assert.equal(renderer.shapeBuilder.geometry.vertices[i].x, x);
        +        assert.equal(renderer.shapeBuilder.geometry.vertices[i].y, y);
        +        assert.equal(renderer.shapeBuilder.geometry.vertices[i].z, z);
               });
         
               const expectedUVs = [
        @@ -1485,7 +1539,7 @@ suite('p5.RendererGL', function() {
                 [1, 0],
                 [1, 1]
               ].flat();
        -      assert.deepEqual(renderer.immediateMode.geometry.uvs, expectedUVs);
        +      assert.deepEqual(renderer.shapeBuilder.geometry.uvs, expectedUVs);
         
               const expectedColors = [
                 [1, 0, 0, 1],
        @@ -1505,7 +1559,7 @@ suite('p5.RendererGL', function() {
                 [1, 0, 1, 1]
               ].flat();
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexColors,
        +        renderer.shapeBuilder.geometry.vertexColors,
                 expectedColors
               );
         
        @@ -1527,145 +1581,153 @@ suite('p5.RendererGL', function() {
                 [21, 22, 23]
               ];
               assert.equal(
        -        renderer.immediateMode.geometry.vertexNormals.length,
        +        renderer.shapeBuilder.geometry.vertexNormals.length,
                 expectedNormals.length
               );
               expectedNormals.forEach(function([x, y, z], i) {
        -        assert.equal(renderer.immediateMode.geometry.vertexNormals[i].x, x);
        -        assert.equal(renderer.immediateMode.geometry.vertexNormals[i].y, y);
        -        assert.equal(renderer.immediateMode.geometry.vertexNormals[i].z, z);
        +        assert.equal(renderer.shapeBuilder.geometry.vertexNormals[i].x, x);
        +        assert.equal(renderer.shapeBuilder.geometry.vertexNormals[i].y, y);
        +        assert.equal(renderer.shapeBuilder.geometry.vertexNormals[i].z, z);
               });
        -
        -      done();
             });
         
        -    test('QUADS mode makes edges for quad outlines', function(done) {
        +    test('QUADS mode makes edges for quad outlines', function() {
               var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
        -      renderer.beginShape(myp5.QUADS);
        -      renderer.vertex(0, 0);
        -      renderer.vertex(0, 1);
        -      renderer.vertex(1, 0);
        -      renderer.vertex(1, 1);
        +      myp5.beginShape(myp5.QUADS);
        +      myp5.vertex(0, 0);
        +      myp5.vertex(0, 1);
        +      myp5.vertex(1, 0);
        +      myp5.vertex(1, 1);
         
        -      renderer.vertex(2, 0);
        -      renderer.vertex(2, 1);
        -      renderer.vertex(3, 0);
        -      renderer.vertex(3, 1);
        -      renderer.endShape();
        +      myp5.vertex(2, 0);
        +      myp5.vertex(2, 1);
        +      myp5.vertex(3, 0);
        +      myp5.vertex(3, 1);
        +      myp5.endShape();
         
        -      assert.equal(renderer.immediateMode.geometry.edges.length, 8);
        -      done();
        +      assert.equal(renderer.shapeBuilder.geometry.edges.length, 8);
             });
         
        -    test('QUAD_STRIP mode makes edges for strip outlines', function(done) {
        +    test('QUAD_STRIP mode makes edges for strip outlines', function() {
               var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
        -      renderer.beginShape(myp5.QUAD_STRIP);
        -      renderer.vertex(0, 0);
        -      renderer.vertex(0, 1);
        -      renderer.vertex(1, 0);
        -      renderer.vertex(1, 1);
        -      renderer.vertex(2, 0);
        -      renderer.vertex(2, 1);
        -      renderer.vertex(3, 0);
        -      renderer.vertex(3, 1);
        -      renderer.endShape();
        +      myp5.beginShape(myp5.QUAD_STRIP);
        +      myp5.vertex(0, 0);
        +      myp5.vertex(0, 1);
        +      myp5.vertex(1, 0);
        +      myp5.vertex(1, 1);
        +      myp5.vertex(2, 0);
        +      myp5.vertex(2, 1);
        +      myp5.vertex(3, 0);
        +      myp5.vertex(3, 1);
        +      myp5.endShape();
         
               // Two full quads (2 * 4) plus two edges connecting them
        -      assert.equal(renderer.immediateMode.geometry.edges.length, 10);
        -      done();
        +      assert.equal(renderer.shapeBuilder.geometry.edges.length, 10);
             });
         
        -    test('TRIANGLE_FAN mode makes edges for each triangle', function(done) {
        +    test('TRIANGLE_FAN mode makes edges for each triangle', function() {
               var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
               //    x
               //    | \
               // x--x--x
               //  \ | /
               //    x
        -      renderer.beginShape(myp5.TRIANGLE_FAN);
        -      renderer.vertex(0, 0);
        -      renderer.vertex(0, -5);
        -      renderer.vertex(5, 0);
        -      renderer.vertex(0, 5);
        -      renderer.vertex(-5, 0);
        -      renderer.endShape();
        +      myp5.beginShape(myp5.TRIANGLE_FAN);
        +      myp5.vertex(0, 0);
        +      myp5.vertex(0, -5);
        +      myp5.vertex(5, 0);
        +      myp5.vertex(0, 5);
        +      myp5.vertex(-5, 0);
        +      myp5.endShape();
         
        -      assert.equal(renderer.immediateMode.geometry.edges.length, 7);
        -      done();
        +      assert.equal(renderer.shapeBuilder.geometry.edges.length, 7);
             });
         
        -    test('TESS preserves vertex data', function(done) {
        +    test('PATH preserves vertex data', function() {
               var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
         
               myp5.textureMode(myp5.NORMAL);
        -      renderer.beginShape(myp5.TESS);
        -      renderer.fill(255, 255, 255);
        -      renderer.normal(-1, -1, 1);
        -      renderer.vertex(-10, -10, 0, 0);
        -      renderer.fill(255, 0, 0);
        -      renderer.normal(1, -1, 1);
        -      renderer.vertex(10, -10, 1, 0);
        -      renderer.fill(0, 255, 0);
        -      renderer.normal(1, 1, 1);
        -      renderer.vertex(10, 10, 1, 1);
        -      renderer.fill(0, 0, 255);
        -      renderer.normal(-1, 1, 1);
        -      renderer.vertex(-10, 10, 0, 1);
        -      renderer.endShape(myp5.CLOSE);
        -
        -      assert.equal(renderer.immediateMode.geometry.vertices.length, 6);
        +      myp5.beginShape(myp5.PATH);
        +      myp5.fill(255, 255, 255);
        +      myp5.normal(-1, -1, 1);
        +      myp5.vertexProperty('aCustom', [1, 1, 1])
        +      myp5.vertex(-10, -10, 0, 0);
        +      myp5.fill(255, 0, 0);
        +      myp5.normal(1, -1, 1);
        +      myp5.vertexProperty('aCustom', [1, 0, 0])
        +      myp5.vertex(10, -10, 1, 0);
        +      myp5.fill(0, 255, 0);
        +      myp5.normal(1, 1, 1);
        +      myp5.vertexProperty('aCustom', [0, 1, 0])
        +      myp5.vertex(10, 10, 1, 1);
        +      myp5.fill(0, 0, 255);
        +      myp5.normal(-1, 1, 1);
        +      myp5.vertexProperty('aCustom', [0, 0, 1])
        +      myp5.vertex(-10, 10, 0, 1);
        +      myp5.endShape(myp5.CLOSE);
        +
        +      assert.equal(renderer.shapeBuilder.geometry.vertices.length, 6);
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[0].array(),
        +        renderer.shapeBuilder.geometry.vertices[0].array(),
                 [10, -10, 0]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[1].array(),
        +        renderer.shapeBuilder.geometry.vertices[1].array(),
                 [-10, 10, 0]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[2].array(),
        +        renderer.shapeBuilder.geometry.vertices[2].array(),
                 [-10, -10, 0]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[3].array(),
        +        renderer.shapeBuilder.geometry.vertices[3].array(),
                 [-10, 10, 0]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[4].array(),
        +        renderer.shapeBuilder.geometry.vertices[4].array(),
                 [10, -10, 0]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[5].array(),
        +        renderer.shapeBuilder.geometry.vertices[5].array(),
                 [10, 10, 0]
               );
         
        -      assert.equal(renderer.immediateMode.geometry.vertexNormals.length, 6);
        +      assert.equal(renderer.shapeBuilder.geometry.vertexNormals.length, 6);
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[0].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[0].array(),
                 [1, -1, 1]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[1].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[1].array(),
                 [-1, 1, 1]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[2].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[2].array(),
                 [-1, -1, 1]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[3].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[3].array(),
                 [-1, 1, 1]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[4].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[4].array(),
                 [1, -1, 1]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[5].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[5].array(),
                 [1, 1, 1]
               );
         
        -      assert.deepEqual(renderer.immediateMode.geometry.vertexColors, [
        +      assert.deepEqual(renderer.shapeBuilder.geometry.aCustomSrc, [
        +          1, 0, 0,
        +          0, 0, 1,
        +          1, 1, 1,
        +          0, 0, 1,
        +          1, 0, 0,
        +          0, 1, 0
        +        ]);
        +
        +      assert.deepEqual(renderer.shapeBuilder.geometry.vertexColors, [
                 1, 0, 0, 1,
                 0, 0, 1, 1,
                 1, 1, 1, 1,
        @@ -1674,7 +1736,7 @@ suite('p5.RendererGL', function() {
                 0, 1, 0, 1
               ]);
         
        -      assert.deepEqual(renderer.immediateMode.geometry.uvs, [
        +      assert.deepEqual(renderer.shapeBuilder.geometry.uvs, [
                 1, 0,
                 0, 1,
                 0, 0,
        @@ -1682,63 +1744,61 @@ suite('p5.RendererGL', function() {
                 1, 0,
                 1, 1
               ]);
        -
        -      done();
             });
         
        -    test('TESS does not affect stroke colors', function(done) {
        +    test('PATH does not affect stroke colors', function() {
               var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
         
               myp5.textureMode(myp5.NORMAL);
        -      renderer.beginShape(myp5.TESS);
        +      myp5.beginShape(myp5.PATH);
               myp5.noFill();
        -      renderer.stroke(255, 255, 255);
        -      renderer.vertex(-10, -10, 0, 0);
        -      renderer.stroke(255, 0, 0);
        -      renderer.vertex(10, -10, 1, 0);
        -      renderer.stroke(0, 255, 0);
        -      renderer.vertex(10, 10, 1, 1);
        -      renderer.stroke(0, 0, 255);
        -      renderer.vertex(-10, 10, 0, 1);
        -      renderer.endShape(myp5.CLOSE);
        -
        -      // Vertex colors are not run through tessy
        -      assert.deepEqual(renderer.immediateMode.geometry.vertexStrokeColors, [
        +      myp5.stroke(255, 255, 255);
        +      myp5.vertex(-10, -10, 0, 0);
        +      myp5.stroke(255, 0, 0);
        +      myp5.vertex(10, -10, 1, 0);
        +      myp5.stroke(0, 255, 0);
        +      myp5.vertex(10, 10, 1, 1);
        +      myp5.stroke(0, 0, 255);
        +      myp5.vertex(-10, 10, 0, 1);
        +      myp5.endShape(myp5.CLOSE);
        +
        +      // Vertex stroke colors are not run through libtess
        +      assert.deepEqual(renderer.shapeBuilder.geometry.vertexStrokeColors, [
                 1, 1, 1, 1,
                 1, 0, 0, 1,
                 0, 1, 0, 1,
        -        0, 0, 1, 1
        +        0, 0, 1, 1,
        +        1, 1, 1, 1,
               ]);
        -
        -      done();
             });
         
        -    test('TESS does not affect texture coordinates', function(done) {
        +    test('PATH does not affect texture coordinates', function() {
               var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
               const texture = new p5.Image(25, 25);
         
               myp5.textureMode(myp5.IMAGE);
               myp5.texture(texture);
        -      renderer.beginShape(myp5.TESS);
        +      myp5.beginShape(myp5.PATH);
               myp5.noFill();
        -      renderer.vertex(-10, -10, 0, 0);
        -      renderer.vertex(10, -10, 25, 0);
        -      renderer.vertex(10, 10, 25, 25);
        -      renderer.vertex(-10, 10, 0, 25);
        -      renderer.endShape(myp5.CLOSE);
        -
        -      // UVs are correctly translated through tessy
        -      assert.deepEqual(renderer.immediateMode.geometry.uvs, [
        +      myp5.vertex(-10, -10, 0, 0);
        +      myp5.vertex(10, -10, 25, 0);
        +      myp5.vertex(10, 10, 25, 25);
        +      myp5.vertex(-10, 10, 0, 25);
        +      myp5.endShape(myp5.CLOSE);
        +
        +      // UVs are correctly translated through libtess
        +      assert.deepEqual(renderer.shapeBuilder.geometry.uvs, [
        +        1, 0,
        +        0, 1,
                 0, 0,
        +
        +        0, 1,
                 1, 0,
        -        1, 1,
        -        0, 1
        +        1, 1
               ]);
        -
        -      done();
             });
         
        -    test('TESS interpolates vertex data at intersections', function(done) {
        +    test('PATH interpolates vertex data at intersections', function() {
               var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
         
               // Hourglass shape:
        @@ -1752,74 +1812,74 @@ suite('p5.RendererGL', function() {
               //
               // Tessy will add a vertex in the middle
               myp5.textureMode(myp5.NORMAL);
        -      renderer.beginShape(myp5.TESS);
        -      renderer.fill(255, 255, 255);
        -      renderer.normal(-1, -1, 1);
        -      renderer.vertex(-10, -10, 0, 0);
        -      renderer.fill(0, 255, 0);
        -      renderer.normal(1, 1, 1);
        -      renderer.vertex(10, 10, 1, 1);
        -      renderer.fill(255, 0, 0);
        -      renderer.normal(1, -1, 1);
        -      renderer.vertex(10, -10, 1, 0);
        -      renderer.fill(0, 0, 255);
        -      renderer.normal(-1, 1, 1);
        -      renderer.vertex(-10, 10, 0, 1);
        -      renderer.endShape(myp5.CLOSE);
        -
        -      assert.equal(renderer.immediateMode.geometry.vertices.length, 6);
        +      myp5.beginShape(myp5.PATH);
        +      myp5.fill(255, 255, 255);
        +      myp5.normal(-1, -1, 1);
        +      myp5.vertex(-10, -10, 0, 0);
        +      myp5.fill(0, 255, 0);
        +      myp5.normal(1, 1, 1);
        +      myp5.vertex(10, 10, 1, 1);
        +      myp5.fill(255, 0, 0);
        +      myp5.normal(1, -1, 1);
        +      myp5.vertex(10, -10, 1, 0);
        +      myp5.fill(0, 0, 255);
        +      myp5.normal(-1, 1, 1);
        +      myp5.vertex(-10, 10, 0, 1);
        +      myp5.endShape(myp5.CLOSE);
        +
        +      assert.equal(renderer.shapeBuilder.geometry.vertices.length, 6);
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[0].array(),
        +        renderer.shapeBuilder.geometry.vertices[0].array(),
                 [0, 0, 0]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[1].array(),
        +        renderer.shapeBuilder.geometry.vertices[1].array(),
                 [-10, 10, 0]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[2].array(),
        +        renderer.shapeBuilder.geometry.vertices[2].array(),
                 [-10, -10, 0]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[3].array(),
        +        renderer.shapeBuilder.geometry.vertices[3].array(),
                 [10, 10, 0]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[4].array(),
        +        renderer.shapeBuilder.geometry.vertices[4].array(),
                 [0, 0, 0]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[5].array(),
        +        renderer.shapeBuilder.geometry.vertices[5].array(),
                 [10, -10, 0]
               );
         
        -      assert.equal(renderer.immediateMode.geometry.vertexNormals.length, 6);
        +      assert.equal(renderer.shapeBuilder.geometry.vertexNormals.length, 6);
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[0].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[0].array(),
                 [0, 0, 1]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[1].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[1].array(),
                 [-1, 1, 1]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[2].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[2].array(),
                 [-1, -1, 1]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[3].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[3].array(),
                 [1, 1, 1]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[4].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[4].array(),
                 [0, 0, 1]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertexNormals[5].array(),
        +        renderer.shapeBuilder.geometry.vertexNormals[5].array(),
                 [1, -1, 1]
               );
         
        -      assert.deepEqual(renderer.immediateMode.geometry.vertexColors, [
        +      assert.deepEqual(renderer.shapeBuilder.geometry.vertexColors, [
                 0.5, 0.5, 0.5, 1,
                 0, 0, 1, 1,
                 1, 1, 1, 1,
        @@ -1828,7 +1888,7 @@ suite('p5.RendererGL', function() {
                 1, 0, 0, 1
               ]);
         
        -      assert.deepEqual(renderer.immediateMode.geometry.uvs, [
        +      assert.deepEqual(renderer.shapeBuilder.geometry.uvs, [
                 0.5, 0.5,
                 0, 1,
                 0, 0,
        @@ -1836,155 +1896,69 @@ suite('p5.RendererGL', function() {
                 0.5, 0.5,
                 1, 0
               ]);
        -
        -      done();
             });
         
        -    test('TESS handles vertex data perpendicular to the camera', function(done) {
        +    test('PATH handles vertex data perpendicular to the camera', function() {
               var renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
         
               myp5.textureMode(myp5.NORMAL);
        -      renderer.beginShape(myp5.TESS);
        -      renderer.vertex(-10, 0, -10);
        -      renderer.vertex(10, 0, -10);
        -      renderer.vertex(10, 0, 10);
        -      renderer.vertex(-10, 0, 10);
        -      renderer.endShape(myp5.CLOSE);
        -
        -      assert.equal(renderer.immediateMode.geometry.vertices.length, 6);
        +      myp5.beginShape(myp5.PATH);
        +      myp5.vertex(-10, 0, -10);
        +      myp5.vertex(10, 0, -10);
        +      myp5.vertex(10, 0, 10);
        +      myp5.vertex(-10, 0, 10);
        +      myp5.endShape(myp5.CLOSE);
        +
        +      assert.equal(renderer.shapeBuilder.geometry.vertices.length, 6);
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[0].array(),
        +        renderer.shapeBuilder.geometry.vertices[0].array(),
                 [10, 0, 10]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[1].array(),
        +        renderer.shapeBuilder.geometry.vertices[1].array(),
                 [-10, 0, -10]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[2].array(),
        +        renderer.shapeBuilder.geometry.vertices[2].array(),
                 [10, 0, -10]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[3].array(),
        +        renderer.shapeBuilder.geometry.vertices[3].array(),
                 [-10, 0, -10]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[4].array(),
        +        renderer.shapeBuilder.geometry.vertices[4].array(),
                 [10, 0, 10]
               );
               assert.deepEqual(
        -        renderer.immediateMode.geometry.vertices[5].array(),
        +        renderer.shapeBuilder.geometry.vertices[5].array(),
                 [-10, 0, 10]
               );
        -
        -      done();
             });
           });
         
           suite('color interpolation', function() {
        -    test('strokes should interpolate colors between vertices', function(done) {
        +    test('strokes should interpolate colors between vertices', function() {
               const renderer = myp5.createCanvas(512, 4, myp5.WEBGL);
         
               // far left color: (242, 236, 40)
               // far right color: (42, 36, 240)
               // expected middle color: (142, 136, 140)
         
        -      renderer.strokeWeight(4);
        -      renderer.beginShape();
        -      renderer.stroke(242, 236, 40);
        -      renderer.vertex(-256, 0);
        -      renderer.stroke(42, 36, 240);
        -      renderer.vertex(256, 0);
        -      renderer.endShape();
        +      myp5.strokeWeight(4);
        +      myp5.beginShape();
        +      myp5.stroke(242, 236, 40);
        +      myp5.vertex(-256, 0);
        +      myp5.stroke(42, 36, 240);
        +      myp5.vertex(256, 0);
        +      myp5.endShape();
         
               assert.deepEqual(myp5.get(0, 2), [242, 236, 40, 255]);
               assert.deepEqual(myp5.get(256, 2), [142, 136, 140, 255]);
               assert.deepEqual(myp5.get(511, 2), [42, 36, 240, 255]);
        -
        -      done();
             });
         
        -    test('bezierVertex() should interpolate curFillColor', function(done) {
        -      const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
        -
        -      // start color: (255, 255, 255)
        -      // end color: (255, 0, 0)
        -      // Intermediate values are expected to be approximately half the value.
        -
        -      renderer.beginShape();
        -      renderer.fill(255);
        -      renderer.vertex(-128, -128);
        -      renderer.fill(255, 0, 0);
        -      renderer.bezierVertex(128, -128, 128, 128, -128, 128);
        -      renderer.endShape();
        -
        -      assert.deepEqual(myp5.get(128, 127), [255, 129, 129, 255]);
        -
        -      done();
        -    });
        -
        -    test('bezierVertex() should interpolate curStrokeColor', function(done) {
        -      const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
        -
        -      // start color: (255, 255, 255)
        -      // end color: (255, 0, 0)
        -      // Intermediate values are expected to be approximately half the value.
        -
        -      renderer.strokeWeight(5);
        -      renderer.beginShape();
        -      myp5.noFill();
        -      renderer.stroke(255);
        -      renderer.vertex(-128, -128);
        -      renderer.stroke(255, 0, 0);
        -      renderer.bezierVertex(128, -128, 128, 128, -128, 128);
        -      renderer.endShape();
        -
        -      assert.deepEqual(myp5.get(190, 127), [255, 128, 128, 255]);
        -
        -      done();
        -    });
        -
        -    test('quadraticVertex() should interpolate curFillColor', function(done) {
        -      const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
        -
        -      // start color: (255, 255, 255)
        -      // end color: (255, 0, 0)
        -      // Intermediate values are expected to be approximately half the value.
        -
        -      renderer.beginShape();
        -      renderer.fill(255);
        -      renderer.vertex(-128, -128);
        -      renderer.fill(255, 0, 0);
        -      renderer.quadraticVertex(256, 0, -128, 128);
        -      renderer.endShape();
        -
        -      assert.deepEqual(myp5.get(128, 127), [255, 128, 128, 255]);
        -
        -      done();
        -    });
        -
        -    test('quadraticVertex() should interpolate curStrokeColor', function(done) {
        -      const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
        -
        -      // start color: (255, 255, 255)
        -      // end color: (255, 0, 0)
        -      // Intermediate values are expected to be approximately half the value.
        -
        -      renderer.strokeWeight(5);
        -      renderer.beginShape();
        -      myp5.noFill();
        -      renderer.stroke(255);
        -      renderer.vertex(-128, -128);
        -      renderer.stroke(255, 0, 0);
        -      renderer.quadraticVertex(256, 0, -128, 128);
        -      renderer.endShape();
        -
        -      assert.deepEqual(myp5.get(190, 127), [255, 128, 128, 255]);
        -
        -      done();
        -    });
        -
        -    test('geometry without stroke colors use curStrokeColor', function(done) {
        +    test('geometry without stroke colors use curStrokeColor', function() {
               const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
               myp5.background(255);
               myp5.fill(255);
        @@ -1995,10 +1969,9 @@ suite('p5.RendererGL', function() {
         
               assert.equal(renderer._useLineColor, false);
               assert.deepEqual(myp5.get(128, 0), [0, 0, 0, 255]);
        -      done();
             });
         
        -    test('geometry with stroke colors use their colors', function(done) {
        +    test('geometry with stroke colors use their colors', function() {
               const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
               const myGeom = new p5.Geometry(1, 1, function() {
                 this.gid = 'strokeColorTest';
        @@ -2019,7 +1992,7 @@ suite('p5.RendererGL', function() {
                   0, 1, 0, 1
                 );
                 this._edgesToVertices();
        -      });
        +      }, myp5._renderer);
               myp5.background(255);
               myp5.fill(255);
               myp5.strokeWeight(4);
        @@ -2028,10 +2001,9 @@ suite('p5.RendererGL', function() {
         
               assert.equal(renderer._useLineColor, true);
               assert.deepEqual(myp5.get(128, 255), [127, 0, 128, 255]);
        -      done();
             });
         
        -    test('immediate mode uses stroke colors', function(done) {
        +    test('immediate mode uses stroke colors', function() {
               const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
               myp5.background(255);
               myp5.fill(255);
        @@ -2049,12 +2021,11 @@ suite('p5.RendererGL', function() {
         
               assert.equal(renderer._useLineColor, true);
               assert.deepEqual(myp5.get(128, 255), [127, 0, 128, 255]);
        -      done();
             });
           });
         
           suite('interpolation of vertex colors', function(){
        -    test('immediate mode uses vertex colors (noLight)', function(done) {
        +    test('immediate mode uses vertex colors (noLight)', function() {
               const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
         
               // upper color: (200, 0, 0, 255);
        @@ -2074,10 +2045,9 @@ suite('p5.RendererGL', function() {
         
               assert.equal(renderer._useVertexColor, true);
               assert.deepEqual(myp5.get(128, 128), [100, 0, 100, 255]);
        -      done();
             });
         
        -    test('immediate mode uses vertex colors (light)', function(done) {
        +    test('immediate mode uses vertex colors (light)', function() {
               const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
         
               myp5.directionalLight(255, 255, 255, 0, 0, -1);
        @@ -2097,10 +2067,9 @@ suite('p5.RendererGL', function() {
         
               assert.equal(renderer._useVertexColor, true);
               assert.deepEqual(myp5.get(128, 128), [73, 0, 73, 255]);
        -      done();
             });
         
        -    test('geom without vertex colors use curFillCol (noLight)', function(done) {
        +    test('geom without vertex colors use curFillCol (noLight)', function() {
               const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
         
               // expected center color is curFillColor.
        @@ -2111,10 +2080,9 @@ suite('p5.RendererGL', function() {
         
               assert.equal(renderer._useVertexColor, false);
               assert.deepEqual(myp5.get(128, 128), [200, 0, 200, 255]);
        -      done();
             });
         
        -    test('geom without vertex colors use curFillCol (light)', function(done) {
        +    test('geom without vertex colors use curFillCol (light)', function() {
               const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
         
               myp5.directionalLight(255, 255, 255, 0, 0, -1);
        @@ -2127,10 +2095,9 @@ suite('p5.RendererGL', function() {
         
               assert.equal(renderer._useVertexColor, false);
               assert.deepEqual(myp5.get(128, 128), [146, 0, 146, 255]);
        -      done();
             });
         
        -    test('geom with vertex colors use their color (noLight)', function(done) {
        +    test('geom with vertex colors use their color (noLight)', function() {
               const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
         
               // upper color: (200, 0, 0, 255);
        @@ -2159,10 +2126,9 @@ suite('p5.RendererGL', function() {
         
               assert.equal(renderer._useVertexColor, true);
               assert.deepEqual(myp5.get(128, 128), [100, 0, 100, 255]);
        -      done();
             });
         
        -    test('geom with vertex colors use their color (light)', function(done) {
        +    test('geom with vertex colors use their color (light)', function() {
               const renderer = myp5.createCanvas(256, 256, myp5.WEBGL);
         
               const myGeom = new p5.Geometry(1, 1, function() {
        @@ -2190,12 +2156,11 @@ suite('p5.RendererGL', function() {
         
               assert.equal(renderer._useVertexColor, true);
               assert.deepEqual(myp5.get(128, 128), [73, 0, 73, 255]);
        -      done();
             });
           });
         
           suite('Test for register availability', function() {
        -    test('register enable/disable flag test', function(done) {
        +    test('register enable/disable flag test', function() {
               const renderer = myp5.createCanvas(16, 16, myp5.WEBGL);
         
               // geometry without aTexCoord.
        @@ -2208,7 +2173,7 @@ suite('p5.RendererGL', function() {
                 this.faces.push([0, 1, 2]);
                 this.faces.push([0, 2, 3]);
                 this.computeNormals();
        -      });
        +      }, myp5._renderer);
         
               myp5.fill(255);
               myp5.noStroke();
        @@ -2228,19 +2193,16 @@ suite('p5.RendererGL', function() {
         
               myp5.triangle(-8, -8, 8, 8, -8, 8);
               assert.equal(renderer.registerEnabled.has(loc), true);
        -
        -      done();
             });
           });
         
           suite('setAttributes', function() {
        -    test('It leaves a reference to the correct canvas', function(done) {
        +    test('It leaves a reference to the correct canvas', function() {
               const renderer = myp5.createCanvas(10, 10, myp5.WEBGL);
               assert.equal(myp5.canvas, renderer.canvas);
         
               myp5.setAttributes({ alpha: true });
               assert.equal(myp5.canvas, renderer.canvas);
        -      done();
             });
           });
         
        @@ -2357,6 +2319,7 @@ suite('p5.RendererGL', function() {
                 myp5.clip(() => myp5.rect(10, 10, 30, 30));
                 myp5.fill('red');
                 myp5.rect(5, 5, 40, 40);
        +        // console.log(myp5._renderer.canvas.toDataURL());
               };
               const pixels = getClippedPixels(myp5.WEBGL, mask);
         
        @@ -2459,6 +2422,7 @@ suite('p5.RendererGL', function() {
               'It can mask a separate shape in a framebuffer from the main canvas',
               function() {
                 myp5.createCanvas(50, 50, myp5.WEBGL);
        +        myp5.noStroke();
                 const fbo = myp5.createFramebuffer({ antialias: false });
                 myp5.rectMode(myp5.CENTER);
                 myp5.background('red');
        @@ -2544,4 +2508,181 @@ suite('p5.RendererGL', function() {
               }
             );
           });
        +
        +  suite('vertexProperty()', function() {
        +    test('Immediate mode data and buffers created in beginShape',
        +      function() {
        +        myp5.createCanvas(50, 50, myp5.WEBGL);
        +
        +        myp5.noStroke();
        +        myp5.beginShape();
        +        myp5.vertexProperty('aCustom', 1);
        +        myp5.vertexProperty('aCustomVec3', [1, 2, 3]);
        +        myp5.vertex(0,0,0);
        +        myp5.vertex(0,1,0);
        +        myp5.vertex(1,1,0);
        +        myp5.endShape();
        +
        +        expect(myp5._renderer.shapeBuilder.geometry.userVertexProperties.aCustom).to.containSubset({
        +          name: 'aCustom',
        +          currentData: [1],
        +          dataSize: 1
        +        });
        +        expect(myp5._renderer.shapeBuilder.geometry.userVertexProperties.aCustomVec3).to.containSubset({
        +          name: 'aCustomVec3',
        +          currentData: [1, 2, 3],
        +          dataSize: 3
        +        });
        +      }
        +    );
        +    test('Immediate mode data and buffers deleted after beginShape',
        +      function() {
        +        myp5.createCanvas(50, 50, myp5.WEBGL);
        +
        +        myp5.beginShape();
        +        myp5.vertexProperty('aCustom', 1);
        +        myp5.vertexProperty('aCustomVec3', [1,2,3]);
        +        myp5.vertex(0,0,0);
        +        myp5.endShape();
        +
        +        myp5.beginShape();
        +        myp5.endShape();
        +        assert.isUndefined(myp5._renderer.shapeBuilder.geometry.aCustomSrc);
        +        assert.isUndefined(myp5._renderer.shapeBuilder.geometry.aCustomVec3Src);
        +        assert.deepEqual(myp5._renderer.shapeBuilder.geometry.userVertexProperties, {});
        +        assert.deepEqual(myp5._renderer.buffers.user, []);
        +      }
        +    );
        +    test('Data copied over from beginGeometry',
        +      function() {
        +        myp5.createCanvas(50, 50, myp5.WEBGL);
        +        const myGeo = myp5.buildGeometry(() => {
        +          myp5.beginShape();
        +          myp5.vertexProperty('aCustom', 1);
        +          myp5.vertexProperty('aCustomVec3', [1,2,3]);
        +          myp5.vertex(0,1,0);
        +          myp5.vertex(-1,0,0);
        +          myp5.vertex(1,0,0);
        +          myp5.endShape();
        +        });
        +        assert.deepEqual(myGeo.aCustomSrc, [1,1,1]);
        +        assert.deepEqual(myGeo.aCustomVec3Src, [1,2,3,1,2,3,1,2,3]);
        +      }
        +    );
        +    test('Retained mode buffers are created for rendering',
        +      function() {
        +        myp5.createCanvas(50, 50, myp5.WEBGL);
        +
        +        const prevDrawFills = myp5._renderer._drawFills;
        +        let called = false;
        +        myp5._renderer._drawFills = function(...args) {
        +          called = true;
        +          expect(myp5._renderer.buffers.user).to.containSubset([
        +            {
        +              size: 1,
        +              src: 'aCustomSrc',
        +              dst: 'aCustomBuffer',
        +              attr: 'aCustom',
        +            },
        +            {
        +              size: 3,
        +              src: 'aCustomVec3Src',
        +              dst: 'aCustomVec3Buffer',
        +              attr: 'aCustomVec3',
        +            }
        +          ]);
        +
        +          prevDrawFills.apply(this, args);
        +        }
        +
        +        try {
        +          const myGeo = myp5.buildGeometry(() => {
        +            myp5.beginShape();
        +            myp5.vertexProperty('aCustom', 1);
        +            myp5.vertexProperty('aCustomVec3', [1,2,3]);
        +            myp5.vertex(0,0,0);
        +            myp5.vertex(1,0,0);
        +            myp5.vertex(1,1,0);
        +            myp5.endShape();
        +          });
        +          myp5.model(myGeo);
        +          expect(called).to.equal(true);
        +        } finally {
        +          myp5._renderer._drawFills = prevDrawFills;
        +        }
        +      }
        +    );
        +    test('Retained mode buffers deleted after rendering',
        +      function() {
        +        myp5.createCanvas(50, 50, myp5.WEBGL);
        +        const myGeo = myp5.buildGeometry(() => {
        +          myp5.beginShape();
        +          myp5.vertexProperty('aCustom', 1);
        +          myp5.vertexProperty('aCustomVec3', [1,2,3]);
        +          myp5.vertex(0,0,0);
        +          myp5.vertex(1,0,0);
        +          myp5.vertex(1,1,0);
        +          myp5.endShape();
        +        });
        +        myp5.model(myGeo);
        +        assert.equal(myp5._renderer.buffers.user.length, 0);
        +      }
        +    );
        +    test.skip('Friendly error if different sizes used',
        +      function() {
        +        myp5.createCanvas(50, 50, myp5.WEBGL);
        +        const logs = [];
        +        const myLog = (...data) => logs.push(data.join(', '));
        +        const oldLog = console.log;
        +        console.log = myLog;
        +        myp5.beginShape();
        +        myp5.vertexProperty('aCustom', [1,2,3]);
        +        myp5.vertex(0,0,0);
        +        myp5.vertexProperty('aCustom', [1,2]);
        +        myp5.vertex(1,0,0);
        +        myp5.endShape();
        +        console.log = oldLog;
        +        expect(logs.join('\n')).to.match(/Custom vertex property 'aCustom' has been set with various data sizes/);
        +      }
        +    );
        +    test.skip('Friendly error too many values set',
        +      function() {
        +        myp5.createCanvas(50, 50, myp5.WEBGL);
        +        const logs = [];
        +        const myLog = (...data) => logs.push(data.join(', '));
        +        const oldLog = console.log;
        +        console.log = myLog;
        +        let myGeo = new p5.Geometry();
        +        myGeo.gid = 'myGeo';
        +        myGeo.vertices.push(new myp5.createVector(0,0,0));
        +        myGeo.vertices.push(new myp5.createVector(1,0,0));
        +        myGeo.vertices.push(new myp5.createVector(1,1,0));
        +        myGeo.vertexProperty('aCustom', 1);
        +        myGeo.vertexProperty('aCustom', 2);
        +        myGeo.vertexProperty('aCustom', 3);
        +        myGeo.vertexProperty('aCustom', 4);
        +        myp5.model(myGeo);
        +        console.log = oldLog;
        +        expect(logs.join('\n')).to.match(/One of the geometries has a custom vertex property 'aCustom' with more values than vertices./);
        +      }
        +    );
        +    test.skip('Friendly error if too few values set',
        +      function() {
        +        myp5.createCanvas(50, 50, myp5.WEBGL);
        +        const logs = [];
        +        const myLog = (...data) => logs.push(data.join(', '));
        +        const oldLog = console.log;
        +        console.log = myLog;
        +        let myGeo = new p5.Geometry();
        +        myGeo.gid = 'myGeo';
        +        myGeo.vertices.push(new myp5.createVector(0,0,0));
        +        myGeo.vertices.push(new myp5.createVector(1,0,0));
        +        myGeo.vertices.push(new myp5.createVector(1,1,0));
        +        myGeo.vertexProperty('aCustom', 1);
        +        myp5.model(myGeo);
        +        console.log = oldLog;
        +        expect(logs.join('\n')).to.match(/One of the geometries has a custom vertex property 'aCustom' with fewer values than vertices./);
        +      }
        +    );
        +  })
         });
        diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js
        index 044bf6ec0a..86e79b4615 100644
        --- a/test/unit/webgl/p5.Shader.js
        +++ b/test/unit/webgl/p5.Shader.js
        @@ -1,12 +1,9 @@
        +import p5 from '../../../src/app.js';
        +
         suite('p5.Shader', function() {
           var myp5;
         
        -  if (!window.Modernizr.webgl) {
        -    //assert(false, 'could not run gl tests');
        -    return;
        -  }
        -
        -  setup(function() {
        +  beforeAll(function() {
             myp5 = new p5(function(p) {
               p.setup = function() {
                 p.createCanvas(100, 100, p.WEBGL);
        @@ -58,7 +55,7 @@ suite('p5.Shader', function() {
             shaderObj.unbindShader();
           };
         
        -  teardown(function() {
        +  afterAll(function() {
             myp5.remove();
           });
         
        @@ -126,13 +123,11 @@ suite('p5.Shader', function() {
               var expectedUniforms = [
                 'uModelViewMatrix',
                 'uProjectionMatrix',
        -        /*'uResolution',*/
        -        'uPointSize'
               ];
         
               testShader(
                 'Immediate Mode Shader',
        -        myp5._renderer._getImmediateModeShader(),
        +        myp5._renderer._getColorShader(),
                 expectedAttributes,
                 expectedUniforms
               );
        @@ -157,9 +152,9 @@ suite('p5.Shader', function() {
               myp5.fill(0);
               var retainedColorShader = myp5._renderer._getColorShader();
               var texLightShader = myp5._renderer._getLightShader();
        -      var immediateColorShader = myp5._renderer._getImmediateModeShader();
        -      var selectedRetainedShader = myp5._renderer._getRetainedFillShader();
        -      var selectedImmediateShader = myp5._renderer._getImmediateFillShader();
        +      var immediateColorShader = myp5._renderer._getColorShader();
        +      var selectedRetainedShader = myp5._renderer._getFillShader();
        +      var selectedImmediateShader = myp5._renderer._getFillShader();
         
               // both color and light shader are valid, depending on
               // conditions set earlier.
        @@ -177,8 +172,8 @@ suite('p5.Shader', function() {
             test('Normal Shader is set after normalMaterial()', function() {
               myp5.normalMaterial();
               var normalShader = myp5._renderer._getNormalShader();
        -      var selectedRetainedShader = myp5._renderer._getRetainedFillShader();
        -      var selectedImmediateShader = myp5._renderer._getRetainedFillShader();
        +      var selectedRetainedShader = myp5._renderer._getFillShader();
        +      var selectedImmediateShader = myp5._renderer._getFillShader();
               assert(
                 normalShader === selectedRetainedShader,
                 "_renderer's retain mode shader was not normal shader"
        @@ -191,8 +186,8 @@ suite('p5.Shader', function() {
             test('Light shader set after ambientMaterial()', function() {
               var lightShader = myp5._renderer._getLightShader();
               myp5.ambientMaterial(128);
        -      var selectedRetainedShader = myp5._renderer._getRetainedFillShader();
        -      var selectedImmediateShader = myp5._renderer._getImmediateFillShader();
        +      var selectedRetainedShader = myp5._renderer._getFillShader();
        +      var selectedImmediateShader = myp5._renderer._getFillShader();
               assert(
                 lightShader === selectedRetainedShader,
                 "_renderer's retain mode shader was not light shader " +
        @@ -207,8 +202,8 @@ suite('p5.Shader', function() {
             test('Light shader set after specularMaterial()', function() {
               var lightShader = myp5._renderer._getLightShader();
               myp5.specularMaterial(128);
        -      var selectedRetainedShader = myp5._renderer._getRetainedFillShader();
        -      var selectedImmediateShader = myp5._renderer._getImmediateFillShader();
        +      var selectedRetainedShader = myp5._renderer._getFillShader();
        +      var selectedImmediateShader = myp5._renderer._getFillShader();
               assert(
                 lightShader === selectedRetainedShader,
                 "_renderer's retain mode shader was not light shader " +
        @@ -223,8 +218,8 @@ suite('p5.Shader', function() {
             test('Light shader set after emissiveMaterial()', function() {
               var lightShader = myp5._renderer._getLightShader();
               myp5.emissiveMaterial(128);
        -      var selectedRetainedShader = myp5._renderer._getRetainedFillShader();
        -      var selectedImmediateShader = myp5._renderer._getImmediateFillShader();
        +      var selectedRetainedShader = myp5._renderer._getFillShader();
        +      var selectedImmediateShader = myp5._renderer._getFillShader();
               assert(
                 lightShader === selectedRetainedShader,
                 "_renderer's retain mode shader was not light shader " +
        @@ -239,80 +234,32 @@ suite('p5.Shader', function() {
         
             test('Able to setUniform empty arrays', function() {
               myp5.shader(myp5._renderer._getLightShader());
        -      var s = myp5._renderer.userFillShader;
        +      var s = myp5._renderer.states.userFillShader;
         
               s.setUniform('uMaterialColor', []);
               s.setUniform('uLightingDirection', []);
             });
         
             test('Able to set shininess', function() {
        -      assert.deepEqual(myp5._renderer._useShininess, 1);
        +      assert.deepEqual(myp5._renderer.states._useShininess, 1);
               myp5.shininess(50);
        -      assert.deepEqual(myp5._renderer._useShininess, 50);
        +      assert.deepEqual(myp5._renderer.states._useShininess, 50);
             });
         
             test('Shader is reset after resetShader is called', function() {
               myp5.shader(myp5._renderer._getColorShader());
        -      var prevShader = myp5._renderer.userFillShader;
        +      var prevShader = myp5._renderer.states.userFillShader;
               assert.isTrue(prevShader !== null);
         
               myp5.resetShader();
        -      var curShader = myp5._renderer.userFillShader;
        +      var curShader = myp5._renderer.states.userFillShader;
               assert.isTrue(curShader === null);
             });
         
        -    test('isTextureShader returns true if there is a sampler', function() {
        -      var s = myp5._renderer._getLightShader();
        -      myp5.shader(s);
        -      assert.isTrue(s.isTextureShader());
        -    });
        -
        -    test('isTextureShader returns false if there is no sampler', function() {
        -      var s = myp5._renderer._getColorShader();
        -      myp5.shader(s);
        -      assert.isFalse(s.isTextureShader());
        -    });
        -
        -    test('isLightShader returns true if there are lighting uniforms', function() {
        -      var s = myp5._renderer._getLightShader();
        -      myp5.shader(s);
        -      assert.isTrue(s.isLightShader());
        -    });
        -
        -    test('isLightShader returns false if there are no lighting uniforms', function() {
        -      var s = myp5._renderer._getPointShader();
        -      myp5.shader(s);
        -      assert.isFalse(s.isLightShader());
        -    });
        -
        -    test('isNormalShader returns true if there is a normal attribute', function() {
        -      var s = myp5._renderer._getNormalShader();
        -      myp5.shader(s);
        -      assert.isTrue(s.isNormalShader());
        -    });
        -
        -    test('isNormalShader returns false if there is no normal attribute', function() {
        -      var s = myp5._renderer._getPointShader();
        -      myp5.shader(s);
        -      assert.isFalse(s.isNormalShader());
        -    });
        -
        -    test('isStrokeShader returns true if there is a stroke weight uniform', function() {
        -      var s = myp5._renderer._getLineShader();
        -      myp5.shader(s);
        -      assert.isTrue(s.isStrokeShader());
        -    });
        -
        -    test('isStrokeShader returns false if there is no stroke weight uniform', function() {
        -      var s = myp5._renderer._getLightShader();
        -      myp5.shader(s);
        -      assert.isFalse(s.isStrokeShader());
        -    });
        -
             suite('Hooks', function() {
               let myShader;
         
        -      setup(function() {
        +      beforeEach(function() {
                 myShader = myp5.createShader(
                   `
                     precision highp float;
        diff --git a/test/unit/webgl/p5.Texture.js b/test/unit/webgl/p5.Texture.js
        index e2d3a0bf3f..80512f0e49 100644
        --- a/test/unit/webgl/p5.Texture.js
        +++ b/test/unit/webgl/p5.Texture.js
        @@ -1,3 +1,5 @@
        +import p5 from '../../../src/app.js';
        +
         suite('p5.Texture', function() {
           var myp5;
           var texImg1;
        @@ -6,63 +8,59 @@ suite('p5.Texture', function() {
           var imgElementNotPowerOfTwo;
           var imgElementPowerOfTwo;
           var canvas;
        +  let prevPixelRatio;
         
        -  if (!window.Modernizr.webgl) {
        -    //assert(false, 'could not run gl tests');
        -    return;
        -  }
        -
        -  setup(function(done) {
        -    myp5 = new p5(function(p) {
        -      p.preload = function() {
        -        // texImg2 must have powerOfTwo dimensions
        -        texImg2 = p.loadImage('unit/assets/target.gif');
        -        // texImg3 must NOT have powerOfTwo dimensions
        -        texImg3 = p.loadImage('unit/assets/nyan_cat.gif');
        -        // texture object isn't created until it's used for something:
        -        //p.box(70, 70, 70);
        -      };
        -      p.setup = function() {
        -        canvas = p.createCanvas(100, 100, p.WEBGL);
        -        texImg1 = p.createGraphics(2, 2, p.WEBGL);
        -        new Promise(resolve => {
        -          p.createImg(texImg2.canvas.toDataURL(), '', 'anonymous', el => {
        -            el.size(50, 50);
        -            imgElementPowerOfTwo = el;
        -            p.texture(imgElementPowerOfTwo);
        -            resolve();
        -          });
        -        }).then(() => new Promise(resolve => {
        -          p.createImg(texImg3.canvas.toDataURL(), '', 'anonymous', el => {
        -            el.size(50, 50);
        -            imgElementNotPowerOfTwo = el;
        -            p.texture(imgElementNotPowerOfTwo);
        -            resolve();
        +  beforeEach(function() {
        +    prevPixelRatio = window.devicePixelRatio;
        +    window.devicePixelRatio = 1;
        +    return new Promise(done => {
        +      myp5 = new p5(function(p) {
        +        p.setup = async function() {
        +          canvas = p.createCanvas(100, 100, p.WEBGL);
        +          p.pixelDensity(1);
        +          texImg1 = p.createGraphics(2, 2, p.WEBGL);
        +          texImg2 = await p.loadImage('/unit/assets/target.gif');
        +          texImg3 = await p.loadImage('/unit/assets/nyan_cat.gif');
        +          new Promise(resolve => {
        +            p.createImg(texImg2.canvas.toDataURL(), '', 'anonymous', el => {
        +              el.size(50, 50);
        +              imgElementPowerOfTwo = el;
        +              p.texture(imgElementPowerOfTwo);
        +              resolve();
        +            });
        +          }).then(() => new Promise(resolve => {
        +            p.createImg(texImg3.canvas.toDataURL(), '', 'anonymous', el => {
        +              el.size(50, 50);
        +              imgElementNotPowerOfTwo = el;
        +              p.texture(imgElementNotPowerOfTwo);
        +              resolve();
        +            });
        +          })).then(() => {
        +            p.texture(texImg1);
        +            done();
                   });
        -        })).then(() => {
        -          p.texture(texImg1);
        -          done();
        -        });
        -      };
        +        };
        +      });
             });
           });
         
        -  teardown(function() {
        +  afterEach(function() {
        +    window.devicePixelRatio = prevPixelRatio;
             myp5.remove();
           });
         
           var testTextureSet = function(src) {
        -    test('Light shader set after texture()', function() {
        -      var lightShader = myp5._renderer._getLightShader();
        -      var selectedShader = myp5._renderer._getRetainedFillShader();
        -      assert(
        -        lightShader === selectedShader,
        -        "_renderer's retain mode shader was not light shader " +
        -          'after call to texture()'
        -      );
        -    });
        -
        +    var lightShader = myp5._renderer._getLightShader();
        +    var selectedShader = myp5._renderer._getFillShader();
        +    console.log('first');
        +    assert(
        +      lightShader === selectedShader,
        +      "_renderer's retain mode shader was not light shader " +
        +        'after call to texture()'
        +    );
        +    console.log('second');
             var tex = myp5._renderer.getTexture(src);
        +    console.log('third');
             assert(tex !== undefined, 'texture was undefined');
             assert(tex instanceof p5.Texture, 'texture was not a p5.Texture object');
             assert(tex.src === src, 'texture did not have expected image as source');
        @@ -135,11 +133,11 @@ suite('p5.Texture', function() {
             );
             test('Set textureMode to NORMAL', function() {
               myp5.textureMode(myp5.NORMAL);
        -      assert.deepEqual(myp5._renderer.textureMode, myp5.NORMAL);
        +      assert.deepEqual(myp5._renderer.states.textureMode, myp5.NORMAL);
             });
             test('Set textureMode to IMAGE', function() {
               myp5.textureMode(myp5.IMAGE);
        -      assert.deepEqual(myp5._renderer.textureMode, myp5.IMAGE);
        +      assert.deepEqual(myp5._renderer.states.textureMode, myp5.IMAGE);
             });
             test('Set global wrap mode to clamp', function() {
               myp5.textureWrap(myp5.CLAMP);
        diff --git a/test/visual.html b/test/visual.html
        deleted file mode 100644
        index 0e25bdf88a..0000000000
        --- a/test/visual.html
        +++ /dev/null
        @@ -1,16 +0,0 @@
        -<!DOCTYPE html>
        -<html>
        -  <head>
        -    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
        -    <title>p5.js Visual Test Runner</title>
        -    <link rel="stylesheet" type="text/css" href="visual/style.css" />
        -  </head>
        -  <body>
        -    <h1>p5.js Visual Test Runner</h1>
        -    <p id="metrics"></p>
        -    <script src="../../lib/p5.js" type="text/javascript"></script>
        -    <script src="unit/visual/visualTest.js" type="text/javascript"></script>
        -    <script src="visual/visualTestRunner.js" type="text/javascript"></script>
        -    <script src="visual/visualTestList.js" type="text/javascript"></script>
        -  </body>
        -</html>
        diff --git a/test/visual/style.css b/test/visual/style.css
        deleted file mode 100644
        index 3b087f3db1..0000000000
        --- a/test/visual/style.css
        +++ /dev/null
        @@ -1,45 +0,0 @@
        -body {
        -  font-family: sans-serif;
        -}
        -
        -h4 {
        -  font-weight: normal;
        -  margin-bottom: 10px;
        -  margin-top: 0;
        -}
        -
        -#metrics {
        -  color: #777;
        -  text-decoration: italic;
        -}
        -
        -.suite {
        -  padding-left: 10px;
        -  border-left: 2px solid rgba(0,0,0,0.2);
        -  margin-bottom: 30px;
        -}
        -.skipped {
        -  opacity: 0.5;
        -}
        -.suite.focused {
        -  border-left-color: #2B2;
        -}
        -.suite.failed {
        -  border-left-color: #F00;
        -}
        -
        -.failed {
        -  color: #F00;
        -}
        -
        -.screenshot img {
        -  border: 2px solid #000;
        -  margin-right: 5px;
        -}
        -.screenshot.failed img {
        -  border-color: #F00;
        -}
        -
        -.diff {
        -  background: #000;
        -}
        diff --git a/test/visual/visualTestList.js b/test/visual/visualTestList.js
        deleted file mode 100644
        index f21c1159cc..0000000000
        --- a/test/visual/visualTestList.js
        +++ /dev/null
        @@ -1,8 +0,0 @@
        -// List all visual test files here that should be manually run
        -const visualTestList = ['webgl', 'typography', 'shape_modes'];
        -
        -for (const file of visualTestList) {
        -  document.write(
        -    `<script src="unit/visual/cases/${file}.js" type="text/javascript"></script>`
        -  );
        -}
        diff --git a/test/visual/visualTestRunner.js b/test/visual/visualTestRunner.js
        deleted file mode 100644
        index ec7106b7ff..0000000000
        --- a/test/visual/visualTestRunner.js
        +++ /dev/null
        @@ -1,125 +0,0 @@
        -let parentEl = document.body;
        -let skipping = false;
        -let setups = [];
        -let teardowns = [];
        -const tests = [];
        -
        -window.devicePixelRatio = 1;
        -
        -// Force default antialiasing to match Chrome in puppeteer
        -const origSetAttributeDefaults = p5.RendererGL.prototype._setAttributeDefaults;
        -p5.RendererGL.prototype._setAttributeDefaults = function(pInst) {
        -  origSetAttributeDefaults(pInst);
        -  pInst._glAttributes = Object.assign({}, pInst._glAttributes);
        -  pInst._glAttributes.antialias = true;
        -};
        -
        -window.suite = function(name, callback) {
        -  const prevSetups = setups;
        -  const prevTeardowns = teardowns;
        -  const prevParent = parentEl;
        -  const suiteEl = document.createElement('div');
        -  suiteEl.classList.add('suite');
        -  const title = document.createElement('h4');
        -  title.innerText = decodeURIComponent(name);
        -  suiteEl.appendChild(title);
        -  parentEl.appendChild(suiteEl);
        -
        -  parentEl = suiteEl;
        -  setups = [...setups];
        -  teardowns = [...teardowns];
        -  callback();
        -
        -  parentEl = prevParent;
        -  setups = prevSetups;
        -  teardowns = prevTeardowns;
        -  return suiteEl;
        -};
        -window.suite.skip = function(name, callback) {
        -  const prevSkipping = skipping;
        -  skipping = true;
        -  const el = window.suite(name, callback);
        -  el.classList.add('skipped');
        -  skipping = prevSkipping;
        -};
        -window.suite.only = function(name, callback) {
        -  const el = window.suite(name, callback);
        -  el.classList.add('focused');
        -};
        -
        -window.setup = function(cb) {
        -  if (!cb) return;
        -  setups.push(cb);
        -};
        -
        -window.teardown = function(cb) {
        -  if (!cb) return;
        -  teardowns.push(cb);
        -};
        -
        -window.test = function(_name, callback) {
        -  const testEl = document.createElement('div');
        -  testEl.classList.add('test');
        -  parentEl.appendChild(testEl);
        -  const currentParent = parentEl;
        -  const testSetups = setups;
        -  const testTeardowns = teardowns;
        -  if (!skipping) {
        -    tests.push(async function() {
        -      const prevCheckMatch = window.checkMatch;
        -      window.checkMatch = function(actual, expected, p5) {
        -        let { ok, diff } = prevCheckMatch(actual, expected, p5);
        -
        -        const screenshot = document.createElement('div');
        -        screenshot.classList.add('screenshot');
        -        const actualPreview = document.createElement('img');
        -        actualPreview.setAttribute('src', actual.canvas.toDataURL());
        -        actualPreview.setAttribute('title', 'Received');
        -        const expectedPreview = document.createElement('img');
        -        expectedPreview.setAttribute('src', expected.canvas.toDataURL());
        -        expectedPreview.setAttribute('title', 'Expected');
        -        const diffPreview = document.createElement('img');
        -        diffPreview.setAttribute('src', diff.canvas.toDataURL());
        -        diffPreview.setAttribute('title', 'Difference');
        -        diffPreview.classList.add('diff');
        -        screenshot.appendChild(actualPreview);
        -        screenshot.appendChild(expectedPreview);
        -        screenshot.appendChild(diffPreview);
        -        if (!ok) {
        -          screenshot.classList.add('failed');
        -          currentParent.classList.add('failed');
        -        }
        -        testEl.appendChild(screenshot);
        -        return { ok, diff };
        -      };
        -      try {
        -        for (const setup of testSetups) {
        -          await setup();
        -        }
        -        await callback();
        -      } catch (e) {
        -        if (!(e instanceof ScreenshotError)) {
        -          const p = document.createElement('p');
        -          p.innerText = e.toString();
        -          testEl.appendChild(p);
        -        }
        -        testEl.classList.add('failed');
        -      }
        -      for (const teardown of testTeardowns) {
        -        await teardown();
        -      }
        -      window.checkMatch = prevCheckMatch;
        -    });
        -  }
        -};
        -
        -window.addEventListener('load', async function() {
        -  for (const test of tests) {
        -    await test();
        -  }
        -
        -  const numTotal = document.querySelectorAll('.test').length;
        -  const numFailed = document.querySelectorAll('.test.failed').length;
        -  document.getElementById('metrics').innerHTML =
        -    `${numTotal - numFailed} passed out of ${numTotal}`;
        -});
        diff --git a/translations/dev.js b/translations/dev.js
        index ee1bb50074..78859c438e 100644
        --- a/translations/dev.js
        +++ b/translations/dev.js
        @@ -5,7 +5,7 @@ export { default as zh_translation } from './zh/translation';
         export { default as hi_translation } from './hi/translation';
         export { default as ja_translation } from './ja/translation';
         
        -/**
        +/*
          * When adding a new language, add a new "export" statement above this.
          * For example, if we were to add fr ( French ), we would write:
          * export { default as fr_translation } from './fr/translation';
        diff --git a/translations/index.js b/translations/index.js
        index 2e0aee4ab2..f4170ee096 100644
        --- a/translations/index.js
        +++ b/translations/index.js
        @@ -3,7 +3,7 @@ import en from './en/translation';
         // Only one language is imported above. This is intentional as other languages
         // will be hosted online and then downloaded whenever needed
         
        -/**
        +/*
          * Here, we define a default/fallback language which we can use without internet.
          * You won't have to change this when adding a new language.
          *
        @@ -15,11 +15,12 @@ export default {
           }
         };
         
        -/**
        +/*
          * This is a list of languages that we have added so far.
          * If you have just added a new language (yay!), add its key to the list below
          * (`en` is english, `es` es español). Also add its export to
          * dev.js, which is another file in this folder.
        + * @private
          */
         export const languages = [
           'en',
        diff --git a/utils/convert.js b/utils/convert.js
        new file mode 100644
        index 0000000000..a92049298a
        --- /dev/null
        +++ b/utils/convert.js
        @@ -0,0 +1,604 @@
        +const fs = require('fs');
        +const path = require('path');
        +
        +const data = JSON.parse(fs.readFileSync(path.join(__dirname, '../docs/data.json')));
        +
        +function getEntries(entry) {
        +  return [
        +    entry,
        +    ...getAllEntries(entry.members.global),
        +    ...getAllEntries(entry.members.inner),
        +    ...getAllEntries(entry.members.instance),
        +    ...getAllEntries(entry.members.events),
        +    ...getAllEntries(entry.members.static)
        +  ];
        +}
        +
        +function getAllEntries(arr) {
        +  return arr.flatMap(getEntries);
        +}
        +
        +const allData = getAllEntries(data);
        +
        +const converted = {
        +  project: {}, // Unimplemented, probably not needed
        +  files: {}, // Unimplemented, probably not needed
        +  modules: {},
        +  classes: {},
        +  classitems: [],
        +  warnings: [], // Intentionally unimplemented
        +  consts: {}
        +};
        +
        +function descriptionString(node, parent) {
        +  if (!node) {
        +    return '';
        +  } else if (node.type === 'text') {
        +    return node.value;
        +  } else if (node.type === 'paragraph') {
        +    const content = node.children.map(n => descriptionString(n, node)).join('');
        +    if (parent && parent.children.length === 1) return content;
        +    return '<p>' + content + '</p>\n';
        +  } else if (node.type === 'inlineCode') {
        +    return '<code>' + node.value + '</code>';
        +  } else if (node.type === 'list') {
        +    const tag = node.type === 'ordered' ? 'ol' : 'ul';
        +    return `<${tag}>` + node.children.map(n => descriptionString(n, node)).join('') + `</${tag}>`;
        +  } else if (node.type === 'listItem') {
        +    return '<li>' + node.children.map(n => descriptionString(n, node)).join('') + '</li>';
        +  } else if (node.value) {
        +    return node.value;
        +  } else if (node.children) {
        +    return node.children.map(n => descriptionString(n, node)).join('');
        +  } else {
        +    return '';
        +  }
        +}
        +
        +function typeObject(node) {
        +  if (!node) return {};
        +
        +  if (node.type === 'OptionalType') {
        +    return { optional: 1, ...typeObject(node.expression) };
        +  } else if (node.type === 'UnionType') {
        +    const names = node.elements.map(n => typeObject(n).type);
        +    return {
        +      type: names.join('|')
        +    };
        +  } else if (node.type === 'TypeApplication') {
        +    const { type: typeName } = typeObject(node.expression);
        +    if (
        +      typeName === 'Array' &&
        +      node.applications.length === 1
        +    ) {
        +      return {
        +        type: `${typeObject(node.applications[0]).type}[]`
        +      };
        +    }
        +    const args = node.applications.map(n => typeObject(n).type);
        +    return {
        +      type: `${typeName}<${args.join(', ')}>`
        +    };
        +  } else if (node.type === 'UndefinedLiteral') {
        +    return { type: 'undefined' };
        +  } else if (node.type === 'FunctionType') {
        +    let signature = `function(${node.params.map(p => typeObject(p).type).join(', ')})`;
        +    if (node.result) {
        +      signature += `: ${typeObject(node.result).type}`;
        +    }
        +    return { type: signature };
        +  } else if (node.type === 'ArrayType') {
        +    return { type: `[${node.elements.map(e => typeObject(e).type).join(', ')}]` };
        +  } else {
        +    // TODO
        +    // - handle record types
        +    return { type: node.name };
        +  }
        +}
        +
        +const constUsage = {};
        +function registerConstantUsage(name, memberof, node) {
        +  if (!node) return;
        +  if (node.type === 'OptionalType') {
        +    registerConstantUsage(name, memberof, node.expression);
        +  } else if (node.type === 'UnionType') {
        +    for (const element of node.elements) {
        +      registerConstantUsage(name, memberof, element);
        +    }
        +  } else if (node.type === 'TypeApplication') {
        +    registerConstantUsage(name, memberof, node.expression);
        +    for (const element of node.applications) {
        +      registerConstantUsage(name, memberof, element);
        +    }
        +  } else if (node.type === 'NameExpression') {
        +    const constant = constUsage[node.name];
        +    if (constant) {
        +      constant.add(`${memberof}.${name}`);
        +    }
        +  }
        +}
        +
        +function locationInfo(node) {
        +  return {
        +    file: node.context.file.slice(node.context.file.indexOf('src/')),
        +    line: node.context.loc.start.line
        +  };
        +}
        +
        +function getExample(node) {
        +  return node.description;
        +}
        +
        +function getAlt(node) {
        +  return node
        +    .tags
        +    .filter(tag => tag.title === 'alt')
        +    .map(tag => tag.description)
        +    .join('\n') || undefined;
        +}
        +
        +// ============================================================================
        +// Modules
        +// ============================================================================
        +const fileModuleInfo = {};
        +const modules = {};
        +const submodules = {};
        +for (const entry of allData) {
        +  if (entry.tags.some(tag => tag.title === 'module')) {
        +    const module = entry.tags.find(tag => tag.title === 'module').name;
        +
        +    const submoduleTag = entry.tags.find(tag => tag.title === 'submodule');
        +    const submodule = submoduleTag ? submoduleTag.description : undefined;
        +
        +    // TODO handle methods in classes that don't have this
        +    const forTag = entry.tags.find(tag => tag.title === 'for');
        +    const forEntry = forTag ? forTag.description : undefined;
        +
        +    const file = entry.context.file;
        +
        +    // Record what module/submodule each file is attached to so that we can
        +    // look this info up for each method based on its file
        +    fileModuleInfo[file] = fileModuleInfo[file] || {
        +      module: undefined,
        +      submodule: undefined,
        +      for: undefined
        +    };
        +    fileModuleInfo[file].module = module;
        +    fileModuleInfo[file].submodule =
        +      fileModuleInfo[file].submodule || submodule;
        +    fileModuleInfo[file].for =
        +      fileModuleInfo[file].for || forEntry;
        +
        +    modules[module] = modules[module] || {
        +      name: module,
        +      submodules: {},
        +      classes: {}
        +    };
        +    if (submodule) {
        +      modules[module].submodules[submodule] = 1;
        +      submodules[submodule] = submodules[submodule] || {
        +        name: submodule,
        +        module,
        +        is_submodule: 1
        +      };
        +    }
        +  }
        +}
        +for (const key in modules) {
        +  converted.modules[key] = modules[key];
        +}
        +for (const key in submodules) {
        +  // Some modules also list themselves as  submodules as a default category
        +  // of sorts. Skip adding these submodules to not overwrite the module itself.
        +  if (converted.modules[key]) continue;
        +  converted.modules[key] = submodules[key];
        +}
        +
        +function getModuleInfo(entry) {
        +  const entryForTag = entry.tags.find(tag => tag.title === 'for');
        +  const entryForTagValue = entryForTag && entryForTag.description;
        +  const file = entry.context.file;
        +  let { module, submodule, for: forEntry } = fileModuleInfo[file] || {};
        +  let memberof = entry.memberof;
        +  if (memberof && memberof !== 'p5' && !memberof.startsWith('p5.')) {
        +    memberof = 'p5.' + memberof;
        +  }
        +  forEntry = memberof || entryForTagValue || forEntry;
        +  return { module, submodule, forEntry };
        +}
        +
        +function getParams(entry) {
        +  // Documentation.js seems to try to grab params from the function itself in
        +  // the code if we don't document all the parameters. This messes with our
        +  // manually-documented overloads. Instead of using the provided entry.params
        +  // array, we'll instead only rely on manually included @param tags.
        +  //
        +  // However, the tags don't include a tree-structured description field, and
        +  // instead convert it to a string. We want a slightly different conversion to
        +  // string, so we match these params to the Documentation.js-provided `params`
        +  // array and grab the description from those.
        +  return (entry.tags || []).filter(t => t.title === 'param').map(node => {
        +    const param = entry.params.find(param => param.name === node.name);
        +    if (param) {
        +      return {
        +        ...node,
        +        description: param.description
        +      };
        +    } else {
        +      return {
        +        ...node,
        +        description: {
        +          type: 'html',
        +          value: node.description
        +        }
        +      };
        +    }
        +  });
        +}
        +
        +// ============================================================================
        +// Constants
        +// ============================================================================
        +for (const entry of allData) {
        +  if (entry.kind === 'constant' || entry.kind === 'typedef') {
        +    constUsage[entry.name] = constUsage[entry.name] || new Set();
        +
        +    const { module, submodule, forEntry } = getModuleInfo(entry);
        +
        +    const examples = entry.examples.map(getExample);
        +    const item = {
        +      itemtype: 'property',
        +      name: entry.name,
        +      ...locationInfo(entry),
        +      ...typeObject(entry.type),
        +      description: descriptionString(entry.description),
        +      example: examples.length > 0 ? examples : undefined,
        +      alt: getAlt(entry),
        +      module,
        +      submodule,
        +      class: forEntry || 'p5'
        +    };
        +
        +    converted.classitems.push(item);
        +  }
        +}
        +
        +// ============================================================================
        +// Classes
        +// ============================================================================
        +for (const entry of allData) {
        +  if (entry.kind === 'class') {
        +    const { module, submodule } = getModuleInfo(entry);
        +
        +    const item = {
        +      name: entry.name,
        +      ...locationInfo(entry),
        +      extends: entry.augments && entry.augments[0] && entry.augments[0].name,
        +      description: descriptionString(entry.description),
        +      example: entry.examples.map(getExample),
        +      alt: getAlt(entry),
        +      params: getParams(entry).map(p => {
        +        return {
        +          name: p.name,
        +          description: p.description && descriptionString(p.description),
        +          ...typeObject(p.type)
        +        };
        +      }),
        +      return: entry.returns[0] && {
        +        description: descriptionString(entry.returns[0].description),
        +        ...typeObject(entry.returns[0].type)
        +      },
        +      is_constructor: 1,
        +      module,
        +      submodule
        +    };
        +
        +    // The @private tag doesn't seem to end up in the Documentation.js output.
        +    // However, it also doesn't seem to grab the description in this case, so
        +    // I'm using this as a proxy to let us know that a class should be private.
        +    // This means any public class *must* have a description.
        +    const isPrivate = !item.description;
        +    if (!isPrivate) {
        +      converted.classes[item.name] = item;
        +    }
        +  }
        +}
        +
        +// ============================================================================
        +// Class properties
        +// ============================================================================
        +const propDefs = {};
        +
        +// Grab properties out of the class nodes. These should have all the properties
        +// but very little of their metadata.
        +for (const entry of allData) {
        +  if (entry.kind !== 'class') continue;
        +
        +  // Ignore private classes
        +  if (!converted.classes[entry.name]) continue;
        +
        +  if (!entry.properties) continue;
        +
        +  const { module, submodule } = getModuleInfo(entry);
        +  const location = locationInfo(entry);
        +  propDefs[entry.name] = propDefs[entry.name] || {};
        +
        +  for (const property of entry.properties) {
        +    const item = {
        +      itemtype: 'property',
        +      name: property.name,
        +      ...location,
        +      line: property.lineNumber || location.line,
        +      ...typeObject(property.type),
        +      module,
        +      submodule,
        +      class: entry.name
        +    };
        +    propDefs[entry.name][property.name] = item;
        +  }
        +}
        +
        +// Grab property metadata out of other loose nodes.
        +for (const entry of allData) {
        +  // These are in a different section
        +  if (entry.kind === 'constant') continue;
        +
        +  const { module, submodule, forEntry } = getModuleInfo(entry);
        +  const propTag = entry.tags.find(tag => tag.title === 'property');
        +  const forTag = entry.tags.find(tag => tag.title === 'for');
        +  let memberof = entry.memberof;
        +  if (memberof && memberof !== 'p5' && !memberof.startsWith('p5.')) {
        +    memberof = 'p5.' + memberof;
        +  }
        +  if (!propTag || (!forEntry && !forTag && !memberof)) continue;
        +
        +  const forName = memberof || (forTag && forTag.description) || forEntry;
        +  propDefs[forName] = propDefs[forName] || {};
        +  const classEntry = propDefs[forName];
        +  if (!classEntry) continue;
        +
        +  registerConstantUsage(entry.type);
        +
        +  const prop = classEntry[propTag.name] || {
        +    itemtype: 'property',
        +    name: propTag.name,
        +    ...locationInfo(entry),
        +    ...typeObject(propTag.type),
        +    module,
        +    submodule,
        +    class: forName
        +  };
        +
        +  const updated = {
        +    ...prop,
        +    example: entry.examples.map(getExample),
        +    alt: getAlt(entry),
        +    description: descriptionString(entry.description)
        +  };
        +  classEntry[propTag.name] = updated;
        +}
        +
        +// Add to the list
        +for (const className in propDefs) {
        +  for (const propName in propDefs[className]) {
        +    converted.classitems.push(propDefs[className][propName]);
        +  }
        +}
        +
        +// ============================================================================
        +// Class methods
        +// ============================================================================
        +const classMethods = {};
        +for (const entry of allData) {
        +  if (entry.kind === 'function' && entry.properties.length === 0) {
        +    const { module, submodule, forEntry } = getModuleInfo(entry);
        +
        +    let memberof = entry.memberof;
        +    if (memberof && memberof !== 'p5' && !memberof.startsWith('p5.')) {
        +      memberof = 'p5.' + memberof;
        +    }
        +
        +    // Ignore functions that aren't methods
        +    if (entry.tags.some(tag => tag.title === 'function')) continue;
        +
        +    // If a previous version of this same method exists, then this is probably
        +    // an overload on that method
        +    const prevItem = (classMethods[memberof] || {})[entry.name] || {};
        +
        +    const className = memberof || prevItem.class || forEntry;
        +
        +    // Ignore methods of private classes
        +    if (!converted.classes[className]) continue;
        +
        +    // Ignore private methods. @private-tagged ones don't show up in the JSON,
        +    // but we also implicitly use this _-prefix convension.
        +    const isPrivate = entry.name.startsWith('_');
        +    if (isPrivate) continue;
        +
        +    for (const param of getParams(entry)) {
        +      registerConstantUsage(entry.name, className, param.type);
        +    }
        +    if (entry.returns[0]) {
        +      registerConstantUsage(entry.returns[0].type);
        +    }
        +
        +    const item = {
        +      name: entry.name,
        +      ...locationInfo(entry),
        +      itemtype: 'method',
        +      chainable: (prevItem.chainable || entry.tags.some(tag => tag.title === 'chainable'))
        +        ? 1
        +        : undefined,
        +      description: prevItem.description || descriptionString(entry.description),
        +      example: [
        +        ...(prevItem.example || []),
        +        ...entry.examples.map(getExample)
        +      ],
        +      alt: getAlt(entry),
        +      overloads: [
        +        ...(prevItem.overloads || []),
        +        {
        +          params: getParams(entry).map(p => {
        +            return {
        +              name: p.name,
        +              description: p.description && descriptionString(p.description),
        +              ...typeObject(p.type)
        +            };
        +          }),
        +          return: entry.returns[0] && {
        +            description: descriptionString(entry.returns[0].description),
        +            ...typeObject(entry.returns[0].type)
        +          }
        +        }
        +      ],
        +      return: prevItem.return || entry.returns[0] && {
        +        description: descriptionString(entry.returns[0].description),
        +        ...typeObject(entry.returns[0].type)
        +      },
        +      class: className,
        +      static: entry.scope === 'static' && 1,
        +      module,
        +      submodule
        +    };
        +
        +    classMethods[memberof] = classMethods[memberof] || {};
        +    classMethods[memberof][entry.name] = item;
        +  }
        +}
        +for (const className in classMethods) {
        +  for (const methodName in classMethods[className]) {
        +    converted.classitems.push(classMethods[className][methodName]);
        +  }
        +}
        +
        +// Done registering const usage, make a finished version
        +for (const key in constUsage) {
        +  converted.consts[key] = [...constUsage[key]];
        +}
        +
        +
        +// ============================================================================
        +// parameterData.json
        +// ============================================================================
        +
        +function cleanUpClassItems(data) {
        +  for (const classItem in data) {
        +    if (typeof data[classItem] === 'object') {
        +      if (data[classItem].overloads) {
        +        delete data[classItem].name;
        +        delete data[classItem].class;
        +      }
        +      cleanUpClassItems(data[classItem]);
        +    }
        +  }
        +
        +  // Reduce the amount of information in each function's overloads, while
        +  // keeping all the essential data available.
        +  const flattenOverloads = funcObj => {
        +    const result = {};
        +
        +    const processOverload = overload => {
        +      if (overload.params) {
        +        return Object.values(overload.params).map(param => processOptionalParam(param));
        +      }
        +      return overload;
        +    }
        +
        +    // To simplify `parameterData.json`, instead of having a separate field for
        +    // optional parameters, we'll add a ? to the end of parameter type to
        +    // indicate that it's optional.
        +    const processOptionalParam = param => {
        +      let type = param.type;
        +      if (param.optional) {
        +        type += '?';
        +      }
        +      return type;
        +    }
        +
        +    // In some cases, even when the arguments are intended to mean different
        +    // things, their types and order are identical. Since the exact meaning
        +    // of the arguments is less important for parameter validation, we'll
        +    // perform overload deduplication here.
        +    const removeDuplicateOverloads = (overload, uniqueOverloads) => {
        +      const overloadString = JSON.stringify(overload);
        +      if (uniqueOverloads.has(overloadString)) {
        +        return false;
        +      }
        +      uniqueOverloads.add(overloadString);
        +      return true;
        +    }
        +
        +    for (const [key, value] of Object.entries(funcObj)) {
        +      if (value && typeof value === 'object' && value.overloads) {
        +        const uniqueOverloads = new Set();
        +        result[key] = {
        +          overloads: Object.values(value.overloads)
        +            .map(overload => processOverload(overload))
        +            .filter(overload => removeDuplicateOverloads(overload, uniqueOverloads))
        +        };
        +      } else {
        +        result[key] = value;
        +      }
        +    }
        +
        +    return result;
        +  };
        +
        +  for (const classItem in data) {
        +    if (typeof data[classItem] === 'object') {
        +      data[classItem] = flattenOverloads(data[classItem]);
        +    }
        +  }
        +
        +  return data;
        +}
        +
        +function buildParamDocs(docs) {
        +  let newClassItems = {};
        +  // the fields we need—note that `name` and `class` are needed at this step because it's used to group classitems together. They will be removed later in cleanUpClassItems.
        +  let allowed = new Set(['name', 'class', 'params', 'overloads']);
        +
        +  for (let classitem of docs.classitems) {
        +    // If `classitem` doesn't have overloads, then it's not a function—skip processing in this case
        +    if (classitem.name && classitem.class && classitem.hasOwnProperty('overloads')) {
        +      // Skip if the item already exists in newClassItems
        +      if (newClassItems[classitem.class] && newClassItems[classitem.class][classitem.name]) {
        +        continue;
        +      }
        +
        +      // Clean up fields that will not be used in each classitem's overloads
        +      classitem.overloads?.forEach(overload => {
        +        delete overload.line;
        +        delete overload.return;
        +        overload.params.forEach(param => {
        +          delete param.description;
        +          delete param.name;
        +        });
        +      });
        +
        +      Object.keys(classitem).forEach(key => {
        +        if (!allowed.has(key)) delete classitem[key];
        +      });
        +
        +      newClassItems[classitem.class] = newClassItems[classitem.class] || {};
        +      newClassItems[classitem.class][classitem.name] = classitem;
        +    }
        +  }
        +
        +  const cleanedClassItems = cleanUpClassItems(newClassItems);
        +
        +  let out = fs.createWriteStream(
        +    path.join(__dirname, '../docs/parameterData.json'),
        +    {
        +      flags: 'w',
        +      mode: '0644'
        +    }
        +  );
        +  out.write(JSON.stringify(cleanedClassItems, null, 2));
        +  out.end();
        +}
        +
        +fs.mkdirSync(path.join(__dirname, '../docs/reference'), { recursive: true });
        +fs.writeFileSync(path.join(__dirname, '../docs/reference/data.json'), JSON.stringify(converted, null, 2));
        +fs.writeFileSync(path.join(__dirname, '../docs/reference/data.min.json'), JSON.stringify(converted));
        +buildParamDocs(JSON.parse(JSON.stringify(converted)));
        diff --git a/utils/sample-linter.js b/utils/sample-linter.mjs
        similarity index 94%
        rename from utils/sample-linter.js
        rename to utils/sample-linter.mjs
        index 4a39c99cab..08b4fb94e1 100644
        --- a/utils/sample-linter.js
        +++ b/utils/sample-linter.mjs
        @@ -1,7 +1,7 @@
         'use strict';
         const EOL = '\n';
         import { ESLint } from 'eslint';
        -import dataDoc from '../docs/reference/data.min.json';
        +import dataDoc from '../docs/reference/data.min.json' assert {type: 'json'};
         // envs: ['eslint-samples/p5'],
         
         const itemtypes = ['method', 'property'];
        @@ -21,7 +21,7 @@ Object.keys(dataDoc.consts).forEach(c => {
         });
         
         dataDoc.classitems
        -  .find(ci => ci.name === 'keyCode' && ci.class === 'p5')
        +  .find(ci => ci.name === 'keyCode')
           .description.match(/[A-Z\r\n, _]{10,}/m)[0]
           .match(/[A-Z_]+/gm)
           .forEach(c => {
        @@ -217,8 +217,6 @@ async function eslintFiles(opts, filesSrc) {
           };
         }
         
        -module.exports.eslintFiles = eslintFiles;
        -
         function splitLines(text) {
           const lines = [];
         
        @@ -248,10 +246,9 @@ function splitLines(text) {
           return lines;
         }
         
        -if (!module.parent) {
        -  eslintFiles(null, process.argv.slice(2))
        -    .then(result => {
        -      console.log(result.output);
        -      process.exit(result.report.errorCount === 0 ? 0 : 1);
        -    });
        -}
        +eslintFiles(null, process.argv.slice(2))
        +  .then(result => {
        +    if(result === true) process.exit(0);
        +    console.log(result.output);
        +    process.exit(result.report.errorCount === 0 ? 0 : 1);
        +  });
        diff --git a/vitest.workspace.mjs b/vitest.workspace.mjs
        new file mode 100644
        index 0000000000..14bac25ce4
        --- /dev/null
        +++ b/vitest.workspace.mjs
        @@ -0,0 +1,44 @@
        +import { defineWorkspace } from 'vitest/config';
        +import vitePluginString from 'vite-plugin-string';
        +
        +const plugins = [
        +  vitePluginString({
        +    include: [
        +      'src/webgl/shaders/**/*'
        +    ]
        +  })
        +];
        +
        +export default defineWorkspace([
        +  {
        +    plugins,
        +    publicDir: './test',
        +    bench: {
        +      name: 'bench',
        +      root: './',
        +      include: [
        +        './test/bench/**/*.js'
        +      ],
        +    },
        +    test: {
        +      name: 'unit',
        +      root: './',
        +      include: [
        +        './test/unit/**/*.js',
        +      ],
        +      exclude: [
        +        './test/unit/spec.js',
        +        './test/unit/assets/**/*',
        +        './test/unit/visual/visualTest.js',
        +      ],
        +      testTimeout: 1000,
        +      globals: true,
        +      browser: {
        +        enabled: true,
        +        name: 'chrome',
        +        provider: 'webdriverio',
        +        screenshotFailures: false
        +      }
        +    }
        +  }
        +]);
        \ No newline at end of file